Merge branch 'master' into ns-fix-softwrap

Conflicts:
	src/row-map.coffee
This commit is contained in:
Nathan Sobo
2014-02-14 08:36:58 -07:00
117 changed files with 2967 additions and 2470 deletions

View File

@@ -2,12 +2,13 @@ Package = require './package'
fs = require 'fs-plus'
path = require 'path'
_ = require 'underscore-plus'
Q = require 'q'
{$} = require './space-pen-extensions'
CSON = require 'season'
{Emitter} = require 'emissary'
### Internal: Loads and resolves packages. ###
# Loads and activates a package's main module and resources such as
# stylesheets, keymaps, grammar, editor properties, and menus.
module.exports =
class AtomPackage extends Package
Emitter.includeInto(this)
@@ -42,11 +43,7 @@ class AtomPackage extends Package
@loadStylesheets()
@loadGrammars()
@loadScopedProperties()
if @metadata.activationEvents?
@registerDeferredDeserializers()
else
@requireMainModule()
@requireMainModule() unless @metadata.activationEvents?
catch e
console.warn "Failed to load package named '#{@name}'", e.stack ? e
@@ -59,14 +56,19 @@ class AtomPackage extends Package
@grammars = []
@scopedProperties = []
activate: ({immediate}={}) ->
activate: ->
return @activationDeferred.promise if @activationDeferred?
@activationDeferred = Q.defer()
@measure 'activateTime', =>
@activateResources()
if @metadata.activationEvents? and not immediate
if @metadata.activationEvents?
@subscribeToActivationEvents()
else
@activateNow()
@activationDeferred.promise
activateNow: ->
try
@activateConfig()
@@ -77,6 +79,8 @@ class AtomPackage extends Package
catch e
console.warn "Failed to activate package named '#{@name}'", e.stack
@activationDeferred.resolve()
activateConfig: ->
return if @configActivated
@@ -162,6 +166,8 @@ class AtomPackage extends Package
console.error "Error serializing package '#{@name}'", e.stack
deactivate: ->
@activationDeferred?.reject()
@activationDeferred = null
@unsubscribeFromActivationEvents()
@deactivateResources()
@deactivateConfig()
@@ -203,12 +209,6 @@ class AtomPackage extends Package
path.join(@path, 'index')
@mainModulePath = fs.resolveExtension(mainModulePath, ["", _.keys(require.extensions)...])
registerDeferredDeserializers: ->
for deserializerName in @metadata.deferredDeserializers ? []
atom.deserializers.addDeferred deserializerName, =>
@activateStylesheets()
@requireMainModule()
subscribeToActivationEvents: ->
return unless @metadata.activationEvents?
if _.isArray(@metadata.activationEvents)
@@ -226,6 +226,8 @@ class AtomPackage extends Package
@unsubscribeFromActivationEvents()
unsubscribeFromActivationEvents: ->
return unless atom.workspaceView?
if _.isArray(@metadata.activationEvents)
atom.workspaceView.off(event, @handleActivationEvent) for event in @metadata.activationEvents
else if _.isString(@metadata.activationEvents)

View File

@@ -6,8 +6,6 @@ path = require 'path'
remote = require 'remote'
screen = require 'screen'
shell = require 'shell'
dialog = remote.require 'dialog'
app = remote.require 'app'
_ = require 'underscore-plus'
{Model} = require 'theorist'
@@ -22,16 +20,18 @@ WindowEventHandler = require './window-event-handler'
#
# ## Useful properties available:
#
# * `atom.config` - A {Config} instance
# * `atom.contextMenu` - A {ContextMenuManager} instance
# * `atom.keymap` - A {Keymap} instance
# * `atom.menu` - A {MenuManager} instance
# * `atom.workspaceView` - A {WorkspaceView} instance
# * `atom.packages` - A {PackageManager} instance
# * `atom.pasteboard` - A {Pasteboard} instance
# * `atom.project` - A {Project} instance
# * `atom.syntax` - A {Syntax} instance
# * `atom.themes` - A {ThemeManager} instance
# * `atom.clipboard` - A {Clipboard} instance
# * `atom.config` - A {Config} instance
# * `atom.contextMenu` - A {ContextMenuManager} instance
# * `atom.deserializers` - A {DeserializerManager} instance
# * `atom.keymap` - A {Keymap} instance
# * `atom.menu` - A {MenuManager} instance
# * `atom.packages` - A {PackageManager} instance
# * `atom.project` - A {Project} instance
# * `atom.syntax` - A {Syntax} instance
# * `atom.themes` - A {ThemeManager} instance
# * `atom.workspace` - A {Workspace} instance
# * `atom.workspaceView` - A {WorkspaceView} instance
module.exports =
class Atom extends Model
# Public: Load or create the Atom environment in the given mode.
@@ -43,11 +43,11 @@ class Atom extends Model
@loadOrCreate: (mode) ->
@deserialize(@loadState(mode)) ? new this({mode, version: @getVersion()})
# Private: Deserializes the Atom environment from a state object
# Deserializes the Atom environment from a state object
@deserialize: (state) ->
new this(state) if state?.version is @getVersion()
# Private: Loads and returns the serialized state corresponding to this window
# Loads and returns the serialized state corresponding to this window
# if it exists; otherwise returns undefined.
@loadState: (mode) ->
statePath = @getStatePath(mode)
@@ -65,7 +65,7 @@ class Atom extends Model
catch error
console.warn "Error parsing window state: #{statePath} #{error.stack}", error
# Private: Returns the path where the state for the current window will be
# Returns the path where the state for the current window will be
# located if it exists.
@getStatePath: (mode) ->
switch mode
@@ -82,41 +82,47 @@ class Atom extends Model
else
null
# Private: Get the directory path to Atom's configuration area.
# Get the directory path to Atom's configuration area.
#
# Returns the absolute path to ~/.atom
@getConfigDirPath: ->
@configDirPath ?= fs.absolute('~/.atom')
# Private: Get the path to Atom's storage directory.
# Get the path to Atom's storage directory.
#
# Returns the absolute path to ~/.atom/storage
@getStorageDirPath: ->
@storageDirPath ?= path.join(@getConfigDirPath(), 'storage')
# Private: Returns the load settings hash associated with the current window.
# Returns the load settings hash associated with the current window.
@getLoadSettings: ->
_.deepClone(@loadSettings ?= _.deepClone(@getCurrentWindow().loadSettings))
@loadSettings ?= JSON.parse(decodeURIComponent(location.search.substr(14)))
cloned = _.deepClone(@loadSettings)
# The loadSettings.windowState could be large, request it only when needed.
cloned.__defineGetter__ 'windowState', =>
@getCurrentWindow().loadSettings.windowState
cloned.__defineSetter__ 'windowState', (value) =>
@getCurrentWindow().loadSettings.windowState = value
cloned
# Private:
@getCurrentWindow: ->
remote.getCurrentWindow()
# Private: Get the version of the Atom application.
# Get the version of the Atom application.
@getVersion: ->
@version ?= app.getVersion()
@version ?= @getLoadSettings().appVersion
# Private: Determine whether the current version is an official release.
# Determine whether the current version is an official release.
@isReleasedVersion: ->
not /\w{7}/.test(@getVersion()) # Check if the release is a 7-character SHA prefix
workspaceViewParentSelector: 'body'
# Private: Call .loadOrCreate instead
# Call .loadOrCreate instead
constructor: (@state) ->
{@mode} = @state
DeserializerManager = require './deserializer-manager'
@deserializers = new DeserializerManager(this)
@deserializers = new DeserializerManager()
# Public: Sets up the basic services that should be available in all modes
# (both spec and application). Call after this instance has been assigned to
@@ -134,7 +140,7 @@ class Atom extends Model
Config = require './config'
Keymap = require './keymap'
PackageManager = require './package-manager'
Pasteboard = require './pasteboard'
Clipboard = require './clipboard'
Syntax = require './syntax'
ThemeManager = require './theme-manager'
ContextMenuManager = require './context-menu-manager'
@@ -148,7 +154,8 @@ class Atom extends Model
@themes = new ThemeManager({packageManager: @packages, configDirPath, resourcePath})
@contextMenu = new ContextMenuManager(devMode)
@menu = new MenuManager({resourcePath})
@pasteboard = new Pasteboard()
@clipboard = new Clipboard()
@syntax = @deserializers.deserialize(@state.syntax) ? new Syntax()
@subscribe @packages, 'activated', => @watchThemes()
@@ -167,7 +174,6 @@ class Atom extends Model
# Deprecated: Callers should be converted to use atom.deserializers
registerRepresentationClasses: ->
# Private:
setBodyPlatformClass: ->
document.body.classList.add("platform-#{process.platform}")
@@ -196,15 +202,13 @@ class Atom extends Model
# + width: The new width.
# + height: The new height.
setWindowDimensions: ({x, y, width, height}) ->
browserWindow = @getCurrentWindow()
if width? and height?
browserWindow.setSize(width, height)
@setSize(width, height)
if x? and y?
browserWindow.setPosition(x, y)
@setPosition(x, y)
else
browserWindow.center()
@center()
# Private:
restoreWindowDimensions: ->
workAreaSize = screen.getPrimaryDisplay().workAreaSize
windowDimensions = @state.windowDimensions ? {}
@@ -213,7 +217,6 @@ class Atom extends Model
windowDimensions.width ?= initialSize?.width ? Math.min(workAreaSize.width, 1024)
@setWindowDimensions(windowDimensions)
# Private:
storeWindowDimensions: ->
@state.windowDimensions = @getWindowDimensions()
@@ -223,12 +226,10 @@ class Atom extends Model
getLoadSettings: ->
@constructor.getLoadSettings()
# Private:
deserializeProject: ->
Project = require './project'
@project ?= @deserializers.deserialize(@project) ? new Project(path: @getLoadSettings().initialPath)
# Private:
deserializeWorkspaceView: ->
Workspace = require './workspace'
WorkspaceView = require './workspace-view'
@@ -236,24 +237,22 @@ class Atom extends Model
@workspaceView = new WorkspaceView(@workspace)
$(@workspaceViewParentSelector).append(@workspaceView)
# Private:
deserializePackageStates: ->
@packages.packageStates = @state.packageStates ? {}
delete @state.packageStates
# Private:
deserializeEditorWindow: ->
@deserializePackageStates()
@deserializeProject()
@deserializeWorkspaceView()
# Private: Call this method when establishing a real application window.
# Call this method when establishing a real application window.
startEditorWindow: ->
CommandInstaller = require './command-installer'
resourcePath = atom.getLoadSettings().resourcePath
CommandInstaller.installAtomCommand resourcePath, (error) ->
CommandInstaller.installAtomCommand resourcePath, false, (error) ->
console.warn error.message if error?
CommandInstaller.installApmCommand resourcePath, (error) ->
CommandInstaller.installApmCommand resourcePath, false, (error) ->
console.warn error.message if error?
@restoreWindowDimensions()
@@ -276,7 +275,6 @@ class Atom extends Model
@displayWindow()
# Private:
unloadEditorWindow: ->
return if not @project and not @workspaceView
@@ -292,11 +290,9 @@ class Atom extends Model
@keymap.destroy()
@windowState = null
# Private:
loadThemes: ->
@themes.load()
# Private:
watchThemes: ->
@themes.on 'reloaded', =>
# Only reload stylesheets from non-theme packages
@@ -309,31 +305,32 @@ class Atom extends Model
# Calling this method without an options parameter will open a prompt to pick
# a file/folder to open in the new window.
#
# * options
# * pathsToOpen: A string array of paths to open
# options - An {Object} with the following keys:
# :pathsToOpen - An {Array} of {String} paths to open.
open: (options) ->
ipc.sendChannel('open', options)
# Public: Open a confirm dialog.
#
# ## Example:
# ```coffeescript
# ## Example
#
# ```coffee
# atom.confirm
# message: 'How you feeling?'
# detailedMessage: 'Be honest.'
# buttons:
# Good: -> window.alert('good to hear')
# Bad: -> window.alert('bummer')
# message: 'How you feeling?'
# detailedMessage: 'Be honest.'
# buttons:
# Good: -> window.alert('good to hear')
# Bad: -> window.alert('bummer')
# ```
#
# * options:
# + message: The string message to display.
# + detailedMessage: The string detailed message to display.
# + buttons: Either an array of strings or an object where the values
# are callbacks to invoke when clicked.
# options - An {Object} with the following keys:
# :message - The {String} message to display.
# :detailedMessage - The {String} detailed message to display.
# :buttons - Either an array of strings or an object where keys are
# button names and the values are callbacks to invoke when
# clicked.
#
# Returns the chosen index if buttons was an array or the return of the
# callback if buttons was an object.
# Returns the chosen button index {Number} if the buttons option was an array.
confirm: ({message, detailedMessage, buttons}={}) ->
buttons ?= {}
if _.isArray(buttons)
@@ -341,6 +338,7 @@ class Atom extends Model
else
buttonLabels = Object.keys(buttons)
dialog = remote.require('dialog')
chosen = dialog.showMessageBox @getCurrentWindow(),
type: 'info'
message: message
@@ -353,42 +351,59 @@ class Atom extends Model
callback = buttons[buttonLabels[chosen]]
callback?()
# Private:
showSaveDialog: (callback) ->
callback(showSaveDialogSync())
# Private:
showSaveDialogSync: (defaultPath) ->
defaultPath ?= @project?.getPath()
currentWindow = @getCurrentWindow()
dialog = remote.require('dialog')
dialog.showSaveDialog currentWindow, {title: 'Save File', defaultPath}
# Public: Open the dev tools for the current window.
openDevTools: ->
@getCurrentWindow().openDevTools()
ipc.sendChannel('call-window-method', 'openDevTools')
# Public: Toggle the visibility of the dev tools for the current window.
toggleDevTools: ->
@getCurrentWindow().toggleDevTools()
ipc.sendChannel('call-window-method', 'toggleDevTools')
# Public: Reload the current window.
reload: ->
@getCurrentWindow().restart()
ipc.sendChannel('call-window-method', 'restart')
# Public: Focus the current window.
focus: ->
@getCurrentWindow().focus()
ipc.sendChannel('call-window-method', 'focus')
$(window).focus()
# Public: Show the current window.
show: ->
@getCurrentWindow().show()
ipc.sendChannel('call-window-method', 'show')
# Public: Hide the current window.
hide: ->
@getCurrentWindow().hide()
ipc.sendChannel('call-window-method', 'hide')
# Private: Schedule the window to be shown and focused on the next tick.
# Public: Set the size of current window.
#
# width - The {Number} of pixels.
# height - The {Number} of pixels.
setSize: (width, height) ->
ipc.sendChannel('call-window-method', 'setSize', width, height)
# Public: Set the position of current window.
#
# x - The {Number} of pixels.
# y - The {Number} of pixels.
setPosition: (x, y) ->
ipc.sendChannel('call-window-method', 'setPosition', x, y)
# Public: Move current window to the center of the screen.
center: ->
ipc.sendChannel('call-window-method', 'center')
# Schedule the window to be shown and focused on the next tick.
#
# This is done in a next tick to prevent a white flicker from occurring
# if called synchronously.
@@ -402,8 +417,9 @@ class Atom extends Model
close: ->
@getCurrentWindow().close()
# Private:
exit: (status) -> app.exit(status)
exit: (status) ->
app = remote.require('app')
app.exit(status)
# Public: Is the current window in development mode?
inDevMode: ->
@@ -419,7 +435,7 @@ class Atom extends Model
# Public: Set the full screen state of the current window.
setFullScreen: (fullScreen=false) ->
@getCurrentWindow().setFullScreen(fullScreen)
ipc.sendChannel('call-window-method', 'setFullScreen', fullScreen)
# Public: Is the current window in full screen mode?
isFullScreen: ->
@@ -450,7 +466,6 @@ class Atom extends Model
getConfigDirPath: ->
@constructor.getConfigDirPath()
# Private:
saveSync: ->
stateString = JSON.stringify(@state)
if statePath = @constructor.getStatePath(@mode)
@@ -468,11 +483,9 @@ class Atom extends Model
getWindowLoadTime: ->
@loadTime
# Private:
crashMainProcess: ->
remote.process.crash()
# Private:
crashRenderProcess: ->
process.crash()
@@ -481,9 +494,12 @@ class Atom extends Model
shell.beep() if @config.get('core.audioBeep')
@workspaceView.trigger 'beep'
# Private:
getUserInitScriptPath: ->
initScriptPath = fs.resolve(@getConfigDirPath(), 'init', ['js', 'coffee'])
initScriptPath ? path.join(@getConfigDirPath(), 'init.coffee')
requireUserInitScript: ->
if userInitScriptPath = fs.resolve(@getConfigDirPath(), 'user', ['js', 'coffee'])
if userInitScriptPath = @getUserInitScriptPath()
try
require userInitScriptPath
catch error
@@ -493,6 +509,9 @@ class Atom extends Model
#
# The globals will be set on the `window` object and removed after the
# require completes.
#
# id - The {String} module name or path.
# globals - An {Object} to set as globals during require (default: {})
requireWithGlobals: (id, globals={}) ->
existingGlobals = {}
for key, value of globals

View File

@@ -3,7 +3,7 @@ ipc = require 'ipc'
Menu = require 'menu'
_ = require 'underscore-plus'
# Private: Used to manage the global application menu.
# Used to manage the global application menu.
#
# It's created by {AtomApplication} upon instantiation and used to add, remove
# and maintain the state of all menu items.
@@ -29,7 +29,7 @@ class ApplicationMenu
@menu = Menu.buildFromTemplate(template)
Menu.setApplicationMenu(@menu)
# Private: Flattens the given menu and submenu items into an single Array.
# Flattens the given menu and submenu items into an single Array.
#
# * menu:
# A complete menu configuration object for atom-shell's menu API.
@@ -42,7 +42,7 @@ class ApplicationMenu
items = items.concat(@flattenMenuItems(item.submenu)) if item.submenu
items
# Private: Flattens the given menu template into an single Array.
# Flattens the given menu template into an single Array.
#
# * template:
# An object describing the menu item.
@@ -64,26 +64,22 @@ class ApplicationMenu
for item in @flattenMenuItems(@menu)
item.enabled = enable if item.metadata?['windowSpecific']
# Private: Replaces VERSION with the current version.
# Replaces VERSION with the current version.
substituteVersion: (template) ->
if (item = _.find(@flattenMenuTemplate(template), (i) -> i.label == 'VERSION'))
item.label = "Version #{@version}"
# Public: Makes the download menu item visible if available.
#
# Note: The update menu item's must match 'Install update' exactly otherwise
# this function will fail to work.
#
# * newVersion:
# FIXME: Unused.
# * quitAndUpdateCallback:
# Function to call when the install menu item has been clicked.
showDownloadUpdateItem: (newVersion, quitAndUpdateCallback) ->
if (item = _.find(@flattenMenuItems(@menu), (i) -> i.label == 'Install update'))
item.visible = true
item.click = quitAndUpdateCallback
# Toggles Install Update Item
showInstallUpdateItem: (visible=true) ->
if (item = _.find(@flattenMenuItems(@menu), (i) -> i.label == 'Restart and Install Update'))
item.visible = visible
# Private: Default list of menu items.
# Toggles Check For Update Item
showCheckForUpdateItem: (visible=true) ->
if (item = _.find(@flattenMenuItems(@menu), (i) -> i.label == 'Check for Update'))
item.visible = visible
# Default list of menu items.
#
# Returns an Array of menu item Objects.
getDefaultTemplate: ->
@@ -97,7 +93,7 @@ class ApplicationMenu
]
]
# Private: Combines a menu template with the appropriate keystroke.
# Combines a menu template with the appropriate keystroke.
#
# * template:
# An Object conforming to atom-shell's menu api but lacking accelerator and
@@ -117,7 +113,7 @@ class ApplicationMenu
@translateTemplate(item.submenu, keystrokesByCommand) if item.submenu
template
# Private: Determine the accelerator for a given command.
# Determine the accelerator for a given command.
#
# * command:
# The name of the command.

View File

@@ -1,6 +1,7 @@
AtomWindow = require './atom-window'
ApplicationMenu = require './application-menu'
AtomProtocolHandler = require './atom-protocol-handler'
BrowserWindow = require 'browser-window'
Menu = require 'menu'
autoUpdater = require 'auto-updater'
app = require 'app'
@@ -21,7 +22,7 @@ socketPath =
else
path.join(os.tmpdir(), 'atom.sock')
# Private: The application's singleton class.
# The application's singleton class.
#
# It's the entry point into the Atom application and maintains the global state
# of the application.
@@ -72,11 +73,11 @@ class AtomApplication
@listenForArgumentsFromNewProcess()
@setupJavaScriptArguments()
@handleEvents()
@checkForUpdates()
@setupAutoUpdater()
@openWithOptions(options)
# Private: Opens a new window based on the options provided.
# Opens a new window based on the options provided.
openWithOptions: ({pathsToOpen, urlsToOpen, test, pidToKillWhenClosed, devMode, newWindow, specDirectory, logFile}) ->
if test
@runSpecs({exitWhenDone: true, @resourcePath, specDirectory, logFile})
@@ -97,7 +98,7 @@ class AtomApplication
@windows.push window
@applicationMenu?.enableWindowSpecificItems(true)
# Private: Creates server to listen for additional atom application launches.
# Creates server to listen for additional atom application launches.
#
# You can run the atom command multiple times, but after the first launch
# the other launches will just pass their information to this server and then
@@ -112,23 +113,60 @@ class AtomApplication
server.listen socketPath
server.on 'error', (error) -> console.error 'Application server failed', error
# Private: Configures required javascript environment flags.
# Configures required javascript environment flags.
setupJavaScriptArguments: ->
app.commandLine.appendSwitch 'js-flags', '--harmony_collections --harmony-proxies'
# Private: Enable updates unless running from a local build of Atom.
checkForUpdates: ->
versionIsSha = /\w{7}/.test @version
# Enable updates unless running from a local build of Atom.
setupAutoUpdater: ->
autoUpdater.setFeedUrl "https://atom.io/api/updates?version=#{@version}"
if versionIsSha
autoUpdater.setAutomaticallyDownloadsUpdates false
autoUpdater.setAutomaticallyChecksForUpdates false
else
autoUpdater.setAutomaticallyDownloadsUpdates true
autoUpdater.setAutomaticallyChecksForUpdates true
autoUpdater.checkForUpdatesInBackground()
autoUpdater.on 'checking-for-update', =>
@applicationMenu.showInstallUpdateItem(false)
@applicationMenu.showCheckForUpdateItem(false)
# Private: Registers basic application commands, non-idempotent.
autoUpdater.on 'update-not-available', =>
@applicationMenu.showInstallUpdateItem(false)
@applicationMenu.showCheckForUpdateItem(true)
autoUpdater.on 'update-downloaded', (event, releaseNotes, releaseName, releaseDate, releaseURL) =>
atomWindow.sendCommand('window:update-available', releaseName) for atomWindow in @windows
@applicationMenu.showInstallUpdateItem(true)
@applicationMenu.showCheckForUpdateItem(false)
@updateVersion = releaseName
autoUpdater.on 'error', (event, message) =>
@applicationMenu.showInstallUpdateItem(false)
@applicationMenu.showCheckForUpdateItem(true)
# Check for update after Atom has fully started and the menus are created
setTimeout((-> autoUpdater.checkForUpdates()), 5000)
checkForUpdate: ->
autoUpdater.once 'update-available', ->
dialog.showMessageBox
type: 'info'
buttons: ['OK']
message: 'Update available.'
detail: 'A new update is being downloading.'
autoUpdater.once 'update-not-available', =>
dialog.showMessageBox
type: 'info'
buttons: ['OK']
message: 'No update available.'
detail: "Version #{@version} is the latest version."
autoUpdater.once 'error', (event, message)->
dialog.showMessageBox
type: 'warning'
buttons: ['OK']
message: 'There was an error checking for updates.'
detail: message
autoUpdater.checkForUpdates()
# Registers basic application commands, non-idempotent.
handleEvents: ->
@on 'application:about', -> Menu.sendActionToFirstResponder('orderFrontStandardAboutPanel:')
@on 'application:run-all-specs', -> @runSpecs(exitWhenDone: false, resourcePath: global.devResourcePath)
@@ -147,9 +185,12 @@ class AtomApplication
@on 'application:inspect', ({x,y}) -> @focusedWindow().browserWindow.inspectElement(x, y)
@on 'application:open-documentation', -> shell.openExternal('https://www.atom.io/docs/latest/?app')
@on 'application:report-issue', -> shell.openExternal('https://github.com/atom/atom/issues/new')
@on 'application:install-update', -> autoUpdater.quitAndInstall()
@on 'application:check-for-update', => @checkForUpdate()
@openPathOnEvent('application:show-settings', 'atom://config')
@openPathOnEvent('application:open-your-config', 'atom://.atom/config')
@openPathOnEvent('application:open-your-init-script', 'atom://.atom/init-script')
@openPathOnEvent('application:open-your-keymap', 'atom://.atom/keymap')
@openPathOnEvent('application:open-your-snippets', 'atom://.atom/snippets')
@openPathOnEvent('application:open-your-stylesheet', 'atom://.atom/stylesheet')
@@ -170,12 +211,6 @@ class AtomApplication
event.preventDefault()
@openUrl({urlToOpen, @devMode})
autoUpdater.on 'ready-for-update-on-quit', (event, version, quitAndUpdateCallback) =>
event.preventDefault()
@updateVersion = version
@applicationMenu.showDownloadUpdateItem(version, quitAndUpdateCallback)
atomWindow.sendCommand('window:update-available', version) for atomWindow in @windows
# A request from the associated render process to open a new render process.
ipc.on 'open', (processId, routingId, options) =>
if options?
@@ -195,6 +230,14 @@ class AtomApplication
ipc.on 'command', (processId, routingId, command) =>
@emit(command)
ipc.on 'window-command', (processId, routingId, command, args...) ->
win = BrowserWindow.fromProcessIdAndRoutingId(processId, routingId)
win.emit(command, args...)
ipc.on 'call-window-method', (processId, routingId, method, args...) ->
win = BrowserWindow.fromProcessIdAndRoutingId(processId, routingId)
win[method](args...)
# Public: Executes the given command.
#
# If it isn't handled globally, delegate to the currently focused window.
@@ -221,7 +264,7 @@ class AtomApplication
else
@openPath({pathToOpen})
# Private: Returns the {AtomWindow} for the given path.
# Returns the {AtomWindow} for the given path.
windowForPath: (pathToOpen) ->
for atomWindow in @windows
return atomWindow if atomWindow.containsPath(pathToOpen)
@@ -309,7 +352,7 @@ class AtomApplication
console.log("Killing process #{pid} failed: #{error.code}")
delete @pidsToOpenWindows[pid]
# Private: Open an atom:// url.
# Open an atom:// url.
#
# The host of the URL being opened is assumed to be the package name
# responsible for opening the URL. A new window will be created with
@@ -341,7 +384,7 @@ class AtomApplication
else
console.log "Opening unknown url: #{urlToOpen}"
# Private: Opens up a new {AtomWindow} to run specs within.
# Opens up a new {AtomWindow} to run specs within.
#
# * options
# + exitWhenDone:
@@ -372,7 +415,7 @@ class AtomApplication
isSpec = true
new AtomWindow({bootstrapScript, @resourcePath, isSpec})
# Private: Opens a native dialog to prompt the user for a path.
# Opens a native dialog to prompt the user for a path.
#
# Once paths are selected, they're opened in a new or existing {AtomWindow}s.
#

