mirror of
https://github.com/atom/atom.git
synced 2026-01-22 21:38:10 -05:00
Merge pull request #3549 from atom/bo-visibility-split
Add sections and split methods into essential / extended API
This commit is contained in:
@@ -8,6 +8,23 @@ describe "the `atom` global", ->
|
||||
beforeEach ->
|
||||
atom.workspaceView = new WorkspaceView
|
||||
|
||||
describe 'window sizing methods', ->
|
||||
describe '::getPosition and ::setPosition', ->
|
||||
it 'sets the position of the window, and can retrieve the position just set', ->
|
||||
atom.setPosition(22, 45)
|
||||
expect(atom.getPosition()).toEqual x: 22, y: 45
|
||||
|
||||
describe '::getSize and ::setSize', ->
|
||||
originalSize = null
|
||||
beforeEach ->
|
||||
originalSize = atom.getSize()
|
||||
afterEach ->
|
||||
atom.setSize(originalSize.width, originalSize.height)
|
||||
|
||||
it 'sets the size of the window, and can retrieve the size just set', ->
|
||||
atom.setSize(100, 400)
|
||||
expect(atom.getSize()).toEqual width: 100, height: 400
|
||||
|
||||
describe "package lifecycle methods", ->
|
||||
describe ".loadPackage(name)", ->
|
||||
it "continues if the package has an invalid package.json", ->
|
||||
|
||||
@@ -2239,7 +2239,7 @@ describe "EditorComponent", ->
|
||||
editor.setSoftWrapped(true)
|
||||
|
||||
callingOrder = []
|
||||
editor.onDidChangeScreenLines -> callingOrder.push 'screen-lines-changed'
|
||||
editor.onDidChange -> callingOrder.push 'screen-lines-changed'
|
||||
wrapperView.on 'editor:display-updated', -> callingOrder.push 'editor:display-updated'
|
||||
editor.insertText("HELLO! HELLO!\n HELLO! HELLO! HELLO! HELLO! HELLO! HELLO! HELLO! HELLO! HELLO! HELLO! HELLO! HELLO! HELLO! HELLO! HELLO! HELLO! HELLO! HELLO! ")
|
||||
nextAnimationFrame()
|
||||
|
||||
429
src/atom.coffee
429
src/atom.coffee
@@ -7,21 +7,21 @@ screen = require 'screen'
|
||||
shell = require 'shell'
|
||||
|
||||
_ = require 'underscore-plus'
|
||||
{deprecated} = require 'grim'
|
||||
{deprecate} = require 'grim'
|
||||
{Model} = require 'theorist'
|
||||
fs = require 'fs-plus'
|
||||
|
||||
{$} = require './space-pen-extensions'
|
||||
WindowEventHandler = require './window-event-handler'
|
||||
|
||||
# Public: Atom global for dealing with packages, themes, menus, and the window.
|
||||
# Essential: Atom global for dealing with packages, themes, menus, and the window.
|
||||
#
|
||||
# An instance of this class is always available as the `atom` global.
|
||||
module.exports =
|
||||
class Atom extends Model
|
||||
@version: 1 # Increment this when the serialization format changes
|
||||
|
||||
# Public: Load or create the Atom environment in the given mode.
|
||||
# Load or create the Atom environment in the given mode.
|
||||
#
|
||||
# * `mode` A {String} mode that is either 'editor' or 'spec' depending on the
|
||||
# kind of environment you want to build.
|
||||
@@ -98,6 +98,10 @@ class Atom extends Model
|
||||
workspaceViewParentSelector: 'body'
|
||||
lastUncaughtError: null
|
||||
|
||||
###
|
||||
Section: Properties
|
||||
###
|
||||
|
||||
# Public: A {Clipboard} instance
|
||||
clipboard: null
|
||||
|
||||
@@ -137,13 +141,17 @@ class Atom extends Model
|
||||
# Public: A {WorkspaceView} instance
|
||||
workspaceView: null
|
||||
|
||||
###
|
||||
Section: Construction and Destruction
|
||||
###
|
||||
|
||||
# Call .loadOrCreate instead
|
||||
constructor: (@state) ->
|
||||
{@mode} = @state
|
||||
DeserializerManager = require './deserializer-manager'
|
||||
@deserializers = new DeserializerManager()
|
||||
|
||||
# Public: Sets up the basic services that should be available in all modes
|
||||
# Sets up the basic services that should be available in all modes
|
||||
# (both spec and application).
|
||||
#
|
||||
# Call after this instance has been assigned to the `atom` global.
|
||||
@@ -203,22 +211,160 @@ class Atom extends Model
|
||||
|
||||
@windowEventHandler = new WindowEventHandler
|
||||
|
||||
# Deprecated: Callers should be converted to use atom.deserializers
|
||||
registerRepresentationClass: ->
|
||||
deprecated("Callers should be converted to use atom.deserializers")
|
||||
###
|
||||
Section: Atom Metadata
|
||||
###
|
||||
|
||||
# Deprecated: Callers should be converted to use atom.deserializers
|
||||
registerRepresentationClasses: ->
|
||||
deprecated("Callers should be converted to use atom.deserializers")
|
||||
# Essential: Is the current window in development mode?
|
||||
inDevMode: ->
|
||||
@getLoadSettings().devMode
|
||||
|
||||
setBodyPlatformClass: ->
|
||||
document.body.classList.add("platform-#{process.platform}")
|
||||
# Essential: Is the current window running specs?
|
||||
inSpecMode: ->
|
||||
@getLoadSettings().isSpec
|
||||
|
||||
# Public: Get the current window
|
||||
# Essential: Get the version of the Atom application.
|
||||
#
|
||||
# Returns the version text {String}.
|
||||
getVersion: ->
|
||||
@appVersion ?= @getLoadSettings().appVersion
|
||||
|
||||
# Essential: 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
|
||||
|
||||
# Essential: Get the directory path to Atom's configuration area.
|
||||
#
|
||||
# Returns the absolute path to `~/.atom`.
|
||||
getConfigDirPath: ->
|
||||
@constructor.getConfigDirPath()
|
||||
|
||||
# Extended: Get the load settings for the current window.
|
||||
#
|
||||
# Returns an {Object} containing all the load setting key/value pairs.
|
||||
getLoadSettings: ->
|
||||
@constructor.getLoadSettings()
|
||||
|
||||
# Extended: Get the time taken to completely load the current window.
|
||||
#
|
||||
# This time include things like loading and activating packages, creating
|
||||
# DOM elements for the editor, and reading the config.
|
||||
#
|
||||
# Returns the {Number} of milliseconds taken to load the window or null
|
||||
# if the window hasn't finished loading yet.
|
||||
getWindowLoadTime: ->
|
||||
@loadTime
|
||||
|
||||
###
|
||||
Section: Managing The Atom Window
|
||||
###
|
||||
|
||||
# Essential: Open a new Atom window using the given options.
|
||||
#
|
||||
# Calling this method without an options parameter will open a prompt to pick
|
||||
# a file/folder to open in the new window.
|
||||
#
|
||||
# * `options` An {Object} with the following keys:
|
||||
# * `pathsToOpen` An {Array} of {String} paths to open.
|
||||
# * `newWindow` A {Boolean}, true to always open a new window instead of
|
||||
# reusing existing windows depending on the paths to open.
|
||||
# * `devMode` A {Boolean}, true to open the window in development mode.
|
||||
# Development mode loads the Atom source from the locally cloned
|
||||
# repository and also loads all the packages in ~/.atom/dev/packages
|
||||
# * `safeMode` A {Boolean}, true to open the window in safe mode. Safe
|
||||
# mode prevents all packages installed to ~/.atom/packages from loading.
|
||||
open: (options) ->
|
||||
ipc.send('open', options)
|
||||
|
||||
# Essential: Close the current window.
|
||||
close: ->
|
||||
@getCurrentWindow().close()
|
||||
|
||||
# Essential: Get the size of current window.
|
||||
#
|
||||
# Returns an {Object} in the format `{width: 1000, height: 700}`
|
||||
getSize: ->
|
||||
[width, height] = @getCurrentWindow().getSize()
|
||||
{width, height}
|
||||
|
||||
# Essential: Set the size of current window.
|
||||
#
|
||||
# * `width` The {Number} of pixels.
|
||||
# * `height` The {Number} of pixels.
|
||||
setSize: (width, height) ->
|
||||
@getCurrentWindow().setSize(width, height)
|
||||
|
||||
# Essential: Get the position of current window.
|
||||
#
|
||||
# Returns an {Object} in the format `{x: 10, y: 20}`
|
||||
getPosition: ->
|
||||
[x, y] = @getCurrentWindow().getPosition()
|
||||
{x, y}
|
||||
|
||||
# Essential: Set the position of current window.
|
||||
#
|
||||
# * `x` The {Number} of pixels.
|
||||
# * `y` The {Number} of pixels.
|
||||
setPosition: (x, y) ->
|
||||
ipc.send('call-window-method', 'setPosition', x, y)
|
||||
|
||||
# Extended: Get the current window
|
||||
getCurrentWindow: ->
|
||||
@constructor.getCurrentWindow()
|
||||
|
||||
# Public: Get the dimensions of this window.
|
||||
# Extended: Move current window to the center of the screen.
|
||||
center: ->
|
||||
ipc.send('call-window-method', 'center')
|
||||
|
||||
# Extended: Focus the current window.
|
||||
focus: ->
|
||||
ipc.send('call-window-method', 'focus')
|
||||
$(window).focus()
|
||||
|
||||
# Extended: Show the current window.
|
||||
show: ->
|
||||
ipc.send('call-window-method', 'show')
|
||||
|
||||
# Extended: Hide the current window.
|
||||
hide: ->
|
||||
ipc.send('call-window-method', 'hide')
|
||||
|
||||
# Extended: Reload the current window.
|
||||
reload: ->
|
||||
ipc.send('call-window-method', 'restart')
|
||||
|
||||
# Extended: Returns a {Boolean} true when the current window is maximized.
|
||||
isMaximixed: ->
|
||||
@getCurrentWindow().isMaximized()
|
||||
|
||||
maximize: ->
|
||||
ipc.send('call-window-method', 'maximize')
|
||||
|
||||
# Extended: Is the current window in full screen mode?
|
||||
isFullScreen: ->
|
||||
@getCurrentWindow().isFullScreen()
|
||||
|
||||
# Extended: Set the full screen state of the current window.
|
||||
setFullScreen: (fullScreen=false) ->
|
||||
ipc.send('call-window-method', 'setFullScreen', fullScreen)
|
||||
if fullScreen then document.body.classList.add("fullscreen") else document.body.classList.remove("fullscreen")
|
||||
|
||||
# Extended: Toggle the full screen state of the current window.
|
||||
toggleFullScreen: ->
|
||||
@setFullScreen(!@isFullScreen())
|
||||
|
||||
# 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.
|
||||
displayWindow: ({maximize}={}) ->
|
||||
setImmediate =>
|
||||
@show()
|
||||
@focus()
|
||||
@setFullScreen(true) if @workspace.fullScreen
|
||||
@maximize() if maximize
|
||||
|
||||
# Get the dimensions of this window.
|
||||
#
|
||||
# Returns an {Object} with the following keys:
|
||||
# * `x` The window's x-position {Number}.
|
||||
@@ -232,7 +378,7 @@ class Atom extends Model
|
||||
maximized = browserWindow.isMaximized()
|
||||
{x, y, width, height, maximized}
|
||||
|
||||
# Public: Set the dimensions of the window.
|
||||
# Set the dimensions of the window.
|
||||
#
|
||||
# The window will be centered if either the x or y coordinate is not set
|
||||
# in the dimensions parameter. If x or y are omitted the window will be
|
||||
@@ -289,41 +435,6 @@ class Atom extends Model
|
||||
dimensions = @getWindowDimensions()
|
||||
@state.windowDimensions = dimensions if @isValidDimensions(dimensions)
|
||||
|
||||
# Public: Get the load settings for the current window.
|
||||
#
|
||||
# Returns an {Object} containing all the load setting key/value pairs.
|
||||
getLoadSettings: ->
|
||||
@constructor.getLoadSettings()
|
||||
|
||||
deserializeProject: ->
|
||||
Project = require './project'
|
||||
|
||||
startTime = Date.now()
|
||||
@project ?= @deserializers.deserialize(@state.project) ? new Project(path: @getLoadSettings().initialPath)
|
||||
@deserializeTimings.project = Date.now() - startTime
|
||||
|
||||
deserializeWorkspaceView: ->
|
||||
Workspace = require './workspace'
|
||||
WorkspaceView = require './workspace-view'
|
||||
|
||||
startTime = Date.now()
|
||||
@workspace = Workspace.deserialize(@state.workspace) ? new Workspace
|
||||
@workspaceView = new WorkspaceView(@workspace)
|
||||
@deserializeTimings.workspace = Date.now() - startTime
|
||||
|
||||
@keymaps.defaultTarget = @workspaceView[0]
|
||||
$(@workspaceViewParentSelector).append(@workspaceView)
|
||||
|
||||
deserializePackageStates: ->
|
||||
@packages.packageStates = @state.packageStates ? {}
|
||||
delete @state.packageStates
|
||||
|
||||
deserializeEditorWindow: ->
|
||||
@deserializeTimings = {}
|
||||
@deserializePackageStates()
|
||||
@deserializeProject()
|
||||
@deserializeWorkspaceView()
|
||||
|
||||
# Call this method when establishing a real application window.
|
||||
startEditorWindow: ->
|
||||
{resourcePath, safeMode} = @getLoadSettings()
|
||||
@@ -374,41 +485,16 @@ class Atom extends Model
|
||||
|
||||
@windowEventHandler?.unsubscribe()
|
||||
|
||||
loadThemes: ->
|
||||
@themes.load()
|
||||
###
|
||||
Section: Messaging the User
|
||||
###
|
||||
|
||||
watchThemes: ->
|
||||
@themes.onDidReloadAll =>
|
||||
# Only reload stylesheets from non-theme packages
|
||||
for pack in @packages.getActivePackages() when pack.getType() isnt 'theme'
|
||||
pack.reloadStylesheets?()
|
||||
null
|
||||
# Essential: Visually and audibly trigger a beep.
|
||||
beep: ->
|
||||
shell.beep() if @config.get('core.audioBeep')
|
||||
@workspaceView.trigger 'beep'
|
||||
|
||||
# Notify the browser project of the window's current project path
|
||||
watchProjectPath: ->
|
||||
onProjectPathChanged = =>
|
||||
ipc.send('window-command', 'project-path-changed', @project.getPath())
|
||||
@subscribe @project, 'path-changed', onProjectPathChanged
|
||||
onProjectPathChanged()
|
||||
|
||||
# Public: Open a new Atom window using the given options.
|
||||
#
|
||||
# Calling this method without an options parameter will open a prompt to pick
|
||||
# a file/folder to open in the new window.
|
||||
#
|
||||
# * `options` An {Object} with the following keys:
|
||||
# * `pathsToOpen` An {Array} of {String} paths to open.
|
||||
# * `newWindow` A {Boolean}, true to always open a new window instead of
|
||||
# reusing existing windows depending on the paths to open.
|
||||
# * `devMode` A {Boolean}, true to open the window in development mode.
|
||||
# Development mode loads the Atom source from the locally cloned
|
||||
# repository and also loads all the packages in ~/.atom/dev/packages
|
||||
# * `safeMode` A {Boolean}, true to open the window in safe mode. Safe
|
||||
# mode prevents all packages installed to ~/.atom/packages from loading.
|
||||
open: (options) ->
|
||||
ipc.send('open', options)
|
||||
|
||||
# Public: Open a confirm dialog.
|
||||
# Essential: A flexible way to open a dialog akin to an alert dialog.
|
||||
#
|
||||
# ## Examples
|
||||
#
|
||||
@@ -418,13 +504,13 @@ class Atom extends Model
|
||||
# detailedMessage: 'Be honest.'
|
||||
# buttons:
|
||||
# Good: -> window.alert('good to hear')
|
||||
# Bad: -> window.alert('bummer')
|
||||
# Bad: -> window.alert('bummer')
|
||||
# ```
|
||||
#
|
||||
# * `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
|
||||
# * `detailedMessage` (optional) The {String} detailed message to display.
|
||||
# * `buttons` (optional) 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 button index {Number} if the buttons option was an array.
|
||||
@@ -448,77 +534,71 @@ class Atom extends Model
|
||||
callback = buttons[buttonLabels[chosen]]
|
||||
callback?()
|
||||
|
||||
showSaveDialog: (callback) ->
|
||||
callback(showSaveDialogSync())
|
||||
###
|
||||
Section: Managing the Dev Tools
|
||||
###
|
||||
|
||||
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.
|
||||
# Extended: Open the dev tools for the current window.
|
||||
openDevTools: ->
|
||||
ipc.send('call-window-method', 'openDevTools')
|
||||
|
||||
# Public: Toggle the visibility of the dev tools for the current window.
|
||||
# Extended: Toggle the visibility of the dev tools for the current window.
|
||||
toggleDevTools: ->
|
||||
ipc.send('call-window-method', 'toggleDevTools')
|
||||
|
||||
# Public: Execute code in dev tools.
|
||||
# Extended: Execute code in dev tools.
|
||||
executeJavaScriptInDevTools: (code) ->
|
||||
ipc.send('call-window-method', 'executeJavaScriptInDevTools', code)
|
||||
|
||||
# Public: Reload the current window.
|
||||
reload: ->
|
||||
ipc.send('call-window-method', 'restart')
|
||||
###
|
||||
Section: Private
|
||||
###
|
||||
|
||||
# Public: Focus the current window.
|
||||
focus: ->
|
||||
ipc.send('call-window-method', 'focus')
|
||||
$(window).focus()
|
||||
deserializeProject: ->
|
||||
Project = require './project'
|
||||
|
||||
# Public: Show the current window.
|
||||
show: ->
|
||||
ipc.send('call-window-method', 'show')
|
||||
startTime = Date.now()
|
||||
@project ?= @deserializers.deserialize(@state.project) ? new Project(path: @getLoadSettings().initialPath)
|
||||
@deserializeTimings.project = Date.now() - startTime
|
||||
|
||||
# Public: Hide the current window.
|
||||
hide: ->
|
||||
ipc.send('call-window-method', 'hide')
|
||||
deserializeWorkspaceView: ->
|
||||
Workspace = require './workspace'
|
||||
WorkspaceView = require './workspace-view'
|
||||
|
||||
# Public: Set the size of current window.
|
||||
#
|
||||
# * `width` The {Number} of pixels.
|
||||
# * `height` The {Number} of pixels.
|
||||
setSize: (width, height) ->
|
||||
@getCurrentWindow().setSize(width, height)
|
||||
startTime = Date.now()
|
||||
@workspace = Workspace.deserialize(@state.workspace) ? new Workspace
|
||||
@workspaceView = new WorkspaceView(@workspace)
|
||||
@deserializeTimings.workspace = Date.now() - startTime
|
||||
|
||||
# Public: Set the position of current window.
|
||||
#
|
||||
# * `x` The {Number} of pixels.
|
||||
# * `y` The {Number} of pixels.
|
||||
setPosition: (x, y) ->
|
||||
ipc.send('call-window-method', 'setPosition', x, y)
|
||||
@keymaps.defaultTarget = @workspaceView[0]
|
||||
$(@workspaceViewParentSelector).append(@workspaceView)
|
||||
|
||||
# Public: Move current window to the center of the screen.
|
||||
center: ->
|
||||
ipc.send('call-window-method', 'center')
|
||||
deserializePackageStates: ->
|
||||
@packages.packageStates = @state.packageStates ? {}
|
||||
delete @state.packageStates
|
||||
|
||||
deserializeEditorWindow: ->
|
||||
@deserializeTimings = {}
|
||||
@deserializePackageStates()
|
||||
@deserializeProject()
|
||||
@deserializeWorkspaceView()
|
||||
|
||||
# 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.
|
||||
displayWindow: ({maximize}={}) ->
|
||||
setImmediate =>
|
||||
@show()
|
||||
@focus()
|
||||
@setFullScreen(true) if @workspace.fullScreen
|
||||
@maximize() if maximize
|
||||
loadThemes: ->
|
||||
@themes.load()
|
||||
|
||||
# Public: Close the current window.
|
||||
close: ->
|
||||
@getCurrentWindow().close()
|
||||
watchThemes: ->
|
||||
@themes.onDidReloadAll =>
|
||||
# Only reload stylesheets from non-theme packages
|
||||
for pack in @packages.getActivePackages() when pack.getType() isnt 'theme'
|
||||
pack.reloadStylesheets?()
|
||||
null
|
||||
|
||||
# Notify the browser project of the window's current project path
|
||||
watchProjectPath: ->
|
||||
onProjectPathChanged = =>
|
||||
ipc.send('window-command', 'project-path-changed', @project.getPath())
|
||||
@subscribe @project, 'path-changed', onProjectPathChanged
|
||||
onProjectPathChanged()
|
||||
|
||||
exit: (status) ->
|
||||
app = remote.require('app')
|
||||
@@ -531,45 +611,14 @@ class Atom extends Model
|
||||
setRepresentedFilename: (filename) ->
|
||||
ipc.send('call-window-method', 'setRepresentedFilename', filename)
|
||||
|
||||
# Public: Is the current window in development mode?
|
||||
inDevMode: ->
|
||||
@getLoadSettings().devMode
|
||||
showSaveDialog: (callback) ->
|
||||
callback(showSaveDialogSync())
|
||||
|
||||
# Public: Is the current window running specs?
|
||||
inSpecMode: ->
|
||||
@getLoadSettings().isSpec
|
||||
|
||||
# Public: Toggle the full screen state of the current window.
|
||||
toggleFullScreen: ->
|
||||
@setFullScreen(!@isFullScreen())
|
||||
|
||||
# Public: Set the full screen state of the current window.
|
||||
setFullScreen: (fullScreen=false) ->
|
||||
ipc.send('call-window-method', 'setFullScreen', fullScreen)
|
||||
if fullScreen then document.body.classList.add("fullscreen") else document.body.classList.remove("fullscreen")
|
||||
|
||||
# Public: Is the current window in full screen mode?
|
||||
isFullScreen: ->
|
||||
@getCurrentWindow().isFullScreen()
|
||||
|
||||
maximize: ->
|
||||
ipc.send('call-window-method', 'maximize')
|
||||
|
||||
# Public: Get the version of the Atom application.
|
||||
#
|
||||
# Returns the version text {String}.
|
||||
getVersion: ->
|
||||
@appVersion ?= @getLoadSettings().appVersion
|
||||
|
||||
# Public: 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
|
||||
|
||||
# Public: Get the directory path to Atom's configuration area.
|
||||
#
|
||||
# Returns the absolute path to `~/.atom`.
|
||||
getConfigDirPath: ->
|
||||
@constructor.getConfigDirPath()
|
||||
showSaveDialogSync: (defaultPath) ->
|
||||
defaultPath ?= @project?.getPath()
|
||||
currentWindow = @getCurrentWindow()
|
||||
dialog = remote.require('dialog')
|
||||
dialog.showSaveDialog currentWindow, {title: 'Save File', defaultPath}
|
||||
|
||||
saveSync: ->
|
||||
stateString = JSON.stringify(@state)
|
||||
@@ -578,27 +627,12 @@ class Atom extends Model
|
||||
else
|
||||
@getCurrentWindow().loadSettings.windowState = stateString
|
||||
|
||||
# Public: Get the time taken to completely load the current window.
|
||||
#
|
||||
# This time include things like loading and activating packages, creating
|
||||
# DOM elements for the editor, and reading the config.
|
||||
#
|
||||
# Returns the {Number} of milliseconds taken to load the window or null
|
||||
# if the window hasn't finished loading yet.
|
||||
getWindowLoadTime: ->
|
||||
@loadTime
|
||||
|
||||
crashMainProcess: ->
|
||||
remote.process.crash()
|
||||
|
||||
crashRenderProcess: ->
|
||||
process.crash()
|
||||
|
||||
# Public: Visually and audibly trigger a beep.
|
||||
beep: ->
|
||||
shell.beep() if @config.get('core.audioBeep')
|
||||
@workspaceView.trigger 'beep'
|
||||
|
||||
getUserInitScriptPath: ->
|
||||
initScriptPath = fs.resolve(@getConfigDirPath(), 'init', ['js', 'coffee'])
|
||||
initScriptPath ? path.join(@getConfigDirPath(), 'init.coffee')
|
||||
@@ -610,7 +644,7 @@ class Atom extends Model
|
||||
catch error
|
||||
console.error "Failed to load `#{userInitScriptPath}`", error.stack, error
|
||||
|
||||
# Public: Require the module with the given globals.
|
||||
# Require the module with the given globals.
|
||||
#
|
||||
# The globals will be set on the `window` object and removed after the
|
||||
# require completes.
|
||||
@@ -630,3 +664,14 @@ class Atom extends Model
|
||||
delete window[key]
|
||||
else
|
||||
window[key] = value
|
||||
|
||||
# Deprecated: Callers should be converted to use atom.deserializers
|
||||
registerRepresentationClass: ->
|
||||
deprecate("Callers should be converted to use atom.deserializers")
|
||||
|
||||
# Deprecated: Callers should be converted to use atom.deserializers
|
||||
registerRepresentationClasses: ->
|
||||
deprecate("Callers should be converted to use atom.deserializers")
|
||||
|
||||
setBodyPlatformClass: ->
|
||||
document.body.classList.add("platform-#{process.platform}")
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
BufferedProcess = require './buffered-process'
|
||||
path = require 'path'
|
||||
|
||||
# Public: Like {BufferedProcess}, but accepts a Node script as the command
|
||||
# Extended: Like {BufferedProcess}, but accepts a Node script as the command
|
||||
# to run.
|
||||
#
|
||||
# This is necessary on Windows since it doesn't support shebang `#!` lines.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
_ = require 'underscore-plus'
|
||||
ChildProcess = require 'child_process'
|
||||
|
||||
# Public: A wrapper which provides standard error/output line buffering for
|
||||
# Extended: A wrapper which provides standard error/output line buffering for
|
||||
# Node's ChildProcess.
|
||||
#
|
||||
# ## Examples
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
clipboard = require 'clipboard'
|
||||
crypto = require 'crypto'
|
||||
|
||||
# Public: Represents the clipboard used for copying and pasting in Atom.
|
||||
# Extended: Represents the clipboard used for copying and pasting in Atom.
|
||||
#
|
||||
# An instance of this class is always available as the `atom.clipboard` global.
|
||||
#
|
||||
|
||||
@@ -6,7 +6,7 @@ path = require 'path'
|
||||
async = require 'async'
|
||||
pathWatcher = require 'pathwatcher'
|
||||
|
||||
# Public: Used to access all of Atom's configuration details.
|
||||
# Essential: Used to access all of Atom's configuration details.
|
||||
#
|
||||
# An instance of this class is always available as the `atom.config` global.
|
||||
#
|
||||
|
||||
@@ -5,7 +5,7 @@ path = require 'path'
|
||||
CSON = require 'season'
|
||||
fs = require 'fs-plus'
|
||||
|
||||
# Public: Provides a registry for commands that you'd like to appear in the
|
||||
# Extended: Provides a registry for commands that you'd like to appear in the
|
||||
# context menu.
|
||||
#
|
||||
# An instance of this class is always available as the `atom.contextMenu`
|
||||
|
||||
@@ -54,11 +54,14 @@ class Cursor extends Model
|
||||
@emitter.dispose()
|
||||
@needsAutoscroll = true
|
||||
|
||||
destroy: ->
|
||||
@marker.destroy()
|
||||
|
||||
###
|
||||
Section: Event Subscription
|
||||
###
|
||||
|
||||
# Essential: Calls your `callback` when the cursor has been moved.
|
||||
# Public: Calls your `callback` when the cursor has been moved.
|
||||
#
|
||||
# * `callback` {Function}
|
||||
# * `event` {Object}
|
||||
@@ -72,7 +75,7 @@ class Cursor extends Model
|
||||
onDidChangePosition: (callback) ->
|
||||
@emitter.on 'did-change-position', callback
|
||||
|
||||
# Extended: Calls your `callback` when the cursor is destroyed
|
||||
# Public: Calls your `callback` when the cursor is destroyed
|
||||
#
|
||||
# * `callback` {Function}
|
||||
#
|
||||
@@ -80,7 +83,7 @@ class Cursor extends Model
|
||||
onDidDestroy: (callback) ->
|
||||
@emitter.on 'did-destroy', callback
|
||||
|
||||
# Extended: Calls your `callback` when the cursor's visibility has changed
|
||||
# Public: Calls your `callback` when the cursor's visibility has changed
|
||||
#
|
||||
# * `callback` {Function}
|
||||
# * `visibility` {Boolean}
|
||||
@@ -102,23 +105,9 @@ class Cursor extends Model
|
||||
super
|
||||
|
||||
###
|
||||
Section: Methods
|
||||
Section: Managing Cursor Position
|
||||
###
|
||||
|
||||
destroy: ->
|
||||
@marker.destroy()
|
||||
|
||||
changePosition: (options, fn) ->
|
||||
@clearSelection()
|
||||
@needsAutoscroll = options.autoscroll ? @isLastCursor()
|
||||
fn()
|
||||
if @needsAutoscroll
|
||||
@emit 'autoscrolled' # Support legacy editor
|
||||
@autoscroll() if @needsAutoscroll and @editor.manageScrollPosition # Support react editor view
|
||||
|
||||
getPixelRect: ->
|
||||
@editor.pixelRectForScreenRange(@getScreenRange())
|
||||
|
||||
# Public: Moves a cursor to a given screen position.
|
||||
#
|
||||
# * `screenPosition` {Array} of two numbers: the screen row, and the screen column.
|
||||
@@ -133,10 +122,6 @@ class Cursor extends Model
|
||||
getScreenPosition: ->
|
||||
@marker.getHeadScreenPosition()
|
||||
|
||||
getScreenRange: ->
|
||||
{row, column} = @getScreenPosition()
|
||||
new Range(new Point(row, column), new Point(row, column + 1))
|
||||
|
||||
# Public: Moves a cursor to a given buffer position.
|
||||
#
|
||||
# * `bufferPosition` {Array} of two numbers: the buffer row, and the buffer column.
|
||||
@@ -151,47 +136,38 @@ class Cursor extends Model
|
||||
getBufferPosition: ->
|
||||
@marker.getHeadBufferPosition()
|
||||
|
||||
autoscroll: (options) ->
|
||||
@editor.scrollToScreenRange(@getScreenRange(), options)
|
||||
# Public: Returns the cursor's current screen row.
|
||||
getScreenRow: ->
|
||||
@getScreenPosition().row
|
||||
|
||||
# Public: If the marker range is empty, the cursor is marked as being visible.
|
||||
updateVisibility: ->
|
||||
@setVisible(@marker.getBufferRange().isEmpty())
|
||||
# Public: Returns the cursor's current screen column.
|
||||
getScreenColumn: ->
|
||||
@getScreenPosition().column
|
||||
|
||||
# Public: Sets whether the cursor is visible.
|
||||
setVisible: (visible) ->
|
||||
if @visible != visible
|
||||
@visible = visible
|
||||
@needsAutoscroll ?= true if @visible and @isLastCursor()
|
||||
@emit 'visibility-changed', @visible
|
||||
@emitter.emit 'did-change-visibility', @visible
|
||||
# Public: Retrieves the cursor's current buffer row.
|
||||
getBufferRow: ->
|
||||
@getBufferPosition().row
|
||||
|
||||
# Public: Returns the visibility of the cursor.
|
||||
isVisible: -> @visible
|
||||
# Public: Returns the cursor's current buffer column.
|
||||
getBufferColumn: ->
|
||||
@getBufferPosition().column
|
||||
|
||||
# Public: Get the RegExp used by the cursor to determine what a "word" is.
|
||||
#
|
||||
# * `options` (optional) {Object} with the following keys:
|
||||
# * `includeNonWordCharacters` A {Boolean} indicating whether to include
|
||||
# non-word characters in the regex. (default: true)
|
||||
#
|
||||
# Returns a {RegExp}.
|
||||
wordRegExp: ({includeNonWordCharacters}={}) ->
|
||||
includeNonWordCharacters ?= true
|
||||
nonWordCharacters = atom.config.get('editor.nonWordCharacters')
|
||||
segments = ["^[\t ]*$"]
|
||||
segments.push("[^\\s#{_.escapeRegExp(nonWordCharacters)}]+")
|
||||
if includeNonWordCharacters
|
||||
segments.push("[#{_.escapeRegExp(nonWordCharacters)}]+")
|
||||
new RegExp(segments.join("|"), "g")
|
||||
# Public: Returns the cursor's current buffer row of text excluding its line
|
||||
# ending.
|
||||
getCurrentBufferLine: ->
|
||||
@editor.lineTextForBufferRow(@getBufferRow())
|
||||
|
||||
# Public: Identifies if this cursor is the last in the {Editor}.
|
||||
#
|
||||
# "Last" is defined as the most recently added cursor.
|
||||
#
|
||||
# Returns a {Boolean}.
|
||||
isLastCursor: ->
|
||||
this == @editor.getLastCursor()
|
||||
# Public: Returns whether the cursor is at the start of a line.
|
||||
isAtBeginningOfLine: ->
|
||||
@getBufferPosition().column == 0
|
||||
|
||||
# Public: Returns whether the cursor is on the line return character.
|
||||
isAtEndOfLine: ->
|
||||
@getBufferPosition().isEqual(@getCurrentLineBufferRange().end)
|
||||
|
||||
###
|
||||
Section: Info about the cursor position
|
||||
###
|
||||
|
||||
# Public: Identifies if the cursor is surrounded by whitespace.
|
||||
#
|
||||
@@ -229,34 +205,42 @@ class Cursor extends Model
|
||||
range = [[row, column], [row, Infinity]]
|
||||
@editor.getTextInBufferRange(range).search(@wordRegExp()) == 0
|
||||
|
||||
# Public: Prevents this cursor from causing scrolling.
|
||||
clearAutoscroll: ->
|
||||
@needsAutoscroll = null
|
||||
# Public: Returns the indentation level of the current line.
|
||||
getIndentLevel: ->
|
||||
if @editor.getSoftTabs()
|
||||
@getBufferColumn() / @editor.getTabLength()
|
||||
else
|
||||
@getBufferColumn()
|
||||
|
||||
# Public: Deselects the current selection.
|
||||
clearSelection: ->
|
||||
@selection?.clear()
|
||||
# Public: Retrieves the grammar's token scopes for the line.
|
||||
#
|
||||
# Returns an {Array} of {String}s.
|
||||
getScopes: ->
|
||||
@editor.scopesForBufferPosition(@getBufferPosition())
|
||||
|
||||
# Public: Returns the cursor's current screen row.
|
||||
getScreenRow: ->
|
||||
@getScreenPosition().row
|
||||
# Public: Returns true if this cursor has no non-whitespace characters before
|
||||
# its current position.
|
||||
hasPrecedingCharactersOnLine: ->
|
||||
bufferPosition = @getBufferPosition()
|
||||
line = @editor.lineTextForBufferRow(bufferPosition.row)
|
||||
firstCharacterColumn = line.search(/\S/)
|
||||
|
||||
# Public: Returns the cursor's current screen column.
|
||||
getScreenColumn: ->
|
||||
@getScreenPosition().column
|
||||
if firstCharacterColumn is -1
|
||||
false
|
||||
else
|
||||
bufferPosition.column > firstCharacterColumn
|
||||
|
||||
# Public: Retrieves the cursor's current buffer row.
|
||||
getBufferRow: ->
|
||||
@getBufferPosition().row
|
||||
# Public: Identifies if this cursor is the last in the {Editor}.
|
||||
#
|
||||
# "Last" is defined as the most recently added cursor.
|
||||
#
|
||||
# Returns a {Boolean}.
|
||||
isLastCursor: ->
|
||||
this == @editor.getLastCursor()
|
||||
|
||||
# Public: Returns the cursor's current buffer column.
|
||||
getBufferColumn: ->
|
||||
@getBufferPosition().column
|
||||
|
||||
# Public: Returns the cursor's current buffer row of text excluding its line
|
||||
# ending.
|
||||
getCurrentBufferLine: ->
|
||||
@editor.lineTextForBufferRow(@getBufferRow())
|
||||
###
|
||||
Section: Moving the Cursor
|
||||
###
|
||||
|
||||
# Public: Moves the cursor up one screen row.
|
||||
#
|
||||
@@ -417,6 +401,20 @@ class Cursor extends Model
|
||||
|
||||
@setBufferPosition(endOfLeadingWhitespace) if endOfLeadingWhitespace.isGreaterThan(position)
|
||||
|
||||
# Public: Moves the cursor to the beginning of the next paragraph
|
||||
moveToBeginningOfNextParagraph: ->
|
||||
if position = @getBeginningOfNextParagraphBufferPosition()
|
||||
@setBufferPosition(position)
|
||||
|
||||
# Public: Moves the cursor to the beginning of the previous paragraph
|
||||
moveToBeginningOfPreviousParagraph: ->
|
||||
if position = @getBeginningOfPreviousParagraphBufferPosition()
|
||||
@setBufferPosition(position)
|
||||
|
||||
###
|
||||
Section: Retrieving Positions and Ranges of local boundaries
|
||||
###
|
||||
|
||||
# Public: Retrieves the buffer position of where the current word starts.
|
||||
#
|
||||
# * `options` (optional) An {Object} with the following keys:
|
||||
@@ -553,15 +551,98 @@ class Cursor extends Model
|
||||
getCurrentLineBufferRange: (options) ->
|
||||
@editor.bufferRangeForBufferRow(@getBufferRow(), options)
|
||||
|
||||
# Public: Moves the cursor to the beginning of the next paragraph
|
||||
moveToBeginningOfNextParagraph: ->
|
||||
if position = @getBeginningOfNextParagraphBufferPosition()
|
||||
@setBufferPosition(position)
|
||||
# Public: Retrieves the range for the current paragraph.
|
||||
#
|
||||
# A paragraph is defined as a block of text surrounded by empty lines.
|
||||
#
|
||||
# Returns a {Range}.
|
||||
getCurrentParagraphBufferRange: ->
|
||||
@editor.languageMode.rowRangeForParagraphAtBufferRow(@getBufferRow())
|
||||
|
||||
# Public: Moves the cursor to the beginning of the previous paragraph
|
||||
moveToBeginningOfPreviousParagraph: ->
|
||||
if position = @getBeginningOfPreviousParagraphBufferPosition()
|
||||
@setBufferPosition(position)
|
||||
# Public: Returns the characters preceding the cursor in the current word.
|
||||
getCurrentWordPrefix: ->
|
||||
@editor.getTextInBufferRange([@getBeginningOfCurrentWordBufferPosition(), @getBufferPosition()])
|
||||
|
||||
###
|
||||
Section: Visibility
|
||||
###
|
||||
|
||||
# Public: If the marker range is empty, the cursor is marked as being visible.
|
||||
updateVisibility: ->
|
||||
@setVisible(@marker.getBufferRange().isEmpty())
|
||||
|
||||
# Public: Sets whether the cursor is visible.
|
||||
setVisible: (visible) ->
|
||||
if @visible != visible
|
||||
@visible = visible
|
||||
@needsAutoscroll ?= true if @visible and @isLastCursor()
|
||||
@emit 'visibility-changed', @visible
|
||||
@emitter.emit 'did-change-visibility', @visible
|
||||
|
||||
# Public: Returns the visibility of the cursor.
|
||||
isVisible: -> @visible
|
||||
|
||||
###
|
||||
Section: Comparing to another cursor
|
||||
###
|
||||
|
||||
# Public: Compare this cursor's buffer position to another cursor's buffer position.
|
||||
#
|
||||
# See {Point::compare} for more details.
|
||||
#
|
||||
# * `otherCursor`{Cursor} to compare against
|
||||
compare: (otherCursor) ->
|
||||
@getBufferPosition().compare(otherCursor.getBufferPosition())
|
||||
|
||||
###
|
||||
Section: Utilities
|
||||
###
|
||||
|
||||
# Public: Prevents this cursor from causing scrolling.
|
||||
clearAutoscroll: ->
|
||||
@needsAutoscroll = null
|
||||
|
||||
# Public: Deselects the current selection.
|
||||
clearSelection: ->
|
||||
@selection?.clear()
|
||||
|
||||
# Public: Get the RegExp used by the cursor to determine what a "word" is.
|
||||
#
|
||||
# * `options` (optional) {Object} with the following keys:
|
||||
# * `includeNonWordCharacters` A {Boolean} indicating whether to include
|
||||
# non-word characters in the regex. (default: true)
|
||||
#
|
||||
# Returns a {RegExp}.
|
||||
wordRegExp: ({includeNonWordCharacters}={}) ->
|
||||
includeNonWordCharacters ?= true
|
||||
nonWordCharacters = atom.config.get('editor.nonWordCharacters')
|
||||
segments = ["^[\t ]*$"]
|
||||
segments.push("[^\\s#{_.escapeRegExp(nonWordCharacters)}]+")
|
||||
if includeNonWordCharacters
|
||||
segments.push("[#{_.escapeRegExp(nonWordCharacters)}]+")
|
||||
new RegExp(segments.join("|"), "g")
|
||||
|
||||
###
|
||||
Section: Private
|
||||
###
|
||||
|
||||
changePosition: (options, fn) ->
|
||||
@clearSelection()
|
||||
@needsAutoscroll = options.autoscroll ? @isLastCursor()
|
||||
fn()
|
||||
if @needsAutoscroll
|
||||
@emit 'autoscrolled' # Support legacy editor
|
||||
@autoscroll() if @needsAutoscroll and @editor.manageScrollPosition # Support react editor view
|
||||
|
||||
getPixelRect: ->
|
||||
@editor.pixelRectForScreenRange(@getScreenRange())
|
||||
|
||||
getScreenRange: ->
|
||||
{row, column} = @getScreenPosition()
|
||||
new Range(new Point(row, column), new Point(row, column + 1))
|
||||
|
||||
autoscroll: (options) ->
|
||||
@editor.scrollToScreenRange(@getScreenRange(), options)
|
||||
|
||||
getBeginningOfNextParagraphBufferPosition: (editor) ->
|
||||
start = @getBufferPosition()
|
||||
@@ -589,56 +670,3 @@ class Cursor extends Model
|
||||
position = range.start
|
||||
stop()
|
||||
@editor.screenPositionForBufferPosition(position)
|
||||
|
||||
# Public: Retrieves the range for the current paragraph.
|
||||
#
|
||||
# A paragraph is defined as a block of text surrounded by empty lines.
|
||||
#
|
||||
# Returns a {Range}.
|
||||
getCurrentParagraphBufferRange: ->
|
||||
@editor.languageMode.rowRangeForParagraphAtBufferRow(@getBufferRow())
|
||||
|
||||
# Public: Returns the characters preceding the cursor in the current word.
|
||||
getCurrentWordPrefix: ->
|
||||
@editor.getTextInBufferRange([@getBeginningOfCurrentWordBufferPosition(), @getBufferPosition()])
|
||||
|
||||
# Public: Returns whether the cursor is at the start of a line.
|
||||
isAtBeginningOfLine: ->
|
||||
@getBufferPosition().column == 0
|
||||
|
||||
# Public: Returns the indentation level of the current line.
|
||||
getIndentLevel: ->
|
||||
if @editor.getSoftTabs()
|
||||
@getBufferColumn() / @editor.getTabLength()
|
||||
else
|
||||
@getBufferColumn()
|
||||
|
||||
# Public: Returns whether the cursor is on the line return character.
|
||||
isAtEndOfLine: ->
|
||||
@getBufferPosition().isEqual(@getCurrentLineBufferRange().end)
|
||||
|
||||
# Public: Retrieves the grammar's token scopes for the line.
|
||||
#
|
||||
# Returns an {Array} of {String}s.
|
||||
getScopes: ->
|
||||
@editor.scopesForBufferPosition(@getBufferPosition())
|
||||
|
||||
# Public: Returns true if this cursor has no non-whitespace characters before
|
||||
# its current position.
|
||||
hasPrecedingCharactersOnLine: ->
|
||||
bufferPosition = @getBufferPosition()
|
||||
line = @editor.lineTextForBufferRow(bufferPosition.row)
|
||||
firstCharacterColumn = line.search(/\S/)
|
||||
|
||||
if firstCharacterColumn is -1
|
||||
false
|
||||
else
|
||||
bufferPosition.column > firstCharacterColumn
|
||||
|
||||
# Public: Compare this cursor's buffer position to another cursor's buffer position.
|
||||
#
|
||||
# See {Point::compare} for more details.
|
||||
#
|
||||
# * `otherCursor`{Cursor} to compare against
|
||||
compare: (otherCursor) ->
|
||||
@getBufferPosition().compare(otherCursor.getBufferPosition())
|
||||
|
||||
@@ -32,7 +32,7 @@ module.exports =
|
||||
class Decoration
|
||||
EmitterMixin.includeInto(this)
|
||||
|
||||
# Extended: Check if the `decorationProperties.type` matches `type`
|
||||
# Private: Check if the `decorationProperties.type` matches `type`
|
||||
#
|
||||
# * `decorationProperties` {Object} eg. `{type: 'gutter', class: 'my-new-class'}`
|
||||
# * `type` {String} type like `'gutter'`, `'line'`, etc. `type` can also
|
||||
@@ -46,6 +46,10 @@ class Decoration
|
||||
else
|
||||
type is decorationProperties.type
|
||||
|
||||
###
|
||||
Section: Construction and Destruction
|
||||
###
|
||||
|
||||
constructor: (@marker, @displayBuffer, @properties) ->
|
||||
@emitter = new Emitter
|
||||
@id = nextId()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Public: Manages the deserializers used for serialized state
|
||||
# Extended: Manages the deserializers used for serialized state
|
||||
#
|
||||
# An instance of this class is always available as the `atom.deserializers`
|
||||
# global.
|
||||
|
||||
@@ -350,7 +350,7 @@ EditorComponent = React.createClass
|
||||
|
||||
observeEditor: ->
|
||||
{editor} = @props
|
||||
@subscribe editor.onDidChangeScreenLines(@onScreenLinesChanged)
|
||||
@subscribe editor.onDidChange(@onScreenLinesChanged)
|
||||
@subscribe editor.observeCursors(@onCursorAdded)
|
||||
@subscribe editor.observeSelections(@onSelectionAdded)
|
||||
@subscribe editor.observeDecorations(@onDecorationAdded)
|
||||
|
||||
@@ -9,6 +9,8 @@ EditorComponent = require './editor-component'
|
||||
# Public: Represents the entire visual pane in Atom.
|
||||
#
|
||||
# The EditorView manages the {Editor}, which manages the file buffers.
|
||||
# `EditorView` is intentionally sparse. Most of the things you'll want
|
||||
# to do are on {Editor}.
|
||||
#
|
||||
# ## Examples
|
||||
#
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
528
src/git.coffee
528
src/git.coffee
@@ -10,7 +10,7 @@ GitUtils = require 'git-utils'
|
||||
|
||||
Task = require './task'
|
||||
|
||||
# Public: Represents the underlying git operations performed by Atom.
|
||||
# Extended: Represents the underlying git operations performed by Atom.
|
||||
#
|
||||
# This class shouldn't be instantiated directly but instead by accessing the
|
||||
# `atom.project` global and calling `getRepo()`. Note that this will only be
|
||||
@@ -47,8 +47,15 @@ class Git
|
||||
EmitterMixin.includeInto(this)
|
||||
Subscriber.includeInto(this)
|
||||
|
||||
@exists: (path) ->
|
||||
if git = @open(path)
|
||||
git.destroy()
|
||||
true
|
||||
else
|
||||
false
|
||||
|
||||
###
|
||||
Section: Class Methods
|
||||
Section: Construction and Destruction
|
||||
###
|
||||
|
||||
# Public: Creates a new Git instance.
|
||||
@@ -66,17 +73,6 @@ class Git
|
||||
catch
|
||||
null
|
||||
|
||||
@exists: (path) ->
|
||||
if git = @open(path)
|
||||
git.destroy()
|
||||
true
|
||||
else
|
||||
false
|
||||
|
||||
###
|
||||
Section: Construction
|
||||
###
|
||||
|
||||
constructor: (path, options={}) ->
|
||||
@emitter = new Emitter
|
||||
@repo = GitUtils.open(path)
|
||||
@@ -100,11 +96,26 @@ class Git
|
||||
if @project?
|
||||
@subscribe @project.eachBuffer (buffer) => @subscribeToBuffer(buffer)
|
||||
|
||||
# Public: Destroy this {Git} object.
|
||||
#
|
||||
# This destroys any tasks and subscriptions and releases the underlying
|
||||
# libgit2 repository handle.
|
||||
destroy: ->
|
||||
if @statusTask?
|
||||
@statusTask.terminate()
|
||||
@statusTask = null
|
||||
|
||||
if @repo?
|
||||
@repo.release()
|
||||
@repo = null
|
||||
|
||||
@unsubscribe()
|
||||
|
||||
###
|
||||
Section: Event Subscription
|
||||
###
|
||||
|
||||
# Essential: Invoke the given callback when a specific file's status has
|
||||
# Public: Invoke the given callback when a specific file's status has
|
||||
# changed. When a file is updated, reloaded, etc, and the status changes, this
|
||||
# will be fired.
|
||||
#
|
||||
@@ -118,7 +129,7 @@ class Git
|
||||
onDidChangeStatus: (callback) ->
|
||||
@emitter.on 'did-change-status', callback
|
||||
|
||||
# Essential: Invoke the given callback when a multiple files' statuses have
|
||||
# Public: Invoke the given callback when a multiple files' statuses have
|
||||
# changed. For example, on window focus, the status of all the paths in the
|
||||
# repo is checked. If any of them have changed, this will be fired. Call
|
||||
# {::getPathStatus(path)} to get the status for your path of choice.
|
||||
@@ -140,7 +151,250 @@ class Git
|
||||
EmitterMixin::on.apply(this, arguments)
|
||||
|
||||
###
|
||||
Section: Instance Methods
|
||||
Section: Repository Data
|
||||
###
|
||||
|
||||
# Public: Returns the {String} path of the repository.
|
||||
getPath: ->
|
||||
@path ?= fs.absolute(@getRepo().getPath())
|
||||
|
||||
# Public: Returns the {String} working directory path of the repository.
|
||||
getWorkingDirectory: -> @getRepo().getWorkingDirectory()
|
||||
|
||||
# Public: Returns true if at the root, false if in a subfolder of the
|
||||
# repository.
|
||||
isProjectAtRoot: ->
|
||||
@projectAtRoot ?= @project?.relativize(@getWorkingDirectory()) is ''
|
||||
|
||||
# Public: Makes a path relative to the repository's working directory.
|
||||
relativize: (path) -> @getRepo().relativize(path)
|
||||
|
||||
# Public: Returns true if the given branch exists.
|
||||
hasBranch: (branch) -> @getReferenceTarget("refs/heads/#{branch}")?
|
||||
|
||||
# Public: Retrieves a shortened version of the HEAD reference value.
|
||||
#
|
||||
# This removes the leading segments of `refs/heads`, `refs/tags`, or
|
||||
# `refs/remotes`. It also shortens the SHA-1 of a detached `HEAD` to 7
|
||||
# characters.
|
||||
#
|
||||
# * `path` An optional {String} path in the repository to get this information
|
||||
# for, only needed if the repository contains submodules.
|
||||
#
|
||||
# Returns a {String}.
|
||||
getShortHead: (path) -> @getRepo(path).getShortHead()
|
||||
|
||||
# Public: Is the given path a submodule in the repository?
|
||||
#
|
||||
# * `path` The {String} path to check.
|
||||
#
|
||||
# Returns a {Boolean}.
|
||||
isSubmodule: (path) ->
|
||||
return false unless path
|
||||
|
||||
repo = @getRepo(path)
|
||||
if repo.isSubmodule(repo.relativize(path))
|
||||
true
|
||||
else
|
||||
# Check if the path is a working directory in a repo that isn't the root.
|
||||
repo isnt @getRepo() and repo.relativize(join(path, 'dir')) is 'dir'
|
||||
|
||||
# Public: Returns the number of commits behind the current branch is from the
|
||||
# its upstream remote branch.
|
||||
#
|
||||
# * `reference` The {String} branch reference name.
|
||||
# * `path` The {String} path in the repository to get this information for,
|
||||
# only needed if the repository contains submodules.
|
||||
getAheadBehindCount: (reference, path) ->
|
||||
@getRepo(path).getAheadBehindCount(reference)
|
||||
|
||||
# Public: Get the cached ahead/behind commit counts for the current branch's
|
||||
# upstream branch.
|
||||
#
|
||||
# * `path` An optional {String} path in the repository to get this information
|
||||
# for, only needed if the repository has submodules.
|
||||
#
|
||||
# Returns an {Object} with the following keys:
|
||||
# * `ahead` The {Number} of commits ahead.
|
||||
# * `behind` The {Number} of commits behind.
|
||||
getCachedUpstreamAheadBehindCount: (path) ->
|
||||
@getRepo(path).upstream ? @upstream
|
||||
|
||||
# Public: Returns the git configuration value specified by the key.
|
||||
#
|
||||
# * `path` An optional {String} path in the repository to get this information
|
||||
# for, only needed if the repository has submodules.
|
||||
getConfigValue: (key, path) -> @getRepo(path).getConfigValue(key)
|
||||
|
||||
# Public: Returns the origin url of the repository.
|
||||
#
|
||||
# * `path` (optional) {String} path in the repository to get this information
|
||||
# for, only needed if the repository has submodules.
|
||||
getOriginUrl: (path) -> @getConfigValue('remote.origin.url', path)
|
||||
|
||||
# Public: Returns the upstream branch for the current HEAD, or null if there
|
||||
# is no upstream branch for the current HEAD.
|
||||
#
|
||||
# * `path` An optional {String} path in the repo to get this information for,
|
||||
# only needed if the repository contains submodules.
|
||||
#
|
||||
# Returns a {String} branch name such as `refs/remotes/origin/master`.
|
||||
getUpstreamBranch: (path) -> @getRepo(path).getUpstreamBranch()
|
||||
|
||||
# Public: Gets all the local and remote references.
|
||||
#
|
||||
# * `path` An optional {String} path in the repository to get this information
|
||||
# for, only needed if the repository has submodules.
|
||||
#
|
||||
# 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: (path) -> @getRepo(path).getReferences()
|
||||
|
||||
# Public: Returns the current {String} SHA for the given reference.
|
||||
#
|
||||
# * `reference` The {String} reference to get the target of.
|
||||
# * `path` An optional {String} path in the repo to get the reference target
|
||||
# for. Only needed if the repository contains submodules.
|
||||
getReferenceTarget: (reference, path) ->
|
||||
@getRepo(path).getReferenceTarget(reference)
|
||||
|
||||
###
|
||||
Section: Reading Status
|
||||
###
|
||||
|
||||
# Public: Returns true if the given path is modified.
|
||||
isPathModified: (path) -> @isStatusModified(@getPathStatus(path))
|
||||
|
||||
# Public: Returns true if the given path is new.
|
||||
isPathNew: (path) -> @isStatusNew(@getPathStatus(path))
|
||||
|
||||
# Public: Is the given path ignored?
|
||||
#
|
||||
# Returns a {Boolean}.
|
||||
isPathIgnored: (path) -> @getRepo().isIgnored(@relativize(path))
|
||||
|
||||
# Public: Get the status of a directory in the repository's working directory.
|
||||
#
|
||||
# * `path` The {String} path to check.
|
||||
#
|
||||
# Returns a {Number} representing the status. This value can be passed to
|
||||
# {::isStatusModified} or {::isStatusNew} to get more information.
|
||||
getDirectoryStatus: (directoryPath) ->
|
||||
directoryPath = "#{@relativize(directoryPath)}/"
|
||||
directoryStatus = 0
|
||||
for path, status of @statuses
|
||||
directoryStatus |= status if path.indexOf(directoryPath) is 0
|
||||
directoryStatus
|
||||
|
||||
# Public: Get the status of a single path in the repository.
|
||||
#
|
||||
# `path` A {String} repository-relative path.
|
||||
#
|
||||
# Returns a {Number} representing the status. This value can be passed to
|
||||
# {::isStatusModified} or {::isStatusNew} to get more information.
|
||||
getPathStatus: (path) ->
|
||||
repo = @getRepo(path)
|
||||
relativePath = @relativize(path)
|
||||
currentPathStatus = @statuses[relativePath] ? 0
|
||||
pathStatus = repo.getStatus(repo.relativize(path)) ? 0
|
||||
pathStatus = 0 if repo.isStatusIgnored(pathStatus)
|
||||
if pathStatus > 0
|
||||
@statuses[relativePath] = pathStatus
|
||||
else
|
||||
delete @statuses[relativePath]
|
||||
if currentPathStatus isnt pathStatus
|
||||
@emit 'status-changed', path, pathStatus
|
||||
@emitter.emit 'did-change-status', {path, pathStatus}
|
||||
|
||||
pathStatus
|
||||
|
||||
# Public: Get the cached status for the given path.
|
||||
#
|
||||
# * `path` A {String} path in the repository, relative or absolute.
|
||||
#
|
||||
# Returns a status {Number} or null if the path is not in the cache.
|
||||
getCachedPathStatus: (path) ->
|
||||
@statuses[@relativize(path)]
|
||||
|
||||
# Public: Returns true if the given status indicates modification.
|
||||
isStatusModified: (status) -> @getRepo().isStatusModified(status)
|
||||
|
||||
# Public: Returns true if the given status indicates a new path.
|
||||
isStatusNew: (status) -> @getRepo().isStatusNew(status)
|
||||
|
||||
###
|
||||
Section: Retrieving Diffs
|
||||
###
|
||||
|
||||
# Public: Retrieves the number of lines added and removed to a path.
|
||||
#
|
||||
# This compares the working directory contents of the path to the `HEAD`
|
||||
# version.
|
||||
#
|
||||
# * `path` The {String} path to check.
|
||||
#
|
||||
# Returns an {Object} with the following keys:
|
||||
# * `added` The {Number} of added lines.
|
||||
# * `deleted` The {Number} of deleted lines.
|
||||
getDiffStats: (path) ->
|
||||
repo = @getRepo(path)
|
||||
repo.getDiffStats(repo.relativize(path))
|
||||
|
||||
# Public: Retrieves the line diffs comparing the `HEAD` version of the given
|
||||
# path and the given text.
|
||||
#
|
||||
# * `path` The {String} path relative to the repository.
|
||||
# * `text` The {String} to compare against the `HEAD` contents
|
||||
#
|
||||
# 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.
|
||||
options = ignoreEolWhitespace: process.platform is 'win32'
|
||||
repo = @getRepo(path)
|
||||
repo.getLineDiffs(repo.relativize(path), text, options)
|
||||
|
||||
###
|
||||
Section: Checking Out
|
||||
###
|
||||
|
||||
# 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:
|
||||
#
|
||||
# ```sh
|
||||
# git reset HEAD -- <path>
|
||||
# git checkout HEAD -- <path>
|
||||
# ```
|
||||
#
|
||||
# * `path` The {String} path to checkout.
|
||||
#
|
||||
# Returns a {Boolean} that's true if the method was successful.
|
||||
checkoutHead: (path) ->
|
||||
repo = @getRepo(path)
|
||||
headCheckedOut = repo.checkoutHead(repo.relativize(path))
|
||||
@getPathStatus(path) if headCheckedOut
|
||||
headCheckedOut
|
||||
|
||||
# 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.
|
||||
#
|
||||
# Returns a Boolean that's true if the method was successful.
|
||||
checkoutReference: (reference, create) ->
|
||||
@getRepo().checkoutReference(reference, create)
|
||||
|
||||
###
|
||||
Section: Private
|
||||
###
|
||||
|
||||
# Subscribes to buffer events.
|
||||
@@ -175,21 +429,6 @@ class Git
|
||||
else
|
||||
checkoutHead()
|
||||
|
||||
# Public: Destroy this {Git} object.
|
||||
#
|
||||
# This destroys any tasks and subscriptions and releases the underlying
|
||||
# libgit2 repository handle.
|
||||
destroy: ->
|
||||
if @statusTask?
|
||||
@statusTask.terminate()
|
||||
@statusTask = null
|
||||
|
||||
if @repo?
|
||||
@repo.release()
|
||||
@repo = null
|
||||
|
||||
@unsubscribe()
|
||||
|
||||
# Returns the corresponding {Repository}
|
||||
getRepo: (path) ->
|
||||
if @repo?
|
||||
@@ -201,233 +440,6 @@ class Git
|
||||
# last time the index was read.
|
||||
refreshIndex: -> @getRepo().refreshIndex()
|
||||
|
||||
# Public: Returns the {String} path of the repository.
|
||||
getPath: ->
|
||||
@path ?= fs.absolute(@getRepo().getPath())
|
||||
|
||||
# Public: Returns the {String} working directory path of the repository.
|
||||
getWorkingDirectory: -> @getRepo().getWorkingDirectory()
|
||||
|
||||
# Public: Get the status of a single path in the repository.
|
||||
#
|
||||
# `path` A {String} repository-relative path.
|
||||
#
|
||||
# Returns a {Number} representing the status. This value can be passed to
|
||||
# {::isStatusModified} or {::isStatusNew} to get more information.
|
||||
getPathStatus: (path) ->
|
||||
repo = @getRepo(path)
|
||||
relativePath = @relativize(path)
|
||||
currentPathStatus = @statuses[relativePath] ? 0
|
||||
pathStatus = repo.getStatus(repo.relativize(path)) ? 0
|
||||
pathStatus = 0 if repo.isStatusIgnored(pathStatus)
|
||||
if pathStatus > 0
|
||||
@statuses[relativePath] = pathStatus
|
||||
else
|
||||
delete @statuses[relativePath]
|
||||
if currentPathStatus isnt pathStatus
|
||||
@emit 'status-changed', path, pathStatus
|
||||
@emitter.emit 'did-change-status', {path, pathStatus}
|
||||
|
||||
pathStatus
|
||||
|
||||
# Public: Is the given path ignored?
|
||||
#
|
||||
# Returns a {Boolean}.
|
||||
isPathIgnored: (path) -> @getRepo().isIgnored(@relativize(path))
|
||||
|
||||
# Public: Returns true if the given status indicates modification.
|
||||
isStatusModified: (status) -> @getRepo().isStatusModified(status)
|
||||
|
||||
# Public: Returns true if the given path is modified.
|
||||
isPathModified: (path) -> @isStatusModified(@getPathStatus(path))
|
||||
|
||||
# Public: Returns true if the given status indicates a new path.
|
||||
isStatusNew: (status) -> @getRepo().isStatusNew(status)
|
||||
|
||||
# Public: Returns true if the given path is new.
|
||||
isPathNew: (path) -> @isStatusNew(@getPathStatus(path))
|
||||
|
||||
# Public: Returns true if at the root, false if in a subfolder of the
|
||||
# repository.
|
||||
isProjectAtRoot: ->
|
||||
@projectAtRoot ?= @project?.relativize(@getWorkingDirectory()) is ''
|
||||
|
||||
# Public: Makes a path relative to the repository's working directory.
|
||||
relativize: (path) -> @getRepo().relativize(path)
|
||||
|
||||
# Public: Retrieves a shortened version of the HEAD reference value.
|
||||
#
|
||||
# This removes the leading segments of `refs/heads`, `refs/tags`, or
|
||||
# `refs/remotes`. It also shortens the SHA-1 of a detached `HEAD` to 7
|
||||
# characters.
|
||||
#
|
||||
# * `path` An optional {String} path in the repository to get this information
|
||||
# for, only needed if the repository contains submodules.
|
||||
#
|
||||
# Returns a {String}.
|
||||
getShortHead: (path) -> @getRepo(path).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:
|
||||
#
|
||||
# ```sh
|
||||
# git reset HEAD -- <path>
|
||||
# git checkout HEAD -- <path>
|
||||
# ```
|
||||
#
|
||||
# * `path` The {String} path to checkout.
|
||||
#
|
||||
# Returns a {Boolean} that's true if the method was successful.
|
||||
checkoutHead: (path) ->
|
||||
repo = @getRepo(path)
|
||||
headCheckedOut = repo.checkoutHead(repo.relativize(path))
|
||||
@getPathStatus(path) if headCheckedOut
|
||||
headCheckedOut
|
||||
|
||||
# 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.
|
||||
#
|
||||
# Returns a Boolean that's true if the method was successful.
|
||||
checkoutReference: (reference, create) ->
|
||||
@getRepo().checkoutReference(reference, create)
|
||||
|
||||
# Public: Retrieves the number of lines added and removed to a path.
|
||||
#
|
||||
# This compares the working directory contents of the path to the `HEAD`
|
||||
# version.
|
||||
#
|
||||
# * `path` The {String} path to check.
|
||||
#
|
||||
# Returns an {Object} with the following keys:
|
||||
# * `added` The {Number} of added lines.
|
||||
# * `deleted` The {Number} of deleted lines.
|
||||
getDiffStats: (path) ->
|
||||
repo = @getRepo(path)
|
||||
repo.getDiffStats(repo.relativize(path))
|
||||
|
||||
# Public: Is the given path a submodule in the repository?
|
||||
#
|
||||
# * `path` The {String} path to check.
|
||||
#
|
||||
# Returns a {Boolean}.
|
||||
isSubmodule: (path) ->
|
||||
return false unless path
|
||||
|
||||
repo = @getRepo(path)
|
||||
if repo.isSubmodule(repo.relativize(path))
|
||||
true
|
||||
else
|
||||
# Check if the path is a working directory in a repo that isn't the root.
|
||||
repo isnt @getRepo() and repo.relativize(join(path, 'dir')) is 'dir'
|
||||
|
||||
# Public: Get the status of a directory in the repository's working directory.
|
||||
#
|
||||
# * `path` The {String} path to check.
|
||||
#
|
||||
# Returns a {Number} representing the status. This value can be passed to
|
||||
# {::isStatusModified} or {::isStatusNew} to get more information.
|
||||
getDirectoryStatus: (directoryPath) ->
|
||||
directoryPath = "#{@relativize(directoryPath)}/"
|
||||
directoryStatus = 0
|
||||
for path, status of @statuses
|
||||
directoryStatus |= status if path.indexOf(directoryPath) is 0
|
||||
directoryStatus
|
||||
|
||||
# Public: Retrieves the line diffs comparing the `HEAD` version of the given
|
||||
# path and the given text.
|
||||
#
|
||||
# * `path` The {String} path relative to the repository.
|
||||
# * `text` The {String} to compare against the `HEAD` contents
|
||||
#
|
||||
# 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.
|
||||
options = ignoreEolWhitespace: process.platform is 'win32'
|
||||
repo = @getRepo(path)
|
||||
repo.getLineDiffs(repo.relativize(path), text, options)
|
||||
|
||||
# Public: Returns the git configuration value specified by the key.
|
||||
#
|
||||
# * `path` An optional {String} path in the repository to get this information
|
||||
# for, only needed if the repository has submodules.
|
||||
getConfigValue: (key, path) -> @getRepo(path).getConfigValue(key)
|
||||
|
||||
# Public: Returns the origin url of the repository.
|
||||
#
|
||||
# * `path` An optional {String} path in the repository to get this information
|
||||
# for, only needed if the repository has submodules.
|
||||
getOriginUrl: (path) -> @getConfigValue('remote.origin.url', path)
|
||||
|
||||
# Public: Returns the upstream branch for the current HEAD, or null if there
|
||||
# is no upstream branch for the current HEAD.
|
||||
#
|
||||
# * `path` An optional {String} path in the repo to get this information for,
|
||||
# only needed if the repository contains submodules.
|
||||
#
|
||||
# Returns a {String} branch name such as `refs/remotes/origin/master`.
|
||||
getUpstreamBranch: (path) -> @getRepo(path).getUpstreamBranch()
|
||||
|
||||
# Public: Returns the current {String} SHA for the given reference.
|
||||
#
|
||||
# * `reference` The {String} reference to get the target of.
|
||||
# * `path` An optional {String} path in the repo to get the reference target
|
||||
# for. Only needed if the repository contains submodules.
|
||||
getReferenceTarget: (reference, path) ->
|
||||
@getRepo(path).getReferenceTarget(reference)
|
||||
|
||||
# Public: Gets all the local and remote references.
|
||||
#
|
||||
# * `path` An optional {String} path in the repository to get this information
|
||||
# for, only needed if the repository has submodules.
|
||||
#
|
||||
# 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: (path) -> @getRepo(path).getReferences()
|
||||
|
||||
# Public: Returns the number of commits behind the current branch is from the
|
||||
# its upstream remote branch.
|
||||
#
|
||||
# * `reference` The {String} branch reference name.
|
||||
# * `path` The {String} path in the repository to get this information for,
|
||||
# only needed if the repository contains submodules.
|
||||
getAheadBehindCount: (reference, path) ->
|
||||
@getRepo(path).getAheadBehindCount(reference)
|
||||
|
||||
# Public: Get the cached ahead/behind commit counts for the current branch's
|
||||
# upstream branch.
|
||||
#
|
||||
# * `path` An optional {String} path in the repository to get this information
|
||||
# for, only needed if the repository has submodules.
|
||||
#
|
||||
# Returns an {Object} with the following keys:
|
||||
# * `ahead` The {Number} of commits ahead.
|
||||
# * `behind` The {Number} of commits behind.
|
||||
getCachedUpstreamAheadBehindCount: (path) ->
|
||||
@getRepo(path).upstream ? @upstream
|
||||
|
||||
# Public: Get the cached status for the given path.
|
||||
#
|
||||
# * `path` A {String} path in the repository, relative or absolute.
|
||||
#
|
||||
# Returns a status {Number} or null if the path is not in the cache.
|
||||
getCachedPathStatus: (path) ->
|
||||
@statuses[@relativize(path)]
|
||||
|
||||
# Public: Returns true if the given branch exists.
|
||||
hasBranch: (branch) -> @getReferenceTarget("refs/heads/#{branch}")?
|
||||
|
||||
# Refreshes the current git status in an outside process and asynchronously
|
||||
# updates the relevant properties.
|
||||
refreshStatus: ->
|
||||
|
||||
@@ -5,7 +5,7 @@ ipc = require 'ipc'
|
||||
CSON = require 'season'
|
||||
fs = require 'fs-plus'
|
||||
|
||||
# Public: Provides a registry for menu items that you'd like to appear in the
|
||||
# Extended: Provides a registry for menu items that you'd like to appear in the
|
||||
# application menu.
|
||||
#
|
||||
# An instance of this class is always available as the `atom.menu` global.
|
||||
|
||||
@@ -48,7 +48,7 @@ class PackageManager
|
||||
Section: Event Subscription
|
||||
###
|
||||
|
||||
# Essential: Invoke the given callback when all packages have been activated.
|
||||
# Public: Invoke the given callback when all packages have been activated.
|
||||
#
|
||||
# * `callback` {Function}
|
||||
#
|
||||
@@ -56,7 +56,7 @@ class PackageManager
|
||||
onDidLoadAll: (callback) ->
|
||||
@emitter.on 'did-load-all', callback
|
||||
|
||||
# Essential: Invoke the given callback when all packages have been activated.
|
||||
# Public: Invoke the given callback when all packages have been activated.
|
||||
#
|
||||
# * `callback` {Function}
|
||||
#
|
||||
@@ -75,10 +75,10 @@ class PackageManager
|
||||
EmitterMixin::on.apply(this, arguments)
|
||||
|
||||
###
|
||||
Section: Instance Methods
|
||||
Section: Package system data
|
||||
###
|
||||
|
||||
# Extended: Get the path to the apm command.
|
||||
# Public: Get the path to the apm command.
|
||||
#
|
||||
# Return a {String} file path to apm.
|
||||
getApmPath: ->
|
||||
@@ -86,19 +86,43 @@ class PackageManager
|
||||
commandName += '.cmd' if process.platform is 'win32'
|
||||
@apmPath ?= path.resolve(__dirname, '..', 'apm', 'node_modules', 'atom-package-manager', 'bin', commandName)
|
||||
|
||||
# Extended: Get the paths being used to look for packages.
|
||||
# Public: Get the paths being used to look for packages.
|
||||
#
|
||||
# Returns an {Array} of {String} directory paths.
|
||||
getPackageDirPaths: ->
|
||||
_.clone(@packageDirPaths)
|
||||
|
||||
getPackageState: (name) ->
|
||||
@packageStates[name]
|
||||
###
|
||||
Section: General package data
|
||||
###
|
||||
|
||||
setPackageState: (name, state) ->
|
||||
@packageStates[name] = state
|
||||
# Public: Resolve the given package name to a path on disk.
|
||||
#
|
||||
# * `name` - The {String} package name.
|
||||
#
|
||||
# Return a {String} folder path or undefined if it could not be resolved.
|
||||
resolvePackagePath: (name) ->
|
||||
return name if fs.isDirectorySync(name)
|
||||
|
||||
# Extended: Enable the package with the given name.
|
||||
packagePath = fs.resolve(@packageDirPaths..., name)
|
||||
return packagePath if fs.isDirectorySync(packagePath)
|
||||
|
||||
packagePath = path.join(@resourcePath, 'node_modules', name)
|
||||
return packagePath if @hasAtomEngine(packagePath)
|
||||
|
||||
# Public: Is the package with the given name bundled with Atom?
|
||||
#
|
||||
# * `name` - The {String} package name.
|
||||
#
|
||||
# Returns a {Boolean}.
|
||||
isBundledPackage: (name) ->
|
||||
@getPackageDependencies().hasOwnProperty(name)
|
||||
|
||||
###
|
||||
Section: Enabling and disabling packages
|
||||
###
|
||||
|
||||
# Public: Enable the package with the given name.
|
||||
#
|
||||
# Returns the {Package} that was enabled or null if it isn't loaded.
|
||||
enablePackage: (name) ->
|
||||
@@ -106,7 +130,7 @@ class PackageManager
|
||||
pack?.enable()
|
||||
pack
|
||||
|
||||
# Extended: Disable the package with the given name.
|
||||
# Public: Disable the package with the given name.
|
||||
#
|
||||
# Returns the {Package} that was disabled or null if it isn't loaded.
|
||||
disablePackage: (name) ->
|
||||
@@ -114,51 +138,23 @@ class PackageManager
|
||||
pack?.disable()
|
||||
pack
|
||||
|
||||
# Activate all the packages that should be activated.
|
||||
activate: ->
|
||||
for [activator, types] in @packageActivators
|
||||
packages = @getLoadedPackagesForTypes(types)
|
||||
activator.activatePackages(packages)
|
||||
@emit 'activated'
|
||||
@emitter.emit 'did-activate-all'
|
||||
# Public: Is the package with the given name disabled?
|
||||
#
|
||||
# * `name` - The {String} package name.
|
||||
#
|
||||
# Returns a {Boolean}.
|
||||
isPackageDisabled: (name) ->
|
||||
_.include(atom.config.get('core.disabledPackages') ? [], name)
|
||||
|
||||
# another type of package manager can handle other package types.
|
||||
# See ThemeManager
|
||||
registerPackageActivator: (activator, types) ->
|
||||
@packageActivators.push([activator, types])
|
||||
###
|
||||
Section: Accessing active packages
|
||||
###
|
||||
|
||||
activatePackages: (packages) ->
|
||||
@activatePackage(pack.name) for pack in packages
|
||||
@observeDisabledPackages()
|
||||
|
||||
# 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
|
||||
|
||||
# Deactivate all packages
|
||||
deactivatePackages: ->
|
||||
@deactivatePackage(pack.name) for pack in @getLoadedPackages()
|
||||
@unobserveDisabledPackages()
|
||||
|
||||
# Deactivate the package with the given name
|
||||
deactivatePackage: (name) ->
|
||||
pack = @getLoadedPackage(name)
|
||||
if @isPackageActive(name)
|
||||
@setPackageState(pack.name, state) if state = pack.serialize?()
|
||||
pack.deactivate()
|
||||
delete @activePackages[pack.name]
|
||||
|
||||
# Essential: Get an {Array} of all the active {Package}s.
|
||||
# Public: Get an {Array} of all the active {Package}s.
|
||||
getActivePackages: ->
|
||||
_.values(@activePackages)
|
||||
|
||||
# Essential: Get the active {Package} with the given name.
|
||||
# Public: Get the active {Package} with the given name.
|
||||
#
|
||||
# * `name` - The {String} package name.
|
||||
#
|
||||
@@ -174,6 +170,91 @@ class PackageManager
|
||||
isPackageActive: (name) ->
|
||||
@getActivePackage(name)?
|
||||
|
||||
###
|
||||
Section: Accessing loaded packages
|
||||
###
|
||||
|
||||
# Public: Get an {Array} of all the loaded {Package}s
|
||||
getLoadedPackages: ->
|
||||
_.values(@loadedPackages)
|
||||
|
||||
# Get packages for a certain package type
|
||||
#
|
||||
# * `types` an {Array} of {String}s like ['atom', 'textmate'].
|
||||
getLoadedPackagesForTypes: (types) ->
|
||||
pack for pack in @getLoadedPackages() when pack.getType() in types
|
||||
|
||||
# Public: Get the loaded {Package} with the given name.
|
||||
#
|
||||
# * `name` - The {String} package name.
|
||||
#
|
||||
# Returns a {Package} or undefined.
|
||||
getLoadedPackage: (name) ->
|
||||
@loadedPackages[name]
|
||||
|
||||
# Public: Is the package with the given name loaded?
|
||||
#
|
||||
# * `name` - The {String} package name.
|
||||
#
|
||||
# Returns a {Boolean}.
|
||||
isPackageLoaded: (name) ->
|
||||
@getLoadedPackage(name)?
|
||||
|
||||
###
|
||||
Section: Accessing available packages
|
||||
###
|
||||
|
||||
# Public: Get an {Array} of {String}s of all the available package paths.
|
||||
getAvailablePackagePaths: ->
|
||||
packagePaths = []
|
||||
|
||||
for packageDirPath in @packageDirPaths
|
||||
for packagePath in fs.listSync(packageDirPath)
|
||||
packagePaths.push(packagePath) if fs.isDirectorySync(packagePath)
|
||||
|
||||
packagesPath = path.join(@resourcePath, 'node_modules')
|
||||
for packageName, packageVersion of @getPackageDependencies()
|
||||
packagePath = path.join(packagesPath, packageName)
|
||||
packagePaths.push(packagePath) if fs.isDirectorySync(packagePath)
|
||||
|
||||
_.uniq(packagePaths)
|
||||
|
||||
# Public: Get an {Array} of {String}s of all the available package names.
|
||||
getAvailablePackageNames: ->
|
||||
_.uniq _.map @getAvailablePackagePaths(), (packagePath) -> path.basename(packagePath)
|
||||
|
||||
# Public: Get an {Array} of {String}s of all the available package metadata.
|
||||
getAvailablePackageMetadata: ->
|
||||
packages = []
|
||||
for packagePath in @getAvailablePackagePaths()
|
||||
name = path.basename(packagePath)
|
||||
metadata = @getLoadedPackage(name)?.metadata ? Package.loadMetadata(packagePath, true)
|
||||
packages.push(metadata)
|
||||
packages
|
||||
|
||||
###
|
||||
Section: Private
|
||||
###
|
||||
|
||||
getPackageState: (name) ->
|
||||
@packageStates[name]
|
||||
|
||||
setPackageState: (name, state) ->
|
||||
@packageStates[name] = state
|
||||
|
||||
getPackageDependencies: ->
|
||||
unless @packageDependencies?
|
||||
try
|
||||
metadataPath = path.join(@resourcePath, 'package.json')
|
||||
{@packageDependencies} = JSON.parse(fs.readFileSync(metadataPath)) ? {}
|
||||
@packageDependencies ?= {}
|
||||
|
||||
@packageDependencies
|
||||
|
||||
hasAtomEngine: (packagePath) ->
|
||||
metadata = Package.loadMetadata(packagePath, true)
|
||||
metadata?.engines?.atom?
|
||||
|
||||
unobserveDisabledPackages: ->
|
||||
@disabledPackagesSubscription?.off()
|
||||
@disabledPackagesSubscription = null
|
||||
@@ -234,99 +315,42 @@ class PackageManager
|
||||
else
|
||||
throw new Error("No loaded package for name '#{name}'")
|
||||
|
||||
# Essential: Get the loaded {Package} with the given name.
|
||||
#
|
||||
# * `name` - The {String} package name.
|
||||
#
|
||||
# Returns a {Package} or undefined.
|
||||
getLoadedPackage: (name) ->
|
||||
@loadedPackages[name]
|
||||
# Activate all the packages that should be activated.
|
||||
activate: ->
|
||||
for [activator, types] in @packageActivators
|
||||
packages = @getLoadedPackagesForTypes(types)
|
||||
activator.activatePackages(packages)
|
||||
@emit 'activated'
|
||||
@emitter.emit 'did-activate-all'
|
||||
|
||||
# Essential: Is the package with the given name loaded?
|
||||
#
|
||||
# * `name` - The {String} package name.
|
||||
#
|
||||
# Returns a {Boolean}.
|
||||
isPackageLoaded: (name) ->
|
||||
@getLoadedPackage(name)?
|
||||
# another type of package manager can handle other package types.
|
||||
# See ThemeManager
|
||||
registerPackageActivator: (activator, types) ->
|
||||
@packageActivators.push([activator, types])
|
||||
|
||||
# Essential: Get an {Array} of all the loaded {Package}s
|
||||
getLoadedPackages: ->
|
||||
_.values(@loadedPackages)
|
||||
activatePackages: (packages) ->
|
||||
@activatePackage(pack.name) for pack in packages
|
||||
@observeDisabledPackages()
|
||||
|
||||
# Get packages for a certain package type
|
||||
#
|
||||
# * `types` an {Array} of {String}s like ['atom', 'textmate'].
|
||||
getLoadedPackagesForTypes: (types) ->
|
||||
pack for pack in @getLoadedPackages() when pack.getType() in types
|
||||
# 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
|
||||
|
||||
# Extended: Resolve the given package name to a path on disk.
|
||||
#
|
||||
# * `name` - The {String} package name.
|
||||
#
|
||||
# Return a {String} folder path or undefined if it could not be resolved.
|
||||
resolvePackagePath: (name) ->
|
||||
return name if fs.isDirectorySync(name)
|
||||
# Deactivate all packages
|
||||
deactivatePackages: ->
|
||||
@deactivatePackage(pack.name) for pack in @getLoadedPackages()
|
||||
@unobserveDisabledPackages()
|
||||
|
||||
packagePath = fs.resolve(@packageDirPaths..., name)
|
||||
return packagePath if fs.isDirectorySync(packagePath)
|
||||
|
||||
packagePath = path.join(@resourcePath, 'node_modules', name)
|
||||
return packagePath if @hasAtomEngine(packagePath)
|
||||
|
||||
# Essential: Is the package with the given name disabled?
|
||||
#
|
||||
# * `name` - The {String} package name.
|
||||
#
|
||||
# Returns a {Boolean}.
|
||||
isPackageDisabled: (name) ->
|
||||
_.include(atom.config.get('core.disabledPackages') ? [], name)
|
||||
|
||||
hasAtomEngine: (packagePath) ->
|
||||
metadata = Package.loadMetadata(packagePath, true)
|
||||
metadata?.engines?.atom?
|
||||
|
||||
# Extended: Is the package with the given name bundled with Atom?
|
||||
#
|
||||
# * `name` - The {String} package name.
|
||||
#
|
||||
# Returns a {Boolean}.
|
||||
isBundledPackage: (name) ->
|
||||
@getPackageDependencies().hasOwnProperty(name)
|
||||
|
||||
getPackageDependencies: ->
|
||||
unless @packageDependencies?
|
||||
try
|
||||
metadataPath = path.join(@resourcePath, 'package.json')
|
||||
{@packageDependencies} = JSON.parse(fs.readFileSync(metadataPath)) ? {}
|
||||
@packageDependencies ?= {}
|
||||
|
||||
@packageDependencies
|
||||
|
||||
# Extended: Get an {Array} of {String}s of all the available package paths.
|
||||
getAvailablePackagePaths: ->
|
||||
packagePaths = []
|
||||
|
||||
for packageDirPath in @packageDirPaths
|
||||
for packagePath in fs.listSync(packageDirPath)
|
||||
packagePaths.push(packagePath) if fs.isDirectorySync(packagePath)
|
||||
|
||||
packagesPath = path.join(@resourcePath, 'node_modules')
|
||||
for packageName, packageVersion of @getPackageDependencies()
|
||||
packagePath = path.join(packagesPath, packageName)
|
||||
packagePaths.push(packagePath) if fs.isDirectorySync(packagePath)
|
||||
|
||||
_.uniq(packagePaths)
|
||||
|
||||
# Extended: Get an {Array} of {String}s of all the available package names.
|
||||
getAvailablePackageNames: ->
|
||||
_.uniq _.map @getAvailablePackagePaths(), (packagePath) -> path.basename(packagePath)
|
||||
|
||||
# Extended: Get an {Array} of {String}s of all the available package metadata.
|
||||
getAvailablePackageMetadata: ->
|
||||
packages = []
|
||||
for packagePath in @getAvailablePackagePaths()
|
||||
name = path.basename(packagePath)
|
||||
metadata = @getLoadedPackage(name)?.metadata ? Package.loadMetadata(packagePath, true)
|
||||
packages.push(metadata)
|
||||
packages
|
||||
# Deactivate the package with the given name
|
||||
deactivatePackage: (name) ->
|
||||
pack = @getLoadedPackage(name)
|
||||
if @isPackageActive(name)
|
||||
@setPackageState(pack.name, state) if state = pack.serialize?()
|
||||
pack.deactivate()
|
||||
delete @activePackages[pack.name]
|
||||
|
||||
@@ -23,14 +23,16 @@ class Project extends Model
|
||||
atom.deserializers.add(this)
|
||||
Serializable.includeInto(this)
|
||||
|
||||
# Public: Find the local path for the given repository URL.
|
||||
#
|
||||
# * `repoUrl` {String} url to a git repository
|
||||
@pathForRepositoryUrl: (repoUrl) ->
|
||||
deprecate '::pathForRepositoryUrl will be removed. Please remove from your code.'
|
||||
[repoName] = url.parse(repoUrl).path.split('/')[-1..]
|
||||
repoName = repoName.replace(/\.git$/, '')
|
||||
path.join(atom.config.get('core.projectHome'), repoName)
|
||||
|
||||
###
|
||||
Section: Construction and Destruction
|
||||
###
|
||||
|
||||
constructor: ({path, @buffers}={}) ->
|
||||
@buffers ?= []
|
||||
|
||||
@@ -40,14 +42,6 @@ class Project extends Model
|
||||
|
||||
@setPath(path)
|
||||
|
||||
serializeParams: ->
|
||||
path: @path
|
||||
buffers: _.compact(@buffers.map (buffer) -> buffer.serialize() if buffer.isRetained())
|
||||
|
||||
deserializeParams: (params) ->
|
||||
params.buffers = params.buffers.map (bufferState) -> atom.deserializers.deserialize(bufferState)
|
||||
params
|
||||
|
||||
destroyed: ->
|
||||
buffer.destroy() for buffer in @getBuffers()
|
||||
@destroyRepo()
|
||||
@@ -60,9 +54,29 @@ class Project extends Model
|
||||
destroyUnretainedBuffers: ->
|
||||
buffer.destroy() for buffer in @getBuffers() when not buffer.isRetained()
|
||||
|
||||
###
|
||||
Section: Serialization
|
||||
###
|
||||
|
||||
serializeParams: ->
|
||||
path: @path
|
||||
buffers: _.compact(@buffers.map (buffer) -> buffer.serialize() if buffer.isRetained())
|
||||
|
||||
deserializeParams: (params) ->
|
||||
params.buffers = params.buffers.map (bufferState) -> atom.deserializers.deserialize(bufferState)
|
||||
params
|
||||
|
||||
###
|
||||
Section: Accessing the git repository
|
||||
###
|
||||
|
||||
# Public: Returns the {Git} repository if available.
|
||||
getRepo: -> @repo
|
||||
|
||||
###
|
||||
Section: Managing Paths
|
||||
###
|
||||
|
||||
# Public: Returns the project's {String} fullpath.
|
||||
getPath: ->
|
||||
@rootDirectory?.path
|
||||
@@ -124,6 +138,99 @@ class Project extends Model
|
||||
contains: (pathToCheck) ->
|
||||
@rootDirectory?.contains(pathToCheck) ? false
|
||||
|
||||
###
|
||||
Section: Searching and Replacing
|
||||
###
|
||||
|
||||
# Public: Performs a search across all the files in the project.
|
||||
#
|
||||
# * `regex` {RegExp} to search with.
|
||||
# * `options` (optional) {Object} (default: {})
|
||||
# * `paths` An {Array} of glob patterns to search within
|
||||
# * `iterator` {Function} callback on each file found
|
||||
scan: (regex, options={}, iterator) ->
|
||||
if _.isFunction(options)
|
||||
iterator = options
|
||||
options = {}
|
||||
|
||||
deferred = Q.defer()
|
||||
|
||||
searchOptions =
|
||||
ignoreCase: regex.ignoreCase
|
||||
inclusions: options.paths
|
||||
includeHidden: true
|
||||
excludeVcsIgnores: atom.config.get('core.excludeVcsIgnoredPaths')
|
||||
exclusions: atom.config.get('core.ignoredNames')
|
||||
|
||||
task = Task.once require.resolve('./scan-handler'), @getPath(), regex.source, searchOptions, ->
|
||||
deferred.resolve()
|
||||
|
||||
task.on 'scan:result-found', (result) =>
|
||||
iterator(result) unless @isPathModified(result.filePath)
|
||||
|
||||
task.on 'scan:file-error', (error) ->
|
||||
iterator(null, error)
|
||||
|
||||
if _.isFunction(options.onPathsSearched)
|
||||
task.on 'scan:paths-searched', (numberOfPathsSearched) ->
|
||||
options.onPathsSearched(numberOfPathsSearched)
|
||||
|
||||
for buffer in @getBuffers() when buffer.isModified()
|
||||
filePath = buffer.getPath()
|
||||
continue unless @contains(filePath)
|
||||
matches = []
|
||||
buffer.scan regex, (match) -> matches.push match
|
||||
iterator {filePath, matches} if matches.length > 0
|
||||
|
||||
promise = deferred.promise
|
||||
promise.cancel = ->
|
||||
task.terminate()
|
||||
deferred.resolve('cancelled')
|
||||
promise
|
||||
|
||||
# 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:
|
||||
# * `options` {Object} with keys `filePath` and `replacements`
|
||||
replace: (regex, replacementText, filePaths, iterator) ->
|
||||
deferred = Q.defer()
|
||||
|
||||
openPaths = (buffer.getPath() for buffer in @getBuffers())
|
||||
outOfProcessPaths = _.difference(filePaths, openPaths)
|
||||
|
||||
inProcessFinished = !openPaths.length
|
||||
outOfProcessFinished = !outOfProcessPaths.length
|
||||
checkFinished = ->
|
||||
deferred.resolve() if outOfProcessFinished and inProcessFinished
|
||||
|
||||
unless outOfProcessFinished.length
|
||||
flags = 'g'
|
||||
flags += 'i' if regex.ignoreCase
|
||||
|
||||
task = Task.once require.resolve('./replace-handler'), outOfProcessPaths, regex.source, flags, replacementText, ->
|
||||
outOfProcessFinished = true
|
||||
checkFinished()
|
||||
|
||||
task.on 'replace:path-replaced', iterator
|
||||
task.on 'replace:file-error', (error) -> iterator(null, error)
|
||||
|
||||
for buffer in @getBuffers()
|
||||
continue unless buffer.getPath() in filePaths
|
||||
replacements = buffer.replace(regex, replacementText, iterator)
|
||||
iterator({filePath: buffer.getPath(), replacements}) if replacements
|
||||
|
||||
inProcessFinished = true
|
||||
checkFinished()
|
||||
|
||||
deferred.promise
|
||||
|
||||
###
|
||||
Section: Private
|
||||
###
|
||||
|
||||
# Given a path to a file, this constructs and associates a new
|
||||
# {Editor}, showing the file.
|
||||
#
|
||||
@@ -222,91 +329,6 @@ class Project extends Model
|
||||
[buffer] = @buffers.splice(index, 1)
|
||||
buffer?.destroy()
|
||||
|
||||
# Public: Performs a search across all the files in the project.
|
||||
#
|
||||
# * `regex` {RegExp} to search with.
|
||||
# * `options` (optional) {Object} (default: {})
|
||||
# * `paths` An {Array} of glob patterns to search within
|
||||
# * `iterator` {Function} callback on each file found
|
||||
scan: (regex, options={}, iterator) ->
|
||||
if _.isFunction(options)
|
||||
iterator = options
|
||||
options = {}
|
||||
|
||||
deferred = Q.defer()
|
||||
|
||||
searchOptions =
|
||||
ignoreCase: regex.ignoreCase
|
||||
inclusions: options.paths
|
||||
includeHidden: true
|
||||
excludeVcsIgnores: atom.config.get('core.excludeVcsIgnoredPaths')
|
||||
exclusions: atom.config.get('core.ignoredNames')
|
||||
|
||||
task = Task.once require.resolve('./scan-handler'), @getPath(), regex.source, searchOptions, ->
|
||||
deferred.resolve()
|
||||
|
||||
task.on 'scan:result-found', (result) =>
|
||||
iterator(result) unless @isPathModified(result.filePath)
|
||||
|
||||
task.on 'scan:file-error', (error) ->
|
||||
iterator(null, error)
|
||||
|
||||
if _.isFunction(options.onPathsSearched)
|
||||
task.on 'scan:paths-searched', (numberOfPathsSearched) ->
|
||||
options.onPathsSearched(numberOfPathsSearched)
|
||||
|
||||
for buffer in @getBuffers() when buffer.isModified()
|
||||
filePath = buffer.getPath()
|
||||
continue unless @contains(filePath)
|
||||
matches = []
|
||||
buffer.scan regex, (match) -> matches.push match
|
||||
iterator {filePath, matches} if matches.length > 0
|
||||
|
||||
promise = deferred.promise
|
||||
promise.cancel = ->
|
||||
task.terminate()
|
||||
deferred.resolve('cancelled')
|
||||
promise
|
||||
|
||||
# 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:
|
||||
# * `options` {Object} with keys `filePath` and `replacements`
|
||||
replace: (regex, replacementText, filePaths, iterator) ->
|
||||
deferred = Q.defer()
|
||||
|
||||
openPaths = (buffer.getPath() for buffer in @getBuffers())
|
||||
outOfProcessPaths = _.difference(filePaths, openPaths)
|
||||
|
||||
inProcessFinished = !openPaths.length
|
||||
outOfProcessFinished = !outOfProcessPaths.length
|
||||
checkFinished = ->
|
||||
deferred.resolve() if outOfProcessFinished and inProcessFinished
|
||||
|
||||
unless outOfProcessFinished.length
|
||||
flags = 'g'
|
||||
flags += 'i' if regex.ignoreCase
|
||||
|
||||
task = Task.once require.resolve('./replace-handler'), outOfProcessPaths, regex.source, flags, replacementText, ->
|
||||
outOfProcessFinished = true
|
||||
checkFinished()
|
||||
|
||||
task.on 'replace:path-replaced', iterator
|
||||
task.on 'replace:file-error', (error) -> iterator(null, error)
|
||||
|
||||
for buffer in @getBuffers()
|
||||
continue unless buffer.getPath() in filePaths
|
||||
replacements = buffer.replace(regex, replacementText, iterator)
|
||||
iterator({filePath: buffer.getPath(), replacements}) if replacements
|
||||
|
||||
inProcessFinished = true
|
||||
checkFinished()
|
||||
|
||||
deferred.promise
|
||||
|
||||
buildEditorForBuffer: (buffer, editorOptions) ->
|
||||
editor = new Editor(_.extend({buffer, registerEditor: true}, editorOptions))
|
||||
editor
|
||||
|
||||
@@ -46,7 +46,11 @@ class SelectListView extends View
|
||||
inputThrottle: 50
|
||||
cancelling: false
|
||||
|
||||
# Public: Initialize the select list view.
|
||||
###
|
||||
Section: Construction
|
||||
###
|
||||
|
||||
# Essential: Initialize the select list view.
|
||||
#
|
||||
# This method can be overridden by subclasses but `super` should always
|
||||
# be called.
|
||||
@@ -85,13 +89,39 @@ class SelectListView extends View
|
||||
@confirmSelection() if $(e.target).closest('li').hasClass('selected')
|
||||
e.preventDefault()
|
||||
|
||||
schedulePopulateList: ->
|
||||
clearTimeout(@scheduleTimeout)
|
||||
populateCallback = =>
|
||||
@populateList() if @isOnDom()
|
||||
@scheduleTimeout = setTimeout(populateCallback, @inputThrottle)
|
||||
###
|
||||
Section: Methods that must be overridden
|
||||
###
|
||||
|
||||
# Public: Set the array of items to display in the list.
|
||||
# Essential: Create a view for the given model item.
|
||||
#
|
||||
# This method must be overridden by subclasses.
|
||||
#
|
||||
# This is called when the item is about to appended to the list view.
|
||||
#
|
||||
# * `item` The model item being rendered. This will always be one of the items
|
||||
# previously passed to {::setItems}.
|
||||
#
|
||||
# Returns a String of HTML, DOM element, jQuery object, or View.
|
||||
viewForItem: (item) ->
|
||||
throw new Error("Subclass must implement a viewForItem(item) method")
|
||||
|
||||
# Essential: Callback function for when an item is selected.
|
||||
#
|
||||
# This method must be overridden by subclasses.
|
||||
#
|
||||
# * `item` The selected model item. This will always be one of the items
|
||||
# previously passed to {::setItems}.
|
||||
#
|
||||
# Returns a DOM element, jQuery object, or {View}.
|
||||
confirmed: (item) ->
|
||||
throw new Error("Subclass must implement a confirmed(item) method")
|
||||
|
||||
###
|
||||
Section: Managing the list of items
|
||||
###
|
||||
|
||||
# Essential: Set the array of items to display in the list.
|
||||
#
|
||||
# This should be model items not actual views. {::viewForItem} will be
|
||||
# called to render the item when it is being appended to the list view.
|
||||
@@ -101,30 +131,26 @@ class SelectListView extends View
|
||||
@populateList()
|
||||
@setLoading()
|
||||
|
||||
# Public: Set the error message to display.
|
||||
# Essential: Get the model item that is currently selected in the list view.
|
||||
#
|
||||
# * `message` The {String} error message (default: '').
|
||||
setError: (message='') ->
|
||||
if message.length is 0
|
||||
@error.text('').hide()
|
||||
else
|
||||
@setLoading()
|
||||
@error.text(message).show()
|
||||
# Returns a model item.
|
||||
getSelectedItem: ->
|
||||
@getSelectedItemView().data('select-list-item')
|
||||
|
||||
# Public: Set the loading message to display.
|
||||
# Extended: Get the property name to use when filtering items.
|
||||
#
|
||||
# * `message` The {String} loading message (default: '').
|
||||
setLoading: (message='') ->
|
||||
if message.length is 0
|
||||
@loading.text("")
|
||||
@loadingBadge.text("")
|
||||
@loadingArea.hide()
|
||||
else
|
||||
@setError()
|
||||
@loading.text(message)
|
||||
@loadingArea.show()
|
||||
# This method may be overridden by classes to allow fuzzy filtering based
|
||||
# on a specific property of the item objects.
|
||||
#
|
||||
# For example if the objects you pass to {::setItems} are of the type
|
||||
# `{"id": 3, "name": "Atom"}` then you would return `"name"` from this method
|
||||
# to fuzzy filter by that property when text is entered into this view's
|
||||
# editor.
|
||||
#
|
||||
# Returns the property name to fuzzy filter by.
|
||||
getFilterKey: ->
|
||||
|
||||
# Public: Get the filter query to use when fuzzy filtering the visible
|
||||
# Extended: Get the filter query to use when fuzzy filtering the visible
|
||||
# elements.
|
||||
#
|
||||
# By default this method returns the text in the mini editor but it can be
|
||||
@@ -134,7 +160,12 @@ class SelectListView extends View
|
||||
getFilterQuery: ->
|
||||
@filterEditorView.getEditor().getText()
|
||||
|
||||
# Public: Populate the list view with the model items previously set by
|
||||
# Extended: Set the maximum numbers of items to display in the list.
|
||||
#
|
||||
# * `maxItems` The maximum {Number} of items to display.
|
||||
setMaxItems: (@maxItems) ->
|
||||
|
||||
# Extended: Populate the list view with the model items previously set by
|
||||
# calling {::setItems}.
|
||||
#
|
||||
# Subclasses may override this method but should always call `super`.
|
||||
@@ -161,7 +192,34 @@ class SelectListView extends View
|
||||
else
|
||||
@setError(@getEmptyMessage(@items.length, filteredItems.length))
|
||||
|
||||
# Public: Get the message to display when there are no items.
|
||||
###
|
||||
Section: Messages to the user
|
||||
###
|
||||
|
||||
# Essential: Set the error message to display.
|
||||
#
|
||||
# * `message` The {String} error message (default: '').
|
||||
setError: (message='') ->
|
||||
if message.length is 0
|
||||
@error.text('').hide()
|
||||
else
|
||||
@setLoading()
|
||||
@error.text(message).show()
|
||||
|
||||
# Essential: Set the loading message to display.
|
||||
#
|
||||
# * `message` The {String} loading message (default: '').
|
||||
setLoading: (message='') ->
|
||||
if message.length is 0
|
||||
@loading.text("")
|
||||
@loadingBadge.text("")
|
||||
@loadingArea.hide()
|
||||
else
|
||||
@setError()
|
||||
@loading.text(message)
|
||||
@loadingArea.show()
|
||||
|
||||
# Extended: Get the message to display when there are no items.
|
||||
#
|
||||
# Subclasses may override this method to customize the message.
|
||||
#
|
||||
@@ -171,10 +229,36 @@ class SelectListView extends View
|
||||
# Returns a {String} message (default: 'No matches found').
|
||||
getEmptyMessage: (itemCount, filteredItemCount) -> 'No matches found'
|
||||
|
||||
# Public: Set the maximum numbers of items to display in the list.
|
||||
###
|
||||
Section: View Actions
|
||||
###
|
||||
|
||||
# Essential: Cancel and close this select list view.
|
||||
#
|
||||
# * `maxItems` The maximum {Number} of items to display.
|
||||
setMaxItems: (@maxItems) ->
|
||||
# This restores focus to the previously focused element if
|
||||
# {::storeFocusedElement} was called prior to this view being attached.
|
||||
cancel: ->
|
||||
@list.empty()
|
||||
@cancelling = true
|
||||
filterEditorViewFocused = @filterEditorView.isFocused
|
||||
@cancelled()
|
||||
@detach()
|
||||
@restoreFocus() if filterEditorViewFocused
|
||||
@cancelling = false
|
||||
clearTimeout(@scheduleTimeout)
|
||||
|
||||
# Extended: Focus the fuzzy filter editor view.
|
||||
focusFilterEditor: ->
|
||||
@filterEditorView.focus()
|
||||
|
||||
# Extended: Store the currently focused element. This element will be given
|
||||
# back focus when {::cancel} is called.
|
||||
storeFocusedElement: ->
|
||||
@previouslyFocusedElement = $(':focus')
|
||||
|
||||
###
|
||||
Section: Private
|
||||
###
|
||||
|
||||
selectPreviousItemView: ->
|
||||
view = @getSelectedItemView().prev()
|
||||
@@ -202,68 +286,6 @@ class SelectListView extends View
|
||||
else if desiredBottom > @list.scrollBottom()
|
||||
@list.scrollBottom(desiredBottom)
|
||||
|
||||
getSelectedItemView: ->
|
||||
@list.find('li.selected')
|
||||
|
||||
# Public: Get the model item that is currently selected in the list view.
|
||||
#
|
||||
# Returns a model item.
|
||||
getSelectedItem: ->
|
||||
@getSelectedItemView().data('select-list-item')
|
||||
|
||||
confirmSelection: ->
|
||||
item = @getSelectedItem()
|
||||
if item?
|
||||
@confirmed(item)
|
||||
else
|
||||
@cancel()
|
||||
|
||||
# Public: Create a view for the given model item.
|
||||
#
|
||||
# This method must be overridden by subclasses.
|
||||
#
|
||||
# This is called when the item is about to appended to the list view.
|
||||
#
|
||||
# * `item` The model item being rendered. This will always be one of the items
|
||||
# previously passed to {::setItems}.
|
||||
#
|
||||
# Returns a String of HTML, DOM element, jQuery object, or View.
|
||||
viewForItem: (item) ->
|
||||
throw new Error("Subclass must implement a viewForItem(item) method")
|
||||
|
||||
# Public: Callback function for when an item is selected.
|
||||
#
|
||||
# This method must be overridden by subclasses.
|
||||
#
|
||||
# * `item` The selected model item. This will always be one of the items
|
||||
# previously passed to {::setItems}.
|
||||
#
|
||||
# Returns a DOM element, jQuery object, or {View}.
|
||||
confirmed: (item) ->
|
||||
throw new Error("Subclass must implement a confirmed(item) method")
|
||||
|
||||
# Public: Get the property name to use when filtering items.
|
||||
#
|
||||
# This method may be overridden by classes to allow fuzzy filtering based
|
||||
# on a specific property of the item objects.
|
||||
#
|
||||
# For example if the objects you pass to {::setItems} are of the type
|
||||
# `{"id": 3, "name": "Atom"}` then you would return `"name"` from this method
|
||||
# to fuzzy filter by that property when text is entered into this view's
|
||||
# editor.
|
||||
#
|
||||
# Returns the property name to fuzzy filter by.
|
||||
getFilterKey: ->
|
||||
|
||||
# Public: Focus the fuzzy filter editor view.
|
||||
focusFilterEditor: ->
|
||||
@filterEditorView.focus()
|
||||
|
||||
# Public: Store the currently focused element. This element will be given
|
||||
# back focus when {::cancel} is called.
|
||||
storeFocusedElement: ->
|
||||
@previouslyFocusedElement = $(':focus')
|
||||
|
||||
restoreFocus: ->
|
||||
if @previouslyFocusedElement?.isOnDom()
|
||||
@previouslyFocusedElement.focus()
|
||||
@@ -273,16 +295,18 @@ class SelectListView extends View
|
||||
cancelled: ->
|
||||
@filterEditorView.getEditor().setText('')
|
||||
|
||||
# Public: Cancel and close this select list view.
|
||||
#
|
||||
# This restores focus to the previously focused element if
|
||||
# {::storeFocusedElement} was called prior to this view being attached.
|
||||
cancel: ->
|
||||
@list.empty()
|
||||
@cancelling = true
|
||||
filterEditorViewFocused = @filterEditorView.isFocused
|
||||
@cancelled()
|
||||
@detach()
|
||||
@restoreFocus() if filterEditorViewFocused
|
||||
@cancelling = false
|
||||
getSelectedItemView: ->
|
||||
@list.find('li.selected')
|
||||
|
||||
confirmSelection: ->
|
||||
item = @getSelectedItem()
|
||||
if item?
|
||||
@confirmed(item)
|
||||
else
|
||||
@cancel()
|
||||
|
||||
schedulePopulateList: ->
|
||||
clearTimeout(@scheduleTimeout)
|
||||
populateCallback = =>
|
||||
@populateList() if @isOnDom()
|
||||
@scheduleTimeout = setTimeout(populateCallback, @inputThrottle)
|
||||
|
||||
@@ -30,6 +30,9 @@ class Selection extends Model
|
||||
@emitter.emit 'did-destroy'
|
||||
@emitter.dispose()
|
||||
|
||||
destroy: ->
|
||||
@marker.destroy()
|
||||
|
||||
###
|
||||
Section: Event Subscription
|
||||
###
|
||||
@@ -60,37 +63,11 @@ class Selection extends Model
|
||||
|
||||
super
|
||||
|
||||
|
||||
###
|
||||
Section: Methods
|
||||
Section: Managing the selection range
|
||||
###
|
||||
|
||||
destroy: ->
|
||||
@marker.destroy()
|
||||
|
||||
finalize: ->
|
||||
@initialScreenRange = null unless @initialScreenRange?.isEqual(@getScreenRange())
|
||||
if @isEmpty()
|
||||
@wordwise = false
|
||||
@linewise = false
|
||||
|
||||
clearAutoscroll: ->
|
||||
@needsAutoscroll = null
|
||||
|
||||
# Public: Determines if the selection contains anything.
|
||||
isEmpty: ->
|
||||
@getBufferRange().isEmpty()
|
||||
|
||||
# Public: Determines if the ending position of a marker is greater than the
|
||||
# starting position.
|
||||
#
|
||||
# This can happen when, for example, you highlight text "up" in a {TextBuffer}.
|
||||
isReversed: ->
|
||||
@marker.isReversed()
|
||||
|
||||
# Public: Returns whether the selection is a single line or not.
|
||||
isSingleScreenLine: ->
|
||||
@getScreenRange().isSingleLine()
|
||||
|
||||
# Public: Returns the screen {Range} for the selection.
|
||||
getScreenRange: ->
|
||||
@marker.getScreenRange()
|
||||
@@ -148,55 +125,61 @@ class Selection extends Model
|
||||
getHeadBufferPosition: ->
|
||||
@marker.getHeadBufferPosition()
|
||||
|
||||
autoscroll: ->
|
||||
@editor.scrollToScreenRange(@getScreenRange())
|
||||
###
|
||||
Section: Info about the selection
|
||||
###
|
||||
|
||||
# Public: Determines if the selection contains anything.
|
||||
isEmpty: ->
|
||||
@getBufferRange().isEmpty()
|
||||
|
||||
# Public: Determines if the ending position of a marker is greater than the
|
||||
# starting position.
|
||||
#
|
||||
# This can happen when, for example, you highlight text "up" in a {TextBuffer}.
|
||||
isReversed: ->
|
||||
@marker.isReversed()
|
||||
|
||||
# Public: Returns whether the selection is a single line or not.
|
||||
isSingleScreenLine: ->
|
||||
@getScreenRange().isSingleLine()
|
||||
|
||||
# Public: Returns the text in the selection.
|
||||
getText: ->
|
||||
@editor.buffer.getTextInRange(@getBufferRange())
|
||||
|
||||
# Public: Identifies if a selection intersects with a given buffer range.
|
||||
#
|
||||
# * `bufferRange` A {Range} to check against.
|
||||
#
|
||||
# Returns a {Boolean}
|
||||
intersectsBufferRange: (bufferRange) ->
|
||||
@getBufferRange().intersectsWith(bufferRange)
|
||||
|
||||
intersectsScreenRowRange: (startRow, endRow) ->
|
||||
@getScreenRange().intersectsRowRange(startRow, endRow)
|
||||
|
||||
intersectsScreenRow: (screenRow) ->
|
||||
@getScreenRange().intersectsRow(screenRow)
|
||||
|
||||
# Public: Identifies if a selection intersects with another selection.
|
||||
#
|
||||
# * `otherSelection` A {Selection} to check against.
|
||||
#
|
||||
# Returns a {Boolean}
|
||||
intersectsWith: (otherSelection, exclusive) ->
|
||||
@getBufferRange().intersectsWith(otherSelection.getBufferRange(), exclusive)
|
||||
|
||||
###
|
||||
Section: Modifying the selected range
|
||||
###
|
||||
|
||||
# Public: Clears the selection, moving the marker to the head.
|
||||
clear: ->
|
||||
@marker.setAttributes(goalBufferRange: null)
|
||||
@marker.clearTail() unless @retainSelection
|
||||
@finalize()
|
||||
|
||||
# Public: Modifies the selection to encompass the current word.
|
||||
#
|
||||
# Returns a {Range}.
|
||||
selectWord: ->
|
||||
options = {}
|
||||
options.wordRegex = /[\t ]*/ if @cursor.isSurroundedByWhitespace()
|
||||
if @cursor.isBetweenWordAndNonWord()
|
||||
options.includeNonWordCharacters = false
|
||||
|
||||
@setBufferRange(@cursor.getCurrentWordBufferRange(options))
|
||||
@wordwise = true
|
||||
@initialScreenRange = @getScreenRange()
|
||||
|
||||
# Public: Expands the newest selection to include the entire word on which
|
||||
# the cursors rests.
|
||||
expandOverWord: ->
|
||||
@setBufferRange(@getBufferRange().union(@cursor.getCurrentWordBufferRange()))
|
||||
|
||||
# Public: Selects an entire line in the buffer.
|
||||
#
|
||||
# * `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))
|
||||
@linewise = true
|
||||
@wordwise = false
|
||||
@initialScreenRange = @getScreenRange()
|
||||
|
||||
# Public: Expands the newest selection to include the entire line on which
|
||||
# the cursor currently rests.
|
||||
#
|
||||
# It also includes the newline character.
|
||||
expandOverLine: ->
|
||||
range = @getBufferRange().union(@cursor.getCurrentLineBufferRange(includeNewline: true))
|
||||
@setBufferRange(range)
|
||||
|
||||
# Public: Selects the text from the current cursor position to a given screen
|
||||
# position.
|
||||
#
|
||||
@@ -311,46 +294,45 @@ class Selection extends Model
|
||||
selectToBeginningOfPreviousParagraph: ->
|
||||
@modifySelection => @cursor.moveToBeginningOfPreviousParagraph()
|
||||
|
||||
# Public: Moves the selection down one row.
|
||||
addSelectionBelow: ->
|
||||
range = (@getGoalBufferRange() ? @getBufferRange()).copy()
|
||||
nextRow = range.end.row + 1
|
||||
# Public: Modifies the selection to encompass the current word.
|
||||
#
|
||||
# Returns a {Range}.
|
||||
selectWord: ->
|
||||
options = {}
|
||||
options.wordRegex = /[\t ]*/ if @cursor.isSurroundedByWhitespace()
|
||||
if @cursor.isBetweenWordAndNonWord()
|
||||
options.includeNonWordCharacters = false
|
||||
|
||||
for row in [nextRow..@editor.getLastBufferRow()]
|
||||
range.start.row = row
|
||||
range.end.row = row
|
||||
clippedRange = @editor.clipBufferRange(range)
|
||||
@setBufferRange(@cursor.getCurrentWordBufferRange(options))
|
||||
@wordwise = true
|
||||
@initialScreenRange = @getScreenRange()
|
||||
|
||||
if range.isEmpty()
|
||||
continue if range.end.column > 0 and clippedRange.end.column is 0
|
||||
else
|
||||
continue if clippedRange.isEmpty()
|
||||
# Public: Expands the newest selection to include the entire word on which
|
||||
# the cursors rests.
|
||||
expandOverWord: ->
|
||||
@setBufferRange(@getBufferRange().union(@cursor.getCurrentWordBufferRange()))
|
||||
|
||||
@editor.addSelectionForBufferRange(range, goalBufferRange: range)
|
||||
break
|
||||
# Public: Selects an entire line in the buffer.
|
||||
#
|
||||
# * `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))
|
||||
@linewise = true
|
||||
@wordwise = false
|
||||
@initialScreenRange = @getScreenRange()
|
||||
|
||||
# FIXME: I have no idea what this does.
|
||||
getGoalBufferRange: ->
|
||||
if goalBufferRange = @marker.getAttributes().goalBufferRange
|
||||
Range.fromObject(goalBufferRange)
|
||||
# Public: Expands the newest selection to include the entire line on which
|
||||
# the cursor currently rests.
|
||||
#
|
||||
# It also includes the newline character.
|
||||
expandOverLine: ->
|
||||
range = @getBufferRange().union(@cursor.getCurrentLineBufferRange(includeNewline: true))
|
||||
@setBufferRange(range)
|
||||
|
||||
# Public: Moves the selection up one row.
|
||||
addSelectionAbove: ->
|
||||
range = (@getGoalBufferRange() ? @getBufferRange()).copy()
|
||||
previousRow = range.end.row - 1
|
||||
|
||||
for row in [previousRow..0]
|
||||
range.start.row = row
|
||||
range.end.row = row
|
||||
clippedRange = @editor.clipBufferRange(range)
|
||||
|
||||
if range.isEmpty()
|
||||
continue if range.end.column > 0 and clippedRange.end.column is 0
|
||||
else
|
||||
continue if clippedRange.isEmpty()
|
||||
|
||||
@editor.addSelectionForBufferRange(range, goalBufferRange: range)
|
||||
break
|
||||
###
|
||||
Section: Modifying the selected text
|
||||
###
|
||||
|
||||
# Public: Replaces text at the current selection.
|
||||
#
|
||||
@@ -391,71 +373,6 @@ class Selection extends Model
|
||||
|
||||
newBufferRange
|
||||
|
||||
# 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.
|
||||
normalizeIndents: (text, indentBasis) ->
|
||||
textPrecedingCursor = @cursor.getCurrentBufferLine()[0...@cursor.getBufferColumn()]
|
||||
isCursorInsideExistingLine = /\S/.test(textPrecedingCursor)
|
||||
|
||||
lines = text.split('\n')
|
||||
firstLineIndentLevel = @editor.indentLevelForLine(lines[0])
|
||||
if isCursorInsideExistingLine
|
||||
minimumIndentLevel = @editor.indentationForBufferRow(@cursor.getBufferRow())
|
||||
else
|
||||
minimumIndentLevel = @cursor.getIndentLevel()
|
||||
|
||||
normalizedLines = []
|
||||
for line, i in lines
|
||||
if i == 0
|
||||
indentLevel = 0
|
||||
else if line == '' # remove all indentation from empty lines
|
||||
indentLevel = 0
|
||||
else
|
||||
lineIndentLevel = @editor.indentLevelForLine(lines[i])
|
||||
indentLevel = minimumIndentLevel + (lineIndentLevel - indentBasis)
|
||||
|
||||
normalizedLines.push(@setIndentationForLine(line, indentLevel))
|
||||
|
||||
normalizedLines.join('\n')
|
||||
|
||||
# Indent the current line(s).
|
||||
#
|
||||
# If the selection is empty, indents the current line if the cursor precedes
|
||||
# non-whitespace characters, and otherwise inserts a tab. If the selection is
|
||||
# non empty, calls {::indentSelectedRows}.
|
||||
#
|
||||
# * `options` (optional) {Object} with the keys:
|
||||
# * `autoIndent` If `true`, the line is indented to an automatically-inferred
|
||||
# level. Otherwise, {Editor::getTabText} is inserted.
|
||||
indent: ({ autoIndent }={}) ->
|
||||
{ row, column } = @cursor.getBufferPosition()
|
||||
|
||||
if @isEmpty()
|
||||
@cursor.skipLeadingWhitespace()
|
||||
desiredIndent = @editor.suggestedIndentForBufferRow(row)
|
||||
delta = desiredIndent - @cursor.getIndentLevel()
|
||||
|
||||
if autoIndent and delta > 0
|
||||
@insertText(@editor.buildIndentString(delta))
|
||||
else
|
||||
@insertText(@editor.buildIndentString(1, @cursor.getBufferColumn()))
|
||||
else
|
||||
@indentSelectedRows()
|
||||
|
||||
# Public: If the selection spans multiple rows, indent all of them.
|
||||
indentSelectedRows: ->
|
||||
[start, end] = @getBufferRowRange()
|
||||
for row in [start..end]
|
||||
@editor.buffer.insert([row, 0], @editor.getTabText()) unless @editor.buffer.lineLengthForRow(row) == 0
|
||||
|
||||
# Public: ?
|
||||
setIndentationForLine: (line, indentLevel) ->
|
||||
desiredIndentLevel = Math.max(0, indentLevel)
|
||||
desiredIndentString = @editor.buildIndentString(desiredIndentLevel)
|
||||
line.replace(/^[\t ]*/, desiredIndentString)
|
||||
|
||||
# Public: Removes the first character before the selection if the selection
|
||||
# is empty otherwise it deletes the selection.
|
||||
backspace: ->
|
||||
@@ -632,41 +549,109 @@ class Selection extends Model
|
||||
@editor.createFold(range.start.row, range.end.row)
|
||||
@cursor.setBufferPosition([range.end.row + 1, 0])
|
||||
|
||||
modifySelection: (fn) ->
|
||||
@retainSelection = true
|
||||
@plantTail()
|
||||
fn()
|
||||
@retainSelection = false
|
||||
# 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.
|
||||
normalizeIndents: (text, indentBasis) ->
|
||||
textPrecedingCursor = @cursor.getCurrentBufferLine()[0...@cursor.getBufferColumn()]
|
||||
isCursorInsideExistingLine = /\S/.test(textPrecedingCursor)
|
||||
|
||||
# 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.
|
||||
#
|
||||
# Returns a {Point} representing the new tail position.
|
||||
plantTail: ->
|
||||
@marker.plantTail()
|
||||
lines = text.split('\n')
|
||||
firstLineIndentLevel = @editor.indentLevelForLine(lines[0])
|
||||
if isCursorInsideExistingLine
|
||||
minimumIndentLevel = @editor.indentationForBufferRow(@cursor.getBufferRow())
|
||||
else
|
||||
minimumIndentLevel = @cursor.getIndentLevel()
|
||||
|
||||
# Public: Identifies if a selection intersects with a given buffer range.
|
||||
#
|
||||
# * `bufferRange` A {Range} to check against.
|
||||
#
|
||||
# Returns a {Boolean}
|
||||
intersectsBufferRange: (bufferRange) ->
|
||||
@getBufferRange().intersectsWith(bufferRange)
|
||||
normalizedLines = []
|
||||
for line, i in lines
|
||||
if i == 0
|
||||
indentLevel = 0
|
||||
else if line == '' # remove all indentation from empty lines
|
||||
indentLevel = 0
|
||||
else
|
||||
lineIndentLevel = @editor.indentLevelForLine(lines[i])
|
||||
indentLevel = minimumIndentLevel + (lineIndentLevel - indentBasis)
|
||||
|
||||
intersectsScreenRowRange: (startRow, endRow) ->
|
||||
@getScreenRange().intersectsRowRange(startRow, endRow)
|
||||
normalizedLines.push(@setIndentationForLine(line, indentLevel))
|
||||
|
||||
intersectsScreenRow: (screenRow) ->
|
||||
@getScreenRange().intersectsRow(screenRow)
|
||||
normalizedLines.join('\n')
|
||||
|
||||
# Public: Identifies if a selection intersects with another selection.
|
||||
# Indent the current line(s).
|
||||
#
|
||||
# * `otherSelection` A {Selection} to check against.
|
||||
# If the selection is empty, indents the current line if the cursor precedes
|
||||
# non-whitespace characters, and otherwise inserts a tab. If the selection is
|
||||
# non empty, calls {::indentSelectedRows}.
|
||||
#
|
||||
# Returns a {Boolean}
|
||||
intersectsWith: (otherSelection, exclusive) ->
|
||||
@getBufferRange().intersectsWith(otherSelection.getBufferRange(), exclusive)
|
||||
# * `options` (optional) {Object} with the keys:
|
||||
# * `autoIndent` If `true`, the line is indented to an automatically-inferred
|
||||
# level. Otherwise, {Editor::getTabText} is inserted.
|
||||
indent: ({ autoIndent }={}) ->
|
||||
{ row, column } = @cursor.getBufferPosition()
|
||||
|
||||
if @isEmpty()
|
||||
@cursor.skipLeadingWhitespace()
|
||||
desiredIndent = @editor.suggestedIndentForBufferRow(row)
|
||||
delta = desiredIndent - @cursor.getIndentLevel()
|
||||
|
||||
if autoIndent and delta > 0
|
||||
@insertText(@editor.buildIndentString(delta))
|
||||
else
|
||||
@insertText(@editor.buildIndentString(1, @cursor.getBufferColumn()))
|
||||
else
|
||||
@indentSelectedRows()
|
||||
|
||||
# Public: If the selection spans multiple rows, indent all of them.
|
||||
indentSelectedRows: ->
|
||||
[start, end] = @getBufferRowRange()
|
||||
for row in [start..end]
|
||||
@editor.buffer.insert([row, 0], @editor.getTabText()) unless @editor.buffer.lineLengthForRow(row) == 0
|
||||
|
||||
setIndentationForLine: (line, indentLevel) ->
|
||||
desiredIndentLevel = Math.max(0, indentLevel)
|
||||
desiredIndentString = @editor.buildIndentString(desiredIndentLevel)
|
||||
line.replace(/^[\t ]*/, desiredIndentString)
|
||||
|
||||
###
|
||||
Section: Managing multiple selections
|
||||
###
|
||||
|
||||
# Public: Moves the selection down one row.
|
||||
addSelectionBelow: ->
|
||||
range = (@getGoalBufferRange() ? @getBufferRange()).copy()
|
||||
nextRow = range.end.row + 1
|
||||
|
||||
for row in [nextRow..@editor.getLastBufferRow()]
|
||||
range.start.row = row
|
||||
range.end.row = row
|
||||
clippedRange = @editor.clipBufferRange(range)
|
||||
|
||||
if range.isEmpty()
|
||||
continue if range.end.column > 0 and clippedRange.end.column is 0
|
||||
else
|
||||
continue if clippedRange.isEmpty()
|
||||
|
||||
@editor.addSelectionForBufferRange(range, goalBufferRange: range)
|
||||
break
|
||||
|
||||
# Public: Moves the selection up one row.
|
||||
addSelectionAbove: ->
|
||||
range = (@getGoalBufferRange() ? @getBufferRange()).copy()
|
||||
previousRow = range.end.row - 1
|
||||
|
||||
for row in [previousRow..0]
|
||||
range.start.row = row
|
||||
range.end.row = row
|
||||
clippedRange = @editor.clipBufferRange(range)
|
||||
|
||||
if range.isEmpty()
|
||||
continue if range.end.column > 0 and clippedRange.end.column is 0
|
||||
else
|
||||
continue if clippedRange.isEmpty()
|
||||
|
||||
@editor.addSelectionForBufferRange(range, goalBufferRange: range)
|
||||
break
|
||||
|
||||
# Public: Combines the given selection into this selection and then destroys
|
||||
# the given selection.
|
||||
@@ -683,6 +668,10 @@ class Selection extends Model
|
||||
@setBufferRange(@getBufferRange().union(otherSelection.getBufferRange()), options)
|
||||
otherSelection.destroy()
|
||||
|
||||
###
|
||||
Section: Comparing to other selections
|
||||
###
|
||||
|
||||
# Public: Compare this selection's buffer range to another selection's buffer
|
||||
# range.
|
||||
#
|
||||
@@ -692,7 +681,41 @@ class Selection extends Model
|
||||
compare: (otherSelection) ->
|
||||
@getBufferRange().compare(otherSelection.getBufferRange())
|
||||
|
||||
###
|
||||
Section: Private Utilities
|
||||
###
|
||||
|
||||
screenRangeChanged: ->
|
||||
@emit 'screen-range-changed', @getScreenRange()
|
||||
@emitter.emit 'did-change-range'
|
||||
@editor.selectionRangeChanged(this)
|
||||
|
||||
finalize: ->
|
||||
@initialScreenRange = null unless @initialScreenRange?.isEqual(@getScreenRange())
|
||||
if @isEmpty()
|
||||
@wordwise = false
|
||||
@linewise = false
|
||||
|
||||
autoscroll: ->
|
||||
@editor.scrollToScreenRange(@getScreenRange())
|
||||
|
||||
clearAutoscroll: ->
|
||||
@needsAutoscroll = null
|
||||
|
||||
modifySelection: (fn) ->
|
||||
@retainSelection = true
|
||||
@plantTail()
|
||||
fn()
|
||||
@retainSelection = false
|
||||
|
||||
# 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.
|
||||
#
|
||||
# Returns a {Point} representing the new tail position.
|
||||
plantTail: ->
|
||||
@marker.plantTail()
|
||||
|
||||
getGoalBufferRange: ->
|
||||
if goalBufferRange = @marker.getAttributes().goalBufferRange
|
||||
Range.fromObject(goalBufferRange)
|
||||
|
||||
@@ -9,7 +9,7 @@ PropertyAccessors = require 'property-accessors'
|
||||
{$, $$} = require './space-pen-extensions'
|
||||
Token = require './token'
|
||||
|
||||
# Public: Syntax class holding the grammars used for tokenizing.
|
||||
# Extended: Syntax class holding the grammars used for tokenizing.
|
||||
#
|
||||
# An instance of this class is always available as the `atom.syntax` global.
|
||||
#
|
||||
|
||||
@@ -2,14 +2,38 @@ _ = require 'underscore-plus'
|
||||
child_process = require 'child_process'
|
||||
{Emitter} = require 'emissary'
|
||||
|
||||
# Public: Run a node script in a separate process.
|
||||
# Extended: Run a node script in a separate process.
|
||||
#
|
||||
# Used by the fuzzy-finder.
|
||||
# Used by the fuzzy-finder and [find in project](https://github.com/atom/atom/blob/master/src/scan-handler.coffee).
|
||||
#
|
||||
# For a real-world example, see the [scan-handler](https://github.com/atom/atom/blob/master/src/scan-handler.coffee)
|
||||
# and the [instantiation of the task](https://github.com/atom/atom/blob/4a20f13162f65afc816b512ad7201e528c3443d7/src/project.coffee#L245).
|
||||
#
|
||||
# ## Examples
|
||||
#
|
||||
# In your package code:
|
||||
#
|
||||
# ```coffee
|
||||
# {Task} = require 'atom'
|
||||
#
|
||||
# task = Task.once '/path/to/task-file.coffee', parameter1, parameter2, ->
|
||||
# console.log 'task has finished'
|
||||
#
|
||||
# task.on 'some-event-from-the-task', (data) =>
|
||||
# console.log data.someString # prints 'yep this is it'
|
||||
# ```
|
||||
#
|
||||
# In `'/path/to/task-file.coffee'`:
|
||||
#
|
||||
# ```coffee
|
||||
# module.exports = (parameter1, parameter2) ->
|
||||
# # Indicates that this task will be async.
|
||||
# # Call the `callback` to finish the task
|
||||
# callback = @async()
|
||||
#
|
||||
# emit('some-event-from-the-task', {someString: 'yep this is it'})
|
||||
#
|
||||
# callback()
|
||||
# ```
|
||||
#
|
||||
# ## Events
|
||||
@@ -55,7 +79,7 @@ class Task
|
||||
# receives a completion callback, this is overridden.
|
||||
callback: null
|
||||
|
||||
# Public: Creates a task.
|
||||
# Public: Creates a task. You should probably use {.once}
|
||||
#
|
||||
# * `taskPath` The {String} path to the CoffeeScript/JavaScript file that
|
||||
# exports a single {Function} to execute.
|
||||
|
||||
@@ -85,17 +85,29 @@ class ThemeManager
|
||||
EmitterMixin::on.apply(this, arguments)
|
||||
|
||||
###
|
||||
Section: Instance Methods
|
||||
Section: Accessing Available Themes
|
||||
###
|
||||
|
||||
getAvailableNames: ->
|
||||
# TODO: Maybe should change to list all the available themes out there?
|
||||
@getLoadedNames()
|
||||
|
||||
###
|
||||
Section: Accessing Loaded Themes
|
||||
###
|
||||
|
||||
# Public: Get an array of all the loaded theme names.
|
||||
getLoadedNames: ->
|
||||
theme.name for theme in @getLoadedThemes()
|
||||
|
||||
# Public: Get an array of all the loaded themes.
|
||||
getLoadedThemes: ->
|
||||
pack for pack in @packageManager.getLoadedPackages() when pack.isTheme()
|
||||
|
||||
###
|
||||
Section: Accessing Active Themes
|
||||
###
|
||||
|
||||
# Public: Get an array of all the active theme names.
|
||||
getActiveNames: ->
|
||||
theme.name for theme in @getActiveThemes()
|
||||
@@ -104,13 +116,13 @@ class ThemeManager
|
||||
getActiveThemes: ->
|
||||
pack for pack in @packageManager.getActivePackages() when pack.isTheme()
|
||||
|
||||
# Public: Get an array of all the loaded themes.
|
||||
getLoadedThemes: ->
|
||||
pack for pack in @packageManager.getLoadedPackages() when pack.isTheme()
|
||||
|
||||
activatePackages: -> @activateThemes()
|
||||
|
||||
# Get the enabled theme names from the config.
|
||||
###
|
||||
Section: Managing Enabled Themes
|
||||
###
|
||||
|
||||
# Public: Get the enabled theme names from the config.
|
||||
#
|
||||
# Returns an array of theme names in the order that they should be activated.
|
||||
getEnabledThemeNames: ->
|
||||
@@ -145,6 +157,147 @@ class ThemeManager
|
||||
# the first/top theme to override later themes in the stack.
|
||||
themeNames.reverse()
|
||||
|
||||
# Public: Set the list of enabled themes.
|
||||
#
|
||||
# * `enabledThemeNames` An {Array} of {String} theme names.
|
||||
setEnabledThemes: (enabledThemeNames) ->
|
||||
atom.config.set('core.themes', enabledThemeNames)
|
||||
|
||||
###
|
||||
Section: Managing Stylesheets
|
||||
###
|
||||
|
||||
# Public: Returns the {String} path to the user's stylesheet under ~/.atom
|
||||
getUserStylesheetPath: ->
|
||||
stylesheetPath = fs.resolve(path.join(@configDirPath, 'styles'), ['css', 'less'])
|
||||
if fs.isFileSync(stylesheetPath)
|
||||
stylesheetPath
|
||||
else
|
||||
path.join(@configDirPath, 'styles.less')
|
||||
|
||||
# Public: Resolve and apply the stylesheet specified by the path.
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
# Returns the absolute path to the required stylesheet.
|
||||
requireStylesheet: (stylesheetPath, type='bundled') ->
|
||||
if fullPath = @resolveStylesheet(stylesheetPath)
|
||||
content = @loadStylesheet(fullPath)
|
||||
@applyStylesheet(fullPath, content, type)
|
||||
else
|
||||
throw new Error("Could not find a file at path '#{stylesheetPath}'")
|
||||
|
||||
fullPath
|
||||
|
||||
unwatchUserStylesheet: ->
|
||||
@userStylesheetFile?.off()
|
||||
@userStylesheetFile = null
|
||||
@removeStylesheet(@userStylesheetPath) if @userStylesheetPath?
|
||||
|
||||
loadUserStylesheet: ->
|
||||
@unwatchUserStylesheet()
|
||||
userStylesheetPath = @getUserStylesheetPath()
|
||||
return unless fs.isFileSync(userStylesheetPath)
|
||||
|
||||
@userStylesheetPath = userStylesheetPath
|
||||
@userStylesheetFile = new File(userStylesheetPath)
|
||||
@userStylesheetFile.on 'contents-changed moved removed', =>
|
||||
@loadUserStylesheet()
|
||||
userStylesheetContents = @loadStylesheet(userStylesheetPath, true)
|
||||
@applyStylesheet(userStylesheetPath, userStylesheetContents, 'userTheme')
|
||||
|
||||
loadBaseStylesheets: ->
|
||||
@requireStylesheet('bootstrap/less/bootstrap')
|
||||
@reloadBaseStylesheets()
|
||||
|
||||
reloadBaseStylesheets: ->
|
||||
@requireStylesheet('../static/atom')
|
||||
if nativeStylesheetPath = fs.resolveOnLoadPath(process.platform, ['css', 'less'])
|
||||
@requireStylesheet(nativeStylesheetPath)
|
||||
|
||||
stylesheetElementForId: (id) ->
|
||||
document.head.querySelector("""style[id="#{id}"]""")
|
||||
|
||||
resolveStylesheet: (stylesheetPath) ->
|
||||
if path.extname(stylesheetPath).length > 0
|
||||
fs.resolveOnLoadPath(stylesheetPath)
|
||||
else
|
||||
fs.resolveOnLoadPath(stylesheetPath, ['css', 'less'])
|
||||
|
||||
loadStylesheet: (stylesheetPath, importFallbackVariables) ->
|
||||
if path.extname(stylesheetPath) is '.less'
|
||||
@loadLessStylesheet(stylesheetPath, importFallbackVariables)
|
||||
else
|
||||
fs.readFileSync(stylesheetPath, 'utf8')
|
||||
|
||||
loadLessStylesheet: (lessStylesheetPath, importFallbackVariables=false) ->
|
||||
unless @lessCache?
|
||||
LessCompileCache = require './less-compile-cache'
|
||||
@lessCache = new LessCompileCache({@resourcePath, importPaths: @getImportPaths()})
|
||||
|
||||
try
|
||||
if importFallbackVariables
|
||||
baseVarImports = """
|
||||
@import "variables/ui-variables";
|
||||
@import "variables/syntax-variables";
|
||||
"""
|
||||
less = fs.readFileSync(lessStylesheetPath, 'utf8')
|
||||
@lessCache.cssForFile(lessStylesheetPath, [baseVarImports, less].join('\n'))
|
||||
else
|
||||
@lessCache.read(lessStylesheetPath)
|
||||
catch error
|
||||
console.error """
|
||||
Error compiling Less stylesheet: #{lessStylesheetPath}
|
||||
Line number: #{error.line}
|
||||
#{error.message}
|
||||
"""
|
||||
|
||||
removeStylesheet: (stylesheetPath) ->
|
||||
fullPath = @resolveStylesheet(stylesheetPath) ? stylesheetPath
|
||||
element = @stylesheetElementForId(@stringToId(fullPath))
|
||||
if element?
|
||||
{sheet} = element
|
||||
element.remove()
|
||||
@emit 'stylesheet-removed', sheet
|
||||
@emitter.emit 'did-remove-stylesheet', sheet
|
||||
@emit 'stylesheets-changed'
|
||||
@emitter.emit 'did-change-stylesheets'
|
||||
|
||||
applyStylesheet: (path, text, type='bundled') ->
|
||||
styleId = @stringToId(path)
|
||||
styleElement = @stylesheetElementForId(styleId)
|
||||
|
||||
if styleElement?
|
||||
@emit 'stylesheet-removed', styleElement.sheet
|
||||
@emitter.emit 'did-remove-stylesheet', styleElement.sheet
|
||||
styleElement.textContent = text
|
||||
else
|
||||
styleElement = document.createElement('style')
|
||||
styleElement.setAttribute('class', type)
|
||||
styleElement.setAttribute('id', styleId)
|
||||
styleElement.textContent = text
|
||||
|
||||
elementToInsertBefore = _.last(document.head.querySelectorAll("style.#{type}"))?.nextElementSibling
|
||||
if elementToInsertBefore?
|
||||
document.head.insertBefore(styleElement, elementToInsertBefore)
|
||||
else
|
||||
document.head.appendChild(styleElement)
|
||||
|
||||
@emit 'stylesheet-added', styleElement.sheet
|
||||
@emitter.emit 'did-add-stylesheet', styleElement.sheet
|
||||
@emit 'stylesheets-changed'
|
||||
@emitter.emit 'did-change-stylesheets'
|
||||
|
||||
###
|
||||
Section: Private
|
||||
###
|
||||
|
||||
stringToId: (string) ->
|
||||
string.replace(/\\/g, '/')
|
||||
|
||||
activateThemes: ->
|
||||
deferred = Q.defer()
|
||||
|
||||
@@ -194,12 +347,6 @@ class ThemeManager
|
||||
refreshLessCache: ->
|
||||
@lessCache?.setImportPaths(@getImportPaths())
|
||||
|
||||
# Public: Set the list of enabled themes.
|
||||
#
|
||||
# * `enabledThemeNames` An {Array} of {String} theme names.
|
||||
setEnabledThemes: (enabledThemeNames) ->
|
||||
atom.config.set('core.themes', enabledThemeNames)
|
||||
|
||||
getImportPaths: ->
|
||||
activeThemes = @getActiveThemes()
|
||||
if activeThemes.length > 0
|
||||
@@ -212,133 +359,6 @@ class ThemeManager
|
||||
|
||||
themePaths.filter (themePath) -> fs.isDirectorySync(themePath)
|
||||
|
||||
# Public: Returns the {String} path to the user's stylesheet under ~/.atom
|
||||
getUserStylesheetPath: ->
|
||||
stylesheetPath = fs.resolve(path.join(@configDirPath, 'styles'), ['css', 'less'])
|
||||
if fs.isFileSync(stylesheetPath)
|
||||
stylesheetPath
|
||||
else
|
||||
path.join(@configDirPath, 'styles.less')
|
||||
|
||||
unwatchUserStylesheet: ->
|
||||
@userStylesheetFile?.off()
|
||||
@userStylesheetFile = null
|
||||
@removeStylesheet(@userStylesheetPath) if @userStylesheetPath?
|
||||
|
||||
loadUserStylesheet: ->
|
||||
@unwatchUserStylesheet()
|
||||
userStylesheetPath = @getUserStylesheetPath()
|
||||
return unless fs.isFileSync(userStylesheetPath)
|
||||
|
||||
@userStylesheetPath = userStylesheetPath
|
||||
@userStylesheetFile = new File(userStylesheetPath)
|
||||
@userStylesheetFile.on 'contents-changed moved removed', =>
|
||||
@loadUserStylesheet()
|
||||
userStylesheetContents = @loadStylesheet(userStylesheetPath, true)
|
||||
@applyStylesheet(userStylesheetPath, userStylesheetContents, 'userTheme')
|
||||
|
||||
loadBaseStylesheets: ->
|
||||
@requireStylesheet('bootstrap/less/bootstrap')
|
||||
@reloadBaseStylesheets()
|
||||
|
||||
reloadBaseStylesheets: ->
|
||||
@requireStylesheet('../static/atom')
|
||||
if nativeStylesheetPath = fs.resolveOnLoadPath(process.platform, ['css', 'less'])
|
||||
@requireStylesheet(nativeStylesheetPath)
|
||||
|
||||
stylesheetElementForId: (id) ->
|
||||
document.head.querySelector("""style[id="#{id}"]""")
|
||||
|
||||
resolveStylesheet: (stylesheetPath) ->
|
||||
if path.extname(stylesheetPath).length > 0
|
||||
fs.resolveOnLoadPath(stylesheetPath)
|
||||
else
|
||||
fs.resolveOnLoadPath(stylesheetPath, ['css', 'less'])
|
||||
|
||||
# Public: Resolve and apply the stylesheet specified by the path.
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
# Returns the absolute path to the required stylesheet.
|
||||
requireStylesheet: (stylesheetPath, type='bundled') ->
|
||||
if fullPath = @resolveStylesheet(stylesheetPath)
|
||||
content = @loadStylesheet(fullPath)
|
||||
@applyStylesheet(fullPath, content, type)
|
||||
else
|
||||
throw new Error("Could not find a file at path '#{stylesheetPath}'")
|
||||
|
||||
fullPath
|
||||
|
||||
loadStylesheet: (stylesheetPath, importFallbackVariables) ->
|
||||
if path.extname(stylesheetPath) is '.less'
|
||||
@loadLessStylesheet(stylesheetPath, importFallbackVariables)
|
||||
else
|
||||
fs.readFileSync(stylesheetPath, 'utf8')
|
||||
|
||||
loadLessStylesheet: (lessStylesheetPath, importFallbackVariables=false) ->
|
||||
unless @lessCache?
|
||||
LessCompileCache = require './less-compile-cache'
|
||||
@lessCache = new LessCompileCache({@resourcePath, importPaths: @getImportPaths()})
|
||||
|
||||
try
|
||||
if importFallbackVariables
|
||||
baseVarImports = """
|
||||
@import "variables/ui-variables";
|
||||
@import "variables/syntax-variables";
|
||||
"""
|
||||
less = fs.readFileSync(lessStylesheetPath, 'utf8')
|
||||
@lessCache.cssForFile(lessStylesheetPath, [baseVarImports, less].join('\n'))
|
||||
else
|
||||
@lessCache.read(lessStylesheetPath)
|
||||
catch error
|
||||
console.error """
|
||||
Error compiling Less stylesheet: #{lessStylesheetPath}
|
||||
Line number: #{error.line}
|
||||
#{error.message}
|
||||
"""
|
||||
|
||||
stringToId: (string) ->
|
||||
string.replace(/\\/g, '/')
|
||||
|
||||
removeStylesheet: (stylesheetPath) ->
|
||||
fullPath = @resolveStylesheet(stylesheetPath) ? stylesheetPath
|
||||
element = @stylesheetElementForId(@stringToId(fullPath))
|
||||
if element?
|
||||
{sheet} = element
|
||||
element.remove()
|
||||
@emit 'stylesheet-removed', sheet
|
||||
@emitter.emit 'did-remove-stylesheet', sheet
|
||||
@emit 'stylesheets-changed'
|
||||
@emitter.emit 'did-change-stylesheets'
|
||||
|
||||
applyStylesheet: (path, text, type='bundled') ->
|
||||
styleId = @stringToId(path)
|
||||
styleElement = @stylesheetElementForId(styleId)
|
||||
|
||||
if styleElement?
|
||||
@emit 'stylesheet-removed', styleElement.sheet
|
||||
@emitter.emit 'did-remove-stylesheet', styleElement.sheet
|
||||
styleElement.textContent = text
|
||||
else
|
||||
styleElement = document.createElement('style')
|
||||
styleElement.setAttribute('class', type)
|
||||
styleElement.setAttribute('id', styleId)
|
||||
styleElement.textContent = text
|
||||
|
||||
elementToInsertBefore = _.last(document.head.querySelectorAll("style.#{type}"))?.nextElementSibling
|
||||
if elementToInsertBefore?
|
||||
document.head.insertBefore(styleElement, elementToInsertBefore)
|
||||
else
|
||||
document.head.appendChild(styleElement)
|
||||
|
||||
@emit 'stylesheet-added', styleElement.sheet
|
||||
@emitter.emit 'did-add-stylesheet', styleElement.sheet
|
||||
@emit 'stylesheets-changed'
|
||||
@emitter.emit 'did-change-stylesheets'
|
||||
|
||||
updateGlobalEditorStyle: (property, value) ->
|
||||
unless styleNode = @stylesheetElementForId('global-editor-styles')
|
||||
@applyStylesheet('global-editor-styles', '.editor {}')
|
||||
|
||||
@@ -167,12 +167,173 @@ class WorkspaceView extends View
|
||||
@command 'core:save', => @saveActivePaneItem()
|
||||
@command 'core:save-as', => @saveActivePaneItemAs()
|
||||
|
||||
# Public: Get the underlying model object.
|
||||
###
|
||||
Section: Accessing the Workspace Model
|
||||
###
|
||||
|
||||
# Essential: Get the underlying model object.
|
||||
#
|
||||
# Returns a {Workspace}.
|
||||
getModel: -> @model
|
||||
|
||||
# Public: Install the Atom shell commands on the user's system.
|
||||
###
|
||||
Section: Accessing Views
|
||||
###
|
||||
|
||||
# Essential: Register a function to be called for every current and future
|
||||
# editor view in the workspace (only includes {EditorView}s that are pane
|
||||
# items).
|
||||
#
|
||||
# * `callback` A {Function} with an {EditorView} as its only argument.
|
||||
# * `editorView` {EditorView}
|
||||
#
|
||||
# Returns a subscription object with an `.off` method that you can call to
|
||||
# unregister the callback.
|
||||
eachEditorView: (callback) ->
|
||||
callback(editorView) for editorView in @getEditorViews()
|
||||
attachedCallback = (e, editorView) ->
|
||||
callback(editorView) unless editorView.mini
|
||||
@on('editor:attached', attachedCallback)
|
||||
off: => @off('editor:attached', attachedCallback)
|
||||
|
||||
# Essential: Register a function to be called for every current and future
|
||||
# pane view in the workspace.
|
||||
#
|
||||
# * `callback` A {Function} with a {PaneView} as its only argument.
|
||||
# * `paneView` {PaneView}
|
||||
#
|
||||
# Returns a subscription object with an `.off` method that you can call to
|
||||
# unregister the callback.
|
||||
eachPaneView: (callback) ->
|
||||
@panes.eachPaneView(callback)
|
||||
|
||||
# Essential: Get all existing pane views.
|
||||
#
|
||||
# Prefer {Workspace::getPanes} if you don't need access to the view objects.
|
||||
# Also consider using {::eachPaneView} if you want to register a callback for
|
||||
# all current and *future* pane views.
|
||||
#
|
||||
# Returns an Array of all open {PaneView}s.
|
||||
getPaneViews: ->
|
||||
@panes.getPaneViews()
|
||||
|
||||
# Essential: Get the active pane view.
|
||||
#
|
||||
# Prefer {Workspace::getActivePane} if you don't actually need access to the
|
||||
# view.
|
||||
#
|
||||
# Returns a {PaneView}.
|
||||
getActivePaneView: ->
|
||||
@panes.getActivePaneView()
|
||||
|
||||
# Essential: Get the view associated with the active pane item.
|
||||
#
|
||||
# Returns a view.
|
||||
getActiveView: ->
|
||||
@panes.getActiveView()
|
||||
|
||||
###
|
||||
Section: Adding elements to the workspace
|
||||
###
|
||||
|
||||
# Essential: Prepend an element or view to the panels at the top of the
|
||||
# workspace.
|
||||
#
|
||||
# * `element` jQuery object or DOM element
|
||||
prependToTop: (element) ->
|
||||
@vertical.prepend(element)
|
||||
|
||||
# Essential: Append an element or view to the panels at the top of the workspace.
|
||||
#
|
||||
# * `element` jQuery object or DOM element
|
||||
appendToTop: (element) ->
|
||||
@panes.before(element)
|
||||
|
||||
# Essential: Prepend an element or view to the panels at the bottom of the
|
||||
# workspace.
|
||||
#
|
||||
# * `element` jQuery object or DOM element
|
||||
prependToBottom: (element) ->
|
||||
@panes.after(element)
|
||||
|
||||
# Essential: Append an element or view to the panels at the bottom of the
|
||||
# workspace.
|
||||
#
|
||||
# * `element` jQuery object or DOM element
|
||||
appendToBottom: (element) ->
|
||||
@vertical.append(element)
|
||||
|
||||
# Essential: Prepend an element or view to the panels at the left of the
|
||||
# workspace.
|
||||
#
|
||||
# * `element` jQuery object or DOM element
|
||||
prependToLeft: (element) ->
|
||||
@horizontal.prepend(element)
|
||||
|
||||
# Essential: Append an element or view to the panels at the left of the
|
||||
# workspace.
|
||||
#
|
||||
# * `element` jQuery object or DOM element
|
||||
appendToLeft: (element) ->
|
||||
@vertical.before(element)
|
||||
|
||||
# Essential: Prepend an element or view to the panels at the right of the
|
||||
# workspace.
|
||||
#
|
||||
# * `element` jQuery object or DOM element
|
||||
prependToRight: (element) ->
|
||||
@vertical.after(element)
|
||||
|
||||
# Essential: Append an element or view to the panels at the right of the
|
||||
# workspace.
|
||||
#
|
||||
# * `element` jQuery object or DOM element
|
||||
appendToRight: (element) ->
|
||||
@horizontal.append(element)
|
||||
|
||||
###
|
||||
Section: Focusing pane views
|
||||
###
|
||||
|
||||
# Focus the previous pane by id.
|
||||
focusPreviousPaneView: -> @model.activatePreviousPane()
|
||||
|
||||
# Focus the next pane by id.
|
||||
focusNextPaneView: -> @model.activateNextPane()
|
||||
|
||||
# Essential: Focus the pane directly above the active pane.
|
||||
focusPaneViewAbove: -> @panes.focusPaneViewAbove()
|
||||
|
||||
# Essential: Focus the pane directly below the active pane.
|
||||
focusPaneViewBelow: -> @panes.focusPaneViewBelow()
|
||||
|
||||
# Essential: Focus the pane directly to the left of the active pane.
|
||||
focusPaneViewOnLeft: -> @panes.focusPaneViewOnLeft()
|
||||
|
||||
# Essential: Focus the pane directly to the right of the active pane.
|
||||
focusPaneViewOnRight: -> @panes.focusPaneViewOnRight()
|
||||
|
||||
###
|
||||
Section: Private
|
||||
###
|
||||
|
||||
afterAttach: (onDom) ->
|
||||
@focus() if onDom
|
||||
|
||||
# Called by SpacePen
|
||||
beforeRemove: ->
|
||||
@model.destroy()
|
||||
|
||||
setEditorFontSize: (fontSize) ->
|
||||
atom.themes.updateGlobalEditorStyle('font-size', fontSize + 'px')
|
||||
|
||||
setEditorFontFamily: (fontFamily) ->
|
||||
atom.themes.updateGlobalEditorStyle('font-family', fontFamily)
|
||||
|
||||
setEditorLineHeight: (lineHeight) ->
|
||||
atom.themes.updateGlobalEditorStyle('line-height', lineHeight)
|
||||
|
||||
# Install the Atom shell commands on the user's system.
|
||||
installShellCommands: ->
|
||||
showErrorDialog = (error) ->
|
||||
installDirectory = CommandInstaller.getInstallDirectory()
|
||||
@@ -207,9 +368,6 @@ class WorkspaceView extends View
|
||||
$(document.body).focus()
|
||||
true
|
||||
|
||||
afterAttach: (onDom) ->
|
||||
@focus() if onDom
|
||||
|
||||
# Prompts to save all unsaved items
|
||||
confirmClose: ->
|
||||
@panes.confirmClose()
|
||||
@@ -248,143 +406,10 @@ class WorkspaceView extends View
|
||||
for editorElement in @panes.element.querySelectorAll('.pane > .item-views > .editor')
|
||||
$(editorElement).view()
|
||||
|
||||
# Public: Prepend an element or view to the panels at the top of the
|
||||
# workspace.
|
||||
#
|
||||
# * `element` jQuery object or DOM element
|
||||
prependToTop: (element) ->
|
||||
@vertical.prepend(element)
|
||||
|
||||
# Public: Append an element or view to the panels at the top of the workspace.
|
||||
#
|
||||
# * `element` jQuery object or DOM element
|
||||
appendToTop: (element) ->
|
||||
@panes.before(element)
|
||||
|
||||
# Public: Prepend an element or view to the panels at the bottom of the
|
||||
# workspace.
|
||||
#
|
||||
# * `element` jQuery object or DOM element
|
||||
prependToBottom: (element) ->
|
||||
@panes.after(element)
|
||||
|
||||
# Public: Append an element or view to the panels at the bottom of the
|
||||
# workspace.
|
||||
#
|
||||
# * `element` jQuery object or DOM element
|
||||
appendToBottom: (element) ->
|
||||
@vertical.append(element)
|
||||
|
||||
# Public: Prepend an element or view to the panels at the left of the
|
||||
# workspace.
|
||||
#
|
||||
# * `element` jQuery object or DOM element
|
||||
prependToLeft: (element) ->
|
||||
@horizontal.prepend(element)
|
||||
|
||||
# Public: Append an element or view to the panels at the left of the
|
||||
# workspace.
|
||||
#
|
||||
# * `element` jQuery object or DOM element
|
||||
appendToLeft: (element) ->
|
||||
@vertical.before(element)
|
||||
|
||||
# Public: Prepend an element or view to the panels at the right of the
|
||||
# workspace.
|
||||
#
|
||||
# * `element` jQuery object or DOM element
|
||||
prependToRight: (element) ->
|
||||
@vertical.after(element)
|
||||
|
||||
# Public: Append an element or view to the panels at the right of the
|
||||
# workspace.
|
||||
#
|
||||
# * `element` jQuery object or DOM element
|
||||
appendToRight: (element) ->
|
||||
@horizontal.append(element)
|
||||
|
||||
# Public: Get the active pane view.
|
||||
#
|
||||
# Prefer {Workspace::getActivePane} if you don't actually need access to the
|
||||
# view.
|
||||
#
|
||||
# Returns a {PaneView}.
|
||||
getActivePaneView: ->
|
||||
@panes.getActivePaneView()
|
||||
|
||||
# Public: Get the view associated with the active pane item.
|
||||
#
|
||||
# Returns a view.
|
||||
getActiveView: ->
|
||||
@panes.getActiveView()
|
||||
|
||||
# Focus the previous pane by id.
|
||||
focusPreviousPaneView: -> @model.activatePreviousPane()
|
||||
|
||||
# Focus the next pane by id.
|
||||
focusNextPaneView: -> @model.activateNextPane()
|
||||
|
||||
# Public: Focus the pane directly above the active pane.
|
||||
focusPaneViewAbove: -> @panes.focusPaneViewAbove()
|
||||
|
||||
# Public: Focus the pane directly below the active pane.
|
||||
focusPaneViewBelow: -> @panes.focusPaneViewBelow()
|
||||
|
||||
# Public: Focus the pane directly to the left of the active pane.
|
||||
focusPaneViewOnLeft: -> @panes.focusPaneViewOnLeft()
|
||||
|
||||
# Public: Focus the pane directly to the right of the active pane.
|
||||
focusPaneViewOnRight: -> @panes.focusPaneViewOnRight()
|
||||
|
||||
# Public: Register a function to be called for every current and future
|
||||
# pane view in the workspace.
|
||||
#
|
||||
# * `callback` A {Function} with a {PaneView} as its only argument.
|
||||
# * `paneView` {PaneView}
|
||||
#
|
||||
# Returns a subscription object with an `.off` method that you can call to
|
||||
# unregister the callback.
|
||||
eachPaneView: (callback) ->
|
||||
@panes.eachPaneView(callback)
|
||||
|
||||
# Public: Get all existing pane views.
|
||||
#
|
||||
# Prefer {Workspace::getPanes} if you don't need access to the view objects.
|
||||
# Also consider using {::eachPaneView} if you want to register a callback for
|
||||
# all current and *future* pane views.
|
||||
#
|
||||
# Returns an Array of all open {PaneView}s.
|
||||
getPaneViews: ->
|
||||
@panes.getPaneViews()
|
||||
|
||||
# Public: Register a function to be called for every current and future
|
||||
# editor view in the workspace (only includes {EditorView}s that are pane
|
||||
# items).
|
||||
#
|
||||
# * `callback` A {Function} with an {EditorView} as its only argument.
|
||||
# * `editorView` {EditorView}
|
||||
#
|
||||
# Returns a subscription object with an `.off` method that you can call to
|
||||
# unregister the callback.
|
||||
eachEditorView: (callback) ->
|
||||
callback(editorView) for editorView in @getEditorViews()
|
||||
attachedCallback = (e, editorView) ->
|
||||
callback(editorView) unless editorView.mini
|
||||
@on('editor:attached', attachedCallback)
|
||||
off: => @off('editor:attached', attachedCallback)
|
||||
|
||||
# Called by SpacePen
|
||||
beforeRemove: ->
|
||||
@model.destroy()
|
||||
|
||||
setEditorFontSize: (fontSize) ->
|
||||
atom.themes.updateGlobalEditorStyle('font-size', fontSize + 'px')
|
||||
|
||||
setEditorFontFamily: (fontFamily) ->
|
||||
atom.themes.updateGlobalEditorStyle('font-family', fontFamily)
|
||||
|
||||
setEditorLineHeight: (lineHeight) ->
|
||||
atom.themes.updateGlobalEditorStyle('line-height', lineHeight)
|
||||
###
|
||||
Section: Deprecated
|
||||
###
|
||||
|
||||
# Deprecated
|
||||
eachPane: (callback) ->
|
||||
|
||||
@@ -10,7 +10,7 @@ Editor = require './editor'
|
||||
PaneContainer = require './pane-container'
|
||||
Pane = require './pane'
|
||||
|
||||
# Public: Represents the state of the user interface for the entire window.
|
||||
# Essential: 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
|
||||
@@ -91,6 +91,33 @@ class Workspace extends Model
|
||||
Section: Event Subscription
|
||||
###
|
||||
|
||||
# Essential: Invoke the given callback with all current and future text
|
||||
# editors in the workspace.
|
||||
#
|
||||
# * `callback` {Function} to be called with current and future text editors.
|
||||
# * `editor` An {Editor} that is present in {::getTextEditors} at the time
|
||||
# of subscription or that is added at some later time.
|
||||
#
|
||||
# Returns a {Disposable} on which `.dispose()` can be called to unsubscribe.
|
||||
observeTextEditors: (callback) ->
|
||||
callback(textEditor) for textEditor in @getTextEditors()
|
||||
@onDidAddTextEditor ({textEditor}) -> callback(textEditor)
|
||||
|
||||
# Essential: Invoke the given callback whenever an item is opened. Unlike
|
||||
# ::onDidAddPaneItem, observers will be notified for items that are already
|
||||
# present in the workspace when they are reopened.
|
||||
#
|
||||
# * `callback` {Function} to be called whenever an item is opened.
|
||||
# * `event` {Object} with the following keys:
|
||||
# * `uri` {String} representing the opened URI. Could be `undefined`.
|
||||
# * `item` The opened item.
|
||||
# * `pane` The pane in which the item was opened.
|
||||
# * `index` The index of the opened item on its pane.
|
||||
#
|
||||
# Returns a {Disposable} on which `.dispose()` can be called to unsubscribe.
|
||||
onDidOpen: (callback) ->
|
||||
@emitter.on 'did-open', callback
|
||||
|
||||
# Extended: Invoke the given callback when a pane is added to the workspace.
|
||||
#
|
||||
# * `callback` {Function} to be called panes are added.
|
||||
@@ -174,33 +201,6 @@ class Workspace extends Model
|
||||
@onDidAddPaneItem ({item, pane, index}) ->
|
||||
callback({textEditor: item, pane, index}) if item instanceof Editor
|
||||
|
||||
# Essential: Invoke the given callback with all current and future text
|
||||
# editors in the workspace.
|
||||
#
|
||||
# * `callback` {Function} to be called with current and future text editors.
|
||||
# * `editor` An {Editor} that is present in {::getTextEditors} at the time
|
||||
# of subscription or that is added at some later time.
|
||||
#
|
||||
# Returns a {Disposable} on which `.dispose()` can be called to unsubscribe.
|
||||
observeTextEditors: (callback) ->
|
||||
callback(textEditor) for textEditor in @getTextEditors()
|
||||
@onDidAddTextEditor ({textEditor}) -> callback(textEditor)
|
||||
|
||||
# Essential: Invoke the given callback whenever an item is opened. Unlike
|
||||
# ::onDidAddPaneItem, observers will be notified for items that are already
|
||||
# present in the workspace when they are reopened.
|
||||
#
|
||||
# * `callback` {Function} to be called whenever an item is opened.
|
||||
# * `event` {Object} with the following keys:
|
||||
# * `uri` {String} representing the opened URI. Could be `undefined`.
|
||||
# * `item` The opened item.
|
||||
# * `pane` The pane in which the item was opened.
|
||||
# * `index` The index of the opened item on its pane.
|
||||
#
|
||||
# Returns a {Disposable} on which `.dispose()` can be called to unsubscribe.
|
||||
onDidOpen: (callback) ->
|
||||
@emitter.on 'did-open', callback
|
||||
|
||||
eachEditor: (callback) ->
|
||||
deprecate("Use Workspace::observeTextEditors instead")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user