mirror of
https://github.com/atom/atom.git
synced 2026-02-12 23:55:10 -05:00
Merge remote-tracking branch 'upstream/master' into move-lines-up-and-down-with-multiple-selections
This commit is contained in:
@@ -11,7 +11,6 @@ _ = require 'underscore-plus'
|
||||
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'
|
||||
@@ -35,28 +34,12 @@ class Atom extends Model
|
||||
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)
|
||||
@@ -123,7 +106,7 @@ class Atom extends Model
|
||||
@getCurrentWindow: ->
|
||||
remote.getCurrentWindow()
|
||||
|
||||
workspaceViewParentSelector: 'body'
|
||||
workspaceParentSelectorctor: 'body'
|
||||
lastUncaughtError: null
|
||||
|
||||
###
|
||||
@@ -484,7 +467,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: ->
|
||||
@@ -680,7 +663,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 +743,18 @@ 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
|
||||
|
||||
@keymaps.defaultTarget = workspaceElement
|
||||
document.querySelector(@workspaceViewParentSelector).appendChild(workspaceElement)
|
||||
document.querySelector(@workspaceParentSelectorctor).appendChild(workspaceElement)
|
||||
|
||||
deserializePackageStates: ->
|
||||
@packages.packageStates = @state.packageStates ? {}
|
||||
@@ -787,7 +763,7 @@ class Atom extends Model
|
||||
deserializeEditorWindow: ->
|
||||
@deserializePackageStates()
|
||||
@deserializeProject()
|
||||
@deserializeWorkspaceView()
|
||||
@deserializeWorkspace()
|
||||
|
||||
loadConfig: ->
|
||||
@config.setSchema null, {type: 'object', properties: _.clone(require('./config-schema'))}
|
||||
@@ -897,6 +873,11 @@ class Atom extends Model
|
||||
ipc.send('call-window-method', 'setAutoHideMenuBar', autoHide)
|
||||
ipc.send('call-window-method', 'setMenuBarVisibility', not autoHide)
|
||||
|
||||
# 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)
|
||||
|
||||
if includeDeprecatedAPIs
|
||||
# Deprecated: Callers should be converted to use atom.deserializers
|
||||
Atom::registerRepresentationClass = ->
|
||||
|
||||
@@ -17,6 +17,8 @@ url = require 'url'
|
||||
{EventEmitter} = require 'events'
|
||||
_ = require 'underscore-plus'
|
||||
|
||||
LocationSuffixRegExp = /(:\d+)(:\d+)?$/
|
||||
|
||||
DefaultSocketPath =
|
||||
if process.platform is 'win32'
|
||||
'\\\\.\\pipe\\atom-sock'
|
||||
@@ -63,15 +65,11 @@ class AtomApplication
|
||||
exit: (status) -> app.exit(status)
|
||||
|
||||
constructor: (options) ->
|
||||
{@resourcePath, @version, @devMode, @safeMode, @socketPath} = options
|
||||
|
||||
# Normalize to make sure drive letter case is consistent on Windows
|
||||
@resourcePath = path.normalize(@resourcePath) if @resourcePath
|
||||
{@resourcePath, @devResourcePath, @version, @devMode, @safeMode, @socketPath} = options
|
||||
|
||||
global.atomApplication = this
|
||||
|
||||
@pidsToOpenWindows = {}
|
||||
@pathsToOpen ?= []
|
||||
@windows = []
|
||||
|
||||
@autoUpdateManager = new AutoUpdateManager(@version, options.test)
|
||||
@@ -88,11 +86,11 @@ class AtomApplication
|
||||
else
|
||||
@loadState() or @openPath(options)
|
||||
|
||||
openWithOptions: ({pathsToOpen, urlsToOpen, test, pidToKillWhenClosed, devMode, safeMode, newWindow, specDirectory, logFile, profileStartup}) ->
|
||||
openWithOptions: ({pathsToOpen, executedFrom, urlsToOpen, test, pidToKillWhenClosed, devMode, safeMode, newWindow, specDirectory, logFile, profileStartup}) ->
|
||||
if test
|
||||
@runSpecs({exitWhenDone: true, @resourcePath, specDirectory, logFile})
|
||||
else if pathsToOpen.length > 0
|
||||
@openPaths({pathsToOpen, pidToKillWhenClosed, newWindow, devMode, safeMode, profileStartup})
|
||||
@openPaths({pathsToOpen, executedFrom, pidToKillWhenClosed, newWindow, devMode, safeMode, profileStartup})
|
||||
else if urlsToOpen.length > 0
|
||||
@openUrl({urlToOpen, devMode, safeMode}) for urlToOpen in urlsToOpen
|
||||
else
|
||||
@@ -163,8 +161,7 @@ class AtomApplication
|
||||
devMode: @focusedWindow()?.devMode
|
||||
safeMode: @focusedWindow()?.safeMode
|
||||
|
||||
@on 'application:run-all-specs', -> @runSpecs(exitWhenDone: false, resourcePath: global.devResourcePath, safeMode: @focusedWindow()?.safeMode)
|
||||
@on 'application:run-benchmarks', -> @runBenchmarks()
|
||||
@on 'application:run-all-specs', -> @runSpecs(exitWhenDone: false, resourcePath: @devResourcePath, safeMode: @focusedWindow()?.safeMode)
|
||||
@on 'application:quit', -> app.quit()
|
||||
@on 'application:new-window', -> @openPath(_.extend(windowDimensions: @focusedWindow()?.getDimensions(), getLoadSettings()))
|
||||
@on 'application:new-file', -> (@focusedWindow() ? this).openPath()
|
||||
@@ -255,7 +252,7 @@ class AtomApplication
|
||||
@applicationMenu.update(win, template, keystrokesByCommand)
|
||||
|
||||
ipc.on 'run-package-specs', (event, specDirectory) =>
|
||||
@runSpecs({resourcePath: global.devResourcePath, specDirectory: specDirectory, exitWhenDone: false})
|
||||
@runSpecs({resourcePath: @devResourcePath, specDirectory: specDirectory, exitWhenDone: false})
|
||||
|
||||
ipc.on 'command', (event, command) =>
|
||||
@emit(command)
|
||||
@@ -371,14 +368,9 @@ class AtomApplication
|
||||
# :safeMode - Boolean to control the opened window's safe mode.
|
||||
# :windowDimensions - Object with height and width keys.
|
||||
# :window - {AtomWindow} to open file paths in.
|
||||
openPaths: ({pathsToOpen, pidToKillWhenClosed, newWindow, devMode, safeMode, windowDimensions, profileStartup, window}={}) ->
|
||||
pathsToOpen = pathsToOpen.map (pathToOpen) ->
|
||||
if fs.existsSync(pathToOpen)
|
||||
fs.normalize(pathToOpen)
|
||||
else
|
||||
pathToOpen
|
||||
|
||||
locationsToOpen = (@locationForPathToOpen(pathToOpen) for pathToOpen in pathsToOpen)
|
||||
openPaths: ({pathsToOpen, executedFrom, pidToKillWhenClosed, newWindow, devMode, safeMode, windowDimensions, profileStartup, window}={}) ->
|
||||
locationsToOpen = (@locationForPathToOpen(pathToOpen, executedFrom) for pathToOpen in pathsToOpen)
|
||||
pathsToOpen = (locationToOpen.pathToOpen for locationToOpen in locationsToOpen)
|
||||
|
||||
unless pidToKillWhenClosed or newWindow
|
||||
existingWindow = @windowForPaths(pathsToOpen, devMode)
|
||||
@@ -401,8 +393,8 @@ class AtomApplication
|
||||
else
|
||||
if devMode
|
||||
try
|
||||
bootstrapScript = require.resolve(path.join(global.devResourcePath, 'src', 'window-bootstrap'))
|
||||
resourcePath = global.devResourcePath
|
||||
bootstrapScript = require.resolve(path.join(@devResourcePath, 'src', 'window-bootstrap'))
|
||||
resourcePath = @devResourcePath
|
||||
|
||||
bootstrapScript ?= require.resolve('../window-bootstrap')
|
||||
resourcePath ?= @resourcePath
|
||||
@@ -503,7 +495,7 @@ class AtomApplication
|
||||
resourcePath = @resourcePath
|
||||
|
||||
try
|
||||
bootstrapScript = require.resolve(path.resolve(global.devResourcePath, 'spec', 'spec-bootstrap'))
|
||||
bootstrapScript = require.resolve(path.resolve(@devResourcePath, 'spec', 'spec-bootstrap'))
|
||||
catch error
|
||||
bootstrapScript = require.resolve(path.resolve(__dirname, '..', '..', 'spec', 'spec-bootstrap'))
|
||||
|
||||
@@ -512,33 +504,22 @@ class AtomApplication
|
||||
safeMode ?= false
|
||||
new AtomWindow({bootstrapScript, resourcePath, exitWhenDone, isSpec, devMode, specDirectory, logFile, safeMode})
|
||||
|
||||
runBenchmarks: ({exitWhenDone, specDirectory}={}) ->
|
||||
try
|
||||
bootstrapScript = require.resolve(path.resolve(global.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) ->
|
||||
locationForPathToOpen: (pathToOpen, executedFrom='') ->
|
||||
return {pathToOpen} unless pathToOpen
|
||||
return {pathToOpen} if url.parse(pathToOpen).protocol?
|
||||
return {pathToOpen} if fs.existsSync(pathToOpen)
|
||||
|
||||
pathToOpen = pathToOpen.replace(/[:\s]+$/, '')
|
||||
match = pathToOpen.match(LocationSuffixRegExp)
|
||||
|
||||
[fileToOpen, initialLine, initialColumn] = path.basename(pathToOpen).split(':')
|
||||
return {pathToOpen} unless initialLine
|
||||
return {pathToOpen} unless parseInt(initialLine) >= 0
|
||||
if match?
|
||||
pathToOpen = pathToOpen.slice(0, -match[0].length)
|
||||
initialLine = Math.max(0, parseInt(match[1].slice(1)) - 1) if match[1]
|
||||
initialColumn = Math.max(0, parseInt(match[2].slice(1)) - 1) if match[2]
|
||||
else
|
||||
initialLine = initialColumn = null
|
||||
|
||||
unless url.parse(pathToOpen).protocol?
|
||||
pathToOpen = path.resolve(executedFrom, fs.normalize(pathToOpen))
|
||||
|
||||
# Convert line numbers to a base of 0
|
||||
initialLine = Math.max(0, initialLine - 1) if initialLine
|
||||
initialColumn = Math.max(0, initialColumn - 1) if initialColumn
|
||||
pathToOpen = path.join(path.dirname(pathToOpen), fileToOpen)
|
||||
{pathToOpen, initialLine, initialColumn}
|
||||
|
||||
# Opens a native dialog to prompt the user for a path.
|
||||
|
||||
@@ -23,9 +23,6 @@ class AtomWindow
|
||||
locationsToOpen ?= [{pathToOpen}] if pathToOpen
|
||||
locationsToOpen ?= []
|
||||
|
||||
# Normalize to make sure drive letter case is consistent on Windows
|
||||
@resourcePath = path.normalize(@resourcePath) if @resourcePath
|
||||
|
||||
options =
|
||||
show: false
|
||||
title: 'Atom'
|
||||
|
||||
@@ -1,27 +1,23 @@
|
||||
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'
|
||||
path = require 'path'
|
||||
yargs = require 'yargs'
|
||||
url = require 'url'
|
||||
nslog = require 'nslog'
|
||||
|
||||
console.log = nslog
|
||||
|
||||
process.on 'uncaughtException', (error={}) ->
|
||||
nslog(error.message) if error.message?
|
||||
nslog(error.stack) if error.stack?
|
||||
console.log = require 'nslog'
|
||||
|
||||
start = ->
|
||||
setupAtomHome()
|
||||
setupCompileCache()
|
||||
return if handleStartupEventWithSquirrel()
|
||||
|
||||
if process.platform is 'win32'
|
||||
SquirrelUpdate = require './squirrel-update'
|
||||
squirrelCommand = process.argv[1]
|
||||
return if SquirrelUpdate.handleStartupEvent(app, squirrelCommand)
|
||||
# NB: This prevents Win10 from showing dupe items in the taskbar
|
||||
app.setAppUserModelId('com.squirrel.atom.atom')
|
||||
|
||||
args = parseCommandLine()
|
||||
|
||||
@@ -29,49 +25,40 @@ start = ->
|
||||
event.preventDefault()
|
||||
args.pathsToOpen.push(pathToOpen)
|
||||
|
||||
args.urlsToOpen = []
|
||||
addUrlToOpen = (event, urlToOpen) ->
|
||||
event.preventDefault()
|
||||
args.urlsToOpen.push(urlToOpen)
|
||||
|
||||
app.on 'open-file', addPathToOpen
|
||||
app.on 'open-url', addUrlToOpen
|
||||
|
||||
app.on 'will-finish-launching', ->
|
||||
setupCrashReporter()
|
||||
app.on 'will-finish-launching', setupCrashReporter
|
||||
|
||||
app.on 'ready', ->
|
||||
app.removeListener 'open-file', addPathToOpen
|
||||
app.removeListener 'open-url', addUrlToOpen
|
||||
|
||||
cwd = args.executedFrom?.toString() or process.cwd()
|
||||
args.pathsToOpen = args.pathsToOpen.map (pathToOpen) ->
|
||||
normalizedPath = fs.normalize(pathToOpen)
|
||||
if url.parse(pathToOpen).protocol?
|
||||
pathToOpen
|
||||
else if cwd
|
||||
path.resolve(cwd, normalizedPath)
|
||||
else
|
||||
path.resolve(pathToOpen)
|
||||
|
||||
if args.devMode
|
||||
AtomApplication = require path.join(args.resourcePath, 'src', 'browser', 'atom-application')
|
||||
else
|
||||
AtomApplication = require './atom-application'
|
||||
|
||||
AtomApplication = require path.join(args.resourcePath, 'src', 'browser', 'atom-application')
|
||||
AtomApplication.open(args)
|
||||
|
||||
console.log("App load time: #{Date.now() - global.shellStartTime}ms") unless args.test
|
||||
|
||||
global.devResourcePath = process.env.ATOM_DEV_RESOURCE_PATH ? path.join(app.getHomeDir(), 'github', 'atom')
|
||||
# Normalize to make sure drive letter case is consistent on Windows
|
||||
global.devResourcePath = path.normalize(global.devResourcePath) if global.devResourcePath
|
||||
normalizeDriveLetterName = (filePath) ->
|
||||
if process.platform is 'win32'
|
||||
filePath.replace /^([a-z]):/, ([driveLetter]) -> driveLetter.toUpperCase() + ":"
|
||||
else
|
||||
filePath
|
||||
|
||||
handleStartupEventWithSquirrel = ->
|
||||
return false unless process.platform is 'win32'
|
||||
SquirrelUpdate = require './squirrel-update'
|
||||
squirrelCommand = process.argv[1]
|
||||
SquirrelUpdate.handleStartupEvent(app, squirrelCommand)
|
||||
|
||||
setupCrashReporter = ->
|
||||
crashReporter.start(productName: 'Atom', companyName: 'GitHub')
|
||||
|
||||
setupAtomHome = ->
|
||||
return if process.env.ATOM_HOME
|
||||
|
||||
atomHome = path.join(app.getHomeDir(), '.atom')
|
||||
try
|
||||
atomHome = fs.realpathSync(atomHome)
|
||||
@@ -129,7 +116,7 @@ parseCommandLine = ->
|
||||
process.stdout.write("#{version}\n")
|
||||
process.exit(0)
|
||||
|
||||
executedFrom = args['executed-from']
|
||||
executedFrom = args['executed-from']?.toString() ? process.cwd()
|
||||
devMode = args['dev']
|
||||
safeMode = args['safe']
|
||||
pathsToOpen = args._
|
||||
@@ -140,6 +127,8 @@ parseCommandLine = ->
|
||||
logFile = args['log-file']
|
||||
socketPath = args['socket-path']
|
||||
profileStartup = args['profile-startup']
|
||||
urlsToOpen = []
|
||||
devResourcePath = process.env.ATOM_DEV_RESOURCE_PATH ? path.join(app.getHomeDir(), 'github', 'atom')
|
||||
|
||||
if args['resource-path']
|
||||
devMode = true
|
||||
@@ -155,7 +144,7 @@ parseCommandLine = ->
|
||||
resourcePath = packageDirectoryPath if packageManifest.name is 'atom'
|
||||
|
||||
if devMode
|
||||
resourcePath ?= global.devResourcePath
|
||||
resourcePath ?= devResourcePath
|
||||
|
||||
unless fs.statSyncNoException(resourcePath)
|
||||
resourcePath = path.dirname(path.dirname(__dirname))
|
||||
@@ -164,7 +153,11 @@ parseCommandLine = ->
|
||||
# explicitly pass it by command line, see http://git.io/YC8_Ew.
|
||||
process.env.PATH = args['path-environment'] if args['path-environment']
|
||||
|
||||
{resourcePath, pathsToOpen, executedFrom, test, version, pidToKillWhenClosed,
|
||||
devMode, safeMode, newWindow, specDirectory, logFile, socketPath, profileStartup}
|
||||
resourcePath = normalizeDriveLetterName(resourcePath)
|
||||
devResourcePath = normalizeDriveLetterName(devResourcePath)
|
||||
|
||||
{resourcePath, devResourcePath, pathsToOpen, urlsToOpen, executedFrom, test,
|
||||
version, pidToKillWhenClosed, devMode, safeMode, newWindow, specDirectory,
|
||||
logFile, socketPath, profileStartup}
|
||||
|
||||
start()
|
||||
|
||||
@@ -70,6 +70,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
|
||||
|
||||
|
||||
@@ -16,7 +16,7 @@ var cacheDirectory = null
|
||||
exports.setAtomHomeDirectory = function (atomHome) {
|
||||
var cacheDir = path.join(atomHome, 'compile-cache')
|
||||
if (process.env.USER === 'root' && process.env.SUDO_USER && process.env.SUDO_USER !== process.env.USER) {
|
||||
cacheDir = path.join(cacheDirectory, 'root')
|
||||
cacheDir = path.join(cacheDir, 'root')
|
||||
}
|
||||
this.setCacheDirectory(cacheDir)
|
||||
}
|
||||
@@ -108,7 +108,7 @@ require('source-map-support').install({
|
||||
// source-map-support module, but we've overridden it to read the javascript
|
||||
// code from our cache directory.
|
||||
retrieveSourceMap: function (filePath) {
|
||||
if (!fs.isFileSync(filePath)) {
|
||||
if (!cacheDirectory || !fs.isFileSync(filePath)) {
|
||||
return null
|
||||
}
|
||||
|
||||
|
||||
@@ -22,32 +22,6 @@ class Cursor extends Model
|
||||
|
||||
@assignId(id)
|
||||
@updateVisibility()
|
||||
@marker.onDidChange (e) =>
|
||||
@updateVisibility()
|
||||
{oldHeadScreenPosition, newHeadScreenPosition} = e
|
||||
{oldHeadBufferPosition, newHeadBufferPosition} = e
|
||||
{textChanged} = e
|
||||
return if oldHeadScreenPosition.isEqual(newHeadScreenPosition)
|
||||
|
||||
@goalColumn = null
|
||||
|
||||
movedEvent =
|
||||
oldBufferPosition: oldHeadBufferPosition
|
||||
oldScreenPosition: oldHeadScreenPosition
|
||||
newBufferPosition: newHeadBufferPosition
|
||||
newScreenPosition: newHeadScreenPosition
|
||||
textChanged: textChanged
|
||||
cursor: this
|
||||
|
||||
@emit 'moved', movedEvent if Grim.includeDeprecatedAPIs
|
||||
@emitter.emit 'did-change-position', movedEvent
|
||||
@editor.cursorMoved(movedEvent)
|
||||
@marker.onDidDestroy =>
|
||||
@destroyed = true
|
||||
@editor.removeCursor(this)
|
||||
@emit 'destroyed' if Grim.includeDeprecatedAPIs
|
||||
@emitter.emit 'did-destroy'
|
||||
@emitter.dispose()
|
||||
|
||||
destroy: ->
|
||||
@marker.destroy()
|
||||
|
||||
@@ -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
|
||||
|
||||
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 (true) {
|
||||
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)
|
||||
})
|
||||
}
|
||||
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()
|
||||
@@ -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})
|
||||
|
||||
@@ -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,7 +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 +60,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()
|
||||
|
||||
@@ -80,7 +80,7 @@ class NotificationManager
|
||||
|
||||
# Public: Get all the notifications.
|
||||
#
|
||||
# Returns an {Array} of {Notifications}s.
|
||||
# Returns an {Array} of {Notification}s.
|
||||
getNotifications: -> @notifications.slice()
|
||||
|
||||
###
|
||||
|
||||
@@ -3,7 +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'
|
||||
@@ -392,7 +391,7 @@ class PackageManager
|
||||
for [activator, types] in @packageActivators
|
||||
packages = @getLoadedPackagesForTypes(types)
|
||||
promises = promises.concat(activator.activatePackages(packages))
|
||||
Q.all(promises).then =>
|
||||
Promise.all(promises).then =>
|
||||
@emit 'activated' if Grim.includeDeprecatedAPIs
|
||||
@emitter.emit 'did-activate-initial-packages'
|
||||
|
||||
@@ -415,14 +414,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
|
||||
|
||||
@@ -6,15 +6,15 @@ 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
|
||||
@@ -138,20 +138,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 +165,7 @@ class Package
|
||||
catch error
|
||||
@handleError("Failed to activate the #{@name} package", error)
|
||||
|
||||
@activationDeferred?.resolve()
|
||||
@resolveActivationPromise?()
|
||||
|
||||
activateConfig: ->
|
||||
return if @configActivated
|
||||
@@ -344,7 +345,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 +360,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 +382,18 @@ class Package
|
||||
settings.activate() if @settingsActivated
|
||||
callback()
|
||||
|
||||
deferred = Q.defer()
|
||||
new Promise (resolve) =>
|
||||
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
|
||||
settingsDirPath = path.join(@path, 'settings')
|
||||
|
||||
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
|
||||
settingsDirPath = path.join(@path, 'settings')
|
||||
fs.exists settingsDirPath, (settingsDirExists) ->
|
||||
return resolve() unless settingsDirExists
|
||||
|
||||
fs.exists settingsDirPath, (settingsDirExists) ->
|
||||
return deferred.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 +403,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()
|
||||
@@ -589,10 +589,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 +613,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 +684,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 +702,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}"
|
||||
|
||||
@@ -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,7 +1,5 @@
|
||||
{CompositeDisposable} = require 'event-kit'
|
||||
Grim = require 'grim'
|
||||
{callAttachHooks} = require './space-pen-extensions'
|
||||
PaneContainerView = null
|
||||
_ = require 'underscore-plus'
|
||||
|
||||
module.exports =
|
||||
@@ -10,13 +8,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 +18,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,8 +1,6 @@
|
||||
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 +12,6 @@ class PaneElement extends HTMLElement
|
||||
|
||||
@initializeContent()
|
||||
@subscribeToDOMEvents()
|
||||
@createSpacePenShim() if Grim.includeDeprecatedAPIs
|
||||
|
||||
attachedCallback: ->
|
||||
@attached = true
|
||||
@@ -55,10 +52,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 +59,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 +87,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 +111,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()
|
||||
@@ -355,6 +355,7 @@ 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
|
||||
|
||||
|
||||
@@ -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,7 +3,6 @@ url = require 'url'
|
||||
|
||||
_ = require 'underscore-plus'
|
||||
fs = require 'fs-plus'
|
||||
Q = require 'q'
|
||||
{includeDeprecatedAPIs, deprecate} = require 'grim'
|
||||
{Emitter} = require 'event-kit'
|
||||
Serializable = require 'serializable'
|
||||
@@ -367,8 +366,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
|
||||
|
||||
@@ -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)
|
||||
@@ -22,14 +22,8 @@ class Selection extends Model
|
||||
@cursor.selection = this
|
||||
@decoration = @editor.decorateMarker(@marker, type: 'highlight', class: 'selection')
|
||||
|
||||
@marker.onDidChange (e) => @screenRangeChanged(e)
|
||||
@marker.onDidDestroy =>
|
||||
unless @editor.isDestroyed()
|
||||
@destroyed = true
|
||||
@editor.removeSelection(this)
|
||||
@emit 'destroyed' if Grim.includeDeprecatedAPIs
|
||||
@emitter.emit 'did-destroy'
|
||||
@emitter.dispose()
|
||||
@marker.onDidChange (e) => @markerDidChange(e)
|
||||
@marker.onDidDestroy => @markerDidDestroy()
|
||||
|
||||
destroy: ->
|
||||
@marker.destroy()
|
||||
@@ -263,10 +257,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: ->
|
||||
@@ -330,9 +329,13 @@ class Selection extends Model
|
||||
#
|
||||
# * `row` The line {Number} to select (default: the row of the cursor).
|
||||
selectLine: (row, options) ->
|
||||
row ?= @cursor.getBufferPosition().row
|
||||
range = @editor.bufferRangeForBufferRow(row, includeNewline: true)
|
||||
@setBufferRange(@getBufferRange().union(range), options)
|
||||
if row?
|
||||
@setBufferRange(@editor.bufferRangeForBufferRow(row, includeNewline: true), options)
|
||||
else
|
||||
startRange = @editor.bufferRangeForBufferRow(@marker.getStartBufferPosition().row)
|
||||
endRange = @editor.bufferRangeForBufferRow(@marker.getEndBufferPosition().row, includeNewline: true)
|
||||
@setBufferRange(startRange.union(endRange), options)
|
||||
|
||||
@linewise = true
|
||||
@wordwise = false
|
||||
@initialScreenRange = @getScreenRange()
|
||||
@@ -574,11 +577,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}
|
||||
@@ -754,20 +762,48 @@ class Selection extends Model
|
||||
Section: Private Utilities
|
||||
###
|
||||
|
||||
screenRangeChanged: (e) ->
|
||||
{oldHeadBufferPosition, oldTailBufferPosition} = e
|
||||
{oldHeadScreenPosition, oldTailScreenPosition} = e
|
||||
markerDidChange: (e) ->
|
||||
{oldHeadBufferPosition, oldTailBufferPosition, newHeadBufferPosition} = e
|
||||
{oldHeadScreenPosition, oldTailScreenPosition, newHeadScreenPosition} = e
|
||||
{textChanged} = e
|
||||
|
||||
eventObject =
|
||||
@cursor.updateVisibility()
|
||||
|
||||
unless oldHeadScreenPosition.isEqual(newHeadScreenPosition)
|
||||
@cursor.goalColumn = null
|
||||
cursorMovedEvent = {
|
||||
oldBufferPosition: oldHeadBufferPosition
|
||||
oldScreenPosition: oldHeadScreenPosition
|
||||
newBufferPosition: newHeadBufferPosition
|
||||
newScreenPosition: newHeadScreenPosition
|
||||
textChanged: textChanged
|
||||
cursor: @cursor
|
||||
}
|
||||
@cursor.emitter.emit('did-change-position', cursorMovedEvent)
|
||||
@editor.cursorMoved(cursorMovedEvent)
|
||||
|
||||
@emitter.emit 'did-change-range'
|
||||
@editor.selectionRangeChanged(
|
||||
oldBufferRange: new Range(oldHeadBufferPosition, oldTailBufferPosition)
|
||||
oldScreenRange: new Range(oldHeadScreenPosition, oldTailScreenPosition)
|
||||
newBufferRange: @getBufferRange()
|
||||
newScreenRange: @getScreenRange()
|
||||
selection: this
|
||||
)
|
||||
|
||||
@emit 'screen-range-changed', @getScreenRange() if Grim.includeDeprecatedAPIs
|
||||
@emitter.emit 'did-change-range'
|
||||
@editor.selectionRangeChanged(eventObject)
|
||||
markerDidDestroy: ->
|
||||
return if @editor.isDestroyed()
|
||||
|
||||
@destroyed = true
|
||||
@cursor.destroyed = true
|
||||
|
||||
@editor.removeSelection(this)
|
||||
|
||||
@cursor.emitter.emit 'did-destroy'
|
||||
@emitter.emit 'did-destroy'
|
||||
|
||||
@cursor.emitter.dispose()
|
||||
@emitter.dispose()
|
||||
|
||||
finalize: ->
|
||||
@initialScreenRange = null unless @initialScreenRange?.isEqual(@getScreenRange())
|
||||
|
||||
@@ -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
|
||||
@@ -12,6 +12,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 +27,6 @@ class TextEditorComponent
|
||||
updatesPaused: false
|
||||
updateRequestedWhilePaused: false
|
||||
heightAndWidthMeasurementRequested: false
|
||||
cursorMoved: false
|
||||
selectionChanged: false
|
||||
inputEnabled: true
|
||||
measureScrollbarsWhenShown: true
|
||||
measureLineHeightAndDefaultCharWidthWhenShown: true
|
||||
@@ -54,6 +53,8 @@ class TextEditorComponent
|
||||
|
||||
@presenter.onDidUpdateState(@requestUpdate)
|
||||
|
||||
@domElementPool = new DOMElementPool
|
||||
|
||||
@domNode = document.createElement('div')
|
||||
if @useShadowDOM
|
||||
@domNode.classList.add('editor-contents--private')
|
||||
@@ -75,7 +76,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})
|
||||
@@ -107,6 +108,7 @@ class TextEditorComponent
|
||||
@disposables.dispose()
|
||||
@presenter.destroy()
|
||||
@gutterContainerComponent?.destroy()
|
||||
@domElementPool.clear()
|
||||
|
||||
getDomNode: ->
|
||||
@domNode
|
||||
@@ -115,11 +117,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 +149,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 +212,6 @@ class TextEditorComponent
|
||||
|
||||
observeEditor: ->
|
||||
@disposables.add @editor.observeGrammar(@onGrammarChanged)
|
||||
@disposables.add @editor.observeCursors(@onCursorAdded)
|
||||
@disposables.add @editor.observeSelections(@onSelectionAdded)
|
||||
|
||||
listenForDOMEvents: ->
|
||||
@domNode.addEventListener 'mousewheel', @onMouseWheel
|
||||
@@ -482,29 +477,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 = {}
|
||||
@@ -545,20 +517,24 @@ class TextEditorComponent
|
||||
disposables.dispose()
|
||||
|
||||
autoscroll = (mouseClientPosition) =>
|
||||
editorClientRect = @domNode.getBoundingClientRect()
|
||||
{top, bottom, left, right} = @scrollViewNode.getBoundingClientRect()
|
||||
top += 30
|
||||
bottom -= 30
|
||||
left += 30
|
||||
right -= 30
|
||||
|
||||
if mouseClientPosition.clientY < editorClientRect.top
|
||||
mouseYDelta = editorClientRect.top - mouseClientPosition.clientY
|
||||
if mouseClientPosition.clientY < top
|
||||
mouseYDelta = top - mouseClientPosition.clientY
|
||||
yDirection = -1
|
||||
else if mouseClientPosition.clientY > editorClientRect.bottom
|
||||
mouseYDelta = mouseClientPosition.clientY - editorClientRect.bottom
|
||||
else if mouseClientPosition.clientY > bottom
|
||||
mouseYDelta = mouseClientPosition.clientY - bottom
|
||||
yDirection = 1
|
||||
|
||||
if mouseClientPosition.clientX < editorClientRect.left
|
||||
mouseXDelta = editorClientRect.left - mouseClientPosition.clientX
|
||||
if mouseClientPosition.clientX < left
|
||||
mouseXDelta = left - mouseClientPosition.clientX
|
||||
xDirection = -1
|
||||
else if mouseClientPosition.clientX > editorClientRect.right
|
||||
mouseXDelta = mouseClientPosition.clientX - editorClientRect.right
|
||||
else if mouseClientPosition.clientX > right
|
||||
mouseXDelta = mouseClientPosition.clientX - right
|
||||
xDirection = 1
|
||||
|
||||
if mouseYDelta?
|
||||
@@ -578,7 +554,7 @@ class TextEditorComponent
|
||||
window.addEventListener('mousemove', onMouseMove)
|
||||
window.addEventListener('mouseup', onMouseUp)
|
||||
disposables = new CompositeDisposable
|
||||
disposables.add(@editor.onWillInsertText(onMouseUp))
|
||||
disposables.add(@editor.getBuffer().onWillChange(onMouseUp))
|
||||
disposables.add(@editor.onDidDestroy(stopDragging))
|
||||
|
||||
isVisible: ->
|
||||
@@ -645,6 +621,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
|
||||
@@ -745,6 +722,13 @@ class TextEditorComponent
|
||||
|
||||
tileComponent?.lineNumberNodeForScreenRow(screenRow)
|
||||
|
||||
tileNodesForLines: ->
|
||||
@linesComponent.getTiles()
|
||||
|
||||
tileNodesForLineNumbers: ->
|
||||
gutterComponent = @gutterContainerComponent.getLineNumberGutterComponent()
|
||||
gutterComponent.getTiles()
|
||||
|
||||
screenRowForNode: (node) ->
|
||||
while node?
|
||||
if screenRow = node.dataset.screenRow
|
||||
@@ -799,6 +783,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)
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
{Emitter} = require 'event-kit'
|
||||
{View, $, callRemoveHooks} = require 'space-pen'
|
||||
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
|
||||
|
||||
@@ -22,7 +20,6 @@ class TextEditorElement extends HTMLElement
|
||||
createdCallback: ->
|
||||
@emitter = new Emitter
|
||||
@initializeContent()
|
||||
@createSpacePenShim() if Grim.includeDeprecatedAPIs
|
||||
@addEventListener 'focus', @focused.bind(this)
|
||||
@addEventListener 'blur', @blurred.bind(this)
|
||||
|
||||
@@ -56,10 +53,6 @@ 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")
|
||||
@@ -90,7 +83,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 +118,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 +163,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.
|
||||
@@ -299,6 +296,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,6 +11,7 @@ class TextEditorPresenter
|
||||
mouseWheelScreenRow: null
|
||||
scopedCharacterWidthsChangeCount: 0
|
||||
overlayDimensions: {}
|
||||
minimumReflowInterval: 200
|
||||
|
||||
constructor: (params) ->
|
||||
{@model, @autoHeight, @explicitHeight, @contentFrameWidth, @scrollTop, @scrollLeft, @boundingClientRect, @windowWidth, @windowHeight, @gutterWidth} = params
|
||||
@@ -20,7 +21,7 @@ class TextEditorPresenter
|
||||
@measuredHorizontalScrollbarHeight = horizontalScrollbarHeight
|
||||
@measuredVerticalScrollbarWidth = verticalScrollbarWidth
|
||||
@gutterWidth ?= 0
|
||||
@tileSize ?= 12
|
||||
@tileSize ?= 6
|
||||
|
||||
@disposables = new CompositeDisposable
|
||||
@emitter = new Emitter
|
||||
@@ -35,6 +36,7 @@ class TextEditorPresenter
|
||||
@observeConfig()
|
||||
@buildState()
|
||||
@startBlinkingCursors() if @focused
|
||||
@startReflowing() if @continuousReflow
|
||||
@updating = false
|
||||
|
||||
destroy: ->
|
||||
@@ -72,6 +74,7 @@ class TextEditorPresenter
|
||||
@updateStartRow()
|
||||
@updateEndRow()
|
||||
@updateCommonGutterState()
|
||||
@updateReflowState()
|
||||
|
||||
@updateFocusedState() if @shouldUpdateFocusedState
|
||||
@updateHeightState() if @shouldUpdateHeightState
|
||||
@@ -254,6 +257,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 +547,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 +604,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 +620,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]
|
||||
|
||||
@@ -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))
|
||||
@@ -17,7 +17,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
|
||||
@@ -545,8 +546,8 @@ 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)
|
||||
|
||||
@@ -1761,18 +1762,11 @@ class TextEditor extends Model
|
||||
@emitter.emit 'did-add-cursor', cursor
|
||||
cursor
|
||||
|
||||
# Remove the given cursor from this editor.
|
||||
removeCursor: (cursor) ->
|
||||
_.remove(@cursors, cursor)
|
||||
@emit 'cursor-removed', cursor if includeDeprecatedAPIs
|
||||
@emitter.emit 'did-remove-cursor', cursor
|
||||
|
||||
moveCursors: (fn) ->
|
||||
fn(cursor) for cursor in @getCursors()
|
||||
@mergeCursors()
|
||||
|
||||
cursorMoved: (event) ->
|
||||
@emit 'cursor-moved', event if includeDeprecatedAPIs
|
||||
@emitter.emit 'did-change-cursor-position', event
|
||||
|
||||
# Merge cursors that have the same screen position
|
||||
@@ -2259,9 +2253,9 @@ class TextEditor extends Model
|
||||
|
||||
# Remove the given selection.
|
||||
removeSelection: (selection) ->
|
||||
_.remove(@cursors, selection.cursor)
|
||||
_.remove(@selections, selection)
|
||||
atom.assert @selections.length > 0, "Removed last selection"
|
||||
@emit 'selection-removed', selection if includeDeprecatedAPIs
|
||||
@emitter.emit 'did-remove-cursor', selection.cursor
|
||||
@emitter.emit 'did-remove-selection', selection
|
||||
|
||||
# Reduce one or more selections to a single empty selection based on the most
|
||||
@@ -2281,7 +2275,6 @@ class TextEditor extends Model
|
||||
|
||||
# Called by the selection
|
||||
selectionRangeChanged: (event) ->
|
||||
@emit 'selection-screen-range-changed', event if includeDeprecatedAPIs
|
||||
@emitter.emit 'did-change-selection-range', event
|
||||
|
||||
createLastSelectionIfNeeded: ->
|
||||
@@ -2672,7 +2665,7 @@ class TextEditor extends Model
|
||||
@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
|
||||
@@ -2680,6 +2673,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
|
||||
###
|
||||
@@ -3059,9 +3061,6 @@ if includeDeprecatedAPIs
|
||||
'$verticalScrollbarWidth', '$horizontalScrollbarHeight', '$scrollTop', '$scrollLeft',
|
||||
toProperty: 'displayBuffer'
|
||||
|
||||
TextEditor::getViewClass = ->
|
||||
require './text-editor-view'
|
||||
|
||||
TextEditor::joinLine = ->
|
||||
deprecate("Use TextEditor::joinLines() instead")
|
||||
@joinLines()
|
||||
|
||||
@@ -3,7 +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.
|
||||
@@ -260,32 +259,29 @@ 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()
|
||||
@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
|
||||
@emit 'reloaded' if Grim.includeDeprecatedAPIs
|
||||
@emitter.emit 'did-change-active-themes'
|
||||
resolve()
|
||||
|
||||
deactivateThemes: ->
|
||||
@removeActiveThemeClasses()
|
||||
|
||||
@@ -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++
|
||||
|
||||
@@ -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: (evenDocumentt) ->
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
|
||||
onDragOver: (event) ->
|
||||
handleDragover: (Documentevent) ->
|
||||
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()
|
||||
@@ -2,7 +2,6 @@
|
||||
_ = require 'underscore-plus'
|
||||
path = require 'path'
|
||||
{join} = path
|
||||
Q = require 'q'
|
||||
Serializable = require 'serializable'
|
||||
{Emitter, Disposable, CompositeDisposable} = require 'event-kit'
|
||||
Grim = require 'grim'
|
||||
@@ -448,17 +447,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])
|
||||
@@ -488,7 +487,7 @@ class Workspace extends Model
|
||||
if uri = @destroyedItemURIs.pop()
|
||||
@open(uri)
|
||||
else
|
||||
Q()
|
||||
Promise.resolve()
|
||||
|
||||
# Public: Register an opener for a uri.
|
||||
#
|
||||
@@ -506,6 +505,15 @@ 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()
|
||||
@@ -895,7 +903,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,36 +933,33 @@ 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
|
||||
inProcessFinished = true
|
||||
checkFinished()
|
||||
|
||||
if includeDeprecatedAPIs
|
||||
Workspace.properties
|
||||
|
||||
Reference in New Issue
Block a user