View File

@@ -3,7 +3,7 @@ fs = require 'fs-plus'
path = require 'path'
protocol = require 'protocol'
# Private: Handles requests with 'atom' protocol.
# Handles requests with 'atom' protocol.
#
# It's created by {AtomApplication} upon instantiation, and is used to create a
# custom resource loader by adding the 'atom' custom protocol.
@@ -18,7 +18,7 @@ class AtomProtocolHandler
@registerAtomProtocol()
# Private: Creates the 'atom' custom protocol handler.
# Creates the 'atom' custom protocol handler.
registerAtomProtocol: ->
protocol.registerProtocol 'atom', (request) =>
relativePath = path.normalize(request.url.substr(7))

View File

@@ -1,13 +1,14 @@
BrowserWindow = require 'browser-window'
Menu = require 'menu'
ContextMenu = require './context-menu'
app = require 'app'
dialog = require 'dialog'
ipc = require 'ipc'
path = require 'path'
fs = require 'fs'
url = require 'url'
_ = require 'underscore-plus'
# Private:
module.exports =
class AtomWindow
@iconPath: path.resolve(__dirname, '..', '..', 'resources', 'atom.png')
@@ -31,6 +32,7 @@ class AtomWindow
loadSettings = _.extend({}, settings)
loadSettings.windowState ?= '{}'
loadSettings.appVersion = app.getVersion()
# Only send to the first non-spec window created
if @constructor.includeShellLoadTime and not @isSpec
@@ -43,7 +45,7 @@ class AtomWindow
@browserWindow.loadSettings = loadSettings
@browserWindow.once 'window:loaded', => @loaded = true
@browserWindow.loadUrl "file://#{@resourcePath}/static/index.html"
@browserWindow.loadUrl @getUrl(loadSettings)
@browserWindow.focusOnWebView() if @isSpec
@openPath(pathToOpen, initialLine)
@@ -51,6 +53,18 @@ class AtomWindow
setupNodePath: (resourcePath) ->
process.env['NODE_PATH'] = path.resolve(resourcePath, 'exports')
getUrl: (loadSettingsObj) ->
# Ignore the windowState when passing loadSettings via URL, since it could
# be quite large.
loadSettings = _.clone(loadSettingsObj)
delete loadSettings['windowState']
url.format
protocol: 'file'
pathname: "#{@resourcePath}/static/index.html"
slashes: true
query: {loadSettings: JSON.stringify(loadSettings)}
getInitialPath: ->
@browserWindow.loadSettings.initialPath

View File

@@ -1,6 +1,5 @@
Menu = require 'menu'
# Private:
module.exports =
class ContextMenu
constructor: (template, browserWindow) ->
@@ -8,7 +7,7 @@ class ContextMenu
menu = Menu.buildFromTemplate(template)
menu.popup(browserWindow)
# Private: It's necessary to build the event handlers in this process, otherwise
# It's necessary to build the event handlers in this process, otherwise
# closures are drug across processes and failed to be garbage collected
# appropriately.
createClickHandlers: (template) ->

View File

@@ -1,6 +1,5 @@
global.shellStartTime = Date.now()
autoUpdater = require 'auto-updater'
crashReporter = require 'crash-reporter'
app = require 'app'
fs = require 'fs'
@@ -42,7 +41,6 @@ start = ->
app.on 'will-finish-launching', ->
setupCrashReporter()
setupAutoUpdater()
app.on 'finish-launching', ->
app.removeListener 'open-file', addPathToOpen
@@ -66,9 +64,6 @@ global.devResourcePath = path.join(app.getHomeDir(), 'github', 'atom')
setupCrashReporter = ->
crashReporter.start(productName: 'Atom', companyName: 'GitHub')
setupAutoUpdater = ->
autoUpdater.setFeedUrl 'https://speakeasy.githubapp.com/apps/27/appcast.xml'
parseCommandLine = ->
version = app.getVersion()
options = optimist(process.argv[1..])

View File

@@ -12,30 +12,27 @@ class BufferedProcess
process: null
killed: false
# Executes the given executable.
# Public: Executes the given executable.
#
# * options
# + command:
# The path to the executable to execute.
# + args:
# The array of arguments to pass to the script (optional).
# + options:
# The options Object to pass to Node's `ChildProcess.spawn` (optional).
# + stdout:
# The callback that receives a single argument which contains the
# standard output of the script. The callback is called as data is
# received but it's buffered to ensure only complete lines are passed
# until the source stream closes. After the source stream has closed
# all remaining data is sent in a final call (optional).
# + stderr:
# The callback that receives a single argument which contains the
# standard error of the script. The callback is called as data is
# received but it's buffered to ensure only complete lines are passed
# until the source stream closes. After the source stream has closed
# all remaining data is sent in a final call (optional).
# + exit:
# The callback which receives a single argument containing the exit
# status (optional).
# options - An {Object} with the following keys:
# :command - The {String} command to execute.
# :args - The {String}} of arguments to pass to the script (optional).
# :options - The options {Object} to pass to Node's `ChildProcess.spawn`
# (optional).
# :stdout - The callback that receives a single argument which contains the
# standard output of the script. The callback is called as data is
# received but it's buffered to ensure only complete lines are
# passed until the source stream closes. After the source stream
# has closed all remaining data is sent in a final call
# (optional).
# :stderr - The callback that receives a single argument which contains the
# standard error of the script. The callback is called as data is
# received but it's buffered to ensure only complete lines are
# passed until the source stream closes. After the source stream
# has closed all remaining data is sent in a final call
# (optional).
# :exit - The callback which receives a single argument containing the exit
# status (optional).
constructor: ({command, args, options, stdout, stderr, exit}={}) ->
options ?= {}
@process = ChildProcess.spawn(command, args, options)
@@ -68,7 +65,7 @@ class BufferedProcess
processExited = true
triggerExitCallback()
# Private: Helper method to pass data line by line.
# Helper method to pass data line by line.
#
# * stream:
# The Stream to read from.
@@ -93,7 +90,7 @@ class BufferedProcess
onLines(buffered) if buffered.length > 0
onDone()
# Public: Terminates the process.
# Public: Terminate the process.
kill: ->
@killed = true
@process.kill()

49
src/clipboard.coffee Normal file
View File

@@ -0,0 +1,49 @@
clipboard = require 'clipboard'
crypto = require 'crypto'
# Public: Represents the clipboard used for copying and pasting in Atom.
#
# An instance of this class is always available as the `atom.clipboard` global.
module.exports =
class Clipboard
metadata: null
signatureForMetadata: null
# Creates an `md5` hash of some text.
#
# text - A {String} to hash.
#
# Returns a hashed {String}.
md5: (text) ->
crypto.createHash('md5').update(text, 'utf8').digest('hex')
# Public: Write the given text to the clipboard.
#
# The metadata associated with the text is available by calling
# {.readWithMetadata}.
#
# text - The {String} to store.
# metadata - The additional info to associate with the text.
write: (text, metadata) ->
@signatureForMetadata = @md5(text)
@metadata = metadata
clipboard.writeText(text)
# Public: Read the text from the clipboard.
#
# Returns a {String}.
read: ->
clipboard.readText()
# Public: Read the text from the clipboard and return both the text and the
# associated metadata.
#
# Returns an {Object} with the following keys:
# :text - The {String} clipboard text.
# :metadata - The metadata stored by an earlier call to {.write}.
readWithMetadata: ->
text = @read()
if @signatureForMetadata is @md5(text)
{text, @metadata}
else
{text}

View File

@@ -3,52 +3,55 @@ _ = require 'underscore-plus'
async = require 'async'
fs = require 'fs-plus'
mkdirp = require 'mkdirp'
runas = require 'runas'
symlinkCommand = (sourcePath, destinationPath, callback) ->
mkdirp path.dirname(destinationPath), (error) ->
if error?
fs.unlink destinationPath, (error) ->
if error? and error?.code != 'ENOENT'
callback(error)
else
fs.symlink sourcePath, destinationPath, (error) ->
mkdirp path.dirname(destinationPath), (error) ->
if error?
callback(error)
else
fs.chmod(destinationPath, 0o755, callback)
fs.symlink sourcePath, destinationPath, (error) ->
if error?
callback(error)
else
fs.chmod(destinationPath, '755', callback)
unlinkCommand = (destinationPath, callback) ->
fs.unlink destinationPath, (error) ->
if error? and error.code isnt 'ENOENT'
callback(error)
else
callback()
symlinkCommandWithPrivilegeSync = (sourcePath, destinationPath) ->
if runas('/bin/rm', ['-f', destinationPath], admin: true) != 0
throw new Error("Failed to remove '#{destinationPath}'")
if runas('/bin/mkdir', ['-p', path.dirname(destinationPath)], admin: true) != 0
throw new Error("Failed to create directory '#{destinationPath}'")
if runas('/bin/ln', ['-s', sourcePath, destinationPath], admin: true) != 0
throw new Error("Failed to symlink '#{sourcePath}' to '#{destinationPath}'")
module.exports =
getInstallDirectory: ->
"/usr/local/bin"
install: (commandPath, callback) ->
install: (commandPath, askForPrivilege, callback) ->
return unless process.platform is 'darwin'
commandName = path.basename(commandPath, path.extname(commandPath))
directory = @getInstallDirectory()
if fs.existsSync(directory)
destinationPath = path.join(directory, commandName)
unlinkCommand destinationPath, (error) =>
if error?
error = new Error "Could not remove file at #{destinationPath}." if error
callback?(error)
else
symlinkCommand commandPath, destinationPath, (error) =>
error = new Error "Failed to symlink #{commandPath} to #{destinationPath}." if error
callback?(error)
else
error = new Error "Directory '#{directory} doesn't exist."
destinationPath = path.join(@getInstallDirectory(), commandName)
symlinkCommand commandPath, destinationPath, (error) =>
if askForPrivilege and error?.code is 'EACCES'
try
error = null
symlinkCommandWithPrivilegeSync(commandPath, destinationPath)
catch error
callback?(error)
installAtomCommand: (resourcePath, callback) ->
installAtomCommand: (resourcePath, askForPrivilege, callback) ->
commandPath = path.join(resourcePath, 'atom.sh')
@install commandPath, callback
@install commandPath, askForPrivilege, callback
installApmCommand: (resourcePath, callback) ->
installApmCommand: (resourcePath, askForPrivilege, callback) ->
commandPath = path.join(resourcePath, 'apm', 'node_modules', '.bin', 'apm')
@install commandPath, callback
@install commandPath, askForPrivilege, callback

View File

@@ -1,12 +0,0 @@
Mixin = require 'mixto'
module.exports =
class ConfigObserver extends Mixin
observeConfig: (keyPath, args...) ->
@configSubscriptions ?= {}
@configSubscriptions[keyPath] = atom.config.observe(keyPath, args...)
unobserveConfig: ->
if @configSubscriptions?
subscription.off() for keyPath, subscription of @configSubscriptions
@configSubscriptions = null

View File

@@ -8,15 +8,14 @@ pathWatcher = require 'pathwatcher'
# Public: Used to access all of Atom's configuration details.
#
# A global instance of this class is available to all plugins which can be
# referenced using `atom.config`
# An instance of this class is always available as the `atom.config` global.
#
# ### Best practices
# ## Best practices
#
# * Create your own root keypath using your package's name.
# * Don't depend on (or write to) configuration keys outside of your keypath.
#
# ### Example
# ## Example
#
# ```coffeescript
# atom.config.set('myplugin.key', 'value')
@@ -27,18 +26,14 @@ module.exports =
class Config
Emitter.includeInto(this)
defaultSettings: null
settings: null
configFileHasErrors: null
# Private: Created during initialization, available as `global.config`
# Created during initialization, available as `atom.config`
constructor: ({@configDirPath, @resourcePath}={}) ->
@defaultSettings = {}
@settings = {}
@configFileHasErrors = false
@configFilePath = fs.resolve(@configDirPath, 'config', ['json', 'cson'])
@configFilePath ?= path.join(@configDirPath, 'config.cson')
# Private:
initializeConfigDirectory: (done) ->
return if fs.existsSync(@configDirPath)
@@ -55,13 +50,11 @@ class Config
queue.push({sourcePath, destinationPath})
fs.traverseTree(templateConfigDirPath, onConfigDirFile, (path) -> true)
# Private:
load: ->
@initializeConfigDirectory()
@loadUserConfig()
@observeUserConfig()
# Private:
loadUserConfig: ->
unless fs.existsSync(@configFilePath)
fs.makeTreeSync(path.dirname(@configFilePath))
@@ -77,17 +70,14 @@ class Config
console.error "Failed to load user config '#{@configFilePath}'", e.message
console.error e.stack
# Private:
observeUserConfig: ->
@watchSubscription ?= pathWatcher.watch @configFilePath, (eventType) =>
@loadUserConfig() if eventType is 'change' and @watchSubscription?
# Private:
unobserveUserConfig: ->
@watchSubscription?.close()
@watchSubscription = null
# Private:
setDefaults: (keyPath, defaults) ->
keys = keyPath.split('.')
hash = @defaultSettings
@@ -160,6 +150,14 @@ class Config
toggle: (keyPath) ->
@set(keyPath, !@get(keyPath))
# Public: Restore the key path to its default value.
#
# keyPath - The {String} name of the key.
#
# Returns the new value.
restoreDefault: (keyPath) ->
@set(keyPath, _.valueForKeyPath(@defaultSettings, keyPath))
# Public: Push the value to the array at the key path.
#
# keyPath - The {String} key path.
@@ -205,6 +203,9 @@ class Config
# options - An optional {Object} containing the `callNow` key.
# callback - The {Function} that fires when the. It is given a single argument, `value`,
# which is the new value of `keyPath`.
#
# Returns an {Object} with the following keys:
# :off - A {Function} that unobserves the `keyPath` with called.
observe: (keyPath, options={}, callback) ->
if _.isFunction(options)
callback = options
@@ -230,12 +231,10 @@ class Config
unobserve: (keyPath) ->
@off("updated.#{keyPath.replace(/\./, '-')}")
# Private:
update: ->
return if @configFileHasErrors
@save()
@emit 'updated'
# Private:
save: ->
CSON.writeFileSync(@configFilePath, @settings)

View File

@@ -5,10 +5,10 @@ remote = require 'remote'
# Public: Provides a registry for commands that you'd like to appear in the
# context menu.
#
# Should be accessed via `atom.contextMenu`.
# An instance of this class is always available as the `atom.contextMenu`
# global.
module.exports =
class ContextMenuManager
# Private:
constructor: (@devMode=false) ->
@definitions = {}
@devModeDefinitions = {}
@@ -24,11 +24,11 @@ class ContextMenuManager
# Public: Creates menu definitions from the object specified by the menu
# cson API.
#
# * name: The path of the file that contains the menu definitions.
# * object: The 'context-menu' object specified in the menu cson API.
# * options:
# + devMode - Determines whether the entries should only be shown when
# the window is in dev mode.
# name - The path of the file that contains the menu definitions.
# object - The 'context-menu' object specified in the menu cson API.
# options - An {Object} with the following keys:
# :devMode - Determines whether the entries should only be shown when
# the window is in dev mode.
#
# Returns nothing.
add: (name, object, {devMode}={}) ->
@@ -36,20 +36,20 @@ class ContextMenuManager
for label, command of items
@addBySelector(selector, {label, command}, {devMode})
# Private: Registers a command to be displayed when the relevant item is right
# Registers a command to be displayed when the relevant item is right
# clicked.
#
# * selector: The css selector for the active element which should include
# the given command in its context menu.
# * definition: The object containing keys which match the menu template API.
# * options:
# + devMode: Indicates whether this command should only appear while the
# editor is in dev mode.
# selector - The css selector for the active element which should include
# the given command in its context menu.
# definition - The object containing keys which match the menu template API.
# options - An {Object} with the following keys:
# :devMode - Indicates whether this command should only appear while the
# editor is in dev mode.
addBySelector: (selector, definition, {devMode}={}) ->
definitions = if devMode then @devModeDefinitions else @definitions
(definitions[selector] ?= []).push(definition)
# Private: Returns definitions which match the element and devMode.
# Returns definitions which match the element and devMode.
definitionsForElement: (element, {devMode}={}) ->
definitions = if devMode then @devModeDefinitions else @definitions
matchedDefinitions = []
@@ -58,14 +58,14 @@ class ContextMenuManager
matchedDefinitions
# Private: Used to generate the context menu for a specific element and it's
# Used to generate the context menu for a specific element and it's
# parents.
#
# The menu items are sorted such that menu items that match closest to the
# active element are listed first. The further down the list you go, the higher
# up the ancestor hierarchy they match.
#
# * element: The DOM element to generate the menu template for.
# element - The DOM element to generate the menu template for.
menuTemplateForMostSpecificElement: (element, {devMode}={}) ->
menuTemplate = @definitionsForElement(element, {devMode})
if element.parentElement
@@ -73,7 +73,7 @@ class ContextMenuManager
else
menuTemplate
# Private: Returns a menu template for both normal entries as well as
# Returns a menu template for both normal entries as well as
# development mode entries.
combinedMenuTemplateForElement: (element) ->
normalItems = @menuTemplateForMostSpecificElement(element)
@@ -83,7 +83,7 @@ class ContextMenuManager
menuTemplate.push({ type: 'separator' }) if normalItems.length > 0 and devItems.length > 0
menuTemplate.concat(devItems)
# Private: Executes `executeAtBuild` if defined for each menu item with
# Executes `executeAtBuild` if defined for each menu item with
# the provided event and then removes the `executeAtBuild` property from
# the menu item.
#

View File

@@ -1,38 +1,49 @@
{View} = require './space-pen-extensions'
{Point, Range} = require 'text-buffer'
_ = require 'underscore-plus'
### Internal ###
module.exports =
class CursorView extends View
@content: ->
@div class: 'cursor idle', => @raw ' '
blinkPeriod: 800
editorView: null
visible: true
@blinkPeriod: 800
@blinkCursors: ->
element.classList.toggle('blink-off') for [element] in @cursorViews
@startBlinking: (cursorView) ->
@cursorViews ?= []
@cursorViews.push(cursorView)
if @cursorViews.length is 1
@blinkInterval = setInterval(@blinkCursors.bind(this), @blinkPeriod / 2)
@stopBlinking: (cursorView) ->
cursorView[0].classList.remove('blink-off')
_.remove(@cursorViews, cursorView)
clearInterval(@blinkInterval) if @cursorViews.length is 0
blinking: false
visible: true
needsUpdate: true
needsRemoval: false
shouldPauseBlinking: false
initialize: (@cursor, @editorView) ->
@cursor.on 'moved.cursor-view', =>
@subscribe @cursor, 'moved', =>
@needsUpdate = true
@shouldPauseBlinking = true
@cursor.on 'visibility-changed.cursor-view', (visible) =>
@subscribe @cursor, 'visibility-changed', =>
@needsUpdate = true
@cursor.on 'autoscrolled.cursor-view', =>
@subscribe @cursor, 'autoscrolled', =>
@editorView.requestDisplayUpdate()
@cursor.on 'destroyed.cursor-view', =>
@subscribe @cursor, 'destroyed', =>
@needsRemoval = true
beforeRemove: ->
@editorView.removeCursorView(this)
@cursor.off('.cursor-view')
@stopBlinking()
updateDisplay: ->
@@ -53,11 +64,7 @@ class CursorView extends View
# Override for speed. The base function checks the computedStyle
isHidden: ->
style = this[0].style
if style.display == 'none' or not @isOnDom()
true
else
false
this[0].style.display is 'none' or not @isOnDom()
needsAutoscroll: ->
@cursor.needsAutoscroll
@@ -69,19 +76,17 @@ class CursorView extends View
@editorView.pixelPositionForScreenPosition(@getScreenPosition())
setVisible: (visible) ->
unless @visible == visible
unless @visible is visible
@visible = visible
@toggle(@visible)
stopBlinking: ->
clearInterval(@blinkInterval) if @blinkInterval
@blinkInterval = null
this[0].classList.remove('blink-off')
@constructor.stopBlinking(this) if @blinking
@blinking = false
startBlinking: ->
return if @blinkInterval?
blink = => @toggleClass('blink-off')
@blinkInterval = setInterval(blink, @blinkPeriod / 2)
@constructor.startBlinking(this) unless @blinking
@blinking = true
resetBlinking: ->
@stopBlinking()

View File

@@ -17,7 +17,7 @@ class Cursor
visible: true
needsAutoscroll: null
# Private: Instantiated by an {Editor}
# Instantiated by an {Editor}
constructor: ({@editor, @marker}) ->
@updateVisibility()
@marker.on 'changed', (e) =>
@@ -45,11 +45,9 @@ class Cursor
@emit 'destroyed'
@needsAutoscroll = true
# Private:
destroy: ->
@marker.destroy()
# Private:
changePosition: (options, fn) ->
@clearSelection()
@needsAutoscroll = options.autoscroll ? @isLastCursor()
@@ -58,12 +56,11 @@ class Cursor
# Public: Moves a cursor to a given screen position.
#
# * screenPosition:
# An {Array} of two numbers: the screen row, and the screen column.
# * options:
# + autoscroll:
# A Boolean which, if `true`, scrolls the {Editor} to wherever the
# cursor moves to.
# screenPosition - An {Array} of two numbers: the screen row, and the screen
# column.
# options - An {Object} with the following keys:
# :autoscroll - A Boolean which, if `true`, scrolls the {Editor} to wherever
# the cursor moves to.
setScreenPosition: (screenPosition, options={}) ->
@changePosition options, =>
@marker.setHeadScreenPosition(screenPosition, options)
@@ -74,12 +71,11 @@ class Cursor
# Public: Moves a cursor to a given buffer position.
#
# * bufferPosition:
# An {Array} of two numbers: the buffer row, and the buffer column.
# * options:
# + autoscroll:
# A Boolean which, if `true`, scrolls the {Editor} to wherever the
# cursor moves to.
# bufferPosition - An {Array} of two numbers: the buffer row, and the buffer
# column.
# options - An {Object} with the following keys:
# :autoscroll - A Boolean which, if `true`, scrolls the {Editor} to wherever
# the cursor moves to.
setBufferPosition: (bufferPosition, options={}) ->
@changePosition options, =>
@marker.setHeadBufferPosition(bufferPosition, options)
@@ -104,11 +100,11 @@ class Cursor
# Public: Get the RegExp used by the cursor to determine what a "word" is.
#
# * options:
# + includeNonWordCharacters:
# A Boolean indicating whether to include non-word characters in the regex.
# options: An {Object} with the following keys:
# :includeNonWordCharacters - A {Boolean} indicating whether to include
# non-word characters in the regex.
#
# Returns a RegExp.
# Returns a {RegExp}.
wordRegExp: ({includeNonWordCharacters}={})->
includeNonWordCharacters ?= true
nonWordCharacters = atom.config.get('editor.nonWordCharacters')
@@ -122,7 +118,7 @@ class Cursor
#
# "Last" is defined as the most recently added cursor.
#
# Returns a Boolean.
# Returns a {Boolean}.
isLastCursor: ->
this == @editor.getCursor()
@@ -131,7 +127,7 @@ class Cursor
# "Surrounded" here means that all characters before and after the cursor is
# whitespace.
#
# Returns a Boolean.
# Returns a {Boolean}.
isSurroundedByWhitespace: ->
{row, column} = @getBufferPosition()
range = [[row, Math.min(0, column - 1)], [row, Math.max(0, column + 1)]]
@@ -217,9 +213,9 @@ class Cursor
# Public: Moves the cursor left one screen column.
#
# * options:
# + moveToEndOfSelection:
# if true, move to the left of the selection if a selection exists.
# options - An {Object} with the following keys:
# :moveToEndOfSelection - if true, move to the left of the selection if a
# selection exists.
moveLeft: ({moveToEndOfSelection}={}) ->
range = @marker.getScreenRange()
if moveToEndOfSelection and not range.isEmpty()
@@ -231,9 +227,9 @@ class Cursor
# Public: Moves the cursor right one screen column.
#
# * options:
# + moveToEndOfSelection:
# if true, move to the right of the selection if a selection exists.
# options - An {Object} with the following keys:
# :moveToEndOfSelection - if true, move to the right of the selection if a
# selection exists.
moveRight: ({moveToEndOfSelection}={}) ->
range = @marker.getScreenRange()
if moveToEndOfSelection and not range.isEmpty()
@@ -313,12 +309,12 @@ class Cursor
# Public: Retrieves the buffer position of where the current word starts.
#
# * options:
# + wordRegex:
# A RegExp indicating what constitutes a "word" (default: {.wordRegExp})
# + includeNonWordCharacters:
# A Boolean indicating whether to include non-word characters in the
# default word regex. Has no effect if wordRegex is set.
# options - An {Object} with the following keys:
# :wordRegex - A {RegExp} indicating what constitutes a "word"
# (default: {.wordRegExp}).
# :includeNonWordCharacters - A {Boolean} indicating whether to include
# non-word characters in the default word regex.
# Has no effect if wordRegex is set.
#
# Returns a {Range}.
getBeginningOfCurrentWordBufferPosition: (options = {}) ->
@@ -381,12 +377,12 @@ class Cursor
# Public: Retrieves the buffer position of where the current word ends.
#
# * options:
# + wordRegex:
# A RegExp indicating what constitutes a "word" (default: {.wordRegExp})
# + includeNonWordCharacters:
# A Boolean indicating whether to include non-word characters in the
# default word regex. Has no effect if wordRegex is set.
# options - An {Object} with the following keys:
# :wordRegex - A {RegExp} indicating what constitutes a "word"
# (default: {.wordRegExp})
# :includeNonWordCharacters - A Boolean indicating whether to include
# non-word characters in the default word regex.
# Has no effect if wordRegex is set.
#
# Returns a {Range}.
getEndOfCurrentWordBufferPosition: (options = {}) ->
@@ -405,9 +401,9 @@ class Cursor
# Public: Retrieves the buffer position of where the next word starts.
#
# * options:
# + wordRegex:
# A RegExp indicating what constitutes a "word" (default: {.wordRegExp})
# options -
# :wordRegex - A {RegExp} indicating what constitutes a "word"
# (default: {.wordRegExp}).
#
# Returns a {Range}.
getBeginningOfNextWordBufferPosition: (options = {}) ->
@@ -424,9 +420,9 @@ class Cursor
# Public: Returns the buffer Range occupied by the word located under the cursor.
#
# * options:
# + wordRegex:
# A RegExp indicating what constitutes a "word" (default: {.wordRegExp})
# options -
# :wordRegex - A {RegExp} indicating what constitutes a "word"
# (default: {.wordRegExp}).
getCurrentWordBufferRange: (options={}) ->
startOptions = _.extend(_.clone(options), allowPrevious: false)
endOptions = _.extend(_.clone(options), allowNext: false)
@@ -434,9 +430,9 @@ class Cursor
# Public: Returns the buffer Range for the current line.
#
# * options:
# + includeNewline:
# A boolean which controls whether the Range should include the newline.
# options -
# :includeNewline: - A {Boolean} which controls whether the Range should
# include the newline.
getCurrentLineBufferRange: (options) ->
@editor.bufferRangeForBufferRow(@getBufferRow(), options)

