Merge branch 'master' into portable-mode

Conflicts:
	src/browser/main.coffee
This commit is contained in:
Dave Rael
2015-10-06 14:28:10 -06:00
200 changed files with 4902 additions and 17171 deletions

View File

@@ -6,12 +6,11 @@ remote = require 'remote'
shell = require 'shell'
_ = require 'underscore-plus'
{deprecate, includeDeprecatedAPIs} = require 'grim'
{deprecate} = require 'grim'
{CompositeDisposable, Emitter} = require 'event-kit'
fs = require 'fs-plus'
{mapSourcePosition} = require 'source-map-support'
Model = require './model'
{$} = require './space-pen-extensions'
WindowEventHandler = require './window-event-handler'
StylesElement = require './styles-element'
StorageFolder = require './storage-folder'
@@ -33,38 +32,6 @@ class Atom extends Model
startTime = Date.now()
atom = @deserialize(@loadState(mode)) ? new this({mode, @version})
atom.deserializeTimings.atom = Date.now() - startTime
if includeDeprecatedAPIs
workspaceViewDeprecationMessage = """
atom.workspaceView is no longer available.
In most cases you will not need the view. See the Workspace docs for
alternatives: https://atom.io/docs/api/latest/Workspace.
If you do need the view, please use `atom.views.getView(atom.workspace)`,
which returns an HTMLElement.
"""
serviceHubDeprecationMessage = """
atom.services is no longer available. To register service providers and
consumers, use the `providedServices` and `consumedServices` fields in
your package's package.json.
"""
Object.defineProperty atom, 'workspaceView',
get: ->
deprecate(workspaceViewDeprecationMessage)
atom.__workspaceView
set: (newValue) ->
deprecate(workspaceViewDeprecationMessage)
atom.__workspaceView = newValue
Object.defineProperty atom, 'services',
get: ->
deprecate(serviceHubDeprecationMessage)
atom.packages.serviceHub
set: (newValue) ->
deprecate(serviceHubDeprecationMessage)
atom.packages.serviceHub = newValue
atom
# Deserializes the Atom environment from a state object
@@ -123,7 +90,7 @@ class Atom extends Model
@getCurrentWindow: ->
remote.getCurrentWindow()
workspaceViewParentSelector: 'body'
workspaceParentSelectorctor: 'body'
lastUncaughtError: null
###
@@ -213,7 +180,6 @@ class Atom extends Model
@openDevTools()
@executeJavaScriptInDevTools('DevToolsAPI.showConsole()')
@emit 'uncaught-error', arguments... if includeDeprecatedAPIs
@emitter.emit 'did-throw-error', {message, url, line, column, originalError}
@disposables?.dispose()
@@ -252,10 +218,6 @@ class Atom extends Model
@config = new Config({configDirPath, resourcePath})
@keymaps = new KeymapManager({configDirPath, resourcePath})
if includeDeprecatedAPIs
@keymap = @keymaps # Deprecated
@keymaps.subscribeToFileReadFailure()
@tooltips = new TooltipManager
@notifications = new NotificationManager
@@ -269,14 +231,7 @@ class Atom extends Model
@contextMenu = new ContextMenuManager({resourcePath, devMode})
@menu = new MenuManager({resourcePath})
@clipboard = new Clipboard()
@grammars = @deserializers.deserialize(@state.grammars ? @state.syntax) ? new GrammarRegistry()
if includeDeprecatedAPIs
Object.defineProperty this, 'syntax', get: ->
deprecate "The atom.syntax global is deprecated. Use atom.grammars instead."
@grammars
@disposables.add @packages.onDidActivateInitialPackages => @watchThemes()
Project = require './project'
@@ -484,7 +439,7 @@ class Atom extends Model
# Extended: Focus the current window.
focus: ->
ipc.send('call-window-method', 'focus')
$(window).focus()
window.focus()
# Extended: Show the current window.
show: ->
@@ -502,10 +457,6 @@ class Atom extends Model
isMaximized: ->
@getCurrentWindow().isMaximized()
isMaximixed: ->
deprecate "Use atom.isMaximized() instead"
@isMaximized()
maximize: ->
ipc.send('call-window-method', 'maximize')
@@ -622,9 +573,11 @@ class Atom extends Model
{safeMode} = @getLoadSettings()
CommandInstaller = require './command-installer'
CommandInstaller.installAtomCommand false, (error) ->
commandInstaller = new CommandInstaller(@getVersion())
commandInstaller.installAtomCommand false, (error) ->
console.warn error.message if error?
CommandInstaller.installApmCommand false, (error) ->
commandInstaller.installApmCommand false, (error) ->
console.warn error.message if error?
@loadConfig()
@@ -680,7 +633,6 @@ class Atom extends Model
# Essential: Visually and audibly trigger a beep.
beep: ->
shell.beep() if @config.get('core.audioBeep')
@__workspaceView?.trigger 'beep'
@emitter.emit 'did-beep'
# Essential: A flexible way to open a dialog akin to an alert dialog.
@@ -761,24 +713,16 @@ class Atom extends Model
@project ?= @deserializers.deserialize(@state.project) ? new Project()
@deserializeTimings.project = Date.now() - startTime
deserializeWorkspaceView: ->
deserializeWorkspace: ->
Workspace = require './workspace'
if includeDeprecatedAPIs
WorkspaceView = require './workspace-view'
startTime = Date.now()
@workspace = Workspace.deserialize(@state.workspace) ? new Workspace
workspaceElement = @views.getView(@workspace)
if includeDeprecatedAPIs
@__workspaceView = workspaceElement.__spacePenView
@deserializeTimings.workspace = Date.now() - startTime
workspaceElement = @views.getView(@workspace)
@keymaps.defaultTarget = workspaceElement
document.querySelector(@workspaceViewParentSelector).appendChild(workspaceElement)
document.querySelector(@workspaceParentSelectorctor).appendChild(workspaceElement)
deserializePackageStates: ->
@packages.packageStates = @state.packageStates ? {}
@@ -787,7 +731,7 @@ class Atom extends Model
deserializeEditorWindow: ->
@deserializePackageStates()
@deserializeProject()
@deserializeWorkspaceView()
@deserializeWorkspace()
loadConfig: ->
@config.setSchema null, {type: 'object', properties: _.clone(require('./config-schema'))}
@@ -897,11 +841,7 @@ class Atom extends Model
ipc.send('call-window-method', 'setAutoHideMenuBar', autoHide)
ipc.send('call-window-method', 'setMenuBarVisibility', not autoHide)
if includeDeprecatedAPIs
# Deprecated: Callers should be converted to use atom.deserializers
Atom::registerRepresentationClass = ->
deprecate("Callers should be converted to use atom.deserializers")
# Deprecated: Callers should be converted to use atom.deserializers
Atom::registerRepresentationClasses = ->
deprecate("Callers should be converted to use atom.deserializers")
# Preserve this deprecation until 2.0. Sorry. Should have removed Q sooner.
Promise.prototype.done = (callback) ->
deprecate("Atom now uses ES6 Promises instead of Q. Call promise.then instead of promise.done")
@then(callback)

View File

@@ -103,6 +103,8 @@ class ApplicationMenu
downloadingUpdateItem.visible = false
installUpdateItem.visible = false
return if @autoUpdateManager.isDisabled()
switch state
when 'idle', 'error', 'no-update-available'
checkForUpdateItem.visible = true
@@ -117,10 +119,9 @@ 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()}
@@ -128,6 +129,10 @@ 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

@@ -72,7 +72,8 @@ class AtomApplication
@pidsToOpenWindows = {}
@windows = []
@autoUpdateManager = new AutoUpdateManager(@version, options.test)
disableAutoUpdate = require(path.join(@resourcePath, 'package.json'))._disableAutoUpdate ? false
@autoUpdateManager = new AutoUpdateManager(@version, options.test, disableAutoUpdate)
@applicationMenu = new ApplicationMenu(@version, @autoUpdateManager)
@atomProtocolHandler = new AtomProtocolHandler(@resourcePath, @safeMode)
@@ -162,7 +163,6 @@ class AtomApplication
safeMode: @focusedWindow()?.safeMode
@on 'application:run-all-specs', -> @runSpecs(exitWhenDone: false, resourcePath: @devResourcePath, safeMode: @focusedWindow()?.safeMode)
@on 'application:run-benchmarks', -> @runBenchmarks()
@on 'application:quit', -> app.quit()
@on 'application:new-window', -> @openPath(_.extend(windowDimensions: @focusedWindow()?.getDimensions(), getLoadSettings()))
@on 'application:new-file', -> (@focusedWindow() ? this).openPath()
@@ -505,18 +505,6 @@ class AtomApplication
safeMode ?= false
new AtomWindow({bootstrapScript, resourcePath, exitWhenDone, isSpec, devMode, specDirectory, logFile, safeMode})
runBenchmarks: ({exitWhenDone, specDirectory}={}) ->
try
bootstrapScript = require.resolve(path.resolve(@devResourcePath, 'benchmark', 'benchmark-bootstrap'))
catch error
bootstrapScript = require.resolve(path.resolve(__dirname, '..', '..', 'benchmark', 'benchmark-bootstrap'))
specDirectory ?= path.dirname(bootstrapScript)
isSpec = true
devMode = true
new AtomWindow({bootstrapScript, @resourcePath, exitWhenDone, isSpec, specDirectory, devMode})
locationForPathToOpen: (pathToOpen, executedFrom='') ->
return {pathToOpen} unless pathToOpen

View File

@@ -15,7 +15,7 @@ module.exports =
class AutoUpdateManager
_.extend @prototype, EventEmitter.prototype
constructor: (@version, @testMode) ->
constructor: (@version, @testMode, @disabled) ->
@state = IdleState
if process.platform is 'win32'
# Squirrel for Windows can't handle query params
@@ -52,8 +52,9 @@ class AutoUpdateManager
@setState(UpdateAvailableState)
@emitUpdateAvailableEvent(@getWindows()...)
# Only released versions should check for updates.
@scheduleUpdateCheck() unless /\w{7}/.test(@version)
# Only check for updates periodically if enabled and running in release
# version.
@scheduleUpdateCheck() unless /\w{7}/.test(@version) or @disabled
switch process.platform
when 'win32'
@@ -61,6 +62,9 @@ class AutoUpdateManager
when 'linux'
@setState(UnsupportedState)
isDisabled: ->
@disabled
emitUpdateAvailableEvent: (windows...) ->
return unless @releaseVersion?
for atomWindow in windows

View File

@@ -1,5 +1,9 @@
global.shellStartTime = Date.now()
process.on 'uncaughtException', (error={}) ->
console.log(error.message) if error.message?
console.log(error.stack) if error.stack?
crashReporter = require 'crash-reporter'
app = require 'app'
fs = require 'fs-plus'
@@ -10,11 +14,13 @@ console.log = require 'nslog'
start = ->
args = parseCommandLine()
setupUncaughtExceptionHandler()
setupAtomHome(args)
setupCompileCache()
return if handleStartupEventWithSquirrel()
# NB: This prevents Win10 from showing dupe items in the taskbar
app.setAppUserModelId('com.squirrel.atom.atom')
addPathToOpen = (event, pathToOpen) ->
event.preventDefault()
args.pathsToOpen.push(pathToOpen)
@@ -42,11 +48,6 @@ normalizeDriveLetterName = (filePath) ->
else
filePath
setupUncaughtExceptionHandler = ->
process.on 'uncaughtException', (error={}) ->
console.log(error.message) if error.message?
console.log(error.stack) if error.stack?
handleStartupEventWithSquirrel = ->
return false unless process.platform is 'win32'
SquirrelUpdate = require './squirrel-update'

View File

@@ -67,9 +67,9 @@ installContextMenu = (callback) ->
installMenu = (keyPath, arg, callback) ->
args = [keyPath, '/ve', '/d', 'Open with Atom']
addToRegistry args, ->
args = [keyPath, '/v', 'Icon', '/d', process.execPath]
args = [keyPath, '/v', 'Icon', '/d', "\"#{process.execPath}\""]
addToRegistry args, ->
args = ["#{keyPath}\\command", '/ve', '/d', "#{process.execPath} \"#{arg}\""]
args = ["#{keyPath}\\command", '/ve', '/d', "\"#{process.execPath}\" \"#{arg}\""]
addToRegistry(args, callback)
installMenu fileKeyPath, '%1', ->

View File

@@ -25,9 +25,15 @@ symlinkCommandWithPrivilegeSync = (sourcePath, destinationPath) ->
throw new Error("Failed to symlink '#{sourcePath}' to '#{destinationPath}'")
module.exports =
class CommandInstaller
constructor: (@appVersion) ->
getInstallDirectory: ->
"/usr/local/bin"
getResourcesDirectory: ->
process.resourcesPath
installShellCommandsInteractively: ->
showErrorDialog = (error) ->
atom.confirm
@@ -47,17 +53,26 @@ module.exports =
detailedMessage: "The shell commands `atom` and `apm` are installed."
installAtomCommand: (askForPrivilege, callback) ->
commandPath = path.join(process.resourcesPath, 'app', 'atom.sh')
@createSymlink commandPath, askForPrivilege, callback
programName = if @appVersion.includes("beta")
"atom-beta"
else
"atom"
commandPath = path.join(@getResourcesDirectory(), 'app', 'atom.sh')
@createSymlink commandPath, programName, askForPrivilege, callback
installApmCommand: (askForPrivilege, callback) ->
commandPath = path.join(process.resourcesPath, 'app', 'apm', 'node_modules', '.bin', 'apm')
@createSymlink commandPath, askForPrivilege, callback
programName = if @appVersion.includes("beta")
"apm-beta"
else
"apm"
createSymlink: (commandPath, askForPrivilege, callback) ->
commandPath = path.join(@getResourcesDirectory(), 'app', 'apm', 'node_modules', '.bin', 'apm')
@createSymlink commandPath, programName, askForPrivilege, callback
createSymlink: (commandPath, commandName, askForPrivilege, callback) ->
return unless process.platform is 'darwin'
commandName = path.basename(commandPath, path.extname(commandPath))
destinationPath = path.join(@getInstallDirectory(), commandName)
fs.readlink destinationPath, (error, realpath) ->
@@ -70,6 +85,7 @@ module.exports =
try
error = null
symlinkCommandWithPrivilegeSync(commandPath, destinationPath)
catch error
catch err
error = err
callback?(error)

View File

@@ -1,7 +1,6 @@
{Emitter, Disposable, CompositeDisposable} = require 'event-kit'
{calculateSpecificity, validateSelector} = require 'clear-cut'
_ = require 'underscore-plus'
{$} = require './space-pen-extensions'
SequenceCount = 0
@@ -138,8 +137,6 @@ class CommandRegistry
# * `name` The name of the command. For example, `user:insert-date`.
# * `displayName` The display name of the command. For example,
# `User: Insert Date`.
# * `jQuery` Present if the command was registered with the legacy
# `$::command` method.
findCommands: ({target}) ->
commandNames = new Set
commands = []
@@ -175,12 +172,8 @@ class CommandRegistry
# * `commandName` {String} indicating the name of the command to dispatch.
dispatch: (target, commandName, detail) ->
event = new CustomEvent(commandName, {bubbles: true, detail})
eventWithTarget = Object.create event,
target: value: target
preventDefault: value: ->
stopPropagation: value: ->
stopImmediatePropagation: value: ->
@handleCommandEvent(eventWithTarget)
Object.defineProperty(event, 'target', value: target)
@handleCommandEvent(event)
# Public: Invoke the given callback before dispatching a command event.
#
@@ -208,34 +201,39 @@ class CommandRegistry
@selectorBasedListenersByCommandName[commandName] = listeners.slice()
return
handleCommandEvent: (originalEvent) =>
handleCommandEvent: (event) =>
propagationStopped = false
immediatePropagationStopped = false
matched = false
currentTarget = originalEvent.target
currentTarget = event.target
{preventDefault, stopPropagation, stopImmediatePropagation, abortKeyBinding} = event
syntheticEvent = Object.create originalEvent,
eventPhase: value: Event.BUBBLING_PHASE
currentTarget: get: -> currentTarget
preventDefault: value: ->
originalEvent.preventDefault()
stopPropagation: value: ->
originalEvent.stopPropagation()
propagationStopped = true
stopImmediatePropagation: value: ->
originalEvent.stopImmediatePropagation()
propagationStopped = true
immediatePropagationStopped = true
abortKeyBinding: value: ->
originalEvent.abortKeyBinding?()
dispatchedEvent = new CustomEvent(event.type, {bubbles: true, detail: event.detail})
Object.defineProperty dispatchedEvent, 'eventPhase', value: Event.BUBBLING_PHASE
Object.defineProperty dispatchedEvent, 'currentTarget', get: -> currentTarget
Object.defineProperty dispatchedEvent, 'target', value: currentTarget
Object.defineProperty dispatchedEvent, 'preventDefault', value: ->
event.preventDefault()
Object.defineProperty dispatchedEvent, 'stopPropagation', value: ->
event.stopPropagation()
propagationStopped = true
Object.defineProperty dispatchedEvent, 'stopImmediatePropagation', value: ->
event.stopImmediatePropagation()
propagationStopped = true
immediatePropagationStopped = true
Object.defineProperty dispatchedEvent, 'abortKeyBinding', value: ->
event.abortKeyBinding?()
@emitter.emit 'will-dispatch', syntheticEvent
for key in Object.keys(event)
dispatchedEvent[key] = event[key]
@emitter.emit 'will-dispatch', dispatchedEvent
loop
listeners = @inlineListenersByCommandName[originalEvent.type]?.get(currentTarget) ? []
listeners = @inlineListenersByCommandName[event.type]?.get(currentTarget) ? []
if currentTarget.webkitMatchesSelector?
selectorBasedListeners =
(@selectorBasedListenersByCommandName[originalEvent.type] ? [])
(@selectorBasedListenersByCommandName[event.type] ? [])
.filter (listener) -> currentTarget.webkitMatchesSelector(listener.selector)
.sort (a, b) -> a.compare(b)
listeners = listeners.concat(selectorBasedListeners)
@@ -244,13 +242,13 @@ class CommandRegistry
for listener in listeners
break if immediatePropagationStopped
listener.callback.call(currentTarget, syntheticEvent)
listener.callback.call(currentTarget, dispatchedEvent)
break if currentTarget is window
break if propagationStopped
currentTarget = currentTarget.parentNode ? window
@emitter.emit 'did-dispatch', syntheticEvent
@emitter.emit 'did-dispatch', dispatchedEvent
matched

View File

@@ -5,7 +5,7 @@ CSON = require 'season'
path = require 'path'
async = require 'async'
pathWatcher = require 'pathwatcher'
Grim = require 'grim'
{pushKeyPath, splitKeyPath, getValueAtKeyPath, setValueAtKeyPath} = require 'key-path-helpers'
Color = require './color'
ScopedPropertyStore = require 'scoped-property-store'
@@ -387,21 +387,9 @@ class Config
observe: ->
if arguments.length is 2
[keyPath, callback] = arguments
else if Grim.includeDeprecatedAPIs and arguments.length is 3 and (_.isArray(arguments[0]) or arguments[0] instanceof ScopeDescriptor)
Grim.deprecate """
Passing a scope descriptor as the first argument to Config::observe is deprecated.
Pass a `scope` in an options hash as the third argument instead.
"""
[scopeDescriptor, keyPath, callback] = arguments
else if arguments.length is 3 and (_.isString(arguments[0]) and _.isObject(arguments[1]))
[keyPath, options, callback] = arguments
scopeDescriptor = options.scope
if Grim.includeDeprecatedAPIs and options.callNow?
Grim.deprecate """
Config::observe no longer takes a `callNow` option. Use ::onDidChange instead.
Note that ::onDidChange passes its callback different arguments.
See https://atom.io/docs/api/latest/Config
"""
else
console.error 'An unsupported form of Config::observe is being used. See https://atom.io/docs/api/latest/Config for details'
return
@@ -435,12 +423,6 @@ class Config
[callback] = arguments
else if arguments.length is 2
[keyPath, callback] = arguments
else if Grim.includeDeprecatedAPIs and _.isArray(arguments[0]) or arguments[0] instanceof ScopeDescriptor
Grim.deprecate """
Passing a scope descriptor as the first argument to Config::onDidChange is deprecated.
Pass a `scope` in an options hash as the third argument instead.
"""
[scopeDescriptor, keyPath, callback] = arguments
else
[keyPath, options, callback] = arguments
scopeDescriptor = options.scope
@@ -514,12 +496,6 @@ class Config
if typeof arguments[0] is 'string' or not arguments[0]?
[keyPath, options] = arguments
{scope} = options
else if Grim.includeDeprecatedAPIs
Grim.deprecate """
Passing a scope descriptor as the first argument to Config::get is deprecated.
Pass a `scope` in an options hash as the final argument instead.
"""
[scope, keyPath] = arguments
else
[keyPath] = arguments
@@ -594,18 +570,10 @@ class Config
# * `true` if the value was set.
# * `false` if the value was not able to be coerced to the type specified in the setting's schema.
set: ->
if Grim.includeDeprecatedAPIs and arguments[0]?[0] is '.'
Grim.deprecate """
Passing a scope selector as the first argument to Config::set is deprecated.
Pass a `scopeSelector` in an options hash as the final argument instead.
"""
[scopeSelector, keyPath, value] = arguments
shouldSave = true
else
[keyPath, value, options] = arguments
scopeSelector = options?.scopeSelector
source = options?.source
shouldSave = options?.save ? true
[keyPath, value, options] = arguments
scopeSelector = options?.scopeSelector
source = options?.source
shouldSave = options?.save ? true
if source and not scopeSelector
throw new Error("::set with a 'source' and no 'sourceSelector' is not yet implemented!")
@@ -633,24 +601,15 @@ class Config
# * `scopeSelector` (optional) {String}. See {::set}
# * `source` (optional) {String}. See {::set}
unset: (keyPath, options) ->
if Grim.includeDeprecatedAPIs and typeof options is 'string'
Grim.deprecate """
Passing a scope selector as the first argument to Config::unset is deprecated.
Pass a `scopeSelector` in an options hash as the second argument instead.
"""
scopeSelector = keyPath
keyPath = options
else
{scopeSelector, source} = options ? {}
{scopeSelector, source} = options ? {}
source ?= @getUserConfigPath()
if scopeSelector?
if keyPath?
settings = @scopedSettingsStore.propertiesForSourceAndSelector(source, scopeSelector)
if _.valueForKeyPath(settings, keyPath)?
if getValueAtKeyPath(settings, keyPath)?
@scopedSettingsStore.removePropertiesForSourceAndSelector(source, scopeSelector)
_.setValueForKeyPath(settings, keyPath, undefined)
setValueAtKeyPath(settings, keyPath, undefined)
settings = withoutEmptyObjects(settings)
@set(null, settings, {scopeSelector, source, priority: @priorityForSource(source)}) if settings?
@requestSave()
@@ -661,7 +620,7 @@ class Config
for scopeSelector of @scopedSettingsStore.propertiesForSource(source)
@unset(keyPath, {scopeSelector, source})
if keyPath? and source is @getUserConfigPath()
@set(keyPath, _.valueForKeyPath(@defaultSettings, keyPath))
@set(keyPath, getValueAtKeyPath(@defaultSettings, keyPath))
# Extended: Get an {Array} of all of the `source` {String}s with which
# settings have been added via {::set}.
@@ -859,9 +818,9 @@ class Config
getRawValue: (keyPath, options) ->
unless options?.excludeSources?.indexOf(@getUserConfigPath()) >= 0
value = _.valueForKeyPath(@settings, keyPath)
value = getValueAtKeyPath(@settings, keyPath)
unless options?.sources?.length > 0
defaultValue = _.valueForKeyPath(@defaultSettings, keyPath)
defaultValue = getValueAtKeyPath(@defaultSettings, keyPath)
if value?
value = @deepClone(value)
@@ -872,11 +831,11 @@ class Config
value
setRawValue: (keyPath, value) ->
defaultValue = _.valueForKeyPath(@defaultSettings, keyPath)
defaultValue = getValueAtKeyPath(@defaultSettings, keyPath)
value = undefined if _.isEqual(defaultValue, value)
if keyPath?
_.setValueForKeyPath(@settings, keyPath, value)
setValueAtKeyPath(@settings, keyPath, value)
else
@settings = value
@emitChangeEvent()
@@ -901,7 +860,7 @@ class Config
_.isEqual(pathTokens, pathSubTokens)
setRawDefault: (keyPath, value) ->
_.setValueForKeyPath(@defaultSettings, keyPath, value)
setValueAtKeyPath(@defaultSettings, keyPath, value)
@emitChangeEvent()
setDefaults: (keyPath, defaults) ->
@@ -956,7 +915,7 @@ class Config
for scope, scopeSchema of schema.scopes
continue unless scopeSchema.hasOwnProperty('default')
scopedDefaults[scope] = {}
_.setValueForKeyPath(scopedDefaults[scope], keyPath, scopeSchema.default)
setValueAtKeyPath(scopedDefaults[scope], keyPath, scopeSchema.default)
@scopedSettingsStore.addProperties('schema-default', scopedDefaults)
if schema.type is 'object' and schema.properties? and isPlainObject(schema.properties)
@@ -1029,7 +988,7 @@ class Config
setRawScopedValue: (keyPath, value, source, selector, options) ->
if keyPath?
newValue = {}
_.setValueForKeyPath(newValue, keyPath, value)
setValueAtKeyPath(newValue, keyPath, value)
value = newValue
settingsBySelector = {}
@@ -1129,7 +1088,7 @@ Config.addSchemaEnforcers
childSchema = schema.properties[prop] ? defaultChildSchema
if childSchema?
try
newValue[prop] = @executeSchemaEnforcers("#{keyPath}.#{prop}", propValue, childSchema)
newValue[prop] = @executeSchemaEnforcers(pushKeyPath(keyPath, prop), propValue, childSchema)
catch error
console.warn "Error setting item in object: #{error.message}"
else if allowsAdditionalProperties
@@ -1184,17 +1143,6 @@ Config.addSchemaEnforcers
isPlainObject = (value) ->
_.isObject(value) and not _.isArray(value) and not _.isFunction(value) and not _.isString(value) and not (value instanceof Color)
splitKeyPath = (keyPath) ->
return [] unless keyPath?
startIndex = 0
keyPathArray = []
for char, i in keyPath
if char is '.' and (i is 0 or keyPath[i-1] isnt '\\')
keyPathArray.push keyPath.substring(startIndex, i)
startIndex = i + 1
keyPathArray.push keyPath.substr(startIndex, keyPath.length)
keyPathArray
withoutEmptyObjects = (object) ->
resultObject = undefined
if isPlainObject(object)
@@ -1206,71 +1154,3 @@ withoutEmptyObjects = (object) ->
else
resultObject = object
resultObject
# TODO remove in 1.0 API
Config::unobserve = (keyPath) ->
Grim.deprecate 'Config::unobserve no longer does anything. Call `.dispose()` on the object returned by Config::observe instead.'
if Grim.includeDeprecatedAPIs
EmitterMixin = require('emissary').Emitter
EmitterMixin.includeInto(Config)
Config::restoreDefault = (scopeSelector, keyPath) ->
Grim.deprecate("Use ::unset instead.")
@unset(scopeSelector, keyPath)
@get(keyPath)
Config::getDefault = ->
Grim.deprecate("Use `::get(keyPath, {scope, excludeSources: [atom.config.getUserConfigPath()]})` instead")
if arguments.length is 1
[keyPath] = arguments
else
[scopeSelector, keyPath] = arguments
scope = [scopeSelector]
@get(keyPath, {scope, excludeSources: [@getUserConfigPath()]})
Config::isDefault = ->
Grim.deprecate("Use `not ::get(keyPath, {scope, sources: [atom.config.getUserConfigPath()]})?` instead")
if arguments.length is 1
[keyPath] = arguments
else
[scopeSelector, keyPath] = arguments
scope = [scopeSelector]
not @get(keyPath, {scope, sources: [@getUserConfigPath()]})?
Config::getSettings = ->
Grim.deprecate "Use ::get(keyPath) instead"
_.deepExtend({}, @settings, @defaultSettings)
Config::getInt = (keyPath) ->
Grim.deprecate '''Config::getInt is no longer necessary. Use ::get instead.
Make sure the config option you are accessing has specified an `integer`
schema. See the schema section of
https://atom.io/docs/api/latest/Config for more info.'''
parseInt(@get(keyPath))
Config::getPositiveInt = (keyPath, defaultValue=0) ->
Grim.deprecate '''Config::getPositiveInt is no longer necessary. Use ::get instead.
Make sure the config option you are accessing has specified an `integer`
schema with `minimum: 1`. See the schema section of
https://atom.io/docs/api/latest/Config for more info.'''
Math.max(@getInt(keyPath), 0) or defaultValue
Config::toggle = (keyPath) ->
Grim.deprecate 'Config::toggle is no longer supported. Please remove from your code.'
@set(keyPath, not @get(keyPath))
Config::addScopedSettings = (source, selector, value, options) ->
Grim.deprecate("Use ::set instead")
settingsBySelector = {}
settingsBySelector[selector] = value
disposable = @scopedSettingsStore.addProperties(source, settingsBySelector, options)
@emitChangeEvent()
new Disposable =>
disposable.dispose()
@emitChangeEvent()
Config::settingsForScopeDescriptor = (scopeDescriptor, keyPath) ->
Grim.deprecate("Use Config::getAll instead")
entries = @getAll(null, scope: scopeDescriptor)
value for {value} in entries when _.valueForKeyPath(value, keyPath)?

View File

@@ -4,7 +4,6 @@ CSON = require 'season'
fs = require 'fs-plus'
{calculateSpecificity, validateSelector} = require 'clear-cut'
{Disposable} = require 'event-kit'
Grim = require 'grim'
MenuHelpers = require './menu-helpers'
platformContextMenu = require('../package.json')?._atomMenu?['context-menu']
@@ -83,25 +82,25 @@ class ContextMenuManager
#
# * `itemsBySelector` An {Object} whose keys are CSS selectors and whose
# values are {Array}s of item {Object}s containing the following keys:
# * `label` (Optional) A {String} containing the menu item's label.
# * `command` (Optional) A {String} containing the command to invoke on the
# * `label` (optional) A {String} containing the menu item's label.
# * `command` (optional) A {String} containing the command to invoke on the
# target of the right click that invoked the context menu.
# * `enabled` (Optional) A {Boolean} indicating whether the menu item
# * `enabled` (optional) A {Boolean} indicating whether the menu item
# should be clickable. Disabled menu items typically appear grayed out.
# Defaults to `true`.
# * `submenu` (Optional) An {Array} of additional items.
# * `type` (Optional) If you want to create a separator, provide an item
# * `submenu` (optional) An {Array} of additional items.
# * `type` (optional) If you want to create a separator, provide an item
# with `type: 'separator'` and no other keys.
# * `visible` (Optional) A {Boolean} indicating whether the menu item
# * `visible` (optional) A {Boolean} indicating whether the menu item
# should appear in the menu. Defaults to `true`.
# * `created` (Optional) A {Function} that is called on the item each time a
# * `created` (optional) A {Function} that is called on the item each time a
# context menu is created via a right click. You can assign properties to
# `this` to dynamically compute the command, label, etc. This method is
# actually called on a clone of the original item template to prevent state
# from leaking across context menu deployments. Called with the following
# argument:
# * `event` The click event that deployed the context menu.
# * `shouldDisplay` (Optional) A {Function} that is called to determine
# * `shouldDisplay` (optional) A {Function} that is called to determine
# whether to display this item on a given context menu deployment. Called
# with the following argument:
# * `event` The click event that deployed the context menu.
@@ -109,27 +108,6 @@ class ContextMenuManager
# Returns a {Disposable} on which `.dispose()` can be called to remove the
# added menu items.
add: (itemsBySelector) ->
if Grim.includeDeprecatedAPIs
# Detect deprecated file path as first argument
if itemsBySelector? and typeof itemsBySelector isnt 'object'
Grim.deprecate """
`ContextMenuManager::add` has changed to take a single object as its
argument. Please see
https://atom.io/docs/api/latest/ContextMenuManager#context-menu-cson-format for more info.
"""
itemsBySelector = arguments[1]
devMode = arguments[2]?.devMode
# Detect deprecated format for items object
for key, value of itemsBySelector
unless _.isArray(value)
Grim.deprecate """
`ContextMenuManager::add` has changed to take a single object as its
argument. Please see
https://atom.io/docs/api/latest/ContextMenuManager#context-menu-cson-format for more info.
"""
itemsBySelector = @convertLegacyItemsBySelector(itemsBySelector, devMode)
addedItemSets = []
for selector, items of itemsBySelector

View File

@@ -1,7 +1,6 @@
{Point, Range} = require 'text-buffer'
{Emitter} = require 'event-kit'
_ = require 'underscore-plus'
Grim = require 'grim'
Model = require './model'
# Extended: The `Cursor` class represents the little blinking line identifying
@@ -62,18 +61,6 @@ class Cursor extends Model
onDidChangeVisibility: (callback) ->
@emitter.on 'did-change-visibility', callback
on: (eventName) ->
return unless Grim.includeDeprecatedAPIs
switch eventName
when 'moved'
Grim.deprecate("Use Cursor::onDidChangePosition instead")
when 'destroyed'
Grim.deprecate("Use Cursor::onDidDestroy instead")
else
Grim.deprecate("::on is no longer supported. Use the event subscription methods instead")
super
###
Section: Managing Cursor Position
###
@@ -578,7 +565,6 @@ class Cursor extends Model
setVisible: (visible) ->
if @visible isnt visible
@visible = visible
@emit 'visibility-changed', @visible if Grim.includeDeprecatedAPIs
@emitter.emit 'did-change-visibility', @visible
# Public: Returns the visibility of the cursor.
@@ -698,12 +684,3 @@ class Cursor extends Model
position = range.start
stop()
position
if Grim.includeDeprecatedAPIs
Cursor::getScopes = ->
Grim.deprecate 'Use Cursor::getScopeDescriptor() instead'
@getScopeDescriptor().getScopesArray()
Cursor::getMoveNextWordBoundaryBufferPosition = (options) ->
Grim.deprecate 'Use `::getNextWordBoundaryBufferPosition(options)` instead'
@getNextWordBoundaryBufferPosition(options)

View File

@@ -98,7 +98,6 @@ class CustomGutterComponent
delete @decorationItemsById[decorationId]
if newItem
# `item` should be either an HTMLElement or a space-pen View.
newItemNode = null
if newItem instanceof HTMLElement
newItemNode = newItem

View File

@@ -1,6 +1,5 @@
_ = require 'underscore-plus'
{Emitter} = require 'event-kit'
Grim = require 'grim'
idCounter = 0
nextId = -> idCounter++
@@ -82,7 +81,6 @@ class Decoration
@markerDestroyDisposable.dispose()
@markerDestroyDisposable = null
@destroyed = true
@emit 'destroyed' if Grim.includeDeprecatedAPIs
@emitter.emit 'did-destroy'
@emitter.dispose()
@@ -155,7 +153,6 @@ class Decoration
@properties.id = @id
if newProperties.type?
@displayBuffer.decorationDidChangeType(this)
@emit 'updated', {oldParams: oldProperties, newParams: newProperties} if Grim.includeDeprecatedAPIs
@emitter.emit 'did-change-properties', {oldProperties, newProperties}
###
@@ -175,34 +172,8 @@ class Decoration
flashObject = {class: klass, duration}
@flashQueue ?= []
@flashQueue.push(flashObject)
@emit 'flash' if Grim.includeDeprecatedAPIs
@emitter.emit 'did-flash'
consumeNextFlash: ->
return @flashQueue.shift() if @flashQueue?.length > 0
null
if Grim.includeDeprecatedAPIs
EmitterMixin = require('emissary').Emitter
EmitterMixin.includeInto(Decoration)
Decoration::on = (eventName) ->
switch eventName
when 'updated'
Grim.deprecate 'Use Decoration::onDidChangeProperties instead'
when 'destroyed'
Grim.deprecate 'Use Decoration::onDidDestroy instead'
when 'flash'
Grim.deprecate 'Use Decoration::onDidFlash instead'
else
Grim.deprecate 'Decoration::on is deprecated. Use event subscription methods instead.'
EmitterMixin::on.apply(this, arguments)
Decoration::getParams = ->
Grim.deprecate 'Use Decoration::getProperties instead'
@getProperties()
Decoration::update = (newProperties) ->
Grim.deprecate 'Use Decoration::setProperties instead'
@setProperties(newProperties)

41
src/delegated-listener.js Normal file
View File

@@ -0,0 +1,41 @@
const EventKit = require('event-kit')
module.exports =
function listen (element, eventName, selector, handler) {
var innerHandler = function (event) {
if (selector) {
var currentTarget = event.target
while (currentTarget) {
if (currentTarget.matches && currentTarget.matches(selector)) {
handler({
type: event.type,
currentTarget: currentTarget,
target: event.target,
preventDefault: function () {
event.preventDefault()
},
originalEvent: event
})
}
if (currentTarget === element) break
currentTarget = currentTarget.parentNode
}
} else {
handler({
type: event.type,
currentTarget: event.currentTarget,
target: event.target,
preventDefault: function () {
event.preventDefault()
},
originalEvent: event
})
}
}
element.addEventListener(eventName, innerHandler)
return new EventKit.Disposable(function () {
element.removeEventListener(eventName, innerHandler)
})
}

View File

@@ -1,5 +1,4 @@
{Disposable} = require 'event-kit'
Grim = require 'grim'
# Extended: Manages the deserializers used for serialized state
#
@@ -60,9 +59,3 @@ class DeserializerManager
name = state.get?('deserializer') ? state.deserializer
@deserializers[name]
if Grim.includeDeprecatedAPIs
DeserializerManager::remove = (classes...) ->
Grim.deprecate("Call .dispose() on the Disposable return from ::add instead")
delete @deserializers[name] for {name} in classes
return

View File

