Files
atom/src/workspace.coffee
2014-04-02 14:19:27 -07:00

272 lines
9.0 KiB
CoffeeScript

{remove, last} = require 'underscore-plus'
{join} = require 'path'
{Model} = require 'theorist'
Q = require 'q'
Serializable = require 'serializable'
Delegator = require 'delegato'
PaneContainer = require './pane-container'
Pane = require './pane'
# Public: Represents the state of the user interface for the entire window.
# An instance of this class is available via the `atom.workspace` global.
#
# Interact with this object to open files, be notified of current and future
# editors, and manipulate panes. To add panels, you'll need to use the
# {WorkspaceView} class for now until we establish APIs at the model layer.
module.exports =
class Workspace extends Model
atom.deserializers.add(this)
Serializable.includeInto(this)
@delegatesProperty 'activePane', 'activePaneItem', toProperty: 'paneContainer'
@properties
paneContainer: -> new PaneContainer
fullScreen: false
destroyedItemUris: -> []
constructor: ->
super
@subscribe @paneContainer, 'item-destroyed', @onPaneItemDestroyed
@registerOpener (filePath) =>
switch filePath
when 'atom://.atom/stylesheet'
@open(atom.themes.getUserStylesheetPath())
when 'atom://.atom/keymap'
@open(atom.keymaps.getUserKeymapPath())
when 'atom://.atom/config'
@open(atom.config.getUserConfigPath())
when 'atom://.atom/init-script'
@open(atom.getUserInitScriptPath())
# Called by the Serializable mixin during deserialization
deserializeParams: (params) ->
params.paneContainer = PaneContainer.deserialize(params.paneContainer)
params
# Called by the Serializable mixin during serialization.
serializeParams: ->
paneContainer: @paneContainer.serialize()
fullScreen: atom.isFullScreen()
# Public: Register a function to be called for every current and future
# {Editor} in the workspace.
#
# callback - A {Function} with an {Editor} as its only argument.
#
# Returns a subscription object with an `.off` method that you can call to
# unregister the callback.
eachEditor: (callback) ->
atom.project.eachEditor(callback)
# Public: Get all current editors in the workspace.
#
# Returns an {Array} of {Editor}s.
getEditors: ->
atom.project.getEditors()
# Public: Open a given a URI in Atom asynchronously.
#
# uri - A {String} containing a URI.
# options - An optional options {Object}
# :initialLine - A {Number} indicating which row to move the cursor to
# initially. Defaults to `0`.
# :split - Either 'left' or 'right'. If 'left', the item will be opened in
# leftmost pane of the current active pane's row. If 'right', the
# item will be opened in the rightmost pane of the current active
# pane's row.
# :activatePane - A {Boolean} indicating whether to call {Pane::activate} on
# the containing pane. Defaults to `true`.
# :searchAllPanes - A {Boolean}. If `true`, the workspace will attempt to
# activate an existing item for the given URI on any pane.
# If `false`, only the active pane will be searched for
# an existing item for the same URI. Defaults to `false`.
#
# Returns a promise that resolves to the {Editor} for the file URI.
open: (uri, options={}) ->
searchAllPanes = options.searchAllPanes
split = options.split
uri = atom.project.resolve(uri)
pane = @paneContainer.paneForUri(uri) if searchAllPanes
pane ?= switch split
when 'left'
@activePane.findLeftmostSibling()
when 'right'
@activePane.findOrCreateRightmostSibling()
else
@activePane
@openUriInPane(uri, pane, options)
# Public: Open Atom's license in the active pane.
openLicense: ->
@open(join(atom.getLoadSettings().resourcePath, 'LICENSE'))
# Synchronously open the given URI in the active pane. **Only use this method
# in specs. Calling this in production code will block the UI thread and
# everyone will be mad at you.**
#
# uri - A {String} containing a URI.
# options - An optional options {Object}
# :initialLine - A {Number} indicating which row to move the cursor to
# initially. Defaults to `0`.
# :activatePane - A {Boolean} indicating whether to call {Pane::activate} on
# the containing pane. Defaults to `true`.
openSync: (uri='', options={}) ->
{initialLine} = options
# TODO: Remove deprecated changeFocus option
activatePane = options.activatePane ? options.changeFocus ? true
uri = atom.project.resolve(uri)
item = @activePane.itemForUri(uri)
if uri
item ?= opener(uri, options) for opener in @getOpeners() when !item
item ?= atom.project.openSync(uri, {initialLine})
@activePane.activateItem(item)
@itemOpened(item)
@activePane.activate() if activatePane
item
openUriInPane: (uri, pane, options={}) ->
changeFocus = options.changeFocus ? true
if uri?
item = pane.itemForUri(uri)
item ?= opener(atom.project.resolve(uri), options) for opener in @getOpeners() when !item
item ?= atom.project.open(uri, options)
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: Reopen the last-closed item's 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: Unregister an opener registered with {::registerOpener}.
unregisterOpener: (opener) ->
atom.project.unregisterOpener(opener)
getOpeners: ->
atom.project.openers
# Public: Get the active {Pane}.
#
# Returns a {Pane}.
getActivePane: ->
@paneContainer.activePane
# Public: Get all {Pane}s.
#
# Returns an {Array} of {Pane}s.
getPanes: ->
@paneContainer.getPanes()
# Public: Save all pane items.
saveAll: ->
@paneContainer.saveAll()
# Public: Make the next pane active.
activateNextPane: ->
@paneContainer.activateNextPane()
# Public: Make the previous pane active.
activatePreviousPane: ->
@paneContainer.activatePreviousPane()
# Public: Get the first pane {Pane} with an item for the given URI.
#
# Returns a {Pane} or `undefined` if no pane exists for the given URI.
paneForUri: (uri) ->
@paneContainer.paneForUri(uri)
# Public: Save the active pane item.
#
# If the active pane item currently has a URI according to the item's
# `.getUri` method, calls `.save` on the item. Otherwise
# {::saveActivePaneItemAs} # will be called instead. This method does nothing
# if the active item does not implement a `.save` method.
saveActivePaneItem: ->
@activePane?.saveActiveItem()
# Public: Prompt the user for a path and save the active pane item to it.
#
# Opens a native dialog where the user selects a path on disk, then calls
# `.saveAs` on the item with the selected path. This method does nothing if
# the active item does not implement a `.saveAs` method.
saveActivePaneItemAs: ->
@activePane?.saveActiveItemAs()
# Public: Destroy (close) the active pane item.
#
# Removes the active pane item and calls the `.destroy` method on it if one is
# defined.
destroyActivePaneItem: ->
@activePane?.destroyActiveItem()
# Public: Destroy (close) the active pane.
destroyActivePane: ->
@activePane?.destroy()
# Public: Get the active item if it is an {Editor}.
#
# Returns an {Editor} or `undefined` if the current active item is not an
# {Editor}.
getActiveEditor: ->
@activePane?.getActiveEditor()
# Public: Increase the editor font size by 1px.
increaseFontSize: ->
atom.config.set("editor.fontSize", atom.config.get("editor.fontSize") + 1)
# Public: Decrease the editor font size by 1px.
decreaseFontSize: ->
fontSize = atom.config.get("editor.fontSize")
atom.config.set("editor.fontSize", fontSize - 1) if fontSize > 1
# Public: Restore to a default editor font size.
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)
# Adds the destroyed item's uri to the list of items to reopen.
onPaneItemDestroyed: (item) =>
if uri = item.getUri?()
@destroyedItemUris.push(uri)
# Called by Model superclass when destroyed
destroyed: ->
@paneContainer.destroy()