View File

@@ -1,25 +1,39 @@
# Public: Manages the deserializers used for serialized state
#
# Should be accessed via `atom.deserializers`
# An instance of this class is always available as the `atom.deserializers`
# global.
#
# ### Registering a deserializer
#
# ```coffee
# class MyPackageView extends View
# atom.deserializers.add(this)
#
# @deserialize: (state) ->
# new MyPackageView(state)
# ```
module.exports =
class DeserializerManager
constructor: (@environment) ->
constructor: ->
@deserializers = {}
@deferredDeserializers = {}
# Public: Register the given class(es) as deserializers.
add: (klasses...) ->
@deserializers[klass.name] = klass for klass in klasses
# Public: Add a deferred deserializer for the given class name.
addDeferred: (name, fn) ->
@deferredDeserializers[name] = fn
#
# classes - One or more classes to register.
add: (classes...) ->
@deserializers[klass.name] = klass for klass in classes
# Public: Remove the given class(es) as deserializers.
remove: (klasses...) ->
delete @deserializers[klass.name] for klass in klasses
#
# classes - One or more classes to remove.
remove: (classes...) ->
delete @deserializers[name] for {name} in classes
# Public: Deserialize the state and params.
#
# state - The state {Object} to deserialize.
# params - The params {Object} to pass as the second arguments to the
# deserialize method of the deserializer.
deserialize: (state, params) ->
return unless state?
@@ -30,13 +44,11 @@ class DeserializerManager
else
console.warn "No deserializer found for", state
# Private: Get the deserializer for the state.
# Get the deserializer for the state.
#
# state - The state {Object} being deserialized.
get: (state) ->
return unless state?
name = state.get?('deserializer') ? state.deserializer
if @deferredDeserializers[name]
@deferredDeserializers[name]()
delete @deferredDeserializers[name]
@deserializers[name]

View File

@@ -7,7 +7,7 @@ pathWatcher = require 'pathwatcher'
File = require './file'
# Public: Represents a directory using {File}s.
# Public: Represents a directory on disk.
#
# ## Requiring in packages
#
@@ -18,15 +18,12 @@ module.exports =
class Directory
Emitter.includeInto(this)
path: null
realPath: null
# Public: Configures a new Directory instance, no files are accessed.
#
# * path:
# A String containing the absolute path to the directory.
# + symlink:
# A Boolean indicating if the path is a symlink (defaults to false).
# path - A {String} containing the absolute path to the directory.
# symlink - A {Boolean} indicating if the path is a symlink (default: false).
constructor: (@path, @symlink=false) ->
@on 'first-contents-changed-subscription-will-be-added', =>
# Triggered by emissary, when a new contents-changed listener attaches
@@ -36,7 +33,7 @@ class Directory
# Triggered by emissary, when the last contents-changed listener detaches
@unsubscribeFromNativeChangeEvents()
# Public: Returns the basename of the directory.
# Public: Returns the {String} basename of the directory.
getBaseName: ->
path.basename(@path)
@@ -108,8 +105,8 @@ class Directory
# Public: Reads file entries in this directory from disk asynchronously.
#
# * callback: A function to call with an Error as the first argument and
# an {Array} of {File} and {Directory} objects as the second argument.
# callback - A {Function} to call with an {Error} as the 1st argument and
# an {Array} of {File} and {Directory} objects as the 2nd argument.
getEntries: (callback) ->
fs.list @path, (error, entries) ->
return callback(error) if error?
@@ -134,18 +131,16 @@ class Directory
async.eachLimit entries, 1, statEntry, ->
callback(null, directories.concat(files))
# Private:
subscribeToNativeChangeEvents: ->
unless @watchSubscription?
@watchSubscription = pathWatcher.watch @path, (eventType) =>
@emit "contents-changed" if eventType is "change"
# Private:
unsubscribeFromNativeChangeEvents: ->
if @watchSubscription?
@watchSubscription.close()
@watchSubscription = null
# Private: Does given full path start with the given prefix?
# Does given full path start with the given prefix?
isPathPrefixOf: (prefix, fullPath) ->
fullPath.indexOf(prefix) is 0 and fullPath[prefix.length] is path.sep

View File

@@ -2,7 +2,6 @@
_ = require 'underscore-plus'
{Emitter, Subscriber} = require 'emissary'
# Private:
module.exports =
class DisplayBufferMarker
Emitter.includeInto(this)
@@ -15,8 +14,6 @@ class DisplayBufferMarker
oldTailScreenPosition: null
wasValid: true
### Internal ###
constructor: ({@bufferMarker, @displayBuffer}) ->
@id = @bufferMarker.id
@oldHeadBufferPosition = @getHeadBufferPosition()
@@ -28,8 +25,6 @@ class DisplayBufferMarker
@subscribe @bufferMarker, 'destroyed', => @destroyed()
@subscribe @bufferMarker, 'changed', (event) => @notifyObservers(event)
### Public ###
copy: (attributes) ->
@displayBuffer.getMarker(@bufferMarker.copy(attributes).id)
@@ -170,8 +165,6 @@ class DisplayBufferMarker
inspect: ->
"DisplayBufferMarker(id: #{@id}, bufferRange: #{@getBufferRange()})"
### Internal ###
destroyed: ->
delete @displayBuffer.markers[@id]
@emit 'destroyed'

View File

@@ -1,5 +1,5 @@
_ = require 'underscore-plus'
{Emitter, Subscriber} = require 'emissary'
{Emitter} = require 'emissary'
guid = require 'guid'
Serializable = require 'serializable'
{Model} = require 'theorist'
@@ -9,13 +9,10 @@ RowMap = require './row-map'
Fold = require './fold'
Token = require './token'
DisplayBufferMarker = require './display-buffer-marker'
ConfigObserver = require './config-observer'
# Private:
module.exports =
class DisplayBuffer extends Model
Serializable.includeInto(this)
ConfigObserver.includeInto(this)
@properties
softWrap: null
@@ -39,10 +36,10 @@ class DisplayBuffer extends Model
@emit 'soft-wrap-changed', softWrap
@updateWrappedScreenLines()
@observeConfig 'editor.preferredLineLength', callNow: false, =>
@subscribe atom.config.observe 'editor.preferredLineLength', callNow: false, =>
@updateWrappedScreenLines() if @softWrap and atom.config.get('editor.softWrapAtPreferredLineLength')
@observeConfig 'editor.softWrapAtPreferredLineLength', callNow: false, =>
@subscribe atom.config.observe 'editor.softWrapAtPreferredLineLength', callNow: false, =>
@updateWrappedScreenLines() if @softWrap
serializeParams: ->
@@ -82,8 +79,6 @@ class DisplayBuffer extends Model
bufferDelta = 0
@emitChanged({ start, end, screenDelta, bufferDelta })
### Public ###
# Sets the visibility of the tokenized buffer.
#
# visible - A {Boolean} indicating of the tokenized buffer is shown
@@ -419,8 +414,6 @@ class DisplayBuffer extends Model
column = screenLine.clipScreenColumn(column, options)
new Point(row, column)
### Public ###
# Given a line, finds the point where it would wrap.
#
# line - The {String} to check
@@ -470,7 +463,7 @@ class DisplayBuffer extends Model
getMarkerCount: ->
@buffer.getMarkerCount()
# Constructs a new marker at the given screen range.
# Public: Constructs a new marker at the given screen range.
#
# range - The marker {Range} (representing the distance between the head and tail)
# options - Options to pass to the {Marker} constructor
@@ -480,7 +473,7 @@ class DisplayBuffer extends Model
bufferRange = @bufferRangeForScreenRange(args.shift())
@markBufferRange(bufferRange, args...)
# Constructs a new marker at the given buffer range.
# Public: Constructs a new marker at the given buffer range.
#
# range - The marker {Range} (representing the distance between the head and tail)
# options - Options to pass to the {Marker} constructor
@@ -489,7 +482,7 @@ class DisplayBuffer extends Model
markBufferRange: (args...) ->
@getMarker(@buffer.markRange(args...).id)
# Constructs a new marker at the given screen position.
# Public: Constructs a new marker at the given screen position.
#
# range - The marker {Range} (representing the distance between the head and tail)
# options - Options to pass to the {Marker} constructor
@@ -498,7 +491,7 @@ class DisplayBuffer extends Model
markScreenPosition: (screenPosition, options) ->
@markBufferPosition(@bufferPositionForScreenPosition(screenPosition), options)
# Constructs a new marker at the given buffer position.
# Public: Constructs a new marker at the given buffer position.
#
# range - The marker {Range} (representing the distance between the head and tail)
# options - Options to pass to the {Marker} constructor
@@ -507,7 +500,7 @@ class DisplayBuffer extends Model
markBufferPosition: (bufferPosition, options) ->
@getMarker(@buffer.markPosition(bufferPosition, options).id)
# Removes the marker with the given id.
# Public: Removes the marker with the given id.
#
# id - The {Number} of the ID to remove
destroyMarker: (id) ->
@@ -573,15 +566,12 @@ class DisplayBuffer extends Model
marker.unsubscribe() for marker in @getMarkers()
@tokenizedBuffer.destroy()
@unsubscribe()
@unobserveConfig()
logLines: (start=0, end=@getLastRow())->
for row in [start..end]
line = @lineForRow(row).text
console.log row, @bufferRowForScreenRow(row), line, line.length
### Internal ###
handleTokenizedBufferChange: (tokenizedBufferChange) =>
{start, end, delta, bufferChange} = tokenizedBufferChange
@updateScreenLines(start, end + 1, delta, delayChangeEvent: bufferChange?)

View File

@@ -26,6 +26,7 @@ module.exports =
class EditorView extends View
@characterWidthCache: {}
@configDefaults:
fontFamily: ''
fontSize: 20
showInvisibles: false
showIndentGuide: false
@@ -41,8 +42,6 @@ class EditorView extends View
@nextEditorId: 1
### Internal ###
@content: (params) ->
attributes = { class: @classes(params), tabindex: -1 }
_.extend(attributes, params.attributes) if params.attributes
@@ -79,14 +78,12 @@ class EditorView extends View
redrawOnReattach: false
bottomPaddingInLines: 10
### Public ###
# The constructor for setting up an `EditorView` instance.
#
# editorOrOptions - Either an {Editor}, or an object with one property, `mini`.
# If `mini` is `true`, a "miniature" `Editor` 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._.
# If `mini` is `true`, a "miniature" `Editor` 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._.
#
initialize: (editorOrOptions) ->
if editorOrOptions instanceof Editor
@@ -120,7 +117,7 @@ class EditorView extends View
else
throw new Error("Must supply an Editor or mini: true")
# Internal: Sets up the core Atom commands.
# Sets up the core Atom commands.
#
# Some commands are excluded from mini-editors.
bindKeys: ->
@@ -209,7 +206,7 @@ class EditorView extends View
'editor:toggle-line-comments': => @toggleLineCommentsInSelection()
'editor:log-cursor-scope': => @logCursorScope()
'editor:checkout-head-revision': => @checkoutHead()
'editor:copy-path': => @copyPathToPasteboard()
'editor:copy-path': => @copyPathToClipboard()
'editor:move-line-up': => @editor.moveLineUp()
'editor:move-line-down': => @editor.moveLineDown()
'editor:duplicate-line': => @editor.duplicateLine()
@@ -223,6 +220,9 @@ class EditorView extends View
do (name, method) =>
@command name, (e) -> method(e); false
# Public: Get the underlying editor model for this view.
#
# Returns an {Editor}.
getEditor: ->
@editor
@@ -238,7 +238,6 @@ class EditorView extends View
insertText: (text, options) ->
@editor.insertText(text, options)
# Private:
setHeightInLines: (heightInLines)->
heightInLines ?= @calculateHeightInLines()
@heightInLines = heightInLines if heightInLines
@@ -248,39 +247,41 @@ class EditorView extends View
widthInChars ?= @calculateWidthInChars()
@editor.setEditorWidthInChars(widthInChars) if widthInChars
# Public: Emulates the "page down" key, where the last row of a buffer scrolls to become the first.
# Public: Emulates the "page down" key, where the last row of a buffer scrolls
# to become the first.
pageDown: ->
newScrollTop = @scrollTop() + @scrollView[0].clientHeight
@editor.moveCursorDown(@getPageRows())
@scrollTop(newScrollTop, adjustVerticalScrollbar: true)
# Public: Emulates the "page up" key, where the frst row of a buffer scrolls to become the last.
# Public: Emulates the "page up" key, where the frst row of a buffer scrolls
# to become the last.
pageUp: ->
newScrollTop = @scrollTop() - @scrollView[0].clientHeight
@editor.moveCursorUp(@getPageRows())
@scrollTop(newScrollTop, adjustVerticalScrollbar: true)
# Gets the number of actual page rows existing in an editor.
# Public: Gets the number of actual page rows existing in an editor.
#
# Returns a {Number}.
getPageRows: ->
Math.max(1, Math.ceil(@scrollView[0].clientHeight / @lineHeight))
# Set whether invisible characters are shown.
# Public: Set whether invisible characters are shown.
#
# showInvisibles - A {Boolean} which, if `true`, show invisible characters
# showInvisibles - A {Boolean} which, if `true`, show invisible characters.
setShowInvisibles: (showInvisibles) ->
return if showInvisibles == @showInvisibles
@showInvisibles = showInvisibles
@resetDisplay()
# Defines which characters are invisible.
# Public: Defines which characters are invisible.
#
# invisibles - A hash defining the invisible characters: The defaults are:
# eol: `\u00ac`
# space: `\u00b7`
# tab: `\u00bb`
# cr: `\u00a4`
# invisibles - An {Object} defining the invisible characters:
# :eol - The end of line invisible {String} (default: `\u00ac`).
# :space - The space invisible {String} (default: `\u00b7`).
# :tab - The tab invisible {String} (default: `\u00bb`).
# :cr - The carriage return invisible {String} (default: `\u00a4`).
setInvisibles: (@invisibles={}) ->
_.defaults @invisibles,
eol: '\u00ac'
@@ -289,14 +290,20 @@ class EditorView extends View
cr: '\u00a4'
@resetDisplay()
# Sets whether you want to show the indentation guides.
# Public: Sets whether you want to show the indentation guides.
#
# showIndentGuide - A {Boolean} you can set to `true` if you want to see the indentation guides.
# showIndentGuide - A {Boolean} you can set to `true` if you want to see the
# indentation guides.
setShowIndentGuide: (showIndentGuide) ->
return if showIndentGuide == @showIndentGuide
@showIndentGuide = showIndentGuide
@resetDisplay()
# Public: Set the text to appear in the editor when it is empty.
#
# This only affects mini editors.
#
# placeholderText - A {String} of text to display when empty.
setPlaceholderText: (placeholderText) ->
return unless @mini
@placeholderText = placeholderText
@@ -310,15 +317,13 @@ class EditorView extends View
if path = @editor.getPath()
atom.project.getRepo()?.checkoutHead(path)
### Internal ###
configure: ->
@observeConfig 'editor.showLineNumbers', (showLineNumbers) => @gutter.setShowLineNumbers(showLineNumbers)
@observeConfig 'editor.showInvisibles', (showInvisibles) => @setShowInvisibles(showInvisibles)
@observeConfig 'editor.showIndentGuide', (showIndentGuide) => @setShowIndentGuide(showIndentGuide)
@observeConfig 'editor.invisibles', (invisibles) => @setInvisibles(invisibles)
@observeConfig 'editor.fontSize', (fontSize) => @setFontSize(fontSize)
@observeConfig 'editor.fontFamily', (fontFamily) => @setFontFamily(fontFamily)
@subscribe atom.config.observe 'editor.showLineNumbers', (showLineNumbers) => @gutter.setShowLineNumbers(showLineNumbers)
@subscribe atom.config.observe 'editor.showInvisibles', (showInvisibles) => @setShowInvisibles(showInvisibles)
@subscribe atom.config.observe 'editor.showIndentGuide', (showIndentGuide) => @setShowIndentGuide(showIndentGuide)
@subscribe atom.config.observe 'editor.invisibles', (invisibles) => @setInvisibles(invisibles)
@subscribe atom.config.observe 'editor.fontSize', (fontSize) => @setFontSize(fontSize)
@subscribe atom.config.observe 'editor.fontFamily', (fontFamily) => @setFontFamily(fontFamily)
handleEvents: ->
@on 'focus', =>
@@ -488,12 +493,11 @@ class EditorView extends View
@trigger 'editor:attached', [this]
# TODO: This should be private and only called from the constructor
edit: (editor) ->
return if editor is @editor
if @editor
@saveScrollPositionForeditor()
@saveScrollPositionForEditor()
@editor.off(".editor")
@editor = editor
@@ -590,19 +594,18 @@ class EditorView extends View
else
@scrollView.scrollRight()
### Public ###
# Scrolls the editor to the bottom.
# Public: Scrolls the editor to the bottom.
scrollToBottom: ->
@scrollBottom(@editor.getScreenLineCount() * @lineHeight)
# Scrolls the editor to the position of the most recently added cursor.
# Public: Scrolls the editor to the position of the most recently added
# cursor.
#
# The editor is also centered.
scrollToCursorPosition: ->
@scrollToBufferPosition(@editor.getCursorBufferPosition(), center: true)
# Scrolls the editor to the given buffer position.
# Public: Scrolls the editor to the given buffer position.
#
# bufferPosition - An object that represents a buffer position. It can be either
# an {Object} (`{row, column}`), {Array} (`[row, column]`), or {Point}
@@ -610,7 +613,7 @@ class EditorView extends View
scrollToBufferPosition: (bufferPosition, options) ->
@scrollToPixelPosition(@pixelPositionForBufferPosition(bufferPosition), options)
# Scrolls the editor to the given screen position.
# Public: Scrolls the editor to the given screen position.
#
# screenPosition - An object that represents a buffer position. It can be either
# an {Object} (`{row, column}`), {Array} (`[row, column]`), or {Point}
@@ -618,18 +621,20 @@ class EditorView extends View
scrollToScreenPosition: (screenPosition, options) ->
@scrollToPixelPosition(@pixelPositionForScreenPosition(screenPosition), options)
# Scrolls the editor to the given pixel position.
# Public: Scrolls the editor to the given pixel position.
#
# pixelPosition - An object that represents a pixel position. It can be either
# an {Object} (`{row, column}`), {Array} (`[row, column]`), or {Point}
# an {Object} (`{row, column}`), {Array} (`[row, column]`), or
# {Point}.
# options - A hash with the following keys:
# center: if `true`, the position is scrolled such that it's in the center of the editor
# :center - if `true`, the position is scrolled such that it's in
# the center of the editor
scrollToPixelPosition: (pixelPosition, options) ->
return unless @attached
@scrollVertically(pixelPosition, options)
@scrollHorizontally(pixelPosition)
# Highlight all the folds within the given buffer range.
# Public: Highlight all the folds within the given buffer range.
#
# "Highlighting" essentially just adds the `fold-selected` class to the line's
# DOM element.
@@ -647,29 +652,27 @@ class EditorView extends View
else
element.removeClass('fold-selected')
saveScrollPositionForeditor: ->
saveScrollPositionForEditor: ->
if @attached
@editor.setScrollTop(@scrollTop())
@editor.setScrollLeft(@scrollLeft())
# Toggle soft tabs on the edit session.
# Public: Toggle soft tabs on the edit session.
toggleSoftTabs: ->
@editor.setSoftTabs(not @editor.getSoftTabs())
# Toggle soft wrap on the edit session.
# Public: Toggle soft wrap on the edit session.
toggleSoftWrap: ->
@setWidthInChars()
@editor.setSoftWrap(not @editor.getSoftWrap())
# Private:
calculateWidthInChars: ->
Math.floor(@scrollView.width() / @charWidth)
# Private:
calculateHeightInLines: ->
Math.ceil($(window).height() / @lineHeight)
# Enables/disables soft wrap on the editor.
# Public: Enables/disables soft wrap on the editor.
#
# softWrap - A {Boolean} which, if `true`, enables soft wrap
setSoftWrap: (softWrap) ->
@@ -679,7 +682,7 @@ class EditorView extends View
else
@removeClass 'soft-wrap'
# Sets the font size for the editor.
# Public: Sets the font size for the editor.
#
# fontSize - A {Number} indicating the font size in pixels.
setFontSize: (fontSize) ->
@@ -692,15 +695,15 @@ class EditorView extends View
else
@redrawOnReattach = @attached
# Retrieves the font size for the editor.
# Public: Retrieves the font size for the editor.
#
# Returns a {Number} indicating the font size in pixels.
getFontSize: ->
parseInt(@css("font-size"))
# Sets the font family for the editor.
# Public: Sets the font family for the editor.
#
# fontFamily - A {String} identifying the CSS `font-family`,
# fontFamily - A {String} identifying the CSS `font-family`.
setFontFamily: (fontFamily='') ->
@css('font-family', fontFamily)
@@ -708,12 +711,12 @@ class EditorView extends View
@redraw()
# Gets the font family for the editor.
# Public: Gets the font family for the editor.
#
# Returns a {String} identifying the CSS `font-family`,
# Returns a {String} identifying the CSS `font-family`.
getFontFamily: -> @css("font-family")
# Redraw the editor
# Public: Redraw the editor
redraw: ->
return unless @hasParent()
return unless @attached
@@ -723,23 +726,27 @@ class EditorView extends View
@updateLayerDimensions()
@requestDisplayUpdate()
# Public: Split the editor view left.
splitLeft: ->
pane = @getPane()
pane?.splitLeft(pane?.copyActiveItem()).activeView
# Public: Split the editor view right.
splitRight: ->
pane = @getPane()
pane?.splitRight(pane?.copyActiveItem()).activeView
# Public: Split the editor view up.
splitUp: ->
pane = @getPane()
pane?.splitUp(pane?.copyActiveItem()).activeView
# Public: Split the editor view down.
splitDown: ->
pane = @getPane()
pane?.splitDown(pane?.copyActiveItem()).activeView
# Retrieve's the `EditorView`'s pane.
# Public: Get this view's pane.
#
# Returns a {Pane}.
getPane: ->
@@ -750,7 +757,6 @@ class EditorView extends View
super
atom.workspaceView?.focus()
# Private:
beforeRemove: ->
@trigger 'editor:will-be-removed'
@removed = true
@@ -797,8 +803,6 @@ class EditorView extends View
appendToLinesView: (view) ->
@overlayer.append(view)
### Internal ###
# Scrolls the editor vertically to a given position.
scrollVertically: (pixelPosition, {center}={}) ->
scrollViewHeight = @scrollView.height()
@@ -835,7 +839,7 @@ class EditorView extends View
@scrollRight(desiredRight)
else if desiredLeft < @scrollLeft()
@scrollLeft(desiredLeft)
@saveScrollPositionForeditor()
@saveScrollPositionForEditor()
calculateDimensions: ->
fragment = $('<div class="line" style="position: absolute; visibility: hidden;"><span>x</span></div>')
@@ -1135,9 +1139,8 @@ class EditorView extends View
@renderedLines.css('padding-bottom', paddingBottom)
@gutter.lineNumbers.css('padding-bottom', paddingBottom)
### Public ###
# Retrieves the number of the row that is visible and currently at the top of the editor.
# Public: Retrieves the number of the row that is visible and currently at the
# top of the editor.
#
# Returns a {Number}.
getFirstVisibleScreenRow: ->
@@ -1145,7 +1148,8 @@ class EditorView extends View
screenRow = 0 if isNaN(screenRow)
screenRow
# Retrieves the number of the row that is visible and currently at the bottom of the editor.
# Public: Retrieves the number of the row that is visible and currently at the
# bottom of the editor.
#
# Returns a {Number}.
getLastVisibleScreenRow: ->
@@ -1154,7 +1158,7 @@ class EditorView extends View
screenRow = 0 if isNaN(screenRow)
screenRow
# Given a row number, identifies if it is currently visible.
# Public: Given a row number, identifies if it is currently visible.
#
# row - A row {Number} to check
#
@@ -1162,8 +1166,6 @@ class EditorView extends View
isScreenRowVisible: (row) ->
@getFirstVisibleScreenRow() <= row <= @getLastVisibleScreenRow()
### Internal ###
handleScreenLinesChange: (change) ->
@pendingChanges.push(change)
@requestDisplayUpdate()
@@ -1246,21 +1248,19 @@ class EditorView extends View
toggleLineCommentsInSelection: ->
@editor.toggleLineCommentsInSelection()
### Public ###
# Converts a buffer position to a pixel position.
# Public: Converts a buffer position to a pixel position.
#
# position - An object that represents a buffer position. It can be either
# an {Object} (`{row, column}`), {Array} (`[row, column]`), or {Point}
# an {Object} (`{row, column}`), {Array} (`[row, column]`), or {Point}
#
# Returns an object with two values: `top` and `left`, representing the pixel positions.
pixelPositionForBufferPosition: (position) ->
@pixelPositionForScreenPosition(@editor.screenPositionForBufferPosition(position))
# Converts a screen position to a pixel position.
# Public: Converts a screen position to a pixel position.
#
# position - An object that represents a screen position. It can be either
# an {Object} (`{row, column}`), {Array} (`[row, column]`), or {Point}
# an {Object} (`{row, column}`), {Array} (`[row, column]`), or {Point}
#
# Returns an object with two values: `top` and `left`, representing the pixel positions.
pixelPositionForScreenPosition: (position) ->
@@ -1297,7 +1297,6 @@ class EditorView extends View
index++
left
# Private:
measureToColumn: (lineElement, tokenizedLine, screenColumn) ->
left = oldLeft = index = 0
iterator = document.createNodeIterator(lineElement, NodeFilter.SHOW_TEXT, TextNodeFilter)
@@ -1343,7 +1342,6 @@ class EditorView extends View
returnLeft ? left
# Private:
getCharacterWidthCache: (scopes, char) ->
scopes ?= NoScope
obj = @constructor.characterWidthCache
@@ -1352,7 +1350,6 @@ class EditorView extends View
return null unless obj?
obj[char]
# Private:
setCharacterWidthCache: (scopes, char, val) ->
scopes ?= NoScope
obj = @constructor.characterWidthCache
@@ -1361,7 +1358,6 @@ class EditorView extends View
obj = obj[scope]
obj[char] = val
# Private:
clearCharacterWidthCache: ->
@constructor.characterWidthCache = {}
@@ -1411,11 +1407,9 @@ class EditorView extends View
@highlightedLine = null
# Copies the current file path to the native clipboard.
copyPathToPasteboard: ->
copyPathToClipboard: ->
path = @editor.getPath()
atom.pasteboard.write(path) if path?
### Internal ###
atom.clipboard.write(path) if path?
@buildLineHtml: ({tokens, text, lineEnding, fold, isSoftWrapped, invisibles, eolInvisibles, htmlEolInvisibles, attributes, showIndentGuide, indentation, editor, mini}) ->
scopeStack = []
@@ -1426,7 +1420,7 @@ class EditorView extends View
line.push("<div #{attributePairs}>")
if text == ''
html = EditorView.buildEmptyLineHtml(showIndentGuide, eolInvisibles, htmlEolInvisibles, indentation, editor, mini)
html = @buildEmptyLineHtml(showIndentGuide, eolInvisibles, htmlEolInvisibles, indentation, editor, mini)
line.push(html) if html
else
firstNonWhitespacePosition = text.search(/\S/)