@@ -1,8 +1,6 @@
_ = require 'underscore-plus'
Serializable = require 'serializable'
{CompositeDisposable, Emitter} = require 'event-kit'
{Point, Range} = require 'text-buffer'
Grim = require 'grim'
TokenizedBuffer = require './tokenized-buffer'
RowMap = require './row-map'
Fold = require './fold'
@@ -18,12 +16,20 @@ class BufferToScreenConversionError extends Error
module.exports =
class DisplayBuffer extends Model
Serializable.includeInto(this)
verticalScrollMargin: 2
horizontalScrollMargin: 6
scopedCharacterWidthsChangeCount: 0
changeCount: 0
softWrapped: null
editorWidthInChars: null
lineHeightInPixels: null
defaultCharWidth: null
height: null
width: null
@deserialize: (state) ->
state.tokenizedBuffer = TokenizedBuffer.deserialize(state.tokenizedBuffer)
new this(state)
constructor: ({tabLength, @editorWidthInChars, @tokenizedBuffer, buffer, ignoreInvisibles, @largeFileMode}={}) ->
super
@@ -83,23 +89,16 @@ class DisplayBuffer extends Model
@updateWrappedScreenLines() if oldConfigSettings? and not _.isEqual(oldConfigSettings, @configSettings)
serializeParams: ->
serialize: ->
deserializer: 'DisplayBuffer'
id: @id
softWrapped: @isSoftWrapped()
editorWidthInChars: @editorWidthInChars
scrollTop: @scrollTop
scrollLeft: @scrollLeft
tokenizedBuffer: @tokenizedBuffer.serialize()
largeFileMode: @largeFileMode
deserializeParams: (params) ->
params.tokenizedBuffer = TokenizedBuffer.deserialize(params.tokenizedBuffer)
params
copy: ->
newDisplayBuffer = new DisplayBuffer({@buffer, tabLength: @getTabLength(), @largeFileMode})
newDisplayBuffer.setScrollTop(@getScrollTop())
newDisplayBuffer.setScrollLeft(@getScrollLeft())
for marker in @findMarkers(displayBufferId: @id)
marker.copy(displayBufferId: newDisplayBuffer.id)
@@ -126,19 +125,8 @@ class DisplayBuffer extends Model
onDidChangeCharacterWidths: (callback) ->
@emitter.on 'did-change-character-widths', callback
onDidChangeScrollTop: (callback) ->
@emitter.on 'did-change-scroll-top', callback
onDidChangeScrollLeft: (callback) ->
@emitter.on 'did-change-scroll-left', callback
observeScrollTop: (callback) ->
callback(@scrollTop)
@onDidChangeScrollTop(callback)
observeScrollLeft: (callback) ->
callback(@scrollLeft)
@onDidChangeScrollLeft(callback)
onDidRequestAutoscroll: (callback) ->
@emitter.on 'did-request-autoscroll', callback
observeDecorations: (callback) ->
callback(decoration) for decoration in @getDecorations()
@@ -157,11 +145,9 @@ class DisplayBuffer extends Model
@emitter.on 'did-update-markers', callback
emitDidChange: (eventProperties, refreshMarkers=true) ->
@emit 'changed', eventProperties if Grim.includeDeprecatedAPIs
@emitter.emit 'did-change', eventProperties
if refreshMarkers
@refreshMarkerScreenPositions()
@emit 'markers-updated' if Grim.includeDeprecatedAPIs
@emitter.emit 'did-update-markers'
updateWrappedScreenLines: ->
@@ -183,105 +169,24 @@ class DisplayBuffer extends Model
setVerticalScrollMargin: (@verticalScrollMargin) -> @verticalScrollMargin
getVerticalScrollMarginInPixels: -> @getVerticalScrollMargin() * @getLineHeightInPixels()
getHorizontalScrollMargin: -> Math.min(@horizontalScrollMargin, Math.floor(((@getWidth() / @getDefaultCharWidth()) - 1) / 2))
setHorizontalScrollMargin: (@horizontalScrollMargin) -> @horizontalScrollMargin
getHorizontalScrollMarginInPixels: -> scrollMarginInPixels = @getHorizontalScrollMargin() * @getDefaultCharWidth()
getHorizontalScrollbarHeight: -> @horizontalScrollbarHeight
setHorizontalScrollbarHeight: (@horizontalScrollbarHeight) -> @horizontalScrollbarHeight
getVerticalScrollbarWidth: -> @verticalScrollbarWidth
setVerticalScrollbarWidth: (@verticalScrollbarWidth) -> @verticalScrollbarWidth
getHeight: ->
if @height?
@height
else
if @horizontallyScrollable()
@getScrollHeight() + @getHorizontalScrollbarHeight()
else
@getScrollHeight()
@height
setHeight: (@height) -> @height
getClientHeight: (reentrant) ->
if @horizontallyScrollable(reentrant)
@getHeight() - @getHorizontalScrollbarHeight()
else
@getHeight()
getClientWidth: (reentrant) ->
if @verticallyScrollable(reentrant)
@getWidth() - @getVerticalScrollbarWidth()
else
@getWidth()
horizontallyScrollable: (reentrant) ->
return false unless @width?
return false if @isSoftWrapped()
if reentrant
@getScrollWidth() > @getWidth()
else
@getScrollWidth() > @getClientWidth(true)
verticallyScrollable: (reentrant) ->
return false unless @height?
if reentrant
@getScrollHeight() > @getHeight()
else
@getScrollHeight() > @getClientHeight(true)
setHeight: (@height) ->
@height
getWidth: ->
if @width?
@width
else
if @verticallyScrollable()
@getScrollWidth() + @getVerticalScrollbarWidth()
else
@getScrollWidth()
@width
setWidth: (newWidth) ->
oldWidth = @width
@width = newWidth
@updateWrappedScreenLines() if newWidth isnt oldWidth and @isSoftWrapped()
@setScrollTop(@getScrollTop()) # Ensure scrollTop is still valid in case horizontal scrollbar disappeared
@width
getScrollTop: -> @scrollTop
setScrollTop: (scrollTop) ->
scrollTop = Math.round(Math.max(0, Math.min(@getMaxScrollTop(), scrollTop)))
unless scrollTop is @scrollTop
@scrollTop = scrollTop
@emitter.emit 'did-change-scroll-top', @scrollTop
@scrollTop
getMaxScrollTop: ->
@getScrollHeight() - @getClientHeight()
getScrollBottom: -> @scrollTop + @getClientHeight()
setScrollBottom: (scrollBottom) ->
@setScrollTop(scrollBottom - @getClientHeight())
@getScrollBottom()
getScrollLeft: -> @scrollLeft
setScrollLeft: (scrollLeft) ->
scrollLeft = Math.round(Math.max(0, Math.min(@getScrollWidth() - @getClientWidth(), scrollLeft)))
unless scrollLeft is @scrollLeft
@scrollLeft = scrollLeft
@emitter.emit 'did-change-scroll-left', @scrollLeft
@scrollLeft
getMaxScrollLeft: ->
@getScrollWidth() - @getClientWidth()
getScrollRight: -> @scrollLeft + @width
setScrollRight: (scrollRight) ->
@setScrollLeft(scrollRight - @width)
@getScrollRight()
getLineHeightInPixels: -> @lineHeightInPixels
setLineHeightInPixels: (@lineHeightInPixels) -> @lineHeightInPixels
@@ -289,7 +194,6 @@ class DisplayBuffer extends Model
setDefaultCharWidth: (defaultCharWidth) ->
if defaultCharWidth isnt @defaultCharWidth
@defaultCharWidth = defaultCharWidth
@computeScrollWidth()
defaultCharWidth
getCursorWidth: -> 1
@@ -318,85 +222,14 @@ class DisplayBuffer extends Model
@characterWidthsChanged() unless @batchingCharacterMeasurement
characterWidthsChanged: ->
@computeScrollWidth()
@emit 'character-widths-changed', @scopedCharacterWidthsChangeCount if Grim.includeDeprecatedAPIs
@emitter.emit 'did-change-character-widths', @scopedCharacterWidthsChangeCount
clearScopedCharWidths: ->
@charWidthsByScope = {}
getScrollHeight: ->
lineHeight = @getLineHeightInPixels()
return 0 unless lineHeight > 0
scrollHeight = @getLineCount() * lineHeight
if @height? and @configSettings.scrollPastEnd
scrollHeight = scrollHeight + @height - (lineHeight * 3)
scrollHeight
getScrollWidth: ->
@scrollWidth
# Returns an {Array} of two numbers representing the first and the last visible rows.
getVisibleRowRange: ->
return [0, 0] unless @getLineHeightInPixels() > 0
startRow = Math.floor(@getScrollTop() / @getLineHeightInPixels())
endRow = Math.ceil((@getScrollTop() + @getHeight()) / @getLineHeightInPixels()) - 1
endRow = Math.min(@getLineCount(), endRow)
[startRow, endRow]
intersectsVisibleRowRange: (startRow, endRow) ->
[visibleStart, visibleEnd] = @getVisibleRowRange()
not (endRow <= visibleStart or visibleEnd <= startRow)
selectionIntersectsVisibleRowRange: (selection) ->
{start, end} = selection.getScreenRange()
@intersectsVisibleRowRange(start.row, end.row + 1)
scrollToScreenRange: (screenRange, options) ->
verticalScrollMarginInPixels = @getVerticalScrollMarginInPixels()
horizontalScrollMarginInPixels = @getHorizontalScrollMarginInPixels()
{top, left} = @pixelRectForScreenRange(new Range(screenRange.start, screenRange.start))
{top: endTop, left: endLeft, height: endHeight} = @pixelRectForScreenRange(new Range(screenRange.end, screenRange.end))
bottom = endTop + endHeight
right = endLeft
if options?.center
desiredScrollCenter = (top + bottom) / 2
unless @getScrollTop() < desiredScrollCenter < @getScrollBottom()
desiredScrollTop = desiredScrollCenter - @getHeight() / 2
desiredScrollBottom = desiredScrollCenter + @getHeight() / 2
else
desiredScrollTop = top - verticalScrollMarginInPixels
desiredScrollBottom = bottom + verticalScrollMarginInPixels
desiredScrollLeft = left - horizontalScrollMarginInPixels
desiredScrollRight = right + horizontalScrollMarginInPixels
if options?.reversed ? true
if desiredScrollBottom > @getScrollBottom()
@setScrollBottom(desiredScrollBottom)
if desiredScrollTop < @getScrollTop()
@setScrollTop(desiredScrollTop)
if desiredScrollRight > @getScrollRight()
@setScrollRight(desiredScrollRight)
if desiredScrollLeft < @getScrollLeft()
@setScrollLeft(desiredScrollLeft)
else
if desiredScrollTop < @getScrollTop()
@setScrollTop(desiredScrollTop)
if desiredScrollBottom > @getScrollBottom()
@setScrollBottom(desiredScrollBottom)
if desiredScrollLeft < @getScrollLeft()
@setScrollLeft(desiredScrollLeft)
if desiredScrollRight > @getScrollRight()
@setScrollRight(desiredScrollRight)
scrollToScreenRange: (screenRange, options = {}) ->
scrollEvent = {screenRange, options}
@emitter.emit "did-request-autoscroll", scrollEvent
scrollToScreenPosition: (screenPosition, options) ->
@scrollToScreenRange(new Range(screenPosition, screenPosition), options)
@@ -404,19 +237,6 @@ class DisplayBuffer extends Model
scrollToBufferPosition: (bufferPosition, options) ->
@scrollToScreenPosition(@screenPositionForBufferPosition(bufferPosition), options)
pixelRectForScreenRange: (screenRange) ->
if screenRange.end.row > screenRange.start.row
top = @pixelPositionForScreenPosition(screenRange.start).top
left = 0
height = (screenRange.end.row - screenRange.start.row + 1) * @getLineHeightInPixels()
width = @getScrollWidth()
else
{top, left} = @pixelPositionForScreenPosition(screenRange.start, false)
height = @getLineHeightInPixels()
width = @pixelPositionForScreenPosition(screenRange.end, false).left - left
{top, left, width, height}
# Retrieves the current tab length.
#
# Returns a {Number}.
@@ -437,7 +257,6 @@ class DisplayBuffer extends Model
@softWrapped = softWrapped
@updateWrappedScreenLines()
softWrapped = @isSoftWrapped()
@emit 'soft-wrap-changed', softWrapped if Grim.includeDeprecatedAPIs
@emitter.emit 'did-change-soft-wrapped', softWrapped
softWrapped
else
@@ -461,8 +280,7 @@ class DisplayBuffer extends Model
# Returns the editor width in characters for soft wrap.
getEditorWidthInChars: ->
width = @width ? @getScrollWidth()
width -= @getVerticalScrollbarWidth()
width = @getWidth()
if width? and @defaultCharWidth > 0
Math.max(0, Math.floor(width / @defaultCharWidth))
else
@@ -674,80 +492,6 @@ class DisplayBuffer extends Model
end = @bufferPositionForScreenPosition(screenRange.end)
new Range(start, end)
pixelRangeForScreenRange: (screenRange, clip=true) ->
{start, end} = Range.fromObject(screenRange)
{start: @pixelPositionForScreenPosition(start, clip), end: @pixelPositionForScreenPosition(end, clip)}
pixelPositionForScreenPosition: (screenPosition, clip=true) ->
screenPosition = Point.fromObject(screenPosition)
screenPosition = @clipScreenPosition(screenPosition) if clip
targetRow = screenPosition.row
targetColumn = screenPosition.column
defaultCharWidth = @defaultCharWidth
top = targetRow * @lineHeightInPixels
left = 0
column = 0
iterator = @tokenizedLineForScreenRow(targetRow).getTokenIterator()
while iterator.next()
charWidths = @getScopedCharWidths(iterator.getScopes())
valueIndex = 0
value = iterator.getText()
while valueIndex < value.length
if iterator.isPairedCharacter()
char = value
charLength = 2
valueIndex += 2
else
char = value[valueIndex]
charLength = 1
valueIndex++
return {top, left} if column is targetColumn
left += charWidths[char] ? defaultCharWidth unless char is '\0'
column += charLength
{top, left}
screenPositionForPixelPosition: (pixelPosition) ->
targetTop = pixelPosition.top
targetLeft = pixelPosition.left
defaultCharWidth = @defaultCharWidth
row = Math.floor(targetTop / @getLineHeightInPixels())
targetLeft = 0 if row < 0
targetLeft = Infinity if row > @getLastRow()
row = Math.min(row, @getLastRow())
row = Math.max(0, row)
left = 0
column = 0
iterator = @tokenizedLineForScreenRow(row).getTokenIterator()
while iterator.next()
charWidths = @getScopedCharWidths(iterator.getScopes())
value = iterator.getText()
valueIndex = 0
while valueIndex < value.length
if iterator.isPairedCharacter()
char = value
charLength = 2
valueIndex += 2
else
char = value[valueIndex]
charLength = 1
valueIndex++
charWidth = charWidths[char] ? defaultCharWidth
break if targetLeft <= left + (charWidth / 2)
left += charWidth
column += charLength
new Point(row, column)
pixelPositionForBufferPosition: (bufferPosition) ->
@pixelPositionForScreenPosition(@screenPositionForBufferPosition(bufferPosition))
# Gets the number of screen lines.
#
# Returns a {Number}.
@@ -999,7 +743,6 @@ class DisplayBuffer extends Model
@decorationsByMarkerId[marker.id].push(decoration)
@overlayDecorationsById[decoration.id] = decoration if decoration.isType('overlay')
@decorationsById[decoration.id] = decoration
@emit 'decoration-added', decoration if Grim.includeDeprecatedAPIs
@emitter.emit 'did-add-decoration', decoration
decoration
@@ -1011,7 +754,6 @@ class DisplayBuffer extends Model
if index > -1
decorations.splice(index, 1)
delete @decorationsById[decoration.id]
@emit 'decoration-removed', decoration if Grim.includeDeprecatedAPIs
@emitter.emit 'did-remove-decoration', decoration
delete @decorationsByMarkerId[marker.id] if decorations.length is 0
delete @overlayDecorationsById[decoration.id]
@@ -1189,7 +931,6 @@ class DisplayBuffer extends Model
@changeCount = @tokenizedBuffer.changeCount
{start, end, delta, bufferChange} = tokenizedBufferChange
@updateScreenLines(start, end + 1, delta, refreshMarkers: false)
@setScrollTop(Math.min(@getScrollTop(), @getMaxScrollTop())) if delta < 0
updateScreenLines: (startBufferRow, endBufferRow, bufferDelta=0, options={}) ->
return if @largeFileMode
@@ -1294,13 +1035,6 @@ class DisplayBuffer extends Model
@longestScreenRow = screenRow
@maxLineLength = length
@computeScrollWidth() if oldMaxLineLength isnt @maxLineLength
computeScrollWidth: ->
@scrollWidth = @pixelPositionForScreenPosition([@longestScreenRow, @maxLineLength]).left
@scrollWidth += 1 unless @isSoftWrapped()
@setScrollLeft(Math.min(@getScrollLeft(), @getMaxScrollLeft()))
handleBufferMarkerCreated: (textBufferMarker) =>
if textBufferMarker.matchesParams(@getFoldMarkerAttributes())
fold = new Fold(this, textBufferMarker)
@@ -1310,7 +1044,6 @@ class DisplayBuffer extends Model
if marker = @getMarker(textBufferMarker.id)
# The marker might have been removed in some other handler called before
# this one. Only emit when the marker still exists.
@emit 'marker-created', marker if Grim.includeDeprecatedAPIs
@emitter.emit 'did-create-marker', marker
decorateFold: (fold) ->
@@ -1338,58 +1071,3 @@ class DisplayBuffer extends Model
atom.assert screenLinesCount is bufferLinesCount, "Display buffer line count out of sync with buffer", (error) ->
error.metadata = {screenLinesCount, tokenizedLinesCount, bufferLinesCount}
if Grim.includeDeprecatedAPIs
DisplayBuffer.properties
softWrapped: null
editorWidthInChars: null
lineHeightInPixels: null
defaultCharWidth: null
height: null
width: null
scrollTop: 0
scrollLeft: 0
scrollWidth: 0
verticalScrollbarWidth: 15
horizontalScrollbarHeight: 15
EmitterMixin = require('emissary').Emitter
DisplayBuffer::on = (eventName) ->
switch eventName
when 'changed'
Grim.deprecate("Use DisplayBuffer::onDidChange instead")
when 'grammar-changed'
Grim.deprecate("Use DisplayBuffer::onDidChangeGrammar instead")
when 'soft-wrap-changed'
Grim.deprecate("Use DisplayBuffer::onDidChangeSoftWrap instead")
when 'character-widths-changed'
Grim.deprecate("Use DisplayBuffer::onDidChangeCharacterWidths instead")
when 'decoration-added'
Grim.deprecate("Use DisplayBuffer::onDidAddDecoration instead")
when 'decoration-removed'
Grim.deprecate("Use DisplayBuffer::onDidRemoveDecoration instead")
when 'decoration-changed'
Grim.deprecate("Use decoration.getMarker().onDidChange() instead")
when 'decoration-updated'
Grim.deprecate("Use Decoration::onDidChangeProperties instead")
when 'marker-created'
Grim.deprecate("Use Decoration::onDidCreateMarker instead")
when 'markers-updated'
Grim.deprecate("Use Decoration::onDidUpdateMarkers instead")
else
Grim.deprecate("DisplayBuffer::on is deprecated. Use event subscription methods instead.")
EmitterMixin::on.apply(this, arguments)
else
DisplayBuffer::softWrapped = null
DisplayBuffer::editorWidthInChars = null
DisplayBuffer::lineHeightInPixels = null
DisplayBuffer::defaultCharWidth = null
DisplayBuffer::height = null
DisplayBuffer::width = null
DisplayBuffer::scrollTop = 0
DisplayBuffer::scrollLeft = 0
DisplayBuffer::scrollWidth = 0
DisplayBuffer::verticalScrollbarWidth = 15
DisplayBuffer::horizontalScrollbarHeight = 15

View File

@@ -0,0 +1,42 @@
module.exports =
class DOMElementPool
constructor: ->
@freeElementsByTagName = {}
@freedElements = new Set
clear: ->
@freedElements.clear()
for tagName, freeElements of @freeElementsByTagName
freeElements.length = 0
return
build: (tagName, className, textContent = "") ->
element = @freeElementsByTagName[tagName]?.pop()
element ?= document.createElement(tagName)
delete element.dataset[dataId] for dataId of element.dataset
element.removeAttribute("class")
element.removeAttribute("style")
element.className = className if className?
element.textContent = textContent
@freedElements.delete(element)
element
freeElementAndDescendants: (element) ->
@free(element)
for index in [element.children.length - 1..0] by -1
child = element.children[index]
@freeElementAndDescendants(child)
return
free: (element) ->
throw new Error("The element cannot be null or undefined.") unless element?
throw new Error("The element has already been freed!") if @freedElements.has(element)
tagName = element.tagName.toLowerCase()
@freeElementsByTagName[tagName] ?= []
@freeElementsByTagName[tagName].push(element)
@freedElements.add(element)
element.remove()

View File

@@ -1,6 +1,17 @@
fs = require 'fs'
{Directory} = require 'pathwatcher'
GitRepository = require './git-repository'
# Returns the .gitdir path in the agnostic Git symlink .git file given, or
# null if the path is not a valid gitfile.
#
# * `gitFile` {String} path of gitfile to parse
gitFileRegex = RegExp "^gitdir: (.+)"
pathFromGitFile = (gitFile) ->
try
gitFileBuff = fs.readFileSync(gitFile, 'utf8')
return gitFileBuff?.match(gitFileRegex)[1]
# Checks whether a valid `.git` directory is contained within the given
# directory or one of its ancestors. If so, a Directory that corresponds to the
# `.git` folder will be returned. Otherwise, returns `null`.
@@ -11,6 +22,9 @@ findGitDirectorySync = (directory) ->
# can return cached values rather than always returning new objects:
# getParent(), getFile(), getSubdirectory().
gitDir = directory.getSubdirectory('.git')
gitDirPath = pathFromGitFile(gitDir.getPath?())
if gitDirPath
gitDir = new Directory(directory.resolve(gitDirPath))
if gitDir.existsSync?() and isValidGitDirectorySync gitDir
gitDir
else if directory.isRoot()

View File

@@ -4,15 +4,14 @@ _ = require 'underscore-plus'
{Emitter, Disposable, CompositeDisposable} = require 'event-kit'
fs = require 'fs-plus'
GitUtils = require 'git-utils'
{includeDeprecatedAPIs, deprecate} = require 'grim'
Task = require './task'
# Extended: Represents the underlying git operations performed by Atom.
#
# This class shouldn't be instantiated directly but instead by accessing the
# `atom.project` global and calling `getRepo()`. Note that this will only be
# available when the project is backed by a Git repository.
# `atom.project` global and calling `getRepositories()`. Note that this will
# only be available when the project is backed by a Git repository.
#
# This class handles submodules automatically by taking a `path` argument to many
# of the methods. This `path` argument will determine which underlying
@@ -21,7 +20,7 @@ Task = require './task'
# For a repository with submodules this would have the following outcome:
#
# ```coffee
# repo = atom.project.getRepo()
# repo = atom.project.getRepositories()[0]
# repo.getShortHead() # 'master'
# repo.getShortHead('vendor/path/to/a/submodule') # 'dead1234'
# ```
@@ -31,7 +30,7 @@ Task = require './task'
# ### Logging the URL of the origin remote
#
# ```coffee
# git = atom.project.getRepo()
# git = atom.project.getRepositories()[0]
# console.log git.getOriginURL()
# ```
#
@@ -327,7 +326,6 @@ class GitRepository
else
delete @statuses[relativePath]
if currentPathStatus isnt pathStatus
@emit 'status-changed', path, pathStatus if includeDeprecatedAPIs
@emitter.emit 'did-change-status', {path, pathStatus}
pathStatus
@@ -496,23 +494,4 @@ class GitRepository
submoduleRepo.upstream = submodules[submodulePath]?.upstream ? {ahead: 0, behind: 0}
unless statusesUnchanged
@emit 'statuses-changed' if includeDeprecatedAPIs
@emitter.emit 'did-change-statuses'
if includeDeprecatedAPIs
EmitterMixin = require('emissary').Emitter
EmitterMixin.includeInto(GitRepository)
GitRepository::on = (eventName) ->
switch eventName
when 'status-changed'
deprecate 'Use GitRepository::onDidChangeStatus instead'
when 'statuses-changed'
deprecate 'Use GitRepository::onDidChangeStatuses instead'
else
deprecate 'GitRepository::on is deprecated. Use event subscription methods instead.'
EmitterMixin::on.apply(this, arguments)
GitRepository::getOriginUrl = (path) ->
deprecate 'Use ::getOriginURL instead.'
@getOriginURL(path)

View File

@@ -1,6 +1,5 @@
_ = require 'underscore-plus'
{Emitter} = require 'event-kit'
{includeDeprecatedAPIs, deprecate} = require 'grim'
FirstMate = require 'first-mate'
Token = require './token'
fs = require 'fs-plus'
@@ -136,37 +135,4 @@ class GrammarRegistry extends FirstMate.GrammarRegistry
undefined
clearObservers: ->
@off() if includeDeprecatedAPIs
@emitter = new Emitter
if includeDeprecatedAPIs
PropertyAccessors = require 'property-accessors'
PropertyAccessors.includeInto(GrammarRegistry)
{Subscriber} = require 'emissary'
Subscriber.includeInto(GrammarRegistry)
# Support old serialization
atom.deserializers.add(name: 'Syntax', deserialize: GrammarRegistry.deserialize)
# Deprecated: Used by settings-view to display snippets for packages
GrammarRegistry::accessor 'propertyStore', ->
deprecate("Do not use this. Use a public method on Config")
atom.config.scopedSettingsStore
GrammarRegistry::addProperties = (args...) ->
args.unshift(null) if args.length is 2
deprecate 'Consider using atom.config.set() instead. A direct (but private) replacement is available at atom.config.addScopedSettings().'
atom.config.addScopedSettings(args...)
GrammarRegistry::removeProperties = (name) ->
deprecate 'atom.config.addScopedSettings() now returns a disposable you can call .dispose() on'
atom.config.scopedSettingsStore.removeProperties(name)
GrammarRegistry::getProperty = (scope, keyPath) ->
deprecate 'A direct (but private) replacement is available at atom.config.getRawScopedValue().'
atom.config.getRawScopedValue(scope, keyPath)
GrammarRegistry::propertiesForScope = (scope, keyPath) ->
deprecate 'Use atom.config.getAll instead.'
atom.config.settingsForScopeDescriptor(scope, keyPath)

View File

@@ -7,7 +7,7 @@ LineNumberGutterComponent = require './line-number-gutter-component'
module.exports =
class GutterContainerComponent
constructor: ({@onLineNumberGutterMouseDown, @editor}) ->
constructor: ({@onLineNumberGutterMouseDown, @editor, @domElementPool}) ->
# An array of objects of the form: {name: {String}, component: {Object}}
@gutterComponents = []
@gutterComponentsByGutterName = {}
@@ -39,7 +39,7 @@ class GutterContainerComponent
gutterComponent = @gutterComponentsByGutterName[gutter.name]
if not gutterComponent
if gutter.name is 'line-number'
gutterComponent = new LineNumberGutterComponent({onMouseDown: @onLineNumberGutterMouseDown, @editor, gutter})
gutterComponent = new LineNumberGutterComponent({onMouseDown: @onLineNumberGutterMouseDown, @editor, gutter, @domElementPool})
@lineNumberGutterComponent = gutterComponent
else
gutterComponent = new CustomGutterComponent({gutter})

View File

@@ -78,14 +78,11 @@ class Gutter
# ## Arguments
#
# * `marker` A {Marker} you want this decoration to follow.
# * `decorationParams` An {Object} representing the decoration
# * `class` This CSS class will be applied to the decorated line number.
# * `onlyHead` (optional) If `true`, the decoration will only be applied to
# the head of the marker.
# * `onlyEmpty` (optional) If `true`, the decoration will only be applied if
# the associated marker is empty.
# * `onlyNonEmpty` (optional) If `true`, the decoration will only be applied
# if the associated marker is non-empty.
# * `decorationParams` An {Object} representing the decoration. It is passed
# to {TextEditor::decorateMarker} as its `decorationParams` and so supports
# all options documented there.
# * `type` __Caveat__: set to `'line-number'` if this is the line-number
# gutter, `'gutter'` otherwise. This cannot be overridden.
#
# Returns a {Decoration} object
decorateMarker: (marker, options) ->

View File

@@ -5,12 +5,11 @@ module.exports =
class HighlightsComponent
oldState: null
constructor: ->
constructor: (@domElementPool) ->
@highlightNodesById = {}
@regionNodesByHighlightId = {}
@domNode = document.createElement('div')
@domNode.classList.add('highlights')
@domNode = @domElementPool.build("div", "highlights")
getDomNode: ->
@domNode
@@ -30,8 +29,7 @@ class HighlightsComponent
# add or update highlights
for id, highlightState of newState
unless @oldState[id]?
highlightNode = document.createElement('div')
highlightNode.classList.add('highlight')
highlightNode = @domElementPool.build("div", "highlight")
@highlightNodesById[id] = highlightNode
@regionNodesByHighlightId[id] = {}
@domNode.appendChild(highlightNode)
@@ -75,12 +73,11 @@ class HighlightsComponent
for newRegionState, i in newHighlightState.regions
unless oldHighlightState.regions[i]?
oldHighlightState.regions[i] = {}
regionNode = document.createElement('div')
regionNode = @domElementPool.build("div", "region")
# This prevents highlights at the tiles boundaries to be hidden by the
# subsequent tile. When this happens, subpixel anti-aliasing gets
# disabled.
regionNode.style.boxSizing = "border-box"
regionNode.classList.add('region')
regionNode.classList.add(newHighlightState.deprecatedRegionClass) if newHighlightState.deprecatedRegionClass?
@regionNodesByHighlightId[id][i] = regionNode
highlightNode.appendChild(regionNode)

View File

@@ -3,6 +3,7 @@ class InputComponent
constructor: ->
@domNode = document.createElement('input')
@domNode.classList.add('hidden-input')
@domNode.setAttribute('tabindex', -1)
@domNode.setAttribute('data-react-skip-selection-restoration', true)
@domNode.style['-webkit-transform'] = 'translateZ(0)'
@domNode.addEventListener 'paste', (event) -> event.preventDefault()

View File

@@ -2,8 +2,6 @@ fs = require 'fs-plus'
path = require 'path'
KeymapManager = require 'atom-keymap'
CSON = require 'season'
{jQuery} = require 'space-pen'
Grim = require 'grim'
bundledKeymaps = require('../package.json')?._atomKeymaps
@@ -61,9 +59,4 @@ KeymapManager::subscribeToFileReadFailure = ->
atom.notifications.addError(message, {detail, dismissable: true})
# This enables command handlers registered via jQuery to call
# `.abortKeyBinding()` on the `jQuery.Event` object passed to the handler.
jQuery.Event::abortKeyBinding = ->
@originalEvent?.abortKeyBinding?()
module.exports = KeymapManager

View File

@@ -1,15 +1,17 @@
TiledComponent = require './tiled-component'
LineNumbersTileComponent = require './line-numbers-tile-component'
WrapperDiv = document.createElement('div')
DummyLineNumberComponent = LineNumbersTileComponent.createDummy()
DOMElementPool = require './dom-element-pool'
module.exports =
class LineNumberGutterComponent extends TiledComponent
dummyLineNumberNode: null
constructor: ({@onMouseDown, @editor, @gutter}) ->
constructor: ({@onMouseDown, @editor, @gutter, @domElementPool}) ->
@visible = true
@dummyLineNumberComponent = LineNumbersTileComponent.createDummy(@domElementPool)
@domNode = atom.views.getView(@gutter)
@lineNumbersNode = @domNode.firstChild
@lineNumbersNode.innerHTML = ''
@@ -60,7 +62,10 @@ class LineNumberGutterComponent extends TiledComponent
@oldState.styles = {}
@oldState.maxLineNumberDigits = @newState.maxLineNumberDigits
buildComponentForTile: (id) -> new LineNumbersTileComponent({id})
buildComponentForTile: (id) -> new LineNumbersTileComponent({id, @domElementPool})
shouldRecreateAllTilesOnUpdate: ->
@newState.continuousReflow
###
Section: Private Methods
@@ -69,14 +74,13 @@ class LineNumberGutterComponent extends TiledComponent
# This dummy line number element holds the gutter to the appropriate width,
# since the real line numbers are absolutely positioned for performance reasons.
appendDummyLineNumber: ->
DummyLineNumberComponent.newState = @newState
WrapperDiv.innerHTML = DummyLineNumberComponent.buildLineNumberHTML({bufferRow: -1})
@dummyLineNumberNode = WrapperDiv.children[0]
@dummyLineNumberComponent.newState = @newState
@dummyLineNumberNode = @dummyLineNumberComponent.buildLineNumberNode({bufferRow: -1})
@lineNumbersNode.appendChild(@dummyLineNumberNode)
updateDummyLineNumber: ->
DummyLineNumberComponent.newState = @newState
@dummyLineNumberNode.innerHTML = DummyLineNumberComponent.buildLineNumberInnerHTML(0, false)
@dummyLineNumberComponent.newState = @newState
@dummyLineNumberComponent.setLineNumberInnerNodes(0, false, @dummyLineNumberNode)
onMouseDown: (event) =>
{target} = event

View File

@@ -1,19 +1,20 @@
_ = require 'underscore-plus'
WrapperDiv = document.createElement('div')
module.exports =
class LineNumbersTileComponent
@createDummy: ->
new LineNumbersTileComponent({id: -1})
@createDummy: (domElementPool) ->
new LineNumbersTileComponent({id: -1, domElementPool})
constructor: ({@id}) ->
constructor: ({@id, @domElementPool}) ->
@lineNumberNodesById = {}
@domNode = document.createElement("div")
@domNode.classList.add("tile")
@domNode = @domElementPool.build("div")
@domNode.style.position = "absolute"
@domNode.style.display = "block"
@domNode.style.top = 0 # Cover the space occupied by a dummy lineNumber
destroy: ->
@domElementPool.freeElementAndDescendants(@domNode)
getDomNode: ->
@domNode
@@ -47,7 +48,9 @@ class LineNumbersTileComponent
@oldTileState.zIndex = @newTileState.zIndex
if @newState.maxLineNumberDigits isnt @oldState.maxLineNumberDigits
node.remove() for id, node of @lineNumberNodesById
for id, node of @lineNumberNodesById
@domElementPool.freeElementAndDescendants(node)
@oldState.tiles[@id] = {lineNumbers: {}}
@oldTileState = @oldState.tiles[@id]
@lineNumberNodesById = {}
@@ -57,11 +60,11 @@ class LineNumbersTileComponent
updateLineNumbers: ->
newLineNumberIds = null
newLineNumbersHTML = null
newLineNumberNodes = null
for id, lineNumberState of @oldTileState.lineNumbers
unless @newTileState.lineNumbers.hasOwnProperty(id)
@lineNumberNodesById[id].remove()
@domElementPool.freeElementAndDescendants(@lineNumberNodesById[id])
delete @lineNumberNodesById[id]
delete @oldTileState.lineNumbers[id]
@@ -70,35 +73,40 @@ class LineNumbersTileComponent
@updateLineNumberNode(id, lineNumberState)
else
newLineNumberIds ?= []
newLineNumbersHTML ?= ""
newLineNumberNodes ?= []
newLineNumberIds.push(id)
newLineNumbersHTML += @buildLineNumberHTML(lineNumberState)
newLineNumberNodes.push(@buildLineNumberNode(lineNumberState))
@oldTileState.lineNumbers[id] = _.clone(lineNumberState)
if newLineNumberIds?
WrapperDiv.innerHTML = newLineNumbersHTML
newLineNumberNodes = _.toArray(WrapperDiv.children)
return unless newLineNumberIds?
node = @domNode
for id, i in newLineNumberIds
lineNumberNode = newLineNumberNodes[i]
@lineNumberNodesById[id] = lineNumberNode
node.appendChild(lineNumberNode)
for id, i in newLineNumberIds
lineNumberNode = newLineNumberNodes[i]
@lineNumberNodesById[id] = lineNumberNode
if nextNode = @findNodeNextTo(lineNumberNode)
@domNode.insertBefore(lineNumberNode, nextNode)
else
@domNode.appendChild(lineNumberNode)
findNodeNextTo: (node) ->
for nextNode in @domNode.children
return nextNode if @screenRowForNode(node) < @screenRowForNode(nextNode)
return
buildLineNumberHTML: (lineNumberState) ->
screenRowForNode: (node) -> parseInt(node.dataset.screenRow)
buildLineNumberNode: (lineNumberState) ->
{screenRow, bufferRow, softWrapped, top, decorationClasses, zIndex} = lineNumberState
if screenRow?
style = "position: absolute; top: #{top}px; z-index: #{zIndex};"
else
style = "visibility: hidden;"
className = @buildLineNumberClassName(lineNumberState)
innerHTML = @buildLineNumberInnerHTML(bufferRow, softWrapped)
lineNumberNode = @domElementPool.build("div", className)
lineNumberNode.dataset.screenRow = screenRow
lineNumberNode.dataset.bufferRow = bufferRow
"<div class=\"#{className}\" style=\"#{style}\" data-buffer-row=\"#{bufferRow}\" data-screen-row=\"#{screenRow}\">#{innerHTML}</div>"
@setLineNumberInnerNodes(bufferRow, softWrapped, lineNumberNode)
lineNumberNode
buildLineNumberInnerHTML: (bufferRow, softWrapped) ->
setLineNumberInnerNodes: (bufferRow, softWrapped, lineNumberNode) ->
{maxLineNumberDigits} = @newState
if softWrapped
@@ -106,9 +114,11 @@ class LineNumbersTileComponent
else
lineNumber = (bufferRow + 1).toString()
padding = _.multiplyString('&nbsp;', maxLineNumberDigits - lineNumber.length)
iconHTML = '<div class="icon-right"></div>'
padding + lineNumber + iconHTML
padding = _.multiplyString("\u00a0", maxLineNumberDigits - lineNumber.length)
iconRight = @domElementPool.build("div", "icon-right")
lineNumberNode.textContent = padding + lineNumber
lineNumberNode.appendChild(iconRight)
updateLineNumberNode: (lineNumberId, newLineNumberState) ->
oldLineNumberState = @oldTileState.lineNumbers[lineNumberId]
@@ -119,18 +129,15 @@ class LineNumbersTileComponent
oldLineNumberState.foldable = newLineNumberState.foldable
oldLineNumberState.decorationClasses = _.clone(newLineNumberState.decorationClasses)
unless oldLineNumberState.top is newLineNumberState.top
node.style.top = newLineNumberState.top + 'px'
unless oldLineNumberState.screenRow is newLineNumberState.screenRow and oldLineNumberState.bufferRow is newLineNumberState.bufferRow
@setLineNumberInnerNodes(newLineNumberState.bufferRow, newLineNumberState.softWrapped, node)
node.dataset.screenRow = newLineNumberState.screenRow
oldLineNumberState.top = newLineNumberState.top
node.dataset.bufferRow = newLineNumberState.bufferRow
oldLineNumberState.screenRow = newLineNumberState.screenRow
unless oldLineNumberState.zIndex is newLineNumberState.zIndex
node.style.zIndex = newLineNumberState.zIndex
oldLineNumberState.zIndex = newLineNumberState.zIndex
oldLineNumberState.bufferRow = newLineNumberState.bufferRow
buildLineNumberClassName: ({bufferRow, foldable, decorationClasses, softWrapped}) ->
className = "line-number line-number-#{bufferRow}"
className = "line-number"
className += " " + decorationClasses.join(' ') if decorationClasses?
className += " foldable" if foldable and not softWrapped
className

View File

@@ -1,16 +1,19 @@
{$$} = require 'space-pen'
CursorsComponent = require './cursors-component'
LinesTileComponent = require './lines-tile-component'
TiledComponent = require './tiled-component'
DummyLineNode = $$(-> @div className: 'line', style: 'position: absolute; visibility: hidden;', => @span 'x')[0]
DummyLineNode = document.createElement('div')
DummyLineNode.className = 'line'
DummyLineNode.style.position = 'absolute'
DummyLineNode.style.visibility = 'hidden'
DummyLineNode.appendChild(document.createElement('span'))
DummyLineNode.firstChild.textContent = 'x'
module.exports =
class LinesComponent extends TiledComponent
placeholderTextDiv: null
constructor: ({@presenter, @hostElement, @useShadowDOM, visible}) ->
constructor: ({@presenter, @hostElement, @useShadowDOM, visible, @domElementPool}) ->
@domNode = document.createElement('div')
@domNode.classList.add('lines')
@tilesNode = document.createElement("div")
@@ -32,7 +35,7 @@ class LinesComponent extends TiledComponent
@domNode
shouldRecreateAllTilesOnUpdate: ->
@oldState.indentGuidesVisible isnt @newState.indentGuidesVisible
@oldState.indentGuidesVisible isnt @newState.indentGuidesVisible or @newState.continuousReflow
beforeUpdateSync: (state) ->
if @newState.maxHeight isnt @oldState.maxHeight
@@ -60,7 +63,7 @@ class LinesComponent extends TiledComponent
@oldState.indentGuidesVisible = @newState.indentGuidesVisible
buildComponentForTile: (id) -> new LinesTileComponent({id, @presenter})
buildComponentForTile: (id) -> new LinesTileComponent({id, @presenter, @domElementPool})
buildEmptyState: ->
{tiles: {}}

