mirror of
https://github.com/atom/atom.git
synced 2026-04-28 03:01:47 -04:00
Merge branch 'master' into portable-mode
Conflicts: src/browser/main.coffee
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'
|
||||
|
||||
@@ -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', ->
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)?
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
41
src/delegated-listener.js
Normal 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)
|
||||
})
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
42
src/dom-element-pool.coffee
Normal file
42
src/dom-element-pool.coffee
Normal 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()
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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})
|
||||
|
||||
@@ -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) ->
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(' ', 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
|
||||
|
||||
@@ -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: {}}
|
||||
|
||||
@@ -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 ' '
|
||||
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 '&'
|
||||
when '"' then '"'
|
||||
when "'" then '''
|
||||
when '<' then '<'
|
||||
when '>' then '>'
|
||||
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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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: ->
|
||||
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
|
||||
@@ -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: ->
|
||||
|
||||
@@ -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()
|
||||
113
src/pane.coffee
113
src/pane.coffee
@@ -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
|
||||
|
||||
@@ -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) ->
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
@@ -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)
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
@@ -1,4 +0,0 @@
|
||||
{Subscriber} = require 'emissary'
|
||||
SubscriberMixin = componentDidUnmount: -> @unsubscribe()
|
||||
Subscriber.extend(SubscriberMixin)
|
||||
module.exports = SubscriberMixin
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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))
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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++
|
||||
|
||||
@@ -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) ->
|
||||
|
||||
@@ -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
456
src/tooltip.js
Normal 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
|
||||
@@ -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)
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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()
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user