View File

@@ -78,7 +78,7 @@ class Editor extends Model
position = [0, 0]
@addCursorAtBufferPosition(position)
@languageMode = new LanguageMode(this, @buffer.getExtension())
@languageMode = new LanguageMode(this)
@subscribe @$scrollTop, (scrollTop) => @emit 'scroll-top-changed', scrollTop
@subscribe @$scrollLeft, (scrollLeft) => @emit 'scroll-left-changed', scrollLeft
@@ -97,7 +97,6 @@ class Editor extends Model
params.registerEditor = true
params
# Private:
subscribeToBuffer: ->
@buffer.retain()
@subscribe @buffer, "path-changed", =>
@@ -111,7 +110,6 @@ class Editor extends Model
@subscribe @buffer, "destroyed", => @destroy()
@preserveCursorPositionOnBufferReload()
# Private:
subscribeToDisplayBuffer: ->
@subscribe @displayBuffer, 'marker-created', @handleMarkerCreated
@subscribe @displayBuffer, "changed", (e) => @emit 'screen-lines-changed', e
@@ -119,11 +117,9 @@ class Editor extends Model
@subscribe @displayBuffer, 'grammar-changed', => @handleGrammarChange()
@subscribe @displayBuffer, 'soft-wrap-changed', (args...) => @emit 'soft-wrap-changed', args...
# Private:
getViewClass: ->
require './editor-view'
# Private:
destroyed: ->
@unsubscribe()
selection.destroy() for selection in @getSelections()
@@ -132,7 +128,7 @@ class Editor extends Model
@languageMode.destroy()
atom.project?.removeEditor(this)
# Private: Creates an {Editor} with the same initial state
# Creates an {Editor} with the same initial state
copy: ->
tabLength = @getTabLength()
displayBuffer = @displayBuffer.copy()
@@ -238,15 +234,14 @@ class Editor extends Model
# Public: Given a position, this clips it to a real position.
#
# For example, if `position`'s row exceeds the row count of the buffer,
# For example, if `bufferPosition`'s row exceeds the row count of the buffer,
# or if its column goes beyond a line's length, this "sanitizes" the value
# to a real position.
#
# * position:
# The {Point} to clip
# bufferPosition - The {Point} to clip.
#
# Returns the new, clipped {Point}. Note that this could be the same as
# `position` if no clipping was performed.
# `bufferPosition` if no clipping was performed.
clipBufferPosition: (bufferPosition) -> @buffer.clipPosition(bufferPosition)
# Public: Given a range, this clips it to a real range.
@@ -255,8 +250,7 @@ class Editor extends Model
# or if its column goes beyond a line's length, this "sanitizes" the value
# to a real range.
#
# * range:
# The {Range} to clip
# range - The {Range} to clip.
#
# Returns the new, clipped {Range}. Note that this could be the same as
# `range` if no clipping was performed.
@@ -264,17 +258,14 @@ class Editor extends Model
# Public: Returns the indentation level of the given a buffer row
#
# * bufferRow:
# A Number indicating the buffer row.
# bufferRow - A {Number} indicating the buffer row.
indentationForBufferRow: (bufferRow) ->
@indentLevelForLine(@lineForBufferRow(bufferRow))
# Public: Sets the indentation level for the given buffer row.
#
# * bufferRow:
# A {Number} indicating the buffer row.
# * newLevel:
# A {Number} indicating the new indentation level.
# bufferRow - A {Number} indicating the buffer row.
# newLevel - A {Number} indicating the new indentation level.
setIndentationForBufferRow: (bufferRow, newLevel) ->
currentIndentLength = @lineForBufferRow(bufferRow).match(/^\s*/)[0].length
newIndentString = @buildIndentString(newLevel)
@@ -282,8 +273,7 @@ class Editor extends Model
# Public: Returns the indentation level of the given line of text.
#
# * line:
# A {String} in the current buffer.
# line - A {String} in the current buffer.
#
# Returns a {Number} or 0 if the text isn't found within the buffer.
indentLevelForLine: (line) ->
@@ -295,7 +285,7 @@ class Editor extends Model
else
0
# Private: Constructs the string used for tabs.
# Constructs the string used for tabs.
buildIndentString: (number) ->
if @getSoftTabs()
_.multiplyString(" ", number * @getTabLength())
@@ -308,9 +298,6 @@ class Editor extends Model
# {Delegates to: TextBuffer.saveAs}
saveAs: (path) -> @buffer.saveAs(path)
# {Delegates to: TextBuffer.getExtension}
getFileExtension: -> @buffer.getExtension()
# {Delegates to: TextBuffer.getPath}
getPath: -> @buffer.getPath()
@@ -326,7 +313,7 @@ class Editor extends Model
# Public: Returns a {Number} representing the number of lines in the editor.
getLineCount: -> @buffer.getLineCount()
# Private: Retrieves the current {TextBuffer}.
# Retrieves the current {TextBuffer}.
getBuffer: -> @buffer
# Public: Retrieves the current buffer's URI.
@@ -353,8 +340,8 @@ class Editor extends Model
# Public: Returns the range for the given buffer row.
#
# * row: A row {Number}.
# * options: An options hash with an `includeNewline` key.
# row - A row {Number}.
# options - An options hash with an `includeNewline` key.
#
# Returns a {Range}.
bufferRangeForBufferRow: (row, options) -> @buffer.rangeForRow(row, options)
@@ -362,7 +349,7 @@ class Editor extends Model
# Public: Returns a {String} representing the contents of the line at the
# given buffer row.
#
# * row - A {Number} representing a zero-indexed buffer row.
# row - A {Number} representing a zero-indexed buffer row.
lineForBufferRow: (row) -> @buffer.lineForRow(row)
# Public: Returns a {Number} representing the line length for the given
@@ -439,10 +426,8 @@ class Editor extends Model
# Public: Inserts text at the current cursor positions
#
# * text:
# A String representing the text to insert.
# * options:
# + A set of options equivalent to {Selection.insertText}
# text - A {String} representing the text to insert.
# options - A set of options equivalent to {Selection.insertText}.
insertText: (text, options={}) ->
options.autoIndentNewline ?= @shouldAutoIndent()
options.autoDecreaseIndent ?= @shouldAutoIndent()
@@ -469,8 +454,7 @@ class Editor extends Model
# Public: Indents the current line.
#
# * options
# + A set of options equivalent to {Selection.indent}.
# options - A set of options equivalent to {Selection.indent}.
indent: (options={})->
options.autoIndent ?= @shouldAutoIndent()
@mutateSelectedText (selection) -> selection.indent(options)
@@ -521,7 +505,7 @@ class Editor extends Model
#
# If the language doesn't have comments, nothing happens.
#
# Returns an {Array} of the commented {Ranges}.
# Returns an {Array} of the commented {Range}s.
toggleLineCommentsInSelection: ->
@mutateSelectedText (selection) -> selection.toggleLineComments()
@@ -537,31 +521,30 @@ class Editor extends Model
# Public: Copies and removes all characters from cursor to the end of the
# line.
cutToEndOfLine: ->
maintainPasteboard = false
maintainClipboard = false
@mutateSelectedText (selection) ->
selection.cutToEndOfLine(maintainPasteboard)
maintainPasteboard = true
selection.cutToEndOfLine(maintainClipboard)
maintainClipboard = true
# Public: Cuts the selected text.
cutSelectedText: ->
maintainPasteboard = false
maintainClipboard = false
@mutateSelectedText (selection) ->
selection.cut(maintainPasteboard)
maintainPasteboard = true
selection.cut(maintainClipboard)
maintainClipboard = true
# Public: Copies the selected text.
copySelectedText: ->
maintainPasteboard = false
maintainClipboard = false
for selection in @getSelections()
selection.copy(maintainPasteboard)
maintainPasteboard = true
selection.copy(maintainClipboard)
maintainClipboard = true
# Public: Pastes the text in the clipboard.
#
# * options:
# + A set of options equivalent to {Selection.insertText}.
# options - A set of options equivalent to {Selection.insertText}.
pasteText: (options={}) ->
[text, metadata] = atom.pasteboard.read()
{text, metadata} = atom.clipboard.readWithMetadata()
containsNewlines = text.indexOf('\n') isnt -1
@@ -640,7 +623,7 @@ class Editor extends Model
largestFoldStartingAtScreenRow: (screenRow) ->
@displayBuffer.largestFoldStartingAtScreenRow(screenRow)
# Public: Moves the selected line up one row.
# Public: Moves the selected lines up one screen row.
moveLineUp: ->
selection = @getSelectedBufferRange()
return if selection.start.row is 0
@@ -652,29 +635,47 @@ class Editor extends Model
rows = [selection.start.row..selection.end.row]
if selection.start.row isnt selection.end.row and selection.end.column is 0
rows.pop() unless @isFoldedAtBufferRow(selection.end.row)
# Move line around the fold that is directly above the selection
precedingScreenRow = @screenPositionForBufferPosition([selection.start.row]).translate([-1])
precedingBufferRow = @bufferPositionForScreenPosition(precedingScreenRow).row
if fold = @largestFoldContainingBufferRow(precedingBufferRow)
insertDelta = fold.getBufferRange().getRowCount()
else
insertDelta = 1
for row in rows
screenRow = @screenPositionForBufferPosition([row]).row
if @isFoldedAtScreenRow(screenRow)
bufferRange = @bufferRangeForScreenRange([[screenRow], [screenRow + 1]])
if fold = @displayBuffer.largestFoldStartingAtBufferRow(row)
bufferRange = fold.getBufferRange()
startRow = bufferRange.start.row
endRow = bufferRange.end.row - 1
foldedRows.push(endRow - 1)
endRow = bufferRange.end.row
foldedRows.push(startRow - insertDelta)
else
startRow = row
endRow = row
insertPosition = Point.fromObject([startRow - insertDelta])
endPosition = Point.min([endRow + 1], @buffer.getEofPosition())
lines = @buffer.getTextInRange([[startRow], endPosition])
if endPosition.row is lastRow and endPosition.column > 0 and not @buffer.lineEndingForRow(endPosition.row)
lines = "#{lines}\n"
@buffer.deleteRows(startRow, endRow)
@buffer.insert([startRow - 1], lines)
@foldBufferRow(foldedRow) for foldedRow in foldedRows
# Make sure the inserted text doesn't go into an existing fold
if fold = @displayBuffer.largestFoldStartingAtBufferRow(insertPosition.row)
@destroyFoldsContainingBufferRow(insertPosition.row)
foldedRows.push(insertPosition.row + endRow - startRow + fold.getBufferRange().getRowCount())
@setSelectedBufferRange(selection.translate([-1]), preserveFolds: true)
@buffer.insert(insertPosition, lines)
# Public: Moves the selected line down one row.
# Restore folds that existed before the lines were moved
for foldedRow in foldedRows when 0 <= foldedRow <= @getLastBufferRow()
@foldBufferRow(foldedRow)
@setSelectedBufferRange(selection.translate([-insertDelta]), preserveFolds: true)
# Public: Moves the selected lines down one screen row.
moveLineDown: ->
selection = @getSelectedBufferRange()
lastRow = @buffer.getLastRow()
@@ -686,13 +687,21 @@ class Editor extends Model
rows = [selection.end.row..selection.start.row]
if selection.start.row isnt selection.end.row and selection.end.column is 0
rows.shift() unless @isFoldedAtBufferRow(selection.end.row)
# Move line around the fold that is directly below the selection
followingScreenRow = @screenPositionForBufferPosition([selection.end.row]).translate([1])
followingBufferRow = @bufferPositionForScreenPosition(followingScreenRow).row
if fold = @largestFoldContainingBufferRow(followingBufferRow)
insertDelta = fold.getBufferRange().getRowCount()
else
insertDelta = 1
for row in rows
screenRow = @screenPositionForBufferPosition([row]).row
if @isFoldedAtScreenRow(screenRow)
bufferRange = @bufferRangeForScreenRange([[screenRow], [screenRow + 1]])
if fold = @displayBuffer.largestFoldStartingAtBufferRow(row)
bufferRange = fold.getBufferRange()
startRow = bufferRange.start.row
endRow = bufferRange.end.row - 1
foldedRows.push(endRow + 1)
endRow = bufferRange.end.row
foldedRows.push(endRow + insertDelta)
else
startRow = row
endRow = row
@@ -703,14 +712,23 @@ class Editor extends Model
endPosition = [endRow + 1]
lines = @buffer.getTextInRange([[startRow], endPosition])
@buffer.deleteRows(startRow, endRow)
insertPosition = Point.min([startRow + 1], @buffer.getEofPosition())
insertPosition = Point.min([startRow + insertDelta], @buffer.getEofPosition())
if insertPosition.row is @buffer.getLastRow() and insertPosition.column > 0
lines = "\n#{lines}"
# Make sure the inserted text doesn't go into an existing fold
if fold = @displayBuffer.largestFoldStartingAtBufferRow(insertPosition.row)
@destroyFoldsContainingBufferRow(insertPosition.row)
foldedRows.push(insertPosition.row + fold.getBufferRange().getRowCount())
@buffer.insert(insertPosition, lines)
@foldBufferRow(foldedRow) for foldedRow in foldedRows
# Restore folds that existed before the lines were moved
for foldedRow in foldedRows when 0 <= foldedRow <= @getLastBufferRow()
@foldBufferRow(foldedRow)
@setSelectedBufferRange(selection.translate([1]), preserveFolds: true)
@setSelectedBufferRange(selection.translate([insertDelta]), preserveFolds: true)
# Public: Duplicates the current line.
#
@@ -739,11 +757,9 @@ class Editor extends Model
@setCursorScreenPosition(@getCursorScreenPosition().translate([1]))
@foldCurrentRow() if cursorRowFolded
# Private:
mutateSelectedText: (fn) ->
@transact => fn(selection) for selection in @getSelections()
# Private:
replaceSelectedText: (options={}, fn) ->
{selectWordIfEmpty} = options
@mutateSelectedText (selection) ->
@@ -787,7 +803,9 @@ class Editor extends Model
destroyMarker: (args...) ->
@displayBuffer.destroyMarker(args...)
# Public: {Delegates to: DisplayBuffer.getMarkerCount}
# Public: Get the number of markers in this editor's buffer.
#
# Returns a {Number}.
getMarkerCount: ->
@buffer.getMarkerCount()
@@ -826,10 +844,8 @@ class Editor extends Model
# Public: Creates a new selection at the given marker.
#
# * marker:
# The {DisplayBufferMarker} to highlight
# * options:
# + A hash of options that pertain to the {Selection} constructor.
# marker - The {DisplayBufferMarker} to highlight
# options - An {Object} that pertains to the {Selection} constructor.
#
# Returns the new {Selection}.
addSelection: (marker, options={}) ->
@@ -850,10 +866,8 @@ class Editor extends Model
# Public: Given a buffer range, this adds a new selection for it.
#
# * bufferRange:
# A {Range} in the buffer
# * options:
# + A hash of options for {.markBufferRange}
# bufferRange - A {Range} in the buffer.
# options - An options {Object} for {.markBufferRange}.
#
# Returns the new {Selection}.
addSelectionForBufferRange: (bufferRange, options={}) ->
@@ -863,20 +877,16 @@ class Editor extends Model
# Public: Given a buffer range, this removes all previous selections and
# creates a new selection for it.
#
# * bufferRange:
# A {Range} in the buffer
# * options:
# + A hash of options for {.setSelectedBufferRanges}
# bufferRange - A {Range} in the buffer.
# options - An options {Object} for {.setSelectedBufferRanges}.
setSelectedBufferRange: (bufferRange, options) ->
@setSelectedBufferRanges([bufferRange], options)
# Public: Given an array of buffer ranges, this removes all previous
# selections and creates new selections for them.
#
# * bufferRange:
# A {Range} in the buffer
# * options:
# + A hash of options for {.setSelectedBufferRanges}
# bufferRange - A {Range} in the buffer.
# options - An options {Object} for {.setSelectedBufferRanges}.
setSelectedBufferRanges: (bufferRanges, options={}) ->
throw new Error("Passed an empty array to setSelectedBufferRanges") unless bufferRanges.length
@@ -893,7 +903,7 @@ class Editor extends Model
# Public: Unselects a given selection.
#
# * selection - The {Selection} to remove.
# selection - The {Selection} to remove.
removeSelection: (selection) ->
_.remove(@selections, selection)
@@ -904,9 +914,7 @@ class Editor extends Model
@consolidateSelections()
@getSelection().clear()
# Public:
#
# Removes all but one cursor (if there are multiple cursors)
# Removes all but one cursor (if there are multiple cursors).
consolidateSelections: ->
selections = @getSelections()
if selections.length > 1
@@ -943,8 +951,7 @@ class Editor extends Model
# Public: Determines if a given buffer range is included in a {Selection}.
#
# * bufferRange:
# The {Range} you're checking against
# bufferRange - The {Range} you're checking against.
#
# Returns a {Boolean}.
selectionIntersectsBufferRange: (bufferRange) ->
@@ -953,10 +960,8 @@ class Editor extends Model
# Public: Moves every local cursor to a given screen position.
#
# * position:
# An {Array} of two numbers: the screen row, and the screen column.
# * options:
# An object with properties based on {Cursor.setScreenPosition}
# position - An {Array} of two numbers: the screen row, and the screen column.
# options - An {Object} with properties based on {Cursor.setScreenPosition}.
setCursorScreenPosition: (position, options) ->
@moveCursors (cursor) -> cursor.setScreenPosition(position, options)
@@ -975,10 +980,8 @@ class Editor extends Model
# Public: Moves every cursor to a given buffer position.
#
# * position:
# An {Array} of two numbers: the buffer row, and the buffer column.
# * options:
# + An object with properties based on {Cursor.setBufferPosition}
# position - An {Array} of two numbers: the buffer row, and the buffer column.
# options - An object with properties based on {Cursor.setBufferPosition}.
setCursorBufferPosition: (position, options) ->
@moveCursors (cursor) -> cursor.setBufferPosition(position, options)
@@ -1021,9 +1024,8 @@ class Editor extends Model
# Public: Returns the word under the most recently added local {Cursor}.
#
# * options:
# + An object with properties based on
# {Cursor.getBeginningOfCurrentWordBufferPosition}.
# options - An object with properties based on
# {Cursor.getBeginningOfCurrentWordBufferPosition}.
getWordUnderCursor: (options) ->
@getTextInBufferRange(@getCursor().getCurrentWordBufferRange(options))
@@ -1091,7 +1093,6 @@ class Editor extends Model
moveCursorToNextWordBoundary: ->
@moveCursors (cursor) -> cursor.moveToNextWordBoundary()
# Internal: Executes given function on all local cursors.
moveCursors: (fn) ->
fn(cursor) for cursor in @getCursors()
@mergeCursors()
@@ -1099,8 +1100,7 @@ class Editor extends Model
# Public: Selects the text from the current cursor position to a given screen
# position.
#
# * position:
# An instance of {Point}, with a given `row` and `column`.
# position - An instance of {Point}, with a given `row` and `column`.
selectToScreenPosition: (position) ->
lastSelection = @getLastSelection()
lastSelection.selectToScreenPosition(position)
@@ -1259,8 +1259,6 @@ class Editor extends Model
@setSelectedBufferRange(range)
range
# Public:
#
# FIXME: Not sure how to describe what this does.
mergeCursors: ->
positions = []
@@ -1271,27 +1269,21 @@ class Editor extends Model
else
positions.push(position)
# Public:
#
# FIXME: Not sure how to describe what this does.
expandSelectionsForward: (fn) ->
@mergeIntersectingSelections =>
fn(selection) for selection in @getSelections()
# Public:
#
# FIXME: Not sure how to describe what this does.
expandSelectionsBackward: (fn) ->
@mergeIntersectingSelections isReversed: true, =>
fn(selection) for selection in @getSelections()
# Public:
#
# FIXME: No idea what this does.
finalizeSelections: ->
selection.finalize() for selection in @getSelections()
# Private: Merges intersecting selections. If passed a function, it executes
# Merges intersecting selections. If passed a function, it executes
# the function with merging suppressed, then merges intersecting selections
# afterward.
mergeIntersectingSelections: (args...) ->
@@ -1315,7 +1307,6 @@ class Editor extends Model
_.reduce(@getSelections(), reducer, [])
# Private:
preserveCursorPositionOnBufferReload: ->
cursorPosition = null
@subscribe @buffer, "will-reload", =>
@@ -1336,7 +1327,6 @@ class Editor extends Model
reloadGrammar: ->
@displayBuffer.reloadGrammar()
# Private:
shouldAutoIndent: ->
atom.config.get("editor.autoIndent")
@@ -1347,32 +1337,24 @@ class Editor extends Model
# undo stack remains relevant.
transact: (fn) -> @buffer.transact(fn)
# Private:
beginTransaction: -> @buffer.beginTransaction()
# Private:
commitTransaction: -> @buffer.commitTransaction()
# Private:
abortTransaction: -> @buffer.abortTransaction()
# Private:
inspect: ->
"<Editor #{@id}>"
# Private:
logScreenLines: (start, end) -> @displayBuffer.logLines(start, end)
# Private:
handleGrammarChange: ->
@unfoldAll()
@emit 'grammar-changed'
# Private:
handleMarkerCreated: (marker) =>
if marker.matchesAttributes(@getSelectionMarkerAttributes())
@addSelection(marker)
# Private:
getSelectionMarkerAttributes: ->
type: 'selection', editorId: @id, invalidate: 'never'