View File

@@ -3,7 +3,6 @@ _ = require 'underscore-plus'
HighlightsComponent = require './highlights-component'
TokenIterator = require './token-iterator'
AcceptFilter = {acceptNode: -> NodeFilter.FILTER_ACCEPT}
WrapperDiv = document.createElement('div')
TokenTextEscapeRegex = /[&"'<>]/g
MaxTokenLength = 20000
@@ -14,20 +13,22 @@ cloneObject = (object) ->
module.exports =
class LinesTileComponent
constructor: ({@presenter, @id}) ->
constructor: ({@presenter, @id, @domElementPool}) ->
@tokenIterator = new TokenIterator
@measuredLines = new Set
@lineNodesByLineId = {}
@screenRowsByLineId = {}
@lineIdsByScreenRow = {}
@domNode = document.createElement("div")
@domNode.classList.add("tile")
@domNode = @domElementPool.build("div")
@domNode.style.position = "absolute"
@domNode.style.display = "block"
@highlightsComponent = new HighlightsComponent
@highlightsComponent = new HighlightsComponent(@domElementPool)
@domNode.appendChild(@highlightsComponent.getDomNode())
destroy: ->
@domElementPool.freeElementAndDescendants(@domNode)
getDomNode: ->
@domNode
@@ -77,7 +78,7 @@ class LinesTileComponent
return
removeLineNode: (id) ->
@lineNodesByLineId[id].remove()
@domElementPool.freeElementAndDescendants(@lineNodesByLineId[id])
delete @lineNodesByLineId[id]
delete @lineIdsByScreenRow[@screenRowsByLineId[id]]
delete @screenRowsByLineId[id]
@@ -89,89 +90,99 @@ class LinesTileComponent
@removeLineNode(id)
newLineIds = null
newLinesHTML = null
newLineNodes = null
for id, lineState of @newTileState.lines
if @oldTileState.lines.hasOwnProperty(id)
@updateLineNode(id)
else
newLineIds ?= []
newLinesHTML ?= ""
newLineNodes ?= []
newLineIds.push(id)
newLinesHTML += @buildLineHTML(id)
newLineNodes.push(@buildLineNode(id))
@screenRowsByLineId[id] = lineState.screenRow
@lineIdsByScreenRow[lineState.screenRow] = id
@oldTileState.lines[id] = cloneObject(lineState)
return unless newLineIds?
WrapperDiv.innerHTML = newLinesHTML
newLineNodes = _.toArray(WrapperDiv.children)
for id, i in newLineIds
lineNode = newLineNodes[i]
@lineNodesByLineId[id] = lineNode
@domNode.appendChild(lineNode)
if nextNode = @findNodeNextTo(lineNode)
@domNode.insertBefore(lineNode, nextNode)
else
@domNode.appendChild(lineNode)
findNodeNextTo: (node) ->
for nextNode, index in @domNode.children
continue if index is 0 # skips highlights node
return nextNode if @screenRowForNode(node) < @screenRowForNode(nextNode)
return
buildLineHTML: (id) ->
screenRowForNode: (node) -> parseInt(node.dataset.screenRow)
buildLineNode: (id) ->
{width} = @newState
{screenRow, tokens, text, top, lineEnding, fold, isSoftWrapped, indentLevel, decorationClasses} = @newTileState.lines[id]
classes = ''
lineNode = @domElementPool.build("div", "line")
lineNode.dataset.screenRow = screenRow
if decorationClasses?
for decorationClass in decorationClasses
classes += decorationClass + ' '
classes += 'line'
lineHTML = "<div class=\"#{classes}\" style=\"position: absolute; top: #{top}px; width: #{width}px;\" data-screen-row=\"#{screenRow}\">"
lineNode.classList.add(decorationClass)
if text is ""
lineHTML += @buildEmptyLineInnerHTML(id)
@setEmptyLineInnerNodes(id, lineNode)
else
lineHTML += @buildLineInnerHTML(id)
@setLineInnerNodes(id, lineNode)
lineHTML += '<span class="fold-marker"></span>' if fold
lineHTML += "</div>"
lineHTML
lineNode.appendChild(@domElementPool.build("span", "fold-marker")) if fold
lineNode
buildEmptyLineInnerHTML: (id) ->
setEmptyLineInnerNodes: (id, lineNode) ->
{indentGuidesVisible} = @newState
{indentLevel, tabLength, endOfLineInvisibles} = @newTileState.lines[id]
if indentGuidesVisible and indentLevel > 0
invisibleIndex = 0
lineHTML = ''
for i in [0...indentLevel]
lineHTML += "<span class='indent-guide'>"
indentGuide = @domElementPool.build("span", "indent-guide")
for j in [0...tabLength]
if invisible = endOfLineInvisibles?[invisibleIndex++]
lineHTML += "<span class='invisible-character'>#{invisible}</span>"
indentGuide.appendChild(
@domElementPool.build("span", "invisible-character", invisible)
)
else
lineHTML += ' '
lineHTML += "</span>"
indentGuide.insertAdjacentText("beforeend", " ")
lineNode.appendChild(indentGuide)
while invisibleIndex < endOfLineInvisibles?.length
lineHTML += "<span class='invisible-character'>#{endOfLineInvisibles[invisibleIndex++]}</span>"
lineHTML
invisible = endOfLineInvisibles[invisibleIndex++]
lineNode.appendChild(
@domElementPool.build("span", "invisible-character", invisible)
)
else
@buildEndOfLineHTML(id) or '&nbsp;'
unless @appendEndOfLineNodes(id, lineNode)
lineNode.textContent = "\u00a0"
buildLineInnerHTML: (id) ->
setLineInnerNodes: (id, lineNode) ->
lineState = @newTileState.lines[id]
{firstNonWhitespaceIndex, firstTrailingWhitespaceIndex, invisibles} = lineState
lineIsWhitespaceOnly = firstTrailingWhitespaceIndex is 0
innerHTML = ""
@tokenIterator.reset(lineState)
openScopeNode = lineNode
while @tokenIterator.next()
for scope in @tokenIterator.getScopeEnds()
innerHTML += "</span>"
openScopeNode = openScopeNode.parentElement
for scope in @tokenIterator.getScopeStarts()
innerHTML += "<span class=\"#{scope.replace(/\.+/g, ' ')}\">"
newScopeNode = @domElementPool.build("span", scope.replace(/\.+/g, ' '))
openScopeNode.appendChild(newScopeNode)
openScopeNode = newScopeNode
tokenStart = @tokenIterator.getScreenStart()
tokenEnd = @tokenIterator.getScreenEnd()
@@ -196,87 +207,79 @@ class LinesTileComponent
(invisibles?.tab and isHardTab) or
(invisibles?.space and (hasLeadingWhitespace or hasTrailingWhitespace))
innerHTML += @buildTokenHTML(tokenText, isHardTab, tokenFirstNonWhitespaceIndex, tokenFirstTrailingWhitespaceIndex, hasIndentGuide, hasInvisibleCharacters)
@appendTokenNodes(tokenText, isHardTab, tokenFirstNonWhitespaceIndex, tokenFirstTrailingWhitespaceIndex, hasIndentGuide, hasInvisibleCharacters, openScopeNode)
for scope in @tokenIterator.getScopeEnds()
innerHTML += "</span>"
@appendEndOfLineNodes(id, lineNode)
for scope in @tokenIterator.getScopes()
innerHTML += "</span>"
innerHTML += @buildEndOfLineHTML(id)
innerHTML
buildTokenHTML: (tokenText, isHardTab, firstNonWhitespaceIndex, firstTrailingWhitespaceIndex, hasIndentGuide, hasInvisibleCharacters) ->
appendTokenNodes: (tokenText, isHardTab, firstNonWhitespaceIndex, firstTrailingWhitespaceIndex, hasIndentGuide, hasInvisibleCharacters, scopeNode) ->
if isHardTab
classes = 'hard-tab'
classes += ' leading-whitespace' if firstNonWhitespaceIndex?
classes += ' trailing-whitespace' if firstTrailingWhitespaceIndex?
classes += ' indent-guide' if hasIndentGuide
classes += ' invisible-character' if hasInvisibleCharacters
return "<span class='#{classes}'>#{@escapeTokenText(tokenText)}</span>"
hardTabNode = @domElementPool.build("span", "hard-tab", tokenText)
hardTabNode.classList.add("leading-whitespace") if firstNonWhitespaceIndex?
hardTabNode.classList.add("trailing-whitespace") if firstTrailingWhitespaceIndex?
hardTabNode.classList.add("indent-guide") if hasIndentGuide
hardTabNode.classList.add("invisible-character") if hasInvisibleCharacters
scopeNode.appendChild(hardTabNode)
else
startIndex = 0
endIndex = tokenText.length
leadingHtml = ''
trailingHtml = ''
leadingWhitespaceNode = null
trailingWhitespaceNode = null
if firstNonWhitespaceIndex?
leadingWhitespace = tokenText.substring(0, firstNonWhitespaceIndex)
leadingWhitespaceNode = @domElementPool.build(
"span",
"leading-whitespace",
tokenText.substring(0, firstNonWhitespaceIndex)
)
leadingWhitespaceNode.classList.add("indent-guide") if hasIndentGuide
leadingWhitespaceNode.classList.add("invisible-character") if hasInvisibleCharacters
classes = 'leading-whitespace'
classes += ' indent-guide' if hasIndentGuide
classes += ' invisible-character' if hasInvisibleCharacters
leadingHtml = "<span class='#{classes}'>#{leadingWhitespace}</span>"
startIndex = firstNonWhitespaceIndex
if firstTrailingWhitespaceIndex?
tokenIsOnlyWhitespace = firstTrailingWhitespaceIndex is 0
trailingWhitespace = tokenText.substring(firstTrailingWhitespaceIndex)
classes = 'trailing-whitespace'
classes += ' indent-guide' if hasIndentGuide and not firstNonWhitespaceIndex? and tokenIsOnlyWhitespace
classes += ' invisible-character' if hasInvisibleCharacters
trailingHtml = "<span class='#{classes}'>#{trailingWhitespace}</span>"
trailingWhitespaceNode = @domElementPool.build(
"span",
"trailing-whitespace",
tokenText.substring(firstTrailingWhitespaceIndex)
)
trailingWhitespaceNode.classList.add("indent-guide") if hasIndentGuide and not firstNonWhitespaceIndex? and tokenIsOnlyWhitespace
trailingWhitespaceNode.classList.add("invisible-character") if hasInvisibleCharacters
endIndex = firstTrailingWhitespaceIndex
html = leadingHtml
scopeNode.appendChild(leadingWhitespaceNode) if leadingWhitespaceNode?
if tokenText.length > MaxTokenLength
while startIndex < endIndex
html += "<span>" + @escapeTokenText(tokenText, startIndex, startIndex + MaxTokenLength) + "</span>"
text = @sliceText(tokenText, startIndex, startIndex + MaxTokenLength)
scopeNode.appendChild(@domElementPool.build("span", null, text))
startIndex += MaxTokenLength
else
html += @escapeTokenText(tokenText, startIndex, endIndex)
scopeNode.insertAdjacentText("beforeend", @sliceText(tokenText, startIndex, endIndex))
html += trailingHtml
html
scopeNode.appendChild(trailingWhitespaceNode) if trailingWhitespaceNode?
escapeTokenText: (tokenText, startIndex, endIndex) ->
sliceText: (tokenText, startIndex, endIndex) ->
if startIndex? and endIndex? and startIndex > 0 or endIndex < tokenText.length
tokenText = tokenText.slice(startIndex, endIndex)
tokenText.replace(TokenTextEscapeRegex, @escapeTokenTextReplace)
tokenText
escapeTokenTextReplace: (match) ->
switch match
when '&' then '&amp;'
when '"' then '&quot;'
when "'" then '&#39;'
when '<' then '&lt;'
when '>' then '&gt;'
else match
buildEndOfLineHTML: (id) ->
appendEndOfLineNodes: (id, lineNode) ->
{endOfLineInvisibles} = @newTileState.lines[id]
html = ''
hasInvisibles = false
if endOfLineInvisibles?
for invisible in endOfLineInvisibles
html += "<span class='invisible-character'>#{invisible}</span>"
html
hasInvisibles = true
lineNode.appendChild(
@domElementPool.build("span", "invisible-character", invisible)
)
hasInvisibles
updateLineNode: (id) ->
oldLineState = @oldTileState.lines[id]
@@ -284,9 +287,6 @@ class LinesTileComponent
lineNode = @lineNodesByLineId[id]
if @newState.width isnt @oldState.width
lineNode.style.width = @newState.width + 'px'
newDecorationClasses = newLineState.decorationClasses
oldDecorationClasses = oldLineState.decorationClasses
@@ -302,10 +302,6 @@ class LinesTileComponent
oldLineState.decorationClasses = newLineState.decorationClasses
if newLineState.top isnt oldLineState.top
lineNode.style.top = newLineState.top + 'px'
oldLineState.top = newLineState.top
if newLineState.screenRow isnt oldLineState.screenRow
lineNode.dataset.screenRow = newLineState.screenRow
oldLineState.screenRow = newLineState.screenRow
@@ -343,8 +339,6 @@ class LinesTileComponent
charLength = 1
textIndex++
continue if char is '\0'
unless charWidths[char]?
unless textNode?
rangeForMeasurement ?= document.createRange()

View File

@@ -1,6 +1,5 @@
_ = require 'underscore-plus'
{CompositeDisposable, Emitter} = require 'event-kit'
Grim = require 'grim'
# Essential: Represents a buffer annotation that remains logically stationary
# even as the buffer changes. This is used to represent cursors, folds, snippet
@@ -335,7 +334,6 @@ class Marker
destroyed: ->
delete @displayBuffer.markers[@id]
@emit 'destroyed' if Grim.includeDeprecatedAPIs
@emitter.emit 'did-destroy'
@emitter.dispose()
@@ -369,35 +367,4 @@ class Marker
@oldTailScreenPosition = newTailScreenPosition
@wasValid = isValid
@emit 'changed', changeEvent if Grim.includeDeprecatedAPIs
@emitter.emit 'did-change', changeEvent
getPixelRange: ->
@displayBuffer.pixelRangeForScreenRange(@getScreenRange(), false)
if Grim.includeDeprecatedAPIs
EmitterMixin = require('emissary').Emitter
EmitterMixin.includeInto(Marker)
Marker::on = (eventName) ->
switch eventName
when 'changed'
Grim.deprecate("Use Marker::onDidChange instead")
when 'destroyed'
Grim.deprecate("Use Marker::onDidDestroy instead")
else
Grim.deprecate("Marker::on is deprecated. Use documented event subscription methods instead.")
EmitterMixin::on.apply(this, arguments)
Marker::getAttributes = ->
Grim.deprecate 'Use Marker::getProperties instead'
@getProperties()
Marker::setAttributes = (properties) ->
Grim.deprecate 'Use Marker::setProperties instead'
@setProperties(properties)
Marker::matchesAttributes = (attributes) ->
Grim.deprecate 'Use Marker::matchesProperties instead'
@matchesProperties(attributes)

View File

@@ -1,16 +1,7 @@
Grim = require 'grim'
if Grim.includeDeprecatedAPIs
module.exports = require('theorist').Model
return
PropertyAccessors = require 'property-accessors'
nextInstanceId = 1
module.exports =
class Model
PropertyAccessors.includeInto(this)
@resetNextInstanceId: -> nextInstanceId = 1
alive: true
@@ -20,9 +11,7 @@ class Model
assignId: (id) ->
@id ?= id ? nextInstanceId++
@::advisedAccessor 'id',
set: (id) -> nextInstanceId = id + 1 if id >= nextInstanceId
nextInstanceId = id + 1 if id >= nextInstanceId
destroy: ->
return unless @isAlive()

View File

@@ -3,8 +3,6 @@ path = require 'path'
_ = require 'underscore-plus'
{Emitter} = require 'event-kit'
fs = require 'fs-plus'
Q = require 'q'
Grim = require 'grim'
ServiceHub = require 'service-hub'
Package = require './package'
@@ -328,18 +326,10 @@ class PackageManager
# of the first package isn't skewed by being the first to require atom
require '../exports/atom'
# TODO: remove after a few atom versions.
@uninstallAutocompletePlus()
packagePaths = @getAvailablePackagePaths()
# TODO: remove after a few atom versions.
@migrateSublimeTabsSettings(packagePaths)
packagePaths = packagePaths.filter (packagePath) => not @isPackageDisabled(path.basename(packagePath))
packagePaths = _.uniq packagePaths, (packagePath) -> path.basename(packagePath)
@loadPackage(packagePath) for packagePath in packagePaths
@emit 'loaded' if Grim.includeDeprecatedAPIs
@emitter.emit 'did-load-initial-packages'
loadPackage: (nameOrPath) ->
@@ -355,7 +345,7 @@ class PackageManager
@handleMetadataError(error, packagePath)
return null
unless @isBundledPackage(metadata.name) or Grim.includeDeprecatedAPIs
unless @isBundledPackage(metadata.name)
if @isDeprecatedPackage(metadata.name, metadata.version)
console.warn "Could not load #{metadata.name}@#{metadata.version} because it uses deprecated APIs that have been removed."
return null
@@ -392,8 +382,7 @@ class PackageManager
for [activator, types] in @packageActivators
packages = @getLoadedPackagesForTypes(types)
promises = promises.concat(activator.activatePackages(packages))
Q.all(promises).then =>
@emit 'activated' if Grim.includeDeprecatedAPIs
Promise.all(promises).then =>
@emitter.emit 'did-activate-initial-packages'
# another type of package manager can handle other package types.
@@ -415,14 +404,14 @@ class PackageManager
# Activate a single package by name
activatePackage: (name) ->
if pack = @getActivePackage(name)
Q(pack)
Promise.resolve(pack)
else if pack = @loadPackage(name)
pack.activate().then =>
@activePackages[pack.name] = pack
@emitter.emit 'did-activate-package', pack
pack
else
Q.reject(new Error("Failed to load package '#{name}'"))
Promise.reject(new Error("Failed to load package '#{name}'"))
triggerActivationHook: (hook) ->
return new Error("Cannot trigger an empty activation hook") unless hook? and _.isString(hook) and hook.length > 0
@@ -456,35 +445,6 @@ class PackageManager
message = "Failed to load the #{path.basename(packagePath)} package"
atom.notifications.addError(message, {stack, detail, dismissable: true})
# TODO: remove these autocomplete-plus specific helpers after a few versions.
uninstallAutocompletePlus: ->
packageDir = null
devDir = path.join("dev", "packages")
for packageDirPath in @packageDirPaths
if not packageDirPath.endsWith(devDir)
packageDir = packageDirPath
break
if packageDir?
dirsToRemove = [
path.join(packageDir, 'autocomplete-plus')
path.join(packageDir, 'autocomplete-atom-api')
path.join(packageDir, 'autocomplete-css')
path.join(packageDir, 'autocomplete-html')
path.join(packageDir, 'autocomplete-snippets')
]
for dirToRemove in dirsToRemove
@uninstallDirectory(dirToRemove)
return
# TODO: remove this after a few versions
migrateSublimeTabsSettings: (packagePaths) ->
return if Grim.includeDeprecatedAPIs
for packagePath in packagePaths when path.basename(packagePath) is 'sublime-tabs'
atom.config.removeAtKeyPath('core.disabledPackages', 'tree-view')
atom.config.removeAtKeyPath('core.disabledPackages', 'tabs')
return
uninstallDirectory: (directory) ->
symlinkPromise = new Promise (resolve) ->
fs.isSymbolicLink directory, (isSymLink) -> resolve(isSymLink)
@@ -496,25 +456,3 @@ class PackageManager
[isSymLink, isDir] = values
if not isSymLink and isDir
fs.remove directory, ->
if Grim.includeDeprecatedAPIs
EmitterMixin = require('emissary').Emitter
EmitterMixin.includeInto(PackageManager)
PackageManager::on = (eventName) ->
switch eventName
when 'loaded'
Grim.deprecate 'Use PackageManager::onDidLoadInitialPackages instead'
when 'activated'
Grim.deprecate 'Use PackageManager::onDidActivateInitialPackages instead'
else
Grim.deprecate 'PackageManager::on is deprecated. Use event subscription methods instead.'
EmitterMixin::on.apply(this, arguments)
PackageManager::onDidLoadAll = (callback) ->
Grim.deprecate("Use `::onDidLoadInitialPackages` instead.")
@onDidLoadInitialPackages(callback)
PackageManager::onDidActivateAll = (callback) ->
Grim.deprecate("Use `::onDidActivateInitialPackages` instead.")
@onDidActivateInitialPackages(callback)

View File

@@ -6,15 +6,14 @@ async = require 'async'
CSON = require 'season'
fs = require 'fs-plus'
{Emitter, CompositeDisposable} = require 'event-kit'
Q = require 'q'
{includeDeprecatedAPIs, deprecate} = require 'grim'
ModuleCache = require './module-cache'
ScopedProperties = require './scoped-properties'
BufferedProcess = require './buffered-process'
packagesCache = require('../package.json')?._atomPackages ? {}
# Loads and activates a package's main module and resources such as
# Extended: Loads and activates a package's main module and resources such as
# stylesheets, keymaps, grammar, editor properties, and menus.
module.exports =
class Package
@@ -48,14 +47,6 @@ class Package
unless typeof metadata.name is 'string' and metadata.name.length > 0
metadata.name = packageName
if includeDeprecatedAPIs and metadata.stylesheetMain?
deprecate("Use the `mainStyleSheet` key instead of `stylesheetMain` in the `package.json` of `#{packageName}`", {packageName})
metadata.mainStyleSheet = metadata.stylesheetMain
if includeDeprecatedAPIs and metadata.stylesheets?
deprecate("Use the `styleSheets` key instead of `stylesheets` in the `package.json` of `#{packageName}`", {packageName})
metadata.styleSheets = metadata.stylesheets
metadata
keymaps: null
@@ -138,20 +129,21 @@ class Package
activate: ->
@grammarsPromise ?= @loadGrammars()
@activationPromise ?=
new Promise (resolve, reject) =>
@resolveActivationPromise = resolve
@rejectActivationPromise = reject
@measure 'activateTime', =>
try
@activateResources()
if @activationShouldBeDeferred()
@subscribeToDeferredActivation()
else
@activateNow()
catch error
@handleError("Failed to activate the #{@name} package", error)
unless @activationDeferred?
@activationDeferred = Q.defer()
@measure 'activateTime', =>
try
@activateResources()
if @activationShouldBeDeferred()
@subscribeToDeferredActivation()
else
@activateNow()
catch error
@handleError("Failed to activate the #{@name} package", error)
Q.all([@grammarsPromise, @settingsPromise, @activationDeferred.promise])
Promise.all([@grammarsPromise, @settingsPromise, @activationPromise])
activateNow: ->
try
@@ -164,7 +156,7 @@ class Package
catch error
@handleError("Failed to activate the #{@name} package", error)
@activationDeferred?.resolve()
@resolveActivationPromise?()
activateConfig: ->
return if @configActivated
@@ -173,11 +165,6 @@ class Package
if @mainModule?
if @mainModule.config? and typeof @mainModule.config is 'object'
atom.config.setSchema @name, {type: 'object', properties: @mainModule.config}
else if @mainModule.configDefaults? and typeof @mainModule.configDefaults is 'object'
deprecate("""Use a config schema instead. See the configuration section
of https://atom.io/docs/latest/hacking-atom-package-word-count and
https://atom.io/docs/api/latest/Config for more details""", {packageName: @name})
atom.config.setDefaults(@name, @mainModule.configDefaults)
@mainModule.activateConfig?()
@configActivated = true
@@ -210,17 +197,6 @@ class Package
for [menuPath, map] in @menus when map['context-menu']?
try
itemsBySelector = map['context-menu']
# Detect deprecated format for items object
for key, value of itemsBySelector
unless _.isArray(value)
deprecate("""
The context menu CSON format has changed. Please see
https://atom.io/docs/api/latest/ContextMenuManager#context-menu-cson-format
for more info.
""", {packageName: @name})
itemsBySelector = atom.contextMenu.convertLegacyItemsBySelector(itemsBySelector)
@activationDisposables.add(atom.contextMenu.add(itemsBySelector))
catch error
if error.code is 'EBADSELECTOR'
@@ -308,11 +284,7 @@ class Package
[stylesheetPath, atom.themes.loadStylesheet(stylesheetPath, true)]
getStylesheetsPath: ->
if fs.isDirectorySync(path.join(@path, 'stylesheets'))
deprecate("Store package style sheets in the `styles/` directory instead of `stylesheets/` in the `#{@name}` package", packageName: @name)
path.join(@path, 'stylesheets')
else
path.join(@path, 'styles')
path.join(@path, 'styles')
getStylesheetPaths: ->
stylesheetDirPath = @getStylesheetsPath()
@@ -344,7 +316,7 @@ class Package
@grammarsActivated = true
loadGrammars: ->
return Q() if @grammarsLoaded
return Promise.resolve() if @grammarsLoaded
loadGrammar = (grammarPath, callback) =>
atom.grammars.readGrammar grammarPath, (error, grammar) =>
@@ -359,14 +331,13 @@ class Package
grammar.activate() if @grammarsActivated
callback()
deferred = Q.defer()
grammarsDirPath = path.join(@path, 'grammars')
fs.exists grammarsDirPath, (grammarsDirExists) ->
return deferred.resolve() unless grammarsDirExists
new Promise (resolve) =>
grammarsDirPath = path.join(@path, 'grammars')
fs.exists grammarsDirPath, (grammarsDirExists) ->
return resolve() unless grammarsDirExists
fs.list grammarsDirPath, ['json', 'cson'], (error, grammarPaths=[]) ->
async.each grammarPaths, loadGrammar, -> deferred.resolve()
deferred.promise
fs.list grammarsDirPath, ['json', 'cson'], (error, grammarPaths=[]) ->
async.each grammarPaths, loadGrammar, -> resolve()
loadSettings: ->
@settings = []
@@ -382,20 +353,14 @@ class Package
settings.activate() if @settingsActivated
callback()
deferred = Q.defer()
if fs.isDirectorySync(path.join(@path, 'scoped-properties'))
settingsDirPath = path.join(@path, 'scoped-properties')
deprecate("Store package settings files in the `settings/` directory instead of `scoped-properties/`", packageName: @name)
else
new Promise (resolve) =>
settingsDirPath = path.join(@path, 'settings')
fs.exists settingsDirPath, (settingsDirExists) ->
return deferred.resolve() unless settingsDirExists
fs.exists settingsDirPath, (settingsDirExists) ->
return resolve() unless settingsDirExists
fs.list settingsDirPath, ['json', 'cson'], (error, settingsPaths=[]) ->
async.each settingsPaths, loadSettingsFile, -> deferred.resolve()
deferred.promise
fs.list settingsDirPath, ['json', 'cson'], (error, settingsPaths=[]) ->
async.each settingsPaths, loadSettingsFile, -> resolve()
serialize: ->
if @mainActivated
@@ -405,8 +370,10 @@ class Package
console.error "Error serializing package '#{@name}'", e.stack
deactivate: ->
@activationDeferred?.reject()
@activationDeferred = null
@rejectActivationPromise?()
@activationPromise = null
@resolveActivationPromise = null
@rejectActivationPromise = null
@activationCommandSubscriptions?.dispose()
@deactivateResources()
@deactivateConfig()
@@ -417,7 +384,6 @@ class Package
@mainActivated = false
catch e
console.error "Error deactivating package '#{@name}'", e.stack
@emit 'deactivated' if includeDeprecatedAPIs
@emitter.emit 'did-deactivate'
deactivateConfig: ->
@@ -533,31 +499,6 @@ class Package
else if _.isArray(commands)
@activationCommands[selector].push(commands...)
if @metadata.activationEvents?
deprecate("""
Use `activationCommands` instead of `activationEvents` in your package.json
Commands should be grouped by selector as follows:
```json
"activationCommands": {
"atom-workspace": ["foo:bar", "foo:baz"],
"atom-text-editor": ["foo:quux"]
}
```
""", {packageName: @name})
if _.isArray(@metadata.activationEvents)
for eventName in @metadata.activationEvents
@activationCommands['atom-workspace'] ?= []
@activationCommands['atom-workspace'].push(eventName)
else if _.isString(@metadata.activationEvents)
eventName = @metadata.activationEvents
@activationCommands['atom-workspace'] ?= []
@activationCommands['atom-workspace'].push(eventName)
else
for eventName, selector of @metadata.activationEvents
selector ?= 'atom-workspace'
@activationCommands[selector] ?= []
@activationCommands[selector].push(eventName)
@activationCommands
subscribeToActivationHooks: ->
@@ -589,10 +530,20 @@ class Package
false
# Get an array of all the native modules that this package depends on.
# This will recurse through all dependencies.
#
# First try to get this information from
# @metadata._atomModuleCache.extensions. If @metadata._atomModuleCache doesn't
# exist, recurse through all dependencies.
getNativeModuleDependencyPaths: ->
nativeModulePaths = []
if @metadata._atomModuleCache?
relativeNativeModuleBindingPaths = @metadata._atomModuleCache.extensions?['.node'] ? []
for relativeNativeModuleBindingPath in relativeNativeModuleBindingPaths
nativeModulePath = path.join(@path, relativeNativeModuleBindingPath, '..', '..', '..')
nativeModulePaths.push(nativeModulePath)
return nativeModulePaths
traversePath = (nodeModulesPath) =>
try
for modulePath in fs.listSync(nodeModulesPath)
@@ -603,6 +554,70 @@ class Package
traversePath(path.join(@path, 'node_modules'))
nativeModulePaths
###
Section: Native Module Compatibility
###
# Extended: Are all native modules depended on by this package correctly
# compiled against the current version of Atom?
#
# Incompatible packages cannot be activated.
#
# Returns a {Boolean}, true if compatible, false if incompatible.
isCompatible: ->
return @compatible if @compatible?
if @path.indexOf(path.join(atom.packages.resourcePath, 'node_modules') + path.sep) is 0
# Bundled packages are always considered compatible
@compatible = true
else if @getMainModulePath()
@incompatibleModules = @getIncompatibleNativeModules()
@compatible = @incompatibleModules.length is 0 and not @getBuildFailureOutput()?
else
@compatible = true
# Extended: Rebuild native modules in this package's dependencies for the
# current version of Atom.
#
# Returns a {Promise} that resolves with an object containing `code`,
# `stdout`, and `stderr` properties based on the results of running
# `apm rebuild` on the package.
rebuild: ->
new Promise (resolve) =>
@runRebuildProcess (result) =>
if result.code is 0
global.localStorage.removeItem(@getBuildFailureOutputStorageKey())
else
@compatible = false
global.localStorage.setItem(@getBuildFailureOutputStorageKey(), result.stderr)
global.localStorage.setItem(@getIncompatibleNativeModulesStorageKey(), '[]')
resolve(result)
# Extended: If a previous rebuild failed, get the contents of stderr.
#
# Returns a {String} or null if no previous build failure occurred.
getBuildFailureOutput: ->
global.localStorage.getItem(@getBuildFailureOutputStorageKey())
runRebuildProcess: (callback) ->
stderr = ''
stdout = ''
new BufferedProcess({
command: atom.packages.getApmPath()
args: ['rebuild', '--no-color']
options: {cwd: @path}
stderr: (output) -> stderr += output
stdout: (output) -> stdout += output
exit: (code) -> callback({code, stdout, stderr})
})
getBuildFailureOutputStorageKey: ->
"installed-packages:#{@name}:#{@metadata.version}:build-error"
getIncompatibleNativeModulesStorageKey: ->
electronVersion = process.versions['electron'] ? process.versions['atom-shell']
"installed-packages:#{@name}:#{@metadata.version}:electron-#{electronVersion}:incompatible-native-modules"
# Get the incompatible native modules that this package depends on.
# This recurses through all dependencies and requires all modules that
# contain a `.node` file.
@@ -610,11 +625,10 @@ class Package
# This information is cached in local storage on a per package/version basis
# to minimize the impact on startup time.
getIncompatibleNativeModules: ->
localStorageKey = "installed-packages:#{@name}:#{@metadata.version}"
unless atom.inDevMode()
try
{incompatibleNativeModules} = JSON.parse(global.localStorage.getItem(localStorageKey)) ? {}
return incompatibleNativeModules if incompatibleNativeModules?
if arrayAsString = global.localStorage.getItem(@getIncompatibleNativeModulesStorageKey())
return JSON.parse(arrayAsString)
incompatibleNativeModules = []
for nativeModulePath in @getNativeModuleDependencyPaths()
@@ -629,28 +643,9 @@ class Package
version: version
error: error.message
global.localStorage.setItem(localStorageKey, JSON.stringify({incompatibleNativeModules}))
global.localStorage.setItem(@getIncompatibleNativeModulesStorageKey(), JSON.stringify(incompatibleNativeModules))
incompatibleNativeModules
# Public: Is this package compatible with this version of Atom?
#
# Incompatible packages cannot be activated. This will include packages
# installed to ~/.atom/packages that were built against node 0.11.10 but
# now need to be upgrade to node 0.11.13.
#
# Returns a {Boolean}, true if compatible, false if incompatible.
isCompatible: ->
return @compatible if @compatible?
if @path.indexOf(path.join(atom.packages.resourcePath, 'node_modules') + path.sep) is 0
# Bundled packages are always considered compatible
@compatible = true
else if @getMainModulePath()
@incompatibleModules = @getIncompatibleNativeModules()
@compatible = @incompatibleModules.length is 0
else
@compatible = true
handleError: (message, error) ->
if error.filename and error.location and (error instanceof SyntaxError)
location = "#{error.filename}:#{error.location.first_line + 1}:#{error.location.first_column + 1}"
@@ -672,15 +667,3 @@ class Package
stack = error.stack ? error
atom.notifications.addFatalError(message, {stack, detail, dismissable: true})
if includeDeprecatedAPIs
EmitterMixin = require('emissary').Emitter
EmitterMixin.includeInto(Package)
Package::on = (eventName) ->
switch eventName
when 'deactivated'
deprecate 'Use Package::onDidDeactivate instead'
else
deprecate 'Package::on is deprecated. Use event subscription methods instead.'
EmitterMixin::on.apply(this, arguments)

View File

@@ -1,5 +1,4 @@
{CompositeDisposable} = require 'event-kit'
{callAttachHooks} = require './space-pen-extensions'
PaneResizeHandleElement = require './pane-resize-handle-element'
class PaneAxisElement extends HTMLElement
@@ -43,8 +42,6 @@ class PaneAxisElement extends HTMLElement
resizeHandle = document.createElement('atom-pane-resize-handle')
@insertBefore(resizeHandle, nextElement)
callAttachHooks(view) # for backward compatibility with SpacePen views
childRemoved: ({child}) ->
view = atom.views.getView(child)
siblingView = view.previousSibling

View File

@@ -1,17 +1,21 @@
{Emitter, CompositeDisposable} = require 'event-kit'
{flatten} = require 'underscore-plus'
Serializable = require 'serializable'
Model = require './model'
module.exports =
class PaneAxis extends Model
atom.deserializers.add(this)
Serializable.includeInto(this)
parent: null
container: null
orientation: null
@deserialize: (state, params) ->
container = params?.container
state.container = container
state.children = state.children.map (childState) -> atom.deserializers.deserialize(childState, {container})
new this(state)
constructor: ({@container, @orientation, children, flexScale}={}) ->
@emitter = new Emitter
@subscriptionsByChild = new WeakMap
@@ -21,12 +25,8 @@ class PaneAxis extends Model
@addChild(child) for child in children
@flexScale = flexScale ? 1
deserializeParams: (params) ->
{container} = params
params.children = params.children.map (childState) -> atom.deserializers.deserialize(childState, {container})
params
serializeParams: ->
serialize: ->
deserializer: 'PaneAxis'
children: @children.map (child) -> child.serialize()
orientation: @orientation
flexScale: @flexScale

View File

@@ -1,7 +1,4 @@
{CompositeDisposable} = require 'event-kit'
Grim = require 'grim'
{callAttachHooks} = require './space-pen-extensions'
PaneContainerView = null
_ = require 'underscore-plus'
module.exports =
@@ -10,13 +7,8 @@ class PaneContainerElement extends HTMLElement
@subscriptions = new CompositeDisposable
@classList.add 'panes'
if Grim.includeDeprecatedAPIs
PaneContainerView ?= require './pane-container-view'
@__spacePenView = new PaneContainerView(this)
initialize: (@model) ->
@subscriptions.add @model.observeRoot(@rootChanged.bind(this))
@__spacePenView.setModel(@model) if Grim.includeDeprecatedAPIs
this
rootChanged: (root) ->
@@ -25,7 +17,6 @@ class PaneContainerElement extends HTMLElement
if root?
view = atom.views.getView(root)
@appendChild(view)
callAttachHooks(view)
focusedElement?.focus()
hasFocus: ->

View File

@@ -1,89 +0,0 @@
{deprecate} = require 'grim'
Delegator = require 'delegato'
{CompositeDisposable} = require 'event-kit'
{$, View, callAttachHooks} = require './space-pen-extensions'
PaneView = require './pane-view'
PaneContainer = require './pane-container'
# Manages the list of panes within a {WorkspaceView}
module.exports =
class PaneContainerView extends View
Delegator.includeInto(this)
@delegatesMethod 'saveAll', toProperty: 'model'
@content: ->
@div class: 'panes'
constructor: (@element) ->
super
@subscriptions = new CompositeDisposable
setModel: (@model) ->
@subscriptions.add @model.onDidChangeActivePaneItem(@onActivePaneItemChanged)
getRoot: ->
view = atom.views.getView(@model.getRoot())
view.__spacePenView ? view
onActivePaneItemChanged: (activeItem) =>
@trigger 'pane-container:active-pane-item-changed', [activeItem]
confirmClose: ->
@model.confirmClose()
getPaneViews: ->
@find('atom-pane').views()
indexOfPane: (paneView) ->
@getPaneViews().indexOf(paneView.view())
paneAtIndex: (index) ->
@getPaneViews()[index]
eachPaneView: (callback) ->
callback(paneView) for paneView in @getPaneViews()
paneViewAttached = (e) -> callback($(e.target).view())
@on 'pane:attached', paneViewAttached
off: => @off 'pane:attached', paneViewAttached
getFocusedPane: ->
@find('atom-pane:has(:focus)').view()
getActivePane: ->
deprecate("Use PaneContainerView::getActivePaneView instead.")
@getActivePaneView()
getActivePaneView: ->
atom.views.getView(@model.getActivePane()).__spacePenView
getActivePaneItem: ->
@model.getActivePaneItem()
getActiveView: ->
@getActivePaneView()?.activeView
paneForUri: (uri) ->
atom.views.getView(@model.paneForURI(uri)).__spacePenView
focusNextPaneView: ->
@model.activateNextPane()
focusPreviousPaneView: ->
@model.activatePreviousPane()
focusPaneViewAbove: ->
@element.focusPaneViewAbove()
focusPaneViewBelow: ->
@element.focusPaneViewBelow()
focusPaneViewOnLeft: ->
@element.focusPaneViewOnLeft()
focusPaneViewOnRight: ->
@element.focusPaneViewOnRight()
getPanes: ->
deprecate("Use PaneContainerView::getPaneViews() instead")
@getPaneViews()

View File

@@ -1,7 +1,5 @@
{find, flatten} = require 'underscore-plus'
Grim = require 'grim'
{Emitter, CompositeDisposable} = require 'event-kit'
Serializable = require 'serializable'
Gutter = require './gutter'
Model = require './model'
Pane = require './pane'
@@ -10,17 +8,23 @@ ItemRegistry = require './item-registry'
module.exports =
class PaneContainer extends Model
atom.deserializers.add(this)
Serializable.includeInto(this)
@version: 1
root: null
@deserialize: (state) ->
container = Object.create(@prototype) # allows us to pass a self reference to our child before invoking constructor
state.root = atom.deserializers.deserialize(state.root, {container})
state.destroyEmptyPanes = atom.config.get('core.destroyEmptyPanes')
state.activePane = find state.root.getPanes(), (pane) -> pane.id is state.activePaneId
@call(container, state) # run constructor
container
constructor: (params) ->
super
unless Grim.includeDeprecatedAPIs
@activePane = params?.activePane
@activePane = params?.activePane
@emitter = new Emitter
@subscriptions = new CompositeDisposable
@@ -35,13 +39,9 @@ class PaneContainer extends Model
@monitorActivePaneItem()
@monitorPaneItems()
deserializeParams: (params) ->
params.root = atom.deserializers.deserialize(params.root, container: this)
params.destroyEmptyPanes = atom.config.get('core.destroyEmptyPanes')
params.activePane = find params.root.getPanes(), (pane) -> pane.id is params.activePaneId
params
serializeParams: (params) ->
serialize: (params) ->
deserializer: 'PaneContainer'
version: @constructor.version
root: @root?.serialize()
activePaneId: @activePane.id
@@ -222,12 +222,3 @@ class PaneContainer extends Model
removedPaneItem: (item) ->
@itemRegistry.removeItem(item)
if Grim.includeDeprecatedAPIs
PaneContainer.properties
activePane: null
PaneContainer.behavior 'activePaneItem', ->
@$activePane
.switch((activePane) -> activePane?.$activeItem)
.distinctUntilChanged()

View File

@@ -1,8 +1,5 @@
path = require 'path'
{CompositeDisposable} = require 'event-kit'
Grim = require 'grim'
{$, callAttachHooks, callRemoveHooks} = require './space-pen-extensions'
PaneView = null
class PaneElement extends HTMLElement
attached: false
@@ -14,7 +11,6 @@ class PaneElement extends HTMLElement
@initializeContent()
@subscribeToDOMEvents()
@createSpacePenShim() if Grim.includeDeprecatedAPIs
attachedCallback: ->
@attached = true
@@ -55,10 +51,6 @@ class PaneElement extends HTMLElement
@addEventListener 'dragover', handleDragOver
@addEventListener 'drop', handleDrop
createSpacePenShim: ->
PaneView ?= require './pane-view'
@__spacePenView = new PaneView(this)
initialize: (@model) ->
@subscriptions.add @model.onDidActivate(@activated.bind(this))
@subscriptions.add @model.observeActive(@activeStatusChanged.bind(this))
@@ -66,8 +58,6 @@ class PaneElement extends HTMLElement
@subscriptions.add @model.onDidRemoveItem(@itemRemoved.bind(this))
@subscriptions.add @model.onDidDestroy(@paneDestroyed.bind(this))
@subscriptions.add @model.observeFlexScale(@flexScaleChanged.bind(this))
@__spacePenView.setModel(@model) if Grim.includeDeprecatedAPIs
this
getModel: -> @model
@@ -96,7 +86,6 @@ class PaneElement extends HTMLElement
unless @itemViews.contains(itemView)
@itemViews.appendChild(itemView)
callAttachHooks(itemView)
for child in @itemViews.children
if child is itemView
@@ -121,7 +110,6 @@ class PaneElement extends HTMLElement
itemRemoved: ({item, index, destroyed}) ->
if viewToRemove = atom.views.getView(item)
callRemoveHooks(viewToRemove) if destroyed
viewToRemove.remove()
paneDestroyed: ->

View File

@@ -1,167 +0,0 @@
{$, View} = require './space-pen-extensions'
Delegator = require 'delegato'
{deprecate} = require 'grim'
{CompositeDisposable} = require 'event-kit'
PropertyAccessors = require 'property-accessors'
Pane = require './pane'
# A container which can contains multiple items to be switched between.
#
# Items can be almost anything however most commonly they're {TextEditorView}s.
#
# Most packages won't need to use this class, unless you're interested in
# building a package that deals with switching between panes or items.
module.exports =
class PaneView extends View
Delegator.includeInto(this)
PropertyAccessors.includeInto(this)
@delegatesProperties 'items', 'activeItem', toProperty: 'model'
@delegatesMethods 'getItems', 'activateNextItem', 'activatePreviousItem', 'getActiveItemIndex',
'activateItemAtIndex', 'activateItem', 'addItem', 'itemAtIndex', 'moveItem', 'moveItemToPane',
'destroyItem', 'destroyItems', 'destroyActiveItem', 'destroyInactiveItems',
'saveActiveItem', 'saveActiveItemAs', 'saveItem', 'saveItemAs', 'saveItems',
'itemForUri', 'activateItemForUri', 'promptToSaveItem', 'copyActiveItem', 'isActive',
'activate', 'getActiveItem', toProperty: 'model'
previousActiveItem: null
attached: false
constructor: (@element) ->
@itemViews = $(element.itemViews)
super
setModel: (@model) ->
@subscriptions = new CompositeDisposable
@subscriptions.add @model.observeActiveItem(@onActiveItemChanged)
@subscriptions.add @model.onDidAddItem(@onItemAdded)
@subscriptions.add @model.onDidRemoveItem(@onItemRemoved)
@subscriptions.add @model.onDidMoveItem(@onItemMoved)
@subscriptions.add @model.onWillDestroyItem(@onBeforeItemDestroyed)
@subscriptions.add @model.observeActive(@onActiveStatusChanged)
@subscriptions.add @model.onDidDestroy(@onPaneDestroyed)
afterAttach: ->
@container ?= @closest('atom-pane-container').view()
@trigger('pane:attached', [this]) unless @attached
@attached = true
onPaneDestroyed: =>
@container?.trigger 'pane:removed', [this]
@subscriptions.dispose()
remove: ->
@model.destroy() unless @model.isDestroyed()
# Essential: Returns the {Pane} model underlying this pane view
getModel: -> @model
# Deprecated: Use ::destroyItem
removeItem: (item) ->
deprecate("Use PaneView::destroyItem instead")
@destroyItem(item)
# Deprecated: Use ::activateItem
showItem: (item) ->
deprecate("Use PaneView::activateItem instead")
@activateItem(item)
# Deprecated: Use ::activateItemForUri
showItemForUri: (item) ->
deprecate("Use PaneView::activateItemForUri instead")
@activateItemForUri(item)
# Deprecated: Use ::activateItemAtIndex
showItemAtIndex: (index) ->
deprecate("Use PaneView::activateItemAtIndex instead")
@activateItemAtIndex(index)
# Deprecated: Use ::activateNextItem
showNextItem: ->
deprecate("Use PaneView::activateNextItem instead")
@activateNextItem()
# Deprecated: Use ::activatePreviousItem
showPreviousItem: ->
deprecate("Use PaneView::activatePreviousItem instead")
@activatePreviousItem()
onActiveStatusChanged: (active) =>
if active
@trigger 'pane:became-active'
else
@trigger 'pane:became-inactive'
# Public: Returns the next pane, ordered by creation.
getNextPane: ->
panes = @container?.getPaneViews()
return unless panes.length > 1
nextIndex = (panes.indexOf(this) + 1) % panes.length
panes[nextIndex]
getActivePaneItem: ->
@activeItem
onActiveItemChanged: (item) =>
@activeItemDisposables.dispose() if @activeItemDisposables?
@activeItemDisposables = new CompositeDisposable()
if @previousActiveItem?.off?
@previousActiveItem.off 'title-changed', @activeItemTitleChanged
@previousActiveItem.off 'modified-status-changed', @activeItemModifiedChanged
@previousActiveItem = item
return unless item?
if item.onDidChangeTitle?
disposable = item.onDidChangeTitle(@activeItemTitleChanged)
@activeItemDisposables.add(disposable) if disposable?.dispose?
else if item.on?
disposable = item.on('title-changed', @activeItemTitleChanged)
@activeItemDisposables.add(disposable) if disposable?.dispose?
if item.onDidChangeModified?
disposable = item.onDidChangeModified(@activeItemModifiedChanged)
@activeItemDisposables.add(disposable) if disposable?.dispose?
else if item.on?
item.on('modified-status-changed', @activeItemModifiedChanged)
@activeItemDisposables.add(disposable) if disposable?.dispose?
@trigger 'pane:active-item-changed', [item]
onItemAdded: ({item, index}) =>
@trigger 'pane:item-added', [item, index]
onItemRemoved: ({item, index, destroyed}) =>
@trigger 'pane:item-removed', [item, index]
onItemMoved: ({item, newIndex}) =>
@trigger 'pane:item-moved', [item, newIndex]
onBeforeItemDestroyed: ({item}) =>
@unsubscribe(item) if typeof item.off is 'function'
@trigger 'pane:before-item-destroyed', [item]
activeItemTitleChanged: =>
@trigger 'pane:active-item-title-changed'
activeItemModifiedChanged: =>
@trigger 'pane:active-item-modified-status-changed'
@::accessor 'activeView', ->
element = atom.views.getView(@activeItem)
$(element).view() ? element
splitLeft: (items...) -> atom.views.getView(@model.splitLeft({items})).__spacePenView
splitRight: (items...) -> atom.views.getView(@model.splitRight({items})).__spacePenView
splitUp: (items...) -> atom.views.getView(@model.splitUp({items})).__spacePenView
splitDown: (items...) -> atom.views.getView(@model.splitDown({items})).__spacePenView
getContainer: -> @closest('atom-pane-container').view()
focus: ->
@element.focus()

View File

@@ -1,7 +1,5 @@
{find, compact, extend, last} = require 'underscore-plus'
{Emitter} = require 'event-kit'
Serializable = require 'serializable'
Grim = require 'grim'
Model = require './model'
PaneAxis = require './pane-axis'
TextEditor = require './text-editor'
@@ -13,14 +11,29 @@ TextEditor = require './text-editor'
module.exports =
class Pane extends Model
atom.deserializers.add(this)
Serializable.includeInto(this)
container: undefined
activeItem: undefined
focused: false
@deserialize: (state, params) ->
{items, activeItemURI, activeItemUri} = state
state.container = params?.container
activeItemURI ?= activeItemUri
state.items = compact(items.map (itemState) -> atom.deserializers.deserialize(itemState))
state.activeItem = find state.items, (item) ->
if typeof item.getURI is 'function'
itemURI = item.getURI()
itemURI is activeItemURI
new this(state)
constructor: (params) ->
super
unless Grim.includeDeprecatedAPIs
@container = params?.container
@activeItem = params?.activeItem
@container = params?.container
@activeItem = params?.activeItem
@focused = params?.focused
@emitter = new Emitter
@itemSubscriptions = new WeakMap
@@ -30,33 +43,17 @@ class Pane extends Model
@setActiveItem(@items[0]) unless @getActiveItem()?
@setFlexScale(params?.flexScale ? 1)
# Called by the Serializable mixin during serialization.
serializeParams: ->
serialize: ->
if typeof @activeItem?.getURI is 'function'
activeItemURI = @activeItem.getURI()
else if Grim.includeDeprecatedAPIs and typeof @activeItem?.getUri is 'function'
activeItemURI = @activeItem.getUri()
deserializer: 'Pane'
id: @id
items: compact(@items.map((item) -> item.serialize?()))
activeItemURI: activeItemURI
focused: @focused
flexScale: @flexScale
# Called by the Serializable mixin during deserialization.
deserializeParams: (params) ->
{items, activeItemURI, activeItemUri} = params
activeItemURI ?= activeItemUri
params.items = compact(items.map (itemState) -> atom.deserializers.deserialize(itemState))
params.activeItem = find params.items, (item) ->
if typeof item.getURI is 'function'
itemURI = item.getURI()
else if Grim.includeDeprecatedAPIs and typeof item.getUri is 'function'
itemURI = item.getUri()
itemURI is activeItemURI
params
getParent: -> @parent
setParent: (@parent) -> @parent
@@ -355,16 +352,14 @@ class Pane extends Model
# Returns the added item.
addItem: (item, index=@getActiveItemIndex() + 1) ->
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 typeof item.onDidDestroy is 'function'
@itemSubscriptions.set item, item.onDidDestroy => @removeItem(item, true)
else if Grim.includeDeprecatedAPIs and typeof item.on is 'function'
@subscribe item, 'destroyed', => @removeItem(item, true)
@items.splice(index, 0, item)
@emit 'item-added', item, index if Grim.includeDeprecatedAPIs
@emitter.emit 'did-add-item', {item, index}
@setActiveItem(item) unless @getActiveItem()?
item
@@ -388,9 +383,6 @@ class Pane extends Model
return if index is -1
@emitter.emit 'will-remove-item', {item, index, destroyed}
if Grim.includeDeprecatedAPIs and typeof item.on is 'function'
@unsubscribe item
@unsubscribeFromItem(item)
if item is @activeItem
@@ -401,7 +393,6 @@ class Pane extends Model
else
@activatePreviousItem()
@items.splice(index, 1)
@emit 'item-removed', item, index, destroyed if Grim.includeDeprecatedAPIs
@emitter.emit 'did-remove-item', {item, index, destroyed}
@container?.didDestroyPaneItem({item, index, pane: this}) if destroyed
@destroy() if @items.length is 0 and atom.config.get('core.destroyEmptyPanes')
@@ -414,7 +405,6 @@ class Pane extends Model
oldIndex = @items.indexOf(item)
@items.splice(oldIndex, 1)
@items.splice(newIndex, 0, item)
@emit 'item-moved', item, newIndex if Grim.includeDeprecatedAPIs
@emitter.emit 'did-move-item', {item, oldIndex, newIndex}
# Public: Move the given item to the given index on another pane.
@@ -442,7 +432,6 @@ class Pane extends Model
destroyItem: (item) ->
index = @items.indexOf(item)
if index isnt -1
@emit 'before-item-destroyed', item if Grim.includeDeprecatedAPIs
@emitter.emit 'will-destroy-item', {item, index}
@container?.willDestroyPaneItem({item, index, pane: this})
if @promptToSaveItem(item)
@@ -582,7 +571,6 @@ class Pane extends Model
throw new Error("Pane has been destroyed") if @isDestroyed()
@container?.setActivePane(this)
@emit 'activated' if Grim.includeDeprecatedAPIs
@emitter.emit 'did-activate'
# Public: Close the pane and destroy all its items.
@@ -731,60 +719,3 @@ class Pane extends Model
atom.notifications.addWarning("Unable to save file: A directory in the path '#{fileName}' could not be written to")
else
throw error
if Grim.includeDeprecatedAPIs
Pane.properties
container: undefined
activeItem: undefined
focused: false
Pane.behavior 'active', ->
@$container
.switch((container) -> container?.$activePane)
.map((activePane) => activePane is this)
.distinctUntilChanged()
Pane::on = (eventName) ->
switch eventName
when 'activated'
Grim.deprecate("Use Pane::onDidActivate instead")
when 'destroyed'
Grim.deprecate("Use Pane::onDidDestroy instead")
when 'item-added'
Grim.deprecate("Use Pane::onDidAddItem instead")
when 'item-removed'
Grim.deprecate("Use Pane::onDidRemoveItem instead")
when 'item-moved'
Grim.deprecate("Use Pane::onDidMoveItem instead")
when 'before-item-destroyed'
Grim.deprecate("Use Pane::onWillDestroyItem instead")
else
Grim.deprecate("Subscribing via ::on is deprecated. Use documented event subscription methods instead.")
super
Pane::behavior = (behaviorName) ->
switch behaviorName
when 'active'
Grim.deprecate("The $active behavior property is deprecated. Use ::observeActive or ::onDidChangeActive instead.")
when 'container'
Grim.deprecate("The $container behavior property is deprecated.")
when 'activeItem'
Grim.deprecate("The $activeItem behavior property is deprecated. Use ::observeActiveItem or ::onDidChangeActiveItem instead.")
when 'focused'
Grim.deprecate("The $focused behavior property is deprecated.")
else
Grim.deprecate("Pane::behavior is deprecated. Use event subscription methods instead.")
super
Pane::itemForUri = (uri) ->
Grim.deprecate("Use `::itemForURI` instead.")
@itemForURI(uri)
Pane::activateItemForUri = (uri) ->
Grim.deprecate("Use `::activateItemForURI` instead.")
@activateItemForURI(uri)
else
Pane::container = undefined
Pane::activeItem = undefined
Pane::focused = undefined

View File

@@ -1,5 +1,4 @@
{CompositeDisposable} = require 'event-kit'
{callAttachHooks} = require './space-pen-extensions'
Panel = require './panel'
class PanelElement extends HTMLElement
@@ -21,7 +20,6 @@ class PanelElement extends HTMLElement
atom.views.getView(@getModel().getItem())
attachedCallback: ->
callAttachHooks(@getItemView()) # for backward compatibility with SpacePen views
@visibleChanged(@getModel().isVisible())
visibleChanged: (visible) ->

View File

@@ -3,12 +3,8 @@ url = require 'url'
_ = require 'underscore-plus'
fs = require 'fs-plus'
Q = require 'q'
{includeDeprecatedAPIs, deprecate} = require 'grim'
{Emitter} = require 'event-kit'
Serializable = require 'serializable'
TextBuffer = require 'text-buffer'
Grim = require 'grim'
DefaultDirectoryProvider = require './default-directory-provider'
Model = require './model'
@@ -22,12 +18,25 @@ GitRepositoryProvider = require './git-repository-provider'
module.exports =
class Project extends Model
atom.deserializers.add(this)
Serializable.includeInto(this)
###
Section: Construction and Destruction
###
@deserialize: (state) ->
state.buffers = _.compact state.buffers.map (bufferState) ->
# Check that buffer's file path is accessible
return if fs.isDirectorySync(bufferState.filePath)
if bufferState.filePath
try
fs.closeSync(fs.openSync(bufferState.filePath, 'r'))
catch error
return unless error.code is 'ENOENT'
atom.deserializers.deserialize(bufferState)
new this(state)
constructor: ({path, paths, @buffers}={}) ->
@emitter = new Emitter
@buffers ?= []
@@ -63,9 +72,6 @@ class Project extends Model
@subscribeToBuffer(buffer) for buffer in @buffers
if Grim.includeDeprecatedAPIs and path?
Grim.deprecate("Pass 'paths' array instead of 'path' to project constructor")
paths ?= _.compact([path])
@setPaths(paths)
@@ -81,23 +87,11 @@ class Project extends Model
Section: Serialization
###
serializeParams: ->
serialize: ->
deserializer: 'Project'
paths: @getPaths()
buffers: _.compact(@buffers.map (buffer) -> buffer.serialize() if buffer.isRetained())
deserializeParams: (params) ->
params.buffers = _.compact params.buffers.map (bufferState) ->
# Check that buffer's file path is accessible
return if fs.isDirectorySync(bufferState.filePath)
if bufferState.filePath
try
fs.closeSync(fs.openSync(bufferState.filePath, 'r'))
catch error
return unless error.code is 'ENOENT'
atom.deserializers.deserialize(bufferState)
params
###
Section: Event Subscription
###
@@ -167,16 +161,12 @@ class Project extends Model
#
# * `projectPaths` {Array} of {String} paths.
setPaths: (projectPaths) ->
if includeDeprecatedAPIs
rootDirectory.off() for rootDirectory in @rootDirectories
repository?.destroy() for repository in @repositories
@rootDirectories = []
@repositories = []
@addPath(projectPath, emitEvent: false) for projectPath in projectPaths
@emit "path-changed" if includeDeprecatedAPIs
@emitter.emit 'did-change-paths', projectPaths
# Public: Add a path to the project's list of root paths
@@ -201,7 +191,6 @@ class Project extends Model
@repositories.push(repo ? null)
unless options?.emitEvent is false
@emit "path-changed" if includeDeprecatedAPIs
@emitter.emit 'did-change-paths', @getPaths()
# Public: remove a path from the project's list of root paths.
@@ -221,9 +210,7 @@ class Project extends Model
if indexToRemove?
[removedDirectory] = @rootDirectories.splice(indexToRemove, 1)
[removedRepository] = @repositories.splice(indexToRemove, 1)
removedDirectory.off() if includeDeprecatedAPIs
removedRepository?.destroy() unless removedRepository in @repositories
@emit "path-changed" if includeDeprecatedAPIs
@emitter.emit "did-change-paths", @getPaths()
true
else
@@ -367,8 +354,11 @@ class Project extends Model
#
# Returns a promise that resolves to the {TextBuffer}.
bufferForPath: (absoluteFilePath) ->
existingBuffer = @findBufferForPath(absoluteFilePath) if absoluteFilePath
Q(existingBuffer ? @buildBuffer(absoluteFilePath))
existingBuffer = @findBufferForPath(absoluteFilePath) if absoluteFilePath?
if existingBuffer
Promise.resolve(existingBuffer)
else
@buildBuffer(absoluteFilePath)
bufferForId: (id) ->
_.find @buffers, (buffer) -> buffer.id is id
@@ -400,7 +390,6 @@ class Project extends Model
addBufferAtIndex: (buffer, index, options={}) ->
@buffers.splice(index, 0, buffer)
@subscribeToBuffer(buffer)
@emit 'buffer-created', buffer if includeDeprecatedAPIs
@emitter.emit 'did-add-buffer', buffer
buffer
@@ -440,66 +429,3 @@ class Project extends Model
""",
detail: error.message
dismissable: true
if includeDeprecatedAPIs
Project.pathForRepositoryUrl = (repoUrl) ->
deprecate '::pathForRepositoryUrl will be removed. Please remove from your code.'
[repoName] = url.parse(repoUrl).path.split('/')[-1..]
repoName = repoName.replace(/\.git$/, '')
path.join(atom.config.get('core.projectHome'), repoName)
Project::registerOpener = (opener) ->
deprecate("Use Workspace::addOpener instead")
atom.workspace.addOpener(opener)
Project::unregisterOpener = (opener) ->
deprecate("Call .dispose() on the Disposable returned from ::addOpener instead")
atom.workspace.unregisterOpener(opener)
Project::eachEditor = (callback) ->
deprecate("Use Workspace::observeTextEditors instead")
atom.workspace.observeTextEditors(callback)
Project::getEditors = ->
deprecate("Use Workspace::getTextEditors instead")
atom.workspace.getTextEditors()
Project::on = (eventName) ->
if eventName is 'path-changed'
Grim.deprecate("Use Project::onDidChangePaths instead")
else
Grim.deprecate("Project::on is deprecated. Use documented event subscription methods instead.")
super
Project::getRepo = ->
Grim.deprecate("Use ::getRepositories instead")
@getRepositories()[0]
Project::getPath = ->
Grim.deprecate("Use ::getPaths instead")
@getPaths()[0]
Project::setPath = (path) ->
Grim.deprecate("Use ::setPaths instead")
@setPaths([path])
Project::getRootDirectory = ->
Grim.deprecate("Use ::getDirectories instead")
@getDirectories()[0]
Project::resolve = (uri) ->
Grim.deprecate("Use `Project::getDirectories()[0]?.resolve()` instead")
@resolvePath(uri)
Project::scan = (regex, options={}, iterator) ->
Grim.deprecate("Use atom.workspace.scan instead of atom.project.scan")
atom.workspace.scan(regex, options, iterator)
Project::replace = (regex, replacementText, filePaths, iterator) ->
Grim.deprecate("Use atom.workspace.replace instead of atom.project.replace")
atom.workspace.replace(regex, replacementText, filePaths, iterator)
Project::openSync = (filePath, options={}) ->
deprecate("Use Project::open instead")
filePath = @resolvePath(filePath)
@buildEditorForBuffer(@bufferForPathSync(filePath), options)

View File

@@ -1,38 +0,0 @@
{View} = require './space-pen-extensions'
# Deprecated: Represents a view that scrolls.
#
# Handles several core events to update scroll position:
#
# * `core:move-up` Scrolls the view up
# * `core:move-down` Scrolls the view down
# * `core:page-up` Scrolls the view up by the height of the page
# * `core:page-down` Scrolls the view down by the height of the page
# * `core:move-to-top` Scrolls the editor to the top
# * `core:move-to-bottom` Scroll the editor to the bottom
#
# Subclasses must call `super` if overriding the `initialize` method.
#
# ## Examples
#
# ```coffee
# {ScrollView} = require 'atom'
#
# class MyView extends ScrollView
# @content: ->
# @div()
#
# initialize: ->
# super
# @text('super long content that will scroll')
# ```
#
module.exports =
class ScrollView extends View
initialize: ->
@on 'core:move-up', => @scrollUp()
@on 'core:move-down', => @scrollDown()
@on 'core:page-up', => @pageUp()
@on 'core:page-down', => @pageDown()
@on 'core:move-to-top', => @scrollToTop()
@on 'core:move-to-bottom', => @scrollToBottom()

View File

@@ -1,312 +0,0 @@
{$, View} = require './space-pen-extensions'
TextEditorView = require './text-editor-view'
fuzzyFilter = require('fuzzaldrin').filter
# Deprecated: Provides a view that renders a list of items with an editor that
# filters the items. Used by many packages such as the fuzzy-finder,
# command-palette, symbols-view and autocomplete.
#
# Subclasses must implement the following methods:
#
# * {::viewForItem}
# * {::confirmed}
#
# ## Requiring in packages
#
# ```coffee
# {SelectListView} = require 'atom'
#
# class MySelectListView extends SelectListView
# initialize: ->
# super
# @addClass('overlay from-top')
# @setItems(['Hello', 'World'])
# atom.workspaceView.append(this)
# @focusFilterEditor()
#
# viewForItem: (item) ->
# "<li>#{item}</li>"
#
# confirmed: (item) ->
# console.log("#{item} was selected")
# ```
module.exports =
class SelectListView extends View
@content: ->
@div class: 'select-list', =>
@subview 'filterEditorView', new TextEditorView(mini: true)
@div class: 'error-message', outlet: 'error'
@div class: 'loading', outlet: 'loadingArea', =>
@span class: 'loading-message', outlet: 'loading'
@span class: 'badge', outlet: 'loadingBadge'
@ol class: 'list-group', outlet: 'list'
maxItems: Infinity
scheduleTimeout: null
inputThrottle: 50
cancelling: false
###
Section: Construction
###
# Essential: Initialize the select list view.
#
# This method can be overridden by subclasses but `super` should always
# be called.
initialize: ->
@filterEditorView.getEditor().getBuffer().onDidChange =>
@schedulePopulateList()
@filterEditorView.on 'blur', =>
@cancel() unless @cancelling
# This prevents the focusout event from firing on the filter editor view
# when the list is scrolled by clicking the scrollbar and dragging.
@list.on 'mousedown', ({target}) =>
false if target is @list[0]
@on 'core:move-up', =>
@selectPreviousItemView()
@on 'core:move-down', =>
@selectNextItemView()
@on 'core:move-to-top', =>
@selectItemView(@list.find('li:first'))
@list.scrollToTop()
false
@on 'core:move-to-bottom', =>
@selectItemView(@list.find('li:last'))
@list.scrollToBottom()
false
@on 'core:confirm', => @confirmSelection()
@on 'core:cancel', => @cancel()
@list.on 'mousedown', 'li', (e) =>
@selectItemView($(e.target).closest('li'))
e.preventDefault()
@list.on 'mouseup', 'li', (e) =>
@confirmSelection() if $(e.target).closest('li').hasClass('selected')
e.preventDefault()
###
Section: Methods that must be overridden
###
# Essential: Create a view for the given model item.
#
# This method must be overridden by subclasses.
#
# This is called when the item is about to appended to the list view.
#
# * `item` The model item being rendered. This will always be one of the items
# previously passed to {::setItems}.
#
# Returns a String of HTML, DOM element, jQuery object, or View.
viewForItem: (item) ->
throw new Error("Subclass must implement a viewForItem(item) method")
# Essential: Callback function for when an item is selected.
#
# This method must be overridden by subclasses.
#
# * `item` The selected model item. This will always be one of the items
# previously passed to {::setItems}.
#
# Returns a DOM element, jQuery object, or {View}.
confirmed: (item) ->
throw new Error("Subclass must implement a confirmed(item) method")
###
Section: Managing the list of items
###
# Essential: Set the array of items to display in the list.
#
# This should be model items not actual views. {::viewForItem} will be
# called to render the item when it is being appended to the list view.
#
# * `items` The {Array} of model items to display in the list (default: []).
setItems: (@items=[]) ->
@populateList()
@setLoading()
# Essential: Get the model item that is currently selected in the list view.
#
# Returns a model item.
getSelectedItem: ->
@getSelectedItemView().data('select-list-item')
# Extended: Get the property name to use when filtering items.
#
# This method may be overridden by classes to allow fuzzy filtering based
# on a specific property of the item objects.
#
# For example if the objects you pass to {::setItems} are of the type
# `{"id": 3, "name": "Atom"}` then you would return `"name"` from this method
# to fuzzy filter by that property when text is entered into this view's
# editor.
#
# Returns the property name to fuzzy filter by.
getFilterKey: ->
# Extended: Get the filter query to use when fuzzy filtering the visible
# elements.
#
# By default this method returns the text in the mini editor but it can be
# overridden by subclasses if needed.
#
# Returns a {String} to use when fuzzy filtering the elements to display.
getFilterQuery: ->
@filterEditorView.getEditor().getText()
# Extended: Set the maximum numbers of items to display in the list.
#
# * `maxItems` The maximum {Number} of items to display.
setMaxItems: (@maxItems) ->
# Extended: Populate the list view with the model items previously set by
# calling {::setItems}.
#
# Subclasses may override this method but should always call `super`.
populateList: ->
return unless @items?
filterQuery = @getFilterQuery()
if filterQuery.length
filteredItems = fuzzyFilter(@items, filterQuery, key: @getFilterKey())
else
filteredItems = @items
@list.empty()
if filteredItems.length
@setError(null)
for i in [0...Math.min(filteredItems.length, @maxItems)]
item = filteredItems[i]
itemView = $(@viewForItem(item))
itemView.data('select-list-item', item)
@list.append(itemView)
@selectItemView(@list.find('li:first'))
else
@setError(@getEmptyMessage(@items.length, filteredItems.length))
###
Section: Messages to the user
###
# Essential: Set the error message to display.
#
# * `message` The {String} error message (default: '').
setError: (message='') ->
if message.length is 0
@error.text('').hide()
else
@setLoading()
@error.text(message).show()
# Essential: Set the loading message to display.
#
# * `message` The {String} loading message (default: '').
setLoading: (message='') ->
if message.length is 0
@loading.text("")
@loadingBadge.text("")
@loadingArea.hide()
else
@setError()
@loading.text(message)
@loadingArea.show()
# Extended: Get the message to display when there are no items.
#
# Subclasses may override this method to customize the message.
#
# * `itemCount` The {Number} of items in the array specified to {::setItems}
# * `filteredItemCount` The {Number} of items that pass the fuzzy filter test.
#
# Returns a {String} message (default: 'No matches found').
getEmptyMessage: (itemCount, filteredItemCount) -> 'No matches found'
###
Section: View Actions
###
# Essential: Cancel and close this select list view.
#
# This restores focus to the previously focused element if
# {::storeFocusedElement} was called prior to this view being attached.
cancel: ->
@list.empty()
@cancelling = true
filterEditorViewFocused = @filterEditorView.isFocused
@cancelled()
@detach()
@restoreFocus() if filterEditorViewFocused
@cancelling = false
clearTimeout(@scheduleTimeout)
# Extended: Focus the fuzzy filter editor view.
focusFilterEditor: ->
@filterEditorView.focus()
# Extended: Store the currently focused element. This element will be given
# back focus when {::cancel} is called.
storeFocusedElement: ->
@previouslyFocusedElement = $(document.activeElement)
###
Section: Private
###
selectPreviousItemView: ->
view = @getSelectedItemView().prev()
view = @list.find('li:last') unless view.length
@selectItemView(view)
selectNextItemView: ->
view = @getSelectedItemView().next()
view = @list.find('li:first') unless view.length
@selectItemView(view)
selectItemView: (view) ->
return unless view.length
@list.find('.selected').removeClass('selected')
view.addClass('selected')
@scrollToItemView(view)
scrollToItemView: (view) ->
scrollTop = @list.scrollTop()
desiredTop = view.position().top + scrollTop
desiredBottom = desiredTop + view.outerHeight()
if desiredTop < scrollTop
@list.scrollTop(desiredTop)
else if desiredBottom > @list.scrollBottom()
@list.scrollBottom(desiredBottom)
restoreFocus: ->
if @previouslyFocusedElement?.isOnDom()
@previouslyFocusedElement.focus()
else
atom.workspaceView.focus()
cancelled: ->
@filterEditorView.getEditor().setText('')
getSelectedItemView: ->
@list.find('li.selected')
confirmSelection: ->
item = @getSelectedItem()
if item?
@confirmed(item)
else
@cancel()
schedulePopulateList: ->
clearTimeout(@scheduleTimeout)
populateCallback = =>
@populateList() if @isOnDom()
@scheduleTimeout = setTimeout(populateCallback, @inputThrottle)

View File

@@ -1,7 +1,6 @@
{Point, Range} = require 'text-buffer'
{pick} = _ = require 'underscore-plus'
{Emitter} = require 'event-kit'
Grim = require 'grim'
Model = require './model'
NonWhitespaceRegExp = /\S/
@@ -257,10 +256,15 @@ class Selection extends Model
@modifySelection => @cursor.moveToFirstCharacterOfLine()
# Public: Selects all the text from the current cursor position to the end of
# the line.
# the screen line.
selectToEndOfLine: ->
@modifySelection => @cursor.moveToEndOfScreenLine()
# Public: Selects all the text from the current cursor position to the end of
# the buffer line.
selectToEndOfBufferLine: ->
@modifySelection => @cursor.moveToEndOfLine()
# Public: Selects all the text from the current cursor position to the
# beginning of the word.
selectToBeginningOfWord: ->
@@ -572,11 +576,16 @@ class Selection extends Model
toggleLineComments: ->
@editor.toggleLineCommentsForBufferRows(@getBufferRowRange()...)
# Public: Cuts the selection until the end of the line.
# Public: Cuts the selection until the end of the screen line.
cutToEndOfLine: (maintainClipboard) ->
@selectToEndOfLine() if @isEmpty()
@cut(maintainClipboard)
# Public: Cuts the selection until the end of the buffer line.
cutToEndOfBufferLine: (maintainClipboard) ->
@selectToEndOfBufferLine() if @isEmpty()
@cut(maintainClipboard)
# Public: Copies the selection to the clipboard and then deletes it.
#
# * `maintainClipboard` {Boolean} (default: false) See {::copy}
@@ -826,25 +835,3 @@ class Selection extends Model
getGoalScreenRange: ->
if goalScreenRange = @marker.getProperties().goalScreenRange
Range.fromObject(goalScreenRange)
if Grim.includeDeprecatedAPIs
Selection::on = (eventName) ->
switch eventName
when 'screen-range-changed'
Grim.deprecate("Use Selection::onDidChangeRange instead. Call ::getScreenRange() yourself in your callback if you need the range.")
when 'destroyed'
Grim.deprecate("Use Selection::onDidDestroy instead.")
else
Grim.deprecate("Selection::on is deprecated. Use documented event subscription methods instead.")
super
# Deprecated: Use {::deleteToBeginningOfWord} instead.
Selection::backspaceToBeginningOfWord = ->
deprecate("Use Selection::deleteToBeginningOfWord() instead")
@deleteToBeginningOfWord()
# Deprecated: Use {::deleteToBeginningOfLine} instead.
Selection::backspaceToBeginningOfLine = ->
deprecate("Use Selection::deleteToBeginningOfLine() instead")
@deleteToBeginningOfLine()

View File

@@ -1,157 +0,0 @@
_ = require 'underscore-plus'
SpacePen = require 'space-pen'
{Subscriber} = require 'emissary'
Subscriber.includeInto(SpacePen.View)
jQuery = SpacePen.jQuery
JQueryCleanData = jQuery.cleanData
jQuery.cleanData = (elements) ->
jQuery(element).view()?.unsubscribe?() for element in elements
JQueryCleanData(elements)
SpacePenCallRemoveHooks = SpacePen.callRemoveHooks
SpacePen.callRemoveHooks = (element) ->
view.unsubscribe?() for view in SpacePen.viewsForElement(element)
SpacePenCallRemoveHooks(element)
NativeEventNames = new Set
NativeEventNames.add(nativeEvent) for nativeEvent in ["blur", "focus", "focusin",
"focusout", "load", "resize", "scroll", "unload", "click", "dblclick", "mousedown",
"mouseup", "mousemove", "mouseover", "mouseout", "mouseenter", "mouseleave", "change",
"select", "submit", "keydown", "keypress", "keyup", "error", "contextmenu", "textInput",
"textinput", "beforeunload"]
JQueryTrigger = jQuery.fn.trigger
jQuery.fn.trigger = (eventName, data) ->
if NativeEventNames.has(eventName) or typeof eventName is 'object'
JQueryTrigger.call(this, eventName, data)
else
data ?= {}
data.jQueryTrigger = true
for element in this
atom.commands.dispatch(element, eventName, data)
this
HandlersByOriginalHandler = new WeakMap
CommandDisposablesByElement = new WeakMap
AddEventListener = (element, type, listener) ->
if NativeEventNames.has(type)
element.addEventListener(type, listener)
else
disposable = atom.commands.add(element, type, listener)
unless disposablesByType = CommandDisposablesByElement.get(element)
disposablesByType = {}
CommandDisposablesByElement.set(element, disposablesByType)
unless disposablesByListener = disposablesByType[type]
disposablesByListener = new WeakMap
disposablesByType[type] = disposablesByListener
disposablesByListener.set(listener, disposable)
RemoveEventListener = (element, type, listener) ->
if NativeEventNames.has(type)
element.removeEventListener(type, listener)
else
CommandDisposablesByElement.get(element)?[type]?.get(listener)?.dispose()
JQueryEventAdd = jQuery.event.add
jQuery.event.add = (elem, types, originalHandler, data, selector) ->
handler = (event) ->
if arguments.length is 1 and event.originalEvent?.detail?
{detail} = event.originalEvent
if Array.isArray(detail)
originalHandler.apply(this, [event].concat(detail))
else
originalHandler.call(this, event, detail)
else
originalHandler.apply(this, arguments)
HandlersByOriginalHandler.set(originalHandler, handler)
JQueryEventAdd.call(this, elem, types, handler, data, selector, AddEventListener if atom?.commands?)
JQueryEventRemove = jQuery.event.remove
jQuery.event.remove = (elem, types, originalHandler, selector, mappedTypes) ->
if originalHandler?
handler = HandlersByOriginalHandler.get(originalHandler) ? originalHandler
JQueryEventRemove(elem, types, handler, selector, mappedTypes, RemoveEventListener if atom?.commands?)
JQueryContains = jQuery.contains
jQuery.contains = (a, b) ->
shadowRoot = null
currentNode = b
while currentNode
if currentNode instanceof ShadowRoot and a.contains(currentNode.host)
return true
currentNode = currentNode.parentNode
JQueryContains.call(this, a, b)
tooltipDefaults =
delay:
show: 1000
hide: 100
container: 'body'
html: true
placement: 'auto top'
viewportPadding: 2
humanizeKeystrokes = (keystroke) ->
keystrokes = keystroke.split(' ')
keystrokes = (_.humanizeKeystroke(stroke) for stroke in keystrokes)
keystrokes.join(' ')
getKeystroke = (bindings) ->
if bindings?.length
"<span class=\"keystroke\">#{humanizeKeystrokes(bindings[0].keystrokes)}</span>"
else
''
requireBootstrapTooltip = _.once ->
atom.requireWithGlobals('bootstrap/js/tooltip', {jQuery})
# options from http://getbootstrap.com/javascript/#tooltips
jQuery.fn.setTooltip = (tooltipOptions, {command, commandElement}={}) ->
requireBootstrapTooltip()
tooltipOptions = {title: tooltipOptions} if _.isString(tooltipOptions)
if commandElement
bindings = atom.keymaps.findKeyBindings(command: command, target: commandElement[0])
else if command
bindings = atom.keymaps.findKeyBindings(command: command)
tooltipOptions.title = "#{tooltipOptions.title} #{getKeystroke(bindings)}"
@tooltip(jQuery.extend({}, tooltipDefaults, tooltipOptions))
jQuery.fn.hideTooltip = ->
tip = @data('bs.tooltip')
if tip
tip.leave(currentTarget: this)
tip.hide()
jQuery.fn.destroyTooltip = ->
@hideTooltip()
requireBootstrapTooltip()
@tooltip('destroy')
# Hide tooltips when window is resized
jQuery(document.body).on 'show.bs.tooltip', ({target}) ->
windowHandler = -> jQuery(target).hideTooltip()
jQuery(window).one('resize', windowHandler)
jQuery(target).one 'hide.bs.tooltip', ->
jQuery(window).off('resize', windowHandler)
jQuery.fn.setTooltip.getKeystroke = getKeystroke
jQuery.fn.setTooltip.humanizeKeystrokes = humanizeKeystrokes
Object.defineProperty jQuery.fn, 'element', get: -> @[0]
module.exports = SpacePen

View File

@@ -1,4 +0,0 @@
{Subscriber} = require 'emissary'
SubscriberMixin = componentDidUnmount: -> @unsubscribe()
Subscriber.extend(SubscriberMixin)
module.exports = SubscriberMixin

View File

@@ -1,6 +1,6 @@
_ = require 'underscore-plus'
ChildProcess = require 'child_process'
{Emitter} = require 'emissary'
{Emitter} = require 'event-kit'
Grim = require 'grim'
# Extended: Run a node script in a separate process.
@@ -38,8 +38,6 @@ Grim = require 'grim'
# ```
module.exports =
class Task
Emitter.includeInto(this)
# Public: A helper method to easily launch and run a task once.
#
# * `taskPath` The {String} path to the CoffeeScript/JavaScript file which
@@ -66,6 +64,8 @@ class Task
# * `taskPath` The {String} path to the CoffeeScript/JavaScript file that
# exports a single {Function} to execute.
constructor: (taskPath) ->
@emitter = new Emitter
compileCacheRequire = "require('#{require.resolve('./compile-cache')}')"
compileCachePath = require('./compile-cache').getCacheDirectory()
taskBootstrapRequire = "require('#{require.resolve('./task-bootstrap')}');"
@@ -95,7 +95,7 @@ class Task
handleEvents: ->
@childProcess.removeAllListeners()
@childProcess.on 'message', ({event, args}) =>
@emit(event, args...) if @childProcess?
@emitter.emit(event, args) if @childProcess?
# Catch the errors that happened before task-bootstrap.
if @childProcess.stdout?
@@ -143,7 +143,12 @@ class Task
# * `callback` The {Function} to call when the event is emitted.
#
# Returns a {Disposable} that can be used to stop listening for the event.
on: (eventName, callback) -> Emitter::on.call(this, eventName, callback)
on: (eventName, callback) -> @emitter.on eventName, (args) -> callback(args...)
once: (eventName, callback) ->
disposable = @on eventName, (args...) ->
disposable.dispose()
callback(args...)
# Public: Forcefully stop the running task.
#
@@ -162,5 +167,5 @@ class Task
cancel: ->
didForcefullyTerminate = @terminate()
if didForcefullyTerminate
@emit('task:cancelled')
@emitter.emit('task:cancelled')
didForcefullyTerminate

View File

@@ -1,7 +1,6 @@
_ = require 'underscore-plus'
scrollbarStyle = require 'scrollbar-style'
{Range, Point} = require 'text-buffer'
grim = require 'grim'
{CompositeDisposable} = require 'event-kit'
ipc = require 'ipc'
@@ -12,6 +11,7 @@ LinesComponent = require './lines-component'
ScrollbarComponent = require './scrollbar-component'
ScrollbarCornerComponent = require './scrollbar-corner-component'
OverlayManager = require './overlay-manager'
DOMElementPool = require './dom-element-pool'
module.exports =
class TextEditorComponent
@@ -26,8 +26,6 @@ class TextEditorComponent
updatesPaused: false
updateRequestedWhilePaused: false
heightAndWidthMeasurementRequested: false
cursorMoved: false
selectionChanged: false
inputEnabled: true
measureScrollbarsWhenShown: true
measureLineHeightAndDefaultCharWidthWhenShown: true
@@ -35,6 +33,13 @@ class TextEditorComponent
stylingChangeAnimationFrameRequested: false
gutterComponent: null
mounted: true
initialized: false
Object.defineProperty @prototype, "domNode",
get: -> @domNodeValue
set: (domNode) ->
atom.assert domNode?, "TextEditorComponent::domNode was set to null."
@domNodeValue = domNode
constructor: ({@editor, @hostElement, @rootElement, @stylesElement, @useShadowDOM, tileSize}) ->
@tileSize = tileSize if tileSize?
@@ -45,8 +50,10 @@ class TextEditorComponent
@presenter = new TextEditorPresenter
model: @editor
scrollTop: @editor.getScrollTop()
scrollLeft: @editor.getScrollLeft()
scrollTop: 0
scrollLeft: 0
scrollRow: @editor.getScrollRow()
scrollColumn: @editor.getScrollColumn()
tileSize: tileSize
cursorBlinkPeriod: @cursorBlinkPeriod
cursorBlinkResumeDelay: @cursorBlinkResumeDelay
@@ -54,6 +61,8 @@ class TextEditorComponent
@presenter.onDidUpdateState(@requestUpdate)
@domElementPool = new DOMElementPool
@domNode = document.createElement('div')
if @useShadowDOM
@domNode.classList.add('editor-contents--private')
@@ -75,7 +84,7 @@ class TextEditorComponent
@hiddenInputComponent = new InputComponent
@scrollViewNode.appendChild(@hiddenInputComponent.getDomNode())
@linesComponent = new LinesComponent({@presenter, @hostElement, @useShadowDOM})
@linesComponent = new LinesComponent({@presenter, @hostElement, @useShadowDOM, @domElementPool})
@scrollViewNode.appendChild(@linesComponent.getDomNode())
@horizontalScrollbarComponent = new ScrollbarComponent({orientation: 'horizontal', onScroll: @onHorizontalScroll})
@@ -101,12 +110,14 @@ class TextEditorComponent
@updateSync()
@checkForVisibilityChange()
@initialized = true
destroy: ->
@mounted = false
@disposables.dispose()
@presenter.destroy()
@gutterContainerComponent?.destroy()
@domElementPool.clear()
getDomNode: ->
@domNode
@@ -115,11 +126,6 @@ class TextEditorComponent
@oldState ?= {}
@newState = @presenter.getState()
cursorMoved = @cursorMoved
selectionChanged = @selectionChanged
@cursorMoved = false
@selectionChanged = false
if @editor.getLastSelection()? and not @editor.getLastSelection().isEmpty()
@domNode.classList.add('has-selection')
else
@@ -152,20 +158,20 @@ class TextEditorComponent
@overlayManager?.render(@newState)
if @clearPoolAfterUpdate
@domElementPool.clear()
@clearPoolAfterUpdate = false
if @editor.isAlive()
@updateParentViewFocusedClassIfNeeded()
@updateParentViewMiniClass()
if grim.includeDeprecatedAPIs
@hostElement.__spacePenView.trigger 'cursor:moved' if cursorMoved
@hostElement.__spacePenView.trigger 'selection:changed' if selectionChanged
@hostElement.__spacePenView.trigger 'editor:display-updated'
readAfterUpdateSync: =>
@linesComponent.measureCharactersInNewLines() if @isVisible() and not @newState.content.scrollingVertically
@overlayManager?.measureOverlays()
mountGutterContainerComponent: ->
@gutterContainerComponent = new GutterContainerComponent({@editor, @onLineNumberGutterMouseDown})
@gutterContainerComponent = new GutterContainerComponent({@editor, @onLineNumberGutterMouseDown, @domElementPool})
@domNode.insertBefore(@gutterContainerComponent.getDomNode(), @domNode.firstChild)
becameVisible: ->
@@ -215,8 +221,6 @@ class TextEditorComponent
observeEditor: ->
@disposables.add @editor.observeGrammar(@onGrammarChanged)
@disposables.add @editor.observeCursors(@onCursorAdded)
@disposables.add @editor.observeSelections(@onSelectionAdded)
listenForDOMEvents: ->
@domNode.addEventListener 'mousewheel', @onMouseWheel
@@ -312,7 +316,7 @@ class TextEditorComponent
inputNode.value = event.data if insertedRange
onVerticalScroll: (scrollTop) =>
return if @updateRequested or scrollTop is @editor.getScrollTop()
return if @updateRequested or scrollTop is @presenter.getScrollTop()
animationFramePending = @pendingScrollTop?
@pendingScrollTop = scrollTop
@@ -321,15 +325,17 @@ class TextEditorComponent
pendingScrollTop = @pendingScrollTop
@pendingScrollTop = null
@presenter.setScrollTop(pendingScrollTop)
@presenter.commitPendingScrollTopPosition()
onHorizontalScroll: (scrollLeft) =>
return if @updateRequested or scrollLeft is @editor.getScrollLeft()
return if @updateRequested or scrollLeft is @presenter.getScrollLeft()
animationFramePending = @pendingScrollLeft?
@pendingScrollLeft = scrollLeft
unless animationFramePending
@requestAnimationFrame =>
@presenter.setScrollLeft(@pendingScrollLeft)
@presenter.commitPendingScrollLeftPosition()
@pendingScrollLeft = null
onMouseWheel: (event) =>
@@ -348,14 +354,18 @@ class TextEditorComponent
if Math.abs(wheelDeltaX) > Math.abs(wheelDeltaY)
# Scrolling horizontally
previousScrollLeft = @presenter.getScrollLeft()
@presenter.setScrollLeft(previousScrollLeft - Math.round(wheelDeltaX * @scrollSensitivity))
event.preventDefault() unless previousScrollLeft is @presenter.getScrollLeft()
updatedScrollLeft = previousScrollLeft - Math.round(wheelDeltaX * @scrollSensitivity)
event.preventDefault() if @presenter.canScrollLeftTo(updatedScrollLeft)
@presenter.setScrollLeft(updatedScrollLeft)
else
# Scrolling vertically
@presenter.setMouseWheelScreenRow(@screenRowForNode(event.target))
previousScrollTop = @presenter.getScrollTop()
@presenter.setScrollTop(previousScrollTop - Math.round(wheelDeltaY * @scrollSensitivity))
event.preventDefault() unless previousScrollTop is @presenter.getScrollTop()
updatedScrollTop = previousScrollTop - Math.round(wheelDeltaY * @scrollSensitivity)
event.preventDefault() if @presenter.canScrollTopTo(updatedScrollTop)
@presenter.setScrollTop(updatedScrollTop)
onScrollViewScroll: =>
if @mounted
@@ -363,6 +373,77 @@ class TextEditorComponent
@scrollViewNode.scrollTop = 0
@scrollViewNode.scrollLeft = 0
onDidChangeScrollTop: (callback) ->
@presenter.onDidChangeScrollTop(callback)
onDidChangeScrollLeft: (callback) ->
@presenter.onDidChangeScrollLeft(callback)
setScrollLeft: (scrollLeft) ->
@presenter.setScrollLeft(scrollLeft)
setScrollRight: (scrollRight) ->
@presenter.setScrollRight(scrollRight)
setScrollTop: (scrollTop) ->
@presenter.setScrollTop(scrollTop)
setScrollBottom: (scrollBottom) ->
@presenter.setScrollBottom(scrollBottom)
getScrollTop: ->
@presenter.getScrollTop()
getScrollLeft: ->
@presenter.getScrollLeft()
getScrollRight: ->
@presenter.getScrollRight()
getScrollBottom: ->
@presenter.getScrollBottom()
getScrollHeight: ->
@presenter.getScrollHeight()
getScrollWidth: ->
@presenter.getScrollWidth()
getVerticalScrollbarWidth: ->
@presenter.getVerticalScrollbarWidth()
getHorizontalScrollbarHeight: ->
@presenter.getHorizontalScrollbarHeight()
getVisibleRowRange: ->
@presenter.getVisibleRowRange()
pixelPositionForScreenPosition: (screenPosition) ->
position = @presenter.pixelPositionForScreenPosition(screenPosition)
position.top += @presenter.getScrollTop()
position.left += @presenter.getScrollLeft()
position
screenPositionForPixelPosition: (pixelPosition) ->
@presenter.screenPositionForPixelPosition(pixelPosition)
pixelRectForScreenRange: (screenRange) ->
rect = @presenter.pixelRectForScreenRange(screenRange)
rect.top += @presenter.getScrollTop()
rect.bottom += @presenter.getScrollTop()
rect.left += @presenter.getScrollLeft()
rect.right += @presenter.getScrollLeft()
rect
pixelRangeForScreenRange: (screenRange, clip=true) ->
{start, end} = Range.fromObject(screenRange)
{start: @pixelPositionForScreenPosition(start, clip), end: @pixelPositionForScreenPosition(end, clip)}
pixelPositionForBufferPosition: (bufferPosition) ->
@pixelPositionForScreenPosition(
@editor.screenPositionForBufferPosition(bufferPosition)
)
onMouseDown: (event) =>
unless event.button is 0 or (event.button is 1 and process.platform is 'linux')
# Only handle mouse down events for left mouse button on all platforms
@@ -482,29 +563,6 @@ class TextEditorComponent
@sampleBackgroundColors()
@remeasureCharacterWidths()
onSelectionAdded: (selection) =>
selectionDisposables = new CompositeDisposable
selectionDisposables.add selection.onDidChangeRange => @onSelectionChanged(selection)
selectionDisposables.add selection.onDidDestroy =>
@onSelectionChanged(selection)
selectionDisposables.dispose()
@disposables.remove(selectionDisposables)
@disposables.add(selectionDisposables)
if @editor.selectionIntersectsVisibleRowRange(selection)
@selectionChanged = true
onSelectionChanged: (selection) =>
if @editor.selectionIntersectsVisibleRowRange(selection)
@selectionChanged = true
onCursorAdded: (cursor) =>
@disposables.add cursor.onDidChangePosition @onCursorMoved
onCursorMoved: =>
@cursorMoved = true
handleDragUntilMouseUp: (dragHandler) =>
dragging = false
lastMousePosition = {}
@@ -567,9 +625,11 @@ class TextEditorComponent
if mouseYDelta?
@presenter.setScrollTop(@presenter.getScrollTop() + yDirection * scaleScrollDelta(mouseYDelta))
@presenter.commitPendingScrollTopPosition()
if mouseXDelta?
@presenter.setScrollLeft(@presenter.getScrollLeft() + xDirection * scaleScrollDelta(mouseXDelta))
@presenter.commitPendingScrollLeftPosition()
scaleScrollDelta = (scrollDelta) ->
Math.pow(scrollDelta / 2, 3) / 280
@@ -586,7 +646,11 @@ class TextEditorComponent
disposables.add(@editor.onDidDestroy(stopDragging))
isVisible: ->
@domNode.offsetHeight > 0 or @domNode.offsetWidth > 0
# Investigating an exception that occurs here due to ::domNode being null.
atom.assert @domNode?, "TextEditorComponent::domNode was null.", (error) =>
error.metadata = {@initialized}
@domNode? and (@domNode.offsetHeight > 0 or @domNode.offsetWidth > 0)
pollDOM: =>
unless @checkForVisibilityChange()
@@ -649,6 +713,7 @@ class TextEditorComponent
{@fontSize, @fontFamily, @lineHeight} = getComputedStyle(@getTopmostDOMNode())
if @fontSize isnt oldFontSize or @fontFamily isnt oldFontFamily or @lineHeight isnt oldLineHeight
@clearPoolAfterUpdate = true
@measureLineHeightAndDefaultCharWidth()
if (@fontSize isnt oldFontSize or @fontFamily isnt oldFontFamily) and @performedInitialMeasurement
@@ -749,6 +814,13 @@ class TextEditorComponent
tileComponent?.lineNumberNodeForScreenRow(screenRow)
tileNodesForLines: ->
@linesComponent.getTiles()
tileNodesForLineNumbers: ->
gutterComponent = @gutterContainerComponent.getLineNumberGutterComponent()
gutterComponent.getTiles()
screenRowForNode: (node) ->
while node?
if screenRow = node.dataset.screenRow
@@ -783,19 +855,22 @@ class TextEditorComponent
screenPositionForMouseEvent: (event, linesClientRect) ->
pixelPosition = @pixelPositionForMouseEvent(event, linesClientRect)
@editor.screenPositionForPixelPosition(pixelPosition)
@presenter.screenPositionForPixelPosition(pixelPosition)
pixelPositionForMouseEvent: (event, linesClientRect) ->
{clientX, clientY} = event
linesClientRect ?= @linesComponent.getDomNode().getBoundingClientRect()
top = clientY - linesClientRect.top + @presenter.scrollTop
left = clientX - linesClientRect.left + @presenter.scrollLeft
bottom = linesClientRect.top + @presenter.scrollTop + linesClientRect.height - clientY
right = linesClientRect.left + @presenter.scrollLeft + linesClientRect.width - clientX
top = clientY - linesClientRect.top + @presenter.getRealScrollTop()
left = clientX - linesClientRect.left + @presenter.getRealScrollLeft()
bottom = linesClientRect.top + @presenter.getRealScrollTop() + linesClientRect.height - clientY
right = linesClientRect.left + @presenter.getRealScrollLeft() + linesClientRect.width - clientX
{top, left, bottom, right}
getGutterWidth: ->
@presenter.getGutterWidth()
getModel: ->
@editor
@@ -803,6 +878,9 @@ class TextEditorComponent
setInputEnabled: (@inputEnabled) -> @inputEnabled
setContinuousReflow: (continuousReflow) ->
@presenter.setContinuousReflow(continuousReflow)
updateParentViewFocusedClassIfNeeded: ->
if @oldState.focused isnt @newState.focused
@hostElement.classList.toggle('is-focused', @newState.focused)
@@ -812,12 +890,3 @@ class TextEditorComponent
updateParentViewMiniClass: ->
@hostElement.classList.toggle('mini', @editor.isMini())
@rootElement.classList.toggle('mini', @editor.isMini())
if grim.includeDeprecatedAPIs
TextEditorComponent::setInvisibles = (invisibles={}) ->
grim.deprecate "Use config.set('editor.invisibles', invisibles) instead"
atom.config.set('editor.invisibles', invisibles)
TextEditorComponent::setShowInvisibles = (showInvisibles) ->
grim.deprecate "Use config.set('editor.showInvisibles', showInvisibles) instead"
atom.config.set('editor.showInvisibles', showInvisibles)

View File

@@ -1,12 +1,9 @@
{Emitter} = require 'event-kit'
{View, $, callRemoveHooks} = require 'space-pen'
{Emitter, CompositeDisposable} = require 'event-kit'
Path = require 'path'
{defaults} = require 'underscore-plus'
TextBuffer = require 'text-buffer'
Grim = require 'grim'
TextEditor = require './text-editor'
TextEditorComponent = require './text-editor-component'
TextEditorView = null
ShadowStyleSheet = null
@@ -18,11 +15,12 @@ class TextEditorElement extends HTMLElement
tileSize: null
focusOnAttach: false
hasTiledRendering: true
logicalDisplayBuffer: true
createdCallback: ->
@emitter = new Emitter
@subscriptions = new CompositeDisposable
@initializeContent()
@createSpacePenShim() if Grim.includeDeprecatedAPIs
@addEventListener 'focus', @focused.bind(this)
@addEventListener 'blur', @blurred.bind(this)
@@ -56,14 +54,11 @@ class TextEditorElement extends HTMLElement
@stylesElement = document.head.querySelector('atom-styles')
@rootElement = this
createSpacePenShim: ->
TextEditorView ?= require './text-editor-view'
@__spacePenView = new TextEditorView(this)
attachedCallback: ->
@buildModel() unless @getModel()?
atom.assert(@model.isAlive(), "Attaching a view for a destroyed editor")
@mountComponent() unless @component?
@listenForComponentEvents()
@component.checkForVisibilityChange()
if this is document.activeElement
@focused()
@@ -71,8 +66,16 @@ class TextEditorElement extends HTMLElement
detachedCallback: ->
@unmountComponent()
@subscriptions.dispose()
@subscriptions = new CompositeDisposable
@emitter.emit("did-detach")
listenForComponentEvents: ->
@subscriptions.add @component.onDidChangeScrollTop =>
@emitter.emit("did-change-scroll-top", arguments...)
@subscriptions.add @component.onDidChangeScrollLeft =>
@emitter.emit("did-change-scroll-left", arguments...)
initialize: (model) ->
@setModel(model)
this
@@ -90,7 +93,6 @@ class TextEditorElement extends HTMLElement
@model.onDidChangeEncoding => @addEncodingAttribute()
@model.onDidDestroy => @unmountComponent()
@model.onDidChangeMini (mini) => if mini then @addMiniAttribute() else @removeMiniAttribute()
@__spacePenView.setModel(@model) if Grim.includeDeprecatedAPIs
@model
getModel: ->
@@ -126,7 +128,6 @@ class TextEditorElement extends HTMLElement
inputNode.addEventListener 'blur', => @dispatchEvent(new FocusEvent('blur', bubbles: false))
unmountComponent: ->
callRemoveHooks(this)
if @component?
@component.destroy()
@component.getDomNode().remove()
@@ -172,6 +173,12 @@ class TextEditorElement extends HTMLElement
isUpdatedSynchronously: -> @updatedSynchronously
# Extended: Continuously reflows lines and line numbers. (Has performance overhead)
#
# `continuousReflow` A {Boolean} indicating whether to keep reflowing or not.
setContinuousReflow: (continuousReflow) ->
@component?.setContinuousReflow(continuousReflow)
# Extended: get the width of a character of text displayed in this element.
#
# Returns a {Number} of pixels.
@@ -185,7 +192,7 @@ class TextEditorElement extends HTMLElement
#
# Returns an {Object} with two values: `top` and `left`, representing the pixel position.
pixelPositionForBufferPosition: (bufferPosition) ->
@getModel().pixelPositionForBufferPosition(bufferPosition, true)
@component.pixelPositionForBufferPosition(bufferPosition)
# Extended: Converts a screen position to a pixel position.
#
@@ -194,21 +201,21 @@ class TextEditorElement extends HTMLElement
#
# Returns an {Object} with two values: `top` and `left`, representing the pixel positions.
pixelPositionForScreenPosition: (screenPosition) ->
@getModel().pixelPositionForScreenPosition(screenPosition, true)
@component.pixelPositionForScreenPosition(screenPosition)
# Extended: Retrieves the number of the row that is visible and currently at the
# top of the editor.
#
# Returns a {Number}.
getFirstVisibleScreenRow: ->
@getModel().getFirstVisibleScreenRow(true)
@getVisibleRowRange()[0]
# Extended: Retrieves the number of the row that is visible and currently at the
# bottom of the editor.
#
# Returns a {Number}.
getLastVisibleScreenRow: ->
@getModel().getLastVisibleScreenRow(true)
@getVisibleRowRange()[1]
# Extended: call the given `callback` when the editor is attached to the DOM.
#
@@ -222,6 +229,90 @@ class TextEditorElement extends HTMLElement
onDidDetach: (callback) ->
@emitter.on("did-detach", callback)
onDidChangeScrollTop: (callback) ->
@emitter.on("did-change-scroll-top", callback)
onDidChangeScrollLeft: (callback) ->
@emitter.on("did-change-scroll-left", callback)
setScrollLeft: (scrollLeft) ->
@component.setScrollLeft(scrollLeft)
setScrollRight: (scrollRight) ->
@component.setScrollRight(scrollRight)
setScrollTop: (scrollTop) ->
@component.setScrollTop(scrollTop)
setScrollBottom: (scrollBottom) ->
@component.setScrollBottom(scrollBottom)
# Essential: Scrolls the editor to the top
scrollToTop: ->
@setScrollTop(0)
# Essential: Scrolls the editor to the bottom
scrollToBottom: ->
@setScrollBottom(Infinity)
getScrollTop: ->
@component?.getScrollTop() or 0
getScrollLeft: ->
@component?.getScrollLeft() or 0
getScrollRight: ->
@component?.getScrollRight() or 0
getScrollBottom: ->
@component?.getScrollBottom() or 0
getScrollHeight: ->
@component?.getScrollHeight() or 0
getScrollWidth: ->
@component?.getScrollWidth() or 0
getVerticalScrollbarWidth: ->
@component?.getVerticalScrollbarWidth() or 0
getHorizontalScrollbarHeight: ->
@component?.getHorizontalScrollbarHeight() or 0
getVisibleRowRange: ->
@component?.getVisibleRowRange() or [0, 0]
intersectsVisibleRowRange: (startRow, endRow) ->
[visibleStart, visibleEnd] = @getVisibleRowRange()
not (endRow <= visibleStart or visibleEnd <= startRow)
selectionIntersectsVisibleRowRange: (selection) ->
{start, end} = selection.getScreenRange()
@intersectsVisibleRowRange(start.row, end.row + 1)
screenPositionForPixelPosition: (pixelPosition) ->
@component.screenPositionForPixelPosition(pixelPosition)
pixelRectForScreenRange: (screenRange) ->
@component.pixelRectForScreenRange(screenRange)
pixelRangeForScreenRange: (screenRange) ->
@component.pixelRangeForScreenRange(screenRange)
setWidth: (width) ->
@style.width = (@component.getGutterWidth() + width) + "px"
@component.measureDimensions()
getWidth: ->
@offsetWidth - @component.getGutterWidth()
setHeight: (height) ->
@style.height = height + "px"
@component.measureDimensions()
getHeight: ->
@offsetHeight
stopEventPropagation = (commandListeners) ->
newCommandListeners = {}
for commandName, commandListener of commandListeners
@@ -299,6 +390,7 @@ atom.commands.add 'atom-text-editor', stopEventPropagationAndGroupUndo(
'editor:delete-to-end-of-subword': -> @deleteToEndOfSubword()
'editor:delete-line': -> @deleteLine()
'editor:cut-to-end-of-line': -> @cutToEndOfLine()
'editor:cut-to-end-of-buffer-line': -> @cutToEndOfBufferLine()
'editor:transpose': -> @transpose()
'editor:upper-case': -> @upperCase()
'editor:lower-case': -> @lowerCase()

View File

@@ -11,16 +11,17 @@ class TextEditorPresenter
mouseWheelScreenRow: null
scopedCharacterWidthsChangeCount: 0
overlayDimensions: {}
minimumReflowInterval: 200
constructor: (params) ->
{@model, @autoHeight, @explicitHeight, @contentFrameWidth, @scrollTop, @scrollLeft, @boundingClientRect, @windowWidth, @windowHeight, @gutterWidth} = params
{@model, @autoHeight, @explicitHeight, @contentFrameWidth, @scrollTop, @scrollLeft, @scrollColumn, @scrollRow, @boundingClientRect, @windowWidth, @windowHeight, @gutterWidth} = params
{horizontalScrollbarHeight, verticalScrollbarWidth} = params
{@lineHeight, @baseCharacterWidth, @backgroundColor, @gutterBackgroundColor, @tileSize} = params
{@cursorBlinkPeriod, @cursorBlinkResumeDelay, @stoppedScrollingDelay, @focused} = params
@measuredHorizontalScrollbarHeight = horizontalScrollbarHeight
@measuredVerticalScrollbarWidth = verticalScrollbarWidth
@gutterWidth ?= 0
@tileSize ?= 12
@tileSize ?= 6
@disposables = new CompositeDisposable
@emitter = new Emitter
@@ -31,10 +32,12 @@ class TextEditorPresenter
@lineNumberDecorationsByScreenRow = {}
@customGutterDecorationsByGutterNameAndScreenRow = {}
@transferMeasurementsToModel()
@transferMeasurementsFromModel()
@observeModel()
@observeConfig()
@buildState()
@startBlinkingCursors() if @focused
@startReflowing() if @continuousReflow
@updating = false
destroy: ->
@@ -48,14 +51,11 @@ class TextEditorPresenter
@emitter.emit "did-update-state" if @isBatching()
transferMeasurementsToModel: ->
@model.setHeight(@explicitHeight) if @explicitHeight?
@model.setWidth(@contentFrameWidth) if @contentFrameWidth?
@model.setLineHeightInPixels(@lineHeight) if @lineHeight?
@model.setDefaultCharWidth(@baseCharacterWidth) if @baseCharacterWidth?
@model.setScrollTop(@scrollTop) if @scrollTop?
@model.setScrollLeft(@scrollLeft) if @scrollLeft?
@model.setVerticalScrollbarWidth(@measuredVerticalScrollbarWidth) if @measuredVerticalScrollbarWidth?
@model.setHorizontalScrollbarHeight(@measuredHorizontalScrollbarHeight) if @measuredHorizontalScrollbarHeight?
transferMeasurementsFromModel: ->
@editorWidthInChars = @model.getEditorWidthInChars()
# Private: Determines whether {TextEditorPresenter} is currently batching changes.
# Returns a {Boolean}, `true` if is collecting changes, `false` if is applying them.
@@ -69,9 +69,12 @@ class TextEditorPresenter
@updateContentDimensions()
@updateScrollbarDimensions()
@updateScrollPosition()
@updateStartRow()
@updateEndRow()
@updateRowsPerPage()
@updateCommonGutterState()
@updateReflowState()
@updateFocusedState() if @shouldUpdateFocusedState
@updateHeightState() if @shouldUpdateHeightState
@@ -112,8 +115,6 @@ class TextEditorPresenter
observeModel: ->
@disposables.add @model.onDidChange =>
@updateContentDimensions()
@shouldUpdateHeightState = true
@shouldUpdateVerticalScrollState = true
@shouldUpdateHorizontalScrollState = true
@@ -159,8 +160,7 @@ class TextEditorPresenter
@disposables.add @model.onDidAddDecoration(@didAddDecoration.bind(this))
@disposables.add @model.onDidAddCursor(@didAddCursor.bind(this))
@disposables.add @model.onDidChangeScrollTop(@setScrollTop.bind(this))
@disposables.add @model.onDidChangeScrollLeft(@setScrollLeft.bind(this))
@disposables.add @model.onDidRequestAutoscroll(@requestAutoscroll.bind(this))
@observeDecoration(decoration) for decoration in @model.getDecorations()
@observeCursor(cursor) for cursor in @model.getCursors()
@disposables.add @model.onDidAddGutter(@didAddGutter.bind(this))
@@ -232,6 +232,7 @@ class TextEditorPresenter
@shouldUpdateLineNumbersState = true
@updateContentDimensions()
@updateScrollPosition()
@updateScrollbarDimensions()
@updateStartRow()
@updateEndRow()
@@ -254,6 +255,23 @@ class TextEditorPresenter
@resetTrackedUpdates()
setContinuousReflow: (@continuousReflow) ->
if @continuousReflow
@startReflowing()
else
@stopReflowing()
updateReflowState: ->
@state.content.continuousReflow = @continuousReflow
@lineNumberGutter.continuousReflow = @continuousReflow
startReflowing: ->
@reflowingInterval = setInterval(@emitDidUpdateState.bind(this), @minimumReflowInterval)
stopReflowing: ->
clearInterval(@reflowingInterval)
@reflowingInterval = null
updateFocusedState: ->
@state.focused = @focused
@@ -527,7 +545,7 @@ class TextEditorPresenter
# decoration.id : {
# top: # of pixels from top
# height: # of pixels height of this decoration
# item (optional): HTMLElement or space-pen View
# item (optional): HTMLElement
# class (optional): {String} class
# }
# }
@@ -584,22 +602,15 @@ class TextEditorPresenter
if startRow > 0
rowBeforeStartRow = startRow - 1
lastBufferRow = @model.bufferRowForScreenRow(rowBeforeStartRow)
wrapCount = rowBeforeStartRow - @model.screenRowForBufferRow(lastBufferRow)
else
lastBufferRow = null
wrapCount = 0
if endRow > startRow
bufferRows = @model.bufferRowsForScreenRows(startRow, endRow - 1)
zIndex = bufferRows.length - 1
for bufferRow, i in bufferRows
if bufferRow is lastBufferRow
wrapCount++
id = bufferRow + '-' + wrapCount
softWrapped = true
else
id = bufferRow
wrapCount = 0
lastBufferRow = bufferRow
softWrapped = false
@@ -607,10 +618,10 @@ class TextEditorPresenter
top = (screenRow - startRow) * @lineHeight
decorationClasses = @lineNumberDecorationClassesForRow(screenRow)
foldable = @model.isFoldableAtScreenRow(screenRow)
id = @model.tokenizedLineForScreenRow(screenRow).id
tileState.lineNumbers[id] = {screenRow, bufferRow, softWrapped, top, decorationClasses, foldable, zIndex}
tileState.lineNumbers[id] = {screenRow, bufferRow, softWrapped, top, decorationClasses, foldable}
visibleLineNumberIds[id] = true
zIndex--
for id of tileState.lineNumbers
delete tileState.lineNumbers[id] unless visibleLineNumberIds[id]
@@ -631,13 +642,19 @@ class TextEditorPresenter
endRow = startRow + visibleLinesCount
@endRow = Math.min(@model.getScreenLineCount(), endRow)
updateRowsPerPage: ->
rowsPerPage = Math.floor(@getClientHeight() / @lineHeight)
if rowsPerPage isnt @rowsPerPage
@rowsPerPage = rowsPerPage
@model.setRowsPerPage(@rowsPerPage)
updateScrollWidth: ->
return unless @contentWidth? and @clientWidth?
scrollWidth = Math.max(@contentWidth, @clientWidth)
unless @scrollWidth is scrollWidth
@scrollWidth = scrollWidth
@updateScrollLeft()
@updateScrollLeft(@scrollLeft)
updateScrollHeight: ->
return unless @contentHeight? and @clientHeight?
@@ -650,7 +667,7 @@ class TextEditorPresenter
unless @scrollHeight is scrollHeight
@scrollHeight = scrollHeight
@updateScrollTop()
@updateScrollTop(@scrollTop)
updateContentDimensions: ->
if @lineHeight?
@@ -677,33 +694,50 @@ class TextEditorPresenter
return unless @height? and @horizontalScrollbarHeight?
clientHeight = @height - @horizontalScrollbarHeight
@model.setHeight(clientHeight, true)
unless @clientHeight is clientHeight
@clientHeight = clientHeight
@updateScrollHeight()
@updateScrollTop()
@updateScrollTop(@scrollTop)
updateClientWidth: ->
return unless @contentFrameWidth? and @verticalScrollbarWidth?
clientWidth = @contentFrameWidth - @verticalScrollbarWidth
@model.setWidth(clientWidth, true) unless @editorWidthInChars
unless @clientWidth is clientWidth
@clientWidth = clientWidth
@updateScrollWidth()
@updateScrollLeft()
@updateScrollLeft(@scrollLeft)
updateScrollTop: (scrollTop) ->
scrollTop = @constrainScrollTop(scrollTop)
if scrollTop isnt @scrollTop and not Number.isNaN(scrollTop)
@realScrollTop = scrollTop
@scrollTop = Math.round(scrollTop)
@scrollRow = Math.round(@scrollTop / @lineHeight)
@model.setScrollRow(@scrollRow)
updateScrollTop: ->
scrollTop = @constrainScrollTop(@scrollTop)
unless @scrollTop is scrollTop
@scrollTop = scrollTop
@updateStartRow()
@updateEndRow()
@didStartScrolling()
@emitter.emit 'did-change-scroll-top', @scrollTop
constrainScrollTop: (scrollTop) ->
return scrollTop unless scrollTop? and @scrollHeight? and @clientHeight?
Math.max(0, Math.min(scrollTop, @scrollHeight - @clientHeight))
updateScrollLeft: ->
@scrollLeft = @constrainScrollLeft(@scrollLeft)
updateScrollLeft: (scrollLeft) ->
scrollLeft = @constrainScrollLeft(scrollLeft)
if scrollLeft isnt @scrollLeft and not Number.isNaN(scrollLeft)
@realScrollLeft = scrollLeft
@scrollLeft = Math.round(scrollLeft)
@scrollColumn = Math.round(@scrollLeft / @baseCharacterWidth)
@model.setScrollColumn(@scrollColumn)
@emitter.emit 'did-change-scroll-left', @scrollLeft
constrainScrollLeft: (scrollLeft) ->
return scrollLeft unless scrollLeft? and @scrollWidth? and @clientWidth?
@@ -796,26 +830,28 @@ class TextEditorPresenter
@emitDidUpdateState()
setScrollTop: (scrollTop) ->
scrollTop = @constrainScrollTop(scrollTop)
return unless scrollTop?
unless @scrollTop is scrollTop or Number.isNaN(scrollTop)
@scrollTop = scrollTop
@model.setScrollTop(scrollTop)
@didStartScrolling()
@shouldUpdateVerticalScrollState = true
@shouldUpdateHiddenInputState = true
@shouldUpdateDecorations = true
@shouldUpdateLinesState = true
@shouldUpdateCursorsState = true
@shouldUpdateLineNumbersState = true
@shouldUpdateCustomGutterDecorationState = true
@shouldUpdateOverlaysState = true
@pendingScrollLogicalPosition = null
@pendingScrollTop = scrollTop
@emitDidUpdateState()
@shouldUpdateVerticalScrollState = true
@shouldUpdateHiddenInputState = true
@shouldUpdateDecorations = true
@shouldUpdateLinesState = true
@shouldUpdateCursorsState = true
@shouldUpdateLineNumbersState = true
@shouldUpdateCustomGutterDecorationState = true
@shouldUpdateOverlaysState = true
@emitDidUpdateState()
getScrollTop: ->
@scrollTop
getRealScrollTop: ->
@realScrollTop ? @scrollTop
didStartScrolling: ->
if @stoppedScrollingTimeoutId?
clearTimeout(@stoppedScrollingTimeoutId)
@@ -835,28 +871,58 @@ class TextEditorPresenter
@emitDidUpdateState()
setScrollLeft: (scrollLeft) ->
scrollLeft = @constrainScrollLeft(scrollLeft)
unless @scrollLeft is scrollLeft or Number.isNaN(scrollLeft)
oldScrollLeft = @scrollLeft
@scrollLeft = scrollLeft
@model.setScrollLeft(scrollLeft)
@shouldUpdateHorizontalScrollState = true
@shouldUpdateHiddenInputState = true
@shouldUpdateCursorsState = true
@shouldUpdateOverlaysState = true
@shouldUpdateDecorations = true
@shouldUpdateLinesState = true
return unless scrollLeft?
@emitDidUpdateState()
@pendingScrollLogicalPosition = null
@pendingScrollLeft = scrollLeft
@shouldUpdateHorizontalScrollState = true
@shouldUpdateHiddenInputState = true
@shouldUpdateCursorsState = true
@shouldUpdateOverlaysState = true
@shouldUpdateDecorations = true
@shouldUpdateLinesState = true
@emitDidUpdateState()
getScrollLeft: ->
@scrollLeft
getRealScrollLeft: ->
@realScrollLeft ? @scrollLeft
getClientHeight: ->
if @clientHeight
@clientHeight
else
@explicitHeight - @horizontalScrollbarHeight
getClientWidth: ->
if @clientWidth
@clientWidth
else
@contentFrameWidth - @verticalScrollbarWidth
getScrollBottom: -> @getScrollTop() + @getClientHeight()
setScrollBottom: (scrollBottom) ->
@setScrollTop(scrollBottom - @getClientHeight())
@getScrollBottom()
getScrollRight: -> @getScrollLeft() + @getClientWidth()
setScrollRight: (scrollRight) ->
@setScrollLeft(scrollRight - @getClientWidth())
@getScrollRight()
getScrollHeight: ->
@scrollHeight
getScrollWidth: ->
@scrollWidth
setHorizontalScrollbarHeight: (horizontalScrollbarHeight) ->
unless @measuredHorizontalScrollbarHeight is horizontalScrollbarHeight
oldHorizontalScrollbarHeight = @measuredHorizontalScrollbarHeight
@measuredHorizontalScrollbarHeight = horizontalScrollbarHeight
@model.setHorizontalScrollbarHeight(horizontalScrollbarHeight)
@shouldUpdateScrollbarsState = true
@shouldUpdateVerticalScrollState = true
@shouldUpdateHorizontalScrollState = true
@@ -868,7 +934,6 @@ class TextEditorPresenter
unless @measuredVerticalScrollbarWidth is verticalScrollbarWidth
oldVerticalScrollbarWidth = @measuredVerticalScrollbarWidth
@measuredVerticalScrollbarWidth = verticalScrollbarWidth
@model.setVerticalScrollbarWidth(verticalScrollbarWidth)
@shouldUpdateScrollbarsState = true
@shouldUpdateVerticalScrollState = true
@shouldUpdateHorizontalScrollState = true
@@ -886,7 +951,6 @@ class TextEditorPresenter
setExplicitHeight: (explicitHeight) ->
unless @explicitHeight is explicitHeight
@explicitHeight = explicitHeight
@model.setHeight(explicitHeight)
@updateHeight()
@shouldUpdateVerticalScrollState = true
@shouldUpdateScrollbarsState = true
@@ -908,10 +972,10 @@ class TextEditorPresenter
@updateEndRow()
setContentFrameWidth: (contentFrameWidth) ->
unless @contentFrameWidth is contentFrameWidth
if @contentFrameWidth isnt contentFrameWidth or @editorWidthInChars?
oldContentFrameWidth = @contentFrameWidth
@contentFrameWidth = contentFrameWidth
@model.setWidth(contentFrameWidth)
@editorWidthInChars = null
@updateScrollbarDimensions()
@updateClientWidth()
@shouldUpdateVerticalScrollState = true
@@ -969,6 +1033,9 @@ class TextEditorPresenter
@gutterWidth = gutterWidth
@updateOverlaysState()
getGutterWidth: ->
@gutterWidth
setLineHeight: (lineHeight) ->
unless @lineHeight is lineHeight
@lineHeight = lineHeight
@@ -1425,3 +1492,160 @@ class TextEditorPresenter
@startBlinkingCursorsAfterDelay ?= _.debounce(@startBlinkingCursors, @getCursorBlinkResumeDelay())
@startBlinkingCursorsAfterDelay()
@emitDidUpdateState()
requestAutoscroll: (position) ->
@pendingScrollLogicalPosition = position
@pendingScrollTop = null
@pendingScrollLeft = null
@shouldUpdateCursorsState = true
@shouldUpdateCustomGutterDecorationState = true
@shouldUpdateDecorations = true
@shouldUpdateHiddenInputState = true
@shouldUpdateHorizontalScrollState = true
@shouldUpdateLinesState = true
@shouldUpdateLineNumbersState = true
@shouldUpdateOverlaysState = true
@shouldUpdateScrollPosition = true
@shouldUpdateVerticalScrollState = true
@emitDidUpdateState()
getVerticalScrollMarginInPixels: ->
@model.getVerticalScrollMargin() * @lineHeight
getHorizontalScrollMarginInPixels: ->
@model.getHorizontalScrollMargin() * @baseCharacterWidth
getVerticalScrollbarWidth: ->
@verticalScrollbarWidth
getHorizontalScrollbarHeight: ->
@horizontalScrollbarHeight
commitPendingLogicalScrollPosition: ->
return unless @pendingScrollLogicalPosition?
{screenRange, options} = @pendingScrollLogicalPosition
verticalScrollMarginInPixels = @getVerticalScrollMarginInPixels()
horizontalScrollMarginInPixels = @getHorizontalScrollMarginInPixels()
{top, left} = @pixelRectForScreenRange(new Range(screenRange.start, screenRange.start))
{top: endTop, left: endLeft, height: endHeight} = @pixelRectForScreenRange(new Range(screenRange.end, screenRange.end))
bottom = endTop + endHeight
right = endLeft
top += @scrollTop
bottom += @scrollTop
left += @scrollLeft
right += @scrollLeft
if options?.center
desiredScrollCenter = (top + bottom) / 2
unless @getScrollTop() < desiredScrollCenter < @getScrollBottom()
desiredScrollTop = desiredScrollCenter - @getClientHeight() / 2
desiredScrollBottom = desiredScrollCenter + @getClientHeight() / 2
else
desiredScrollTop = top - verticalScrollMarginInPixels
desiredScrollBottom = bottom + verticalScrollMarginInPixels
desiredScrollLeft = left - horizontalScrollMarginInPixels
desiredScrollRight = right + horizontalScrollMarginInPixels
if options?.reversed ? true
if desiredScrollBottom > @getScrollBottom()
@setScrollBottom(desiredScrollBottom)
if desiredScrollTop < @getScrollTop()
@setScrollTop(desiredScrollTop)
if desiredScrollRight > @getScrollRight()
@setScrollRight(desiredScrollRight)
if desiredScrollLeft < @getScrollLeft()
@setScrollLeft(desiredScrollLeft)
else
if desiredScrollTop < @getScrollTop()
@setScrollTop(desiredScrollTop)
if desiredScrollBottom > @getScrollBottom()
@setScrollBottom(desiredScrollBottom)
if desiredScrollLeft < @getScrollLeft()
@setScrollLeft(desiredScrollLeft)
if desiredScrollRight > @getScrollRight()
@setScrollRight(desiredScrollRight)
@pendingScrollLogicalPosition = null
commitPendingScrollLeftPosition: ->
if @pendingScrollLeft?
@updateScrollLeft(@pendingScrollLeft)
@pendingScrollLeft = null
commitPendingScrollTopPosition: ->
if @pendingScrollTop?
@updateScrollTop(@pendingScrollTop)
@pendingScrollTop = null
restoreScrollPosition: ->
return if @hasRestoredScrollPosition or not @hasPixelPositionRequirements()
@setScrollTop(@scrollRow * @lineHeight) if @scrollRow?
@setScrollLeft(@scrollColumn * @baseCharacterWidth) if @scrollColumn?
@hasRestoredScrollPosition = true
updateScrollPosition: ->
@restoreScrollPosition()
@commitPendingLogicalScrollPosition()
@commitPendingScrollLeftPosition()
@commitPendingScrollTopPosition()
canScrollLeftTo: (scrollLeft) ->
@scrollLeft isnt @constrainScrollLeft(scrollLeft)
canScrollTopTo: (scrollTop) ->
@scrollTop isnt @constrainScrollTop(scrollTop)
onDidChangeScrollTop: (callback) ->
@emitter.on 'did-change-scroll-top', callback
onDidChangeScrollLeft: (callback) ->
@emitter.on 'did-change-scroll-left', callback
getVisibleRowRange: ->
[@startRow, @endRow]
screenPositionForPixelPosition: (pixelPosition) ->
targetTop = pixelPosition.top
targetLeft = pixelPosition.left
defaultCharWidth = @baseCharacterWidth
row = Math.floor(targetTop / @lineHeight)
targetLeft = 0 if row < 0
targetLeft = Infinity if row > @model.getLastScreenRow()
row = Math.min(row, @model.getLastScreenRow())
row = Math.max(0, row)
left = 0
column = 0
iterator = @model.tokenizedLineForScreenRow(row).getTokenIterator()
while iterator.next()
charWidths = @getScopedCharacterWidths(iterator.getScopes())
value = iterator.getText()
valueIndex = 0
while valueIndex < value.length
if iterator.isPairedCharacter()
char = value
charLength = 2
valueIndex += 2
else
char = value[valueIndex]
charLength = 1
valueIndex++
charWidth = charWidths[char] ? defaultCharWidth
break if targetLeft <= left + (charWidth / 2)
left += charWidth
column += charLength
new Point(row, column)

View File

@@ -1,324 +0,0 @@
{View, $} = require 'space-pen'
{defaults} = require 'underscore-plus'
TextBuffer = require 'text-buffer'
TextEditor = require './text-editor'
TextEditorElement = require './text-editor-element'
TextEditorComponent = require './text-editor-component'
{deprecate} = require 'grim'
# Deprecated: Represents the entire visual pane in Atom.
#
# The TextEditorView manages the {TextEditor}, which manages the file buffers.
# `TextEditorView` is intentionally sparse. Most of the things you'll want
# to do are on {TextEditor}.
#
# ## Examples
#
# Requiring in packages
#
# ```coffee
# {TextEditorView} = require 'atom'
#
# miniEditorView = new TextEditorView(mini: true)
# ```
#
# Iterating over the open editor views
#
# ```coffee
# for editorView in atom.workspaceView.getEditorViews()
# console.log(editorView.getModel().getPath())
# ```
#
# Subscribing to every current and future editor
#
# ```coffee
# atom.workspace.eachEditorView (editorView) ->
# console.log(editorView.getModel().getPath())
# ```
module.exports =
class TextEditorView extends View
# The constructor for setting up an `TextEditorView` instance.
#
# * `modelOrParams` Either an {TextEditor}, or an object with one property, `mini`.
# If `mini` is `true`, a "miniature" `TextEditor` is constructed.
# Typically, this is ideal for scenarios where you need an Atom editor,
# but without all the chrome, like scrollbars, gutter, _e.t.c._.
#
constructor: (modelOrParams, props) ->
# Handle direct construction with an editor or params
unless modelOrParams instanceof HTMLElement
if modelOrParams instanceof TextEditor
model = modelOrParams
else
{editor, mini, placeholderText, attributes} = modelOrParams
model = editor ? new TextEditor
buffer: new TextBuffer
softWrapped: false
tabLength: 2
softTabs: true
mini: mini
placeholderText: placeholderText
element = new TextEditorElement
element.tileSize = props?.tileSize
element.setAttribute(name, value) for name, value of attributes if attributes?
element.setModel(model)
return element.__spacePenView
# Handle construction with an element
@element = modelOrParams
super
setModel: (@model) ->
@editor = @model
@root = $(@element.rootElement)
@scrollView = @root.find('.scroll-view')
if atom.config.get('editor.useShadowDOM')
@underlayer = $("<div class='underlayer'></div>").appendTo(this)
@overlayer = $("<div class='overlayer'></div>").appendTo(this)
else
@underlayer = @find('.highlights').addClass('underlayer')
@overlayer = @find('.lines').addClass('overlayer')
@hiddenInput = @root.find('.hidden-input')
@hiddenInput.on = (args...) =>
args[0] = 'blur' if args[0] is 'focusout'
$::on.apply(this, args)
@subscribe atom.config.observe 'editor.showLineNumbers', =>
@gutter = @root.find('.gutter')
@gutter.removeClassFromAllLines = (klass) =>
deprecate('Use decorations instead: http://blog.atom.io/2014/07/24/decorations.html')
@gutter.find('.line-number').removeClass(klass)
@gutter.getLineNumberElement = (bufferRow) =>
deprecate('Use decorations instead: http://blog.atom.io/2014/07/24/decorations.html')
@gutter.find("[data-buffer-row='#{bufferRow}']")
@gutter.addClassToLine = (bufferRow, klass) =>
deprecate('Use decorations instead: http://blog.atom.io/2014/07/24/decorations.html')
lines = @gutter.find("[data-buffer-row='#{bufferRow}']")
lines.addClass(klass)
lines.length > 0
find: ->
shadowResult = @root.find.apply(@root, arguments)
if shadowResult.length > 0
shadowResult
else
super
# Public: Get the underlying editor model for this view.
#
# Returns an {TextEditor}
getModel: -> @model
getEditor: -> @model
Object.defineProperty @prototype, 'lineHeight', get: -> @model.getLineHeightInPixels()
Object.defineProperty @prototype, 'charWidth', get: -> @model.getDefaultCharWidth()
Object.defineProperty @prototype, 'firstRenderedScreenRow', get: -> @component.getRenderedRowRange()[0]
Object.defineProperty @prototype, 'lastRenderedScreenRow', get: -> @component.getRenderedRowRange()[1]
Object.defineProperty @prototype, 'active', get: -> @is(@getPaneView()?.activeView)
Object.defineProperty @prototype, 'isFocused', get: -> document.activeElement is @element or document.activeElement is @element.component?.hiddenInputComponent?.getDomNode()
Object.defineProperty @prototype, 'mini', get: -> @model?.isMini()
Object.defineProperty @prototype, 'component', get: -> @element?.component
afterAttach: (onDom) ->
return unless onDom
return if @attached
@attached = true
@trigger 'editor:attached', [this]
beforeRemove: ->
@trigger 'editor:detached', [this]
@trigger 'editor:will-be-removed', [this]
@attached = false
remove: (selector, keepData) ->
@model.destroy() unless keepData
super
scrollTop: (scrollTop) ->
if scrollTop?
@model.setScrollTop(scrollTop)
else
@model.getScrollTop()
scrollLeft: (scrollLeft) ->
if scrollLeft?
@model.setScrollLeft(scrollLeft)
else
@model.getScrollLeft()
scrollToBottom: ->
deprecate 'Use TextEditor::scrollToBottom instead. You can get the editor via editorView.getModel()'
@model.setScrollBottom(Infinity)
scrollToScreenPosition: (screenPosition, options) ->
deprecate 'Use TextEditor::scrollToScreenPosition instead. You can get the editor via editorView.getModel()'
@model.scrollToScreenPosition(screenPosition, options)
scrollToBufferPosition: (bufferPosition, options) ->
deprecate 'Use TextEditor::scrollToBufferPosition instead. You can get the editor via editorView.getModel()'
@model.scrollToBufferPosition(bufferPosition, options)
scrollToCursorPosition: ->
deprecate 'Use TextEditor::scrollToCursorPosition instead. You can get the editor via editorView.getModel()'
@model.scrollToCursorPosition()
pixelPositionForBufferPosition: (bufferPosition) ->
deprecate 'Use TextEditorElement::pixelPositionForBufferPosition instead. You can get the editor via editorView.getModel()'
@model.pixelPositionForBufferPosition(bufferPosition, true)
pixelPositionForScreenPosition: (screenPosition) ->
deprecate 'Use TextEditorElement::pixelPositionForScreenPosition instead. You can get the editor via editorView.getModel()'
@model.pixelPositionForScreenPosition(screenPosition, true)
appendToLinesView: (view) ->
view.css('position', 'absolute')
view.css('z-index', 1)
@overlayer.append(view)
splitLeft: ->
deprecate """
Use Pane::splitLeft instead.
To duplicate this editor into the split use:
editorView.getPaneView().getModel().splitLeft(copyActiveItem: true)
"""
pane = @getPaneView()
pane?.splitLeft(pane?.copyActiveItem()).activeView
splitRight: ->
deprecate """
Use Pane::splitRight instead.
To duplicate this editor into the split use:
editorView.getPaneView().getModel().splitRight(copyActiveItem: true)
"""
pane = @getPaneView()
pane?.splitRight(pane?.copyActiveItem()).activeView
splitUp: ->
deprecate """
Use Pane::splitUp instead.
To duplicate this editor into the split use:
editorView.getPaneView().getModel().splitUp(copyActiveItem: true)
"""
pane = @getPaneView()
pane?.splitUp(pane?.copyActiveItem()).activeView
splitDown: ->
deprecate """
Use Pane::splitDown instead.
To duplicate this editor into the split use:
editorView.getPaneView().getModel().splitDown(copyActiveItem: true)
"""
pane = @getPaneView()
pane?.splitDown(pane?.copyActiveItem()).activeView
# Public: Get this {TextEditorView}'s {PaneView}.
#
# Returns a {PaneView}
getPaneView: ->
@parent('.item-views').parents('atom-pane').view()
getPane: ->
deprecate 'Use TextEditorView::getPaneView() instead'
@getPaneView()
show: ->
super
@component?.checkForVisibilityChange()
hide: ->
super
@component?.checkForVisibilityChange()
pageDown: ->
deprecate('Use editorView.getModel().pageDown()')
@model.pageDown()
pageUp: ->
deprecate('Use editorView.getModel().pageUp()')
@model.pageUp()
getFirstVisibleScreenRow: ->
deprecate 'Use TextEditorElement::getFirstVisibleScreenRow instead.'
@model.getFirstVisibleScreenRow(true)
getLastVisibleScreenRow: ->
deprecate 'Use TextEditor::getLastVisibleScreenRow instead. You can get the editor via editorView.getModel()'
@model.getLastVisibleScreenRow()
getFontFamily: ->
deprecate 'This is going away. Use atom.config.get("editor.fontFamily") instead'
@component?.getFontFamily()
setFontFamily: (fontFamily) ->
deprecate 'This is going away. Use atom.config.set("editor.fontFamily", "my-font") instead'
@component?.setFontFamily(fontFamily)
getFontSize: ->
deprecate 'This is going away. Use atom.config.get("editor.fontSize") instead'
@component?.getFontSize()
setFontSize: (fontSize) ->
deprecate 'This is going away. Use atom.config.set("editor.fontSize", 12) instead'
@component?.setFontSize(fontSize)
setLineHeight: (lineHeight) ->
deprecate 'This is going away. Use atom.config.set("editor.lineHeight", 1.5) instead'
@component.setLineHeight(lineHeight)
setWidthInChars: (widthInChars) ->
@component.getDOMNode().style.width = (@model.getDefaultCharWidth() * widthInChars) + 'px'
setShowIndentGuide: (showIndentGuide) ->
deprecate 'This is going away. Use atom.config.set("editor.showIndentGuide", true|false) instead'
atom.config.set("editor.showIndentGuide", showIndentGuide)
setSoftWrap: (softWrapped) ->
deprecate 'Use TextEditor::setSoftWrapped instead. You can get the editor via editorView.getModel()'
@model.setSoftWrapped(softWrapped)
setShowInvisibles: (showInvisibles) ->
deprecate 'This is going away. Use atom.config.set("editor.showInvisibles", true|false) instead'
@component.setShowInvisibles(showInvisibles)
getText: ->
@model.getText()
setText: (text) ->
@model.setText(text)
insertText: (text) ->
@model.insertText(text)
isInputEnabled: ->
@component.isInputEnabled()
setInputEnabled: (inputEnabled) ->
@component.setInputEnabled(inputEnabled)
requestDisplayUpdate: ->
deprecate('Please remove from your code. ::requestDisplayUpdate no longer does anything')
updateDisplay: ->
deprecate('Please remove from your code. ::updateDisplay no longer does anything')
resetDisplay: ->
deprecate('Please remove from your code. ::resetDisplay no longer does anything')
redraw: ->
deprecate('Please remove from your code. ::redraw no longer does anything')
setPlaceholderText: (placeholderText) ->
deprecate('Use TextEditor::setPlaceholderText instead. eg. editorView.getModel().setPlaceholderText(text)')
@model.setPlaceholderText(placeholderText)
lineElementForScreenRow: (screenRow) ->
$(@component.lineNodeForScreenRow(screenRow))

View File

@@ -1,8 +1,6 @@
_ = require 'underscore-plus'
path = require 'path'
Serializable = require 'serializable'
Delegator = require 'delegato'
{includeDeprecatedAPIs, deprecate} = require 'grim'
Grim = require 'grim'
{CompositeDisposable, Emitter} = require 'event-kit'
{Point, Range} = TextBuffer = require 'text-buffer'
LanguageMode = require './language-mode'
@@ -17,7 +15,8 @@ GutterContainer = require './gutter-container'
# Essential: This class represents all essential editing state for a single
# {TextBuffer}, including cursor and selection positions, folds, and soft wraps.
# If you're manipulating the state of an editor, use this class. If you're
# interested in the visual appearance of editors, use {TextEditorView} instead.
# interested in the visual appearance of editors, use {TextEditorElement}
# instead.
#
# A single {TextBuffer} can belong to multiple editors. For example, if the
# same file is open in two different panes, Atom creates a separate editor for
@@ -55,11 +54,8 @@ GutterContainer = require './gutter-container'
# soft wraps and folds to ensure your code interacts with them correctly.
module.exports =
class TextEditor extends Model
Serializable.includeInto(this)
atom.deserializers.add(this)
Delegator.includeInto(this)
deserializing: false
callDisplayBufferCreatedHook: false
registerEditor: false
buffer: null
@@ -71,11 +67,20 @@ class TextEditor extends Model
selectionFlashDuration: 500
gutterContainer: null
@delegatesMethods 'suggestedIndentForBufferRow', 'autoIndentBufferRow', 'autoIndentBufferRows',
'autoDecreaseIndentForBufferRow', 'toggleLineCommentForBufferRow', 'toggleLineCommentsForBufferRows',
toProperty: 'languageMode'
@deserialize: (state) ->
try
displayBuffer = DisplayBuffer.deserialize(state.displayBuffer)
catch error
if error.syscall is 'read'
return # Error reading the file, don't deserialize an editor for it
else
throw error
constructor: ({@softTabs, initialLine, initialColumn, tabLength, softWrapped, @displayBuffer, buffer, registerEditor, suppressCursorCreation, @mini, @placeholderText, lineNumberGutterVisible, largeFileMode}={}) ->
state.displayBuffer = displayBuffer
state.registerEditor = true
new this(state)
constructor: ({@softTabs, @scrollRow, @scrollColumn, initialLine, initialColumn, tabLength, softWrapped, @displayBuffer, buffer, registerEditor, suppressCursorCreation, @mini, @placeholderText, lineNumberGutterVisible, largeFileMode}={}) ->
super
@emitter = new Emitter
@@ -104,14 +109,6 @@ class TextEditor extends Model
@setEncoding(atom.config.get('core.fileEncoding', scope: @getRootScopeDescriptor()))
@disposables.add @displayBuffer.onDidChangeScrollTop (scrollTop) =>
@emit 'scroll-top-changed', scrollTop if includeDeprecatedAPIs
@emitter.emit 'did-change-scroll-top', scrollTop
@disposables.add @displayBuffer.onDidChangeScrollLeft (scrollLeft) =>
@emit 'scroll-left-changed', scrollLeft if includeDeprecatedAPIs
@emitter.emit 'did-change-scroll-left', scrollLeft
@gutterContainer = new GutterContainer(this)
@lineNumberGutter = @gutterContainer.addGutter
name: 'line-number'
@@ -120,45 +117,25 @@ class TextEditor extends Model
atom.workspace?.editorAdded(this) if registerEditor
serializeParams: ->
serialize: ->
deserializer: 'TextEditor'
id: @id
softTabs: @softTabs
scrollTop: @scrollTop
scrollLeft: @scrollLeft
scrollRow: @getScrollRow()
scrollColumn: @getScrollColumn()
displayBuffer: @displayBuffer.serialize()
deserializeParams: (params) ->
try
displayBuffer = DisplayBuffer.deserialize(params.displayBuffer)
catch error
if error.syscall is 'read'
return # Error reading the file, don't deserialize an editor for it
else
throw error
params.displayBuffer = displayBuffer
params.registerEditor = true
params
subscribeToBuffer: ->
@buffer.retain()
@disposables.add @buffer.onDidChangePath =>
unless atom.project.getPaths().length > 0
atom.project.setPaths([path.dirname(@getPath())])
@emit "title-changed" if includeDeprecatedAPIs
@emitter.emit 'did-change-title', @getTitle()
@emit "path-changed" if includeDeprecatedAPIs
@emitter.emit 'did-change-path', @getPath()
@disposables.add @buffer.onDidChangeEncoding =>
@emitter.emit 'did-change-encoding', @getEncoding()
@disposables.add @buffer.onDidDestroy => @destroy()
# TODO: remove these when we remove the deprecations. They are old events.
if includeDeprecatedAPIs
@subscribe @buffer.onDidStopChanging => @emit "contents-modified"
@subscribe @buffer.onDidConflict => @emit "contents-conflicted"
@subscribe @buffer.onDidChangeModified => @emit "modified-status-changed"
@preserveCursorPositionOnBufferReload()
subscribeToDisplayBuffer: ->
@@ -167,22 +144,14 @@ class TextEditor extends Model
@disposables.add @displayBuffer.onDidTokenize => @handleTokenization()
@disposables.add @displayBuffer.onDidChange (e) =>
@mergeIntersectingSelections()
@emit 'screen-lines-changed', e if includeDeprecatedAPIs
@emitter.emit 'did-change', e
# TODO: remove these when we remove the deprecations. Though, no one is likely using them
if includeDeprecatedAPIs
@subscribe @displayBuffer.onDidChangeSoftWrapped (softWrapped) => @emit 'soft-wrap-changed', softWrapped
@subscribe @displayBuffer.onDidAddDecoration (decoration) => @emit 'decoration-added', decoration
@subscribe @displayBuffer.onDidRemoveDecoration (decoration) => @emit 'decoration-removed', decoration
subscribeToTabTypeConfig: ->
@tabTypeSubscription?.dispose()
@tabTypeSubscription = atom.config.observe 'editor.tabType', scope: @getRootScopeDescriptor(), =>
@softTabs = @shouldUseSoftTabs(defaultValue: @softTabs)
destroyed: ->
@unsubscribe() if includeDeprecatedAPIs
@disposables.dispose()
@tabTypeSubscription.dispose()
selection.destroy() for selection in @selections.slice()
@@ -458,10 +427,17 @@ class TextEditor extends Model
@displayBuffer.onDidChangeCharacterWidths(callback)
onDidChangeScrollTop: (callback) ->
@emitter.on 'did-change-scroll-top', callback
Grim.deprecate("This is now a view method. Call TextEditorElement::onDidChangeScrollTop instead.")
atom.views.getView(this).onDidChangeScrollTop(callback)
onDidChangeScrollLeft: (callback) ->
@emitter.on 'did-change-scroll-left', callback
Grim.deprecate("This is now a view method. Call TextEditorElement::onDidChangeScrollLeft instead.")
atom.views.getView(this).onDidChangeScrollLeft(callback)
onDidRequestAutoscroll: (callback) ->
@displayBuffer.onDidRequestAutoscroll(callback)
# TODO Remove once the tabs package no longer uses .on subscriptions
onDidChangeIcon: (callback) ->
@@ -545,11 +521,15 @@ class TextEditor extends Model
# Set the number of characters that can be displayed horizontally in the
# editor.
#
# * `editorWidthInChars` A {Number} representing the width of the {TextEditorView}
# in characters.
# * `editorWidthInChars` A {Number} representing the width of the
# {TextEditorElement} in characters.
setEditorWidthInChars: (editorWidthInChars) ->
@displayBuffer.setEditorWidthInChars(editorWidthInChars)
# Returns the editor width in characters.
getEditorWidthInChars: ->
@displayBuffer.getEditorWidthInChars()
###
Section: File Details
###
@@ -778,7 +758,6 @@ class TextEditor extends Model
(selection) =>
range = selection.insertText(text, options)
didInsertEvent = {text, range}
@emit('did-insert-text', didInsertEvent) if includeDeprecatedAPIs
@emitter.emit 'did-insert-text', didInsertEvent
range
, groupingInterval
@@ -1130,10 +1109,14 @@ class TextEditor extends Model
@buffer.transact(groupingInterval, fn)
# Deprecated: Start an open-ended transaction.
beginTransaction: (groupingInterval) -> @buffer.beginTransaction(groupingInterval)
beginTransaction: (groupingInterval) ->
Grim.deprecate('Transactions should be performed via TextEditor::transact only')
@buffer.beginTransaction(groupingInterval)
# Deprecated: Commit an open-ended transaction started with {::beginTransaction}.
commitTransaction: -> @buffer.commitTransaction()
commitTransaction: ->
Grim.deprecate('Transactions should be performed via TextEditor::transact only')
@buffer.commitTransaction()
# Extended: Abort an open transaction, undoing any operations performed so far
# within the transaction.
@@ -1275,7 +1258,7 @@ class TextEditor extends Model
# is invalidated, or is destroyed, the decoration will be updated to reflect
# the marker's state.
#
# There are three types of supported decorations:
# The following are the supported decorations types:
#
# * __line__: Adds your CSS `class` to the line nodes within the range
# marked by the marker
@@ -1291,43 +1274,50 @@ class TextEditor extends Model
# <div class="region"></div>
# </div>
# ```
# * __overlay__: Positions the view associated with the given item at the head
# or tail of the given `Marker`.
# * __gutter__: A decoration that tracks a {Marker} in a {Gutter}. Gutter
# decorations are created by calling {Gutter::decorateMarker} on the
# desired `Gutter` instance.
#
# ## Arguments
#
# * `marker` A {Marker} you want this decoration to follow.
# * `decorationParams` An {Object} representing the decoration e.g.
# `{type: 'line-number', class: 'linter-error'}`
# * `type` There are a few supported decoration types: `line-number`, `line`,
# `highlight`, and `overlay`. The behavior of the types are as follows:
# * `line-number` Adds the given `class` to the line numbers overlapping the
# rows spanned by the marker.
# * `type` There are several supported decoration types. The behavior of the
# types are as follows:
# * `line` Adds the given `class` to the lines overlapping the rows
# spanned by the marker.
# spanned by the `Marker`.
# * `line-number` Adds the given `class` to the line numbers overlapping
# the rows spanned by the `Marker`.
# * `highlight` Creates a `.highlight` div with the nested class with up
# to 3 nested regions that fill the area spanned by the marker.
# to 3 nested regions that fill the area spanned by the `Marker`.
# * `overlay` Positions the view associated with the given item at the
# head or tail of the given marker, depending on the `position`
# head or tail of the given `Marker`, depending on the `position`
# property.
# * `gutter` Tracks a {Marker} in a {Gutter}. Created by calling
# {Gutter::decorateMarker} on the desired `Gutter` instance.
# * `class` This CSS class will be applied to the decorated line number,
# line, highlight, or overlay.
# * `item` (optional) An {HTMLElement} or a model {Object} with a
# corresponding view registered. Only applicable to the `gutter` and
# `overlay` types.
# * `onlyHead` (optional) If `true`, the decoration will only be applied to
# the head of the marker. Only applicable to the `line` and `line-number`
# types.
# * `onlyEmpty` (optional) If `true`, the decoration will only be applied if
# the associated marker is empty. Only applicable to the `line` and
# the head of the `Marker`. Only applicable to the `line` and
# `line-number` types.
# * `onlyEmpty` (optional) If `true`, the decoration will only be applied if
# the associated `Marker` is empty. Only applicable to the `gutter`,
# `line`, and `line-number` types.
# * `onlyNonEmpty` (optional) If `true`, the decoration will only be applied
# if the associated marker is non-empty. Only applicable to the `line`
# and `line-number` types.
# if the associated `Marker` is non-empty. Only applicable to the
# `gutter`, `line`, and `line-number` types.
# * `position` (optional) Only applicable to decorations of type `overlay`,
# controls where the overlay view is positioned relative to the marker.
# controls where the overlay view is positioned relative to the `Marker`.
# Values can be `'head'` (the default), or `'tail'`.
#
# Returns a {Decoration} object
decorateMarker: (marker, decorationParams) ->
if includeDeprecatedAPIs and decorationParams.type is 'gutter' and not decorationParams.gutterName
deprecate("Decorations of `type: 'gutter'` have been renamed to `type: 'line-number'`.")
decorationParams.type = 'line-number'
@displayBuffer.decorateMarker(marker, decorationParams)
# Essential: Get all the decorations within a screen row range.
@@ -1757,7 +1747,6 @@ class TextEditor extends Model
@decorateMarker(marker, type: 'line-number', class: 'cursor-line')
@decorateMarker(marker, type: 'line-number', class: 'cursor-line-no-selection', onlyHead: true, onlyEmpty: true)
@decorateMarker(marker, type: 'line', class: 'cursor-line', onlyEmpty: true)
@emit 'cursor-added', cursor if includeDeprecatedAPIs
@emitter.emit 'did-add-cursor', cursor
cursor
@@ -2246,7 +2235,6 @@ class TextEditor extends Model
if selection.intersectsBufferRange(selectionBufferRange)
return selection
else
@emit 'selection-added', selection if includeDeprecatedAPIs
@emitter.emit 'did-add-selection', selection
selection
@@ -2263,11 +2251,11 @@ class TextEditor extends Model
@consolidateSelections()
@getLastSelection().clear(options)
# Reduce multiple selections to the most recently added selection.
# Reduce multiple selections to the least recently added selection.
consolidateSelections: ->
selections = @getSelections()
if selections.length > 1
selection.destroy() for selection in selections[0...-1]
selection.destroy() for selection in selections[1...(selections.length)]
true
else
false
@@ -2366,10 +2354,6 @@ class TextEditor extends Model
# Returns a {Boolean} or undefined if no non-comment lines had leading
# whitespace.
usesSoftTabs: ->
# FIXME Remove once this can be specified as a scoped setting in the
# language-make package
return false if @getGrammar()?.scopeName is 'source.makefile'
for bufferRow in [0..@buffer.getLastRow()]
continue if @displayBuffer.tokenizedBuffer.tokenizedLineForRow(bufferRow).isComment()
@@ -2660,11 +2644,10 @@ class TextEditor extends Model
range = selection.insertText(text, options)
didInsertEvent = {text, range}
@emit('did-insert-text', didInsertEvent) if includeDeprecatedAPIs
@emitter.emit 'did-insert-text', didInsertEvent
# Essential: For each selection, if the selection is empty, cut all characters
# of the containing line following the cursor. Otherwise cut the selected
# of the containing screen line following the cursor. Otherwise cut the selected
# text.
cutToEndOfLine: ->
maintainClipboard = false
@@ -2672,6 +2655,15 @@ class TextEditor extends Model
selection.cutToEndOfLine(maintainClipboard)
maintainClipboard = true
# Essential: For each selection, if the selection is empty, cut all characters
# of the containing buffer line following the cursor. Otherwise cut the
# selected text.
cutToEndOfBufferLine: ->
maintainClipboard = false
@mutateSelectedText (selection) ->
selection.cutToEndOfBufferLine(maintainClipboard)
maintainClipboard = true
###
Section: Folds
###
@@ -2871,25 +2863,27 @@ class TextEditor extends Model
scrollToScreenPosition: (screenPosition, options) ->
@displayBuffer.scrollToScreenPosition(screenPosition, options)
# Essential: Scrolls the editor to the top
scrollToTop: ->
@setScrollTop(0)
Grim.deprecate("This is now a view method. Call TextEditorElement::scrollToTop instead.")
atom.views.getView(this).scrollToTop()
# Essential: Scrolls the editor to the bottom
scrollToBottom: ->
@setScrollBottom(Infinity)
Grim.deprecate("This is now a view method. Call TextEditorElement::scrollToTop instead.")
atom.views.getView(this).scrollToBottom()
scrollToScreenRange: (screenRange, options) -> @displayBuffer.scrollToScreenRange(screenRange, options)
horizontallyScrollable: -> @displayBuffer.horizontallyScrollable()
getHorizontalScrollbarHeight: ->
Grim.deprecate("This is now a view method. Call TextEditorElement::getHorizontalScrollbarHeight instead.")
verticallyScrollable: -> @displayBuffer.verticallyScrollable()
atom.views.getView(this).getHorizontalScrollbarHeight()
getHorizontalScrollbarHeight: -> @displayBuffer.getHorizontalScrollbarHeight()
setHorizontalScrollbarHeight: (height) -> @displayBuffer.setHorizontalScrollbarHeight(height)
getVerticalScrollbarWidth: ->
Grim.deprecate("This is now a view method. Call TextEditorElement::getVerticalScrollbarWidth instead.")
getVerticalScrollbarWidth: -> @displayBuffer.getVerticalScrollbarWidth()
setVerticalScrollbarWidth: (width) -> @displayBuffer.setVerticalScrollbarWidth(width)
atom.views.getView(this).getVerticalScrollbarWidth()
pageUp: ->
@moveUp(@getRowsPerPage())
@@ -2905,7 +2899,9 @@ class TextEditor extends Model
# Returns the number of rows per page
getRowsPerPage: ->
Math.max(1, Math.floor(@getHeight() / @getLineHeightInPixels()))
Math.max(@rowsPerPage ? 1, 1)
setRowsPerPage: (@rowsPerPage) ->
###
Section: Config
@@ -2952,25 +2948,21 @@ class TextEditor extends Model
@placeholderText = placeholderText
@emitter.emit 'did-change-placeholder-text', @placeholderText
getFirstVisibleScreenRow: (suppressDeprecation) ->
unless suppressDeprecation
deprecate("This is now a view method. Call TextEditorElement::getFirstVisibleScreenRow instead.")
@getVisibleRowRange()[0]
getFirstVisibleScreenRow: ->
deprecate("This is now a view method. Call TextEditorElement::getFirstVisibleScreenRow instead.")
atom.views.getView(this).getVisibleRowRange()[0]
getLastVisibleScreenRow: (suppressDeprecation) ->
unless suppressDeprecation
deprecate("This is now a view method. Call TextEditorElement::getLastVisibleScreenRow instead.")
@getVisibleRowRange()[1]
getLastVisibleScreenRow: ->
Grim.deprecate("This is now a view method. Call TextEditorElement::getLastVisibleScreenRow instead.")
atom.views.getView(this).getVisibleRowRange()[1]
pixelPositionForBufferPosition: (bufferPosition, suppressDeprecation) ->
unless suppressDeprecation
deprecate("This method is deprecated on the model layer. Use `TextEditorElement::pixelPositionForBufferPosition` instead")
@displayBuffer.pixelPositionForBufferPosition(bufferPosition)
pixelPositionForBufferPosition: (bufferPosition) ->
Grim.deprecate("This method is deprecated on the model layer. Use `TextEditorElement::pixelPositionForBufferPosition` instead")
atom.views.getView(this).pixelPositionForBufferPosition(bufferPosition)
pixelPositionForScreenPosition: (screenPosition, suppressDeprecation) ->
unless suppressDeprecation
deprecate("This method is deprecated on the model layer. Use `TextEditorElement::pixelPositionForScreenPosition` instead")
@displayBuffer.pixelPositionForScreenPosition(screenPosition)
pixelPositionForScreenPosition: (screenPosition) ->
Grim.deprecate("This method is deprecated on the model layer. Use `TextEditorElement::pixelPositionForScreenPosition` instead")
atom.views.getView(this).pixelPositionForScreenPosition(screenPosition)
getSelectionMarkerAttributes: ->
{type: 'selection', editorId: @id, invalidate: 'never', maintainHistory: true}
@@ -2996,38 +2988,110 @@ class TextEditor extends Model
getDefaultCharWidth: -> @displayBuffer.getDefaultCharWidth()
setDefaultCharWidth: (defaultCharWidth) -> @displayBuffer.setDefaultCharWidth(defaultCharWidth)
setHeight: (height) -> @displayBuffer.setHeight(height)
getHeight: -> @displayBuffer.getHeight()
setHeight: (height, reentrant=false) ->
if reentrant
@displayBuffer.setHeight(height)
else
Grim.deprecate("This is now a view method. Call TextEditorElement::setHeight instead.")
atom.views.getView(this).setHeight(height)
getHeight: ->
Grim.deprecate("This is now a view method. Call TextEditorElement::getHeight instead.")
@displayBuffer.getHeight()
getClientHeight: -> @displayBuffer.getClientHeight()
setWidth: (width) -> @displayBuffer.setWidth(width)
getWidth: -> @displayBuffer.getWidth()
setWidth: (width, reentrant=false) ->
if reentrant
@displayBuffer.setWidth(width)
else
Grim.deprecate("This is now a view method. Call TextEditorElement::setWidth instead.")
atom.views.getView(this).setWidth(width)
getScrollTop: -> @displayBuffer.getScrollTop()
setScrollTop: (scrollTop) -> @displayBuffer.setScrollTop(scrollTop)
getWidth: ->
Grim.deprecate("This is now a view method. Call TextEditorElement::getWidth instead.")
@displayBuffer.getWidth()
getScrollBottom: -> @displayBuffer.getScrollBottom()
setScrollBottom: (scrollBottom) -> @displayBuffer.setScrollBottom(scrollBottom)
getScrollRow: -> @scrollRow
setScrollRow: (@scrollRow) ->
getScrollLeft: -> @displayBuffer.getScrollLeft()
setScrollLeft: (scrollLeft) -> @displayBuffer.setScrollLeft(scrollLeft)
getScrollColumn: -> @scrollColumn
setScrollColumn: (@scrollColumn) ->
getScrollRight: -> @displayBuffer.getScrollRight()
setScrollRight: (scrollRight) -> @displayBuffer.setScrollRight(scrollRight)
getScrollTop: ->
Grim.deprecate("This is now a view method. Call TextEditorElement::getScrollTop instead.")
getScrollHeight: -> @displayBuffer.getScrollHeight()
getScrollWidth: -> @displayBuffer.getScrollWidth()
atom.views.getView(this).getScrollTop()
getVisibleRowRange: -> @displayBuffer.getVisibleRowRange()
setScrollTop: (scrollTop) ->
Grim.deprecate("This is now a view method. Call TextEditorElement::setScrollTop instead.")
intersectsVisibleRowRange: (startRow, endRow) -> @displayBuffer.intersectsVisibleRowRange(startRow, endRow)
atom.views.getView(this).setScrollTop(scrollTop)
selectionIntersectsVisibleRowRange: (selection) -> @displayBuffer.selectionIntersectsVisibleRowRange(selection)
getScrollBottom: ->
Grim.deprecate("This is now a view method. Call TextEditorElement::getScrollBottom instead.")
screenPositionForPixelPosition: (pixelPosition) -> @displayBuffer.screenPositionForPixelPosition(pixelPosition)
atom.views.getView(this).getScrollBottom()
pixelRectForScreenRange: (screenRange) -> @displayBuffer.pixelRectForScreenRange(screenRange)
setScrollBottom: (scrollBottom) ->
Grim.deprecate("This is now a view method. Call TextEditorElement::setScrollBottom instead.")
atom.views.getView(this).setScrollBottom(scrollBottom)
getScrollLeft: ->
Grim.deprecate("This is now a view method. Call TextEditorElement::getScrollLeft instead.")
atom.views.getView(this).getScrollLeft()
setScrollLeft: (scrollLeft) ->
Grim.deprecate("This is now a view method. Call TextEditorElement::setScrollLeft instead.")
atom.views.getView(this).setScrollLeft(scrollLeft)
getScrollRight: ->
Grim.deprecate("This is now a view method. Call TextEditorElement::getScrollRight instead.")
atom.views.getView(this).getScrollRight()
setScrollRight: (scrollRight) ->
Grim.deprecate("This is now a view method. Call TextEditorElement::setScrollRight instead.")
atom.views.getView(this).setScrollRight(scrollRight)
getScrollHeight: ->
Grim.deprecate("This is now a view method. Call TextEditorElement::getScrollHeight instead.")
atom.views.getView(this).getScrollHeight()
getScrollWidth: ->
Grim.deprecate("This is now a view method. Call TextEditorElement::getScrollWidth instead.")
atom.views.getView(this).getScrollWidth()
getVisibleRowRange: ->
Grim.deprecate("This is now a view method. Call TextEditorElement::getVisibleRowRange instead.")
atom.views.getView(this).getVisibleRowRange()
intersectsVisibleRowRange: (startRow, endRow) ->
Grim.deprecate("This is now a view method. Call TextEditorElement::intersectsVisibleRowRange instead.")
atom.views.getView(this).intersectsVisibleRowRange(startRow, endRow)
selectionIntersectsVisibleRowRange: (selection) ->
Grim.deprecate("This is now a view method. Call TextEditorElement::selectionIntersectsVisibleRowRange instead.")
atom.views.getView(this).selectionIntersectsVisibleRowRange(selection)
screenPositionForPixelPosition: (pixelPosition) ->
Grim.deprecate("This is now a view method. Call TextEditorElement::screenPositionForPixelPosition instead.")
atom.views.getView(this).screenPositionForPixelPosition(pixelPosition)
pixelRectForScreenRange: (screenRange) ->
Grim.deprecate("This is now a view method. Call TextEditorElement::pixelRectForScreenRange instead.")
atom.views.getView(this).pixelRectForScreenRange(screenRange)
###
Section: Utility
@@ -3042,235 +3106,21 @@ class TextEditor extends Model
result = true
cancel = -> result = false
willInsertEvent = {cancel, text}
@emit('will-insert-text', willInsertEvent) if includeDeprecatedAPIs
@emitter.emit 'will-insert-text', willInsertEvent
result
if includeDeprecatedAPIs
TextEditor.delegatesProperties '$lineHeightInPixels', '$defaultCharWidth', '$height', '$width',
'$verticalScrollbarWidth', '$horizontalScrollbarHeight', '$scrollTop', '$scrollLeft',
toProperty: 'displayBuffer'
###
Section: Language Mode Delegated Methods
###
TextEditor::getViewClass = ->
require './text-editor-view'
suggestedIndentForBufferRow: (bufferRow, options) -> @languageMode.suggestedIndentForBufferRow(bufferRow, options)
TextEditor::joinLine = ->
deprecate("Use TextEditor::joinLines() instead")
@joinLines()
autoIndentBufferRow: (bufferRow, options) -> @languageMode.autoIndentBufferRow(bufferRow, options)
TextEditor::scopesAtCursor = ->
deprecate 'Use editor.getLastCursor().getScopeDescriptor() instead'
@getLastCursor().getScopeDescriptor().getScopesArray()
autoIndentBufferRows: (startRow, endRow) -> @languageMode.autoIndentBufferRows(startRow, endRow)
TextEditor::getCursorScopes = ->
deprecate 'Use editor.getLastCursor().getScopeDescriptor() instead'
@scopesAtCursor()
autoDecreaseIndentForBufferRow: (bufferRow) -> @languageMode.autoDecreaseIndentForBufferRow(bufferRow)
TextEditor::getUri = ->
deprecate("Use `::getURI` instead")
@getURI()
toggleLineCommentForBufferRow: (row) -> @languageMode.toggleLineCommentsForBufferRow(row)
TextEditor::lineForBufferRow = (bufferRow) ->
deprecate 'Use TextEditor::lineTextForBufferRow(bufferRow) instead'
@lineTextForBufferRow(bufferRow)
TextEditor::lineForScreenRow = (screenRow) ->
deprecate "TextEditor::tokenizedLineForScreenRow(bufferRow) is the new name. But it's private. Try to use TextEditor::lineTextForScreenRow instead"
@tokenizedLineForScreenRow(screenRow)
TextEditor::linesForScreenRows = (start, end) ->
deprecate "Use TextEditor::tokenizedLinesForScreenRows instead"
@tokenizedLinesForScreenRows(start, end)
TextEditor::lineLengthForBufferRow = (row) ->
deprecate "Use editor.lineTextForBufferRow(row).length instead"
@lineTextForBufferRow(row).length
TextEditor::duplicateLine = ->
deprecate("Use TextEditor::duplicateLines() instead")
@duplicateLines()
TextEditor::scopesForBufferPosition = (bufferPosition) ->
deprecate 'Use ::scopeDescriptorForBufferPosition instead. The return value has changed! It now returns a `ScopeDescriptor`'
@scopeDescriptorForBufferPosition(bufferPosition).getScopesArray()
TextEditor::toggleSoftWrap = ->
deprecate("Use TextEditor::toggleSoftWrapped instead")
@toggleSoftWrapped()
TextEditor::setSoftWrap = (softWrapped) ->
deprecate("Use TextEditor::setSoftWrapped instead")
@setSoftWrapped(softWrapped)
TextEditor::backspaceToBeginningOfWord = ->
deprecate("Use TextEditor::deleteToBeginningOfWord() instead")
@deleteToBeginningOfWord()
TextEditor::backspaceToBeginningOfLine = ->
deprecate("Use TextEditor::deleteToBeginningOfLine() instead")
@deleteToBeginningOfLine()
TextEditor::getGutterDecorations = (propertyFilter) ->
deprecate("Use ::getLineNumberDecorations instead")
@getLineNumberDecorations(propertyFilter)
TextEditor::getCursorScreenRow = ->
deprecate('Use `editor.getCursorScreenPosition().row` instead')
@getCursorScreenPosition().row
TextEditor::moveCursorUp = (lineCount) ->
deprecate("Use TextEditor::moveUp() instead")
@moveUp(lineCount)
TextEditor::moveCursorDown = (lineCount) ->
deprecate("Use TextEditor::moveDown() instead")
@moveDown(lineCount)
TextEditor::moveCursorLeft = ->
deprecate("Use TextEditor::moveLeft() instead")
@moveLeft()
TextEditor::moveCursorRight = ->
deprecate("Use TextEditor::moveRight() instead")
@moveRight()
TextEditor::moveCursorToBeginningOfLine = ->
deprecate("Use TextEditor::moveToBeginningOfLine() instead")
@moveToBeginningOfLine()
TextEditor::moveCursorToBeginningOfScreenLine = ->
deprecate("Use TextEditor::moveToBeginningOfScreenLine() instead")
@moveToBeginningOfScreenLine()
TextEditor::moveCursorToFirstCharacterOfLine = ->
deprecate("Use TextEditor::moveToFirstCharacterOfLine() instead")
@moveToFirstCharacterOfLine()
TextEditor::moveCursorToEndOfLine = ->
deprecate("Use TextEditor::moveToEndOfLine() instead")
@moveToEndOfLine()
TextEditor::moveCursorToEndOfScreenLine = ->
deprecate("Use TextEditor::moveToEndOfScreenLine() instead")
@moveToEndOfScreenLine()
TextEditor::moveCursorToBeginningOfWord = ->
deprecate("Use TextEditor::moveToBeginningOfWord() instead")
@moveToBeginningOfWord()
TextEditor::moveCursorToEndOfWord = ->
deprecate("Use TextEditor::moveToEndOfWord() instead")
@moveToEndOfWord()
TextEditor::moveCursorToTop = ->
deprecate("Use TextEditor::moveToTop() instead")
@moveToTop()
TextEditor::moveCursorToBottom = ->
deprecate("Use TextEditor::moveToBottom() instead")
@moveToBottom()
TextEditor::moveCursorToBeginningOfNextWord = ->
deprecate("Use TextEditor::moveToBeginningOfNextWord() instead")
@moveToBeginningOfNextWord()
TextEditor::moveCursorToPreviousWordBoundary = ->
deprecate("Use TextEditor::moveToPreviousWordBoundary() instead")
@moveToPreviousWordBoundary()
TextEditor::moveCursorToNextWordBoundary = ->
deprecate("Use TextEditor::moveToNextWordBoundary() instead")
@moveToNextWordBoundary()
TextEditor::moveCursorToBeginningOfNextParagraph = ->
deprecate("Use TextEditor::moveToBeginningOfNextParagraph() instead")
@moveToBeginningOfNextParagraph()
TextEditor::moveCursorToBeginningOfPreviousParagraph = ->
deprecate("Use TextEditor::moveToBeginningOfPreviousParagraph() instead")
@moveToBeginningOfPreviousParagraph()
TextEditor::getCursor = ->
deprecate("Use TextEditor::getLastCursor() instead")
@getLastCursor()
TextEditor::selectLine = ->
deprecate('Use TextEditor::selectLinesContainingCursors instead')
@selectLinesContainingCursors()
TextEditor::selectWord = ->
deprecate('Use TextEditor::selectWordsContainingCursors instead')
@selectWordsContainingCursors()
TextEditor::getSelection = (index) ->
if index?
deprecate("Use TextEditor::getSelections()[index] instead when getting a specific selection")
@getSelections()[index]
else
deprecate("Use TextEditor::getLastSelection() instead")
@getLastSelection()
TextEditor::getSoftWrapped = ->
deprecate("Use TextEditor::isSoftWrapped instead")
@displayBuffer.isSoftWrapped()
EmitterMixin = require('emissary').Emitter
TextEditor::on = (eventName) ->
switch eventName
when 'title-changed'
deprecate("Use TextEditor::onDidChangeTitle instead")
when 'path-changed'
deprecate("Use TextEditor::onDidChangePath instead")
when 'modified-status-changed'
deprecate("Use TextEditor::onDidChangeModified instead")
when 'soft-wrap-changed'
deprecate("Use TextEditor::onDidChangeSoftWrapped instead")
when 'grammar-changed'
deprecate("Use TextEditor::onDidChangeGrammar instead")
when 'character-widths-changed'
deprecate("Use TextEditor::onDidChangeCharacterWidths instead")
when 'contents-modified'
deprecate("Use TextEditor::onDidStopChanging instead")
when 'contents-conflicted'
deprecate("Use TextEditor::onDidConflict instead")
when 'will-insert-text'
deprecate("Use TextEditor::onWillInsertText instead")
when 'did-insert-text'
deprecate("Use TextEditor::onDidInsertText instead")
when 'cursor-added'
deprecate("Use TextEditor::onDidAddCursor instead")
when 'cursor-removed'
deprecate("Use TextEditor::onDidRemoveCursor instead")
when 'cursor-moved'
deprecate("Use TextEditor::onDidChangeCursorPosition instead")
when 'selection-added'
deprecate("Use TextEditor::onDidAddSelection instead")
when 'selection-removed'
deprecate("Use TextEditor::onDidRemoveSelection instead")
when 'selection-screen-range-changed'
deprecate("Use TextEditor::onDidChangeSelectionRange instead")
when 'decoration-added'
deprecate("Use TextEditor::onDidAddDecoration instead")
when 'decoration-removed'
deprecate("Use TextEditor::onDidRemoveDecoration instead")
when 'decoration-updated'
deprecate("Use Decoration::onDidChangeProperties instead. You will get the decoration back from `TextEditor::decorateMarker()`")
when 'decoration-changed'
deprecate("Use Marker::onDidChange instead. e.g. `editor::decorateMarker(...).getMarker().onDidChange()`")
when 'screen-lines-changed'
deprecate("Use TextEditor::onDidChange instead")
when 'scroll-top-changed'
deprecate("Use TextEditor::onDidChangeScrollTop instead")
when 'scroll-left-changed'
deprecate("Use TextEditor::onDidChangeScrollLeft instead")
else
deprecate("TextEditor::on is deprecated. Use documented event subscription methods instead.")
EmitterMixin::on.apply(this, arguments)
toggleLineCommentsForBufferRows: (start, end) -> @languageMode.toggleLineCommentsForBufferRows(start, end)

View File

@@ -30,7 +30,6 @@ isSurrogatePair = (charCodeA, charCodeB) ->
#
# Return a {Boolean}.
isVariationSequence = (charCodeA, charCodeB) ->
return false if charCodeA is 0
not isVariationSelector(charCodeA) and isVariationSelector(charCodeB)
# Are the given character codes a combined character pair?
@@ -40,7 +39,6 @@ isVariationSequence = (charCodeA, charCodeB) ->
#
# Return a {Boolean}.
isCombinedCharacter = (charCodeA, charCodeB) ->
return false if charCodeA is 0
not isCombiningCharacter(charCodeA) and isCombiningCharacter(charCodeB)
# Is the character at the given index the start of high/low surrogate pair

View File

@@ -3,8 +3,6 @@ _ = require 'underscore-plus'
{Emitter, Disposable, CompositeDisposable} = require 'event-kit'
{File} = require 'pathwatcher'
fs = require 'fs-plus'
Q = require 'q'
Grim = require 'grim'
# Extended: Handles loading and activating available themes.
#
@@ -17,35 +15,6 @@ class ThemeManager
@lessCache = null
@initialLoadComplete = false
@packageManager.registerPackageActivator(this, ['theme'])
@sheetsByStyleElement = new WeakMap
stylesElement = document.head.querySelector('atom-styles')
stylesElement.onDidAddStyleElement @styleElementAdded.bind(this)
stylesElement.onDidRemoveStyleElement @styleElementRemoved.bind(this)
stylesElement.onDidUpdateStyleElement @styleElementUpdated.bind(this)
styleElementAdded: (styleElement) ->
{sheet} = styleElement
@sheetsByStyleElement.set(styleElement, sheet)
@emit 'stylesheet-added', sheet if Grim.includeDeprecatedAPIs
@emitter.emit 'did-add-stylesheet', sheet
@emit 'stylesheets-changed' if Grim.includeDeprecatedAPIs
@emitter.emit 'did-change-stylesheets'
styleElementRemoved: (styleElement) ->
sheet = @sheetsByStyleElement.get(styleElement)
@emit 'stylesheet-removed', sheet if Grim.includeDeprecatedAPIs
@emitter.emit 'did-remove-stylesheet', sheet
@emit 'stylesheets-changed' if Grim.includeDeprecatedAPIs
@emitter.emit 'did-change-stylesheets'
styleElementUpdated: ({sheet}) ->
@emit 'stylesheet-removed', sheet if Grim.includeDeprecatedAPIs
@emitter.emit 'did-remove-stylesheet', sheet
@emit 'stylesheet-added', sheet if Grim.includeDeprecatedAPIs
@emitter.emit 'did-add-stylesheet', sheet
@emit 'stylesheets-changed' if Grim.includeDeprecatedAPIs
@emitter.emit 'did-change-stylesheets'
###
Section: Event Subscription
@@ -57,7 +26,6 @@ class ThemeManager
# * `callback` {Function}
onDidChangeActiveThemes: (callback) ->
@emitter.on 'did-change-active-themes', callback
@emitter.on 'did-reload-all', callback # TODO: Remove once deprecated pre-1.0 APIs are gone
###
Section: Accessing Available Themes
@@ -97,6 +65,13 @@ class ThemeManager
Section: Managing Enabled Themes
###
warnForNonExistentThemes: ->
themeNames = atom.config.get('core.themes') ? []
themeNames = [themeNames] unless _.isArray(themeNames)
for themeName in themeNames
unless themeName and typeof themeName is 'string' and atom.packages.resolvePackagePath(themeName)
console.warn("Enabled theme '#{themeName}' is not installed.")
# Public: Get the enabled theme names from the config.
#
# Returns an array of theme names in the order that they should be activated.
@@ -106,7 +81,6 @@ class ThemeManager
themeNames = themeNames.filter (themeName) ->
if themeName and typeof themeName is 'string'
return true if atom.packages.resolvePackagePath(themeName)
console.warn("Enabled theme '#{themeName}' is not installed.")
false
# Use a built-in syntax and UI theme any time the configured themes are not
@@ -260,32 +234,30 @@ class ThemeManager
string.replace(/\\/g, '/')
activateThemes: ->
deferred = Q.defer()
new Promise (resolve) =>
# atom.config.observe runs the callback once, then on subsequent changes.
atom.config.observe 'core.themes', =>
@deactivateThemes()
# atom.config.observe runs the callback once, then on subsequent changes.
atom.config.observe 'core.themes', =>
@deactivateThemes()
@warnForNonExistentThemes()
@refreshLessCache() # Update cache for packages in core.themes config
@refreshLessCache() # Update cache for packages in core.themes config
promises = []
for themeName in @getEnabledThemeNames()
if @packageManager.resolvePackagePath(themeName)
promises.push(@packageManager.activatePackage(themeName))
else
console.warn("Failed to activate theme '#{themeName}' because it isn't installed.")
promises = []
for themeName in @getEnabledThemeNames()
if @packageManager.resolvePackagePath(themeName)
promises.push(@packageManager.activatePackage(themeName))
else
console.warn("Failed to activate theme '#{themeName}' because it isn't installed.")
Q.all(promises).then =>
@addActiveThemeClasses()
@refreshLessCache() # Update cache again now that @getActiveThemes() is populated
@loadUserStylesheet()
@reloadBaseStylesheets()
@initialLoadComplete = true
@emit 'reloaded' if Grim.includeDeprecatedAPIs
@emitter.emit 'did-change-active-themes'
deferred.resolve()
deferred.promise
Promise.all(promises).then =>
@addActiveThemeClasses()
@refreshLessCache() # Update cache again now that @getActiveThemes() is populated
@loadUserStylesheet()
@reloadBaseStylesheets()
@initialLoadComplete = true
@emitter.emit 'did-change-active-themes'
resolve()
deactivateThemes: ->
@removeActiveThemeClasses()
@@ -296,10 +268,10 @@ class ThemeManager
isInitialLoadComplete: -> @initialLoadComplete
addActiveThemeClasses: ->
workspaceElement = atom.views.getView(atom.workspace)
for pack in @getActiveThemes()
workspaceElement.classList.add("theme-#{pack.name}")
return
if workspaceElement = atom.views.getView(atom.workspace)
for pack in @getActiveThemes()
workspaceElement.classList.add("theme-#{pack.name}")
return
removeActiveThemeClasses: ->
workspaceElement = atom.views.getView(atom.workspace)
@@ -325,59 +297,3 @@ class ThemeManager
themePaths.push(path.join(themePath, 'styles'))
themePaths.filter (themePath) -> fs.isDirectorySync(themePath)
if Grim.includeDeprecatedAPIs
EmitterMixin = require('emissary').Emitter
EmitterMixin.includeInto(ThemeManager)
ThemeManager::on = (eventName) ->
switch eventName
when 'reloaded'
Grim.deprecate 'Use ThemeManager::onDidChangeActiveThemes instead'
when 'stylesheet-added'
Grim.deprecate 'Use ThemeManager::onDidAddStylesheet instead'
when 'stylesheet-removed'
Grim.deprecate 'Use ThemeManager::onDidRemoveStylesheet instead'
when 'stylesheet-updated'
Grim.deprecate 'Use ThemeManager::onDidUpdateStylesheet instead'
when 'stylesheets-changed'
Grim.deprecate 'Use ThemeManager::onDidChangeStylesheets instead'
else
Grim.deprecate 'ThemeManager::on is deprecated. Use event subscription methods instead.'
EmitterMixin::on.apply(this, arguments)
ThemeManager::onDidReloadAll = (callback) ->
Grim.deprecate("Use `::onDidChangeActiveThemes` instead.")
@onDidChangeActiveThemes(callback)
ThemeManager::onDidAddStylesheet = (callback) ->
Grim.deprecate("Use atom.styles.onDidAddStyleElement instead")
@emitter.on 'did-add-stylesheet', callback
ThemeManager::onDidRemoveStylesheet = (callback) ->
Grim.deprecate("Use atom.styles.onDidRemoveStyleElement instead")
@emitter.on 'did-remove-stylesheet', callback
ThemeManager::onDidUpdateStylesheet = (callback) ->
Grim.deprecate("Use atom.styles.onDidUpdateStyleElement instead")
@emitter.on 'did-update-stylesheet', callback
ThemeManager::onDidChangeStylesheets = (callback) ->
Grim.deprecate("Use atom.styles.onDidAdd/RemoveStyleElement instead")
@emitter.on 'did-change-stylesheets', callback
ThemeManager::getUserStylesheetPath = ->
Grim.deprecate("Call atom.styles.getUserStyleSheetPath() instead")
atom.styles.getUserStyleSheetPath()
ThemeManager::getLoadedNames = ->
Grim.deprecate("Use `::getLoadedThemeNames` instead.")
@getLoadedThemeNames()
ThemeManager::getActiveNames = ->
Grim.deprecate("Use `::getActiveThemeNames` instead.")
@getActiveThemeNames()
ThemeManager::setEnabledThemes = (enabledThemeNames) ->
Grim.deprecate("Use `atom.config.set('core.themes', arrayOfThemeNames)` instead")
atom.config.set('core.themes', enabledThemeNames)

View File

@@ -1,4 +1,3 @@
Q = require 'q'
Package = require './package'
module.exports =
@@ -18,14 +17,12 @@ class ThemePackage extends Package
this
activate: ->
return @activationDeferred.promise if @activationDeferred?
@activationDeferred = Q.defer()
@measure 'activateTime', =>
try
@loadStylesheets()
@activateNow()
catch error
@handleError("Failed to activate the #{@name} theme", error)
@activationDeferred.promise
@activationPromise ?= new Promise (resolve, reject) =>
@resolveActivationPromise = resolve
@rejectActivationPromise = reject
@measure 'activateTime', =>
try
@loadStylesheets()
@activateNow()
catch error
@handleError("Failed to activate the #{@name} theme", error)

View File

@@ -1,3 +1,5 @@
{values} = require 'underscore-plus'
cloneObject = (object) ->
clone = {}
clone[key] = value for key, value of object
@@ -21,9 +23,7 @@ class TiledComponent
return
removeTileNode: (tileRow) ->
node = @componentsByTileId[tileRow].getDomNode()
node.remove()
@componentsByTileId[tileRow].destroy()
delete @componentsByTileId[tileRow]
delete @oldState.tiles[tileRow]
@@ -49,3 +49,6 @@ class TiledComponent
getComponentForTile: (tileRow) ->
@componentsByTileId[tileRow]
getTiles: ->
values(@componentsByTileId).map (component) -> component.getDomNode()

View File

@@ -31,11 +31,14 @@ class TokenIterator
while @index < tags.length
tag = tags[@index]
if tag < 0
scope = atom.grammars.scopeForId(tag)
if tag % 2 is 0
@scopeEnds.push(atom.grammars.scopeForId(tag + 1))
if @scopeStarts[@scopeStarts.length - 1] is scope
@scopeStarts.pop()
else
@scopeEnds.push(scope)
@scopes.pop()
else
scope = atom.grammars.scopeForId(tag)
@scopeStarts.push(scope)
@scopes.push(scope)
@index++

View File

@@ -2,18 +2,14 @@ _ = require 'underscore-plus'
{CompositeDisposable, Emitter} = require 'event-kit'
{Point, Range} = require 'text-buffer'
{ScopeSelector} = require 'first-mate'
Serializable = require 'serializable'
Model = require './model'
TokenizedLine = require './tokenized-line'
TokenIterator = require './token-iterator'
Token = require './token'
ScopeDescriptor = require './scope-descriptor'
Grim = require 'grim'
module.exports =
class TokenizedBuffer extends Model
Serializable.includeInto(this)
grammar: null
currentGrammarScore: null
buffer: null
@@ -25,6 +21,10 @@ class TokenizedBuffer extends Model
configSettings: null
changeCount: 0
@deserialize: (state) ->
state.buffer = atom.project.bufferForPathSync(state.bufferPath)
new this(state)
constructor: ({@buffer, @tabLength, @ignoreInvisibles, @largeFileMode}) ->
@emitter = new Emitter
@disposables = new CompositeDisposable
@@ -41,16 +41,13 @@ class TokenizedBuffer extends Model
destroyed: ->
@disposables.dispose()
serializeParams: ->
serialize: ->
deserializer: 'TokenizedBuffer'
bufferPath: @buffer.getPath()
tabLength: @tabLength
ignoreInvisibles: @ignoreInvisibles
largeFileMode: @largeFileMode
deserializeParams: (params) ->
params.buffer = atom.project.bufferForPathSync(params.bufferPath)
params
observeGrammar: (callback) ->
callback(@grammar)
@onDidChangeGrammar(callback)
@@ -128,7 +125,6 @@ class TokenizedBuffer extends Model
@invalidateRow(0)
@fullyTokenized = false
event = {start: 0, end: lastRow, delta: 0}
@emit 'changed', event if Grim.includeDeprecatedAPIs
@emitter.emit 'did-change', event
setVisible: (@visible) ->
@@ -191,7 +187,6 @@ class TokenizedBuffer extends Model
[startRow, endRow] = @updateFoldableStatus(startRow, endRow)
event = {start: startRow, end: endRow, delta: 0}
@emit 'changed', event if Grim.includeDeprecatedAPIs
@emitter.emit 'did-change', event
if @firstInvalidRow()?
@@ -201,7 +196,6 @@ class TokenizedBuffer extends Model
markTokenizationComplete: ->
unless @fullyTokenized
@emit 'tokenized' if Grim.includeDeprecatedAPIs
@emitter.emit 'did-tokenize'
@fullyTokenized = true
@@ -255,7 +249,6 @@ class TokenizedBuffer extends Model
end -= delta
event = {start, end, delta, bufferChange: e}
@emit 'changed', event if Grim.includeDeprecatedAPIs
@emitter.emit 'did-change', event
retokenizeWhitespaceRowsIfIndentLevelChanged: (row, increment) ->
@@ -539,22 +532,6 @@ class TokenizedBuffer extends Model
console.log row, line, line.length
return
if Grim.includeDeprecatedAPIs
EmitterMixin = require('emissary').Emitter
TokenizedBuffer::on = (eventName) ->
switch eventName
when 'changed'
Grim.deprecate("Use TokenizedBuffer::onDidChange instead")
when 'grammar-changed'
Grim.deprecate("Use TokenizedBuffer::onDidChangeGrammar instead")
when 'tokenized'
Grim.deprecate("Use TokenizedBuffer::onDidTokenize instead")
else
Grim.deprecate("TokenizedBuffer::on is deprecated. Use event subscription methods instead.")
EmitterMixin::on.apply(this, arguments)
selectorMatchesAnyScope = (selector, scopes) ->
targetClasses = selector.replace(/^\./, '').split('.')
_.any scopes, (scope) ->

View File

@@ -1,6 +1,6 @@
_ = require 'underscore-plus'
{Disposable} = require 'event-kit'
{$} = require './space-pen-extensions'
{Disposable, CompositeDisposable} = require 'event-kit'
Tooltip = null
# Essential: Associates tooltips with HTML elements or selectors.
#
@@ -71,7 +71,12 @@ class TooltipManager
# Returns a {Disposable} on which `.dispose()` can be called to remove the
# tooltip.
add: (target, options) ->
requireBootstrapTooltip()
if target.jquery
disposable = new CompositeDisposable
disposable.add @add(element, options) for element in target
return disposable
Tooltip ?= require './tooltip'
{keyBindingCommand, keyBindingTarget} = options
@@ -83,15 +88,20 @@ class TooltipManager
else if keystroke?
options.title = getKeystroke(bindings)
$target = $(target)
$target.tooltip(_.defaults(options, @defaults))
tooltip = new Tooltip(target, _.defaults(options, @defaults))
new Disposable ->
tooltip = $target.data('bs.tooltip')
if tooltip?
tooltip.leave(currentTarget: target)
tooltip.hide()
$target.tooltip('destroy')
hideTooltip = ->
tooltip.leave(currentTarget: target)
tooltip.hide()
window.addEventListener('resize', hideTooltip)
disposable = new Disposable ->
window.removeEventListener('resize', hideTooltip)
hideTooltip()
tooltip.destroy()
disposable
humanizeKeystrokes = (keystroke) ->
keystrokes = keystroke.split(' ')
@@ -101,7 +111,3 @@ humanizeKeystrokes = (keystroke) ->
getKeystroke = (bindings) ->
if bindings?.length
"<span class=\"keystroke\">#{humanizeKeystrokes(bindings[0].keystrokes)}</span>"
else
requireBootstrapTooltip = _.once ->
atom.requireWithGlobals('bootstrap/js/tooltip', {jQuery: $})

456
src/tooltip.js Normal file
View File

@@ -0,0 +1,456 @@
'use strict'
const EventKit = require('event-kit')
const tooltipComponentsByElement = new WeakMap()
const listen = require('./delegated-listener')
// This tooltip class is derived from Bootstrap 3, but modified to not require
// jQuery, which is an expensive dependency we want to eliminate.
var Tooltip = function (element, options) {
this.options = null
this.enabled = null
this.timeout = null
this.hoverState = null
this.element = null
this.inState = null
this.init(element, options)
}
Tooltip.VERSION = '3.3.5'
Tooltip.TRANSITION_DURATION = 150
Tooltip.DEFAULTS = {
animation: true,
placement: 'top',
selector: false,
template: '<div class="tooltip" role="tooltip"><div class="tooltip-arrow"></div><div class="tooltip-inner"></div></div>',
trigger: 'hover focus',
title: '',
delay: 0,
html: false,
container: false,
viewport: {
selector: 'body',
padding: 0
}
}
Tooltip.prototype.init = function (element, options) {
this.enabled = true
this.element = element
this.options = this.getOptions(options)
this.disposables = new EventKit.CompositeDisposable()
if (this.options.viewport) {
if (typeof this.options.viewport === 'function') {
this.viewport = this.options.viewport.call(this, this.element)
} else {
this.viewport = document.querySelector(this.options.viewport.selector || this.options.viewport)
}
}
this.inState = {click: false, hover: false, focus: false}
if (this.element instanceof document.constructor && !this.options.selector) {
throw new Error('`selector` option must be specified when initializing tooltip on the window.document object!')
}
var triggers = this.options.trigger.split(' ')
for (var i = triggers.length; i--;) {
var trigger = triggers[i]
if (trigger === 'click') {
this.disposables.add(listen(this.element, 'click', this.options.selector, this.toggle.bind(this)))
} else if (trigger !== 'manual') {
var eventIn, eventOut
if (trigger === 'hover') {
if (this.options.selector) {
eventIn = 'mouseover'
eventOut = 'mouseout'
} else {
eventIn = 'mouseenter'
eventOut = 'mouseleave'
}
} else {
eventIn = 'focusin'
eventOut = 'focusout'
}
this.disposables.add(listen(this.element, eventIn, this.options.selector, this.enter.bind(this)))
this.disposables.add(listen(this.element, eventOut, this.options.selector, this.leave.bind(this)))
}
}
this.options.selector ?
(this._options = extend({}, this.options, { trigger: 'manual', selector: '' })) :
this.fixTitle()
}
Tooltip.prototype.getDefaults = function () {
return Tooltip.DEFAULTS
}
Tooltip.prototype.getOptions = function (options) {
options = extend({}, this.getDefaults(), options)
if (options.delay && typeof options.delay === 'number') {
options.delay = {
show: options.delay,
hide: options.delay
}
}
return options
}
Tooltip.prototype.getDelegateOptions = function () {
var options = {}
var defaults = this.getDefaults()
if (this._options) {
for (var key of Object.getOwnPropertyNames(this._options)) {
var value = this._options[key]
if (defaults[key] !== value) options[key] = value
}
}
return options
}
Tooltip.prototype.enter = function (event) {
if (event) {
if (event.currentTarget !== this.element) {
this.getDelegateComponent(event.currentTarget).enter(event)
return
}
this.inState[event.type === 'focusin' ? 'focus' : 'hover'] = true
}
if (this.getTooltipElement().classList.contains('in') || this.hoverState === 'in') {
this.hoverState = 'in'
return
}
clearTimeout(this.timeout)
this.hoverState = 'in'
if (!this.options.delay || !this.options.delay.show) return this.show()
this.timeout = setTimeout(function () {
if (this.hoverState === 'in') this.show()
}.bind(this), this.options.delay.show)
}
Tooltip.prototype.isInStateTrue = function () {
for (var key in this.inState) {
if (this.inState[key]) return true
}
return false
}
Tooltip.prototype.leave = function (event) {
if (event) {
if (event.currentTarget !== this.element) {
this.getDelegateComponent(event.currentTarget).leave(event)
return
}
this.inState[event.type === 'focusout' ? 'focus' : 'hover'] = false
}
if (this.isInStateTrue()) return
clearTimeout(this.timeout)
this.hoverState = 'out'
if (!this.options.delay || !this.options.delay.hide) return this.hide()
this.timeout = setTimeout(function () {
if (this.hoverState === 'out') this.hide()
}.bind(this), this.options.delay.hide)
}
Tooltip.prototype.show = function () {
if (this.hasContent() && this.enabled) {
var tip = this.getTooltipElement()
var tipId = this.getUID('tooltip')
this.setContent()
tip.setAttribute('id', tipId)
this.element.setAttribute('aria-describedby', tipId)
if (this.options.animation) tip.classList.add('fade')
var placement = typeof this.options.placement === 'function' ?
this.options.placement.call(this, tip, this.element) :
this.options.placement
var autoToken = /\s?auto?\s?/i
var autoPlace = autoToken.test(placement)
if (autoPlace) placement = placement.replace(autoToken, '') || 'top'
tip.remove()
tip.style.top = '0px'
tip.style.left = '0px'
tip.style.display = 'block'
tip.classList.add(placement)
document.body.appendChild(tip)
var pos = this.element.getBoundingClientRect()
var actualWidth = tip.offsetWidth
var actualHeight = tip.offsetHeight
if (autoPlace) {
var orgPlacement = placement
var viewportDim = this.viewport.getBoundingClientRect()
placement = placement === 'bottom' && pos.bottom + actualHeight > viewportDim.bottom ? 'top' :
placement === 'top' && pos.top - actualHeight < viewportDim.top ? 'bottom' :
placement === 'right' && pos.right + actualWidth > viewportDim.width ? 'left' :
placement === 'left' && pos.left - actualWidth < viewportDim.left ? 'right' :
placement
tip.classList.remove(orgPlacement)
tip.classList.add(placement)
}
var calculatedOffset = this.getCalculatedOffset(placement, pos, actualWidth, actualHeight)
this.applyPlacement(calculatedOffset, placement)
var prevHoverState = this.hoverState
this.hoverState = null
if (prevHoverState === 'out') this.leave()
}
}
Tooltip.prototype.applyPlacement = function (offset, placement) {
var tip = this.getTooltipElement()
var width = tip.offsetWidth
var height = tip.offsetHeight
// manually read margins because getBoundingClientRect includes difference
var computedStyle = window.getComputedStyle(tip)
var marginTop = parseInt(computedStyle.marginTop, 10)
var marginLeft = parseInt(computedStyle.marginLeft, 10)
offset.top += marginTop
offset.left += marginLeft
tip.style.top = offset.top + 'px'
tip.style.left = offset.left + 'px'
tip.classList.add('in')
// check to see if placing tip in new offset caused the tip to resize itself
var actualWidth = tip.offsetWidth
var actualHeight = tip.offsetHeight
if (placement === 'top' && actualHeight !== height) {
offset.top = offset.top + height - actualHeight
}
var delta = this.getViewportAdjustedDelta(placement, offset, actualWidth, actualHeight)
if (delta.left) offset.left += delta.left
else offset.top += delta.top
var isVertical = /top|bottom/.test(placement)
var arrowDelta = isVertical ? delta.left * 2 - width + actualWidth : delta.top * 2 - height + actualHeight
var arrowOffsetPosition = isVertical ? 'offsetWidth' : 'offsetHeight'
tip.style.top = offset.top + 'px'
tip.style.left = offset.left + 'px'
this.replaceArrow(arrowDelta, tip[arrowOffsetPosition], isVertical)
}
Tooltip.prototype.replaceArrow = function (delta, dimension, isVertical) {
var arrow = this.getArrowElement()
var amount = 50 * (1 - delta / dimension) + '%'
if (isVertical) {
arrow.style.left = amount
arrow.style.top = ''
} else {
arrow.style.top = amount
arrow.style.left = ''
}
}
Tooltip.prototype.setContent = function () {
var tip = this.getTooltipElement()
var title = this.getTitle()
var inner = tip.querySelector('.tooltip-inner')
if (this.options.html) {
inner.innerHTML = title
} else {
inner.textContent = title
}
tip.classList.remove('fade', 'in', 'top', 'bottom', 'left', 'right')
}
Tooltip.prototype.hide = function (callback) {
this.tip && this.tip.classList.remove('in')
if (this.hoverState !== 'in') this.tip && this.tip.remove()
this.element.removeAttribute('aria-describedby')
callback && callback()
this.hoverState = null
return this
}
Tooltip.prototype.fixTitle = function () {
if (this.element.getAttribute('title') || typeof this.element.getAttribute('data-original-title') !== 'string') {
this.element.setAttribute('data-original-title', this.element.getAttribute('title') || '')
this.element.setAttribute('title', '')
}
}
Tooltip.prototype.hasContent = function () {
return this.getTitle()
}
Tooltip.prototype.getCalculatedOffset = function (placement, pos, actualWidth, actualHeight) {
return placement === 'bottom' ? { top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2 } :
placement === 'top' ? { top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2 } :
placement === 'left' ? { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth } :
/* placement === 'right' */ { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width }
}
Tooltip.prototype.getViewportAdjustedDelta = function (placement, pos, actualWidth, actualHeight) {
var delta = { top: 0, left: 0 }
if (!this.viewport) return delta
var viewportPadding = this.options.viewport && this.options.viewport.padding || 0
var viewportDimensions = this.viewport.getBoundingClientRect()
if (/right|left/.test(placement)) {
var topEdgeOffset = pos.top - viewportPadding - viewportDimensions.scroll
var bottomEdgeOffset = pos.top + viewportPadding - viewportDimensions.scroll + actualHeight
if (topEdgeOffset < viewportDimensions.top) { // top overflow
delta.top = viewportDimensions.top - topEdgeOffset
} else if (bottomEdgeOffset > viewportDimensions.top + viewportDimensions.height) { // bottom overflow
delta.top = viewportDimensions.top + viewportDimensions.height - bottomEdgeOffset
}
} else {
var leftEdgeOffset = pos.left - viewportPadding
var rightEdgeOffset = pos.left + viewportPadding + actualWidth
if (leftEdgeOffset < viewportDimensions.left) { // left overflow
delta.left = viewportDimensions.left - leftEdgeOffset
} else if (rightEdgeOffset > viewportDimensions.right) { // right overflow
delta.left = viewportDimensions.left + viewportDimensions.width - rightEdgeOffset
}
}
return delta
}
Tooltip.prototype.getTitle = function () {
var title = this.element.getAttribute('data-original-title')
if (title) {
return title
} else {
return (typeof this.options.title === 'function')
? this.options.title.call(this.element)
: this.options.title
}
}
Tooltip.prototype.getUID = function (prefix) {
do prefix += ~~(Math.random() * 1000000)
while (document.getElementById(prefix))
return prefix
}
Tooltip.prototype.getTooltipElement = function () {
if (!this.tip) {
let div = document.createElement('div')
div.innerHTML = this.options.template
if (div.children.length !== 1) {
throw new Error('Tooltip `template` option must consist of exactly 1 top-level element!')
}
this.tip = div.firstChild
}
return this.tip
}
Tooltip.prototype.getArrowElement = function () {
this.arrow = this.arrow || this.getTooltipElement().querySelector('.tooltip-arrow')
return this.arrow
}
Tooltip.prototype.enable = function () {
this.enabled = true
}
Tooltip.prototype.disable = function () {
this.enabled = false
}
Tooltip.prototype.toggleEnabled = function () {
this.enabled = !this.enabled
}
Tooltip.prototype.toggle = function (event) {
if (event) {
if (event.currentTarget !== this.element) {
this.getDelegateComponent(event.currentTarget).toggle(event)
return
}
this.inState.click = !this.inState.click
if (this.isInStateTrue()) this.enter()
else this.leave()
} else {
this.getTooltipElement().classList.contains('in') ? this.leave() : this.enter()
}
}
Tooltip.prototype.destroy = function () {
clearTimeout(this.timeout)
this.tip && this.tip.remove()
this.disposables.dispose()
}
Tooltip.prototype.getDelegateComponent = function (element) {
var component = tooltipComponentsByElement.get(element)
if (!component) {
component = new Tooltip(element, this.getDelegateOptions())
tooltipComponentsByElement.set(element, component)
}
return component
}
function extend () {
var args = Array.prototype.slice.apply(arguments)
var target = args.shift()
var source = args.shift()
while (source) {
for (var key of Object.getOwnPropertyNames(source)) {
target[key] = source[key]
}
source = args.shift()
}
return target
}
module.exports = Tooltip

View File

@@ -1,132 +1,56 @@
path = require 'path'
{$} = require './space-pen-extensions'
{Disposable} = require 'event-kit'
{Disposable, CompositeDisposable} = require 'event-kit'
ipc = require 'ipc'
shell = require 'shell'
{Subscriber} = require 'emissary'
fs = require 'fs-plus'
listen = require './delegated-listener'
# Handles low-level events related to the window.
module.exports =
class WindowEventHandler
Subscriber.includeInto(this)
constructor: ->
@reloadRequested = false
@subscriptions = new CompositeDisposable
@subscribe ipc, 'message', (message, detail) ->
switch message
when 'open-locations'
needsProjectPaths = atom.project?.getPaths().length is 0
@on(ipc, 'message', @handleIPCMessage)
@on(ipc, 'command', @handleIPCCommand)
@on(ipc, 'context-command', @handleIPCContextCommand)
for {pathToOpen, initialLine, initialColumn} in detail
if pathToOpen? and needsProjectPaths
if fs.existsSync(pathToOpen)
atom.project.addPath(pathToOpen)
else if fs.existsSync(path.dirname(pathToOpen))
atom.project.addPath(path.dirname(pathToOpen))
else
atom.project.addPath(pathToOpen)
@addEventListener(window, 'focus', @handleWindowFocus)
@addEventListener(window, 'blur', @handleWindowBlur)
@addEventListener(window, 'beforeunload', @handleWindowBeforeunload)
@addEventListener(window, 'unload', @handleWindowUnload)
unless fs.isDirectorySync(pathToOpen)
atom.workspace?.open(pathToOpen, {initialLine, initialColumn})
@addEventListener(document, 'keydown', @handleDocumentKeydown)
@addEventListener(document, 'drop', @handleDocumentDrop)
@addEventListener(document, 'dragover', @handleDocumentDragover)
@addEventListener(document, 'contextmenu', @handleDocumentContextmenu)
@subscriptions.add listen(document, 'click', 'a', @handleLinkClick)
@subscriptions.add listen(document, 'submit', 'form', @handleFormSubmit)
return
when 'update-available'
atom.updateAvailable(detail)
# FIXME: Remove this when deprecations are removed
{releaseVersion} = detail
detail = [releaseVersion]
if workspaceElement = atom.views.getView(atom.workspace)
atom.commands.dispatch workspaceElement, "window:update-available", detail
@subscribe ipc, 'command', (command, args...) ->
activeElement = document.activeElement
# Use the workspace element view if body has focus
if activeElement is document.body and workspaceElement = atom.views.getView(atom.workspace)
activeElement = workspaceElement
atom.commands.dispatch(activeElement, command, args[0])
@subscribe ipc, 'context-command', (command, args...) ->
$(atom.contextMenu.activeElement).trigger(command, args...)
@subscribe $(window), 'focus', -> document.body.classList.remove('is-blurred')
@subscribe $(window), 'blur', -> document.body.classList.add('is-blurred')
@subscribe $(window), 'beforeunload', =>
confirmed = atom.workspace?.confirmClose(windowCloseRequested: true)
atom.hide() if confirmed and not @reloadRequested and atom.getCurrentWindow().isWebViewFocused()
@reloadRequested = false
atom.storeDefaultWindowDimensions()
atom.storeWindowDimensions()
if confirmed
atom.unloadEditorWindow()
else
ipc.send('cancel-window-close')
confirmed
@subscribe $(window), 'blur', -> atom.storeDefaultWindowDimensions()
@subscribe $(window), 'unload', -> atom.removeEditorWindow()
@subscribeToCommand $(window), 'window:toggle-full-screen', -> atom.toggleFullScreen()
@subscribeToCommand $(window), 'window:close', -> atom.close()
@subscribeToCommand $(window), 'window:reload', =>
@reloadRequested = true
atom.reload()
@subscribeToCommand $(window), 'window:toggle-dev-tools', -> atom.toggleDevTools()
@subscriptions.add atom.commands.add window,
'window:toggle-full-screen': @handleWindowToggleFullScreen
'window:close': @handleWindowClose
'window:reload': @handleWindowReload
'window:toggle-dev-tools': @handleWindowToggleDevTools
if process.platform in ['win32', 'linux']
@subscribeToCommand $(window), 'window:toggle-menu-bar', ->
atom.config.set('core.autoHideMenuBar', not atom.config.get('core.autoHideMenuBar'))
@subscriptions.add atom.commands.add window,
'window:toggle-menu-bar': @handleWindowToggleMenuBar
if atom.config.get('core.autoHideMenuBar')
detail = "To toggle, press the Alt key or execute the window:toggle-menu-bar command"
atom.notifications.addInfo('Menu bar hidden', {detail})
@subscribeToCommand $(document), 'core:focus-next', @focusNext
@subscribeToCommand $(document), 'core:focus-previous', @focusPrevious
document.addEventListener 'keydown', @onKeydown
document.addEventListener 'drop', @onDrop
@subscribe new Disposable =>
document.removeEventListener('drop', @onDrop)
document.addEventListener 'dragover', @onDragOver
@subscribe new Disposable =>
document.removeEventListener('dragover', @onDragOver)
@subscribe $(document), 'click', 'a', @openLink
# Prevent form submits from changing the current window's URL
@subscribe $(document), 'submit', 'form', (e) -> e.preventDefault()
@subscribe $(document), 'contextmenu', (e) ->
e.preventDefault()
atom.contextMenu.showForEvent(e)
@subscriptions.add atom.commands.add document,
'core:focus-next': @handleFocusNext
'core:focus-previous': @handleFocusPrevious
@handleNativeKeybindings()
# Wire commands that should be handled by Chromium for elements with the
# `.native-key-bindings` class.
handleNativeKeybindings: ->
menu = null
bindCommandToAction = (command, action) =>
@subscribe $(document), command, (event) ->
@addEventListener document, command, (event) ->
if event.target.webkitMatchesSelector('.native-key-bindings')
atom.getCurrentWindow().webContents[action]()
true
bindCommandToAction('core:copy', 'copy')
bindCommandToAction('core:paste', 'paste')
@@ -135,38 +59,41 @@ class WindowEventHandler
bindCommandToAction('core:select-all', 'selectAll')
bindCommandToAction('core:cut', 'cut')
onKeydown: (event) ->
unsubscribe: ->
@subscriptions.dispose()
on: (target, eventName, handler) ->
target.on(eventName, handler)
@subscriptions.add(new Disposable ->
target.removeListener(eventName, handler)
)
addEventListener: (target, eventName, handler) ->
target.addEventListener(eventName, handler)
@subscriptions.add(new Disposable(-> target.removeEventListener(eventName, handler)))
handleDocumentKeydown: (event) ->
atom.keymaps.handleKeyboardEvent(event)
event.stopImmediatePropagation()
onDrop: (event) ->
handleDrop: (event) ->
event.preventDefault()
event.stopPropagation()
onDragOver: (event) ->
handleDragover: (event) ->
event.preventDefault()
event.stopPropagation()
event.dataTransfer.dropEffect = 'none'
openLink: ({target, currentTarget}) ->
location = target?.getAttribute('href') or currentTarget?.getAttribute('href')
if location and location[0] isnt '#' and /^https?:\/\//.test(location)
shell.openExternal(location)
false
eachTabIndexedElement: (callback) ->
for element in $('[tabindex]')
element = $(element)
continue if element.isDisabled()
tabIndex = parseInt(element.attr('tabindex'))
continue unless tabIndex >= 0
callback(element, tabIndex)
for element in document.querySelectorAll('[tabindex]')
continue if element.disabled
continue unless element.tabIndex >= 0
callback(element, element.tabIndex)
return
focusNext: =>
focusedTabIndex = parseInt($(':focus').attr('tabindex')) or -Infinity
handleFocusNext: =>
focusedTabIndex = document.activeElement.tabIndex ? -Infinity
nextElement = null
nextTabIndex = Infinity
@@ -186,8 +113,8 @@ class WindowEventHandler
else if lowestElement?
lowestElement.focus()
focusPrevious: =>
focusedTabIndex = parseInt($(':focus').attr('tabindex')) or Infinity
handleFocusPrevious: =>
focusedTabIndex = document.activeElement.tabIndex ? Infinity
previousElement = null
previousTabIndex = -Infinity
@@ -206,3 +133,92 @@ class WindowEventHandler
previousElement.focus()
else if highestElement?
highestElement.focus()
handleIPCMessage: (message, detail) ->
switch message
when 'open-locations'
needsProjectPaths = atom.project?.getPaths().length is 0
for {pathToOpen, initialLine, initialColumn} in detail
if pathToOpen? and needsProjectPaths
if fs.existsSync(pathToOpen)
atom.project.addPath(pathToOpen)
else if fs.existsSync(path.dirname(pathToOpen))
atom.project.addPath(path.dirname(pathToOpen))
else
atom.project.addPath(pathToOpen)
unless fs.isDirectorySync(pathToOpen)
atom.workspace?.open(pathToOpen, {initialLine, initialColumn})
return
when 'update-available'
atom.updateAvailable(detail)
handleIPCCommand: (command, args...) ->
activeElement = document.activeElement
# Use the workspace element view if body has focus
if activeElement is document.body and workspaceElement = atom.views.getView(atom.workspace)
activeElement = workspaceElement
atom.commands.dispatch(activeElement, command, args[0])
handleIPCContextCommand: (command, args...) ->
atom.commands.dispatch(atom.contextMenu.activeElement, command, args)
handleWindowFocus: ->
document.body.classList.remove('is-blurred')
handleWindowBlur: ->
document.body.classList.add('is-blurred')
atom.storeDefaultWindowDimensions()
handleWindowBeforeunload: =>
confirmed = atom.workspace?.confirmClose(windowCloseRequested: true)
atom.hide() if confirmed and not @reloadRequested and atom.getCurrentWindow().isWebViewFocused()
@reloadRequested = false
atom.storeDefaultWindowDimensions()
atom.storeWindowDimensions()
if confirmed
atom.unloadEditorWindow()
else
ipc.send('cancel-window-close')
confirmed
handleWindowUnload: ->
atom.removeEditorWindow()
handleWindowToggleFullScreen: ->
atom.toggleFullScreen()
handleWindowClose: ->
atom.close()
handleWindowReload: ->
@reloadRequested = true
atom.reload()
handleWindowToggleDevTools: ->
atom.toggleDevTools()
handleWindowToggleMenuBar: ->
atom.config.set('core.autoHideMenuBar', not atom.config.get('core.autoHideMenuBar'))
if atom.config.get('core.autoHideMenuBar')
detail = "To toggle, press the Alt key or execute the window:toggle-menu-bar command"
atom.notifications.addInfo('Menu bar hidden', {detail})
handleLinkClick: (event) ->
event.preventDefault()
location = event.currentTarget?.getAttribute('href')
if location and location[0] isnt '#' and /^https?:\/\//.test(location)
shell.openExternal(location)
handleFormSubmit: (event) ->
# Prevent form submits from changing the current window's URL
event.preventDefault()
handleDocumentContextmenu: (event) ->
event.preventDefault()
atom.contextMenu.showForEvent(event)

View File

@@ -3,8 +3,6 @@ path = require 'path'
{Disposable, CompositeDisposable} = require 'event-kit'
Grim = require 'grim'
scrollbarStyle = require 'scrollbar-style'
{callAttachHooks} = require 'space-pen'
WorkspaceView = null
module.exports =
class WorkspaceElement extends HTMLElement
@@ -15,10 +13,8 @@ class WorkspaceElement extends HTMLElement
@initializeContent()
@observeScrollbarStyle()
@observeTextEditorFontConfig()
@createSpacePenShim() if Grim.includeDeprecatedAPIs
attachedCallback: ->
callAttachHooks(this) if Grim.includeDeprecatedAPIs
@focus()
detachedCallback: ->
@@ -64,10 +60,6 @@ class WorkspaceElement extends HTMLElement
"""
atom.styles.addStyleSheet(styleSheetSource, sourcePath: 'global-text-editor-styles')
createSpacePenShim: ->
WorkspaceView ?= require './workspace-view'
@__spacePenView = new WorkspaceView(this)
initialize: (@model) ->
@paneContainer = atom.views.getView(@model.paneContainer)
@verticalAxis.appendChild(@paneContainer)
@@ -88,7 +80,6 @@ class WorkspaceElement extends HTMLElement
@appendChild(@panelContainers.modal)
@__spacePenView.setModel(@model) if Grim.includeDeprecatedAPIs
this
getModel: -> @model
@@ -117,7 +108,6 @@ atom.commands.add 'atom-workspace',
'window:reset-font-size': -> @getModel().resetFontSize()
'application:about': -> ipc.send('command', 'application:about')
'application:run-all-specs': -> ipc.send('command', 'application:run-all-specs')
'application:run-benchmarks': -> ipc.send('command', 'application:run-benchmarks')
'application:show-preferences': -> ipc.send('command', 'application:show-settings')
'application:show-settings': -> ipc.send('command', 'application:show-settings')
'application:quit': -> ipc.send('command', 'application:quit')

View File

@@ -1,340 +0,0 @@
ipc = require 'ipc'
path = require 'path'
Q = require 'q'
_ = require 'underscore-plus'
Delegator = require 'delegato'
{deprecate, logDeprecationWarnings} = require 'grim'
{$, $$, View} = require './space-pen-extensions'
fs = require 'fs-plus'
Workspace = require './workspace'
PaneView = require './pane-view'
PaneContainerView = require './pane-container-view'
TextEditor = require './text-editor'
# Deprecated: The top-level view for the entire window. An instance of this class is
# available via the `atom.workspaceView` global.
#
# It is backed by a model object, an instance of {Workspace}, which is available
# via the `atom.workspace` global or {::getModel}. You should prefer to interact
# with the model object when possible, but it won't always be possible with the
# current API.
#
# ## Adding Perimeter Panels
#
# Use the following methods if possible to attach panels to the perimeter of the
# workspace rather than manipulating the DOM directly to better insulate you to
# changes in the workspace markup:
#
# * {::prependToTop}
# * {::appendToTop}
# * {::prependToBottom}
# * {::appendToBottom}
# * {::prependToLeft}
# * {::appendToLeft}
# * {::prependToRight}
# * {::appendToRight}
#
# ## Requiring in package specs
#
# If you need a `WorkspaceView` instance to test your package, require it via
# the built-in `atom` module.
#
# ```coffee
# {WorkspaceView} = require 'atom'
# ```
#
# You can assign it to the `atom.workspaceView` global in the spec or just use
# it as a local, depending on what you're trying to accomplish. Building the
# `WorkspaceView` is currently expensive, so you should try build a {Workspace}
# instead if possible.
module.exports =
class WorkspaceView extends View
Delegator.includeInto(this)
@delegatesProperty 'fullScreen', 'destroyedItemURIs', toProperty: 'model'
@delegatesMethods 'open', 'openSync',
'saveActivePaneItem', 'saveActivePaneItemAs', 'saveAll', 'destroyActivePaneItem',
'destroyActivePane', 'increaseFontSize', 'decreaseFontSize', toProperty: 'model'
constructor: (@element) ->
unless @element?
return atom.views.getView(atom.workspace).__spacePenView
super
@deprecateViewEvents()
@attachedEditorViews = new WeakSet
setModel: (@model) ->
@horizontal = @find('atom-workspace-axis.horizontal')
@vertical = @find('atom-workspace-axis.vertical')
@panes = @find('atom-pane-container').view()
@subscribe @model.onDidOpen => @trigger 'uri-opened'
beforeRemove: ->
@model?.destroy()
###
Section: Accessing the Workspace Model
###
# Essential: Get the underlying model object.
#
# Returns a {Workspace}.
getModel: -> @model
###
Section: Accessing Views
###
# Essential: Register a function to be called for every current and future
# editor view in the workspace (only includes {TextEditorView}s that are pane
# items).
#
# * `callback` A {Function} with an {TextEditorView} as its only argument.
# * `editorView` {TextEditorView}
#
# Returns a subscription object with an `.off` method that you can call to
# unregister the callback.
eachEditorView: (callback) ->
for editorView in @getEditorViews()
@attachedEditorViews.add(editorView)
callback(editorView)
attachedCallback = (e, editorView) =>
unless @attachedEditorViews.has(editorView)
@attachedEditorViews.add(editorView)
callback(editorView) unless editorView.mini
@on('editor:attached', attachedCallback)
off: => @off('editor:attached', attachedCallback)
# Essential: Register a function to be called for every current and future
# pane view in the workspace.
#
# * `callback` A {Function} with a {PaneView} as its only argument.
# * `paneView` {PaneView}
#
# Returns a subscription object with an `.off` method that you can call to
# unregister the callback.
eachPaneView: (callback) ->
@panes.eachPaneView(callback)
# Essential: Get all existing pane views.
#
# Prefer {Workspace::getPanes} if you don't need access to the view objects.
# Also consider using {::eachPaneView} if you want to register a callback for
# all current and *future* pane views.
#
# Returns an Array of all open {PaneView}s.
getPaneViews: ->
@panes.getPaneViews()
# Essential: Get the active pane view.
#
# Prefer {Workspace::getActivePane} if you don't actually need access to the
# view.
#
# Returns a {PaneView}.
getActivePaneView: ->
@panes.getActivePaneView()
# Essential: Get the view associated with the active pane item.
#
# Returns a view.
getActiveView: ->
@panes.getActiveView()
###
Section: Adding elements to the workspace
###
prependToTop: (element) ->
deprecate 'Please use Workspace::addTopPanel() instead'
@vertical.prepend(element)
appendToTop: (element) ->
deprecate 'Please use Workspace::addTopPanel() instead'
@panes.before(element)
prependToBottom: (element) ->
deprecate 'Please use Workspace::addBottomPanel() instead'
@panes.after(element)
appendToBottom: (element) ->
deprecate 'Please use Workspace::addBottomPanel() instead'
@vertical.append(element)
prependToLeft: (element) ->
deprecate 'Please use Workspace::addLeftPanel() instead'
@horizontal.prepend(element)
appendToLeft: (element) ->
deprecate 'Please use Workspace::addLeftPanel() instead'
@vertical.before(element)
prependToRight: (element) ->
deprecate 'Please use Workspace::addRightPanel() instead'
@vertical.after(element)
appendToRight: (element) ->
deprecate 'Please use Workspace::addRightPanel() instead'
@horizontal.append(element)
###
Section: Focusing pane views
###
# Focus the previous pane by id.
focusPreviousPaneView: -> @model.activatePreviousPane()
# Focus the next pane by id.
focusNextPaneView: -> @model.activateNextPane()
# Focus the pane directly above the active pane.
focusPaneViewAbove: -> @panes.focusPaneViewAbove()
# Focus the pane directly below the active pane.
focusPaneViewBelow: -> @panes.focusPaneViewBelow()
# Focus the pane directly to the left of the active pane.
focusPaneViewOnLeft: -> @panes.focusPaneViewOnLeft()
# Focus the pane directly to the right of the active pane.
focusPaneViewOnRight: -> @panes.focusPaneViewOnRight()
###
Section: Private
###
# Prompts to save all unsaved items
confirmClose: ->
@model.confirmClose()
# Get all editor views.
#
# You should prefer {Workspace::getEditors} unless you absolutely need access
# to the view objects. Also consider using {::eachEditorView}, which will call
# a callback for all current and *future* editor views.
#
# Returns an {Array} of {TextEditorView}s.
getEditorViews: ->
for editorElement in @panes.element.querySelectorAll('atom-pane > .item-views > atom-text-editor')
$(editorElement).view()
###
Section: Deprecated
###
deprecateViewEvents: ->
originalWorkspaceViewOn = @on
@on = (eventName) =>
switch eventName
when 'beep'
deprecate('Use Atom::onDidBeep instead')
when 'cursor:moved'
deprecate('Use TextEditor::onDidChangeCursorPosition instead')
when 'editor:attached'
deprecate('Use Workspace::onDidAddTextEditor instead')
when 'editor:detached'
deprecate('Use TextEditor::onDidDestroy instead')
when 'editor:will-be-removed'
deprecate('Use TextEditor::onDidDestroy instead')
when 'pane:active-item-changed'
deprecate('Use Pane::onDidChangeActiveItem instead')
when 'pane:active-item-modified-status-changed'
deprecate('Use Pane::onDidChangeActiveItem and call onDidChangeModified on the active item instead')
when 'pane:active-item-title-changed'
deprecate('Use Pane::onDidChangeActiveItem and call onDidChangeTitle on the active item instead')
when 'pane:attached'
deprecate('Use Workspace::onDidAddPane instead')
when 'pane:became-active'
deprecate('Use Pane::onDidActivate instead')
when 'pane:became-inactive'
deprecate('Use Pane::onDidChangeActive instead')
when 'pane:item-added'
deprecate('Use Pane::onDidAddItem instead')
when 'pane:item-moved'
deprecate('Use Pane::onDidMoveItem instead')
when 'pane:item-removed'
deprecate('Use Pane::onDidRemoveItem instead')
when 'pane:removed'
deprecate('Use Pane::onDidDestroy instead')
when 'pane-container:active-pane-item-changed'
deprecate('Use Workspace::onDidChangeActivePaneItem instead')
when 'selection:changed'
deprecate('Use TextEditor::onDidChangeSelectionRange instead')
when 'uri-opened'
deprecate('Use Workspace::onDidOpen instead')
originalWorkspaceViewOn.apply(this, arguments)
TextEditorView = require './text-editor-view'
originalEditorViewOn = TextEditorView::on
TextEditorView::on = (eventName) ->
switch eventName
when 'cursor:moved'
deprecate('Use TextEditor::onDidChangeCursorPosition instead')
when 'editor:attached'
deprecate('Use TextEditor::onDidAddTextEditor instead')
when 'editor:detached'
deprecate('Use TextEditor::onDidDestroy instead')
when 'editor:will-be-removed'
deprecate('Use TextEditor::onDidDestroy instead')
when 'selection:changed'
deprecate('Use TextEditor::onDidChangeSelectionRange instead')
originalEditorViewOn.apply(this, arguments)
originalPaneViewOn = PaneView::on
PaneView::on = (eventName) ->
switch eventName
when 'cursor:moved'
deprecate('Use TextEditor::onDidChangeCursorPosition instead')
when 'editor:attached'
deprecate('Use TextEditor::onDidAddTextEditor instead')
when 'editor:detached'
deprecate('Use TextEditor::onDidDestroy instead')
when 'editor:will-be-removed'
deprecate('Use TextEditor::onDidDestroy instead')
when 'pane:active-item-changed'
deprecate('Use Pane::onDidChangeActiveItem instead')
when 'pane:active-item-modified-status-changed'
deprecate('Use Pane::onDidChangeActiveItem and call onDidChangeModified on the active item instead')
when 'pane:active-item-title-changed'
deprecate('Use Pane::onDidChangeActiveItem and call onDidChangeTitle on the active item instead')
when 'pane:attached'
deprecate('Use Workspace::onDidAddPane instead')
when 'pane:became-active'
deprecate('Use Pane::onDidActivate instead')
when 'pane:became-inactive'
deprecate('Use Pane::onDidChangeActive instead')
when 'pane:item-added'
deprecate('Use Pane::onDidAddItem instead')
when 'pane:item-moved'
deprecate('Use Pane::onDidMoveItem instead')
when 'pane:item-removed'
deprecate('Use Pane::onDidRemoveItem instead')
when 'pane:removed'
deprecate('Use Pane::onDidDestroy instead')
when 'selection:changed'
deprecate('Use TextEditor::onDidChangeSelectionRange instead')
originalPaneViewOn.apply(this, arguments)
# Deprecated
eachPane: (callback) ->
deprecate("Use WorkspaceView::eachPaneView instead")
@eachPaneView(callback)
# Deprecated
getPanes: ->
deprecate("Use WorkspaceView::getPaneViews instead")
@getPaneViews()
# Deprecated
getActivePane: ->
deprecate("Use WorkspaceView::getActivePaneView instead")
@getActivePaneView()
# Deprecated: Call {Workspace::getActivePaneItem} instead.
getActivePaneItem: ->
deprecate("Use Workspace::getActivePaneItem instead")
@model.getActivePaneItem()

View File

@@ -1,11 +1,7 @@
{includeDeprecatedAPIs, deprecate} = require 'grim'
_ = require 'underscore-plus'
path = require 'path'
{join} = path
Q = require 'q'
Serializable = require 'serializable'
{Emitter, Disposable, CompositeDisposable} = require 'event-kit'
Grim = require 'grim'
fs = require 'fs-plus'
DefaultDirectorySearcher = require './default-directory-searcher'
Model = require './model'
@@ -31,15 +27,22 @@ Task = require './task'
module.exports =
class Workspace extends Model
atom.deserializers.add(this)
Serializable.includeInto(this)
@deserialize: (state) ->
return unless state?
for packageName in state.packagesWithActiveGrammars ? []
atom.packages.getLoadedPackage(packageName)?.loadGrammarsSync()
state.paneContainer = PaneContainer.deserialize(state.paneContainer)
new this(state)
constructor: (params) ->
super
unless Grim.includeDeprecatedAPIs
@paneContainer = params?.paneContainer
@fullScreen = params?.fullScreen ? false
@destroyedItemURIs = params?.destroyedItemURIs ? []
@paneContainer = params?.paneContainer
@fullScreen = params?.fullScreen ? false
@destroyedItemURIs = params?.destroyedItemURIs ? []
@emitter = new Emitter
@openers = []
@@ -85,16 +88,9 @@ class Workspace extends Model
@subscribeToFontSize()
# Called by the Serializable mixin during deserialization
deserializeParams: (params) ->
for packageName in params.packagesWithActiveGrammars ? []
atom.packages.getLoadedPackage(packageName)?.loadGrammarsSync()
params.paneContainer = PaneContainer.deserialize(params.paneContainer)
params
# Called by the Serializable mixin during serialization.
serializeParams: ->
serialize: ->
deserializer: 'Workspace'
paneContainer: @paneContainer.serialize()
fullScreen: atom.isFullScreen()
packagesWithActiveGrammars: @getPackageNamesWithActiveGrammars()
@@ -121,10 +117,11 @@ class Workspace extends Model
_.uniq(packageNames)
editorAdded: (editor) ->
@emit 'editor-created', editor if includeDeprecatedAPIs
installShellCommands: ->
require('./command-installer').installShellCommandsInteractively()
CommandInstaller = require('./command-installer')
commandInstaller = new CommandInstaller(atom.getVersion())
commandInstaller.installShellCommandsInteractively()
subscribeToActiveItem: ->
@updateWindowTitle()
@@ -410,12 +407,6 @@ class Workspace extends Model
# * `activatePane` A {Boolean} indicating whether to call {Pane::activate} on
# the containing pane. Defaults to `true`.
openSync: (uri='', options={}) ->
# TODO: Remove deprecated changeFocus option
if includeDeprecatedAPIs and options.changeFocus?
deprecate("The `changeFocus` option has been renamed to `activatePane`")
options.activatePane = options.changeFocus
delete options.changeFocus
{initialLine, initialColumn} = options
activatePane = options.activatePane ? true
@@ -431,12 +422,6 @@ class Workspace extends Model
item
openURIInPane: (uri, pane, options={}) ->
# TODO: Remove deprecated changeFocus option
if includeDeprecatedAPIs and options.changeFocus?
deprecate("The `changeFocus` option has been renamed to `activatePane`")
options.activatePane = options.changeFocus
delete options.changeFocus
activatePane = options.activatePane ? true
if uri?
@@ -448,17 +433,17 @@ class Workspace extends Model
catch error
switch error.code
when 'CANCELLED'
return Q()
return Promise.resolve()
when 'EACCES'
atom.notifications.addWarning("Permission denied '#{error.path}'")
return Q()
return Promise.resolve()
when 'EPERM', 'EBUSY', 'ENXIO', 'EIO', 'ENOTCONN', 'UNKNOWN', 'ECONNRESET', 'EINVAL'
atom.notifications.addWarning("Unable to open '#{error.path ? uri}'", detail: error.message)
return Q()
return Promise.resolve()
else
throw error
Q(item)
Promise.resolve(item)
.then (item) =>
if not pane
pane = new Pane(items: [item])
@@ -476,7 +461,6 @@ class Workspace extends Model
item.setCursorBufferPosition?([initialLine, initialColumn])
index = pane.getActiveItemIndex()
@emit "uri-opened" if includeDeprecatedAPIs
@emitter.emit 'did-open', {uri, pane, item, index}
item
@@ -488,7 +472,7 @@ class Workspace extends Model
if uri = @destroyedItemURIs.pop()
@open(uri)
else
Q()
Promise.resolve()
# Public: Register an opener for a uri.
#
@@ -506,25 +490,18 @@ class Workspace extends Model
#
# Returns a {Disposable} on which `.dispose()` can be called to remove the
# opener.
#
# Note that the opener will be called if and only if the URI is not already open
# in the current pane. The searchAllPanes flag expands the search from the
# current pane to all panes. If you wish to open a view of a different type for
# a file that is already open, consider changing the protocol of the URI. For
# example, perhaps you wish to preview a rendered version of the file `/foo/bar/baz.quux`
# that is already open in a text editor view. You could signal this by calling
# {Workspace::open} on the URI `quux-preview://foo/bar/baz.quux`. Then your opener
# can check the protocol for quux-preview and only handle those URIs that match.
addOpener: (opener) ->
if includeDeprecatedAPIs
packageName = @getCallingPackageName()
wrappedOpener = (uri, options) ->
item = opener(uri, options)
if item? and typeof item.getUri is 'function' and typeof item.getURI isnt 'function'
Grim.deprecate("Pane item with class `#{item.constructor.name}` should implement `::getURI` instead of `::getUri`.", {packageName})
if item? and typeof item.on is 'function' and typeof item.onDidChangeTitle isnt 'function'
Grim.deprecate("If you would like your pane item with class `#{item.constructor.name}` to support title change behavior, please implement a `::onDidChangeTitle()` method. `::on` methods for items are no longer supported. If not, ignore this message.", {packageName})
if item? and typeof item.on is 'function' and typeof item.onDidChangeModified isnt 'function'
Grim.deprecate("If you would like your pane item with class `#{item.constructor.name}` to support modified behavior, please implement a `::onDidChangeModified()` method. If not, ignore this message. `::on` methods for items are no longer supported.", {packageName})
item
@openers.push(wrappedOpener)
new Disposable => _.remove(@openers, wrappedOpener)
else
@openers.push(opener)
new Disposable => _.remove(@openers, opener)
@openers.push(opener)
new Disposable => _.remove(@openers, opener)
getOpeners: ->
@openers
@@ -895,7 +872,12 @@ class Workspace extends Model
resolve('cancelled')
else
resolve(null)
searchPromise.then(onSuccess, reject)
onFailure = ->
promise.cancel() for promise in allSearches
reject()
searchPromise.then(onSuccess, onFailure)
cancellablePromise.cancel = ->
isCancelled = true
# Note that cancelling all of the members of allSearches will cause all of the searches
@@ -920,126 +902,30 @@ class Workspace extends Model
#
# Returns a `Promise`.
replace: (regex, replacementText, filePaths, iterator) ->
deferred = Q.defer()
new Promise (resolve, reject) ->
openPaths = (buffer.getPath() for buffer in atom.project.getBuffers())
outOfProcessPaths = _.difference(filePaths, openPaths)
openPaths = (buffer.getPath() for buffer in atom.project.getBuffers())
outOfProcessPaths = _.difference(filePaths, openPaths)
inProcessFinished = not openPaths.length
outOfProcessFinished = not outOfProcessPaths.length
checkFinished = ->
resolve() if outOfProcessFinished and inProcessFinished
inProcessFinished = not openPaths.length
outOfProcessFinished = not outOfProcessPaths.length
checkFinished = ->
deferred.resolve() if outOfProcessFinished and inProcessFinished
unless outOfProcessFinished.length
flags = 'g'
flags += 'i' if regex.ignoreCase
unless outOfProcessFinished.length
flags = 'g'
flags += 'i' if regex.ignoreCase
task = Task.once require.resolve('./replace-handler'), outOfProcessPaths, regex.source, flags, replacementText, ->
outOfProcessFinished = true
checkFinished()
task = Task.once require.resolve('./replace-handler'), outOfProcessPaths, regex.source, flags, replacementText, ->
outOfProcessFinished = true
checkFinished()
task.on 'replace:path-replaced', iterator
task.on 'replace:file-error', (error) -> iterator(null, error)
task.on 'replace:path-replaced', iterator
task.on 'replace:file-error', (error) -> iterator(null, error)
for buffer in atom.project.getBuffers()
continue unless buffer.getPath() in filePaths
replacements = buffer.replace(regex, replacementText, iterator)
iterator({filePath: buffer.getPath(), replacements}) if replacements
for buffer in atom.project.getBuffers()
continue unless buffer.getPath() in filePaths
replacements = buffer.replace(regex, replacementText, iterator)
iterator({filePath: buffer.getPath(), replacements}) if replacements
inProcessFinished = true
checkFinished()
deferred.promise
if includeDeprecatedAPIs
Workspace.properties
paneContainer: null
fullScreen: false
destroyedItemURIs: -> []
Object.defineProperty Workspace::, 'activePaneItem',
get: ->
Grim.deprecate "Use ::getActivePaneItem() instead of the ::activePaneItem property"
@getActivePaneItem()
Object.defineProperty Workspace::, 'activePane',
get: ->
Grim.deprecate "Use ::getActivePane() instead of the ::activePane property"
@getActivePane()
StackTraceParser = require 'stacktrace-parser'
Workspace::getCallingPackageName = ->
error = new Error
Error.captureStackTrace(error)
stack = StackTraceParser.parse(error.stack)
packagePaths = @getPackagePathsByPackageName()
for i in [0...stack.length]
stackFramePath = stack[i].file
# Empty when it was run from the dev console
return unless stackFramePath
for packageName, packagePath of packagePaths
continue if stackFramePath is 'node.js'
relativePath = path.relative(packagePath, stackFramePath)
return packageName unless /^\.\./.test(relativePath)
return
Workspace::getPackagePathsByPackageName = ->
packagePathsByPackageName = {}
for pack in atom.packages.getLoadedPackages()
packagePath = pack.path
if packagePath.indexOf('.atom/dev/packages') > -1 or packagePath.indexOf('.atom/packages') > -1
packagePath = fs.realpathSync(packagePath)
packagePathsByPackageName[pack.name] = packagePath
packagePathsByPackageName
Workspace::eachEditor = (callback) ->
deprecate("Use Workspace::observeTextEditors instead")
callback(editor) for editor in @getEditors()
@subscribe this, 'editor-created', (editor) -> callback(editor)
Workspace::getEditors = ->
deprecate("Use Workspace::getTextEditors instead")
editors = []
for pane in @paneContainer.getPanes()
editors.push(item) for item in pane.getItems() when item instanceof TextEditor
editors
Workspace::on = (eventName) ->
switch eventName
when 'editor-created'
deprecate("Use Workspace::onDidAddTextEditor or Workspace::observeTextEditors instead.")
when 'uri-opened'
deprecate("Use Workspace::onDidOpen or Workspace::onDidAddPaneItem instead. https://atom.io/docs/api/latest/Workspace#instance-onDidOpen")
else
deprecate("Subscribing via ::on is deprecated. Use documented event subscription methods instead.")
super
Workspace::reopenItemSync = ->
deprecate("Use Workspace::reopenItem instead")
if uri = @destroyedItemURIs.pop()
@openSync(uri)
Workspace::registerOpener = (opener) ->
Grim.deprecate("Call Workspace::addOpener instead")
@addOpener(opener)
Workspace::unregisterOpener = (opener) ->
Grim.deprecate("Call .dispose() on the Disposable returned from ::addOpener instead")
_.remove(@openers, opener)
Workspace::getActiveEditor = ->
Grim.deprecate "Call ::getActiveTextEditor instead"
@getActivePane()?.getActiveEditor()
Workspace::paneForUri = (uri) ->
deprecate("Use ::paneForURI instead.")
@paneForURI(uri)
inProcessFinished = true
checkFinished()