View File

@@ -5,6 +5,7 @@ Q = require 'q'
{Emitter} = require 'emissary'
_ = require 'underscore-plus'
fs = require 'fs-plus'
runas = require 'runas'
# Public: Represents an individual file.
#
@@ -25,16 +26,14 @@ class File
# Public: Creates a new file.
#
# * path:
# A String containing the absolute path to the file
# * symlink:
# A Boolean indicating if the path is a symlink (default: false)
# path - A {String} containing the absolute path to the file
# symlink - A {Boolean} indicating if the path is a symlink (default: false).
constructor: (@path, @symlink=false) ->
throw new Error("#{@path} is a directory") if fs.isDirectorySync(@path)
@handleEventSubscriptions()
# Private: Subscribes to file system notifications when necessary.
# Subscribes to file system notifications when necessary.
handleEventSubscriptions: ->
eventNames = ['contents-changed', 'moved', 'removed']
@@ -49,24 +48,24 @@ class File
subscriptionsEmpty = _.every eventNames, (eventName) => @getSubscriptionCount(eventName) is 0
@unsubscribeFromNativeChangeEvents() if subscriptionsEmpty
# Private: Sets the path for the file.
# Sets the path for the file.
setPath: (@path) ->
# Public: Returns the path for the file.
# Public: Returns the {String} path for the file.
getPath: -> @path
# Public: Return the filename without any directory information.
# Public: Return the {String} filename without any directory information.
getBaseName: ->
path.basename(@path)
# Public: Overwrites the file with the given String.
write: (text) ->
previouslyExisted = @exists()
@writeFileWithPrivilegeEscalationSync(@getPath(), text)
@cachedContents = text
fs.writeFileSync(@getPath(), text)
@subscribeToNativeChangeEvents() if not previouslyExisted and @hasSubscriptions()
# Private: Deprecated
# Deprecated
readSync: (flushCache) ->
if not @exists()
@cachedContents = null
@@ -80,9 +79,8 @@ class File
# Public: Reads the contents of the file.
#
# * flushCache:
# A Boolean indicating whether to require a direct read or if a cached
# copy is acceptable.
# flushCache - A {Boolean} indicating whether to require a direct read or if
# a cached copy is acceptable.
#
# Returns a promise that resovles to a String.
read: (flushCache) ->
@@ -118,7 +116,6 @@ class File
exists: ->
fs.existsSync(@getPath())
# Private:
setDigest: (contents) ->
@digest = crypto.createHash('sha1').update(contents ? '').digest('hex')
@@ -126,7 +123,21 @@ class File
getDigest: ->
@digest ? @setDigest(@readSync())
# Private:
# Writes the text to specified path.
#
# Privilege escalation would be asked when current user doesn't have
# permission to the path.
writeFileWithPrivilegeEscalationSync: (path, text) ->
try
fs.writeFileSync(path, text)
catch error
if error.code is 'EACCES' and process.platform is 'darwin'
authopen = '/usr/libexec/authopen' # man 1 authopen
unless runas(authopen, ['-w', '-c', path], stdin: text) is 0
throw error
else
throw error
handleNativeChangeEvent: (eventType, path) ->
if eventType is "delete"
@unsubscribeFromNativeChangeEvents()
@@ -139,11 +150,9 @@ class File
@read(true).done (newContents) =>
@emit 'contents-changed' unless oldContents == newContents
# Private:
detectResurrectionAfterDelay: ->
_.delay (=> @detectResurrection()), 50
# Private:
detectResurrection: ->
if @exists()
@subscribeToNativeChangeEvents()
@@ -152,13 +161,11 @@ class File
@cachedContents = null
@emit "removed"
# Private:
subscribeToNativeChangeEvents: ->
unless @watchSubscription?
@watchSubscription = pathWatcher.watch @path, (eventType, path) =>
@handleNativeChangeEvent(eventType, path)
# Private:
unsubscribeFromNativeChangeEvents: ->
if @watchSubscription?
@watchSubscription.close()

View File

@@ -1,6 +1,6 @@
{Point, Range} = require 'text-buffer'
# Private: Represents a fold that collapses multiple buffer lines into a single
# Represents a fold that collapses multiple buffer lines into a single
# line on the screen.
#
# Their creation is managed by the {DisplayBuffer}.
@@ -10,8 +10,6 @@ class Fold
displayBuffer: null
marker: null
### Internal ###
constructor: (@displayBuffer, @marker) ->
@id = @marker.id
@displayBuffer.foldsByMarkerId[@marker.id] = this

View File

@@ -27,17 +27,19 @@ class Git
Emitter.includeInto(this)
Subscriber.includeInto(this)
# Private: Creates a new `Git` instance.
# Public: Creates a new Git instance.
#
# * path: The path to the git repository to open
# * options:
# + refreshOnWindowFocus:
# A Boolean that identifies if the windows should refresh
# path - The path to the Git repository to open.
# options - An object with the following keys (default: {}):
# :refreshOnWindowFocus - `true` to refresh the index and statuses when the
# window is focused.
#
# Returns a Git instance or null if the repository could not be opened.
@open: (path, options) ->
return null unless path
try
new Git(path, options)
catch e
catch
null
@exists: (path) ->
@@ -47,20 +49,6 @@ class Git
else
false
path: null
statuses: null
upstream: null
branch: null
statusTask: null
# Private: Creates a new `Git` object.
#
# * path: The {String} representing the path to your git working directory
# * options:
# + refreshOnWindowFocus: If `true`, {#refreshIndex} and {#refreshStatus}
# are called on focus
# + project: A project that supplies buffers that will be monitored for
# save and reload events to trigger status refreshes.
constructor: (path, options={}) ->
@repo = GitUtils.open(path)
unless @repo?
@@ -80,7 +68,7 @@ class Git
if @project?
@subscribe @project.eachBuffer (buffer) => @subscribeToBuffer(buffer)
# Private: Subscribes to buffer events.
# Subscribes to buffer events.
subscribeToBuffer: (buffer) ->
@subscribe buffer, 'saved reloaded path-changed', =>
if path = buffer.getPath()
@@ -100,29 +88,29 @@ class Git
@unsubscribe()
# Private: Returns the corresponding {Repository}
# Returns the corresponding {Repository}
getRepo: ->
unless @repo?
throw new Error("Repository has been destroyed")
@repo
# Public: Reread the index to update any values that have changed since the
# Reread the index to update any values that have changed since the
# last time the index was read.
refreshIndex: -> @getRepo().refreshIndex()
# Public: Returns the path of the repository.
# Public: Returns the {String} path of the repository.
getPath: ->
@path ?= fs.absolute(@getRepo().getPath())
# Public: Returns the working directory of the repository.
# Public: Returns the {String} working directory path of the repository.
getWorkingDirectory: -> @getRepo().getWorkingDirectory()
# Public: Returns the status of a single path in the repository.
# Public: Get the status of a single path in the repository.
#
# * path:
# A String defining a relative path
# path - A {String} repository-relative path.
#
# Returns a {Number}, FIXME representing what?
# Returns a {Number} representing the status. This value can be passed to
# {.isStatusModified} or {.isStatusNew} to get more information.
getPathStatus: (path) ->
currentPathStatus = @statuses[path] ? 0
pathStatus = @getRepo().getStatus(@relativize(path)) ? 0
@@ -134,7 +122,9 @@ class Git
@emit 'status-changed', path, pathStatus
pathStatus
# Public: Returns true if the given path is ignored.
# Public: Is the given path ignored?
#
# Returns a {Boolean}.
isPathIgnored: (path) -> @getRepo().isIgnored(@relativize(path))
# Public: Returns true if the given status indicates modification.
@@ -163,22 +153,22 @@ class Git
# `refs/remotes`. It also shortens the SHA-1 of a detached `HEAD` to 7
# characters.
#
# Returns a String.
# Returns a {String}.
getShortHead: -> @getRepo().getShortHead()
# Public: Restore the contents of a path in the working directory and index
# to the version at `HEAD`.
#
# This is essentially the same as running:
#
# ```
# git reset HEAD -- <path>
# git checkout HEAD -- <path>
# ```
#
# * path:
# The String path to checkout
# path - The {String} path to checkout.
#
# Returns a Boolean that's true if the method was successful.
# Returns a {Boolean} that's true if the method was successful.
checkoutHead: (path) ->
headCheckedOut = @getRepo().checkoutHead(@relativize(path))
@getPathStatus(path) if headCheckedOut
@@ -186,10 +176,9 @@ class Git
# Public: Checks out a branch in your repository.
#
# * reference:
# The String reference to checkout
# * create:
# A Boolean value which, if true creates the new reference if it doesn't exist.
# reference - The String reference to checkout
# create - A Boolean value which, if true creates the new reference if it
# doesn't exist.
#
# Returns a Boolean that's true if the method was successful.
checkoutReference: (reference, create) ->
@@ -200,27 +189,26 @@ class Git
# This compares the working directory contents of the path to the `HEAD`
# version.
#
# * path:
# The String path to check
# path - The {String} path to check.
#
# Returns an object with two keys, `added` and `deleted`. These will always
# be greater than 0.
# Returns an {Object} with the following keys:
# :added - The {Number} of added lines.
# :deleted - The {Number} of deleted lines.
getDiffStats: (path) -> @getRepo().getDiffStats(@relativize(path))
# Public: Identifies if a path is a submodule.
# Public: Is the given path a submodule in the repository?
#
# * path:
# The String path to check
# path - The {String} path to check.
#
# Returns a Boolean.
# Returns a {Boolean}.
isSubmodule: (path) -> @getRepo().isSubmodule(@relativize(path))
# Public: Retrieves the status of a directory.
# Public: Get the status of a directory in the repository's working directory.
#
# * path:
# The String path to check
# path - The {String} path to check.
#
# Returns a Number representing the status.
# Returns a {Number} representing the status. This value can be passed to
# {.isStatusModified} or {.isStatusNew} to get more information.
getDirectoryStatus: (directoryPath) ->
{sep} = require 'path'
directoryPath = "#{directoryPath}#{sep}"
@@ -232,16 +220,14 @@ class Git
# Public: Retrieves the line diffs comparing the `HEAD` version of the given
# path and the given text.
#
# This is similar to the commit numbers reported by `git status` when a
# remote tracking branch exists.
# path - The {String} path relative to the repository.
# text - The {String} to compare against the `HEAD` contents
#
# * path:
# The String path (relative to the repository)
# * text:
# The String to compare against the `HEAD` contents
#
# Returns an object with two keys, `ahead` and `behind`. These will always be
# greater than zero.
# Returns an {Array} of hunk {Object}s with the following keys:
# :oldStart - The line {Number} of the old hunk.
# :newStart - The line {Number} of the new hunk.
# :oldLines - The {Number} of lines in the old hunk.
# :newLines - The {Number} of lines in the new hunk
getLineDiffs: (path, text) ->
# Ignore eol of line differences on windows so that files checked in as
# LF don't report every line modified when the text contains CRLF endings.
@@ -257,7 +243,7 @@ class Git
# Public: Returns the upstream branch for the current HEAD, or null if there
# is no upstream branch for the current HEAD.
#
# Returns a String branch name such as `refs/remotes/origin/master`
# Returns a {String} branch name such as `refs/remotes/origin/master`.
getUpstreamBranch: -> @getRepo().getUpstreamBranch()
# Public: Returns the current SHA for the given reference.
@@ -265,19 +251,21 @@ class Git
# Public: Gets all the local and remote references.
#
# Returns an object with three keys: `heads`, `remotes`, and `tags`. Each key
# can be an array of strings containing the reference names.
# Returns an {Object} with the following keys:
# :heads - An {Array} of head reference names.
# :remotes - An {Array} of remote reference names.
# :tags - An {Array} of tag reference names.
getReferences: -> @getRepo().getReferences()
# Public: Returns the number of commits behind the current branch is from the
# default remote branch.
# its upstream remote branch.
getAheadBehindCount: (reference) -> @getRepo().getAheadBehindCount(reference)
# Public: Returns true if the given branch exists.
hasBranch: (branch) -> @getReferenceTarget("refs/heads/#{branch}")?
# Private: Refreshes the current git status in an outside process and
# asynchronously updates the relevant properties.
# Refreshes the current git status in an outside process and asynchronously
# updates the relevant properties.
refreshStatus: ->
@statusTask = Task.once require.resolve('./repository-status-handler'), @getPath(), ({statuses, upstream, branch}) =>
statusesUnchanged = _.isEqual(statuses, @statuses) and _.isEqual(upstream, @upstream) and _.isEqual(branch, @branch)

View File

@@ -2,14 +2,11 @@
{Range} = require 'text-buffer'
_ = require 'underscore-plus'
# Private: Represents the portion of the {EditorView} containing row numbers.
# Represents the portion of the {EditorView} containing row numbers.
#
# The gutter also indicates if rows are folded.
module.exports =
class GutterView extends View
### Internal ###
@content: ->
@div class: 'gutter', =>
@div outlet: 'lineNumbers', class: 'line-numbers'
@@ -51,8 +48,6 @@ class GutterView extends View
$(document).on "mousemove.gutter-#{editorView.id}", moveHandler
$(document).one "mouseup.gutter-#{editorView.id}", => $(document).off 'mousemove', moveHandler
### Public ###
# Retrieves the containing {EditorView}.
#
# Returns an {EditorView}.
@@ -138,8 +133,6 @@ class GutterView extends View
el.classList.remove(klass) if hasClass
classesRemoved
### Internal ###
updateLineNumbers: (changes, startScreenRow, endScreenRow) ->
# Check if we have something already rendered that overlaps the requested range
updateAllLines = not (startScreenRow? and endScreenRow?)
@@ -223,7 +216,7 @@ class GutterView extends View
html
# Private: Called to update the 'foldable' class of line numbers when there's
# Called to update the 'foldable' class of line numbers when there's
# a change to the display buffer that doesn't regenerate all the line numbers
# anyway.
updateFoldableClasses: (changes) ->

View File

@@ -2,8 +2,6 @@ _ = require 'underscore-plus'
fs = require 'fs-plus'
{specificity} = require 'clear-cut'
### Internal ###
module.exports =
class KeyBinding
@parser: null

View File

@@ -9,19 +9,23 @@ File = require './file'
Modifiers = ['alt', 'control', 'ctrl', 'shift', 'cmd']
# Internal: Associates keymaps with actions.
# Public: Associates keybindings with commands.
#
# Keymaps are defined in a CSON format. A typical keymap looks something like this:
# An instance of this class is always available as the `atom.keymap` global.
#
# Keymaps are defined in a CSON/JSON format. A typical keymap looks something
# like this:
#
# ```cson
# 'body':
# 'ctrl-l': 'package:do-something'
#'.someClass':
# 'enter': 'package:confirm'
# 'ctrl-l': 'package:do-something'
# '.someClass':
# 'enter': 'package:confirm'
# ```
#
# As a key, you define the DOM element you want to work on, using CSS notation. For that
# key, you define one or more key:value pairs, associating keystrokes with a command to execute.
# As a key, you define the DOM element you want to work on, using CSS notation.
# For that key, you define one or more key:value pairs, associating keystrokes
# with a command to execute.
module.exports =
class Keymap
Emitter.includeInto(this)
@@ -39,10 +43,8 @@ class Keymap
# Public: Returns a array of {KeyBinding}s (sorted by selector specificity)
# that match a keystroke and element.
#
# * keystroke:
# The string representing the keys pressed (e.g. ctrl-P).
# * element:
# The DOM node that will match a {KeyBinding}'s selector.
# keystroke - The {String} representing the keys pressed (e.g. ctrl-P).
# element - The DOM node that will match a {KeyBinding}'s selector.
keyBindingsForKeystrokeMatchingElement: (keystroke, element) ->
keyBindings = @keyBindingsForKeystroke(keystroke)
@keyBindingsMatchingElement(element, keyBindings)
@@ -50,41 +52,37 @@ class Keymap
# Public: Returns a array of {KeyBinding}s (sorted by selector specificity)
# that match a command.
#
# * command:
# The string representing the command (tree-view:toggle)
# * element:
# The DOM node that will match a {KeyBinding}'s selector.
# command - The {String} representing the command (tree-view:toggle).
# element - The DOM node that will match a {KeyBinding}'s selector.
keyBindingsForCommandMatchingElement: (command, element) ->
keyBindings = @keyBindingsForCommand(command)
@keyBindingsMatchingElement(element, keyBindings)
# Public: Returns an array of {KeyBinding}s that match a keystroke
# * keystroke:
# The string representing the keys pressed (e.g. ctrl-P)
#
# keystroke: The {String} representing the keys pressed (e.g. ctrl-P)
keyBindingsForKeystroke: (keystroke) ->
keystroke = KeyBinding.normalizeKeystroke(keystroke)
@keyBindings.filter (keyBinding) -> keyBinding.matches(keystroke)
# Public: Returns an array of {KeyBinding}s that match a command
# * keystroke:
# The string representing the keys pressed (e.g. ctrl-P)
#
# keystroke - The {String} representing the keys pressed (e.g. ctrl-P)
keyBindingsForCommand: (command) ->
@keyBindings.filter (keyBinding) -> keyBinding.command == command
# Public: Returns a array of {KeyBinding}s (sorted by selector specificity)
# whos selector matches the element.
#
# * element:
# The DOM node that will match a {KeyBinding}'s selector.
# element - The DOM node that will match a {KeyBinding}'s selector.
keyBindingsMatchingElement: (element, keyBindings=@keyBindings) ->
keyBindings = keyBindings.filter ({selector}) -> $(element).closest(selector).length > 0
keyBindings.sort (a, b) -> a.compare(b)
# Public: Returns a keystroke string derived from an event.
# * event:
# A DOM or jQuery event
# * previousKeystroke:
# An optional string used for multiKeystrokes
#
# event - A DOM or jQuery event.
# previousKeystroke - An optional string used for multiKeystrokes.
keystrokeStringForEvent: (event, previousKeystroke) ->
if event.originalEvent.keyIdentifier.indexOf('U+') == 0
hexCharCode = event.originalEvent.keyIdentifier[2..]

View File

@@ -3,30 +3,19 @@ _ = require 'underscore-plus'
{OnigRegExp} = require 'oniguruma'
{Emitter, Subscriber} = require 'emissary'
### Internal ###
module.exports =
class LanguageMode
Emitter.includeInto(this)
Subscriber.includeInto(this)
buffer: null
grammar: null
editor: null
currentGrammarScore: null
### Internal ###
destroy: ->
@unsubscribe()
### Public ###
# Sets up a `LanguageMode` for the given {Editor}.
#
# editor - The {Editor} to associate with
constructor: (@editor) ->
@buffer = @editor.buffer
{@buffer} = @editor
destroy: ->
@unsubscribe()
toggleLineCommentForBufferRow: (row) ->
@toggleLineCommentsForBufferRows(row, row)
@@ -187,7 +176,7 @@ class LanguageMode
isFoldableAtBufferRow: (bufferRow) ->
@isFoldableCodeAtBufferRow(bufferRow) or @isFoldableCommentAtBufferRow(bufferRow)
# Private: Returns a {Boolean} indicating whether the given buffer row starts
# Returns a {Boolean} indicating whether the given buffer row starts
# a a foldable row range due to the code's indentation patterns.
isFoldableCodeAtBufferRow: (bufferRow) ->
return false if @editor.isBufferRowBlank(bufferRow) or @isLineCommentedAtBufferRow(bufferRow)
@@ -195,14 +184,14 @@ class LanguageMode
return false unless nextNonEmptyRow?
@editor.indentationForBufferRow(nextNonEmptyRow) > @editor.indentationForBufferRow(bufferRow)
# Private: Returns a {Boolean} indicating whether the given buffer row starts
# Returns a {Boolean} indicating whether the given buffer row starts
# a foldable row range due to being the start of a multi-line comment.
isFoldableCommentAtBufferRow: (bufferRow) ->
@isLineCommentedAtBufferRow(bufferRow) and
@isLineCommentedAtBufferRow(bufferRow + 1) and
not @isLineCommentedAtBufferRow(bufferRow - 1)
# Private: Returns a {Boolean} indicating whether the line at the given buffer
# Returns a {Boolean} indicating whether the line at the given buffer
# row is a comment.
isLineCommentedAtBufferRow: (bufferRow) ->
return false unless 0 <= bufferRow <= @editor.getLastBufferRow()

View File

@@ -5,7 +5,7 @@ LessCache = require 'less-cache'
tmpDir = if process.platform is 'win32' then os.tmpdir() else '/tmp'
# Private: {LessCache} wrapper used by {ThemeManager} to read stylesheets.
# {LessCache} wrapper used by {ThemeManager} to read stylesheets.
module.exports =
class LessCompileCache
Subscriber.includeInto(this)

View File

@@ -8,61 +8,81 @@ fs = require 'fs-plus'
# Public: Provides a registry for menu items that you'd like to appear in the
# application menu.
#
# Should be accessed via `atom.menu`.
# An instance of this class is always available as the `atom.menu` global.
module.exports =
class MenuManager
# Private:
constructor: ({@resourcePath}) ->
@pendingUpdateOperation = null
@template = []
atom.keymap.on 'bundled-keymaps-loaded', => @loadPlatformItems()
# Public: Adds the given item definition to the existing template.
#
# * item:
# An object which describes a menu item as defined by
# https://github.com/atom/atom-shell/blob/master/docs/api/browser/menu.md
# ## Example
# ```coffee
# atom.menu.add [
# {
# label: 'Hello'
# submenu : [{label: 'World!', command: 'hello:world'}]
# }
# ]
# ```
#
# items - An {Array} of menu item {Object}s containing the keys:
# :label - The {String} menu label.
# :submenu - An optional {Array} of sub menu items.
# :command - An optional {String} command to trigger when the item is
# clicked.
#
# Returns nothing.
add: (items) ->
@merge(@template, item) for item in items
@update()
# Private: Should the binding for the given selector be included in the menu
# Should the binding for the given selector be included in the menu
# commands.
#
# * selector: A String selector to check.
# selector - A {String} selector to check.
#
# Returns true to include the selector, false otherwise.
includeSelector: (selector) ->
return true if document.body.webkitMatchesSelector(selector)
# Simulate an .editor element attached to a body element that has the same
# classes as the current body element.
# Simulate an .editor element attached to a .workspace element attached to
# a body element that has the same classes as the current body element.
unless @testEditor?
testBody = document.createElement('body')
testBody.classList.add(@classesForElement(document.body)...)
testWorkspace = document.createElement('body')
workspaceClasses = @classesForElement(document.body.querySelector('.workspace')) ? ['.workspace']
testWorkspace.classList.add(workspaceClasses...)
testBody.appendChild(testWorkspace)
@testEditor = document.createElement('div')
@testEditor.classList.add('editor')
testBody = document.createElement('body')
testBody.classList.add(document.body.classList.toString().split(' ')...)
testBody.appendChild(@testEditor)
testWorkspace.appendChild(@testEditor)
@testEditor.webkitMatchesSelector(selector)
# Public: Refreshes the currently visible menu.
update: ->
keystrokesByCommand = {}
for binding in atom.keymap.getKeyBindings() when @includeSelector(binding.selector)
keystrokesByCommand[binding.command] ?= []
keystrokesByCommand[binding.command].push binding.keystroke
@sendToBrowserProcess(@template, keystrokesByCommand)
clearImmediate(@pendingUpdateOperation) if @pendingUpdateOperation?
@pendingUpdateOperation = setImmediate =>
keystrokesByCommand = {}
for binding in atom.keymap.getKeyBindings() when @includeSelector(binding.selector)
keystrokesByCommand[binding.command] ?= []
keystrokesByCommand[binding.command].push binding.keystroke
@sendToBrowserProcess(@template, keystrokesByCommand)
# Private:
loadPlatformItems: ->
menusDirPath = path.join(@resourcePath, 'menus')
platformMenuPath = fs.resolve(menusDirPath, process.platform, ['cson', 'json'])
{menu} = CSON.readFileSync(platformMenuPath)
@add(menu)
# Private: Merges an item in a submenu aware way such that new items are always
# Merges an item in a submenu aware way such that new items are always
# appended to the bottom of existing menus where possible.
merge: (menu, item) ->
item = _.deepClone(item)
@@ -72,7 +92,7 @@ class MenuManager
else
menu.push(item) unless _.find(menu, (i) => @normalizeLabel(i.label) == @normalizeLabel(item.label))
# Private: OSX can't handle displaying accelerators for multiple keystrokes.
# OSX can't handle displaying accelerators for multiple keystrokes.
# If they are sent across, it will stop processing accelerators for the rest
# of the menu items.
filterMultipleKeystroke: (keystrokesByCommand) ->
@@ -85,12 +105,10 @@ class MenuManager
filtered[key].push(binding)
filtered
# Private:
sendToBrowserProcess: (template, keystrokesByCommand) ->
keystrokesByCommand = @filterMultipleKeystroke(keystrokesByCommand)
ipc.sendChannel 'update-application-menu', template, keystrokesByCommand
# Private:
normalizeLabel: (label) ->
return undefined unless label?
@@ -98,3 +116,7 @@ class MenuManager
label.replace(/\&/g, '')
else
label
# Get an {Array} of {String} classes for the given element.
classesForElement: (element) ->
element?.classList.toString().split(' ') ? []

View File

@@ -1,11 +1,14 @@
{Emitter} = require 'emissary'
fs = require 'fs-plus'
_ = require 'underscore-plus'
Q = require 'q'
Package = require './package'
path = require 'path'
# Public: Package manager for coordinating the lifecycle of Atom packages.
#
# An instance of this class is always available as the `atom.packages` global.
#
# Packages can be loaded, activated, and deactivated, and unloaded:
# * Loading a package reads and parses the package's metadata and resources
# such as keymaps, menus, stylesheets, etc.
@@ -17,13 +20,10 @@ path = require 'path'
#
# Packages can also be enabled/disabled via the `core.disabledPackages` config
# settings and also by calling `enablePackage()/disablePackage()`.
#
# An instance of this class is globally available via `atom.packages`.
module.exports =
class PackageManager
Emitter.includeInto(this)
# Private:
constructor: ({configDirPath, devMode, @resourcePath}) ->
@packageDirPaths = [path.join(configDirPath, "packages")]
if devMode
@@ -32,7 +32,6 @@ class PackageManager
@loadedPackages = {}
@activePackages = {}
@packageStates = {}
@observingDisabledPackages = false
@packageActivators = []
@registerPackageActivator(this, ['atom', 'textmate'])
@@ -47,11 +46,9 @@ class PackageManager
getPackageDirPaths: ->
_.clone(@packageDirPaths)
# Private:
getPackageState: (name) ->
@packageStates[name]
# Private:
setPackageState: (name, state) ->
@packageStates[name] = state
@@ -67,44 +64,44 @@ class PackageManager
pack?.disable()
pack
# Private: Activate all the packages that should be activated.
# Activate all the packages that should be activated.
activate: ->
for [activator, types] in @packageActivators
packages = @getLoadedPackagesForTypes(types)
activator.activatePackages(packages)
@emit 'activated'
# Private: another type of package manager can handle other package types.
# another type of package manager can handle other package types.
# See ThemeManager
registerPackageActivator: (activator, types) ->
@packageActivators.push([activator, types])
# Private:
activatePackages: (packages) ->
@activatePackage(pack.name) for pack in packages
@observeDisabledPackages()
# Private: Activate a single package by name
activatePackage: (name, options) ->
return pack if pack = @getActivePackage(name)
if pack = @loadPackage(name, options)
@activePackages[pack.name] = pack
pack.activate(options)
pack
# Activate a single package by name
activatePackage: (name) ->
if pack = @getActivePackage(name)
Q(pack)
else
pack = @loadPackage(name)
pack.activate().then =>
@activePackages[pack.name] = pack
pack
# Private: Deactivate all packages
# Deactivate all packages
deactivatePackages: ->
@deactivatePackage(pack.name) for pack in @getActivePackages()
@deactivatePackage(pack.name) for pack in @getLoadedPackages()
@unobserveDisabledPackages()
# Private: Deactivate the package with the given name
# Deactivate the package with the given name
deactivatePackage: (name) ->
if pack = @getActivePackage(name)
pack = @getLoadedPackage(name)
if @isPackageActive(name)
@setPackageState(pack.name, state) if state = pack.serialize?()
pack.deactivate()
delete @activePackages[pack.name]
else
throw new Error("No active package for name '#{name}'")
pack.deactivate()
delete @activePackages[pack.name]
# Public: Get an array of all the active packages
getActivePackages: ->
@@ -118,17 +115,12 @@ class PackageManager
isPackageActive: (name) ->
@getActivePackage(name)?
# Private:
unobserveDisabledPackages: ->
return unless @observingDisabledPackages
atom.config.unobserve('core.disabledPackages')
@observingDisabledPackages = false
@disabledPackagesSubscription?.off()
@disabledPackagesSubscription = null
# Private:
observeDisabledPackages: ->
return if @observingDisabledPackages
atom.config.observe 'core.disabledPackages', callNow: false, (disabledPackages, {previous}) =>
@disabledPackagesSubscription ?= atom.config.observe 'core.disabledPackages', callNow: false, (disabledPackages, {previous}) =>
packagesToEnable = _.difference(previous, disabledPackages)
packagesToDisable = _.difference(disabledPackages, previous)
@@ -136,10 +128,7 @@ class PackageManager
@activatePackage(packageName) for packageName in packagesToEnable
null
@observingDisabledPackages = true
# Private:
loadPackages: (options) ->
loadPackages: ->
# Ensure atom exports is already in the require cache so the load time
# of the first package isn't skewed by being the first to require atom
require '../exports/atom'
@@ -147,27 +136,24 @@ class PackageManager
packagePaths = @getAvailablePackagePaths()
packagePaths = packagePaths.filter (packagePath) => not @isPackageDisabled(path.basename(packagePath))
packagePaths = _.uniq packagePaths, (packagePath) -> path.basename(packagePath)
@loadPackage(packagePath, options) for packagePath in packagePaths
@loadPackage(packagePath) for packagePath in packagePaths
@emit 'loaded'
# Private:
loadPackage: (nameOrPath, options) ->
loadPackage: (nameOrPath) ->
if packagePath = @resolvePackagePath(nameOrPath)
name = path.basename(nameOrPath)
return pack if pack = @getLoadedPackage(name)
pack = Package.load(packagePath, options)
pack = Package.load(packagePath)
@loadedPackages[pack.name] = pack if pack?
pack
else
throw new Error("Could not resolve '#{nameOrPath}' to a package path")
# Private:
unloadPackages: ->
@unloadPackage(name) for name in _.keys(@loadedPackages)
null
# Private:
unloadPackage: (name) ->
if @isPackageActive(name)
throw new Error("Tried to unload active package '#{name}'")
@@ -189,9 +175,9 @@ class PackageManager
getLoadedPackages: ->
_.values(@loadedPackages)
# Private: Get packages for a certain package type
# Get packages for a certain package type
#
# * types: an {Array} of {String}s like ['atom', 'textmate']
# types - an {Array} of {String}s like ['atom', 'textmate'].
getLoadedPackagesForTypes: (types) ->
pack for pack in @getLoadedPackages() when pack.getType() in types
@@ -209,7 +195,6 @@ class PackageManager
isPackageDisabled: (name) ->
_.include(atom.config.get('core.disabledPackages') ? [], name)
# Private:
hasAtomEngine: (packagePath) ->
metadata = Package.loadMetadata(packagePath, true)
metadata?.engines?.atom?
@@ -218,7 +203,6 @@ class PackageManager
isBundledPackage: (name) ->
@getPackageDependencies().hasOwnProperty(name)
# Private:
getPackageDependencies: ->
unless @packageDependencies?
try

View File

@@ -1,7 +1,6 @@
CSON = require 'season'
{basename, join} = require 'path'
### Internal ###
module.exports =
class Package
@build: (path) ->
@@ -23,9 +22,9 @@ class Package
pack
@load: (path, options) ->
@load: (path) ->
pack = @build(path)
pack?.load(options)
pack?.load()
pack
@loadMetadata: (path, ignoreErrors=false) ->
@@ -44,9 +43,6 @@ class Package
constructor: (@path) ->
@name = basename(@path)
isActive: ->
atom.packages.isPackageActive(@name)
enable: ->
atom.config.removeAtKeyPath('core.disabledPackages', @metadata.name)
@@ -54,9 +50,8 @@ class Package
atom.config.pushAtKeyPath('core.disabledPackages', @metadata.name)
isTheme: ->
!!@metadata?.theme
@metadata?.theme?
# Private:
measure: (key, fn) ->
startTime = Date.now()
value = fn()

View File

@@ -1,7 +1,6 @@
{View} = require './space-pen-extensions'
PaneView = null
### Internal ###
module.exports =
class PaneAxisView extends View
initialize: (@model) ->

View File

@@ -2,7 +2,6 @@
_ = require 'underscore-plus'
PaneAxisView = require './pane-axis-view'
# Internal:
module.exports =
class PaneColumnView extends PaneAxisView

View File

@@ -3,7 +3,7 @@ Delegator = require 'delegato'
PaneView = require './pane-view'
PaneContainer = require './pane-container'
# Private: Manages the list of panes within a {WorkspaceView}
# Manages the list of panes within a {WorkspaceView}
module.exports =
class PaneContainerView extends View
Delegator.includeInto(this)
@@ -27,8 +27,6 @@ class PaneContainerView extends View
viewClass = model.getViewClass()
model._view ?= new viewClass(model)
### Public ###
getRoot: ->
@children().first().view()
@@ -98,3 +96,50 @@ class PaneContainerView extends View
focusPreviousPane: ->
@model.activatePreviousPane()
focusPaneAbove: ->
@nearestPaneInDirection('above')?.focus()
focusPaneBelow: ->
@nearestPaneInDirection('below')?.focus()
focusPaneOnLeft: ->
@nearestPaneInDirection('left')?.focus()
focusPaneOnRight: ->
@nearestPaneInDirection('right')?.focus()
nearestPaneInDirection: (direction) ->
distance = (pointA, pointB) ->
x = pointB.x - pointA.x
y = pointB.y - pointA.y
Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2))
pane = @getActivePane()
box = @boundingBoxForPane(pane)
panes = @getPanes()
.filter (otherPane) =>
otherBox = @boundingBoxForPane(otherPane)
switch direction
when 'left' then otherBox.right.x <= box.left.x
when 'right' then otherBox.left.x >= box.right.x
when 'above' then otherBox.bottom.y <= box.top.y
when 'below' then otherBox.top.y >= box.bottom.y
.sort (paneA, paneB) =>
boxA = @boundingBoxForPane(paneA)
boxB = @boundingBoxForPane(paneB)
switch direction
when 'left' then distance(box.left, boxA.right) - distance(box.left, boxB.right)
when 'right' then distance(box.right, boxA.left) - distance(box.right, boxB.left)
when 'above' then distance(box.top, boxA.bottom) - distance(box.top, boxB.bottom)
when 'below' then distance(box.bottom, boxA.top) - distance(box.bottom, boxB.top)
panes[0]
boundingBoxForPane: (pane) ->
boundingBox = pane[0].getBoundingClientRect()
left: {x: boundingBox.left, y: boundingBox.top}
right: {x: boundingBox.right, y: boundingBox.top}
top: {x: boundingBox.left, y: boundingBox.top}
bottom: {x: boundingBox.left, y: boundingBox.bottom}

View File

@@ -86,6 +86,6 @@ class PaneContainer extends Model
itemDestroyed: (item) ->
@emit 'item-destroyed', item
# Private: Called by Model superclass when destroyed
# Called by Model superclass when destroyed
destroyed: ->
pane.destroy() for pane in @getPanes()

View File

@@ -2,8 +2,6 @@
_ = require 'underscore-plus'
PaneAxisView = require './pane-axis-view'
### Internal ###
module.exports =
class PaneRowView extends PaneAxisView
@content: ->

View File

@@ -27,11 +27,10 @@ class PaneView extends View
'destroyItem', 'destroyItems', 'destroyActiveItem', 'destroyInactiveItems',
'saveActiveItem', 'saveActiveItemAs', 'saveItem', 'saveItemAs', 'saveItems',
'itemForUri', 'activateItemForUri', 'promptToSaveItem', 'copyActiveItem', 'isActive',
'activate', toProperty: 'model'
'activate', 'getActiveItem', toProperty: 'model'
previousActiveItem: null
# Private:
initialize: (args...) ->
if args[0] instanceof Pane
@model = args[0]
@@ -97,7 +96,6 @@ class PaneView extends View
# Deprecated: Use ::activatePreviousItem
showPreviousItem: -> @activatePreviousItem()
# Private:
afterAttach: (onDom) ->
@focus() if @model.focused and onDom
@@ -167,11 +165,9 @@ class PaneView extends View
@unsubscribe(item) if typeof item.off is 'function'
@trigger 'pane:before-item-destroyed', [item]
# Private:
activeItemTitleChanged: =>
@trigger 'pane:active-item-title-changed'
# Private:
viewForItem: (item) ->
return unless item?
if item instanceof $
@@ -184,7 +180,6 @@ class PaneView extends View
@viewsByItem.set(item, view)
view
# Private:
@::accessor 'activeView', -> @viewForItem(@activeItem)
splitLeft: (items...) -> @model.splitLeft({items})._view
@@ -195,14 +190,15 @@ class PaneView extends View
splitDown: (items...) -> @model.splitDown({items})._view
# Public:
# Public: Get the container view housing this pane.
#
# Returns a {View}.
getContainer: ->
@closest('.panes').view()
beforeRemove: ->
@model.destroy() unless @model.isDestroyed()
# Private:
remove: (selector, keepData) ->
return super if keepData
@unsubscribe()

View File

@@ -3,6 +3,7 @@
{Model, Sequence} = require 'theorist'
Serializable = require 'serializable'
PaneAxis = require './pane-axis'
Editor = require './editor'
PaneView = null
# Public: A container for multiple items, one of which is *active* at a given
@@ -27,7 +28,6 @@ class Pane extends Model
.map((activePane) => activePane is this)
.distinctUntilChanged()
# Private:
constructor: (params) ->
super
@@ -43,31 +43,31 @@ class Pane extends Model
@activate() if params?.active
# Private: Called by the Serializable mixin during serialization.
# Called by the Serializable mixin during serialization.
serializeParams: ->
items: compact(@items.map((item) -> item.serialize?()))
activeItemUri: @activeItem?.getUri?()
focused: @focused
active: @active
# Private: Called by the Serializable mixin during deserialization.
# Called by the Serializable mixin during deserialization.
deserializeParams: (params) ->
{items, activeItemUri} = params
params.items = compact(items.map (itemState) -> atom.deserializers.deserialize(itemState))
params.activeItem = find params.items, (item) -> item.getUri?() is activeItemUri
params
# Private: Called by the view layer to construct a view for this model.
# Called by the view layer to construct a view for this model.
getViewClass: -> PaneView ?= require './pane-view'
isActive: -> @active
# Private: Called by the view layer to indicate that the pane has gained focus.
# Called by the view layer to indicate that the pane has gained focus.
focus: ->
@focused = true
@activate() unless @isActive()
# Private: Called by the view layer to indicate that the pane has lost focus.
# Called by the view layer to indicate that the pane has lost focus.
blur: ->
@focused = false
true # if this is called from an event handler, don't cancel it
@@ -78,13 +78,25 @@ class Pane extends Model
@container?.activePane = this
@emit 'activated'
# Private:
getPanes: -> [this]
# Public:
# Public: Get the items in this pane.
#
# Returns an {Array} of items.
getItems: ->
@items.slice()
# Public: Get the active pane item in this pane.
#
# Returns a pane item.
getActiveItem: ->
@activeItem
# Public: Returns an {Editor} if the pane item is an {Editor}, or null
# otherwise.
getActiveEditor: ->
@activeItem if @activeItem instanceof Editor
# Public: Returns the item at the specified index.
itemAtIndex: (index) ->
@items[index]
@@ -105,15 +117,15 @@ class Pane extends Model
else
@activateItemAtIndex(@items.length - 1)
# Public: Returns the index of the current active item.
# Returns the index of the current active item.
getActiveItemIndex: ->
@items.indexOf(@activeItem)
# Public: Makes the item at the given index active.
# Makes the item at the given index active.
activateItemAtIndex: (index) ->
@activateItem(@itemAtIndex(index))
# Public: Makes the given item active, adding the item if necessary.
# Makes the given item active, adding the item if necessary.
activateItem: (item) ->
if item?
@addItem(item)
@@ -121,11 +133,9 @@ class Pane extends Model
# Public: Adds the item to the pane.
#
# * item:
# The item to add. It can be a model with an associated view or a view.
# * index:
# An optional index at which to add the item. If omitted, the item is
# added after the current active item.
# item - The item to add. It can be a model with an associated view or a view.
# index - An optional index at which to add the item. If omitted, the item is
# added after the current active item.
#
# Returns the added item
addItem: (item, index=@getActiveItemIndex() + 1) ->
@@ -138,12 +148,11 @@ class Pane extends Model
# Public: Adds the given items to the pane.
#
# * items:
# An {Array} of items to add. Items can be models with associated views
# or views. Any items that are already present in items will not be added.
# * index:
# An optional index at which to add the item. If omitted, the item is
# added after the current active item.
# items - An {Array} of items to add. Items can be models with associated
# views or views. Any items that are already present in items will
# not be added.
# index - An optional index at which to add the item. If omitted, the item is
# added after the current active item.
#
# Returns an {Array} of the added items
addItems: (items, index=@getActiveItemIndex() + 1) ->
@@ -151,7 +160,6 @@ class Pane extends Model
@addItem(item, index + i) for item, i in items
items
# Private:
removeItem: (item, destroying) ->
index = @items.indexOf(item)
return if index is -1
@@ -207,7 +215,7 @@ class Pane extends Model
destroy: ->
super unless @container?.isAlive() and @container?.getPanes().length is 1
# Private: Called by model superclass.
# Called by model superclass.
destroyed: ->
@container.activateNextPane() if @isActive()
item.destroy?() for item in @items.slice()
@@ -238,8 +246,9 @@ class Pane extends Model
# Public: Saves the specified item.
#
# * item: The item to save.
# * nextAction: An optional function which will be called after the item is saved.
# item - The item to save.
# nextAction - An optional function which will be called after the item is
# saved.
saveItem: (item, nextAction) ->
if item?.getUri?()
item.save?()
@@ -249,8 +258,9 @@ class Pane extends Model
# Public: Saves the given item at a prompted-for location.
#
# * item: The item to save.
# * nextAction: An optional function which will be called after the item is saved.
# item - The item to save.
# nextAction - An optional function which will be called after the item is
# saved.
saveItemAs: (item, nextAction) ->
return unless item?.saveAs?
@@ -279,15 +289,14 @@ class Pane extends Model
else
false
# Private:
copyActiveItem: ->
if @activeItem?
@activeItem.copy?() ? atom.deserializers.deserialize(@activeItem.serialize())
# Public: Creates a new pane to the left of the receiver.
#
# * params:
# + items: An optional array of items with which to construct the new pane.
# params - An object with keys:
# :items - An optional array of items with which to construct the new pane.
#
# Returns the new {Pane}.
splitLeft: (params) ->
@@ -295,8 +304,8 @@ class Pane extends Model
# Public: Creates a new pane to the right of the receiver.
#
# * params:
# + items: An optional array of items with which to construct the new pane.
# params - An object with keys:
# :items - An optional array of items with which to construct the new pane.
#
# Returns the new {Pane}.
splitRight: (params) ->
@@ -304,8 +313,8 @@ class Pane extends Model
# Public: Creates a new pane above the receiver.
#
# * params:
# + items: An optional array of items with which to construct the new pane.
# params - An object with keys:
# :items - An optional array of items with which to construct the new pane.
#
# Returns the new {Pane}.
splitUp: (params) ->
@@ -313,14 +322,13 @@ class Pane extends Model
# Public: Creates a new pane below the receiver.
#
# * params:
# + items: An optional array of items with which to construct the new pane.
# params - An object with keys:
# :items - An optional array of items with which to construct the new pane.
#
# Returns the new {Pane}.
splitDown: (params) ->
@split('vertical', 'after', params)
# Private:
split: (orientation, side, params) ->
if @parent.orientation isnt orientation
@parent.replaceChild(this, new PaneAxis({@container, orientation, children: [this]}))
@@ -333,7 +341,7 @@ class Pane extends Model
newPane.activate()
newPane
# Private: If the parent is a horizontal axis, returns its first child;
# If the parent is a horizontal axis, returns its first child;
# otherwise this pane.
findLeftmostSibling: ->
if @parent.orientation is 'horizontal'
@@ -341,7 +349,7 @@ class Pane extends Model
else
this
# Private: If the parent is a horizontal axis, returns its last child;
# If the parent is a horizontal axis, returns its last child;
# otherwise returns a new pane created by splitting this pane rightward.
findOrCreateRightmostSibling: ->
if @parent.orientation is 'horizontal'

View File

@@ -1,36 +0,0 @@
clipboard = require 'clipboard'
crypto = require 'crypto'
# Public: Represents the clipboard used for copying and pasting in Atom.
#
# A pasteboard instance is always available under the `atom.pasteboard` global.
module.exports =
class Pasteboard
signatureForMetadata: null
# Creates an `md5` hash of some text.
#
# text - A {String} to encrypt.
#
# Returns an encrypted {String}.
md5: (text) ->
crypto.createHash('md5').update(text, 'utf8').digest('hex')
# Public: Write the given text to the clipboard.
#
# text - A {String} to store.
# metadata - An {Object} of additional info to associate with the text.
write: (text, metadata) ->
@signatureForMetadata = @md5(text)
@metadata = metadata
clipboard.writeText(text)
# Public: Read the text from the clipboard.
#
# Returns an {Array}. The first element is the saved text and the second is
# any metadata associated with the text.
read: ->
text = clipboard.readText()
value = [text]
value.push(@metadata) if @signatureForMetadata == @md5(text)
value

View File

@@ -16,7 +16,7 @@ Git = require './git'
# Public: Represents a project that's opened in Atom.
#
# There is always a project available under the `atom.project` global.
# An instance of this class is always available as the `atom.project` global.
module.exports =
class Project extends Model
atom.deserializers.add(this)
@@ -30,11 +30,12 @@ class Project extends Model
constructor: ({path, @buffers}={}) ->
@buffers ?= []
@openers = []
for buffer in @buffers
do (buffer) =>
buffer.once 'destroyed', => @removeBuffer(buffer)
@openers = []
@editors = []
@setPath(path)
@@ -46,36 +47,16 @@ class Project extends Model
params.buffers = params.buffers.map (bufferState) -> atom.deserializers.deserialize(bufferState)
params
# Public: Register an opener for project files.
#
# An {Editor} will be used if no openers return a value.
#
# ## Example:
# ```coffeescript
# atom.project.registerOpener (filePath) ->
# if path.extname(filePath) is '.toml'
# return new TomlEditor(filePath)
# ```
#
# * opener: A function to be called when a path is being opened.
registerOpener: (opener) -> @openers.push(opener)
# Public: Remove a previously registered opener.
unregisterOpener: (opener) -> _.remove(@openers, opener)
# Private:
destroyed: ->
editor.destroy() for editor in @getEditors()
buffer.destroy() for buffer in @getBuffers()
@destroyRepo()
# Private:
destroyRepo: ->
if @repo?
@repo.destroy()
@repo = null
# Private:
destroyUnretainedBuffers: ->
buffer.destroy() for buffer in @getBuffers() when not buffer.isRetained()
@@ -111,8 +92,7 @@ class Project extends Model
# the path is already absolute or if it is prefixed with a scheme, it is
# returned unchanged.
#
# * uri:
# The String name of the path to convert
# uri - The {String} name of the path to convert.
#
# Returns a String.
resolve: (uri) ->
@@ -133,71 +113,53 @@ class Project extends Model
contains: (pathToCheck) ->
@rootDirectory?.contains(pathToCheck) ? false
# Public: Given a path to a file, this constructs and associates a new
# Given a path to a file, this constructs and associates a new
# {Editor}, showing the file.
#
# * filePath:
# The {String} path of the file to associate with
# * options:
# Options that you can pass to the {Editor} constructor
# filePath - The {String} path of the file to associate with.
# options - Options that you can pass to the {Editor} constructor.
#
# Returns a promise that resolves to an {Editor}.
open: (filePath, options={}) ->
filePath = @resolve(filePath)
resource = null
_.find @openers, (opener) -> resource = opener(filePath, options)
@bufferForPath(filePath).then (buffer) =>
@buildEditorForBuffer(buffer, options)
if resource
Q(resource)
else
@bufferForPath(filePath).then (buffer) =>
@buildEditorForBuffer(buffer, options)
# Private: Only be used in specs
# Deprecated
openSync: (filePath, options={}) ->
filePath = @resolve(filePath)
for opener in @openers
return resource if resource = opener(filePath, options)
@buildEditorForBuffer(@bufferForPathSync(filePath), options)
# Public: Retrieves all {Editor}s for all open files.
#
# Returns an {Array} of {Editor}s.
getEditors: ->
new Array(@editors...)
# Public: Add the given {Editor}.
# Add the given {Editor}.
addEditor: (editor) ->
@editors.push editor
@emit 'editor-created', editor
# Public: Return and removes the given {Editor}.
# Return and removes the given {Editor}.
removeEditor: (editor) ->
_.remove(@editors, editor)
# Private: Retrieves all the {TextBuffer}s in the project; that is, the
# Retrieves all the {TextBuffer}s in the project; that is, the
# buffers for all open files.
#
# Returns an {Array} of {TextBuffer}s.
getBuffers: ->
@buffers.slice()
# Private: Is the buffer for the given path modified?
# Is the buffer for the given path modified?
isPathModified: (filePath) ->
@findBufferForPath(@resolve(filePath))?.isModified()
# Private:
findBufferForPath: (filePath) ->
_.find @buffers, (buffer) -> buffer.getPath() == filePath
# Private: Only to be used in specs
# Only to be used in specs
bufferForPathSync: (filePath) ->
absoluteFilePath = @resolve(filePath)
existingBuffer = @findBufferForPath(absoluteFilePath) if filePath
existingBuffer ? @buildBufferSync(absoluteFilePath)
# Private: Given a file path, this retrieves or creates a new {TextBuffer}.
# Given a file path, this retrieves or creates a new {TextBuffer}.
#
# If the `filePath` already has a `buffer`, that value is used instead. Otherwise,
# `text` is used as the contents of the new buffer.
@@ -210,21 +172,20 @@ class Project extends Model
existingBuffer = @findBufferForPath(absoluteFilePath) if absoluteFilePath
Q(existingBuffer ? @buildBuffer(absoluteFilePath))
# Private:
bufferForId: (id) ->
_.find @buffers, (buffer) -> buffer.id is id
# Private: DEPRECATED
# DEPRECATED
buildBufferSync: (absoluteFilePath) ->
buffer = new TextBuffer({filePath: absoluteFilePath})
@addBuffer(buffer)
buffer.loadSync()
buffer
# Private: Given a file path, this sets its {TextBuffer}.
# Given a file path, this sets its {TextBuffer}.
#
# absoluteFilePath - A {String} representing a path
# text - The {String} text to use as a buffer
# absoluteFilePath - A {String} representing a path.
# text - The {String} text to use as a buffer.
#
# Returns a promise that resolves to the {TextBuffer}.
buildBuffer: (absoluteFilePath) ->
@@ -234,38 +195,33 @@ class Project extends Model
.then((buffer) -> buffer)
.catch(=> @removeBuffer(buffer))
# Private:
addBuffer: (buffer, options={}) ->
@addBufferAtIndex(buffer, @buffers.length, options)
buffer.once 'destroyed', => @removeBuffer(buffer)
# Private:
addBufferAtIndex: (buffer, index, options={}) ->
@buffers.splice(index, 0, buffer)
buffer.once 'destroyed', => @removeBuffer(buffer)
@emit 'buffer-created', buffer
buffer
# Private: Removes a {TextBuffer} association from the project.
# Removes a {TextBuffer} association from the project.
#
# Returns the removed {TextBuffer}.
removeBuffer: (buffer) ->
index = @buffers.indexOf(buffer)
@removeBufferAtIndex(index) unless index is -1
# Private:
removeBufferAtIndex: (index, options={}) ->
[buffer] = @buffers.splice(index, 1)
buffer?.destroy()
# Public: Performs a search across all the files in the project.
#
# * regex:
# A RegExp to search with
# * options:
# - paths: an {Array} of glob patterns to search within
# * iterator:
# A Function callback on each file found
# regex - A {RegExp} to search with.
# options - An optional options {Object} (default: {}):
# :paths - An {Array} of glob patterns to search within
# iterator - A {Function} callback on each file found
scan: (regex, options={}, iterator) ->
if _.isFunction(options)
iterator = options
@@ -304,10 +260,11 @@ class Project extends Model
# Public: Performs a replace across all the specified files in the project.
#
# * regex: A RegExp to search with
# * replacementText: Text to replace all matches of regex with
# * filePaths: List of file path strings to run the replace on.
# * iterator: A Function callback on each file with replacements. `({filePath, replacements}) ->`
# regex - A {RegExp} to search with.
# replacementText - Text to replace all matches of regex with
# filePaths - List of file path strings to run the replace on.
# iterator - A {Function} callback on each file with replacements:
# `({filePath, replacements}) ->`.
replace: (regex, replacementText, filePaths, iterator) ->
deferred = Q.defer()
@@ -339,18 +296,11 @@ class Project extends Model
deferred.promise
# Private:
buildEditorForBuffer: (buffer, editorOptions) ->
editor = new Editor(_.extend({buffer}, editorOptions))
@addEditor(editor)
editor
# Private:
eachEditor: (callback) ->
callback(editor) for editor in @getEditors()
@on 'editor-created', (editor) -> callback(editor)
# Private:
eachBuffer: (args...) ->
subscriber = args.shift() if args.length > 1
callback = args.shift()
@@ -360,3 +310,20 @@ class Project extends Model
subscriber.subscribe this, 'buffer-created', (buffer) -> callback(buffer)
else
@on 'buffer-created', (buffer) -> callback(buffer)
# Deprecated: delegate
registerOpener: (opener) ->
@openers.push(opener)
# Deprecated: delegate
unregisterOpener: (opener) ->
_.remove(@openers, opener)
# Deprecated: delegate
eachEditor: (callback) ->
callback(editor) for editor in @getEditors()
@on 'editor-created', (editor) -> callback(editor)
# Deprecated: delegate
getEditors: ->
new Array(@editors...)

View File

@@ -2,8 +2,14 @@
# Public: Represents a view that scrolls.
#
# This `View` subclass listens to events such as `page-up`, `page-down`,
# `move-to-top`, and `move-to-bottom`.
# Subclasses must call `super` if overriding the `initialize` method or else
# the following events won't be handled by the ScrollView.
#
# ## Events
# * `core:page-up`
# * `core:page-down`
# * `core:move-to-top`
# * `core:move-to-bottom`
#
# ## Requiring in packages
#
@@ -12,8 +18,6 @@
# ```
module.exports =
class ScrollView extends View
# Internal: The constructor.
initialize: ->
@on 'core:page-up', => @pageUp()
@on 'core:page-down', => @pageDown()

View File

@@ -12,8 +12,6 @@ fuzzyFilter = require('fuzzaldrin').filter
# ```
module.exports =
class SelectListView extends View
# Private:
@content: ->
@div class: @viewClass(), =>
@subview 'miniEditor', new EditorView(mini: true)
@@ -23,7 +21,6 @@ class SelectListView extends View
@span class: 'badge', outlet: 'loadingBadge'
@ol class: 'list-group', outlet: 'list'
# Private:
@viewClass: -> 'select-list'
maxItems: Infinity
@@ -59,7 +56,6 @@ class SelectListView extends View
@confirmSelection() if $(e.target).closest('li').hasClass('selected')
e.preventDefault()
# Private:
schedulePopulateList: ->
clearTimeout(@scheduleTimeout)
populateCallback = =>
@@ -68,14 +64,14 @@ class SelectListView extends View
# Public: Set the array of items to display in the list.
#
# * array: The array of model elements to display in the list.
# array - The {Array} of model elements to display in the list.
setArray: (@array=[]) ->
@populateList()
@setLoading()
# Public: Set the error message to display.
#
# * message: The error message.
# message - The {String} error message (default: '').
setError: (message='') ->
if message.length is 0
@error.text('').hide()
@@ -85,7 +81,7 @@ class SelectListView extends View
# Public: Set the loading message to display.
#
# * message: The loading message.
# message - The {String} loading message (default: '').
setLoading: (message='') ->
if message.length is 0
@loading.text("")
@@ -135,30 +131,26 @@ class SelectListView extends View
#
# Subclasses may override this method to customize the message.
#
# * itemCount: The number of items in the array specified to {.setArray}
# * filteredItemCount: The number of items that pass the fuzzy filter test.
# itemCount - The {Number} of items in the array specified to {.setArray}
# filteredItemCount - The {Number} of items that pass the fuzzy filter test.
getEmptyMessage: (itemCount, filteredItemCount) -> 'No matches found'
# Private:
selectPreviousItem: ->
item = @getSelectedItem().prev()
item = @list.find('li:last') unless item.length
@selectItem(item)
# Private:
selectNextItem: ->
item = @getSelectedItem().next()
item = @list.find('li:first') unless item.length
@selectItem(item)
# Private:
selectItem: (item) ->
return unless item.length
@list.find('.selected').removeClass('selected')
item.addClass 'selected'
@scrollToItem(item)
# Private:
scrollToItem: (item) ->
scrollTop = @list.scrollTop()
desiredTop = item.position().top + scrollTop
@@ -181,7 +173,6 @@ class SelectListView extends View
getSelectedElement: ->
@getSelectedItem().data('select-list-element')
# Private:
confirmSelection: ->
element = @getSelectedElement()
if element?
@@ -193,25 +184,21 @@ class SelectListView extends View
#
# This method should be overridden by subclasses.
#
# * element: The selected model element.
# element - The selected model element.
confirmed: (element) ->
# Private:
attach: ->
@storeFocusedElement()
# Private:
storeFocusedElement: ->
@previouslyFocusedElement = $(':focus')
# Private:
restoreFocus: ->
if @previouslyFocusedElement?.isOnDom()
@previouslyFocusedElement.focus()
else
atom.workspaceView.focus()
# Private:
cancelled: ->
@miniEditor.getEditor().setText('')
@miniEditor.updateDisplay()

View File

@@ -1,7 +1,6 @@
{Point, Range} = require 'text-buffer'
{View, $$} = require './space-pen-extensions'
# Internal:
module.exports =
class SelectionView extends View

View File

@@ -14,7 +14,6 @@ class Selection
wordwise: false
needsAutoscroll: null
# Private:
constructor: ({@cursor, @marker, @editor}) ->
@cursor.selection = this
@marker.on 'changed', => @screenRangeChanged()
@@ -23,18 +22,15 @@ class Selection
@editor.removeSelection(this)
@emit 'destroyed' unless @editor.isDestroyed()
# Private:
destroy: ->
@marker.destroy()
# Private:
finalize: ->
@initialScreenRange = null unless @initialScreenRange?.isEqual(@getScreenRange())
if @isEmpty()
@wordwise = false
@linewise = false
# Private:
clearAutoscroll: ->
@needsAutoscroll = null
@@ -59,10 +55,8 @@ class Selection
# Public: Modifies the screen range for the selection.
#
# * screenRange:
# The new {Range} to use
# * options:
# + A hash of options matching those found in {.setBufferRange}
# screenRange - The new {Range} to use.
# options - A hash of options matching those found in {.setBufferRange}.
setScreenRange: (screenRange, options) ->
@setBufferRange(@editor.bufferRangeForScreenRange(screenRange), options)
@@ -72,13 +66,11 @@ class Selection
# Public: Modifies the buffer {Range} for the selection.
#
# * screenRange:
# The new {Range} to select
# * options
# + preserveFolds:
# if `true`, the fold settings are preserved after the selection moves
# + autoscroll:
# if `true`, the {Editor} scrolls to the new selection
# screenRange - The new {Range} to select.
# options - An {Object} with the keys:
# :preserveFolds - if `true`, the fold settings are preserved after the
# selection moves.
# :autoscroll - if `true`, the {Editor} scrolls to the new selection.
setBufferRange: (bufferRange, options={}) ->
bufferRange = Range.fromObject(bufferRange)
@needsAutoscroll = options.autoscroll
@@ -128,8 +120,7 @@ class Selection
# Public: Selects an entire line in the buffer.
#
# * row:
# The line Number to select (default: the row of the cursor)
# row - The line {Number} to select (default: the row of the cursor).
selectLine: (row=@cursor.getBufferPosition().row) ->
range = @editor.bufferRangeForBufferRow(row, includeNewline: true)
@setBufferRange(@getBufferRange().union(range))
@@ -148,8 +139,7 @@ class Selection
# Public: Selects the text from the current cursor position to a given screen
# position.
#
# * position:
# An instance of {Point}, with a given `row` and `column`.
# position - An instance of {Point}, with a given `row` and `column`.
selectToScreenPosition: (position) ->
@modifySelection =>
if @initialScreenRange
@@ -168,8 +158,7 @@ class Selection
# Public: Selects the text from the current cursor position to a given buffer
# position.
#
# * position:
# An instance of {Point}, with a given `row` and `column`.
# position - An instance of {Point}, with a given `row` and `column`.
selectToBufferPosition: (position) ->
@modifySelection => @cursor.setBufferPosition(position)
@@ -259,8 +248,6 @@ class Selection
@editor.addSelectionForBufferRange(range, goalBufferRange: range)
break
# Public:
#
# FIXME: I have no idea what this does.
getGoalBufferRange: ->
@marker.getAttributes().goalBufferRange
@@ -285,20 +272,14 @@ class Selection
# Public: Replaces text at the current selection.
#
# * text:
# A {String} representing the text to add
# * options
# + select:
# if `true`, selects the newly added text
# + autoIndent:
# if `true`, indents all inserted text appropriately
# + autoIndentNewline:
# if `true`, indent newline appropriately
# + autoDecreaseIndent:
# if `true`, decreases indent level appropriately (for example, when a
# closing bracket is inserted)
# + undo:
# if `skip`, skips the undo stack for this operation.
# text - A {String} representing the text to add
# options - An {Object} with keys:
# :select - if `true`, selects the newly added text.
# :autoIndent - if `true`, indents all inserted text appropriately.
# :autoIndentNewline - if `true`, indent newline appropriately.
# :autoDecreaseIndent - if `true`, decreases indent level appropriately
# (for example, when a closing bracket is inserted).
# :undo - if `skip`, skips the undo stack for this operation.
insertText: (text, options={}) ->
oldBufferRange = @getBufferRange()
@editor.destroyFoldsContainingBufferRow(oldBufferRange.end.row)
@@ -326,10 +307,8 @@ class Selection
# Public: Indents the given text to the suggested level based on the grammar.
#
# * text:
# The string to indent within the selection.
# * indentBasis:
# The beginning indent level.
# text - The {String} to indent within the selection.
# indentBasis - The beginning indent level.
normalizeIndents: (text, indentBasis) ->
textPrecedingCursor = @cursor.getCurrentBufferLine()[0...@cursor.getBufferColumn()]
isCursorInsideExistingLine = /\S/.test(textPrecedingCursor)
@@ -357,10 +336,9 @@ class Selection
# Public: Indents the selection.
#
# * options - A hash with one key,
# + autoIndent:
# If `true`, the indentation is performed appropriately. Otherwise,
# {Editor.getTabText} is used
# options - A {Object} with the keys:
# :autoIndent - If `true`, the indentation is performed appropriately.
# Otherwise, {Editor.getTabText} is used.
indent: ({ autoIndent }={})->
{ row, column } = @cursor.getBufferPosition()
@@ -505,35 +483,25 @@ class Selection
@editor.toggleLineCommentsForBufferRows(@getBufferRowRange()...)
# Public: Cuts the selection until the end of the line.
#
# * maintainPasteboard:
# ?
cutToEndOfLine: (maintainPasteboard) ->
cutToEndOfLine: (maintainClipboard) ->
@selectToEndOfLine() if @isEmpty()
@cut(maintainPasteboard)
@cut(maintainClipboard)
# Public: Copies the selection to the pasteboard and then deletes it.
#
# * maintainPasteboard:
# ?
cut: (maintainPasteboard=false) ->
@copy(maintainPasteboard)
# Public: Copies the selection to the clipboard and then deletes it.
cut: (maintainClipboard=false) ->
@copy(maintainClipboard)
@delete()
# Public: Copies the current selection to the pasteboard.
#
# * maintainPasteboard:
# ?
copy: (maintainPasteboard=false) ->
# Public: Copies the current selection to the clipboard.
copy: (maintainClipboard=false) ->
return if @isEmpty()
text = @editor.buffer.getTextInRange(@getBufferRange())
if maintainPasteboard
[currentText, metadata] = atom.pasteboard.read()
text = currentText + '\n' + text
if maintainClipboard
text = "#{atom.clipboard.read()}\n#{text}"
else
metadata = { indentBasis: @editor.indentationForBufferRow(@getBufferRange().start.row) }
atom.pasteboard.write(text, metadata)
atom.clipboard.write(text, metadata)
# Public: Creates a fold containing the current selection.
fold: ->
@@ -541,14 +509,13 @@ class Selection
@editor.createFold(range.start.row, range.end.row)
@cursor.setBufferPosition([range.end.row + 1, 0])
# Public: ?
modifySelection: (fn) ->
@retainSelection = true
@plantTail()
fn()
@retainSelection = false
# Private: Sets the marker's tail to the same position as the marker's head.
# Sets the marker's tail to the same position as the marker's head.
#
# This only works if there isn't already a tail position.
#
@@ -558,8 +525,7 @@ class Selection
# Public: Identifies if a selection intersects with a given buffer range.
#
# * bufferRange:
# A {Range} to check against
# bufferRange - A {Range} to check against.
#
# Returns a Boolean.
intersectsBufferRange: (bufferRange) ->
@@ -567,8 +533,7 @@ class Selection
# Public: Identifies if a selection intersects with another selection.
#
# * otherSelection:
# A {Selection} to check against
# otherSelection - A {Selection} to check against.
#
# Returns a Boolean.
intersectsWith: (otherSelection) ->
@@ -577,10 +542,8 @@ class Selection
# Public: Combines the given selection into this selection and then destroys
# the given selection.
#
# * otherSelection:
# A {Selection} to merge with
# * options
# + A hash of options matching those found in {.setBufferRange}
# otherSelection - A {Selection} to merge with.
# options - A hash of options matching those found in {.setBufferRange}.
merge: (otherSelection, options) ->
myGoalBufferRange = @getGoalBufferRange()
otherGoalBufferRange = otherSelection.getGoalBufferRange()
@@ -596,12 +559,10 @@ class Selection
#
# See {Range.compare} for more details.
#
# * otherSelection:
# A {Selection} to compare with.
# otherSelection - A {Selection} to compare against.
compare: (otherSelection) ->
@getBufferRange().compare(otherSelection.getBufferRange())
# Private:
screenRangeChanged: ->
screenRange = @getScreenRange()
@emit 'screen-range-changed', screenRange

View File

@@ -1,18 +1,13 @@
_ = require 'underscore-plus'
spacePen = require 'space-pen'
{Subscriber} = require 'emissary'
ConfigObserver = require './config-observer'
ConfigObserver.includeInto(spacePen.View)
Subscriber.includeInto(spacePen.View)
jQuery = spacePen.jQuery
originalCleanData = jQuery.cleanData
jQuery.cleanData = (elements) ->
for element in elements
if view = jQuery(element).view()
view.unobserveConfig()
view.unsubscribe()
jQuery(element).view()?.unsubscribe() for element in elements
originalCleanData(elements)
tooltipDefaults =

View File

@@ -8,14 +8,13 @@ Token = require './token'
# Public: Syntax class holding the grammars used for tokenizing.
#
# An instance of this class is always available as the `atom.syntax` global.
#
# The Syntax class also contains properties for things such as the
# language-specific comment regexes.
#
# There is always a syntax object available under the `atom.syntax` global.
module.exports =
class Syntax extends GrammarRegistry
Subscriber.includeInto(this)
atom.deserializers.add(this)
@deserialize: ({grammarOverridesByPath}) ->
@@ -62,8 +61,8 @@ class Syntax extends GrammarRegistry
# console.log(comment) # '# '
# ```
#
# * scope: An {Array} of {String} scopes.
# * keyPath: A {String} key path.
# scope - An {Array} of {String} scopes.
# keyPath - A {String} key path.
#
# Returns a {String} property value or undefined.
getProperty: (scope, keyPath) ->

View File

@@ -24,18 +24,16 @@ class Task
# Public: A helper method to easily launch and run a task once.
#
# * taskPath:
# The path to the Coffeescript/Javascript file which exports a single
# function to execute.
# * args:
# The Array of arguments to pass to the exported function.
# taskPath - The {String} path to the CoffeeScript/JavaScript file which
# exports a single {Function} to execute.
# args - The arguments to pass to the exported function.
@once: (taskPath, args...) ->
task = new Task(taskPath)
task.once 'task:completed', -> task.terminate()
task.start(args...)
task
# Public: Called upon task completion.
# Called upon task completion.
#
# It receives the same arguments that were passed to the task.
#
@@ -45,9 +43,8 @@ class Task
# Public: Creates a task.
#
# * taskPath:
# The path to the Coffeescript/Javascript file that exports a single
# function to execute.
# taskPath - The {String} path to the CoffeeScript/JavaScript file that
# exports a single {Function} to execute.
constructor: (taskPath) ->
coffeeCacheRequire = "require('#{require.resolve('./coffee-cache')}').register();"
coffeeScriptRequire = "require('#{require.resolve('coffee-script')}').register();"
@@ -73,7 +70,7 @@ class Task
@handleEvents()
# Private: Routes messages from the child to the appropriate event.
# Routes messages from the child to the appropriate event.
handleEvents: ->
@childProcess.removeAllListeners()
@childProcess.on 'message', ({event, args}) =>
@@ -81,21 +78,21 @@ class Task
# Public: Starts the task.
#
# * args:
# The Array of arguments to pass to the function exported by the script. If
# the last argument is a function, its removed from the array and called
# upon completion (and replaces the complete function on the task instance).
start: (args...) ->
# args - The arguments to pass to the function exported by this task's script.
# callback - An optional {Function} to call when the task completes.
start: (args..., callback) ->
throw new Error("Cannot start terminated process") unless @childProcess?
@handleEvents()
@callback = args.pop() if _.isFunction(args[args.length - 1])
if _.isFunction(callback)
@callback = callback
else
args = arguments
@send({event: 'start', args})
# Public: Send message to the task.
#
# * message:
# The message to send
# message - The message to send to the task.
send: (message) ->
throw new Error("Cannot send message to terminated process") unless @childProcess?
@childProcess.send(message)

View File

@@ -8,7 +8,7 @@ TextBufferCore = require 'text-buffer'
File = require './file'
# Private: Represents the contents of a file.
# Represents the contents of a file.
#
# The `TextBuffer` is often associated with a {File}. However, this is not always
# the case, as a `TextBuffer` could be an unsaved chunk of text.
@@ -40,7 +40,6 @@ class TextBuffer extends TextBufferCore
@load() if loadWhenAttached
# Private:
serializeParams: ->
params = super
_.extend params,
@@ -48,7 +47,6 @@ class TextBuffer extends TextBufferCore
modifiedWhenLastPersisted: @isModified()
digestWhenLastPersisted: @file?.getDigest()
# Private:
deserializeParams: (params) ->
params = super(params)
params.loadWhenAttached = true
@@ -71,8 +69,6 @@ class TextBuffer extends TextBufferCore
@clearUndoStack()
this
### Internal ###
handleTextChange: (event) =>
@conflict = false if @conflict and !@isModified()
@scheduleModifiedEvents()
@@ -127,8 +123,6 @@ class TextBuffer extends TextBufferCore
@file.on "moved", =>
@emit "path-changed", this
### Public ###
# Identifies if the buffer belongs to multiple editors.
#
# For example, if the {EditorView} was split.
@@ -145,11 +139,11 @@ class TextBuffer extends TextBufferCore
@emitModifiedStatusChanged(false)
@emit 'reloaded'
# Private: Rereads the contents of the file, and stores them in the cache.
# Rereads the contents of the file, and stores them in the cache.
updateCachedDiskContentsSync: ->
@cachedDiskContents = @file?.readSync() ? ""
# Private: Rereads the contents of the file, and stores them in the cache.
# Rereads the contents of the file, and stores them in the cache.
updateCachedDiskContents: ->
Q(@file?.read() ? "").then (contents) =>
@cachedDiskContents = contents
@@ -171,29 +165,20 @@ class TextBuffer extends TextBufferCore
# Sets the path for the file.
#
# path - A {String} representing the new file path
setPath: (path) ->
return if path == @getPath()
# filePath - A {String} representing the new file path
setPath: (filePath) ->
return if filePath == @getPath()
@file?.off()
if path
@file = new File(path)
if filePath
@file = new File(filePath)
@subscribeToFile()
else
@file = null
@emit "path-changed", this
# Retrieves the current buffer's file extension.
#
# Returns a {String}.
getExtension: ->
if @getPath()
@getPath().split('/').pop().split('.').pop()
else
null
# Deprecated: Use ::getEndPosition instead
getEofPosition: -> @getEndPosition()
@@ -203,14 +188,15 @@ class TextBuffer extends TextBufferCore
# Saves the buffer at a specific path.
#
# path - The path to save at.
saveAs: (path) ->
unless path then throw new Error("Can't save buffer with no file path")
# filePath - The path to save at.
saveAs: (filePath) ->
unless filePath then throw new Error("Can't save buffer with no file path")
@emit 'will-be-saved', this
@setPath(path)
@cachedDiskContents = @getText()
@setPath(filePath)
@file.write(@getText())
@cachedDiskContents = @getText()
@conflict = false
@emitModifiedStatusChanged(false)
@emit 'saved', this
@@ -227,7 +213,10 @@ class TextBuffer extends TextBufferCore
else
not @isEmpty()
# Identifies if a buffer is in a git conflict with `HEAD`.
# Is the buffer's text in conflict with the text on disk?
#
# This occurs when the buffer's file changes on disk while the buffer has
# unsaved changes.
#
# Returns a {Boolean}.
isInConflict: -> @conflict
@@ -390,8 +379,6 @@ class TextBuffer extends TextBufferCore
return match[0][0] != '\t'
undefined
### Internal ###
change: (oldRange, newText, options={}) ->
@setTextInRange(oldRange, newText, options.normalizeLineEndings)

View File

@@ -2,9 +2,7 @@ Package = require './package'
path = require 'path'
_ = require 'underscore-plus'
fs = require 'fs-plus'
async = require 'async'
### Internal ###
Q = require 'q'
module.exports =
class TextMatePackage extends Package
@@ -12,13 +10,12 @@ class TextMatePackage extends Package
packageName = path.basename(packageName)
/(^language-.+)|((\.|_|-)tmbundle$)/.test(packageName)
@getLoadQueue: ->
return @loadQueue if @loadQueue
@loadQueue = async.queue (pack, done) ->
pack.loadGrammars ->
pack.loadScopedProperties(done)
@loadQueue
@addToActivationPromise = (pack) ->
@activationPromise ?= Q()
@activationPromise = @activationPromise.then =>
pack.loadGrammars()
.then -> pack.loadScopedProperties()
.fail (error) -> console.log pack.name, error
constructor: ->
super
@@ -28,21 +25,16 @@ class TextMatePackage extends Package
getType: -> 'textmate'
load: ({sync}={}) ->
load: ->
@measure 'loadTime', =>
@metadata = Package.loadMetadata(@path, true)
if sync
@loadGrammarsSync()
@loadScopedPropertiesSync()
else
TextMatePackage.getLoadQueue().push(this)
activate: ({sync, immediate}={})->
TextMatePackage.addToActivationPromise(this)
activate: ->
@measure 'activateTime', =>
grammar.activate() for grammar in @grammars
for { selector, properties } in @scopedProperties
atom.syntax.addProperties(@path, selector, properties)
activateSync: ->
@loadGrammarsSync()
@loadScopedPropertiesSync()
activateConfig: -> # noop
@@ -52,33 +44,35 @@ class TextMatePackage extends Package
legalGrammarExtensions: ['plist', 'tmLanguage', 'tmlanguage', 'json', 'cson']
loadGrammars: (done) ->
loadGrammars: ->
deferred = Q.defer()
fs.isDirectory @getSyntaxesPath(), (isDirectory) =>
if isDirectory
fs.list @getSyntaxesPath(), @legalGrammarExtensions, (error, paths) =>
if error?
console.log("Error loading grammars of TextMate package '#{@path}':", error.stack, error)
done()
else
async.eachSeries(paths, @loadGrammarAtPath, done)
else
done()
return deferred.resolve() unless isDirectory
loadGrammarAtPath: (grammarPath, done) =>
fs.list @getSyntaxesPath(), @legalGrammarExtensions, (error, paths) =>
if error?
console.log("Error loading grammars of TextMate package '#{@path}':", error.stack, error)
deferred.resolve()
else
promises = paths.map (path) => @loadGrammarAtPath(path)
Q.all(promises).then -> deferred.resolve()
deferred.promise
loadGrammarAtPath: (grammarPath) ->
deferred = Q.defer()
atom.syntax.readGrammar grammarPath, (error, grammar) =>
if error?
console.log("Error loading grammar at path '#{grammarPath}':", error.stack ? error)
else
@addGrammar(grammar)
done?()
deferred.resolve()
loadGrammarsSync: ->
for grammarPath in fs.listSync(@getSyntaxesPath(), @legalGrammarExtensions)
@addGrammar(atom.syntax.readGrammarSync(grammarPath))
deferred.promise
addGrammar: (grammar) ->
@grammars.push(grammar)
grammar.activate() if @isActive()
grammar.activate()
getGrammars: -> @grammars
@@ -96,23 +90,7 @@ class TextMatePackage extends Package
else
path.join(@path, "Preferences")
loadScopedPropertiesSync: ->
for grammar in @getGrammars()
if properties = @propertiesFromTextMateSettings(grammar)
selector = atom.syntax.cssSelectorFromScopeSelector(grammar.scopeName)
@scopedProperties.push({selector, properties})
for preferencePath in fs.listSync(@getPreferencesPath())
{scope, settings} = fs.readObjectSync(preferencePath)
if properties = @propertiesFromTextMateSettings(settings)
selector = atom.syntax.cssSelectorFromScopeSelector(scope) if scope?
@scopedProperties.push({selector, properties})
if @isActive()
for {selector, properties} in @scopedProperties
atom.syntax.addProperties(@path, selector, properties)
loadScopedProperties: (callback) ->
loadScopedProperties: ->
scopedProperties = []
for grammar in @getGrammars()
@@ -120,38 +98,37 @@ class TextMatePackage extends Package
selector = atom.syntax.cssSelectorFromScopeSelector(grammar.scopeName)
scopedProperties.push({selector, properties})
preferenceObjects = []
done = =>
@loadTextMatePreferenceObjects().then (preferenceObjects=[]) =>
for {scope, settings} in preferenceObjects
if properties = @propertiesFromTextMateSettings(settings)
selector = atom.syntax.cssSelectorFromScopeSelector(scope) if scope?
scopedProperties.push({selector, properties})
@scopedProperties = scopedProperties
if @isActive()
for {selector, properties} in @scopedProperties
atom.syntax.addProperties(@path, selector, properties)
callback?()
@loadTextMatePreferenceObjects(preferenceObjects, done)
for {selector, properties} in @scopedProperties
atom.syntax.addProperties(@path, selector, properties)
loadTextMatePreferenceObjects: (preferenceObjects, done) ->
loadTextMatePreferenceObjects: ->
deferred = Q.defer()
fs.isDirectory @getPreferencesPath(), (isDirectory) =>
return done() unless isDirectory
return deferred.resolve() unless isDirectory
fs.list @getPreferencesPath(), (error, paths) =>
if error?
console.log("Error loading preferences of TextMate package '#{@path}':", error.stack, error)
done()
return
deferred.resolve()
else
promises = paths.map (path) => @loadPreferencesAtPath(path)
Q.all(promises).then (preferenceObjects) -> deferred.resolve(preferenceObjects)
loadPreferencesAtPath = (preferencePath, done) ->
fs.readObject preferencePath, (error, preferences) =>
if error?
console.warn("Failed to parse preference at path '#{preferencePath}'", error.stack, error)
else
preferenceObjects.push(preferences)
done()
async.eachSeries paths, loadPreferencesAtPath, done
deferred.promise
loadPreferencesAtPath: (preferencePath) ->
deferred = Q.defer()
fs.readObject preferencePath, (error, preference) ->
if error?
console.warn("Failed to parse preference at path '#{preferencePath}'", error.stack, error)
deferred.resolve(preference)
deferred.promise
propertiesFromTextMateSettings: (textMateSettings) ->
if textMateSettings.shellVariables
@@ -169,3 +146,24 @@ class TextMatePackage extends Package
completions: textMateSettings.completions
)
{ editor: editorProperties } if _.size(editorProperties) > 0
# Deprecated
loadGrammarsSync: ->
for grammarPath in fs.listSync(@getSyntaxesPath(), @legalGrammarExtensions)
@addGrammar(atom.syntax.readGrammarSync(grammarPath))
# Deprecated
loadScopedPropertiesSync: ->
for grammar in @getGrammars()
if properties = @propertiesFromTextMateSettings(grammar)
selector = atom.syntax.cssSelectorFromScopeSelector(grammar.scopeName)
@scopedProperties.push({selector, properties})
for preferencePath in fs.listSync(@getPreferencesPath())
{scope, settings} = fs.readObjectSync(preferencePath)
if properties = @propertiesFromTextMateSettings(settings)
selector = atom.syntax.cssSelectorFromScopeSelector(scope) if scope?
@scopedProperties.push({selector, properties})
for {selector, properties} in @scopedProperties
atom.syntax.addProperties(@path, selector, properties)

View File

@@ -3,6 +3,7 @@ path = require 'path'
_ = require 'underscore-plus'
{Emitter} = require 'emissary'
fs = require 'fs-plus'
Q = require 'q'
{$} = require './space-pen-extensions'
AtomPackage = require './atom-package'
@@ -10,7 +11,7 @@ File = require './file'
# Public: Handles loading and activating available themes.
#
# A ThemeManager instance is always available under the `atom.themes` global.
# An instance of this class is always available as the `atom.themes` global.
module.exports =
class ThemeManager
Emitter.includeInto(this)
@@ -41,7 +42,7 @@ class ThemeManager
activatePackages: (themePackages) -> @activateThemes()
# Private: Get the enabled theme names from the config.
# Get the enabled theme names from the config.
#
# Returns an array of theme names in the order that they should be activated.
getEnabledThemeNames: ->
@@ -53,19 +54,22 @@ class ThemeManager
themeNames.reverse()
activateThemes: ->
deferred = Q.defer()
# 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
for themeName in @getEnabledThemeNames()
@packageManager.activatePackage(themeName)
promises = @getEnabledThemeNames().map (themeName) => @packageManager.activatePackage(themeName)
Q.all(promises).then =>
@refreshLessCache() # Update cache again now that @getActiveThemes() is populated
@loadUserStylesheet()
@reloadBaseStylesheets()
@emit('reloaded')
deferred.resolve()
@refreshLessCache() # Update cache again now that @getActiveThemes() is populated
@loadUserStylesheet()
@reloadBaseStylesheets()
@emit('reloaded')
deferred.promise
deactivateThemes: ->
@unwatchUserStylesheet()
@@ -77,7 +81,7 @@ class ThemeManager
# Public: Set the list of enabled themes.
#
# * enabledThemeNames: An {Array} of {String} theme names.
# enabledThemeNames - An {Array} of {String} theme names.
setEnabledThemes: (enabledThemeNames) ->
atom.config.set('core.themes', enabledThemeNames)
@@ -91,7 +95,7 @@ class ThemeManager
if themePath = @packageManager.resolvePackagePath(themeName)
themePaths.push(path.join(themePath, AtomPackage.stylesheetsDir))
themePath for themePath in themePaths when fs.isDirectorySync(themePath)
themePaths.filter (themePath) -> fs.isDirectorySync(themePath)
# Public: Returns the {String} path to the user's stylesheet under ~/.atom
getUserStylesheetPath: ->
@@ -140,8 +144,9 @@ class ThemeManager
#
# This supports both CSS and LESS stylsheets.
#
# * stylesheetPath: A {String} path to the stylesheet that can be an absolute
# path or a relative path that will be resolved against the load path.
# stylesheetPath - A {String} path to the stylesheet that can be an absolute
# path or a relative path that will be resolved against the
# load path.
#
# Returns the absolute path to the required stylesheet.
requireStylesheet: (stylesheetPath, ttype = 'bundled', htmlElement) ->

View File

@@ -1,11 +1,8 @@
Q = require 'q'
AtomPackage = require './atom-package'
Package = require './package'
### Internal: Loads and resolves packages. ###
module.exports =
class ThemePackage extends AtomPackage
getType: -> 'theme'
getStylesheetType: -> 'theme'
@@ -25,6 +22,11 @@ class ThemePackage extends AtomPackage
this
activate: ->
return @activationDeferred.promise if @activationDeferred?
@activationDeferred = Q.defer()
@measure 'activateTime', =>
@loadStylesheets()
@activateNow()
@activationDeferred.promise

View File

@@ -12,7 +12,7 @@ WhitespaceRegex = /\S/
MaxTokenLength = 20000
# Private: Represents a single unit of text as selected by a grammar.
# Represents a single unit of text as selected by a grammar.
module.exports =
class Token
value: null
@@ -21,15 +21,11 @@ class Token
isAtomic: null
isHardTab: null
### Internal ###
constructor: ({@value, @scopes, @isAtomic, @bufferDelta, @isHardTab}) ->
@screenDelta = @value.length
@bufferDelta ?= @screenDelta
@hasSurrogatePair = textUtils.hasSurrogatePair(@value)
### Public ###
isEqual: (other) ->
@value == other.value and _.isEqual(@scopes, other.scopes) and !!@isAtomic == !!other.isAtomic

View File

@@ -5,8 +5,6 @@ Serializable = require 'serializable'
TokenizedLine = require './tokenized-line'
Token = require './token'
### Internal ###
module.exports =
class TokenizedBuffer extends Model
Serializable.includeInto(this)

View File

@@ -1,7 +1,5 @@
_ = require 'underscore-plus'
### Internal ###
module.exports =
class TokenizedLine
constructor: ({tokens, @lineEnding, @ruleStack, @startBufferColumn, @fold, tabLength}) ->

View File

@@ -1,9 +1,6 @@
# Like sands through the hourglass, so are the days of our lives.
startTime = Date.now()
# Start the crash reporter before anything else.
require('crash-reporter').start(productName: 'Atom', companyName: 'GitHub')
require './window'
Atom = require './atom'

View File

@@ -5,7 +5,7 @@ shell = require 'shell'
{Subscriber} = require 'emissary'
fs = require 'fs-plus'
# Private: Handles low-level events related to the window.
# Handles low-level events related to the window.
module.exports =
class WindowEventHandler
Subscriber.includeInto(this)
@@ -75,7 +75,7 @@ class WindowEventHandler
@handleNativeKeybindings()
# Private: Wire commands that should be handled by the native menu
# Wire commands that should be handled by the native menu
# for elements with the `.native-key-bindings` class.
handleNativeKeybindings: ->
menu = null

View File

@@ -1,9 +1,8 @@
# Public: Measure how long a function takes to run.
#
# * description:
# A {String} description that will be logged to the console.
# * fn:
# A {Function} to measure the duration of.
# description - A {String} description that will be logged to the console when
# the function completes.
# fn - A {Function} to measure the duration of.
#
# Returns the value returned by the given function.
window.measure = (description, fn) ->
@@ -15,13 +14,11 @@ window.measure = (description, fn) ->
# Public: Create a dev tools profile for a function.
#
# * description:
# A {String} descrption that will be available in the Profiles tab of the dev
# tools.
# * fn:
# A {Function} to profile.
# description - A {String} description that will be available in the Profiles
# tab of the dev tools.
# fn - A {Function} to profile.
#
# Return the value returned by the given function.
# Returns the value returned by the given function.
window.profile = (description, fn) ->
measure description, ->
console.profile(description)

View File

@@ -16,6 +16,9 @@ Editor = require './editor'
# Public: The container for the entire Atom application.
#
# An instance of this class is always available as the `atom.workspaceView`
# global.
#
# ## Commands
#
# * `application:about` - Opens the about dialog.
@@ -48,7 +51,7 @@ class WorkspaceView extends View
Delegator.includeInto(this)
@delegatesProperty 'fullScreen', 'destroyedItemUris', toProperty: 'model'
@delegatesMethods 'open', 'openSync', 'openSingletonSync', 'reopenItemSync',
@delegatesMethods 'open', 'openSync', 'reopenItemSync',
'saveActivePaneItem', 'saveActivePaneItemAs', 'saveAll', 'destroyActivePaneItem',
'destroyActivePane', 'increaseFontSize', 'decreaseFontSize', toProperty: 'model'
@@ -63,14 +66,12 @@ class WorkspaceView extends View
audioBeep: true
destroyEmptyPanes: false
# Private:
@content: ->
@div class: 'workspace', tabindex: -1, =>
@div class: 'horizontal', outlet: 'horizontal', =>
@div class: 'vertical', outlet: 'vertical', =>
@div class: 'panes', outlet: 'panes'
# Private:
initialize: (@model) ->
@model ?= new Workspace
@@ -106,6 +107,7 @@ class WorkspaceView extends View
@command 'application:zoom', -> ipc.sendChannel('command', 'application:zoom')
@command 'application:bring-all-windows-to-front', -> ipc.sendChannel('command', 'application:bring-all-windows-to-front')
@command 'application:open-your-config', -> ipc.sendChannel('command', 'application:open-your-config')
@command 'application:open-your-init-script', -> ipc.sendChannel('command', 'application:open-your-init-script')
@command 'application:open-your-keymap', -> ipc.sendChannel('command', 'application:open-your-keymap')
@command 'application:open-your-snippets', -> ipc.sendChannel('command', 'application:open-your-snippets')
@command 'application:open-your-stylesheet', -> ipc.sendChannel('command', 'application:open-your-stylesheet')
@@ -115,9 +117,14 @@ class WorkspaceView extends View
@command 'window:run-package-specs', => ipc.sendChannel('run-package-specs', path.join(atom.project.getPath(), 'spec'))
@command 'window:increase-font-size', => @increaseFontSize()
@command 'window:decrease-font-size', => @decreaseFontSize()
@command 'window:reset-font-size', => @model.resetFontSize()
@command 'window:focus-next-pane', => @focusNextPane()
@command 'window:focus-previous-pane', => @focusPreviousPane()
@command 'window:focus-pane-above', => @focusPaneAbove()
@command 'window:focus-pane-below', => @focusPaneBelow()
@command 'window:focus-pane-on-left', => @focusPaneOnLeft()
@command 'window:focus-pane-on-right', => @focusPaneOnRight()
@command 'window:save-all', => @saveAll()
@command 'window:toggle-invisibles', =>
atom.config.toggle("editor.showInvisibles")
@@ -135,23 +142,22 @@ class WorkspaceView extends View
showErrorDialog = (error) ->
installDirectory = CommandInstaller.getInstallDirectory()
atom.confirm
message: error.message
detailedMessage: "Make sure #{installDirectory} exists and is writable. Run 'sudo mkdir -p #{installDirectory} && sudo chown $USER #{installDirectory}' to fix this problem."
message: "Failed to install shell commands"
detailedMessage: error.message
resourcePath = atom.getLoadSettings().resourcePath
CommandInstaller.installAtomCommand resourcePath, (error) =>
CommandInstaller.installAtomCommand resourcePath, true, (error) =>
if error?
showDialog(error)
showErrorDialog(error)
else
CommandInstaller.installApmCommand resourcePath, (error) =>
CommandInstaller.installApmCommand resourcePath, true, (error) =>
if error?
showDialog(error)
showErrorDialog(error)
else
atom.confirm
message: "Commands installed."
detailedMessage: "The shell commands `atom` and `apm` are installed."
# Private:
handleFocus: (e) ->
if @getActivePane()
@getActivePane().focus()
@@ -166,7 +172,6 @@ class WorkspaceView extends View
$(document.body).focus()
true
# Private:
afterAttach: (onDom) ->
@focus() if onDom
@@ -188,7 +193,7 @@ class WorkspaceView extends View
setTitle: (title) ->
document.title = title
# Private: Returns an Array of all of the application's {EditorView}s.
# Returns an Array of all of the application's {EditorView}s.
getEditorViews: ->
@panes.find('.pane > .item-views > .editor').map(-> $(this).view()).toArray()
@@ -225,9 +230,13 @@ class WorkspaceView extends View
@horizontal.append(element)
# Public: Returns the currently focused {PaneView}.
getActivePane: ->
getActivePaneView: ->
@panes.getActivePane()
# Deprecated: Returns the currently focused {PaneView}.
getActivePane: ->
@getActivePaneView()
# Public: Returns the currently focused item from within the focused {PaneView}
getActivePaneItem: ->
@model.activePaneItem
@@ -242,6 +251,18 @@ class WorkspaceView extends View
# Public: Focuses the next pane by id.
focusNextPane: -> @model.activateNextPane()
# Public: Focuses the pane directly above the active pane.
focusPaneAbove: -> @panes.focusPaneAbove()
# Public: Focuses the pane directly below the active pane.
focusPaneBelow: -> @panes.focusPaneBelow()
# Public: Focuses the pane directly to the left of the active pane.
focusPaneOnLeft: -> @panes.focusPaneOnLeft()
# Public: Focuses the pane directly to the right of the active pane.
focusPaneOnRight: -> @panes.focusPaneOnRight()
# Public:
#
# FIXME: Difference between active and focused pane?
@@ -266,11 +287,11 @@ class WorkspaceView extends View
@on('editor:attached', attachedCallback)
off: => @off('editor:attached', attachedCallback)
# Private: Called by SpacePen
# Called by SpacePen
beforeRemove: ->
@model.destroy()
# Private: Destroys everything.
# Destroys everything.
remove: ->
editorView.remove() for editorView in @getEditorViews()
super

View File

@@ -7,8 +7,9 @@ PaneContainer = require './pane-container'
Pane = require './pane'
# Public: Represents the view state of the entire window, including the panes at
# the center and panels around the periphery. You can access the singleton
# instance via `atom.workspace`.
# the center and panels around the periphery.
#
# An instance of this class is always available as the `atom.workspace` global.
module.exports =
class Workspace extends Model
atom.deserializers.add(this)
@@ -23,11 +24,10 @@ class Workspace extends Model
fullScreen: false
destroyedItemUris: -> []
# Private:
constructor: ->
super
@subscribe @paneContainer, 'item-destroyed', @onPaneItemDestroyed
atom.project.registerOpener (filePath) =>
@registerOpener (filePath) =>
switch filePath
when 'atom://.atom/stylesheet'
@open(atom.themes.getUserStylesheetPath())
@@ -35,93 +35,126 @@ class Workspace extends Model
@open(atom.keymap.getUserKeymapPath())
when 'atom://.atom/config'
@open(atom.config.getUserConfigPath())
when 'atom://.atom/init-script'
@open(atom.getUserInitScriptPath())
# Private: Called by the Serializable mixin during deserialization
# Called by the Serializable mixin during deserialization
deserializeParams: (params) ->
params.paneContainer = PaneContainer.deserialize(params.paneContainer)
params
# Private: Called by the Serializable mixin during serialization.
# Called by the Serializable mixin during serialization.
serializeParams: ->
paneContainer: @paneContainer.serialize()
fullScreen: atom.isFullScreen()
# Public: Calls callback for every existing {Editor} and for all new {Editors}
# that are created.
#
# callback - A {Function} with an {Editor} as its only argument
eachEditor: (callback) ->
atom.project.eachEditor(callback)
# Public: Returns an {Array} of all open {Editor}s.
getEditors: ->
atom.project.getEditors()
# Public: Asynchronously opens a given a filepath in Atom.
#
# * filePath: A file path
# * options
# + initialLine: The buffer line number to open to.
# uri - A {String} uri.
# options - An options {Object} (default: {}).
# :initialLine - A {Number} indicating which line number to open to.
# :split - A {String} ('left' or 'right') that opens the filePath in a new
# pane or an existing one if it exists.
# :changeFocus - A {Boolean} that allows the filePath to be opened without
# changing focus.
# :searchAllPanes - A {Boolean} that will open existing editors from any pane
# if the uri is already open (default: false)
#
# Returns a promise that resolves to the {Editor} for the file URI.
open: (filePath, options={}) ->
changeFocus = options.changeFocus ? true
filePath = atom.project.resolve(filePath)
initialLine = options.initialLine
activePane = @activePane
open: (uri, options={}) ->
searchAllPanes = options.searchAllPanes
split = options.split
uri = atom.project.relativize(uri) ? ''
editor = activePane.itemForUri(atom.project.relativize(filePath)) if activePane and filePath
promise = atom.project.open(filePath, {initialLine}) if not editor
pane = switch split
when 'left'
@activePane.findLeftmostSibling()
when 'right'
@activePane.findOrCreateRightmostSibling()
else
if searchAllPanes
@paneContainer.paneForUri(uri) ? @activePane
else
@activePane
Q(editor ? promise)
.then (editor) =>
if not activePane
activePane = new Pane(items: [editor])
@paneContainer.root = activePane
@openUriInPane(uri, pane, options)
@itemOpened(editor)
activePane.activateItem(editor)
activePane.activate() if changeFocus
@emit "uri-opened"
editor
.catch (error) ->
console.error(error.stack ? error)
# Private: Only used in specs
# Only used in specs
openSync: (uri, options={}) ->
{initialLine} = options
# TODO: Remove deprecated changeFocus option
activatePane = options.activatePane ? options.changeFocus ? true
uri = atom.project.relativize(uri)
uri = atom.project.relativize(uri) ? ''
if uri?
editor = @activePane.itemForUri(uri) ? atom.project.openSync(uri, {initialLine})
else
editor = atom.project.openSync()
item = @activePane.itemForUri(uri)
if uri
item ?= opener(atom.project.resolve(uri), options) for opener in @getOpeners() when !item
item ?= atom.project.openSync(uri, {initialLine})
@activePane.activateItem(editor)
@itemOpened(editor)
@activePane.activateItem(item)
@itemOpened(item)
@activePane.activate() if activatePane
editor
item
# Public: Synchronously open an editor for the given URI or activate an existing
# editor in any pane if one already exists.
openSingletonSync: (uri, options={}) ->
{initialLine, split} = options
# TODO: Remove deprecated changeFocus option
activatePane = options.activatePane ? options.changeFocus ? true
uri = atom.project.relativize(uri)
openUriInPane: (uri, pane, options={}) ->
changeFocus = options.changeFocus ? true
if pane = @paneContainer.paneForUri(uri)
editor = pane.itemForUri(uri)
else
pane = switch split
when 'left'
@activePane.findLeftmostSibling()
when 'right'
@activePane.findOrCreateRightmostSibling()
else
@activePane
editor = atom.project.openSync(uri, {initialLine})
item = pane.itemForUri(uri)
if uri
item ?= opener(atom.project.resolve(uri), options) for opener in @getOpeners() when !item
item ?= atom.project.open(uri, options)
pane.activateItem(editor)
pane.activate() if activatePane
editor
Q(item)
.then (item) =>
if not pane
pane = new Pane(items: [item])
@paneContainer.root = pane
@itemOpened(item)
pane.activateItem(item)
pane.activate() if changeFocus
@emit "uri-opened"
item
.catch (error) ->
console.error(error.stack ? error)
# Public: Reopens the last-closed item uri if it hasn't already been reopened.
reopenItemSync: ->
if uri = @destroyedItemUris.pop()
@openSync(uri)
# Public: Register an opener for a uri.
#
# An {Editor} will be used if no openers return a value.
#
# ## Example
# ```coffeescript
# atom.project.registerOpener (uri) ->
# if path.extname(uri) is '.toml'
# return new TomlEditor(uri)
# ```
#
# opener - A {Function} to be called when a path is being opened.
registerOpener: (opener) ->
atom.project.registerOpener(opener)
# Public: Remove a registered opener.
unregisterOpener: (opener) ->
atom.project.unregisterOpener(opener)
getOpeners: ->
atom.project.openers
# Public: save the active item.
saveActivePaneItem: ->
@activePane?.saveActiveItem()
@@ -138,6 +171,11 @@ class Workspace extends Model
destroyActivePane: ->
@activePane?.destroy()
# Public: Returns an {Editor} if the active pane item is an {Editor},
# or null otherwise.
getActiveEditor: ->
@activePane?.getActiveEditor()
increaseFontSize: ->
atom.config.set("editor.fontSize", atom.config.get("editor.fontSize") + 1)
@@ -145,16 +183,19 @@ class Workspace extends Model
fontSize = atom.config.get("editor.fontSize")
atom.config.set("editor.fontSize", fontSize - 1) if fontSize > 1
# Private: Removes the item's uri from the list of potential items to reopen.
resetFontSize: ->
atom.config.restoreDefault("editor.fontSize")
# Removes the item's uri from the list of potential items to reopen.
itemOpened: (item) ->
if uri = item.getUri?()
remove(@destroyedItemUris, uri)
# Private: Adds the destroyed item's uri to the list of items to reopen.
# Adds the destroyed item's uri to the list of items to reopen.
onPaneItemDestroyed: (item) =>
if uri = item.getUri?()
@destroyedItemUris.push(uri)
# Private: Called by Model superclass when destroyed
# Called by Model superclass when destroyed
destroyed: ->
@paneContainer.destroy()