Merge branch 'master' of github.com:atom/atom into cn-move-lines-up-and-down-with-multiple-selections

This commit is contained in:
abe33
2015-11-02 18:39:04 +01:00
140 changed files with 3601 additions and 2399 deletions

View File

@@ -0,0 +1,169 @@
_ = require 'underscore-plus'
ipc = require 'ipc'
remote = require 'remote'
shell = require 'shell'
webFrame = require 'web-frame'
{Disposable} = require 'event-kit'
{getWindowLoadSettings, setWindowLoadSettings} = require './window-load-settings-helpers'
module.exports =
class ApplicationDelegate
open: (params) ->
ipc.send('open', params)
pickFolder: (callback) ->
responseChannel = "atom-pick-folder-response"
ipc.on responseChannel, (path) ->
ipc.removeAllListeners(responseChannel)
callback(path)
ipc.send("pick-folder", responseChannel)
getCurrentWindow: ->
remote.getCurrentWindow()
closeWindow: ->
ipc.send("call-window-method", "close")
getWindowSize: ->
[width, height] = remote.getCurrentWindow().getSize()
{width, height}
setWindowSize: (width, height) ->
remote.getCurrentWindow().setSize(width, height)
getWindowPosition: ->
[x, y] = remote.getCurrentWindow().getPosition()
{x, y}
setWindowPosition: (x, y) ->
ipc.send("call-window-method", "setPosition", x, y)
centerWindow: ->
ipc.send("call-window-method", "center")
focusWindow: ->
ipc.send("call-window-method", "focus")
showWindow: ->
ipc.send("call-window-method", "show")
hideWindow: ->
ipc.send("call-window-method", "hide")
restartWindow: ->
ipc.send("call-window-method", "restart")
isWindowMaximized: ->
remote.getCurrentWindow().isMaximized()
maximizeWindow: ->
ipc.send("call-window-method", "maximize")
isWindowFullScreen: ->
remote.getCurrentWindow().isFullScreen()
setWindowFullScreen: (fullScreen=false) ->
ipc.send("call-window-method", "setFullScreen", fullScreen)
openWindowDevTools: ->
remote.getCurrentWindow().openDevTools()
toggleWindowDevTools: ->
remote.getCurrentWindow().toggleDevTools()
executeJavaScriptInWindowDevTools: (code) ->
remote.getCurrentWindow().executeJavaScriptInDevTools(code)
setWindowDocumentEdited: (edited) ->
ipc.send("call-window-method", "setDocumentEdited", edited)
setRepresentedFilename: (filename) ->
ipc.send("call-window-method", "setRepresentedFilename", filename)
setRepresentedDirectoryPaths: (paths) ->
loadSettings = getWindowLoadSettings()
loadSettings['initialPaths'] = paths
setWindowLoadSettings(loadSettings)
setAutoHideWindowMenuBar: (autoHide) ->
ipc.send("call-window-method", "setAutoHideMenuBar", autoHide)
setWindowMenuBarVisibility: (visible) ->
remote.getCurrentWindow().setMenuBarVisibility(visible)
getPrimaryDisplayWorkAreaSize: ->
screen = remote.require 'screen'
screen.getPrimaryDisplay().workAreaSize
confirm: ({message, detailedMessage, buttons}) ->
buttons ?= {}
if _.isArray(buttons)
buttonLabels = buttons
else
buttonLabels = Object.keys(buttons)
dialog = remote.require('dialog')
chosen = dialog.showMessageBox(remote.getCurrentWindow(), {
type: 'info'
message: message
detail: detailedMessage
buttons: buttonLabels
})
if _.isArray(buttons)
chosen
else
callback = buttons[buttonLabels[chosen]]
callback?()
showMessageDialog: (params) ->
showSaveDialog: (params) ->
if _.isString(params)
params = defaultPath: params
else
params = _.clone(params)
params.title ?= 'Save File'
params.defaultPath ?= getWindowLoadSettings().initialPaths[0]
dialog = remote.require('dialog')
dialog.showSaveDialog remote.getCurrentWindow(), params
playBeepSound: ->
shell.beep()
onDidOpenLocations: (callback) ->
outerCallback = (message, detail) ->
if message is 'open-locations'
callback(detail)
ipc.on('message', outerCallback)
new Disposable ->
ipc.removeEventListener('message', outerCallback)
onUpdateAvailable: (callback) ->
outerCallback = (message, detail) ->
if message is 'update-available'
callback(detail)
ipc.on('message', outerCallback)
new Disposable ->
ipc.removeEventListener('message', outerCallback)
onApplicationMenuCommand: (callback) ->
ipc.on('command', callback)
new Disposable ->
ipc.removeEventListener('command', callback)
onContextMenuCommand: (callback) ->
ipc.on('context-command', callback)
new Disposable ->
ipc.removeEventListener('context-command', callback)
didCancelWindowUnload: ->
ipc.send('did-cancel-window-unload')
openExternal: (url) ->
shell.openExternal(url)
disablePinchToZoom: ->
webFrame.setZoomLevelLimits(1, 1)

View File

@@ -1,9 +1,5 @@
crypto = require 'crypto'
ipc = require 'ipc'
os = require 'os'
path = require 'path'
remote = require 'remote'
shell = require 'shell'
_ = require 'underscore-plus'
{deprecate} = require 'grim'
@@ -14,83 +10,52 @@ Model = require './model'
WindowEventHandler = require './window-event-handler'
StylesElement = require './styles-element'
StorageFolder = require './storage-folder'
{getWindowLoadSettings} = require './window-load-settings-helpers'
registerDefaultCommands = require './register-default-commands'
DeserializerManager = require './deserializer-manager'
ViewRegistry = require './view-registry'
NotificationManager = require './notification-manager'
Config = require './config'
KeymapManager = require './keymap-extensions'
TooltipManager = require './tooltip-manager'
CommandRegistry = require './command-registry'
GrammarRegistry = require './grammar-registry'
StyleManager = require './style-manager'
PackageManager = require './package-manager'
ThemeManager = require './theme-manager'
MenuManager = require './menu-manager'
ContextMenuManager = require './context-menu-manager'
CommandInstaller = require './command-installer'
Clipboard = require './clipboard'
Project = require './project'
Workspace = require './workspace'
PanelContainer = require './panel-container'
Panel = require './panel'
PaneContainer = require './pane-container'
PaneAxis = require './pane-axis'
Pane = require './pane'
Project = require './project'
TextEditor = require './text-editor'
TextBuffer = require 'text-buffer'
Gutter = require './gutter'
WorkspaceElement = require './workspace-element'
PanelContainerElement = require './panel-container-element'
PanelElement = require './panel-element'
PaneContainerElement = require './pane-container-element'
PaneAxisElement = require './pane-axis-element'
PaneElement = require './pane-element'
TextEditorElement = require './text-editor-element'
{createGutterView} = require './gutter-component-helpers'
# 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
class AtomEnvironment extends Model
@version: 1 # Increment this when the serialization format changes
# 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.
#
# Returns an Atom instance, fully initialized
@loadOrCreate: (mode) ->
startTime = Date.now()
atom = @deserialize(@loadState(mode)) ? new this({mode, @version})
atom.deserializeTimings.atom = Date.now() - startTime
atom
# Deserializes the Atom environment from a state object
@deserialize: (state) ->
new this(state) if state?.version is @version
# Loads and returns the serialized state corresponding to this window
# if it exists; otherwise returns undefined.
@loadState: (mode) ->
if stateKey = @getStateKey(@getLoadSettings().initialPaths, mode)
if state = @getStorageFolder().load(stateKey)
return state
if windowState = @getLoadSettings().windowState
try
JSON.parse(@getLoadSettings().windowState)
catch error
console.warn "Error parsing window state: #{statePath} #{error.stack}", error
# Returns the path where the state for the current window will be
# located if it exists.
@getStateKey: (paths, mode) ->
if mode is 'spec'
'spec'
else if mode is 'editor' and paths?.length > 0
sha1 = crypto.createHash('sha1').update(paths.slice().sort().join("\n")).digest('hex')
"editor-#{sha1}"
else
null
# Get the directory path to Atom's configuration area.
#
# Returns the absolute path to ~/.atom
@getConfigDirPath: ->
@configDirPath ?= process.env.ATOM_HOME
@getStorageFolder: ->
@storageFolder ?= new StorageFolder(@getConfigDirPath())
# Returns the load settings hash associated with the current window.
@getLoadSettings: ->
@loadSettings ?= JSON.parse(decodeURIComponent(location.hash.substr(1)))
cloned = _.deepClone(@loadSettings)
# The loadSettings.windowState could be large, request it only when needed.
cloned.__defineGetter__ 'windowState', =>
@getCurrentWindow().loadSettings.windowState
cloned.__defineSetter__ 'windowState', (value) =>
@getCurrentWindow().loadSettings.windowState = value
cloned
@updateLoadSetting: (key, value) ->
@getLoadSettings()
@loadSettings[key] = value
location.hash = encodeURIComponent(JSON.stringify(@loadSettings))
@getCurrentWindow: ->
remote.getCurrentWindow()
workspaceParentSelectorctor: 'body'
lastUncaughtError: null
###
@@ -150,122 +115,199 @@ class Atom extends Model
###
# Call .loadOrCreate instead
constructor: (@state) ->
@emitter = new Emitter
@disposables = new CompositeDisposable
{@mode} = @state
DeserializerManager = require './deserializer-manager'
@deserializers = new DeserializerManager()
@deserializeTimings = {}
constructor: (params={}) ->
{@applicationDelegate, @window, @document, configDirPath, @enablePersistence} = params
# 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.
initialize: ->
window.onerror = =>
@lastUncaughtError = Array::slice.call(arguments)
[message, url, line, column, originalError] = @lastUncaughtError
{line, column} = mapSourcePosition({source: url, line, column})
eventObject = {message, url, line, column, originalError}
openDevTools = true
eventObject.preventDefault = -> openDevTools = false
@emitter.emit 'will-throw-error', eventObject
if openDevTools
@openDevTools()
@executeJavaScriptInDevTools('DevToolsAPI.showConsole()')
@emitter.emit 'did-throw-error', {message, url, line, column, originalError}
@disposables?.dispose()
@disposables = new CompositeDisposable
@displayWindow() unless @inSpecMode()
@setBodyPlatformClass()
@state = {version: @constructor.version}
@loadTime = null
Config = require './config'
KeymapManager = require './keymap-extensions'
ViewRegistry = require './view-registry'
CommandRegistry = require './command-registry'
TooltipManager = require './tooltip-manager'
NotificationManager = require './notification-manager'
PackageManager = require './package-manager'
Clipboard = require './clipboard'
GrammarRegistry = require './grammar-registry'
ThemeManager = require './theme-manager'
StyleManager = require './style-manager'
ContextMenuManager = require './context-menu-manager'
MenuManager = require './menu-manager'
{devMode, safeMode, resourcePath} = @getLoadSettings()
configDirPath = @getConfigDirPath()
# Add 'exports' to module search path.
exportsPath = path.join(resourcePath, 'exports')
require('module').globalPaths.push(exportsPath)
# Still set NODE_PATH since tasks may need it.
process.env.NODE_PATH = exportsPath
@emitter = new Emitter
@disposables = new CompositeDisposable
# Make react.js faster
process.env.NODE_ENV ?= 'production' unless devMode
@deserializers = new DeserializerManager(this)
@deserializeTimings = {}
@views = new ViewRegistry(this)
@config = new Config({configDirPath, resourcePath})
@keymaps = new KeymapManager({configDirPath, resourcePath})
@keymaps.subscribeToFileReadFailure()
@tooltips = new TooltipManager
@notifications = new NotificationManager
@config = new Config({configDirPath, resourcePath, notificationManager: @notifications, @enablePersistence})
@setConfigSchema()
@keymaps = new KeymapManager({configDirPath, resourcePath, notificationManager: @notifications})
@tooltips = new TooltipManager(keymapManager: @keymaps)
@commands = new CommandRegistry
@views = new ViewRegistry
@registerViewProviders()
@packages = new PackageManager({devMode, configDirPath, resourcePath, safeMode})
@styles = new StyleManager
document.head.appendChild(new StylesElement)
@themes = new ThemeManager({packageManager: @packages, configDirPath, resourcePath, safeMode})
@contextMenu = new ContextMenuManager({resourcePath, devMode})
@menu = new MenuManager({resourcePath})
@commands.attach(@window)
@grammars = new GrammarRegistry({@config})
@styles = new StyleManager({configDirPath})
@packages = new PackageManager({
devMode, configDirPath, resourcePath, safeMode, @config, styleManager: @styles,
commandRegistry: @commands, keymapManager: @keymaps, notificationManager: @notifications,
grammarRegistry: @grammars
})
@themes = new ThemeManager({
packageManager: @packages, configDirPath, resourcePath, safeMode, @config,
styleManager: @styles, notificationManager: @notifications, viewRegistry: @views
})
@menu = new MenuManager({resourcePath, keymapManager: @keymaps, packageManager: @packages})
@contextMenu = new ContextMenuManager({resourcePath, devMode, keymapManager: @keymaps})
@packages.setMenuManager(@menu)
@packages.setContextMenuManager(@contextMenu)
@packages.setThemeManager(@themes)
@clipboard = new Clipboard()
@grammars = @deserializers.deserialize(@state.grammars ? @state.syntax) ? new GrammarRegistry()
@disposables.add @packages.onDidActivateInitialPackages => @watchThemes()
Project = require './project'
TextBuffer = require 'text-buffer'
@project = new Project({notificationManager: @notifications, packageManager: @packages, @config})
@commandInstaller = new CommandInstaller(@getVersion(), @applicationDelegate)
@workspace = new Workspace({
@config, @project, packageManager: @packages, grammarRegistry: @grammars, deserializerManager: @deserializers,
notificationManager: @notifications, @applicationDelegate, @clipboard, viewRegistry: @views, assert: @assert.bind(this)
})
@themes.workspace = @workspace
@config.load()
@themes.loadBaseStylesheets()
@initialStyleElements = @styles.getSnapshot()
@themes.initialLoadComplete = true
@setBodyPlatformClass()
@stylesElement = @styles.buildStylesElement()
@document.head.appendChild(@stylesElement)
@applicationDelegate.disablePinchToZoom()
@keymaps.subscribeToFileReadFailure()
@keymaps.loadBundledKeymaps()
@registerDefaultCommands()
@registerDefaultOpeners()
@registerDefaultDeserializers()
@registerDefaultViewProviders()
@installUncaughtErrorHandler()
@installWindowEventHandler()
@observeAutoHideMenuBar()
setConfigSchema: ->
@config.setSchema null, {type: 'object', properties: _.clone(require('./config-schema'))}
registerDefaultDeserializers: ->
@deserializers.add(Workspace)
@deserializers.add(PaneContainer)
@deserializers.add(PaneAxis)
@deserializers.add(Pane)
@deserializers.add(Project)
@deserializers.add(TextEditor)
@deserializers.add(TextBuffer)
TokenizedBuffer = require './tokenized-buffer'
DisplayBuffer = require './display-buffer'
TextEditor = require './text-editor'
@windowEventHandler = new WindowEventHandler
registerDefaultCommands: ->
registerDefaultCommands({commandRegistry: @commands, @config, @commandInstaller})
# Register the core views as early as possible in case they are needed for
# package deserialization.
registerViewProviders: ->
Gutter = require './gutter'
Pane = require './pane'
PaneElement = require './pane-element'
PaneContainer = require './pane-container'
PaneContainerElement = require './pane-container-element'
PaneAxis = require './pane-axis'
PaneAxisElement = require './pane-axis-element'
TextEditor = require './text-editor'
TextEditorElement = require './text-editor-element'
{createGutterView} = require './gutter-component-helpers'
registerDefaultViewProviders: ->
@views.addViewProvider Workspace, (model, env) ->
new WorkspaceElement().initialize(model, env)
@views.addViewProvider PanelContainer, (model, env) ->
new PanelContainerElement().initialize(model, env)
@views.addViewProvider Panel, (model, env) ->
new PanelElement().initialize(model, env)
@views.addViewProvider PaneContainer, (model, env) ->
new PaneContainerElement().initialize(model, env)
@views.addViewProvider PaneAxis, (model, env) ->
new PaneAxisElement().initialize(model, env)
@views.addViewProvider Pane, (model, env) ->
new PaneElement().initialize(model, env)
@views.addViewProvider TextEditor, (model, env) ->
new TextEditorElement().initialize(model, env)
@views.addViewProvider(Gutter, createGutterView)
atom.views.addViewProvider PaneContainer, (model) ->
new PaneContainerElement().initialize(model)
atom.views.addViewProvider PaneAxis, (model) ->
new PaneAxisElement().initialize(model)
atom.views.addViewProvider Pane, (model) ->
new PaneElement().initialize(model)
atom.views.addViewProvider TextEditor, (model) ->
new TextEditorElement().initialize(model)
atom.views.addViewProvider(Gutter, createGutterView)
registerDefaultOpeners: ->
@workspace.addOpener (uri) =>
switch uri
when 'atom://.atom/stylesheet'
@workspace.open(@styles.getUserStyleSheetPath())
when 'atom://.atom/keymap'
@workspace.open(@keymaps.getUserKeymapPath())
when 'atom://.atom/config'
@workspace.open(@config.getUserConfigPath())
when 'atom://.atom/init-script'
@workspace.open(@getUserInitScriptPath())
registerDefaultTargetForKeymaps: ->
@keymaps.defaultTarget = @views.getView(@workspace)
observeAutoHideMenuBar: ->
@disposables.add @config.onDidChange 'core.autoHideMenuBar', ({newValue}) =>
@setAutoHideMenuBar(newValue)
@setAutoHideMenuBar(true) if @config.get('core.autoHideMenuBar')
reset: ->
@deserializers.clear()
@registerDefaultDeserializers()
@config.clear()
@setConfigSchema()
@keymaps.clear()
@keymaps.loadBundledKeymaps()
@commands.clear()
@registerDefaultCommands()
@styles.restoreSnapshot(@initialStyleElements)
@menu.clear()
@clipboard.reset()
@notifications.clear()
@contextMenu.clear()
@packages.reset()
@workspace.reset(@packages)
@registerDefaultOpeners()
@project.reset(@packages)
@workspace.subscribeToEvents()
@grammars.clear()
@views.clear()
@registerDefaultViewProviders()
@state.packageStates = {}
delete @state.workspace
destroy: ->
return if not @project
@disposables.dispose()
@workspace?.destroy()
@workspace = null
@themes.workspace = null
@project?.destroy()
@project = null
@commands.clear()
@stylesElement.remove()
@uninstallWindowEventHandler()
###
Section: Event Subscription
@@ -341,12 +383,6 @@ class Atom extends Model
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()
# Public: Get the time taken to completely load the current window.
#
# This time include things like loading and activating packages, creating
@@ -361,7 +397,7 @@ class Atom extends Model
#
# Returns an {Object} containing all the load setting key/value pairs.
getLoadSettings: ->
@constructor.getLoadSettings()
getWindowLoadSettings()
###
Section: Managing The Atom Window
@@ -372,7 +408,7 @@ class Atom extends Model
# Calling this method without an options parameter will open a prompt to pick
# a file/folder to open in the new window.
#
# * `options` An {Object} with the following keys:
# * `params` 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.
@@ -381,8 +417,8 @@ class Atom extends Model
# 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)
open: (params) ->
@applicationDelegate.open(params)
# Extended: Prompt the user to select one or more folders.
#
@@ -390,87 +426,81 @@ class Atom extends Model
# * `paths` An {Array} of {String} paths that the user selected, or `null`
# if the user dismissed the dialog.
pickFolder: (callback) ->
responseChannel = "atom-pick-folder-response"
ipc.on responseChannel, (path) ->
ipc.removeAllListeners(responseChannel)
callback(path)
ipc.send("pick-folder", responseChannel)
@applicationDelegate.pickFolder(callback)
# Essential: Close the current window.
close: ->
@getCurrentWindow().close()
@applicationDelegate.closeWindow()
# Essential: Get the size of current window.
#
# Returns an {Object} in the format `{width: 1000, height: 700}`
getSize: ->
[width, height] = @getCurrentWindow().getSize()
{width, height}
@applicationDelegate.getWindowSize()
# Essential: Set the size of current window.
#
# * `width` The {Number} of pixels.
# * `height` The {Number} of pixels.
setSize: (width, height) ->
@getCurrentWindow().setSize(width, height)
@applicationDelegate.setWindowSize(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}
@applicationDelegate.getWindowPosition()
# 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)
@applicationDelegate.setWindowPosition(x, y)
# Extended: Get the current window
getCurrentWindow: ->
@constructor.getCurrentWindow()
@applicationDelegate.getCurrentWindow()
# Extended: Move current window to the center of the screen.
center: ->
ipc.send('call-window-method', 'center')
@applicationDelegate.centerWindow()
# Extended: Focus the current window.
focus: ->
ipc.send('call-window-method', 'focus')
window.focus()
@applicationDelegate.focusWindow()
@window.focus()
# Extended: Show the current window.
show: ->
ipc.send('call-window-method', 'show')
@applicationDelegate.showWindow()
# Extended: Hide the current window.
hide: ->
ipc.send('call-window-method', 'hide')
@applicationDelegate.hideWindow()
# Extended: Reload the current window.
reload: ->
ipc.send('call-window-method', 'restart')
@applicationDelegate.restartWindow()
# Extended: Returns a {Boolean} that is `true` if the current window is maximized.
isMaximized: ->
@getCurrentWindow().isMaximized()
@applicationDelegate.isWindowMaximized()
maximize: ->
ipc.send('call-window-method', 'maximize')
@applicationDelegate.maximizeWindow()
# Extended: Returns a {Boolean} that is `true` if the current window is in full screen mode.
isFullScreen: ->
@getCurrentWindow().isFullScreen()
@applicationDelegate.isWindowFullScreen()
# Extended: Set the full screen state of the current window.
setFullScreen: (fullScreen=false) ->
ipc.send('call-window-method', 'setFullScreen', fullScreen)
@applicationDelegate.setWindowFullScreen(fullScreen)
if fullScreen
document.body.classList.add("fullscreen")
@document.body.classList.add("fullscreen")
else
document.body.classList.remove("fullscreen")
@document.body.classList.remove("fullscreen")
# Extended: Toggle the full screen state of the current window.
toggleFullScreen: ->
@@ -546,8 +576,7 @@ class Atom extends Model
if @isValidDimensions(dimensions)
dimensions
else
screen = remote.require 'screen'
{width, height} = screen.getPrimaryDisplay().workAreaSize
{width, height} = @applicationDelegate.getPrimaryDisplayWorkAreaSize()
{x: 0, y: 0, width: Math.min(1024, width), height}
restoreWindowDimensions: ->
@@ -565,37 +594,34 @@ class Atom extends Model
return if @inSpecMode()
workspaceElement = @views.getView(@workspace)
backgroundColor = window.getComputedStyle(workspaceElement)['background-color']
window.localStorage.setItem('atom:window-background-color', backgroundColor)
backgroundColor = @window.getComputedStyle(workspaceElement)['background-color']
@window.localStorage.setItem('atom:window-background-color', backgroundColor)
# Call this method when establishing a real application window.
startEditorWindow: ->
{safeMode} = @getLoadSettings()
CommandInstaller = require './command-installer'
commandInstaller = new CommandInstaller(@getVersion())
commandInstaller.installAtomCommand false, (error) ->
@commandInstaller.installAtomCommand false, (error) ->
console.warn error.message if error?
commandInstaller.installApmCommand false, (error) ->
@commandInstaller.installApmCommand false, (error) ->
console.warn error.message if error?
@loadConfig()
@keymaps.loadBundledKeymaps()
@themes.loadBaseStylesheets()
@disposables.add(@applicationDelegate.onDidOpenLocations(@openLocations.bind(this)))
@disposables.add(@applicationDelegate.onApplicationMenuCommand(@dispatchApplicationMenuCommand.bind(this)))
@disposables.add(@applicationDelegate.onContextMenuCommand(@dispatchContextMenuCommand.bind(this)))
@listenForUpdates()
@registerDefaultTargetForKeymaps()
@packages.loadPackages()
@deserializeEditorWindow()
@document.body.appendChild(@views.getView(@workspace))
@watchProjectPath()
@packages.activate()
@keymaps.loadUserKeymap()
@requireUserInitScript() unless safeMode
@requireUserInitScript() unless @getLoadSettings().safeMode
@menu.update()
@disposables.add @config.onDidChange 'core.autoHideMenuBar', ({newValue}) =>
@setAutoHideMenuBar(newValue)
@setAutoHideMenuBar(true) if @config.get('core.autoHideMenuBar')
@openInitialEmptyEditorIfNecessary()
@@ -603,36 +629,56 @@ class Atom extends Model
return if not @project
@storeWindowBackground()
@state.grammars = @grammars.serialize()
@state.grammars = {grammarOverridesByPath: @grammars.grammarOverridesByPath}
@state.project = @project.serialize()
@state.workspace = @workspace.serialize()
@packages.deactivatePackages()
@state.packageStates = @packages.packageStates
@saveSync()
@windowState = null
removeEditorWindow: ->
return if not @project
@workspace?.destroy()
@workspace = null
@project?.destroy()
@project = null
@windowEventHandler?.unsubscribe()
@state.fullScreen = @isFullScreen()
@saveStateSync()
openInitialEmptyEditorIfNecessary: ->
return unless @config.get('core.openEmptyEditorOnStart')
if @getLoadSettings().initialPaths?.length is 0 and @workspace.getPaneItems().length is 0
@workspace.open(null)
installUncaughtErrorHandler: ->
@previousWindowErrorHandler = @window.onerror
@window.onerror = =>
@lastUncaughtError = Array::slice.call(arguments)
[message, url, line, column, originalError] = @lastUncaughtError
{line, column} = mapSourcePosition({source: url, line, column})
eventObject = {message, url, line, column, originalError}
openDevTools = true
eventObject.preventDefault = -> openDevTools = false
@emitter.emit 'will-throw-error', eventObject
if openDevTools
@openDevTools()
@executeJavaScriptInDevTools('DevToolsAPI.showConsole()')
@emitter.emit 'did-throw-error', {message, url, line, column, originalError}
uninstallUncaughtErrorHandler: ->
@window.onerror = @previousWindowErrorHandler
installWindowEventHandler: ->
@windowEventHandler = new WindowEventHandler({atomEnvironment: this, @applicationDelegate, @window, @document})
uninstallWindowEventHandler: ->
@windowEventHandler?.unsubscribe()
###
Section: Messaging the User
###
# Essential: Visually and audibly trigger a beep.
beep: ->
shell.beep() if @config.get('core.audioBeep')
@applicationDelegate.playBeepSound() if @config.get('core.audioBeep')
@emitter.emit 'did-beep'
# Essential: A flexible way to open a dialog akin to an alert dialog.
@@ -655,25 +701,8 @@ class Atom extends Model
# button names and the values are callbacks to invoke when clicked.
#
# Returns the chosen button index {Number} if the buttons option was an array.
confirm: ({message, detailedMessage, buttons}={}) ->
buttons ?= {}
if _.isArray(buttons)
buttonLabels = buttons
else
buttonLabels = Object.keys(buttons)
dialog = remote.require('dialog')
chosen = dialog.showMessageBox @getCurrentWindow(),
type: 'info'
message: message
detail: detailedMessage
buttons: buttonLabels
if _.isArray(buttons)
chosen
else
callback = buttons[buttonLabels[chosen]]
callback?()
confirm: (params={}) ->
@applicationDelegate.confirm(params)
###
Section: Managing the Dev Tools
@@ -681,15 +710,15 @@ class Atom extends Model
# Extended: Open the dev tools for the current window.
openDevTools: ->
ipc.send('call-window-method', 'openDevTools')
@applicationDelegate.openWindowDevTools()
# Extended: Toggle the visibility of the dev tools for the current window.
toggleDevTools: ->
ipc.send('call-window-method', 'toggleDevTools')
@applicationDelegate.toggleWindowDevTools()
# Extended: Execute code in dev tools.
executeJavaScriptInDevTools: (code) ->
ipc.send('call-window-method', 'executeJavaScriptInDevTools', code)
@applicationDelegate.executeJavaScriptInWindowDevTools(code)
###
Section: Private
@@ -706,62 +735,19 @@ class Atom extends Model
false
deserializeProject: ->
Project = require './project'
startTime = Date.now()
@project ?= @deserializers.deserialize(@state.project) ? new Project()
@deserializeTimings.project = Date.now() - startTime
deserializeWorkspace: ->
Workspace = require './workspace'
startTime = Date.now()
@workspace = Workspace.deserialize(@state.workspace) ? new Workspace
@deserializeTimings.workspace = Date.now() - startTime
workspaceElement = @views.getView(@workspace)
@keymaps.defaultTarget = workspaceElement
document.querySelector(@workspaceParentSelectorctor).appendChild(workspaceElement)
deserializePackageStates: ->
@packages.packageStates = @state.packageStates ? {}
delete @state.packageStates
deserializeEditorWindow: ->
@deserializePackageStates()
@deserializeProject()
@deserializeWorkspace()
loadConfig: ->
@config.setSchema null, {type: 'object', properties: _.clone(require('./config-schema'))}
@config.load()
loadThemes: ->
@themes.load()
watchThemes: ->
@themes.onDidChangeActiveThemes =>
# Only reload stylesheets from non-theme packages
for pack in @packages.getActivePackages() when pack.getType() isnt 'theme'
pack.reloadStylesheets?()
return
# Notify the browser project of the window's current project path
watchProjectPath: ->
@disposables.add @project.onDidChangePaths =>
@constructor.updateLoadSetting('initialPaths', @project.getPaths())
exit: (status) ->
app = remote.require('app')
app.emit('will-exit')
remote.process.exit(status)
@applicationDelegate.setRepresentedDirectoryPaths(@project.getPaths())
setDocumentEdited: (edited) ->
ipc.send('call-window-method', 'setDocumentEdited', edited)
@applicationDelegate.setWindowDocumentEdited?(edited)
setRepresentedFilename: (filename) ->
ipc.send('call-window-method', 'setRepresentedFilename', filename)
@applicationDelegate.setWindowRepresentedFilename?(filename)
addProjectFolder: ->
@pickFolder (selectedPaths = []) =>
@@ -771,27 +757,61 @@ class Atom extends Model
callback(showSaveDialogSync())
showSaveDialogSync: (options={}) ->
if _.isString(options)
options = defaultPath: options
else
options = _.clone(options)
currentWindow = @getCurrentWindow()
dialog = remote.require('dialog')
options.title ?= 'Save File'
options.defaultPath ?= @project?.getPaths()[0]
dialog.showSaveDialog currentWindow, options
@applicationDelegate.showSaveDialog(options)
saveSync: ->
if storageKey = @constructor.getStateKey(@project?.getPaths(), @mode)
@constructor.getStorageFolder().store(storageKey, @state)
saveStateSync: ->
return unless @enablePersistence
if storageKey = @getStateKey(@project?.getPaths())
@getStorageFolder().store(storageKey, @state)
else
@getCurrentWindow().loadSettings.windowState = JSON.stringify(@state)
crashMainProcess: ->
remote.process.crash()
loadStateSync: ->
return unless @enablePersistence
crashRenderProcess: ->
process.crash()
startTime = Date.now()
if stateKey = @getStateKey(@getLoadSettings().initialPaths)
if state = @getStorageFolder().load(stateKey)
@state = state
if not @state? and windowState = @getLoadSettings().windowState
try
if state = JSON.parse(@getLoadSettings().windowState)
@state = state
catch error
console.warn "Error parsing window state: #{statePath} #{error.stack}", error
@deserializeTimings.atom = Date.now() - startTime
if grammarOverridesByPath = @state.grammars?.grammarOverridesByPath
@grammars.grammarOverridesByPath = grammarOverridesByPath
@setFullScreen(@state.fullScreen)
@packages.packageStates = @state.packageStates ? {}
startTime = Date.now()
@project.deserialize(@state.project, @deserializers) if @state.project?
@deserializeTimings.project = Date.now() - startTime
startTime = Date.now()
@workspace.deserialize(@state.workspace, @deserializers) if @state.workspace?
@deserializeTimings.workspace = Date.now() - startTime
getStateKey: (paths) ->
if paths?.length > 0
sha1 = crypto.createHash('sha1').update(paths.slice().sort().join("\n")).digest('hex')
"editor-#{sha1}"
else
null
getConfigDirPath: ->
@configDirPath ?= process.env.ATOM_HOME
getStorageFolder: ->
@storageFolder ?= new StorageFolder(@getConfigDirPath())
getUserInitScriptPath: ->
initScriptPath = fs.resolve(@getConfigDirPath(), 'init', ['js', 'coffee'])
@@ -802,44 +822,52 @@ class Atom extends Model
try
require(userInitScriptPath) if fs.isFileSync(userInitScriptPath)
catch error
atom.notifications.addError "Failed to load `#{userInitScriptPath}`",
@notifications.addError "Failed to load `#{userInitScriptPath}`",
detail: error.message
dismissable: true
# Require the module with the given globals.
#
# The globals will be set on the `window` object and removed after the
# require completes.
#
# * `id` The {String} module name or path.
# * `globals` An optional {Object} to set as globals during require.
requireWithGlobals: (id, globals={}) ->
existingGlobals = {}
for key, value of globals
existingGlobals[key] = window[key]
window[key] = value
require(id)
for key, value of existingGlobals
if value is undefined
delete window[key]
else
window[key] = value
return
onUpdateAvailable: (callback) ->
@emitter.on 'update-available', callback
updateAvailable: (details) ->
@emitter.emit 'update-available', details
listenForUpdates: ->
@disposables.add(@applicationDelegate.onUpdateAvailable(@updateAvailable.bind(this)))
setBodyPlatformClass: ->
document.body.classList.add("platform-#{process.platform}")
@document.body.classList.add("platform-#{process.platform}")
setAutoHideMenuBar: (autoHide) ->
ipc.send('call-window-method', 'setAutoHideMenuBar', autoHide)
ipc.send('call-window-method', 'setMenuBarVisibility', not autoHide)
@applicationDelegate.setAutoHideWindowMenuBar(autoHide)
@applicationDelegate.setWindowMenuBarVisibility(not autoHide)
dispatchApplicationMenuCommand: (command, arg) ->
activeElement = @document.activeElement
# Use the workspace element if body has focus
if activeElement is @document.body and workspaceElement = @views.getView(@workspace)
activeElement = workspaceElement
@commands.dispatch(activeElement, command, arg)
dispatchContextMenuCommand: (command, args...) ->
@commands.dispatch(@contextMenu.activeElement, command, args)
openLocations: (locations) ->
needsProjectPaths = @project?.getPaths().length is 0
for {pathToOpen, initialLine, initialColumn} in locations
if pathToOpen? and needsProjectPaths
if fs.existsSync(pathToOpen)
@project.addPath(pathToOpen)
else if fs.existsSync(path.dirname(pathToOpen))
@project.addPath(path.dirname(pathToOpen))
else
@project.addPath(pathToOpen)
unless fs.isDirectorySync(pathToOpen)
@workspace?.open(pathToOpen, {initialLine, initialColumn})
return
# Preserve this deprecation until 2.0. Sorry. Should have removed Q sooner.
Promise.prototype.done = (callback) ->

View File

@@ -16,6 +16,8 @@ net = require 'net'
url = require 'url'
{EventEmitter} = require 'events'
_ = require 'underscore-plus'
FindParentDir = null
Resolve = null
LocationSuffixRegExp = /(:\d+)(:\d+)?$/
@@ -63,7 +65,9 @@ class AtomApplication
exit: (status) -> app.exit(status)
constructor: (options) ->
{@resourcePath, @devResourcePath, @version, @devMode, @safeMode, @socketPath} = options
{@resourcePath, @devResourcePath, @version, @devMode, @safeMode, @socketPath, timeout} = options
@socketPath = null if options.test
global.atomApplication = this
@@ -83,11 +87,11 @@ class AtomApplication
if options.pathsToOpen?.length > 0 or options.urlsToOpen?.length > 0 or options.test
@openWithOptions(options)
else
@loadState() or @openPath(options)
@loadState(options) or @openPath(options)
openWithOptions: ({pathsToOpen, executedFrom, urlsToOpen, test, pidToKillWhenClosed, devMode, safeMode, newWindow, specDirectory, logFile, profileStartup}) ->
openWithOptions: ({pathsToOpen, executedFrom, urlsToOpen, test, pidToKillWhenClosed, devMode, safeMode, newWindow, logFile, profileStartup, timeout}) ->
if test
@runSpecs({exitWhenDone: true, @resourcePath, specDirectory, logFile})
@runTests({headless: true, devMode, @resourcePath, executedFrom, pathsToOpen, logFile, timeout})
else if pathsToOpen.length > 0
@openPaths({pathsToOpen, executedFrom, pidToKillWhenClosed, newWindow, devMode, safeMode, profileStartup})
else if urlsToOpen.length > 0
@@ -130,6 +134,7 @@ class AtomApplication
# the other launches will just pass their information to this server and then
# close immediately.
listenForArgumentsFromNewProcess: ->
return unless @socketPath?
@deleteSocketFile()
server = net.createServer (connection) =>
connection.on 'data', (data) =>
@@ -139,7 +144,7 @@ class AtomApplication
server.on 'error', (error) -> console.error 'Application server failed', error
deleteSocketFile: ->
return if process.platform is 'win32'
return if process.platform is 'win32' or not @socketPath?
if fs.existsSync(@socketPath)
try
@@ -160,7 +165,6 @@ class AtomApplication
devMode: @focusedWindow()?.devMode
safeMode: @focusedWindow()?.safeMode
@on 'application:run-all-specs', -> @runSpecs(exitWhenDone: false, resourcePath: @devResourcePath, safeMode: @focusedWindow()?.safeMode)
@on 'application:quit', -> app.quit()
@on 'application:new-window', -> @openPath(_.extend(windowDimensions: @focusedWindow()?.getDimensions(), getLoadSettings()))
@on 'application:new-file', -> (@focusedWindow() ? this).openPath()
@@ -215,11 +219,6 @@ class AtomApplication
@killAllProcesses()
@deleteSocketFile()
app.on 'will-exit', =>
@saveState(false)
@killAllProcesses()
@deleteSocketFile()
app.on 'open-file', (event, pathToOpen) =>
event.preventDefault()
@openPath({pathToOpen})
@@ -250,8 +249,8 @@ class AtomApplication
win = BrowserWindow.fromWebContents(event.sender)
@applicationMenu.update(win, template, keystrokesByCommand)
ipc.on 'run-package-specs', (event, specDirectory) =>
@runSpecs({resourcePath: @devResourcePath, specDirectory: specDirectory, exitWhenDone: false})
ipc.on 'run-package-specs', (event, packageSpecPath) =>
@runTests({resourcePath: @devResourcePath, pathsToOpen: [packageSpecPath], headless: false})
ipc.on 'command', (event, command) =>
@emit(command)
@@ -268,13 +267,19 @@ class AtomApplication
@promptForPath "folder", (selectedPaths) ->
event.sender.send(responseChannel, selectedPaths)
ipc.on 'cancel-window-close', =>
ipc.on 'did-cancel-window-unload', =>
@quitting = false
clipboard = require '../safe-clipboard'
ipc.on 'write-text-to-selection-clipboard', (event, selectedText) ->
clipboard.writeText(selectedText, 'selection')
ipc.on 'write-to-stdout', (event, output) ->
process.stdout.write(output)
ipc.on 'write-to-stderr', (event, output) ->
process.stderr.write(output)
# Public: Executes the given command.
#
# If it isn't handled globally, delegate to the currently focused window.
@@ -373,14 +378,16 @@ class AtomApplication
unless pidToKillWhenClosed or newWindow
existingWindow = @windowForPaths(pathsToOpen, devMode)
# Default to using the specified window or the last focused window
currentWindow = window ? @lastFocusedWindow
stats = (fs.statSyncNoException(pathToOpen) for pathToOpen in pathsToOpen)
existingWindow ?= currentWindow if (
stats.every((stat) -> stat.isFile?()) or
stats.some((stat) -> stat.isDirectory?()) and not currentWindow?.hasProjectPath()
)
unless existingWindow?
if currentWindow = window ? @lastFocusedWindow
existingWindow = currentWindow if (
currentWindow.devMode is devMode and
(
stats.every((stat) -> stat.isFile?()) or
stats.some((stat) -> stat.isDirectory?() and not currentWindow.hasProjectPath())
)
)
if existingWindow?
openedWindow = existingWindow
@@ -392,12 +399,12 @@ class AtomApplication
else
if devMode
try
bootstrapScript = require.resolve(path.join(@devResourcePath, 'src', 'window-bootstrap'))
windowInitializationScript = require.resolve(path.join(@devResourcePath, 'src', 'initialize-application-window'))
resourcePath = @devResourcePath
bootstrapScript ?= require.resolve('../window-bootstrap')
windowInitializationScript ?= require.resolve('../initialize-application-window')
resourcePath ?= @resourcePath
openedWindow = new AtomWindow({locationsToOpen, bootstrapScript, resourcePath, devMode, safeMode, windowDimensions, profileStartup})
openedWindow = new AtomWindow({locationsToOpen, windowInitializationScript, resourcePath, devMode, safeMode, windowDimensions, profileStartup})
if pidToKillWhenClosed?
@pidsToOpenWindows[pidToKillWhenClosed] = openedWindow
@@ -436,15 +443,15 @@ class AtomApplication
if states.length > 0 or allowEmpty
@storageFolder.store('application.json', states)
loadState: ->
loadState: (options) ->
if (states = @storageFolder.load('application.json'))?.length > 0
for state in states
@openWithOptions({
@openWithOptions(_.extend(options, {
pathsToOpen: state.initialPaths
urlsToOpen: []
devMode: @devMode
safeMode: @safeMode
})
}))
true
else
false
@@ -472,9 +479,9 @@ class AtomApplication
if pack?
if pack.urlMain
packagePath = @packages.resolvePackagePath(packageName)
bootstrapScript = path.resolve(packagePath, pack.urlMain)
windowInitializationScript = path.resolve(packagePath, pack.urlMain)
windowDimensions = @focusedWindow()?.getDimensions()
new AtomWindow({bootstrapScript, @resourcePath, devMode, safeMode, urlToOpen, windowDimensions})
new AtomWindow({windowInitializationScript, @resourcePath, devMode, safeMode, urlToOpen, windowDimensions})
else
console.log "Package '#{pack.name}' does not have a url main: #{urlToOpen}"
else
@@ -483,25 +490,63 @@ class AtomApplication
# Opens up a new {AtomWindow} to run specs within.
#
# options -
# :exitWhenDone - A Boolean that, if true, will close the window upon
# :headless - A Boolean that, if true, will close the window upon
# completion.
# :resourcePath - The path to include specs from.
# :specPath - The directory to load specs from.
# :safeMode - A Boolean that, if true, won't run specs from ~/.atom/packages
# and ~/.atom/dev/packages, defaults to false.
runSpecs: ({exitWhenDone, resourcePath, specDirectory, logFile, safeMode}) ->
runTests: ({headless, devMode, resourcePath, executedFrom, pathsToOpen, logFile, safeMode, timeout}) ->
if resourcePath isnt @resourcePath and not fs.existsSync(resourcePath)
resourcePath = @resourcePath
try
bootstrapScript = require.resolve(path.resolve(@devResourcePath, 'spec', 'spec-bootstrap'))
catch error
bootstrapScript = require.resolve(path.resolve(__dirname, '..', '..', 'spec', 'spec-bootstrap'))
timeoutInSeconds = Number.parseFloat(timeout)
unless Number.isNaN(timeoutInSeconds)
timeoutHandler = ->
console.log "The test suite has timed out because it has been running for more than #{timeoutInSeconds} seconds."
process.exit(124) # Use the same exit code as the UNIX timeout util.
setTimeout(timeoutHandler, timeoutInSeconds * 1000)
try
windowInitializationScript = require.resolve(path.resolve(@devResourcePath, 'src', 'initialize-test-window'))
catch error
windowInitializationScript = require.resolve(path.resolve(__dirname, '..', '..', 'src', 'initialize-test-window'))
testPaths = []
if pathsToOpen?
for pathToOpen in pathsToOpen
testPaths.push(path.resolve(executedFrom, fs.normalize(pathToOpen)))
if testPaths.length is 0
process.stderr.write 'Error: Specify at least one test path\n\n'
process.exit(1)
legacyTestRunnerPath = @resolveLegacyTestRunnerPath()
testRunnerPath = @resolveTestRunnerPath(testPaths[0])
isSpec = true
devMode = true
safeMode ?= false
new AtomWindow({bootstrapScript, resourcePath, exitWhenDone, isSpec, devMode, specDirectory, logFile, safeMode})
new AtomWindow({windowInitializationScript, resourcePath, headless, isSpec, devMode, testRunnerPath, legacyTestRunnerPath, testPaths, logFile, safeMode})
resolveTestRunnerPath: (testPath) ->
FindParentDir ?= require 'find-parent-dir'
if packageRoot = FindParentDir.sync(testPath, 'package.json')
packageMetadata = require(path.join(packageRoot, 'package.json'))
if packageMetadata.atomTestRunner
Resolve ?= require('resolve')
if testRunnerPath = Resolve.sync(packageMetadata.atomTestRunner, basedir: packageRoot, extensions: Object.keys(require.extensions))
return testRunnerPath
else
process.stderr.write "Error: Could not resolve test runner path '#{packageMetadata.atomTestRunner}'"
process.exit(1)
@resolveLegacyTestRunnerPath()
resolveLegacyTestRunnerPath: ->
try
require.resolve(path.resolve(@devResourcePath, 'spec', 'jasmine-test-runner'))
catch error
require.resolve(path.resolve(__dirname, '..', '..', 'spec', 'jasmine-test-runner'))
locationForPathToOpen: (pathToOpen, executedFrom='') ->
return {pathToOpen} unless pathToOpen

View File

@@ -29,7 +29,7 @@ class AtomProtocolHandler
# Creates the 'atom' custom protocol handler.
registerAtomProtocol: ->
protocol.registerProtocol 'atom', (request) =>
protocol.registerFileProtocol 'atom', (request, callback) =>
relativePath = path.normalize(request.url.substr(7))
if relativePath.indexOf('assets/') is 0
@@ -41,4 +41,4 @@ class AtomProtocolHandler
filePath = path.join(loadPath, relativePath)
break if fs.statSyncNoException(filePath).isFile?()
new protocol.RequestFileJob(filePath)
callback(filePath)

View File

@@ -19,7 +19,7 @@ class AtomWindow
isSpec: null
constructor: (settings={}) ->
{@resourcePath, pathToOpen, locationsToOpen, @isSpec, @exitWhenDone, @safeMode, @devMode} = settings
{@resourcePath, pathToOpen, locationsToOpen, @isSpec, @headless, @safeMode, @devMode} = settings
locationsToOpen ?= [{pathToOpen}] if pathToOpen
locationsToOpen ?= []
@@ -29,6 +29,10 @@ class AtomWindow
'web-preferences':
'direct-write': true
'subpixel-font-scaling': true
if @isSpec
options['web-preferences']['page-visibility'] = true
# Don't set icon on Windows so the exe's ico will be used as window and
# taskbar's icon. See https://github.com/atom/atom/issues/4811 for more.
if process.platform is 'linux'
@@ -84,7 +88,7 @@ class AtomWindow
hash: encodeURIComponent(JSON.stringify(loadSettings))
getLoadSettings: ->
if @browserWindow.webContents?.loaded
if @browserWindow.webContents? and not @browserWindow.webContents.isLoading()
hash = url.parse(@browserWindow.webContents.getUrl()).hash.substr(1)
JSON.parse(decodeURIComponent(hash))
@@ -131,7 +135,7 @@ class AtomWindow
@browserWindow.destroy() if chosen is 0
@browserWindow.webContents.on 'crashed', =>
global.atomApplication.exit(100) if @exitWhenDone
global.atomApplication.exit(100) if @headless
chosen = dialog.showMessageBox @browserWindow,
type: 'warning'
@@ -142,6 +146,10 @@ class AtomWindow
when 0 then @browserWindow.destroy()
when 1 then @browserWindow.restart()
@browserWindow.webContents.on 'will-navigate', (event, url) =>
unless url is @browserWindow.webContents.getUrl()
event.preventDefault()
@setupContextMenu()
if @isSpec

View File

@@ -17,14 +17,8 @@ class AutoUpdateManager
constructor: (@version, @testMode, @disabled) ->
@state = IdleState
if process.platform is 'win32'
# Squirrel for Windows can't handle query params
# https://github.com/Squirrel/Squirrel.Windows/issues/132
@feedUrl = 'https://atom.io/api/updates'
else
@iconPath = path.resolve(__dirname, '..', '..', 'resources', 'atom.png')
@feedUrl = "https://atom.io/api/updates?version=#{@version}"
@iconPath = path.resolve(__dirname, '..', '..', 'resources', 'atom.png')
@feedUrl = "https://atom.io/api/updates?version=#{@version}"
process.nextTick => @setupAutoUpdater()
setupAutoUpdater: ->

View File

@@ -99,9 +99,9 @@ parseCommandLine = ->
options.alias('n', 'new-window').boolean('n').describe('n', 'Open a new window.')
options.boolean('profile-startup').describe('profile-startup', 'Create a profile of the startup execution time.')
options.alias('r', 'resource-path').string('r').describe('r', 'Set the path to the Atom source directory and enable dev-mode.')
options.alias('s', 'spec-directory').string('s').describe('s', 'Set the directory from which to run package specs (default: Atom\'s spec directory).')
options.boolean('safe').describe('safe', 'Do not load packages from ~/.atom/packages or ~/.atom/dev/packages.')
options.alias('t', 'test').boolean('t').describe('t', 'Run the specified specs and exit with error code on failures.')
options.string('timeout').describe('timeout', 'When in test mode, waits until the specified time (in minutes) and kills the process (exit code: 130).')
options.alias('v', 'version').boolean('v').describe('v', 'Print the version.')
options.alias('w', 'wait').boolean('w').describe('w', 'Wait for window to be closed before returning.')
options.string('socket-path')
@@ -121,7 +121,7 @@ parseCommandLine = ->
safeMode = args['safe']
pathsToOpen = args._
test = args['test']
specDirectory = args['spec-directory']
timeout = args['timeout']
newWindow = args['new-window']
pidToKillWhenClosed = args['pid'] if args['wait']
logFile = args['log-file']
@@ -133,18 +133,9 @@ parseCommandLine = ->
if args['resource-path']
devMode = true
resourcePath = args['resource-path']
else
# Set resourcePath based on the specDirectory if running specs on atom core
if specDirectory?
packageDirectoryPath = path.join(specDirectory, '..')
packageManifestPath = path.join(packageDirectoryPath, 'package.json')
if fs.statSyncNoException(packageManifestPath)
try
packageManifest = JSON.parse(fs.readFileSync(packageManifestPath))
resourcePath = packageDirectoryPath if packageManifest.name is 'atom'
if devMode
resourcePath ?= devResourcePath
devMode = true if test
resourcePath ?= devResourcePath if devMode
unless fs.statSyncNoException(resourcePath)
resourcePath = path.dirname(path.dirname(__dirname))
@@ -157,7 +148,7 @@ parseCommandLine = ->
devResourcePath = normalizeDriveLetterName(devResourcePath)
{resourcePath, devResourcePath, pathsToOpen, urlsToOpen, executedFrom, test,
version, pidToKillWhenClosed, devMode, safeMode, newWindow, specDirectory,
logFile, socketPath, profileStartup}
version, pidToKillWhenClosed, devMode, safeMode, newWindow,
logFile, socketPath, profileStartup, timeout}
start()

View File

@@ -14,8 +14,12 @@ clipboard = require './safe-clipboard'
# ```
module.exports =
class Clipboard
metadata: null
signatureForMetadata: null
constructor: ->
@reset()
reset: ->
@metadata = null
@signatureForMetadata = null
# Creates an `md5` hash of some text.
#

View File

@@ -29,6 +29,10 @@ exports.compile = function (sourceCode, filePath) {
Error.prepareStackTrace = previousPrepareStackTrace
}
if (process.platform === 'win32') {
filePath = 'file:///' + path.resolve(filePath).replace(/\\/g, '/')
}
var output = CoffeeScript.compile(sourceCode, {
filename: filePath,
sourceFiles: [filePath],

View File

@@ -26,7 +26,7 @@ symlinkCommandWithPrivilegeSync = (sourcePath, destinationPath) ->
module.exports =
class CommandInstaller
constructor: (@appVersion) ->
constructor: (@appVersion, @applicationDelegate) ->
getInstallDirectory: ->
"/usr/local/bin"
@@ -36,7 +36,7 @@ class CommandInstaller
installShellCommandsInteractively: ->
showErrorDialog = (error) ->
atom.confirm
@applicationDelegate.confirm
message: "Failed to install shell commands"
detailedMessage: error.message
@@ -44,11 +44,11 @@ class CommandInstaller
if error?
showErrorDialog(error)
else
@installApmCommand true, (error) ->
@installApmCommand true, (error) =>
if error?
showErrorDialog(error)
else
atom.confirm
@applicationDelegate.confirm
message: "Commands installed."
detailedMessage: "The shell commands `atom` and `apm` are installed."

View File

@@ -44,15 +44,23 @@ SequenceCount = 0
# ```
module.exports =
class CommandRegistry
constructor: (@rootNode) ->
constructor: ->
@rootNode = null
@clear()
clear: ->
@registeredCommands = {}
@selectorBasedListenersByCommandName = {}
@inlineListenersByCommandName = {}
@emitter = new Emitter
attach: (@rootNode) ->
@commandRegistered(command) for command of @selectorBasedListenersByCommandName
@commandRegistered(command) for command of @inlineListenersByCommandName
destroy: ->
for commandName of @registeredCommands
window.removeEventListener(commandName, @handleCommandEvent, true)
@rootNode.removeEventListener(commandName, @handleCommandEvent, true)
return
# Public: Add one or more command listeners associated with a selector.
@@ -253,8 +261,8 @@ class CommandRegistry
matched
commandRegistered: (commandName) ->
unless @registeredCommands[commandName]
window.addEventListener(commandName, @handleCommandEvent, true)
if @rootNode? and not @registeredCommands[commandName]
@rootNode.addEventListener(commandName, @handleCommandEvent, true)
@registeredCommands[commandName] = true
class SelectorBasedListener

View File

@@ -158,6 +158,27 @@ require('source-map-support').install({
}
})
var sourceMapPrepareStackTrace = Error.prepareStackTrace
var prepareStackTrace = sourceMapPrepareStackTrace
// Prevent coffee-script from reassigning Error.prepareStackTrace
Object.defineProperty(Error, 'prepareStackTrace', {
get: function () { return prepareStackTrace },
set: function (newValue) {}
})
// Enable Grim to access the raw stack without reassigning Error.prepareStackTrace
Error.prototype.getRawStack = function () { // eslint-disable-line no-extend-native
prepareStackTrace = getRawStack
var result = this.stack
prepareStackTrace = sourceMapPrepareStackTrace
return result
}
function getRawStack (_, stack) {
return stack
}
Object.keys(COMPILERS).forEach(function (extension) {
var compiler = COMPILERS[extension]

View File

@@ -12,24 +12,27 @@ module.exports =
default: [".git", ".hg", ".svn", ".DS_Store", "._*", "Thumbs.db"]
items:
type: 'string'
description: 'List of string glob patterns. Files and directories matching these patterns will be ignored by some packages, such as the fuzzy finder and tree view. Individual packages might have additional config settings for ignoring names.'
excludeVcsIgnoredPaths:
type: 'boolean'
default: true
title: 'Exclude VCS Ignored Paths'
description: 'Files and directories ignored by the current project\'s VCS system will be ignored by some packages, such as the fuzzy finder and find and replace. For example, projects using Git have these paths defined in the .gitignore file. Individual packages might have additional config settings for ignoring VCS ignored files and folders.'
followSymlinks:
type: 'boolean'
default: true
title: 'Follow symlinks'
description: 'Used when searching and when opening files with the fuzzy finder.'
description: 'Follow symbolic links when searching files and when opening files with the fuzzy finder.'
disabledPackages:
type: 'array'
default: []
items:
type: 'string'
description: 'List of names of installed packages which are not loaded at startup.'
customFileTypes:
type: 'object'
default: {}
description: 'Associates scope names (e.g. "source.js") with arrays of file extensions and file names (e.g. ["Somefile", ".js2"])'
description: 'Associates scope names (e.g. `"source.js"`) with arrays of file extensions and file names (e.g. `["Somefile", ".js2"]`)'
additionalProperties:
type: 'array'
items:
@@ -39,15 +42,19 @@ module.exports =
default: ['one-dark-ui', 'one-dark-syntax']
items:
type: 'string'
description: 'Names of UI and syntax themes which will be used when Atom starts.'
projectHome:
type: 'string'
default: path.join(fs.getHomeDirectory(), 'github')
description: 'The directory where projects are assumed to be located. Packages created using the Package Generator will be stored here by default.'
audioBeep:
type: 'boolean'
default: true
description: 'Trigger the system\'s beep sound when certain actions cannot be executed or there are no results.'
destroyEmptyPanes:
type: 'boolean'
default: true
description: 'When the last item of a pane is removed, remove that pane as well.'
fileEncoding:
description: 'Default character set encoding to use when reading and writing files.'
type: 'string'
@@ -90,7 +97,7 @@ module.exports =
'windows866'
]
openEmptyEditorOnStart:
description: 'Automatically opens an empty editor when atom starts.'
description: 'Automatically open an empty editor on startup.'
type: 'boolean'
default: true
@@ -113,41 +120,51 @@ module.exports =
fontFamily:
type: 'string'
default: ''
description: 'The name of the font family used for editor text.'
fontSize:
type: 'integer'
default: 14
minimum: 1
maximum: 100
description: 'Height in pixels of editor text.'
lineHeight:
type: ['string', 'number']
default: 1.5
description: 'Height of editor lines, as a multiplier of font size.'
showInvisibles:
type: 'boolean'
default: false
description: 'Render placeholders for invisible characters, such as tabs, spaces and newlines.'
showIndentGuide:
type: 'boolean'
default: false
description: 'Show indentation indicators in the editor.'
showLineNumbers:
type: 'boolean'
default: true
description: 'Show line numbers in the editor\'s gutter.'
autoIndent:
type: 'boolean'
default: true
description: 'Automatically indent the cursor when inserting a newline'
description: 'Automatically indent the cursor when inserting a newline.'
autoIndentOnPaste:
type: 'boolean'
default: true
description: 'Automatically indent pasted text based on the indentation of the previous line.'
nonWordCharacters:
type: 'string'
default: "/\\()\"':,.;<>~!@#$%^&*|+=[]{}`?-…"
description: 'A string of non-word characters to define word boundaries.'
preferredLineLength:
type: 'integer'
default: 80
minimum: 1
description: 'Identifies the length of a line which is used when wrapping text with the `Soft Wrap At Preferred Line Length` setting enabled, in number of characters.'
tabLength:
type: 'integer'
default: 2
enum: [1, 2, 3, 4, 6, 8]
description: 'Number of spaces used to represent a tab.'
softWrap:
type: 'boolean'
default: false
@@ -155,32 +172,36 @@ module.exports =
softTabs:
type: 'boolean'
default: true
description: 'If the `Tab Type` config setting is set to "auto" and autodetection of tab type from buffer content fails, then this config setting determines whether a soft tab or a hard tab will be inserted when the Tab key is pressed.'
tabType:
type: 'string'
default: 'auto'
enum: ['auto', 'soft', 'hard']
description: 'Determine character inserted during Tab keypress.'
description: 'Determine character inserted when Tab key is pressed. Possible values: "auto", "soft" and "hard". When set to "soft" or "hard", soft tabs (spaces) or hard tabs (tab characters) are used. When set to "auto", the editor auto-detects the tab type based on the contents of the buffer (it uses the first leading whitespace on a non-comment line), or uses the value of the Soft Tabs config setting if auto-detection fails.'
softWrapAtPreferredLineLength:
type: 'boolean'
default: false
description: 'Will wrap to the number of characters defined by the `Preferred Line Length` setting. This will only take effect when soft wrap is enabled globally or for the current language.'
description: 'Instead of wrapping lines to the window\'s width, wrap lines to the number of characters defined by the `Preferred Line Length` setting. This will only take effect when the soft wrap config setting is enabled globally or for the current language.'
softWrapHangingIndent:
type: 'integer'
default: 0
minimum: 0
description: 'When soft wrap is enabled, defines length of additional indentation applied to wrapped lines, in number of characters.'
scrollSensitivity:
type: 'integer'
default: 40
minimum: 10
maximum: 200
description: 'Determines how fast the editor scrolls when using a mouse or trackpad.'
scrollPastEnd:
type: 'boolean'
default: false
description: 'Allow the editor to be scrolled past the end of the last line.'
undoGroupingInterval:
type: 'integer'
default: 300
minimum: 0
description: 'Time interval in milliseconds within which operations will be grouped together in the undo history'
description: 'Time interval in milliseconds within which text editing operations will be grouped together in the undo history.'
useShadowDOM:
type: 'boolean'
default: true
@@ -190,33 +211,39 @@ module.exports =
type: 'boolean'
default: true
title: 'Confirm Checkout HEAD Revision'
description: 'Show confirmation dialog when checking out the HEAD revision and discarding changes to current file since last commit.'
backUpBeforeSaving:
type: 'boolean'
default: false
description: 'Ensure file contents aren\'t lost if there is an I/O error during save by making a temporary backup copy.'
invisibles:
type: 'object'
description: 'A hash of characters Atom will use to render whitespace characters. Keys are whitespace character types, values are rendered characters (use value false to turn off individual whitespace character types).'
properties:
eol:
type: ['boolean', 'string']
default: '\u00ac'
maximumLength: 1
description: 'Character used to render newline characters (\\n) when the `Show Invisibles` setting is enabled. '
space:
type: ['boolean', 'string']
default: '\u00b7'
maximumLength: 1
description: 'Character used to render leading and trailing space characters when the `Show Invisibles` setting is enabled.'
tab:
type: ['boolean', 'string']
default: '\u00bb'
maximumLength: 1
description: 'Character used to render hard tab characters (\\t) when the `Show Invisibles` setting is enabled.'
cr:
type: ['boolean', 'string']
default: '\u00a4'
maximumLength: 1
description: 'Character used to render carriage return characters (for Microsoft-style line endings) when the `Show Invisibles` setting is enabled.'
zoomFontWhenCtrlScrolling:
type: 'boolean'
default: process.platform isnt 'darwin'
description: 'Increase/decrease the editor font size when pressing the Ctrl key and scrolling the mouse up/down.'
description: 'Change the editor font size when pressing the Ctrl key and scrolling the mouse up/down.'
if process.platform in ['win32', 'linux']
module.exports.core.properties.autoHideMenuBar =

View File

@@ -5,7 +5,10 @@ CSON = require 'season'
path = require 'path'
async = require 'async'
pathWatcher = require 'pathwatcher'
{pushKeyPath, splitKeyPath, getValueAtKeyPath, setValueAtKeyPath} = require 'key-path-helpers'
{
getValueAtKeyPath, setValueAtKeyPath, deleteValueAtKeyPath,
pushKeyPath, splitKeyPath,
} = require 'key-path-helpers'
Color = require './color'
ScopedPropertyStore = require 'scoped-property-store'
@@ -331,7 +334,13 @@ class Config
value
# Created during initialization, available as `atom.config`
constructor: ({@configDirPath, @resourcePath}={}) ->
constructor: ({@configDirPath, @resourcePath, @notificationManager, @enablePersistence}={}) ->
if @enablePersistence?
@configFilePath = fs.resolve(@configDirPath, 'config', ['json', 'cson'])
@configFilePath ?= path.join(@configDirPath, 'config.cson')
@clear()
clear: ->
@emitter = new Emitter
@schema =
type: 'object'
@@ -340,11 +349,8 @@ class Config
@settings = {}
@scopedSettingsStore = new ScopedPropertyStore
@configFileHasErrors = false
@configFilePath = fs.resolve(@configDirPath, 'config', ['json', 'cson'])
@configFilePath ?= path.join(@configDirPath, 'config.cson')
@transactDepth = 0
@savePending = false
@requestLoad = _.debounce(@loadUserConfig, 100)
@requestSave = =>
@savePending = true
@@ -354,6 +360,8 @@ class Config
@save()
debouncedSave = _.debounce(save, 100)
shouldNotAccessFileSystem: -> not @enablePersistence
###
Section: Config Subscription
###
@@ -414,7 +422,6 @@ class Config
# * `event` {Object}
# * `newValue` the new value of the key
# * `oldValue` the prior value of the key.
# * `keyPath` the keyPath of the changed key
#
# Returns a {Disposable} with the following keys on which you can call
# `.dispose()` to unsubscribe.
@@ -724,7 +731,7 @@ class Config
###
initializeConfigDirectory: (done) ->
return if fs.existsSync(@configDirPath)
return if fs.existsSync(@configDirPath) or @shouldNotAccessFileSystem()
fs.makeTreeSync(@configDirPath)
@@ -740,6 +747,8 @@ class Config
fs.traverseTree(templateConfigDirPath, onConfigDirFile, (path) -> true)
loadUserConfig: ->
return if @shouldNotAccessFileSystem()
unless fs.existsSync(@configFilePath)
fs.makeTreeSync(path.dirname(@configFilePath))
CSON.writeFileSync(@configFilePath, {})
@@ -763,6 +772,8 @@ class Config
@notifyFailure(message, detail)
observeUserConfig: ->
return if @shouldNotAccessFileSystem()
try
@watchSubscription ?= pathWatcher.watch @configFilePath, (eventType) =>
@requestLoad() if eventType is 'change' and @watchSubscription?
@@ -779,9 +790,11 @@ class Config
@watchSubscription = null
notifyFailure: (errorMessage, detail) ->
atom.notifications.addError(errorMessage, {detail, dismissable: true})
@notificationManager.addError(errorMessage, {detail, dismissable: true})
save: ->
return if @shouldNotAccessFileSystem()
allSettings = {'*': @settings}
allSettings = _.extend allSettings, @scopedSettingsStore.propertiesForSource(@getUserConfigPath())
try
@@ -832,12 +845,16 @@ class Config
setRawValue: (keyPath, value) ->
defaultValue = getValueAtKeyPath(@defaultSettings, keyPath)
value = undefined if _.isEqual(defaultValue, value)
if keyPath?
setValueAtKeyPath(@settings, keyPath, value)
if _.isEqual(defaultValue, value)
if keyPath?
deleteValueAtKeyPath(@settings, keyPath)
else
@settings = null
else
@settings = value
if keyPath?
setValueAtKeyPath(@settings, keyPath, value)
else
@settings = value
@emitChangeEvent()
observeKeyPath: (keyPath, options, callback) ->

View File

@@ -4,6 +4,7 @@ CSON = require 'season'
fs = require 'fs-plus'
{calculateSpecificity, validateSelector} = require 'clear-cut'
{Disposable} = require 'event-kit'
remote = require 'remote'
MenuHelpers = require './menu-helpers'
platformContextMenu = require('../package.json')?._atomMenu?['context-menu']
@@ -40,11 +41,11 @@ platformContextMenu = require('../package.json')?._atomMenu?['context-menu']
# {::add} for more information.
module.exports =
class ContextMenuManager
constructor: ({@resourcePath, @devMode}) ->
constructor: ({@resourcePath, @devMode, @keymapManager}) ->
@definitions = {'.overlayer': []} # TODO: Remove once color picker package stops touching private data
@clear()
atom.keymaps.onDidLoadBundledKeymaps => @loadPlatformItems()
@keymapManager.onDidLoadBundledKeymaps => @loadPlatformItems()
loadPlatformItems: ->
if platformContextMenu?
@@ -175,7 +176,7 @@ class ContextMenuManager
menuTemplate = @templateForEvent(event)
return unless menuTemplate?.length > 0
atom.getCurrentWindow().emit('context-menu', menuTemplate)
remote.getCurrentWindow().emit('context-menu', menuTemplate)
return
clear: ->

View File

@@ -16,7 +16,7 @@ class Cursor extends Model
visible: true
# Instantiated by a {TextEditor}
constructor: ({@editor, @marker, id}) ->
constructor: ({@editor, @marker, @config, id}) ->
@emitter = new Emitter
@assignId(id)
@@ -75,7 +75,7 @@ class Cursor extends Model
@changePosition options, =>
@marker.setHeadScreenPosition(screenPosition, options)
# Public: Returns the screen position of the cursor as an Array.
# Public: Returns the screen position of the cursor as a {Point}.
getScreenPosition: ->
@marker.getHeadScreenPosition()
@@ -158,7 +158,7 @@ class Cursor extends Model
[before, after] = @editor.getTextInBufferRange(range)
return false if /\s/.test(before) or /\s/.test(after)
nonWordCharacters = atom.config.get('editor.nonWordCharacters', scope: @getScopeDescriptor()).split('')
nonWordCharacters = @config.get('editor.nonWordCharacters', scope: @getScopeDescriptor()).split('')
_.contains(nonWordCharacters, before) isnt _.contains(nonWordCharacters, after)
# Public: Returns whether this cursor is between a word's start and end.
@@ -605,7 +605,7 @@ class Cursor extends Model
# Returns a {RegExp}.
wordRegExp: ({includeNonWordCharacters}={}) ->
includeNonWordCharacters ?= true
nonWordCharacters = atom.config.get('editor.nonWordCharacters', scope: @getScopeDescriptor())
nonWordCharacters = @config.get('editor.nonWordCharacters', scope: @getScopeDescriptor())
segments = ["^[\t ]*$"]
segments.push("[^\\s#{_.escapeRegExp(nonWordCharacters)}]+")
if includeNonWordCharacters
@@ -620,7 +620,7 @@ class Cursor extends Model
#
# Returns a {RegExp}.
subwordRegExp: (options={}) ->
nonWordCharacters = atom.config.get('editor.nonWordCharacters', scope: @getScopeDescriptor())
nonWordCharacters = @config.get('editor.nonWordCharacters', scope: @getScopeDescriptor())
lowercaseLetters = 'a-z\\u00DF-\\u00F6\\u00F8-\\u00FF'
uppercaseLetters = 'A-Z\\u00C0-\\u00D6\\u00D8-\\u00DE'
snakeCamelSegment = "[#{uppercaseLetters}]?[#{lowercaseLetters}]+"

View File

@@ -6,12 +6,12 @@
module.exports =
class CustomGutterComponent
constructor: ({@gutter}) ->
constructor: ({@gutter, @views}) ->
@decorationNodesById = {}
@decorationItemsById = {}
@visible = true
@domNode = atom.views.getView(@gutter)
@domNode = @views.getView(@gutter)
@decorationsNode = @domNode.firstChild
# Clear the contents in case the domNode is being reused.
@decorationsNode.innerHTML = ''

View File

@@ -21,7 +21,7 @@
# ```
module.exports =
class DeserializerManager
constructor: ->
constructor: (@atomEnvironment) ->
@deserializers = {}
# Public: Register the given class(es) as deserializers.
@@ -29,7 +29,10 @@ class DeserializerManager
# * `deserializers` One or more deserializers to register. A deserializer can
# be any object with a `.name` property and a `.deserialize()` method. A
# common approach is to register a *constructor* as the deserializer for its
# instances by adding a `.deserialize()` class method.
# instances by adding a `.deserialize()` class method. When your method is
# called, it will be passed serialized state as the first argument and the
# {Atom} environment object as the second argument, which is useful if you
# wish to avoid referencing the `atom` global.
add: (deserializers...) ->
@deserializers[deserializer.name] = deserializer for deserializer in deserializers
new Disposable =>
@@ -39,15 +42,13 @@ class DeserializerManager
# Public: Deserialize the state and params.
#
# * `state` The state {Object} to deserialize.
# * `params` The params {Object} to pass as the second arguments to the
# deserialize method of the deserializer.
deserialize: (state, params) ->
deserialize: (state) ->
return unless state?
if deserializer = @get(state)
stateVersion = state.get?('version') ? state.version
return if deserializer.version? and deserializer.version isnt stateVersion
deserializer.deserialize(state, params)
deserializer.deserialize(state, @atomEnvironment)
else
console.warn "No deserializer found for", state
@@ -59,3 +60,6 @@ class DeserializerManager
name = state.get?('deserializer') ? state.deserializer
@deserializers[name]
clear: ->
@deserializers = {}

View File

@@ -26,17 +26,29 @@ class DisplayBuffer extends Model
height: null
width: null
@deserialize: (state) ->
state.tokenizedBuffer = TokenizedBuffer.deserialize(state.tokenizedBuffer)
@deserialize: (state, atomEnvironment) ->
state.tokenizedBuffer = TokenizedBuffer.deserialize(state.tokenizedBuffer, atomEnvironment)
state.config = atomEnvironment.config
state.assert = atomEnvironment.assert
state.grammarRegistry = atomEnvironment.grammars
state.packageManager = atomEnvironment.packages
new this(state)
constructor: ({tabLength, @editorWidthInChars, @tokenizedBuffer, buffer, ignoreInvisibles, @largeFileMode}={}) ->
constructor: (params={}) ->
super
{
tabLength, @editorWidthInChars, @tokenizedBuffer, buffer, ignoreInvisibles,
@largeFileMode, @config, @assert, @grammarRegistry, @packageManager
} = params
@emitter = new Emitter
@disposables = new CompositeDisposable
@tokenizedBuffer ?= new TokenizedBuffer({tabLength, buffer, ignoreInvisibles, @largeFileMode})
@tokenizedBuffer ?= new TokenizedBuffer({
tabLength, buffer, ignoreInvisibles, @largeFileMode, @config,
@grammarRegistry, @packageManager, @assert
})
@buffer = @tokenizedBuffer.buffer
@charWidthsByScope = {}
@markers = {}
@@ -61,29 +73,29 @@ class DisplayBuffer extends Model
oldConfigSettings = @configSettings
@configSettings =
scrollPastEnd: atom.config.get('editor.scrollPastEnd', scope: scopeDescriptor)
softWrap: atom.config.get('editor.softWrap', scope: scopeDescriptor)
softWrapAtPreferredLineLength: atom.config.get('editor.softWrapAtPreferredLineLength', scope: scopeDescriptor)
softWrapHangingIndent: atom.config.get('editor.softWrapHangingIndent', scope: scopeDescriptor)
preferredLineLength: atom.config.get('editor.preferredLineLength', scope: scopeDescriptor)
scrollPastEnd: @config.get('editor.scrollPastEnd', scope: scopeDescriptor)
softWrap: @config.get('editor.softWrap', scope: scopeDescriptor)
softWrapAtPreferredLineLength: @config.get('editor.softWrapAtPreferredLineLength', scope: scopeDescriptor)
softWrapHangingIndent: @config.get('editor.softWrapHangingIndent', scope: scopeDescriptor)
preferredLineLength: @config.get('editor.preferredLineLength', scope: scopeDescriptor)
subscriptions.add atom.config.onDidChange 'editor.softWrap', scope: scopeDescriptor, ({newValue}) =>
subscriptions.add @config.onDidChange 'editor.softWrap', scope: scopeDescriptor, ({newValue}) =>
@configSettings.softWrap = newValue
@updateWrappedScreenLines()
subscriptions.add atom.config.onDidChange 'editor.softWrapHangingIndent', scope: scopeDescriptor, ({newValue}) =>
subscriptions.add @config.onDidChange 'editor.softWrapHangingIndent', scope: scopeDescriptor, ({newValue}) =>
@configSettings.softWrapHangingIndent = newValue
@updateWrappedScreenLines()
subscriptions.add atom.config.onDidChange 'editor.softWrapAtPreferredLineLength', scope: scopeDescriptor, ({newValue}) =>
subscriptions.add @config.onDidChange 'editor.softWrapAtPreferredLineLength', scope: scopeDescriptor, ({newValue}) =>
@configSettings.softWrapAtPreferredLineLength = newValue
@updateWrappedScreenLines() if @isSoftWrapped()
subscriptions.add atom.config.onDidChange 'editor.preferredLineLength', scope: scopeDescriptor, ({newValue}) =>
subscriptions.add @config.onDidChange 'editor.preferredLineLength', scope: scopeDescriptor, ({newValue}) =>
@configSettings.preferredLineLength = newValue
@updateWrappedScreenLines() if @isSoftWrapped() and atom.config.get('editor.softWrapAtPreferredLineLength', scope: scopeDescriptor)
@updateWrappedScreenLines() if @isSoftWrapped() and @config.get('editor.softWrapAtPreferredLineLength', scope: scopeDescriptor)
subscriptions.add atom.config.observe 'editor.scrollPastEnd', scope: scopeDescriptor, (value) =>
subscriptions.add @config.observe 'editor.scrollPastEnd', scope: scopeDescriptor, (value) =>
@configSettings.scrollPastEnd = value
@updateWrappedScreenLines() if oldConfigSettings? and not _.isEqual(oldConfigSettings, @configSettings)
@@ -97,7 +109,10 @@ class DisplayBuffer extends Model
largeFileMode: @largeFileMode
copy: ->
newDisplayBuffer = new DisplayBuffer({@buffer, tabLength: @getTabLength(), @largeFileMode})
newDisplayBuffer = new DisplayBuffer({
@buffer, tabLength: @getTabLength(), @largeFileMode, @config, @assert,
@grammarRegistry, @packageManager
})
for marker in @findMarkers(displayBufferId: @id)
marker.copy(displayBufferId: newDisplayBuffer.id)
@@ -189,10 +204,24 @@ class DisplayBuffer extends Model
getLineHeightInPixels: -> @lineHeightInPixels
setLineHeightInPixels: (@lineHeightInPixels) -> @lineHeightInPixels
getKoreanCharWidth: -> @koreanCharWidth
getHalfWidthCharWidth: -> @halfWidthCharWidth
getDoubleWidthCharWidth: -> @doubleWidthCharWidth
getDefaultCharWidth: -> @defaultCharWidth
setDefaultCharWidth: (defaultCharWidth) ->
if defaultCharWidth isnt @defaultCharWidth
setDefaultCharWidth: (defaultCharWidth, doubleWidthCharWidth, halfWidthCharWidth, koreanCharWidth) ->
doubleWidthCharWidth ?= defaultCharWidth
halfWidthCharWidth ?= defaultCharWidth
koreanCharWidth ?= defaultCharWidth
if defaultCharWidth isnt @defaultCharWidth or doubleWidthCharWidth isnt @doubleWidthCharWidth and halfWidthCharWidth isnt @halfWidthCharWidth and koreanCharWidth isnt @koreanCharWidth
@defaultCharWidth = defaultCharWidth
@doubleWidthCharWidth = doubleWidthCharWidth
@halfWidthCharWidth = halfWidthCharWidth
@koreanCharWidth = koreanCharWidth
@updateWrappedScreenLines() if @isSoftWrapped() and @getEditorWidthInChars()?
defaultCharWidth
getCursorWidth: -> 1
@@ -262,6 +291,40 @@ class DisplayBuffer extends Model
else
@getEditorWidthInChars()
getSoftWrapColumnForTokenizedLine: (tokenizedLine) ->
lineMaxWidth = @getSoftWrapColumn() * @getDefaultCharWidth()
return if Number.isNaN(lineMaxWidth)
return 0 if lineMaxWidth is 0
iterator = tokenizedLine.getTokenIterator(false)
column = 0
currentWidth = 0
while iterator.next()
textIndex = 0
text = iterator.getText()
while textIndex < text.length
if iterator.isPairedCharacter()
charLength = 2
else
charLength = 1
if iterator.hasDoubleWidthCharacterAt(textIndex)
charWidth = @getDoubleWidthCharWidth()
else if iterator.hasHalfWidthCharacterAt(textIndex)
charWidth = @getHalfWidthCharWidth()
else if iterator.hasKoreanCharacterAt(textIndex)
charWidth = @getKoreanCharWidth()
else
charWidth = @getDefaultCharWidth()
return column if currentWidth + charWidth > lineMaxWidth
currentWidth += charWidth
column += charLength
textIndex += charLength
column
# Gets the screen line for the given screen row.
#
# * `screenRow` - A {Number} indicating the screen row.
@@ -958,7 +1021,7 @@ class DisplayBuffer extends Model
else
softWraps = 0
if @isSoftWrapped()
while wrapScreenColumn = tokenizedLine.findWrapColumn(@getSoftWrapColumn())
while wrapScreenColumn = tokenizedLine.findWrapColumn(@getSoftWrapColumnForTokenizedLine(tokenizedLine))
[wrappedLine, tokenizedLine] = tokenizedLine.softWrapAt(
wrapScreenColumn,
@configSettings.softWrapHangingIndent
@@ -1036,8 +1099,8 @@ class DisplayBuffer extends Model
tokenizedLinesCount = @tokenizedBuffer.getLineCount()
bufferLinesCount = @buffer.getLineCount()
atom.assert screenLinesCount is tokenizedLinesCount, "Display buffer line count out of sync with tokenized buffer", (error) ->
@assert screenLinesCount is tokenizedLinesCount, "Display buffer line count out of sync with tokenized buffer", (error) ->
error.metadata = {screenLinesCount, tokenizedLinesCount, bufferLinesCount}
atom.assert screenLinesCount is bufferLinesCount, "Display buffer line count out of sync with buffer", (error) ->
@assert screenLinesCount is bufferLinesCount, "Display buffer line count out of sync with buffer", (error) ->
error.metadata = {screenLinesCount, tokenizedLinesCount, bufferLinesCount}

View File

@@ -48,7 +48,7 @@ isValidGitDirectorySync = (directory) ->
module.exports =
class GitRepositoryProvider
constructor: (@project) ->
constructor: (@project, @config) ->
# Keys are real paths that end in `.git`.
# Values are the corresponding GitRepository objects.
@pathToRepository = {}
@@ -75,7 +75,7 @@ class GitRepositoryProvider
gitDirPath = gitDir.getPath()
repo = @pathToRepository[gitDirPath]
unless repo
repo = GitRepository.open(gitDirPath, project: @project)
repo = GitRepository.open(gitDirPath, {@project, @config})
return null unless repo
repo.onDidDestroy(=> delete @pathToRepository[gitDirPath])
@pathToRepository[gitDirPath] = repo

View File

@@ -80,7 +80,7 @@ class GitRepository
for submodulePath, submoduleRepo of @repo.submodules
submoduleRepo.upstream = {ahead: 0, behind: 0}
{@project, refreshOnWindowFocus} = options
{@project, @config, refreshOnWindowFocus} = options
refreshOnWindowFocus ?= true
if refreshOnWindowFocus
@@ -443,25 +443,10 @@ class GitRepository
# Subscribes to editor view event.
checkoutHeadForEditor: (editor) ->
filePath = editor.getPath()
return unless filePath
fileName = basename(filePath)
checkoutHead = =>
if filePath = editor.getPath()
editor.buffer.reload() if editor.buffer.isModified()
@checkoutHead(filePath)
if atom.config.get('editor.confirmCheckoutHeadRevision')
atom.confirm
message: 'Confirm Checkout HEAD Revision'
detailedMessage: "Are you sure you want to discard all changes to \"#{fileName}\" since the last Git commit?"
buttons:
OK: checkoutHead
Cancel: null
else
checkoutHead()
# Returns the corresponding {Repository}
getRepo: (path) ->
if @repo?

View File

@@ -14,19 +14,9 @@ PathSplitRegex = new RegExp("[/.]")
# language-specific comment regexes. See {::getProperty} for more details.
module.exports =
class GrammarRegistry extends FirstMate.GrammarRegistry
@deserialize: ({grammarOverridesByPath}) ->
grammarRegistry = new GrammarRegistry()
grammarRegistry.grammarOverridesByPath = grammarOverridesByPath
grammarRegistry
atom.deserializers.add(this)
constructor: ->
constructor: ({@config}={}) ->
super(maxTokensPerLine: 100)
serialize: ->
{deserializer: @constructor.name, @grammarOverridesByPath}
createToken: (value, scopes) -> new Token({value, scopes})
# Extended: Select a grammar for the given file path and file contents.
@@ -70,7 +60,7 @@ class GrammarRegistry extends FirstMate.GrammarRegistry
pathScore = -1
fileTypes = grammar.fileTypes
if customFileTypes = atom.config.get('core.customFileTypes')?[grammar.scopeName]
if customFileTypes = @config.get('core.customFileTypes')?[grammar.scopeName]
fileTypes = fileTypes.concat(customFileTypes)
for fileType, i in fileTypes
@@ -133,6 +123,3 @@ class GrammarRegistry extends FirstMate.GrammarRegistry
clearGrammarOverrides: ->
@grammarOverridesByPath = {}
undefined
clearObservers: ->
@emitter = new Emitter

View File

@@ -7,7 +7,7 @@ LineNumberGutterComponent = require './line-number-gutter-component'
module.exports =
class GutterContainerComponent
constructor: ({@onLineNumberGutterMouseDown, @editor, @domElementPool}) ->
constructor: ({@onLineNumberGutterMouseDown, @editor, @domElementPool, @views}) ->
# An array of objects of the form: {name: {String}, component: {Object}}
@gutterComponents = []
@gutterComponentsByGutterName = {}
@@ -39,10 +39,10 @@ class GutterContainerComponent
gutterComponent = @gutterComponentsByGutterName[gutter.name]
if not gutterComponent
if gutter.name is 'line-number'
gutterComponent = new LineNumberGutterComponent({onMouseDown: @onLineNumberGutterMouseDown, @editor, gutter, @domElementPool})
gutterComponent = new LineNumberGutterComponent({onMouseDown: @onLineNumberGutterMouseDown, @editor, gutter, @domElementPool, @views})
@lineNumberGutterComponent = gutterComponent
else
gutterComponent = new CustomGutterComponent({gutter})
gutterComponent = new CustomGutterComponent({gutter, @views})
if visible then gutterComponent.showNode() else gutterComponent.hideNode()
# Pass the gutter only the state that it needs.

View File

@@ -0,0 +1,34 @@
# Like sands through the hourglass, so are the days of our lives.
path = require 'path'
require './window'
{getWindowLoadSettings} = require './window-load-settings-helpers'
{resourcePath, isSpec, devMode} = getWindowLoadSettings()
# Add application-specific exports to module search path.
exportsPath = path.join(resourcePath, 'exports')
require('module').globalPaths.push(exportsPath)
process.env.NODE_PATH = exportsPath
# Make React faster
process.env.NODE_ENV ?= 'production' unless devMode
AtomEnvironment = require './atom-environment'
ApplicationDelegate = require './application-delegate'
window.atom = new AtomEnvironment({
window, document,
applicationDelegate: new ApplicationDelegate,
configDirPath: process.env.ATOM_HOME
enablePersistence: true
})
atom.displayWindow()
atom.loadStateSync()
atom.startEditorWindow()
# Workaround for focus getting cleared upon window creation
windowFocused = ->
window.removeEventListener('focus', windowFocused)
setTimeout (-> document.querySelector('atom-workspace').focus()), 0
window.addEventListener('focus', windowFocused)

View File

@@ -0,0 +1,69 @@
# Start the crash reporter before anything else.
require('crash-reporter').start(productName: 'Atom', companyName: 'GitHub')
remote = require 'remote'
exitWithStatusCode = (status) ->
remote.require('app').emit('will-quit')
remote.process.exit(status)
try
path = require 'path'
ipc = require 'ipc'
{getWindowLoadSettings} = require './window-load-settings-helpers'
AtomEnvironment = require '../src/atom-environment'
ApplicationDelegate = require '../src/application-delegate'
{testRunnerPath, legacyTestRunnerPath, headless, logFile, testPaths} = getWindowLoadSettings()
if headless
# Override logging in headless mode so it goes to the console, regardless
# of the --enable-logging flag to Electron.
console.log = (args...) ->
ipc.send 'write-to-stdout', args.join(' ') + '\n'
console.warn = (args...) ->
ipc.send 'write-to-stderr', args.join(' ') + '\n'
console.error = (args...) ->
ipc.send 'write-to-stderr', args.join(' ') + '\n'
else
# Show window synchronously so a focusout doesn't fire on input elements
# that are focused in the very first spec run.
remote.getCurrentWindow().show()
handleKeydown = (event) ->
# Reload: cmd-r / ctrl-r
if (event.metaKey or event.ctrlKey) and event.keyCode is 82
ipc.send('call-window-method', 'restart')
# Toggle Dev Tools: cmd-alt-i / ctrl-alt-i
if (event.metaKey or event.ctrlKey) and event.altKey and event.keyCode is 73
ipc.send('call-window-method', 'toggleDevTools')
# Reload: cmd-w / ctrl-w
if (event.metaKey or event.ctrlKey) and event.keyCode is 87
ipc.send('call-window-method', 'close')
window.addEventListener('keydown', handleKeydown, true)
# Add 'exports' to module search path.
exportsPath = path.join(getWindowLoadSettings().resourcePath, 'exports')
require('module').globalPaths.push(exportsPath)
process.env.NODE_PATH = exportsPath # Set NODE_PATH env variable since tasks may need it.
document.title = "Spec Suite"
testRunner = require(testRunnerPath)
legacyTestRunner = require(legacyTestRunnerPath)
buildAtomEnvironment = (params) -> new AtomEnvironment(params)
buildDefaultApplicationDelegate = (params) -> new ApplicationDelegate()
promise = testRunner({
logFile, headless, testPaths, buildAtomEnvironment, buildDefaultApplicationDelegate, legacyTestRunner
})
promise.then(exitWithStatusCode) if getWindowLoadSettings().headless
catch error
if getWindowLoadSettings().headless
console.error(error.stack ? error)
exitWithStatusCode(1)
else
throw error

View File

@@ -20,6 +20,8 @@ KeymapManager::loadBundledKeymaps = ->
@emitter.emit 'did-load-bundled-keymaps'
KeymapManager::getUserKeymapPath = ->
return "" unless @configDirPath?
if userKeymapPath = CSON.resolve(path.join(@configDirPath, 'keymap'))
userKeymapPath
else
@@ -41,11 +43,11 @@ KeymapManager::loadUserKeymap = ->
[this document][watches] for more info.
[watches]:https://github.com/atom/atom/blob/master/docs/build-instructions/linux.md#typeerror-unable-to-watch-path
"""
atom.notifications.addError(message, {dismissable: true})
@notificationManager.addError(message, {dismissable: true})
else
detail = error.path
stack = error.stack
atom.notifications.addFatalError(error.message, {detail, stack, dismissable: true})
@notificationManager.addFatalError(error.message, {detail, stack, dismissable: true})
KeymapManager::subscribeToFileReadFailure = ->
@onDidFailToReadFile (error) =>
@@ -57,6 +59,6 @@ KeymapManager::subscribeToFileReadFailure = ->
else
error.message
atom.notifications.addError(message, {detail, dismissable: true})
@notificationManager.addError(message, {detail, dismissable: true})
module.exports = KeymapManager

View File

@@ -8,7 +8,7 @@ class LanguageMode
# Sets up a `LanguageMode` for the given {TextEditor}.
#
# editor - The {TextEditor} to associate with
constructor: (@editor) ->
constructor: (@editor, @config) ->
{@buffer} = @editor
destroy: ->
@@ -327,7 +327,7 @@ class LanguageMode
@editor.setIndentationForBufferRow(bufferRow, desiredIndentLevel)
getRegexForProperty: (scopeDescriptor, property) ->
if pattern = atom.config.get(property, scope: scopeDescriptor)
if pattern = @config.get(property, scope: scopeDescriptor)
new OnigRegExp(pattern)
increaseIndentRegexForScopeDescriptor: (scopeDescriptor) ->
@@ -343,8 +343,8 @@ class LanguageMode
@getRegexForProperty(scopeDescriptor, 'editor.foldEndPattern')
commentStartAndEndStringsForScope: (scope) ->
commentStartEntry = atom.config.getAll('editor.commentStart', {scope})[0]
commentEndEntry = _.find atom.config.getAll('editor.commentEnd', {scope}), (entry) ->
commentStartEntry = @config.getAll('editor.commentStart', {scope})[0]
commentEndEntry = _.find @config.getAll('editor.commentEnd', {scope}), (entry) ->
entry.scopeSelector is commentStartEntry.scopeSelector
commentStartString = commentStartEntry?.value
commentEndString = commentEndEntry?.value

View File

@@ -7,12 +7,12 @@ module.exports =
class LineNumberGutterComponent extends TiledComponent
dummyLineNumberNode: null
constructor: ({@onMouseDown, @editor, @gutter, @domElementPool}) ->
constructor: ({@onMouseDown, @editor, @gutter, @domElementPool, @views}) ->
@visible = true
@dummyLineNumberComponent = LineNumbersTileComponent.createDummy(@domElementPool)
@domNode = atom.views.getView(@gutter)
@domNode = @views.getView(@gutter)
@lineNumbersNode = @domNode.firstChild
@lineNumbersNode.innerHTML = ''

View File

@@ -7,13 +7,21 @@ DummyLineNode.className = 'line'
DummyLineNode.style.position = 'absolute'
DummyLineNode.style.visibility = 'hidden'
DummyLineNode.appendChild(document.createElement('span'))
DummyLineNode.firstChild.textContent = 'x'
DummyLineNode.appendChild(document.createElement('span'))
DummyLineNode.appendChild(document.createElement('span'))
DummyLineNode.appendChild(document.createElement('span'))
DummyLineNode.children[0].textContent = 'x'
DummyLineNode.children[1].textContent = ''
DummyLineNode.children[2].textContent = ''
DummyLineNode.children[3].textContent = ''
RangeForMeasurement = document.createRange()
module.exports =
class LinesComponent extends TiledComponent
placeholderTextDiv: null
constructor: ({@presenter, @useShadowDOM, @domElementPool}) ->
constructor: ({@presenter, @useShadowDOM, @domElementPool, @assert, @grammars}) ->
@domNode = document.createElement('div')
@domNode.classList.add('lines')
@tilesNode = document.createElement("div")
@@ -64,7 +72,7 @@ class LinesComponent extends TiledComponent
@oldState.indentGuidesVisible = @newState.indentGuidesVisible
buildComponentForTile: (id) -> new LinesTileComponent({id, @presenter, @domElementPool})
buildComponentForTile: (id) -> new LinesTileComponent({id, @presenter, @domElementPool, @assert, @grammars})
buildEmptyState: ->
{tiles: {}}
@@ -76,12 +84,18 @@ class LinesComponent extends TiledComponent
measureLineHeightAndDefaultCharWidth: ->
@domNode.appendChild(DummyLineNode)
textNode = DummyLineNode.firstChild.childNodes[0]
lineHeightInPixels = DummyLineNode.getBoundingClientRect().height
charWidth = DummyLineNode.firstChild.getBoundingClientRect().width
defaultCharWidth = DummyLineNode.children[0].getBoundingClientRect().width
doubleWidthCharWidth = DummyLineNode.children[1].getBoundingClientRect().width
halfWidthCharWidth = DummyLineNode.children[2].getBoundingClientRect().width
koreanCharWidth = DummyLineNode.children[3].getBoundingClientRect().width
@domNode.removeChild(DummyLineNode)
@presenter.setLineHeight(lineHeightInPixels)
@presenter.setBaseCharacterWidth(charWidth)
@presenter.setBaseCharacterWidth(defaultCharWidth, doubleWidthCharWidth, halfWidthCharWidth, koreanCharWidth)
lineNodeForLineIdAndScreenRow: (lineId, screenRow) ->
tile = @presenter.tileForRow(screenRow)

View File

@@ -13,8 +13,8 @@ cloneObject = (object) ->
module.exports =
class LinesTileComponent
constructor: ({@presenter, @id, @domElementPool}) ->
@tokenIterator = new TokenIterator
constructor: ({@presenter, @id, @domElementPool, @assert, grammars}) ->
@tokenIterator = new TokenIterator(grammarRegistry: grammars)
@measuredLines = new Set
@lineNodesByLineId = {}
@screenRowsByLineId = {}

View File

@@ -3,8 +3,8 @@ TokenIterator = require './token-iterator'
module.exports =
class LinesYardstick
constructor: (@model, @presenter, @lineNodesProvider) ->
@tokenIterator = new TokenIterator
constructor: (@model, @presenter, @lineNodesProvider, grammarRegistry) ->
@tokenIterator = new TokenIterator({grammarRegistry})
@rangeForMeasurement = document.createRange()
@invalidateCache()
@@ -40,7 +40,7 @@ class LinesYardstick
previousColumn = 0
previousLeft = 0
@tokenIterator.reset(line)
@tokenIterator.reset(line, false)
while @tokenIterator.next()
text = @tokenIterator.getText()
textIndex = 0
@@ -112,7 +112,7 @@ class LinesYardstick
indexWithinTextNode = null
charIndex = 0
@tokenIterator.reset(line)
@tokenIterator.reset(line, false)
while @tokenIterator.next()
break if foundIndexWithinTextNode?
@@ -159,9 +159,12 @@ class LinesYardstick
0
leftPixelPositionForCharInTextNode: (lineNode, textNode, charIndex) ->
@rangeForMeasurement.setStart(textNode, 0)
@rangeForMeasurement.setEnd(textNode, charIndex)
width = @rangeForMeasurement.getBoundingClientRect().width
if charIndex is 0
width = 0
else
@rangeForMeasurement.setStart(textNode, 0)
@rangeForMeasurement.setEnd(textNode, charIndex)
width = @rangeForMeasurement.getBoundingClientRect().width
@rangeForMeasurement.setStart(textNode, 0)
@rangeForMeasurement.setEnd(textNode, textNode.textContent.length)

View File

@@ -59,12 +59,12 @@ platformMenu = require('../package.json')?._atomMenu?.menu
# See {::add} for more info about adding menu's directly.
module.exports =
class MenuManager
constructor: ({@resourcePath}) ->
constructor: ({@resourcePath, @keymapManager, @packageManager}) ->
@pendingUpdateOperation = null
@template = []
atom.keymaps.onDidLoadBundledKeymaps => @loadPlatformItems()
atom.keymaps.onDidReloadKeymap => @update()
atom.packages.onDidActivateInitialPackages => @sortPackagesMenu()
@keymapManager.onDidLoadBundledKeymaps => @loadPlatformItems()
@keymapManager.onDidReloadKeymap => @update()
@packageManager.onDidActivateInitialPackages => @sortPackagesMenu()
# Public: Adds the given items to the application menu.
#
@@ -96,6 +96,10 @@ class MenuManager
@unmerge(@template, item) for item in items
@update()
clear: ->
@template = []
@update()
# Should the binding for the given selector be included in the menu
# commands.
#
@@ -143,7 +147,7 @@ class MenuManager
includedBindings = []
unsetKeystrokes = new Set
for binding in atom.keymaps.getKeyBindings() when @includeSelector(binding.selector)
for binding in @keymapManager.getKeyBindings() when @includeSelector(binding.selector)
includedBindings.push(binding)
if binding.command is 'unset!'
unsetKeystrokes.add(binding.keystrokes)
@@ -191,7 +195,10 @@ class MenuManager
# Get an {Array} of {String} classes for the given element.
classesForElement: (element) ->
element?.classList.toString().split(' ') ? []
if classList = element?.classList
Array::slice.apply(classList)
else
[]
sortPackagesMenu: ->
packagesMenu = _.find @template, ({label}) -> MenuHelpers.normalizeLabel(label) is 'Packages'

View File

@@ -29,40 +29,65 @@ class NotificationManager
# Public: Add a success notification.
#
# * `message` A {String} message
# * `options` An options {Object} with optional keys such as:
# * `detail` A {String} with additional details about the notification
# * `options` (optional) An options {Object} with the following keys:
# * `detail` (optional) A {String} with additional details about the
# notification.
# * `dismissable` (optional) A {Boolean} indicating whether this
# notification can be dismissed by the user. Defaults to `false`.
# * `icon` (optional) A {String} name of an icon from Octicons to display
# in the notification header. Defaults to `'check'`.
addSuccess: (message, options) ->
@addNotification(new Notification('success', message, options))
# Public: Add an informational notification.
#
# * `message` A {String} message
# * `options` An options {Object} with optional keys such as:
# * `detail` A {String} with additional details about the notification
# * `options` (optional) An options {Object} with the following keys:
# * `detail` (optional) A {String} with additional details about the
# notification.
# * `dismissable` (optional) A {Boolean} indicating whether this
# notification can be dismissed by the user. Defaults to `false`.
# * `icon` (optional) A {String} name of an icon from Octicons to display
# in the notification header. Defaults to `'info'`.
addInfo: (message, options) ->
@addNotification(new Notification('info', message, options))
# Public: Add a warning notification.
#
# * `message` A {String} message
# * `options` An options {Object} with optional keys such as:
# * `detail` A {String} with additional details about the notification
# * `options` (optional) An options {Object} with the following keys:
# * `detail` (optional) A {String} with additional details about the
# notification.
# * `dismissable` (optional) A {Boolean} indicating whether this
# notification can be dismissed by the user. Defaults to `false`.
# * `icon` (optional) A {String} name of an icon from Octicons to display
# in the notification header. Defaults to `'alert'`.
addWarning: (message, options) ->
@addNotification(new Notification('warning', message, options))
# Public: Add an error notification.
#
# * `message` A {String} message
# * `options` An options {Object} with optional keys such as:
# * `detail` A {String} with additional details about the notification
# * `options` (optional) An options {Object} with the following keys:
# * `detail` (optional) A {String} with additional details about the
# notification.
# * `dismissable` (optional) A {Boolean} indicating whether this
# notification can be dismissed by the user. Defaults to `false`.
# * `icon` (optional) A {String} name of an icon from Octicons to display
# in the notification header. Defaults to `'flame'`.
addError: (message, options) ->
@addNotification(new Notification('error', message, options))
# Public: Add a fatal error notification.
#
# * `message` A {String} message
# * `options` An options {Object} with optional keys such as:
# * `detail` A {String} with additional details about the notification
# * `options` (optional) An options {Object} with the following keys:
# * `detail` (optional) A {String} with additional details about the
# notification.
# * `dismissable` (optional) A {Boolean} indicating whether this
# notification can be dismissed by the user. Defaults to `false`.
# * `icon` (optional) A {String} name of an icon from Octicons to display
# in the notification header. Defaults to `'bug'`.
addFatalError: (message, options) ->
@addNotification(new Notification('fatal', message, options))

View File

@@ -1,6 +1,6 @@
module.exports =
class OverlayManager
constructor: (@presenter, @container) ->
constructor: (@presenter, @container, @views) ->
@overlaysById = {}
render: (state) ->
@@ -28,7 +28,7 @@ class OverlayManager
@presenter.setOverlayDimensions(decorationId, itemView.offsetWidth, itemView.offsetHeight, contentMargin)
renderOverlay: (state, decorationId, {item, pixelPosition, class: klass}) ->
itemView = atom.views.getView(item)
itemView = @views.getView(item)
cachedOverlay = @overlaysById[decorationId]
unless overlayNode = cachedOverlay?.overlayNode
overlayNode = document.createElement('atom-overlay')

View File

@@ -1,8 +1,10 @@
path = require 'path'
normalizePackageData = null
_ = require 'underscore-plus'
{Emitter} = require 'event-kit'
fs = require 'fs-plus'
CSON = require 'season'
ServiceHub = require 'service-hub'
Package = require './package'
@@ -26,15 +28,21 @@ ThemePackage = require './theme-package'
# settings and also by calling `enablePackage()/disablePackage()`.
module.exports =
class PackageManager
constructor: ({configDirPath, @devMode, safeMode, @resourcePath}) ->
constructor: (params) ->
{
configDirPath, @devMode, safeMode, @resourcePath, @config, @styleManager,
@notificationManager, @keymapManager, @commandRegistry, @grammarRegistry
} = params
@emitter = new Emitter
@activationHookEmitter = new Emitter
@packageDirPaths = []
unless safeMode
if configDirPath? and not safeMode
if @devMode
@packageDirPaths.push(path.join(configDirPath, "dev", "packages"))
@packageDirPaths.push(path.join(configDirPath, "packages"))
@packagesCache = require('../package.json')?._atomPackages ? {}
@loadedPackages = {}
@activePackages = {}
@packageStates = {}
@@ -43,6 +51,17 @@ class PackageManager
@packageActivators = []
@registerPackageActivator(this, ['atom', 'textmate'])
setContextMenuManager: (@contextMenuManager) ->
setMenuManager: (@menuManager) ->
setThemeManager: (@themeManager) ->
reset: ->
@serviceHub.clear()
@deactivatePackages()
@packageStates = {}
###
Section: Event Subscription
###
@@ -185,7 +204,7 @@ class PackageManager
#
# Returns a {Boolean}.
isPackageDisabled: (name) ->
_.include(atom.config.get('core.disabledPackages') ? [], name)
_.include(@config.get('core.disabledPackages') ? [], name)
###
Section: Accessing active packages
@@ -269,7 +288,7 @@ class PackageManager
packages = []
for packagePath in @getAvailablePackagePaths()
name = path.basename(packagePath)
metadata = @getLoadedPackage(name)?.metadata ? Package.loadMetadata(packagePath, true)
metadata = @getLoadedPackage(name)?.metadata ? @loadPackageMetadata(packagePath, true)
packages.push(metadata)
packages
@@ -292,7 +311,7 @@ class PackageManager
@packageDependencies
hasAtomEngine: (packagePath) ->
metadata = Package.loadMetadata(packagePath, true)
metadata = @loadPackageMetadata(packagePath, true)
metadata?.engines?.atom?
unobserveDisabledPackages: ->
@@ -300,7 +319,7 @@ class PackageManager
@disabledPackagesSubscription = null
observeDisabledPackages: ->
@disabledPackagesSubscription ?= atom.config.onDidChange 'core.disabledPackages', ({newValue, oldValue}) =>
@disabledPackagesSubscription ?= @config.onDidChange 'core.disabledPackages', ({newValue, oldValue}) =>
packagesToEnable = _.difference(oldValue, newValue)
packagesToDisable = _.difference(newValue, oldValue)
@@ -313,7 +332,7 @@ class PackageManager
@packagesWithKeymapsDisabledSubscription = null
observePackagesWithKeymapsDisabled: ->
@packagesWithKeymapsDisabledSubscription ?= atom.config.onDidChange 'core.packagesWithKeymapsDisabled', ({newValue, oldValue}) =>
@packagesWithKeymapsDisabledSubscription ?= @config.onDidChange 'core.packagesWithKeymapsDisabled', ({newValue, oldValue}) =>
keymapsToEnable = _.difference(oldValue, newValue)
keymapsToDisable = _.difference(newValue, oldValue)
@@ -340,7 +359,7 @@ class PackageManager
return pack if pack = @getLoadedPackage(name)
try
metadata = Package.loadMetadata(packagePath) ? {}
metadata = @loadPackageMetadata(packagePath) ? {}
catch error
@handleMetadataError(error, packagePath)
return null
@@ -350,10 +369,15 @@ class PackageManager
console.warn "Could not load #{metadata.name}@#{metadata.version} because it uses deprecated APIs that have been removed."
return null
options = {
path: packagePath, metadata, packageManager: this, @config, @styleManager,
@commandRegistry, @keymapManager, @devMode, @notificationManager,
@grammarRegistry, @themeManager, @menuManager, @contextMenuManager
}
if metadata.theme
pack = new ThemePackage(packagePath, metadata)
pack = new ThemePackage(options)
else
pack = new Package(packagePath, metadata)
pack = new Package(options)
pack.load()
@loadedPackages[pack.name] = pack
@emitter.emit 'did-load-package', pack
@@ -392,7 +416,7 @@ class PackageManager
activatePackages: (packages) ->
promises = []
atom.config.transact =>
@config.transact =>
for pack in packages
promise = @activatePackage(pack.name)
promises.push(promise) unless pack.hasActivationCommands()
@@ -423,7 +447,7 @@ class PackageManager
# Deactivate all packages
deactivatePackages: ->
atom.config.transact =>
@config.transact =>
@deactivatePackage(pack.name) for pack in @getLoadedPackages()
return
@unobserveDisabledPackages()
@@ -443,7 +467,7 @@ class PackageManager
detail = "#{error.message} in #{metadataPath}"
stack = "#{error.stack}\n at #{metadataPath}:1:1"
message = "Failed to load the #{path.basename(packagePath)} package"
atom.notifications.addError(message, {stack, detail, dismissable: true})
@notificationManager.addError(message, {stack, detail, dismissable: true})
uninstallDirectory: (directory) ->
symlinkPromise = new Promise (resolve) ->
@@ -456,3 +480,40 @@ class PackageManager
[isSymLink, isDir] = values
if not isSymLink and isDir
fs.remove directory, ->
reloadActivePackageStyleSheets: ->
for pack in @getActivePackages() when pack.getType() isnt 'theme'
pack.reloadStylesheets?()
return
isBundledPackagePath: (packagePath) ->
if @devMode
return false unless @resourcePath.startsWith("#{process.resourcesPath}#{path.sep}")
@resourcePathWithTrailingSlash ?= "#{@resourcePath}#{path.sep}"
packagePath?.startsWith(@resourcePathWithTrailingSlash)
loadPackageMetadata: (packagePath, ignoreErrors=false) ->
packageName = path.basename(packagePath)
if @isBundledPackagePath(packagePath)
metadata = @packagesCache[packageName]?.metadata
unless metadata?
if metadataPath = CSON.resolve(path.join(packagePath, 'package'))
try
metadata = CSON.readFileSync(metadataPath)
@normalizePackageMetadata(metadata)
catch error
throw error unless ignoreErrors
metadata ?= {}
unless typeof metadata.name is 'string' and metadata.name.length > 0
metadata.name = packageName
metadata
normalizePackageMetadata: (metadata) ->
unless metadata?._id
normalizePackageData ?= require 'normalize-package-data'
normalizePackageData(metadata)
if metadata.repository?.type is 'git' and typeof metadata.repository.url is 'string'
metadata.repository.url = metadata.repository.url.replace(/^git\+/, '')

View File

@@ -1,5 +1,4 @@
path = require 'path'
normalizePackageData = null
_ = require 'underscore-plus'
async = require 'async'
@@ -11,44 +10,10 @@ ModuleCache = require './module-cache'
ScopedProperties = require './scoped-properties'
BufferedProcess = require './buffered-process'
packagesCache = require('../package.json')?._atomPackages ? {}
# Extended: Loads and activates a package's main module and resources such as
# stylesheets, keymaps, grammar, editor properties, and menus.
module.exports =
class Package
@isBundledPackagePath: (packagePath) ->
if atom.packages.devMode
return false unless atom.packages.resourcePath.startsWith("#{process.resourcesPath}#{path.sep}")
@resourcePathWithTrailingSlash ?= "#{atom.packages.resourcePath}#{path.sep}"
packagePath?.startsWith(@resourcePathWithTrailingSlash)
@normalizeMetadata: (metadata) ->
unless metadata?._id
normalizePackageData ?= require 'normalize-package-data'
normalizePackageData(metadata)
if metadata.repository?.type is 'git' and typeof metadata.repository.url is 'string'
metadata.repository.url = metadata.repository.url.replace(/^git\+/, '')
@loadMetadata: (packagePath, ignoreErrors=false) ->
packageName = path.basename(packagePath)
if @isBundledPackagePath(packagePath)
metadata = packagesCache[packageName]?.metadata
unless metadata?
if metadataPath = CSON.resolve(path.join(packagePath, 'package'))
try
metadata = CSON.readFileSync(metadataPath)
@normalizeMetadata(metadata)
catch error
throw error unless ignoreErrors
metadata ?= {}
unless typeof metadata.name is 'string' and metadata.name.length > 0
metadata.name = packageName
metadata
keymaps: null
menus: null
stylesheets: null
@@ -64,10 +29,16 @@ class Package
Section: Construction
###
constructor: (@path, @metadata) ->
constructor: (params) ->
{
@path, @metadata, @packageManager, @config, @styleManager, @commandRegistry,
@keymapManager, @devMode, @notificationManager, @grammarRegistry, @themeManager,
@menuManager, @contextMenuManager
} = params
@emitter = new Emitter
@metadata ?= Package.loadMetadata(@path)
@bundledPackage = Package.isBundledPackagePath(@path)
@metadata ?= @packageManager.loadPackageMetadata(@path)
@bundledPackage = @packageManager.isBundledPackagePath(@path)
@name = @metadata?.name ? path.basename(@path)
ModuleCache.add(@path, @metadata)
@reset()
@@ -89,10 +60,10 @@ class Package
###
enable: ->
atom.config.removeAtKeyPath('core.disabledPackages', @name)
@config.removeAtKeyPath('core.disabledPackages', @name)
disable: ->
atom.config.pushAtKeyPath('core.disabledPackages', @name)
@config.pushAtKeyPath('core.disabledPackages', @name)
isTheme: ->
@metadata?.theme?
@@ -132,7 +103,6 @@ class Package
@activationPromise ?=
new Promise (resolve, reject) =>
@resolveActivationPromise = resolve
@rejectActivationPromise = reject
@measure 'activateTime', =>
try
@activateResources()
@@ -150,7 +120,7 @@ class Package
@activateConfig()
@activateStylesheets()
if @mainModule? and not @mainActivated
@mainModule.activate?(atom.packages.getPackageState(@name) ? {})
@mainModule.activate?(@packageManager.getPackageState(@name) ? {})
@mainActivated = true
@activateServices()
catch error
@@ -164,7 +134,7 @@ class Package
@requireMainModule() unless @mainModule?
if @mainModule?
if @mainModule.config? and typeof @mainModule.config is 'object'
atom.config.setSchema @name, {type: 'object', properties: @mainModule.config}
@config.setSchema @name, {type: 'object', properties: @mainModule.config}
@mainModule.activateConfig?()
@configActivated = true
@@ -182,13 +152,13 @@ class Package
else
context = undefined
@stylesheetDisposables.add(atom.styles.addStyleSheet(source, {sourcePath, priority, context}))
@stylesheetDisposables.add(@styleManager.addStyleSheet(source, {sourcePath, priority, context}))
@stylesheetsActivated = true
activateResources: ->
@activationDisposables = new CompositeDisposable
keymapIsDisabled = _.include(atom.config.get("core.packagesWithKeymapsDisabled") ? [], @name)
keymapIsDisabled = _.include(@config.get("core.packagesWithKeymapsDisabled") ? [], @name)
if keymapIsDisabled
@deactivateKeymaps()
else
@@ -197,14 +167,14 @@ class Package
for [menuPath, map] in @menus when map['context-menu']?
try
itemsBySelector = map['context-menu']
@activationDisposables.add(atom.contextMenu.add(itemsBySelector))
@activationDisposables.add(@contextMenuManager.add(itemsBySelector))
catch error
if error.code is 'EBADSELECTOR'
error.message += " in #{menuPath}"
error.stack += "\n at #{menuPath}:1:1"
throw error
@activationDisposables.add(atom.menu.add(map['menu'])) for [menuPath, map] in @menus when map['menu']?
@activationDisposables.add(@menuManager.add(map['menu'])) for [menuPath, map] in @menus when map['menu']?
unless @grammarsActivated
grammar.activate() for grammar in @grammars
@@ -218,8 +188,8 @@ class Package
@keymapDisposables = new CompositeDisposable()
@keymapDisposables.add(atom.keymaps.add(keymapPath, map)) for [keymapPath, map] in @keymaps
atom.menu.update()
@keymapDisposables.add(@keymapManager.add(keymapPath, map)) for [keymapPath, map] in @keymaps
@menuManager.update()
@keymapActivated = true
@@ -227,7 +197,7 @@ class Package
return if not @keymapActivated
@keymapDisposables?.dispose()
atom.menu.update()
@menuManager.update()
@keymapActivated = false
@@ -243,24 +213,24 @@ class Package
for version, methodName of versions
if typeof @mainModule[methodName] is 'function'
servicesByVersion[version] = @mainModule[methodName]()
@activationDisposables.add atom.packages.serviceHub.provide(name, servicesByVersion)
@activationDisposables.add @packageManager.serviceHub.provide(name, servicesByVersion)
for name, {versions} of @metadata.consumedServices
for version, methodName of versions
if typeof @mainModule[methodName] is 'function'
@activationDisposables.add atom.packages.serviceHub.consume(name, version, @mainModule[methodName].bind(@mainModule))
@activationDisposables.add @packageManager.serviceHub.consume(name, version, @mainModule[methodName].bind(@mainModule))
return
loadKeymaps: ->
if @bundledPackage and packagesCache[@name]?
@keymaps = (["#{atom.packages.resourcePath}#{path.sep}#{keymapPath}", keymapObject] for keymapPath, keymapObject of packagesCache[@name].keymaps)
if @bundledPackage and @packageManager.packagesCache[@name]?
@keymaps = (["#{@packageManager.resourcePath}#{path.sep}#{keymapPath}", keymapObject] for keymapPath, keymapObject of @packageManager.packagesCache[@name].keymaps)
else
@keymaps = @getKeymapPaths().map (keymapPath) -> [keymapPath, CSON.readFileSync(keymapPath) ? {}]
return
loadMenus: ->
if @bundledPackage and packagesCache[@name]?
@menus = (["#{atom.packages.resourcePath}#{path.sep}#{menuPath}", menuObject] for menuPath, menuObject of packagesCache[@name].menus)
if @bundledPackage and @packageManager.packagesCache[@name]?
@menus = (["#{@packageManager.resourcePath}#{path.sep}#{menuPath}", menuObject] for menuPath, menuObject of @packageManager.packagesCache[@name].menus)
else
@menus = @getMenuPaths().map (menuPath) -> [menuPath, CSON.readFileSync(menuPath) ? {}]
return
@@ -280,8 +250,8 @@ class Package
fs.listSync(menusDirPath, ['cson', 'json'])
loadStylesheets: ->
@stylesheets = @getStylesheetPaths().map (stylesheetPath) ->
[stylesheetPath, atom.themes.loadStylesheet(stylesheetPath, true)]
@stylesheets = @getStylesheetPaths().map (stylesheetPath) =>
[stylesheetPath, @themeManager.loadStylesheet(stylesheetPath, true)]
getStylesheetsPath: ->
path.join(@path, 'styles')
@@ -304,7 +274,7 @@ class Package
grammarPaths = fs.listSync(grammarsDirPath, ['json', 'cson'])
for grammarPath in grammarPaths
try
grammar = atom.grammars.readGrammarSync(grammarPath)
grammar = @grammarRegistry.readGrammarSync(grammarPath)
grammar.packageName = @name
grammar.bundledPackage = @bundledPackage
@grammars.push(grammar)
@@ -319,11 +289,11 @@ class Package
return Promise.resolve() if @grammarsLoaded
loadGrammar = (grammarPath, callback) =>
atom.grammars.readGrammar grammarPath, (error, grammar) =>
@grammarRegistry.readGrammar grammarPath, (error, grammar) =>
if error?
detail = "#{error.message} in #{grammarPath}"
stack = "#{error.stack}\n at #{grammarPath}:1:1"
atom.notifications.addFatalError("Failed to load a #{@name} package grammar", {stack, detail, dismissable: true})
@notificationManager.addFatalError("Failed to load a #{@name} package grammar", {stack, detail, dismissable: true})
else
grammar.packageName = @name
grammar.bundledPackage = @bundledPackage
@@ -343,11 +313,11 @@ class Package
@settings = []
loadSettingsFile = (settingsPath, callback) =>
ScopedProperties.load settingsPath, (error, settings) =>
ScopedProperties.load settingsPath, @config, (error, settings) =>
if error?
detail = "#{error.message} in #{settingsPath}"
stack = "#{error.stack}\n at #{settingsPath}:1:1"
atom.notifications.addFatalError("Failed to load the #{@name} package settings", {stack, detail, dismissable: true})
@notificationManager.addFatalError("Failed to load the #{@name} package settings", {stack, detail, dismissable: true})
else
@settings.push(settings)
settings.activate() if @settingsActivated
@@ -370,10 +340,8 @@ class Package
console.error "Error serializing package '#{@name}'", e.stack
deactivate: ->
@rejectActivationPromise?()
@activationPromise = null
@resolveActivationPromise = null
@rejectActivationPromise = null
@activationCommandSubscriptions?.dispose()
@deactivateResources()
@deactivateConfig()
@@ -430,9 +398,9 @@ class Package
return @mainModulePath if @resolvedMainModulePath
@resolvedMainModulePath = true
if @bundledPackage and packagesCache[@name]?
if packagesCache[@name].main
@mainModulePath = "#{atom.packages.resourcePath}#{path.sep}#{packagesCache[@name].main}"
if @bundledPackage and @packageManager.packagesCache[@name]?
if @packageManager.packagesCache[@name].main
@mainModulePath = "#{@packageManager.resourcePath}#{path.sep}#{@packageManager.packagesCache[@name].main}"
else
@mainModulePath = null
else
@@ -466,7 +434,7 @@ class Package
# Add dummy command so it appears in menu.
# The real command will be registered on package activation
try
@activationCommandSubscriptions.add atom.commands.add selector, command, ->
@activationCommandSubscriptions.add @commandRegistry.add selector, command, ->
catch error
if error.code is 'EBADSELECTOR'
metadataPath = path.join(@path, 'package.json')
@@ -474,7 +442,7 @@ class Package
error.stack += "\n at #{metadataPath}:1:1"
throw error
@activationCommandSubscriptions.add atom.commands.onWillDispatch (event) =>
@activationCommandSubscriptions.add @commandRegistry.onWillDispatch (event) =>
return unless event.type is command
currentTarget = event.target
while currentTarget
@@ -505,7 +473,7 @@ class Package
@activationHookSubscriptions = new CompositeDisposable
for hook in @getActivationHooks()
do (hook) =>
@activationHookSubscriptions.add(atom.packages.onDidTriggerActivationHook(hook, => @activateNow())) if hook? and _.isString(hook) and hook.trim().length > 0
@activationHookSubscriptions.add(@packageManager.onDidTriggerActivationHook(hook, => @activateNow())) if hook? and _.isString(hook) and hook.trim().length > 0
return
@@ -567,7 +535,7 @@ class Package
isCompatible: ->
return @compatible if @compatible?
if @path.indexOf(path.join(atom.packages.resourcePath, 'node_modules') + path.sep) is 0
if @path.indexOf(path.join(@packageManager.resourcePath, 'node_modules') + path.sep) is 0
# Bundled packages are always considered compatible
@compatible = true
else if @getMainModulePath()
@@ -603,7 +571,7 @@ class Package
stderr = ''
stdout = ''
new BufferedProcess({
command: atom.packages.getApmPath()
command: @packageManager.getApmPath()
args: ['rebuild', '--no-color']
options: {cwd: @path}
stderr: (output) -> stderr += output
@@ -625,7 +593,7 @@ class Package
# This information is cached in local storage on a per package/version basis
# to minimize the impact on startup time.
getIncompatibleNativeModules: ->
unless atom.inDevMode()
unless @devMode
try
if arrayAsString = global.localStorage.getItem(@getIncompatibleNativeModulesStorageKey())
return JSON.parse(arrayAsString)
@@ -666,4 +634,4 @@ class Package
detail = error.message
stack = error.stack ? error
atom.notifications.addFatalError(message, {stack, detail, dismissable: true})
@notificationManager.addFatalError(message, {stack, detail, dismissable: true})

View File

@@ -8,7 +8,9 @@ class PaneAxisElement extends HTMLElement
detachedCallback: ->
@subscriptions.dispose()
initialize: (@model) ->
initialize: (@model, {@views}) ->
throw new Error("Must pass a views parameter when initializing TextEditorElements") unless @views?
@subscriptions.add @model.onDidAddChild(@childAdded.bind(this))
@subscriptions.add @model.onDidRemoveChild(@childRemoved.bind(this))
@subscriptions.add @model.onDidReplaceChild(@childReplaced.bind(this))
@@ -27,7 +29,7 @@ class PaneAxisElement extends HTMLElement
element?.nodeName.toLowerCase() is 'atom-pane-resize-handle'
childAdded: ({child, index}) ->
view = atom.views.getView(child)
view = @views.getView(child)
@insertBefore(view, @children[index * 2])
prevElement = view.previousSibling
@@ -43,7 +45,7 @@ class PaneAxisElement extends HTMLElement
@insertBefore(resizeHandle, nextElement)
childRemoved: ({child}) ->
view = atom.views.getView(child)
view = @views.getView(child)
siblingView = view.previousSibling
# make sure next sibling view is pane resize view
if siblingView? and @isPaneResizeHandleElement(siblingView)

View File

@@ -4,19 +4,16 @@ Model = require './model'
module.exports =
class PaneAxis extends Model
atom.deserializers.add(this)
parent: null
container: null
orientation: null
@deserialize: (state, params) ->
container = params?.container
state.container = container
state.children = state.children.map (childState) -> atom.deserializers.deserialize(childState, {container})
@deserialize: (state, {deserializers}) ->
state.children = state.children.map (childState) ->
deserializers.deserialize(childState)
new this(state)
constructor: ({@container, @orientation, children, flexScale}={}) ->
constructor: ({@orientation, children, flexScale}={}) ->
@emitter = new Emitter
@subscriptionsByChild = new WeakMap
@subscriptions = new CompositeDisposable
@@ -43,7 +40,10 @@ class PaneAxis extends Model
getContainer: -> @container
setContainer: (@container) -> @container
setContainer: (container) ->
if container and container isnt @container
@container = container
child.setContainer(container) for child in @children
getOrientation: -> @orientation

View File

@@ -7,7 +7,9 @@ class PaneContainerElement extends HTMLElement
@subscriptions = new CompositeDisposable
@classList.add 'panes'
initialize: (@model) ->
initialize: (@model, {@views}) ->
throw new Error("Must pass a views parameter when initializing PaneContainerElements") unless @views?
@subscriptions.add @model.observeRoot(@rootChanged.bind(this))
this
@@ -15,7 +17,7 @@ class PaneContainerElement extends HTMLElement
focusedElement = document.activeElement if @hasFocus()
@firstChild?.remove()
if root?
view = atom.views.getView(root)
view = @views.getView(root)
@appendChild(view)
focusedElement?.focus()
@@ -40,7 +42,7 @@ class PaneContainerElement extends HTMLElement
y = pointB.y - pointA.y
Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2))
paneView = atom.views.getView(@model.getActivePane())
paneView = @views.getView(@model.getActivePane())
box = @boundingBoxForPaneView(paneView)
paneViews = _.toArray(@querySelectorAll('atom-pane'))

View File

@@ -7,44 +7,37 @@ ItemRegistry = require './item-registry'
module.exports =
class PaneContainer extends Model
atom.deserializers.add(this)
@version: 1
serializationVersion: 1
root: null
@deserialize: (state) ->
container = Object.create(@prototype) # allows us to pass a self reference to our child before invoking constructor
state.root = atom.deserializers.deserialize(state.root, {container})
state.destroyEmptyPanes = atom.config.get('core.destroyEmptyPanes')
state.activePane = find state.root.getPanes(), (pane) -> pane.id is state.activePaneId
@call(container, state) # run constructor
container
stoppedChangingActivePaneItemDelay: 100
stoppedChangingActivePaneItemTimeout: null
constructor: (params) ->
super
@activePane = params?.activePane
{@config, applicationDelegate, notificationManager, deserializerManager} = params
@emitter = new Emitter
@subscriptions = new CompositeDisposable
@itemRegistry = new ItemRegistry
@setRoot(params?.root ? new Pane)
@setActivePane(@getPanes()[0]) unless @getActivePane()
@destroyEmptyPanes() if params?.destroyEmptyPanes
@setRoot(new Pane({container: this, @config, applicationDelegate, notificationManager, deserializerManager}))
@setActivePane(@getRoot())
@monitorActivePaneItem()
@monitorPaneItems()
serialize: (params) ->
deserializer: 'PaneContainer'
version: @constructor.version
version: @serializationVersion
root: @root?.serialize()
activePaneId: @activePane.id
deserialize: (state, deserializerManager) ->
return unless state.version is @serializationVersion
@setRoot(deserializerManager.deserialize(state.root))
activePane = find @getRoot().getPanes(), (pane) -> pane.id is state.activePaneId
@setActivePane(activePane ? @getPanes()[0])
@destroyEmptyPanes() if @config.get('core.destroyEmptyPanes')
onDidChangeRoot: (fn) ->
@emitter.on 'did-change-root', fn
@@ -82,6 +75,9 @@ class PaneContainer extends Model
onDidChangeActivePaneItem: (fn) ->
@emitter.on 'did-change-active-pane-item', fn
onDidStopChangingActivePaneItem: (fn) ->
@emitter.on 'did-stop-changing-active-pane-item', fn
observeActivePaneItem: (fn) ->
fn(@getActivePaneItem())
@onDidChangeActivePaneItem(fn)
@@ -189,12 +185,18 @@ class PaneContainer extends Model
# Called by Model superclass when destroyed
destroyed: ->
@cancelStoppedChangingActivePaneItemTimeout()
pane.destroy() for pane in @getPanes()
@subscriptions.dispose()
@emitter.dispose()
cancelStoppedChangingActivePaneItemTimeout: ->
if @stoppedChangingActivePaneItemTimeout?
clearTimeout(@stoppedChangingActivePaneItemTimeout)
monitorActivePaneItem: ->
childSubscription = null
@subscriptions.add @observeActivePane (activePane) =>
if childSubscription?
@subscriptions.remove(childSubscription)
@@ -202,6 +204,14 @@ class PaneContainer extends Model
childSubscription = activePane.observeActiveItem (activeItem) =>
@emitter.emit 'did-change-active-pane-item', activeItem
@cancelStoppedChangingActivePaneItemTimeout()
stoppedChangingActivePaneItemCallback = =>
@stoppedChangingActivePaneItemTimeout = null
@emitter.emit 'did-stop-changing-active-pane-item', activeItem
@stoppedChangingActivePaneItemTimeout =
setTimeout(
stoppedChangingActivePaneItemCallback,
@stoppedChangingActivePaneItemDelay)
@subscriptions.add(childSubscription)

View File

@@ -44,14 +44,17 @@ class PaneElement extends HTMLElement
event.stopPropagation()
@getModel().activate()
pathsToOpen = Array::map.call event.dataTransfer.files, (file) -> file.path
atom.open({pathsToOpen}) if pathsToOpen.length > 0
@applicationDelegate.open({pathsToOpen}) if pathsToOpen.length > 0
@addEventListener 'focus', handleFocus, true
@addEventListener 'blur', handleBlur, true
@addEventListener 'dragover', handleDragOver
@addEventListener 'drop', handleDrop
initialize: (@model) ->
initialize: (@model, {@views, @applicationDelegate}) ->
throw new Error("Must pass a views parameter when initializing PaneElements") unless @views?
throw new Error("Must pass an applicationDelegate parameter when initializing PaneElements") unless @applicationDelegate?
@subscriptions.add @model.onDidActivate(@activated.bind(this))
@subscriptions.add @model.observeActive(@activeStatusChanged.bind(this))
@subscriptions.add @model.observeActiveItem(@activeItemChanged.bind(this))
@@ -78,7 +81,7 @@ class PaneElement extends HTMLElement
return unless item?
hasFocus = @hasFocus()
itemView = atom.views.getView(item)
itemView = @views.getView(item)
if itemPath = item.getPath?()
@dataset.activeItemName = path.basename(itemPath)
@@ -109,7 +112,7 @@ class PaneElement extends HTMLElement
itemView.style.display = 'none'
itemRemoved: ({item, index, destroyed}) ->
if viewToRemove = atom.views.getView(item)
if viewToRemove = @views.getView(item)
viewToRemove.remove()
paneDestroyed: ->
@@ -118,35 +121,9 @@ class PaneElement extends HTMLElement
flexScaleChanged: (flexScale) ->
@style.flexGrow = flexScale
getActiveView: -> atom.views.getView(@model.getActiveItem())
getActiveView: -> @views.getView(@model.getActiveItem())
hasFocus: ->
this is document.activeElement or @contains(document.activeElement)
atom.commands.add 'atom-workspace',
'pane:show-next-item': -> @getModel().getActivePane().activateNextItem()
'pane:show-previous-item': -> @getModel().getActivePane().activatePreviousItem()
'pane:show-item-1': -> @getModel().getActivePane().activateItemAtIndex(0)
'pane:show-item-2': -> @getModel().getActivePane().activateItemAtIndex(1)
'pane:show-item-3': -> @getModel().getActivePane().activateItemAtIndex(2)
'pane:show-item-4': -> @getModel().getActivePane().activateItemAtIndex(3)
'pane:show-item-5': -> @getModel().getActivePane().activateItemAtIndex(4)
'pane:show-item-6': -> @getModel().getActivePane().activateItemAtIndex(5)
'pane:show-item-7': -> @getModel().getActivePane().activateItemAtIndex(6)
'pane:show-item-8': -> @getModel().getActivePane().activateItemAtIndex(7)
'pane:show-item-9': -> @getModel().getActivePane().activateItemAtIndex(8)
'pane:move-item-right': -> @getModel().getActivePane().moveItemRight()
'pane:move-item-left': -> @getModel().getActivePane().moveItemLeft()
atom.commands.add 'atom-pane',
'pane:save-items': -> @getModel().saveItems()
'pane:split-left': -> @getModel().splitLeft(copyActiveItem: true)
'pane:split-right': -> @getModel().splitRight(copyActiveItem: true)
'pane:split-up': -> @getModel().splitUp(copyActiveItem: true)
'pane:split-down': -> @getModel().splitDown(copyActiveItem: true)
'pane:close': -> @getModel().close()
'pane:close-other-items': -> @getModel().destroyInactiveItems()
'pane:increase-size': -> @getModel().increaseSize()
'pane:decrease-size': -> @getModel().decreaseSize()
module.exports = PaneElement = document.registerElement 'atom-pane', prototype: PaneElement.prototype

View File

@@ -10,30 +10,31 @@ TextEditor = require './text-editor'
# the default configuration, tabs are also displayed for each item.
module.exports =
class Pane extends Model
atom.deserializers.add(this)
container: undefined
activeItem: undefined
focused: false
@deserialize: (state, params) ->
@deserialize: (state, {deserializers, applicationDelegate, config, notifications}) ->
{items, activeItemURI, activeItemUri} = state
state.container = params?.container
activeItemURI ?= activeItemUri
state.items = compact(items.map (itemState) -> atom.deserializers.deserialize(itemState))
state.items = compact(items.map (itemState) -> deserializers.deserialize(itemState))
state.activeItem = find state.items, (item) ->
if typeof item.getURI is 'function'
itemURI = item.getURI()
itemURI is activeItemURI
new this(state)
new Pane(extend(state, {
deserializerManager: deserializers,
notificationManager: notifications,
config, applicationDelegate
}))
constructor: (params) ->
super
@container = params?.container
@activeItem = params?.activeItem
@focused = params?.focused
{
@activeItem, @focused, @applicationDelegate, @notificationManager, @config,
@deserializerManager
} = params
@emitter = new Emitter
@itemSubscriptions = new WeakMap
@@ -61,7 +62,7 @@ class Pane extends Model
getContainer: -> @container
setContainer: (container) ->
unless container is @container
if container and container isnt @container
@container = container
container.didAddPane({pane: this})
@@ -395,7 +396,7 @@ class Pane extends Model
@items.splice(index, 1)
@emitter.emit 'did-remove-item', {item, index, destroyed: not moved, moved}
@container?.didDestroyPaneItem({item, index, pane: this}) unless moved
@destroy() if @items.length is 0 and atom.config.get('core.destroyEmptyPanes')
@destroy() if @items.length is 0 and @config.get('core.destroyEmptyPanes')
# Public: Move the given item to the given index.
#
@@ -461,7 +462,7 @@ class Pane extends Model
else
return true
chosen = atom.confirm
chosen = @applicationDelegate.confirm
message: "'#{item.getTitle?() ? uri}' has changes, do you want to save them?"
detailedMessage: "Your changes will be lost if you close this item without saving."
buttons: ["Save", "Cancel", "Don't Save"]
@@ -514,7 +515,7 @@ class Pane extends Model
saveOptions = item.getSaveDialogOptions?() ? {}
saveOptions.defaultPath ?= item.getPath()
newItemPath = atom.showSaveDialogSync(saveOptions)
newItemPath = @applicationDelegate.showSaveDialog(saveOptions)
if newItemPath
try
item.saveAs(newItemPath)
@@ -524,7 +525,8 @@ class Pane extends Model
# Public: Save all items.
saveItems: ->
@saveItem(item) for item in @getItems()
for item in @getItems()
@saveItem(item) if item.isModified?()
return
# Public: Return the first item that matches the given URI or undefined if
@@ -554,7 +556,7 @@ class Pane extends Model
copyActiveItem: ->
if @activeItem?
@activeItem.copy?() ? atom.deserializers.deserialize(@activeItem.serialize())
@activeItem.copy?() ? @deserializerManager.deserialize(@activeItem.serialize())
###
Section: Lifecycle
@@ -646,7 +648,7 @@ class Pane extends Model
@parent.replaceChild(this, new PaneAxis({@container, orientation, children: [this], @flexScale}))
@setFlexScale(1)
newPane = new @constructor(params)
newPane = new Pane(extend({@applicationDelegate, @deserializerManager, @config}, params))
switch side
when 'before' then @parent.insertChildBefore(this, newPane)
when 'after' then @parent.insertChildAfter(this, newPane)
@@ -678,6 +680,30 @@ class Pane extends Model
else
@splitRight()
# If the parent is a vertical axis, returns its first child if it is a pane;
# otherwise returns this pane.
findTopmostSibling: ->
if @parent.orientation is 'vertical'
[topmostSibling] = @parent.children
if topmostSibling instanceof PaneAxis
this
else
topmostSibling
else
this
# If the parent is a vertical axis, returns its last child if it is a pane;
# otherwise returns a new pane created by splitting this pane bottomward.
findOrCreateBottommostSibling: ->
if @parent.orientation is 'vertical'
bottommostSibling = last(@parent.children)
if bottommostSibling instanceof PaneAxis
@splitRight()
else
bottommostSibling
else
@splitDown()
close: ->
@destroy() if @confirmClose()
@@ -688,12 +714,12 @@ class Pane extends Model
handleSaveError: (error, item) ->
itemPath = error.path ? item?.getPath?()
addWarningWithPath = (message, options) ->
addWarningWithPath = (message, options) =>
message = "#{message} '#{itemPath}'" if itemPath
atom.notifications.addWarning(message, options)
@notificationManager.addWarning(message, options)
if error.code is 'EISDIR' or error.message?.endsWith?('is a directory')
atom.notifications.addWarning("Unable to save file: #{error.message}")
@notificationManager.addWarning("Unable to save file: #{error.message}")
else if error.code is 'EACCES'
addWarningWithPath('Unable to save file: Permission denied')
else if error.code in ['EPERM', 'EBUSY', 'UNKNOWN', 'EEXIST']
@@ -716,6 +742,6 @@ class Pane extends Model
addWarningWithPath('Unable to save file: Invalid seek')
else if errorMatch = /ENOTDIR, not a directory '([^']+)'/.exec(error.message)
fileName = errorMatch[1]
atom.notifications.addWarning("Unable to save file: A directory in the path '#{fileName}' could not be written to")
@notificationManager.addWarning("Unable to save file: A directory in the path '#{fileName}' could not be written to")
else
throw error

View File

@@ -4,9 +4,10 @@ class PanelContainerElement extends HTMLElement
createdCallback: ->
@subscriptions = new CompositeDisposable
initialize: (@model) ->
initialize: (@model, {@views}) ->
throw new Error("Must pass a views parameter when initializing PanelContainerElements") unless @views?
@subscriptions.add @model.onDidAddPanel(@panelAdded.bind(this))
@subscriptions.add @model.onDidRemovePanel(@panelRemoved.bind(this))
@subscriptions.add @model.onDidDestroy(@destroyed.bind(this))
@classList.add(@model.getLocation())
this
@@ -14,7 +15,7 @@ class PanelContainerElement extends HTMLElement
getModel: -> @model
panelAdded: ({panel, index}) ->
panelElement = atom.views.getView(panel)
panelElement = @views.getView(panel)
panelElement.classList.add(@model.getLocation())
if @model.isModal()
panelElement.classList.add("overlay", "from-top")
@@ -32,9 +33,6 @@ class PanelContainerElement extends HTMLElement
@subscriptions.add panel.onDidChangeVisible (visible) =>
@hideAllPanelsExcept(panel) if visible
panelRemoved: ({panel, index}) ->
@removeChild(atom.views.getView(panel))
destroyed: ->
@subscriptions.dispose()
@parentNode?.removeChild(this)

View File

@@ -5,7 +5,9 @@ class PanelElement extends HTMLElement
createdCallback: ->
@subscriptions = new CompositeDisposable
initialize: (@model) ->
initialize: (@model, {@views}) ->
throw new Error("Must pass a views parameter when initializing PanelElements") unless @views?
@appendChild(@getItemView())
@classList.add(@model.getClassName().split(' ')...) if @model.getClassName()?
@@ -17,7 +19,7 @@ class PanelElement extends HTMLElement
@model ?= new Panel
getItemView: ->
atom.views.getView(@getModel().getItem())
@views.getView(@getModel().getItem())
attachedCallback: ->
@visibleChanged(@getModel().isVisible())
@@ -30,6 +32,6 @@ class PanelElement extends HTMLElement
destroyed: ->
@subscriptions.dispose()
@parentNode?.removeChild(this)
@remove()
module.exports = PanelElement = document.registerElement 'atom-panel', prototype: PanelElement.prototype

View File

@@ -3,7 +3,7 @@ url = require 'url'
_ = require 'underscore-plus'
fs = require 'fs-plus'
{Emitter} = require 'event-kit'
{Emitter, Disposable} = require 'event-kit'
TextBuffer = require 'text-buffer'
DefaultDirectoryProvider = require './default-directory-provider'
@@ -17,68 +17,35 @@ GitRepositoryProvider = require './git-repository-provider'
# An instance of this class is always available as the `atom.project` global.
module.exports =
class Project extends Model
atom.deserializers.add(this)
###
Section: Construction and Destruction
###
@deserialize: (state) ->
state.buffers = _.compact state.buffers.map (bufferState) ->
# Check that buffer's file path is accessible
return if fs.isDirectorySync(bufferState.filePath)
if bufferState.filePath
try
fs.closeSync(fs.openSync(bufferState.filePath, 'r'))
catch error
return unless error.code is 'ENOENT'
atom.deserializers.deserialize(bufferState)
new this(state)
constructor: ({path, paths, @buffers}={}) ->
constructor: ({@notificationManager, packageManager, config}) ->
@emitter = new Emitter
@buffers ?= []
@buffers = []
@paths = []
@rootDirectories = []
@repositories = []
@directoryProviders = []
@defaultDirectoryProvider = new DefaultDirectoryProvider()
atom.packages.serviceHub.consume(
'atom.directory-provider',
'^0.1.0',
(provider) => @directoryProviders.unshift(provider))
# Mapping from the real path of a {Directory} to a {Promise} that resolves
# to either a {Repository} or null. Ideally, the {Directory} would be used
# as the key; however, there can be multiple {Directory} objects created for
# the same real path, so it is not a good key.
@repositoryPromisesByPath = new Map()
@repositoryProviders = [new GitRepositoryProvider(this)]
atom.packages.serviceHub.consume(
'atom.repository-provider',
'^0.1.0',
(provider) =>
@repositoryProviders.push(provider)
# If a path in getPaths() does not have a corresponding Repository, try
# to assign one by running through setPaths() again now that
# @repositoryProviders has been updated.
if null in @repositories
@setPaths(@getPaths())
)
@subscribeToBuffer(buffer) for buffer in @buffers
paths ?= _.compact([path])
@setPaths(paths)
@repositoryProviders = [new GitRepositoryProvider(this, config)]
@consumeServices(packageManager)
destroyed: ->
buffer.destroy() for buffer in @getBuffers()
buffer.destroy() for buffer in @buffers
@setPaths([])
reset: (packageManager) ->
@emitter.dispose()
@emitter = new Emitter
buffer?.destroy() for buffer in @buffers
@buffers = []
@setPaths([])
@consumeServices(packageManager)
destroyUnretainedBuffers: ->
buffer.destroy() for buffer in @getBuffers() when not buffer.isRetained()
return
@@ -87,6 +54,22 @@ class Project extends Model
Section: Serialization
###
deserialize: (state, deserializerManager) ->
states.paths = [state.path] if state.path? # backward compatibility
@buffers = _.compact state.buffers.map (bufferState) ->
# Check that buffer's file path is accessible
return if fs.isDirectorySync(bufferState.filePath)
if bufferState.filePath
try
fs.closeSync(fs.openSync(bufferState.filePath, 'r'))
catch error
return unless error.code is 'ENOENT'
deserializerManager.deserialize(bufferState)
@subscribeToBuffer(buffer) for buffer in @buffers
@setPaths(state.paths)
serialize: ->
deserializer: 'Project'
paths: @getPaths()
@@ -291,39 +274,25 @@ class Project extends Model
Section: Private
###
# Given a path to a file, this constructs and associates a new
# {TextEditor}, showing the file.
#
# * `filePath` The {String} path of the file to associate with.
# * `options` Options that you can pass to the {TextEditor} constructor.
#
# Returns a promise that resolves to an {TextEditor}.
open: (filePath, options={}) ->
filePath = @resolvePath(filePath)
consumeServices: ({serviceHub}) ->
serviceHub.consume(
'atom.directory-provider',
'^0.1.0',
(provider) =>
@directoryProviders.unshift(provider)
new Disposable =>
@directoryProviders.splice(@directoryProviders.indexOf(provider), 1)
)
if filePath?
try
fs.closeSync(fs.openSync(filePath, 'r'))
catch error
# allow ENOENT errors to create an editor for paths that dont exist
throw error unless error.code is 'ENOENT'
absoluteFilePath = @resolvePath(filePath)
fileSize = fs.getSizeSync(absoluteFilePath)
if fileSize >= 20 * 1048576 # 20MB
choice = atom.confirm
message: 'Atom will be unresponsive during the loading of very large files.'
detailedMessage: "Do you still want to load this file?"
buttons: ["Proceed", "Cancel"]
if choice is 1
error = new Error
error.code = 'CANCELLED'
throw error
@bufferForPath(absoluteFilePath).then (buffer) =>
@buildEditorForBuffer(buffer, _.extend({fileSize}, options))
serviceHub.consume(
'atom.repository-provider',
'^0.1.0',
(provider) =>
@repositoryProviders.push(provider)
@setPaths(@getPaths()) if null in @repositories
new Disposable =>
@repositoryProviders.splice(@repositoryProviders.indexOf(provider), 1)
)
# Retrieves all the {TextBuffer}s in the project; that is, the
# buffers for all open files.
@@ -404,11 +373,6 @@ class Project extends Model
[buffer] = @buffers.splice(index, 1)
buffer?.destroy()
buildEditorForBuffer: (buffer, editorOptions) ->
largeFileMode = editorOptions.fileSize >= 2 * 1048576 # 2MB
editor = new TextEditor(_.extend({buffer, largeFileMode, registerEditor: true}, editorOptions))
editor
eachBuffer: (args...) ->
subscriber = args.shift() if args.length > 1
callback = args.shift()
@@ -421,9 +385,9 @@ class Project extends Model
subscribeToBuffer: (buffer) ->
buffer.onDidDestroy => @removeBuffer(buffer)
buffer.onWillThrowWatchError ({error, handle}) ->
buffer.onWillThrowWatchError ({error, handle}) =>
handle()
atom.notifications.addWarning """
@notificationManager.addWarning """
Unable to read file after file `#{error.eventType}` event.
Make sure you have permission to access `#{buffer.getPath()}`.
""",

View File

@@ -0,0 +1,212 @@
ipc = require 'ipc'
module.exports = ({commandRegistry, commandInstaller, config}) ->
commandRegistry.add 'atom-workspace',
'pane:show-next-item': -> @getModel().getActivePane().activateNextItem()
'pane:show-previous-item': -> @getModel().getActivePane().activatePreviousItem()
'pane:show-item-1': -> @getModel().getActivePane().activateItemAtIndex(0)
'pane:show-item-2': -> @getModel().getActivePane().activateItemAtIndex(1)
'pane:show-item-3': -> @getModel().getActivePane().activateItemAtIndex(2)
'pane:show-item-4': -> @getModel().getActivePane().activateItemAtIndex(3)
'pane:show-item-5': -> @getModel().getActivePane().activateItemAtIndex(4)
'pane:show-item-6': -> @getModel().getActivePane().activateItemAtIndex(5)
'pane:show-item-7': -> @getModel().getActivePane().activateItemAtIndex(6)
'pane:show-item-8': -> @getModel().getActivePane().activateItemAtIndex(7)
'pane:show-item-9': -> @getModel().getActivePane().activateItemAtIndex(8)
'pane:move-item-right': -> @getModel().getActivePane().moveItemRight()
'pane:move-item-left': -> @getModel().getActivePane().moveItemLeft()
'window:increase-font-size': -> @getModel().increaseFontSize()
'window:decrease-font-size': -> @getModel().decreaseFontSize()
'window:reset-font-size': -> @getModel().resetFontSize()
'application:about': -> ipc.send('command', 'application:about')
'application:show-preferences': -> ipc.send('command', 'application:show-settings')
'application:show-settings': -> ipc.send('command', 'application:show-settings')
'application:quit': -> ipc.send('command', 'application:quit')
'application:hide': -> ipc.send('command', 'application:hide')
'application:hide-other-applications': -> ipc.send('command', 'application:hide-other-applications')
'application:install-update': -> ipc.send('command', 'application:install-update')
'application:unhide-all-applications': -> ipc.send('command', 'application:unhide-all-applications')
'application:new-window': -> ipc.send('command', 'application:new-window')
'application:new-file': -> ipc.send('command', 'application:new-file')
'application:open': -> ipc.send('command', 'application:open')
'application:open-file': -> ipc.send('command', 'application:open-file')
'application:open-folder': -> ipc.send('command', 'application:open-folder')
'application:open-dev': -> ipc.send('command', 'application:open-dev')
'application:open-safe': -> ipc.send('command', 'application:open-safe')
'application:add-project-folder': -> atom.addProjectFolder()
'application:minimize': -> ipc.send('command', 'application:minimize')
'application:zoom': -> ipc.send('command', 'application:zoom')
'application:bring-all-windows-to-front': -> ipc.send('command', 'application:bring-all-windows-to-front')
'application:open-your-config': -> ipc.send('command', 'application:open-your-config')
'application:open-your-init-script': -> ipc.send('command', 'application:open-your-init-script')
'application:open-your-keymap': -> ipc.send('command', 'application:open-your-keymap')
'application:open-your-snippets': -> ipc.send('command', 'application:open-your-snippets')
'application:open-your-stylesheet': -> ipc.send('command', 'application:open-your-stylesheet')
'application:open-license': -> @getModel().openLicense()
'window:run-package-specs': -> @runPackageSpecs()
'window:focus-next-pane': -> @getModel().activateNextPane()
'window:focus-previous-pane': -> @getModel().activatePreviousPane()
'window:focus-pane-above': -> @focusPaneViewAbove()
'window:focus-pane-below': -> @focusPaneViewBelow()
'window:focus-pane-on-left': -> @focusPaneViewOnLeft()
'window:focus-pane-on-right': -> @focusPaneViewOnRight()
'window:save-all': -> @getModel().saveAll()
'window:toggle-invisibles': -> config.set("editor.showInvisibles", not config.get("editor.showInvisibles"))
'window:log-deprecation-warnings': -> Grim.logDeprecations()
'window:toggle-auto-indent': -> config.set("editor.autoIndent", not config.get("editor.autoIndent"))
'pane:reopen-closed-item': -> @getModel().reopenItem()
'core:close': -> @getModel().destroyActivePaneItemOrEmptyPane()
'core:save': -> @getModel().saveActivePaneItem()
'core:save-as': -> @getModel().saveActivePaneItemAs()
if process.platform is 'darwin'
commandRegistry.add 'atom-workspace', 'window:install-shell-commands', ->
commandInstaller.installShellCommandsInteractively()
commandRegistry.add 'atom-pane',
'pane:save-items': -> @getModel().saveItems()
'pane:split-left': -> @getModel().splitLeft(copyActiveItem: true)
'pane:split-right': -> @getModel().splitRight(copyActiveItem: true)
'pane:split-up': -> @getModel().splitUp(copyActiveItem: true)
'pane:split-down': -> @getModel().splitDown(copyActiveItem: true)
'pane:close': -> @getModel().close()
'pane:close-other-items': -> @getModel().destroyInactiveItems()
'pane:increase-size': -> @getModel().increaseSize()
'pane:decrease-size': -> @getModel().decreaseSize()
commandRegistry.add 'atom-text-editor', stopEventPropagation(
'core:undo': -> @undo()
'core:redo': -> @redo()
'core:move-left': -> @moveLeft()
'core:move-right': -> @moveRight()
'core:select-left': -> @selectLeft()
'core:select-right': -> @selectRight()
'core:select-up': -> @selectUp()
'core:select-down': -> @selectDown()
'core:select-all': -> @selectAll()
'editor:select-word': -> @selectWordsContainingCursors()
'editor:consolidate-selections': (event) -> event.abortKeyBinding() unless @consolidateSelections()
'editor:move-to-beginning-of-next-paragraph': -> @moveToBeginningOfNextParagraph()
'editor:move-to-beginning-of-previous-paragraph': -> @moveToBeginningOfPreviousParagraph()
'editor:move-to-beginning-of-screen-line': -> @moveToBeginningOfScreenLine()
'editor:move-to-beginning-of-line': -> @moveToBeginningOfLine()
'editor:move-to-end-of-screen-line': -> @moveToEndOfScreenLine()
'editor:move-to-end-of-line': -> @moveToEndOfLine()
'editor:move-to-first-character-of-line': -> @moveToFirstCharacterOfLine()
'editor:move-to-beginning-of-word': -> @moveToBeginningOfWord()
'editor:move-to-end-of-word': -> @moveToEndOfWord()
'editor:move-to-beginning-of-next-word': -> @moveToBeginningOfNextWord()
'editor:move-to-previous-word-boundary': -> @moveToPreviousWordBoundary()
'editor:move-to-next-word-boundary': -> @moveToNextWordBoundary()
'editor:move-to-previous-subword-boundary': -> @moveToPreviousSubwordBoundary()
'editor:move-to-next-subword-boundary': -> @moveToNextSubwordBoundary()
'editor:select-to-beginning-of-next-paragraph': -> @selectToBeginningOfNextParagraph()
'editor:select-to-beginning-of-previous-paragraph': -> @selectToBeginningOfPreviousParagraph()
'editor:select-to-end-of-line': -> @selectToEndOfLine()
'editor:select-to-beginning-of-line': -> @selectToBeginningOfLine()
'editor:select-to-end-of-word': -> @selectToEndOfWord()
'editor:select-to-beginning-of-word': -> @selectToBeginningOfWord()
'editor:select-to-beginning-of-next-word': -> @selectToBeginningOfNextWord()
'editor:select-to-next-word-boundary': -> @selectToNextWordBoundary()
'editor:select-to-previous-word-boundary': -> @selectToPreviousWordBoundary()
'editor:select-to-next-subword-boundary': -> @selectToNextSubwordBoundary()
'editor:select-to-previous-subword-boundary': -> @selectToPreviousSubwordBoundary()
'editor:select-to-first-character-of-line': -> @selectToFirstCharacterOfLine()
'editor:select-line': -> @selectLinesContainingCursors()
)
commandRegistry.add 'atom-text-editor', stopEventPropagationAndGroupUndo(config,
'core:backspace': -> @backspace()
'core:delete': -> @delete()
'core:cut': -> @cutSelectedText()
'core:copy': -> @copySelectedText()
'core:paste': -> @pasteText()
'editor:delete-to-previous-word-boundary': -> @deleteToPreviousWordBoundary()
'editor:delete-to-next-word-boundary': -> @deleteToNextWordBoundary()
'editor:delete-to-beginning-of-word': -> @deleteToBeginningOfWord()
'editor:delete-to-beginning-of-line': -> @deleteToBeginningOfLine()
'editor:delete-to-end-of-line': -> @deleteToEndOfLine()
'editor:delete-to-end-of-word': -> @deleteToEndOfWord()
'editor:delete-to-beginning-of-subword': -> @deleteToBeginningOfSubword()
'editor:delete-to-end-of-subword': -> @deleteToEndOfSubword()
'editor:delete-line': -> @deleteLine()
'editor:cut-to-end-of-line': -> @cutToEndOfLine()
'editor:cut-to-end-of-buffer-line': -> @cutToEndOfBufferLine()
'editor:transpose': -> @transpose()
'editor:upper-case': -> @upperCase()
'editor:lower-case': -> @lowerCase()
'editor:copy-selection': -> @copyOnlySelectedText()
)
commandRegistry.add 'atom-text-editor:not([mini])', stopEventPropagation(
'core:move-up': -> @moveUp()
'core:move-down': -> @moveDown()
'core:move-to-top': -> @moveToTop()
'core:move-to-bottom': -> @moveToBottom()
'core:page-up': -> @pageUp()
'core:page-down': -> @pageDown()
'core:select-to-top': -> @selectToTop()
'core:select-to-bottom': -> @selectToBottom()
'core:select-page-up': -> @selectPageUp()
'core:select-page-down': -> @selectPageDown()
'editor:add-selection-below': -> @addSelectionBelow()
'editor:add-selection-above': -> @addSelectionAbove()
'editor:split-selections-into-lines': -> @splitSelectionsIntoLines()
'editor:toggle-soft-tabs': -> @toggleSoftTabs()
'editor:toggle-soft-wrap': -> @toggleSoftWrapped()
'editor:fold-all': -> @foldAll()
'editor:unfold-all': -> @unfoldAll()
'editor:fold-current-row': -> @foldCurrentRow()
'editor:unfold-current-row': -> @unfoldCurrentRow()
'editor:fold-selection': -> @foldSelectedLines()
'editor:fold-at-indent-level-1': -> @foldAllAtIndentLevel(0)
'editor:fold-at-indent-level-2': -> @foldAllAtIndentLevel(1)
'editor:fold-at-indent-level-3': -> @foldAllAtIndentLevel(2)
'editor:fold-at-indent-level-4': -> @foldAllAtIndentLevel(3)
'editor:fold-at-indent-level-5': -> @foldAllAtIndentLevel(4)
'editor:fold-at-indent-level-6': -> @foldAllAtIndentLevel(5)
'editor:fold-at-indent-level-7': -> @foldAllAtIndentLevel(6)
'editor:fold-at-indent-level-8': -> @foldAllAtIndentLevel(7)
'editor:fold-at-indent-level-9': -> @foldAllAtIndentLevel(8)
'editor:log-cursor-scope': -> @logCursorScope()
'editor:copy-path': -> @copyPathToClipboard()
'editor:toggle-indent-guide': -> config.set('editor.showIndentGuide', not config.get('editor.showIndentGuide'))
'editor:toggle-line-numbers': -> config.set('editor.showLineNumbers', not config.get('editor.showLineNumbers'))
'editor:scroll-to-cursor': -> @scrollToCursorPosition()
)
commandRegistry.add 'atom-text-editor:not([mini])', stopEventPropagationAndGroupUndo(config,
'editor:indent': -> @indent()
'editor:auto-indent': -> @autoIndentSelectedRows()
'editor:indent-selected-rows': -> @indentSelectedRows()
'editor:outdent-selected-rows': -> @outdentSelectedRows()
'editor:newline': -> @insertNewline()
'editor:newline-below': -> @insertNewlineBelow()
'editor:newline-above': -> @insertNewlineAbove()
'editor:toggle-line-comments': -> @toggleLineCommentsInSelection()
'editor:checkout-head-revision': -> @checkoutHeadRevision()
'editor:move-line-up': -> @moveLineUp()
'editor:move-line-down': -> @moveLineDown()
'editor:duplicate-lines': -> @duplicateLines()
'editor:join-lines': -> @joinLines()
)
stopEventPropagation = (commandListeners) ->
newCommandListeners = {}
for commandName, commandListener of commandListeners
do (commandListener) ->
newCommandListeners[commandName] = (event) ->
event.stopPropagation()
commandListener.call(@getModel(), event)
newCommandListeners
stopEventPropagationAndGroupUndo = (config, commandListeners) ->
newCommandListeners = {}
for commandName, commandListener of commandListeners
do (commandListener) ->
newCommandListeners[commandName] = (event) ->
event.stopPropagation()
model = @getModel()
model.transact config.get('editor.undoGroupingInterval'), ->
commandListener.call(model, event)
newCommandListeners

View File

@@ -3,21 +3,21 @@ CSON = require 'season'
module.exports =
class ScopedProperties
@load: (scopedPropertiesPath, callback) ->
@load: (scopedPropertiesPath, config, callback) ->
CSON.readFile scopedPropertiesPath, (error, scopedProperties={}) ->
if error?
callback(error)
else
callback(null, new ScopedProperties(scopedPropertiesPath, scopedProperties))
callback(null, new ScopedProperties(scopedPropertiesPath, scopedProperties, config))
constructor: (@path, @scopedProperties) ->
constructor: (@path, @scopedProperties, @config) ->
activate: ->
for selector, properties of @scopedProperties
atom.config.set(null, properties, scopeSelector: selector, source: @path)
@config.set(null, properties, scopeSelector: selector, source: @path)
return
deactivate: ->
for selector of @scopedProperties
atom.config.unset(null, scopeSelector: selector, source: @path)
@config.unset(null, scopeSelector: selector, source: @path)
return

View File

@@ -14,7 +14,7 @@ class Selection extends Model
initialScreenRange: null
wordwise: false
constructor: ({@cursor, @marker, @editor, id}) ->
constructor: ({@cursor, @marker, @editor, id, @clipboard}) ->
@emitter = new Emitter
@assignId(id)
@@ -611,7 +611,7 @@ class Selection extends Model
startLevel = @editor.indentLevelForLine(precedingText)
if maintainClipboard
{text: clipboardText, metadata} = atom.clipboard.readWithMetadata()
{text: clipboardText, metadata} = @clipboard.readWithMetadata()
metadata ?= {}
unless metadata.selections?
metadata.selections = [{
@@ -624,9 +624,9 @@ class Selection extends Model
indentBasis: startLevel,
fullLine: fullLine
})
atom.clipboard.write([clipboardText, selectionText].join("\n"), metadata)
@clipboard.write([clipboardText, selectionText].join("\n"), metadata)
else
atom.clipboard.write(selectionText, {
@clipboard.write(selectionText, {
indentBasis: startLevel,
fullLine: fullLine
})

View File

@@ -4,12 +4,16 @@ fs = require "fs-plus"
module.exports =
class StorageFolder
constructor: (containingPath) ->
@path = path.join(containingPath, "storage")
@path = path.join(containingPath, "storage") if containingPath?
store: (name, object) ->
return unless @path?
fs.writeFileSync(@pathForKey(name), JSON.stringify(object), 'utf8')
load: (name) ->
return unless @path?
statePath = @pathForKey(name)
try
stateString = fs.readFileSync(statePath, 'utf8')

View File

@@ -1,6 +1,7 @@
fs = require 'fs-plus'
path = require 'path'
{Emitter, Disposable} = require 'event-kit'
StylesElement = require './styles-element'
# Extended: A singleton instance of this class available via `atom.styles`,
# which you can use to globally query and observe the set of active style
@@ -9,7 +10,7 @@ path = require 'path'
# which clone and attach style elements in different contexts.
module.exports =
class StyleManager
constructor: ->
constructor: ({@configDirPath}) ->
@emitter = new Emitter
@styleElements = []
@styleElementsBySourcePath = {}
@@ -154,6 +155,11 @@ class StyleManager
return
buildStylesElement: ->
stylesElement = new StylesElement
stylesElement.initialize(this)
stylesElement
###
Section: Paths
###
@@ -162,8 +168,10 @@ class StyleManager
#
# Returns a {String}.
getUserStyleSheetPath: ->
stylesheetPath = fs.resolve(path.join(atom.getConfigDirPath(), 'styles'), ['css', 'less'])
return "" unless @configDirPath?
stylesheetPath = fs.resolve(path.join(@configDirPath, 'styles'), ['css', 'less'])
if fs.isFileSync(stylesheetPath)
stylesheetPath
else
path.join(atom.getConfigDirPath(), 'styles.less')
path.join(@configDirPath, 'styles.less')

View File

@@ -14,6 +14,7 @@ class StylesElement extends HTMLElement
@emitter.on 'did-update-style-element', callback
createdCallback: ->
@subscriptions = new CompositeDisposable
@emitter = new Emitter
@styleElementClonesByOriginalElement = new WeakMap
@@ -21,31 +22,29 @@ class StylesElement extends HTMLElement
if @context is 'atom-text-editor'
for styleElement in @children
@upgradeDeprecatedSelectors(styleElement)
@initialize()
@context = @getAttribute('context') ? undefined
detachedCallback: ->
@subscriptions.dispose()
@subscriptions = null
@subscriptions = new CompositeDisposable
attributeChangedCallback: (attrName, oldVal, newVal) ->
@contextChanged() if attrName is 'context'
initialize: ->
return if @subscriptions?
initialize: (@styleManager) ->
throw new Error("Must pass a styleManager parameter when initializing a StylesElement") unless @styleManager?
@subscriptions = new CompositeDisposable
@context = @getAttribute('context') ? undefined
@subscriptions.add atom.styles.observeStyleElements(@styleElementAdded.bind(this))
@subscriptions.add atom.styles.onDidRemoveStyleElement(@styleElementRemoved.bind(this))
@subscriptions.add atom.styles.onDidUpdateStyleElement(@styleElementUpdated.bind(this))
@subscriptions.add @styleManager.observeStyleElements(@styleElementAdded.bind(this))
@subscriptions.add @styleManager.onDidRemoveStyleElement(@styleElementRemoved.bind(this))
@subscriptions.add @styleManager.onDidUpdateStyleElement(@styleElementUpdated.bind(this))
contextChanged: ->
return unless @subscriptions?
@styleElementRemoved(child) for child in Array::slice.call(@children)
@context = @getAttribute('context')
@styleElementAdded(styleElement) for styleElement in atom.styles.getStyleElements()
@styleElementAdded(styleElement) for styleElement in @styleManager.getStyleElements()
return
styleElementAdded: (styleElement) ->

View File

@@ -38,15 +38,15 @@ class TextEditorComponent
Object.defineProperty @prototype, "domNode",
get: -> @domNodeValue
set: (domNode) ->
atom.assert domNode?, "TextEditorComponent::domNode was set to null."
@assert domNode?, "TextEditorComponent::domNode was set to null."
@domNodeValue = domNode
constructor: ({@editor, @hostElement, @rootElement, @stylesElement, @useShadowDOM, tileSize}) ->
constructor: ({@editor, @hostElement, @rootElement, @stylesElement, @useShadowDOM, tileSize, @views, @themes, @config, @workspace, @assert, @grammars}) ->
@tileSize = tileSize if tileSize?
@disposables = new CompositeDisposable
@observeConfig()
@setScrollSensitivity(atom.config.get('editor.scrollSensitivity'))
@setScrollSensitivity(@config.get('editor.scrollSensitivity'))
@presenter = new TextEditorPresenter
model: @editor
@@ -58,6 +58,7 @@ class TextEditorComponent
cursorBlinkPeriod: @cursorBlinkPeriod
cursorBlinkResumeDelay: @cursorBlinkResumeDelay
stoppedScrollingDelay: 200
config: @config
@presenter.onDidUpdateState(@requestUpdate)
@@ -70,10 +71,10 @@ class TextEditorComponent
insertionPoint = document.createElement('content')
insertionPoint.setAttribute('select', 'atom-overlay')
@domNode.appendChild(insertionPoint)
@overlayManager = new OverlayManager(@presenter, @hostElement)
@overlayManager = new OverlayManager(@presenter, @hostElement, @views)
else
@domNode.classList.add('editor-contents')
@overlayManager = new OverlayManager(@presenter, @domNode)
@overlayManager = new OverlayManager(@presenter, @domNode, @views)
@scrollViewNode = document.createElement('div')
@scrollViewNode.classList.add('scroll-view')
@@ -82,10 +83,10 @@ class TextEditorComponent
@hiddenInputComponent = new InputComponent
@scrollViewNode.appendChild(@hiddenInputComponent.getDomNode())
@linesComponent = new LinesComponent({@presenter, @hostElement, @useShadowDOM, @domElementPool})
@linesComponent = new LinesComponent({@presenter, @hostElement, @useShadowDOM, @domElementPool, @assert, @grammars})
@scrollViewNode.appendChild(@linesComponent.getDomNode())
@linesYardstick = new LinesYardstick(@editor, @presenter, @linesComponent)
@linesYardstick = new LinesYardstick(@editor, @presenter, @linesComponent, @grammars)
@presenter.setLinesYardstick(@linesYardstick)
@horizontalScrollbarComponent = new ScrollbarComponent({orientation: 'horizontal', onScroll: @onHorizontalScroll})
@@ -103,11 +104,11 @@ class TextEditorComponent
@disposables.add @stylesElement.onDidAddStyleElement @onStylesheetsChanged
@disposables.add @stylesElement.onDidUpdateStyleElement @onStylesheetsChanged
@disposables.add @stylesElement.onDidRemoveStyleElement @onStylesheetsChanged
unless atom.themes.isInitialLoadComplete()
@disposables.add atom.themes.onDidChangeActiveThemes @onAllThemesLoaded
unless @themes.isInitialLoadComplete()
@disposables.add @themes.onDidChangeActiveThemes @onAllThemesLoaded
@disposables.add scrollbarStyle.onDidChangePreferredScrollbarStyle @refreshScrollbars
@disposables.add atom.views.pollDocument(@pollDOM)
@disposables.add @views.pollDocument(@pollDOM)
@updateSync()
@checkForVisibilityChange()
@@ -177,7 +178,7 @@ class TextEditorComponent
@overlayManager?.measureOverlays()
mountGutterContainerComponent: ->
@gutterContainerComponent = new GutterContainerComponent({@editor, @onLineNumberGutterMouseDown, @domElementPool})
@gutterContainerComponent = new GutterContainerComponent({@editor, @onLineNumberGutterMouseDown, @domElementPool, @views})
@domNode.insertBefore(@gutterContainerComponent.getDomNode(), @domNode.firstChild)
becameVisible: ->
@@ -204,10 +205,10 @@ class TextEditorComponent
@updateSync()
else unless @updateRequested
@updateRequested = true
atom.views.updateDocument =>
@views.updateDocument =>
@updateRequested = false
@updateSync() if @canUpdate()
atom.views.readDocument(@readAfterUpdateSync)
@views.readDocument(@readAfterUpdateSync)
canUpdate: ->
@mounted and @editor.isAlive()
@@ -275,13 +276,13 @@ class TextEditorComponent
timeoutId = setTimeout(writeSelectedTextToSelectionClipboard)
observeConfig: ->
@disposables.add atom.config.onDidChange 'editor.fontSize', =>
@disposables.add @config.onDidChange 'editor.fontSize', =>
@sampleFontStyling()
@invalidateCharacterWidths()
@disposables.add atom.config.onDidChange 'editor.fontFamily', =>
@disposables.add @config.onDidChange 'editor.fontFamily', =>
@sampleFontStyling()
@invalidateCharacterWidths()
@disposables.add atom.config.onDidChange 'editor.lineHeight', =>
@disposables.add @config.onDidChange 'editor.lineHeight', =>
@sampleFontStyling()
@invalidateCharacterWidths()
@@ -294,7 +295,7 @@ class TextEditorComponent
@disposables.add(@scopedConfigDisposables)
scope = @editor.getRootScopeDescriptor()
@scopedConfigDisposables.add atom.config.observe 'editor.scrollSensitivity', {scope}, @setScrollSensitivity
@scopedConfigDisposables.add @config.observe 'editor.scrollSensitivity', {scope}, @setScrollSensitivity
focused: ->
if @mounted
@@ -354,11 +355,11 @@ class TextEditorComponent
{wheelDeltaX, wheelDeltaY} = event
# Ctrl+MouseWheel adjusts font size.
if event.ctrlKey and atom.config.get('editor.zoomFontWhenCtrlScrolling')
if event.ctrlKey and @config.get('editor.zoomFontWhenCtrlScrolling')
if wheelDeltaY > 0
atom.workspace.increaseFontSize()
@workspace.increaseFontSize()
else if wheelDeltaY < 0
atom.workspace.decreaseFontSize()
@workspace.decreaseFontSize()
event.preventDefault()
return
@@ -420,6 +421,9 @@ class TextEditorComponent
getScrollWidth: ->
@presenter.getScrollWidth()
getMaxScrollTop: ->
@presenter.getMaxScrollTop()
getVerticalScrollbarWidth: ->
@presenter.getVerticalScrollbarWidth()
@@ -543,7 +547,7 @@ class TextEditorComponent
onStylesheetsChanged: (styleElement) =>
return unless @performedInitialMeasurement
return unless atom.themes.isInitialLoadComplete()
return unless @themes.isInitialLoadComplete()
# This delay prevents the styling from going haywire when stylesheets are
# reloaded in dev mode. It seems like a workaround for a browser bug, but
@@ -650,7 +654,7 @@ class TextEditorComponent
isVisible: ->
# Investigating an exception that occurs here due to ::domNode being null.
atom.assert @domNode?, "TextEditorComponent::domNode was null.", (error) =>
@assert @domNode?, "TextEditorComponent::domNode was null.", (error) =>
error.metadata = {@initialized}
@domNode? and (@domNode.offsetHeight > 0 or @domNode.offsetWidth > 0)
@@ -848,7 +852,7 @@ class TextEditorComponent
@presenter.characterWidthsChanged()
setShowIndentGuide: (showIndentGuide) ->
atom.config.set("editor.showIndentGuide", showIndentGuide)
@config.set("editor.showIndentGuide", showIndentGuide)
setScrollSensitivity: (scrollSensitivity) =>
if scrollSensitivity = parseInt(scrollSensitivity)

View File

@@ -4,6 +4,7 @@ Path = require 'path'
TextBuffer = require 'text-buffer'
TextEditor = require './text-editor'
TextEditorComponent = require './text-editor-component'
StylesElement = require './styles-element'
ShadowStyleSheet = null
@@ -18,29 +19,38 @@ class TextEditorElement extends HTMLElement
logicalDisplayBuffer: true
createdCallback: ->
# Use globals when the following instance variables aren't set.
@config = atom.config
@themes = atom.themes
@workspace = atom.workspace
@assert = atom.assert
@views = atom.views
@styles = atom.styles
@grammars = atom.grammars
@emitter = new Emitter
@subscriptions = new CompositeDisposable
@initializeContent()
@addEventListener 'focus', @focused.bind(this)
@addEventListener 'blur', @blurred.bind(this)
initializeContent: (attributes) ->
@classList.add('editor')
@setAttribute('tabindex', -1)
if atom.config.get('editor.useShadowDOM')
initializeContent: (attributes) ->
if @config.get('editor.useShadowDOM')
@useShadowDOM = true
unless ShadowStyleSheet?
ShadowStyleSheet = document.createElement('style')
ShadowStyleSheet.textContent = atom.themes.loadLessStylesheet(require.resolve('../static/text-editor-shadow.less'))
ShadowStyleSheet.textContent = @themes.loadLessStylesheet(require.resolve('../static/text-editor-shadow.less'))
@createShadowRoot()
@shadowRoot.appendChild(ShadowStyleSheet.cloneNode(true))
@stylesElement = document.createElement('atom-styles')
@stylesElement = new StylesElement
@stylesElement.initialize(@styles)
@stylesElement.setAttribute('context', 'atom-text-editor')
@stylesElement.initialize()
@rootElement = document.createElement('div')
@rootElement.classList.add('editor--private')
@@ -56,7 +66,7 @@ class TextEditorElement extends HTMLElement
attachedCallback: ->
@buildModel() unless @getModel()?
atom.assert(@model.isAlive(), "Attaching a view for a destroyed editor")
@assert(@model.isAlive(), "Attaching a view for a destroyed editor")
@mountComponent() unless @component?
@listenForComponentEvents()
@component.checkForVisibilityChange()
@@ -76,7 +86,15 @@ class TextEditorElement extends HTMLElement
@subscriptions.add @component.onDidChangeScrollLeft =>
@emitter.emit("did-change-scroll-left", arguments...)
initialize: (model) ->
initialize: (model, {@views, @config, @themes, @workspace, @assert, @styles, @grammars}) ->
throw new Error("Must pass a config parameter when initializing TextEditorElements") unless @views?
throw new Error("Must pass a config parameter when initializing TextEditorElements") unless @config?
throw new Error("Must pass a themes parameter when initializing TextEditorElements") unless @themes?
throw new Error("Must pass a workspace parameter when initializing TextEditorElements") unless @workspace?
throw new Error("Must pass a assert parameter when initializing TextEditorElements") unless @assert?
throw new Error("Must pass a styles parameter when initializing TextEditorElements") unless @styles?
throw new Error("Must pass a grammars parameter when initializing TextEditorElements") unless @grammars?
@setModel(model)
this
@@ -85,6 +103,7 @@ class TextEditorElement extends HTMLElement
return if model.isDestroyed()
@model = model
@initializeContent()
@mountComponent()
@addGrammarScopeAttribute()
@addMiniAttribute() if @model.isMini()
@@ -99,7 +118,7 @@ class TextEditorElement extends HTMLElement
@model ? @buildModel()
buildModel: ->
@setModel(new TextEditor(
@setModel(@workspace.buildTextEditor(
buffer: new TextBuffer(@textContent)
softWrapped: false
tabLength: 2
@@ -117,6 +136,12 @@ class TextEditorElement extends HTMLElement
editor: @model
tileSize: @tileSize
useShadowDOM: @useShadowDOM
views: @views
themes: @themes
config: @config
workspace: @workspace
assert: @assert
grammars: @grammars
)
@rootElement.appendChild(@component.getDomNode())
@@ -185,6 +210,12 @@ class TextEditorElement extends HTMLElement
getDefaultCharacterWidth: ->
@getModel().getDefaultCharWidth()
# Extended: Get the maximum scroll top that can be applied to this element.
#
# Returns a {Number} of pixels.
getMaxScrollTop: ->
@component?.getMaxScrollTop()
# Extended: Converts a buffer position to a pixel position.
#
# * `bufferPosition` An object that represents a buffer position. It can be either
@@ -313,141 +344,4 @@ class TextEditorElement extends HTMLElement
getHeight: ->
@offsetHeight
stopEventPropagation = (commandListeners) ->
newCommandListeners = {}
for commandName, commandListener of commandListeners
do (commandListener) ->
newCommandListeners[commandName] = (event) ->
event.stopPropagation()
commandListener.call(@getModel(), event)
newCommandListeners
stopEventPropagationAndGroupUndo = (commandListeners) ->
newCommandListeners = {}
for commandName, commandListener of commandListeners
do (commandListener) ->
newCommandListeners[commandName] = (event) ->
event.stopPropagation()
model = @getModel()
model.transact atom.config.get('editor.undoGroupingInterval'), ->
commandListener.call(model, event)
newCommandListeners
atom.commands.add 'atom-text-editor', stopEventPropagation(
'core:undo': -> @undo()
'core:redo': -> @redo()
'core:move-left': -> @moveLeft()
'core:move-right': -> @moveRight()
'core:select-left': -> @selectLeft()
'core:select-right': -> @selectRight()
'core:select-up': -> @selectUp()
'core:select-down': -> @selectDown()
'core:select-all': -> @selectAll()
'editor:select-word': -> @selectWordsContainingCursors()
'editor:consolidate-selections': (event) -> event.abortKeyBinding() unless @consolidateSelections()
'editor:move-to-beginning-of-next-paragraph': -> @moveToBeginningOfNextParagraph()
'editor:move-to-beginning-of-previous-paragraph': -> @moveToBeginningOfPreviousParagraph()
'editor:move-to-beginning-of-screen-line': -> @moveToBeginningOfScreenLine()
'editor:move-to-beginning-of-line': -> @moveToBeginningOfLine()
'editor:move-to-end-of-screen-line': -> @moveToEndOfScreenLine()
'editor:move-to-end-of-line': -> @moveToEndOfLine()
'editor:move-to-first-character-of-line': -> @moveToFirstCharacterOfLine()
'editor:move-to-beginning-of-word': -> @moveToBeginningOfWord()
'editor:move-to-end-of-word': -> @moveToEndOfWord()
'editor:move-to-beginning-of-next-word': -> @moveToBeginningOfNextWord()
'editor:move-to-previous-word-boundary': -> @moveToPreviousWordBoundary()
'editor:move-to-next-word-boundary': -> @moveToNextWordBoundary()
'editor:move-to-previous-subword-boundary': -> @moveToPreviousSubwordBoundary()
'editor:move-to-next-subword-boundary': -> @moveToNextSubwordBoundary()
'editor:select-to-beginning-of-next-paragraph': -> @selectToBeginningOfNextParagraph()
'editor:select-to-beginning-of-previous-paragraph': -> @selectToBeginningOfPreviousParagraph()
'editor:select-to-end-of-line': -> @selectToEndOfLine()
'editor:select-to-beginning-of-line': -> @selectToBeginningOfLine()
'editor:select-to-end-of-word': -> @selectToEndOfWord()
'editor:select-to-beginning-of-word': -> @selectToBeginningOfWord()
'editor:select-to-beginning-of-next-word': -> @selectToBeginningOfNextWord()
'editor:select-to-next-word-boundary': -> @selectToNextWordBoundary()
'editor:select-to-previous-word-boundary': -> @selectToPreviousWordBoundary()
'editor:select-to-next-subword-boundary': -> @selectToNextSubwordBoundary()
'editor:select-to-previous-subword-boundary': -> @selectToPreviousSubwordBoundary()
'editor:select-to-first-character-of-line': -> @selectToFirstCharacterOfLine()
'editor:select-line': -> @selectLinesContainingCursors()
)
atom.commands.add 'atom-text-editor', stopEventPropagationAndGroupUndo(
'core:backspace': -> @backspace()
'core:delete': -> @delete()
'core:cut': -> @cutSelectedText()
'core:copy': -> @copySelectedText()
'core:paste': -> @pasteText()
'editor:delete-to-previous-word-boundary': -> @deleteToPreviousWordBoundary()
'editor:delete-to-next-word-boundary': -> @deleteToNextWordBoundary()
'editor:delete-to-beginning-of-word': -> @deleteToBeginningOfWord()
'editor:delete-to-beginning-of-line': -> @deleteToBeginningOfLine()
'editor:delete-to-end-of-line': -> @deleteToEndOfLine()
'editor:delete-to-end-of-word': -> @deleteToEndOfWord()
'editor:delete-to-beginning-of-subword': -> @deleteToBeginningOfSubword()
'editor:delete-to-end-of-subword': -> @deleteToEndOfSubword()
'editor:delete-line': -> @deleteLine()
'editor:cut-to-end-of-line': -> @cutToEndOfLine()
'editor:cut-to-end-of-buffer-line': -> @cutToEndOfBufferLine()
'editor:transpose': -> @transpose()
'editor:upper-case': -> @upperCase()
'editor:lower-case': -> @lowerCase()
'editor:copy-selection': -> @copyOnlySelectedText()
)
atom.commands.add 'atom-text-editor:not([mini])', stopEventPropagation(
'core:move-up': -> @moveUp()
'core:move-down': -> @moveDown()
'core:move-to-top': -> @moveToTop()
'core:move-to-bottom': -> @moveToBottom()
'core:page-up': -> @pageUp()
'core:page-down': -> @pageDown()
'core:select-to-top': -> @selectToTop()
'core:select-to-bottom': -> @selectToBottom()
'core:select-page-up': -> @selectPageUp()
'core:select-page-down': -> @selectPageDown()
'editor:add-selection-below': -> @addSelectionBelow()
'editor:add-selection-above': -> @addSelectionAbove()
'editor:split-selections-into-lines': -> @splitSelectionsIntoLines()
'editor:toggle-soft-tabs': -> @toggleSoftTabs()
'editor:toggle-soft-wrap': -> @toggleSoftWrapped()
'editor:fold-all': -> @foldAll()
'editor:unfold-all': -> @unfoldAll()
'editor:fold-current-row': -> @foldCurrentRow()
'editor:unfold-current-row': -> @unfoldCurrentRow()
'editor:fold-selection': -> @foldSelectedLines()
'editor:fold-at-indent-level-1': -> @foldAllAtIndentLevel(0)
'editor:fold-at-indent-level-2': -> @foldAllAtIndentLevel(1)
'editor:fold-at-indent-level-3': -> @foldAllAtIndentLevel(2)
'editor:fold-at-indent-level-4': -> @foldAllAtIndentLevel(3)
'editor:fold-at-indent-level-5': -> @foldAllAtIndentLevel(4)
'editor:fold-at-indent-level-6': -> @foldAllAtIndentLevel(5)
'editor:fold-at-indent-level-7': -> @foldAllAtIndentLevel(6)
'editor:fold-at-indent-level-8': -> @foldAllAtIndentLevel(7)
'editor:fold-at-indent-level-9': -> @foldAllAtIndentLevel(8)
'editor:log-cursor-scope': -> @logCursorScope()
'editor:copy-path': -> @copyPathToClipboard()
'editor:toggle-indent-guide': -> atom.config.set('editor.showIndentGuide', not atom.config.get('editor.showIndentGuide'))
'editor:toggle-line-numbers': -> atom.config.set('editor.showLineNumbers', not atom.config.get('editor.showLineNumbers'))
'editor:scroll-to-cursor': -> @scrollToCursorPosition()
)
atom.commands.add 'atom-text-editor:not([mini])', stopEventPropagationAndGroupUndo(
'editor:indent': -> @indent()
'editor:auto-indent': -> @autoIndentSelectedRows()
'editor:indent-selected-rows': -> @indentSelectedRows()
'editor:outdent-selected-rows': -> @outdentSelectedRows()
'editor:newline': -> @insertNewline()
'editor:newline-below': -> @insertNewlineBelow()
'editor:newline-above': -> @insertNewlineAbove()
'editor:toggle-line-comments': -> @toggleLineCommentsInSelection()
'editor:checkout-head-revision': -> @checkoutHeadRevision()
'editor:move-line-up': -> @moveLineUp()
'editor:move-line-down': -> @moveLineDown()
'editor:duplicate-lines': -> @duplicateLines()
'editor:join-lines': -> @joinLines()
)
module.exports = TextEditorElement = document.registerElement 'atom-text-editor', prototype: TextEditorElement.prototype

View File

@@ -13,7 +13,7 @@ class TextEditorPresenter
minimumReflowInterval: 200
constructor: (params) ->
{@model, @autoHeight, @explicitHeight, @contentFrameWidth, @scrollTop, @scrollLeft, @scrollColumn, @scrollRow, @boundingClientRect, @windowWidth, @windowHeight, @gutterWidth} = params
{@model, @config, @autoHeight, @explicitHeight, @contentFrameWidth, @scrollTop, @scrollLeft, @scrollColumn, @scrollRow, @boundingClientRect, @windowWidth, @windowHeight, @gutterWidth} = params
{horizontalScrollbarHeight, verticalScrollbarWidth} = params
{@lineHeight, @baseCharacterWidth, @backgroundColor, @gutterBackgroundColor, @tileSize} = params
{@cursorBlinkPeriod, @cursorBlinkResumeDelay, @stoppedScrollingDelay, @focused} = params
@@ -22,6 +22,8 @@ class TextEditorPresenter
@gutterWidth ?= 0
@tileSize ?= 6
@realScrollTop = @scrollTop
@realScrollLeft = @scrollLeft
@disposables = new CompositeDisposable
@emitter = new Emitter
@visibleHighlights = {}
@@ -223,9 +225,9 @@ class TextEditorPresenter
observeConfig: ->
configParams = {scope: @model.getRootScopeDescriptor()}
@scrollPastEnd = atom.config.get('editor.scrollPastEnd', configParams)
@showLineNumbers = atom.config.get('editor.showLineNumbers', configParams)
@showIndentGuide = atom.config.get('editor.showIndentGuide', configParams)
@scrollPastEnd = @config.get('editor.scrollPastEnd', configParams)
@showLineNumbers = @config.get('editor.showLineNumbers', configParams)
@showIndentGuide = @config.get('editor.showIndentGuide', configParams)
if @configDisposables?
@configDisposables?.dispose()
@@ -234,19 +236,19 @@ class TextEditorPresenter
@configDisposables = new CompositeDisposable
@disposables.add(@configDisposables)
@configDisposables.add atom.config.onDidChange 'editor.showIndentGuide', configParams, ({newValue}) =>
@configDisposables.add @config.onDidChange 'editor.showIndentGuide', configParams, ({newValue}) =>
@showIndentGuide = newValue
@shouldUpdateContentState = true
@emitDidUpdateState()
@configDisposables.add atom.config.onDidChange 'editor.scrollPastEnd', configParams, ({newValue}) =>
@configDisposables.add @config.onDidChange 'editor.scrollPastEnd', configParams, ({newValue}) =>
@scrollPastEnd = newValue
@shouldUpdateVerticalScrollState = true
@shouldUpdateScrollbarsState = true
@updateScrollHeight()
@emitDidUpdateState()
@configDisposables.add atom.config.onDidChange 'editor.showLineNumbers', configParams, ({newValue}) =>
@configDisposables.add @config.onDidChange 'editor.showLineNumbers', configParams, ({newValue}) =>
@showLineNumbers = newValue
@shouldUpdateLineNumberGutterState = true
@shouldUpdateGutterOrderState = true
@@ -775,7 +777,7 @@ class TextEditorPresenter
updateScrollTop: (scrollTop) ->
scrollTop = @constrainScrollTop(scrollTop)
if scrollTop isnt @scrollTop and not Number.isNaN(scrollTop)
if scrollTop isnt @realScrollTop and not Number.isNaN(scrollTop)
@realScrollTop = scrollTop
@scrollTop = Math.round(scrollTop)
@scrollRow = Math.round(@scrollTop / @lineHeight)
@@ -792,7 +794,7 @@ class TextEditorPresenter
updateScrollLeft: (scrollLeft) ->
scrollLeft = @constrainScrollLeft(scrollLeft)
if scrollLeft isnt @scrollLeft and not Number.isNaN(scrollLeft)
if scrollLeft isnt @realScrollLeft and not Number.isNaN(scrollLeft)
@realScrollLeft = scrollLeft
@scrollLeft = Math.round(scrollLeft)
@scrollColumn = Math.round(@scrollLeft / @baseCharacterWidth)
@@ -980,6 +982,13 @@ class TextEditorPresenter
getScrollWidth: ->
@scrollWidth
getMaxScrollTop: ->
scrollHeight = @getScrollHeight()
clientHeight = @getClientHeight()
return 0 unless scrollHeight? and clientHeight?
scrollHeight - clientHeight
setHorizontalScrollbarHeight: (horizontalScrollbarHeight) ->
unless @measuredHorizontalScrollbarHeight is horizontalScrollbarHeight
oldHorizontalScrollbarHeight = @measuredHorizontalScrollbarHeight
@@ -1120,10 +1129,13 @@ class TextEditorPresenter
@mouseWheelScreenRow = screenRow
@didStartScrolling()
setBaseCharacterWidth: (baseCharacterWidth) ->
unless @baseCharacterWidth is baseCharacterWidth
setBaseCharacterWidth: (baseCharacterWidth, doubleWidthCharWidth, halfWidthCharWidth, koreanCharWidth) ->
unless @baseCharacterWidth is baseCharacterWidth and @doubleWidthCharWidth is doubleWidthCharWidth and @halfWidthCharWidth is halfWidthCharWidth and koreanCharWidth is @koreanCharWidth
@baseCharacterWidth = baseCharacterWidth
@model.setDefaultCharWidth(baseCharacterWidth)
@doubleWidthCharWidth = doubleWidthCharWidth
@halfWidthCharWidth = halfWidthCharWidth
@koreanCharWidth = koreanCharWidth
@model.setDefaultCharWidth(baseCharacterWidth, doubleWidthCharWidth, halfWidthCharWidth, koreanCharWidth)
@characterWidthsChanged()
characterWidthsChanged: ->

View File

@@ -54,10 +54,7 @@ GutterContainer = require './gutter-container'
# soft wraps and folds to ensure your code interacts with them correctly.
module.exports =
class TextEditor extends Model
atom.deserializers.add(this)
callDisplayBufferCreatedHook: false
registerEditor: false
buffer: null
languageMode: null
cursors: null
@@ -67,9 +64,9 @@ class TextEditor extends Model
selectionFlashDuration: 500
gutterContainer: null
@deserialize: (state) ->
@deserialize: (state, atomEnvironment) ->
try
displayBuffer = DisplayBuffer.deserialize(state.displayBuffer)
displayBuffer = DisplayBuffer.deserialize(state.displayBuffer, atomEnvironment)
catch error
if error.syscall is 'read'
return # Error reading the file, don't deserialize an editor for it
@@ -77,19 +74,46 @@ class TextEditor extends Model
throw error
state.displayBuffer = displayBuffer
state.registerEditor = true
state.config = atomEnvironment.config
state.notificationManager = atomEnvironment.notifications
state.packageManager = atomEnvironment.packages
state.clipboard = atomEnvironment.clipboard
state.viewRegistry = atomEnvironment.views
state.grammarRegistry = atomEnvironment.grammars
state.project = atomEnvironment.project
state.assert = atomEnvironment.assert.bind(atomEnvironment)
state.applicationDelegate = atomEnvironment.applicationDelegate
new this(state)
constructor: ({@softTabs, @scrollRow, @scrollColumn, initialLine, initialColumn, tabLength, softWrapped, @displayBuffer, buffer, registerEditor, suppressCursorCreation, @mini, @placeholderText, lineNumberGutterVisible, largeFileMode}={}) ->
constructor: (params={}) ->
super
{
@softTabs, @scrollRow, @scrollColumn, initialLine, initialColumn, tabLength,
softWrapped, @displayBuffer, buffer, suppressCursorCreation, @mini, @placeholderText,
lineNumberGutterVisible, largeFileMode, @config, @notificationManager, @packageManager,
@clipboard, @viewRegistry, @grammarRegistry, @project, @assert, @applicationDelegate
} = params
throw new Error("Must pass a config parameter when constructing TextEditors") unless @config?
throw new Error("Must pass a notificationManager parameter when constructing TextEditors") unless @notificationManager?
throw new Error("Must pass a packageManager parameter when constructing TextEditors") unless @packageManager?
throw new Error("Must pass a clipboard parameter when constructing TextEditors") unless @clipboard?
throw new Error("Must pass a viewRegistry parameter when constructing TextEditors") unless @viewRegistry?
throw new Error("Must pass a grammarRegistry parameter when constructing TextEditors") unless @grammarRegistry?
throw new Error("Must pass a project parameter when constructing TextEditors") unless @project?
throw new Error("Must pass an assert parameter when constructing TextEditors") unless @assert?
@emitter = new Emitter
@disposables = new CompositeDisposable
@cursors = []
@selections = []
buffer ?= new TextBuffer
@displayBuffer ?= new DisplayBuffer({buffer, tabLength, softWrapped, ignoreInvisibles: @mini, largeFileMode})
@displayBuffer ?= new DisplayBuffer({
buffer, tabLength, softWrapped, ignoreInvisibles: @mini, largeFileMode,
@config, @assert, @grammarRegistry, @packageManager
})
@buffer = @displayBuffer.buffer
for marker in @findMarkers(@getSelectionMarkerAttributes())
@@ -105,9 +129,9 @@ class TextEditor extends Model
initialColumn = Math.max(parseInt(initialColumn) or 0, 0)
@addCursorAtBufferPosition([initialLine, initialColumn])
@languageMode = new LanguageMode(this)
@languageMode = new LanguageMode(this, @config)
@setEncoding(atom.config.get('core.fileEncoding', scope: @getRootScopeDescriptor()))
@setEncoding(@config.get('core.fileEncoding', scope: @getRootScopeDescriptor()))
@gutterContainer = new GutterContainer(this)
@lineNumberGutter = @gutterContainer.addGutter
@@ -115,8 +139,6 @@ class TextEditor extends Model
priority: 0
visible: lineNumberGutterVisible
atom.workspace?.editorAdded(this) if registerEditor
serialize: ->
deserializer: 'TextEditor'
id: @id
@@ -128,8 +150,8 @@ class TextEditor extends Model
subscribeToBuffer: ->
@buffer.retain()
@disposables.add @buffer.onDidChangePath =>
unless atom.project.getPaths().length > 0
atom.project.setPaths([path.dirname(@getPath())])
unless @project.getPaths().length > 0
@project.setPaths([path.dirname(@getPath())])
@emitter.emit 'did-change-title', @getTitle()
@emitter.emit 'did-change-path', @getPath()
@disposables.add @buffer.onDidChangeEncoding =>
@@ -148,7 +170,7 @@ class TextEditor extends Model
subscribeToTabTypeConfig: ->
@tabTypeSubscription?.dispose()
@tabTypeSubscription = atom.config.observe 'editor.tabType', scope: @getRootScopeDescriptor(), =>
@tabTypeSubscription = @config.observe 'editor.tabType', scope: @getRootScopeDescriptor(), =>
@softTabs = @shouldUseSoftTabs(defaultValue: @softTabs)
destroyed: ->
@@ -429,12 +451,12 @@ class TextEditor extends Model
onDidChangeScrollTop: (callback) ->
Grim.deprecate("This is now a view method. Call TextEditorElement::onDidChangeScrollTop instead.")
atom.views.getView(this).onDidChangeScrollTop(callback)
@viewRegistry.getView(this).onDidChangeScrollTop(callback)
onDidChangeScrollLeft: (callback) ->
Grim.deprecate("This is now a view method. Call TextEditorElement::onDidChangeScrollLeft instead.")
atom.views.getView(this).onDidChangeScrollLeft(callback)
@viewRegistry.getView(this).onDidChangeScrollLeft(callback)
onDidRequestAutoscroll: (callback) ->
@displayBuffer.onDidRequestAutoscroll(callback)
@@ -456,7 +478,11 @@ class TextEditor extends Model
copy: ->
displayBuffer = @displayBuffer.copy()
softTabs = @getSoftTabs()
newEditor = new TextEditor({@buffer, displayBuffer, @tabLength, softTabs, suppressCursorCreation: true, registerEditor: true})
newEditor = new TextEditor({
@buffer, displayBuffer, @tabLength, softTabs, suppressCursorCreation: true,
@config, @notificationManager, @packageManager, @clipboard, @viewRegistry,
@grammarRegistry, @project, @assert, @applicationDelegate
})
for marker in @findMarkers(editorId: @id)
marker.copy(editorId: newEditor.id, preserveFolds: true)
newEditor
@@ -547,6 +573,45 @@ class TextEditor extends Model
else
'untitled'
# Essential: Get unique title for display in other parts of the UI
# such as the window title.
#
# If the editor's buffer is unsaved, its title is "untitled"
# If the editor's buffer is saved, its unique title is formatted as one
# of the following,
# * "<filename>" when it is the only editing buffer with this file name.
# * "<unique-dir-prefix>/.../<filename>", where the "..." may be omitted
# if the the direct parent directory is already different.
#
# Returns a {String}
getUniqueTitle: ->
if sessionPath = @getPath()
title = @getTitle()
# find text editors with identical file name.
paths = []
for textEditor in atom.workspace.getTextEditors() when textEditor isnt this
if textEditor.getTitle() is title
paths.push(textEditor.getPath())
if paths.length is 0
return title
fileName = path.basename(sessionPath)
# find the first directory in all these paths that is unique
nLevel = 0
while (_.some(paths, (apath) -> path.basename(apath) is path.basename(sessionPath)))
sessionPath = path.dirname(sessionPath)
paths = _.map(paths, (apath) -> path.dirname(apath))
nLevel += 1
directory = path.basename sessionPath
if nLevel > 1
path.join(directory, "...", fileName)
else
path.join(directory, fileName)
else
'untitled'
# Essential: Get the editor's long title for display in other parts of the UI
# such as the window title.
#
@@ -557,7 +622,7 @@ class TextEditor extends Model
getLongTitle: ->
if sessionPath = @getPath()
fileName = path.basename(sessionPath)
directory = atom.project.relativize(path.dirname(sessionPath))
directory = @project.relativize(path.dirname(sessionPath))
directory = if directory.length > 0 then directory else path.basename(path.dirname(sessionPath))
"#{fileName} - #{directory}"
else
@@ -585,7 +650,7 @@ class TextEditor extends Model
# Copies the current file path to the native clipboard.
copyPathToClipboard: ->
if filePath = @getPath()
atom.clipboard.write(filePath)
@clipboard.write(filePath)
###
Section: File Operations
@@ -594,14 +659,14 @@ class TextEditor extends Model
# Essential: Saves the editor's text buffer.
#
# See {TextBuffer::save} for more details.
save: -> @buffer.save(backup: atom.config.get('editor.backUpBeforeSaving'))
save: -> @buffer.save(backup: @config.get('editor.backUpBeforeSaving'))
# Essential: Saves the editor's text buffer as the given path.
#
# See {TextBuffer::saveAs} for more details.
#
# * `filePath` A {String} path.
saveAs: (filePath) -> @buffer.saveAs(filePath, backup: atom.config.get('editor.backUpBeforeSaving'))
saveAs: (filePath) -> @buffer.saveAs(filePath, backup: @config.get('editor.backUpBeforeSaving'))
# Determine whether the user should be prompted to save before closing
# this editor.
@@ -617,9 +682,20 @@ class TextEditor extends Model
checkoutHeadRevision: ->
if filePath = this.getPath()
atom.project.repositoryForDirectory(new Directory(path.dirname(filePath)))
.then (repository) =>
repository?.checkoutHeadForEditor(this)
checkoutHead = =>
@project.repositoryForDirectory(new Directory(path.dirname(filePath)))
.then (repository) =>
repository?.checkoutHeadForEditor(this)
if @config.get('editor.confirmCheckoutHeadRevision')
@applicationDelegate.confirm
message: 'Confirm Checkout HEAD Revision'
detailedMessage: "Are you sure you want to discard all changes to \"#{path.basename(filePath)}\" since the last Git commit?"
buttons:
OK: checkoutHead
Cancel: null
else
checkoutHead()
else
Promise.resolve(false)
@@ -748,7 +824,7 @@ class TextEditor extends Model
return false unless @emitWillInsertTextEvent(text)
groupingInterval = if options.groupUndo
atom.config.get('editor.undoGroupingInterval')
@config.get('editor.undoGroupingInterval')
else
0
@@ -1765,7 +1841,7 @@ class TextEditor extends Model
# Add a cursor based on the given {Marker}.
addCursor: (marker) ->
cursor = new Cursor(editor: this, marker: marker)
cursor = new Cursor(editor: this, marker: marker, config: @config)
@cursors.push(cursor)
@decorateMarker(marker, type: 'line-number', class: 'cursor-line')
@decorateMarker(marker, type: 'line-number', class: 'cursor-line-no-selection', onlyHead: true, onlyEmpty: true)
@@ -2248,7 +2324,7 @@ class TextEditor extends Model
unless marker.getProperties().preserveFolds
@destroyFoldsContainingBufferRange(marker.getBufferRange())
cursor = @addCursor(marker)
selection = new Selection(_.extend({editor: this, marker, cursor}, options))
selection = new Selection(_.extend({editor: this, marker, cursor, @clipboard}, options))
@selections.push(selection)
selectionBufferRange = selection.getBufferRange()
@mergeIntersectingSelections(preserveFolds: marker.getProperties().preserveFolds)
@@ -2405,10 +2481,10 @@ class TextEditor extends Model
#
# Returns a {Boolean}
shouldUseSoftTabs: ({defaultValue}) ->
tabType = atom.config.get('editor.tabType', scope: @getRootScopeDescriptor())
tabType = @config.get('editor.tabType', scope: @getRootScopeDescriptor())
switch tabType
when 'auto'
@usesSoftTabs() ? defaultValue ? atom.config.get('editor.softTabs') ? true
@usesSoftTabs() ? defaultValue ? @config.get('editor.softTabs') ? true
when 'hard'
false
when 'soft'
@@ -2584,7 +2660,7 @@ class TextEditor extends Model
list = list.map (item) -> "* #{item}"
content = "Scopes at Cursor\n#{list.join('\n')}"
atom.notifications.addInfo(content, dismissable: true)
@notificationManager.addInfo(content, dismissable: true)
# {Delegates to: DisplayBuffer.tokenForBufferPosition}
tokenForBufferPosition: (bufferPosition) -> @displayBuffer.tokenForBufferPosition(bufferPosition)
@@ -2636,7 +2712,7 @@ class TextEditor extends Model
#
# * `options` (optional) See {Selection::insertText}.
pasteText: (options={}) ->
{text: clipboardText, metadata} = atom.clipboard.readWithMetadata()
{text: clipboardText, metadata} = @clipboard.readWithMetadata()
return false unless @emitWillInsertTextEvent(clipboardText)
metadata ?= {}
@@ -2889,24 +2965,24 @@ class TextEditor extends Model
scrollToTop: ->
Grim.deprecate("This is now a view method. Call TextEditorElement::scrollToTop instead.")
atom.views.getView(this).scrollToTop()
@viewRegistry.getView(this).scrollToTop()
scrollToBottom: ->
Grim.deprecate("This is now a view method. Call TextEditorElement::scrollToTop instead.")
atom.views.getView(this).scrollToBottom()
@viewRegistry.getView(this).scrollToBottom()
scrollToScreenRange: (screenRange, options) -> @displayBuffer.scrollToScreenRange(screenRange, options)
getHorizontalScrollbarHeight: ->
Grim.deprecate("This is now a view method. Call TextEditorElement::getHorizontalScrollbarHeight instead.")
atom.views.getView(this).getHorizontalScrollbarHeight()
@viewRegistry.getView(this).getHorizontalScrollbarHeight()
getVerticalScrollbarWidth: ->
Grim.deprecate("This is now a view method. Call TextEditorElement::getVerticalScrollbarWidth instead.")
atom.views.getView(this).getVerticalScrollbarWidth()
@viewRegistry.getView(this).getVerticalScrollbarWidth()
pageUp: ->
@moveUp(@getRowsPerPage())
@@ -2931,10 +3007,10 @@ class TextEditor extends Model
###
shouldAutoIndent: ->
atom.config.get("editor.autoIndent", scope: @getRootScopeDescriptor())
@config.get("editor.autoIndent", scope: @getRootScopeDescriptor())
shouldAutoIndentOnPaste: ->
atom.config.get("editor.autoIndentOnPaste", scope: @getRootScopeDescriptor())
@config.get("editor.autoIndentOnPaste", scope: @getRootScopeDescriptor())
###
Section: Event Handlers
@@ -2973,19 +3049,19 @@ class TextEditor extends Model
getFirstVisibleScreenRow: ->
deprecate("This is now a view method. Call TextEditorElement::getFirstVisibleScreenRow instead.")
atom.views.getView(this).getVisibleRowRange()[0]
@viewRegistry.getView(this).getVisibleRowRange()[0]
getLastVisibleScreenRow: ->
Grim.deprecate("This is now a view method. Call TextEditorElement::getLastVisibleScreenRow instead.")
atom.views.getView(this).getVisibleRowRange()[1]
@viewRegistry.getView(this).getVisibleRowRange()[1]
pixelPositionForBufferPosition: (bufferPosition) ->
Grim.deprecate("This method is deprecated on the model layer. Use `TextEditorElement::pixelPositionForBufferPosition` instead")
atom.views.getView(this).pixelPositionForBufferPosition(bufferPosition)
@viewRegistry.getView(this).pixelPositionForBufferPosition(bufferPosition)
pixelPositionForScreenPosition: (screenPosition) ->
Grim.deprecate("This method is deprecated on the model layer. Use `TextEditorElement::pixelPositionForScreenPosition` instead")
atom.views.getView(this).pixelPositionForScreenPosition(screenPosition)
@viewRegistry.getView(this).pixelPositionForScreenPosition(screenPosition)
getSelectionMarkerAttributes: ->
{type: 'selection', editorId: @id, invalidate: 'never', maintainHistory: true}
@@ -2999,15 +3075,22 @@ class TextEditor extends Model
getLineHeightInPixels: -> @displayBuffer.getLineHeightInPixels()
setLineHeightInPixels: (lineHeightInPixels) -> @displayBuffer.setLineHeightInPixels(lineHeightInPixels)
getKoreanCharWidth: -> @displayBuffer.getKoreanCharWidth()
getHalfWidthCharWidth: -> @displayBuffer.getHalfWidthCharWidth()
getDoubleWidthCharWidth: -> @displayBuffer.getDoubleWidthCharWidth()
getDefaultCharWidth: -> @displayBuffer.getDefaultCharWidth()
setDefaultCharWidth: (defaultCharWidth) -> @displayBuffer.setDefaultCharWidth(defaultCharWidth)
setDefaultCharWidth: (defaultCharWidth, doubleWidthCharWidth, halfWidthCharWidth, koreanCharWidth) ->
@displayBuffer.setDefaultCharWidth(defaultCharWidth, doubleWidthCharWidth, halfWidthCharWidth, koreanCharWidth)
setHeight: (height, reentrant=false) ->
if reentrant
@displayBuffer.setHeight(height)
else
Grim.deprecate("This is now a view method. Call TextEditorElement::setHeight instead.")
atom.views.getView(this).setHeight(height)
@viewRegistry.getView(this).setHeight(height)
getHeight: ->
Grim.deprecate("This is now a view method. Call TextEditorElement::getHeight instead.")
@@ -3020,7 +3103,7 @@ class TextEditor extends Model
@displayBuffer.setWidth(width)
else
Grim.deprecate("This is now a view method. Call TextEditorElement::setWidth instead.")
atom.views.getView(this).setWidth(width)
@viewRegistry.getView(this).setWidth(width)
getWidth: ->
Grim.deprecate("This is now a view method. Call TextEditorElement::getWidth instead.")
@@ -3035,77 +3118,82 @@ class TextEditor extends Model
getScrollTop: ->
Grim.deprecate("This is now a view method. Call TextEditorElement::getScrollTop instead.")
atom.views.getView(this).getScrollTop()
@viewRegistry.getView(this).getScrollTop()
setScrollTop: (scrollTop) ->
Grim.deprecate("This is now a view method. Call TextEditorElement::setScrollTop instead.")
atom.views.getView(this).setScrollTop(scrollTop)
@viewRegistry.getView(this).setScrollTop(scrollTop)
getScrollBottom: ->
Grim.deprecate("This is now a view method. Call TextEditorElement::getScrollBottom instead.")
atom.views.getView(this).getScrollBottom()
@viewRegistry.getView(this).getScrollBottom()
setScrollBottom: (scrollBottom) ->
Grim.deprecate("This is now a view method. Call TextEditorElement::setScrollBottom instead.")
atom.views.getView(this).setScrollBottom(scrollBottom)
@viewRegistry.getView(this).setScrollBottom(scrollBottom)
getScrollLeft: ->
Grim.deprecate("This is now a view method. Call TextEditorElement::getScrollLeft instead.")
atom.views.getView(this).getScrollLeft()
@viewRegistry.getView(this).getScrollLeft()
setScrollLeft: (scrollLeft) ->
Grim.deprecate("This is now a view method. Call TextEditorElement::setScrollLeft instead.")
atom.views.getView(this).setScrollLeft(scrollLeft)
@viewRegistry.getView(this).setScrollLeft(scrollLeft)
getScrollRight: ->
Grim.deprecate("This is now a view method. Call TextEditorElement::getScrollRight instead.")
atom.views.getView(this).getScrollRight()
@viewRegistry.getView(this).getScrollRight()
setScrollRight: (scrollRight) ->
Grim.deprecate("This is now a view method. Call TextEditorElement::setScrollRight instead.")
atom.views.getView(this).setScrollRight(scrollRight)
@viewRegistry.getView(this).setScrollRight(scrollRight)
getScrollHeight: ->
Grim.deprecate("This is now a view method. Call TextEditorElement::getScrollHeight instead.")
atom.views.getView(this).getScrollHeight()
@viewRegistry.getView(this).getScrollHeight()
getScrollWidth: ->
Grim.deprecate("This is now a view method. Call TextEditorElement::getScrollWidth instead.")
atom.views.getView(this).getScrollWidth()
@viewRegistry.getView(this).getScrollWidth()
getMaxScrollTop: ->
Grim.deprecate("This is now a view method. Call TextEditorElement::getMaxScrollTop instead.")
@viewRegistry.getView(this).getMaxScrollTop()
getVisibleRowRange: ->
Grim.deprecate("This is now a view method. Call TextEditorElement::getVisibleRowRange instead.")
atom.views.getView(this).getVisibleRowRange()
@viewRegistry.getView(this).getVisibleRowRange()
intersectsVisibleRowRange: (startRow, endRow) ->
Grim.deprecate("This is now a view method. Call TextEditorElement::intersectsVisibleRowRange instead.")
atom.views.getView(this).intersectsVisibleRowRange(startRow, endRow)
@viewRegistry.getView(this).intersectsVisibleRowRange(startRow, endRow)
selectionIntersectsVisibleRowRange: (selection) ->
Grim.deprecate("This is now a view method. Call TextEditorElement::selectionIntersectsVisibleRowRange instead.")
atom.views.getView(this).selectionIntersectsVisibleRowRange(selection)
@viewRegistry.getView(this).selectionIntersectsVisibleRowRange(selection)
screenPositionForPixelPosition: (pixelPosition) ->
Grim.deprecate("This is now a view method. Call TextEditorElement::screenPositionForPixelPosition instead.")
atom.views.getView(this).screenPositionForPixelPosition(pixelPosition)
@viewRegistry.getView(this).screenPositionForPixelPosition(pixelPosition)
pixelRectForScreenRange: (screenRange) ->
Grim.deprecate("This is now a view method. Call TextEditorElement::pixelRectForScreenRange instead.")
atom.views.getView(this).pixelRectForScreenRange(screenRange)
@viewRegistry.getView(this).pixelRectForScreenRange(screenRange)
###
Section: Utility

View File

@@ -57,6 +57,38 @@ isPairedCharacter = (string, index=0) ->
isVariationSequence(charCodeA, charCodeB) or
isCombinedCharacter(charCodeA, charCodeB)
isJapaneseCharacter = (charCode) ->
0x3000 <= charCode <= 0x30FF
isCjkUnifiedIdeograph = (charCode) ->
0x4E00 <= charCode <= 0x9FAF
isFullWidthForm = (charCode) ->
0xFF01 <= charCode <= 0xFF5E or
0xFFE0 <= charCode <= 0xFFE6
isDoubleWidthCharacter = (character) ->
charCode = character.charCodeAt(0)
isJapaneseCharacter(charCode) or
isCjkUnifiedIdeograph(charCode) or
isFullWidthForm(charCode)
isHalfWidthCharacter = (character) ->
charCode = character.charCodeAt(0)
0xFF65 <= charCode <= 0xFFDC or
0xFFE8 <= charCode <= 0xFFEE
isKoreanCharacter = (character) ->
charCode = character.charCodeAt(0)
0xAC00 <= charCode <= 0xD7A3 or
0x1100 <= charCode <= 0x11FF or
0x3130 <= charCode <= 0x318F or
0xA960 <= charCode <= 0xA97F or
0xD7B0 <= charCode <= 0xD7FF
# Does the given string contain at least surrogate pair, variation sequence,
# or combined character?
#
@@ -70,4 +102,4 @@ hasPairedCharacter = (string) ->
index++
false
module.exports = {isPairedCharacter, hasPairedCharacter}
module.exports = {isPairedCharacter, hasPairedCharacter, isDoubleWidthCharacter, isHalfWidthCharacter, isKoreanCharacter}

View File

@@ -9,12 +9,14 @@ fs = require 'fs-plus'
# An instance of this class is always available as the `atom.themes` global.
module.exports =
class ThemeManager
constructor: ({@packageManager, @resourcePath, @configDirPath, @safeMode}) ->
constructor: ({@packageManager, @resourcePath, @configDirPath, @safeMode, @config, @styleManager, @notificationManager, @viewRegistry}) ->
@emitter = new Emitter
@styleSheetDisposablesBySourcePath = {}
@lessCache = null
@initialLoadComplete = false
@packageManager.registerPackageActivator(this, ['theme'])
@packageManager.onDidActivateInitialPackages =>
@onDidChangeActiveThemes => @packageManager.reloadActivePackageStyleSheets()
###
Section: Event Subscription
@@ -66,21 +68,21 @@ class ThemeManager
###
warnForNonExistentThemes: ->
themeNames = atom.config.get('core.themes') ? []
themeNames = @config.get('core.themes') ? []
themeNames = [themeNames] unless _.isArray(themeNames)
for themeName in themeNames
unless themeName and typeof themeName is 'string' and atom.packages.resolvePackagePath(themeName)
unless themeName and typeof themeName is 'string' and @packageManager.resolvePackagePath(themeName)
console.warn("Enabled theme '#{themeName}' is not installed.")
# Public: Get the enabled theme names from the config.
#
# Returns an array of theme names in the order that they should be activated.
getEnabledThemeNames: ->
themeNames = atom.config.get('core.themes') ? []
themeNames = @config.get('core.themes') ? []
themeNames = [themeNames] unless _.isArray(themeNames)
themeNames = themeNames.filter (themeName) ->
themeNames = themeNames.filter (themeName) =>
if themeName and typeof themeName is 'string'
return true if atom.packages.resolvePackagePath(themeName)
return true if @packageManager.resolvePackagePath(themeName)
false
# Use a built-in syntax and UI theme any time the configured themes are not
@@ -139,7 +141,7 @@ class ThemeManager
loadUserStylesheet: ->
@unwatchUserStylesheet()
userStylesheetPath = atom.styles.getUserStyleSheetPath()
userStylesheetPath = @styleManager.getUserStyleSheetPath()
return unless fs.isFileSync(userStylesheetPath)
try
@@ -158,14 +160,14 @@ class ThemeManager
[this document][watches] for more info.
[watches]:https://github.com/atom/atom/blob/master/docs/build-instructions/linux.md#typeerror-unable-to-watch-path
"""
atom.notifications.addError(message, dismissable: true)
@notificationManager.addError(message, dismissable: true)
try
userStylesheetContents = @loadStylesheet(userStylesheetPath, true)
catch
return
@userStyleSheetDisposable = atom.styles.addStyleSheet(userStylesheetContents, sourcePath: userStylesheetPath, priority: 2)
@userStyleSheetDisposable = @styleManager.addStyleSheet(userStylesheetContents, sourcePath: userStylesheetPath, priority: 2)
loadBaseStylesheets: ->
@requireStylesheet('../static/bootstrap')
@@ -221,22 +223,22 @@ class ThemeManager
message = "Error loading Less stylesheet: `#{lessStylesheetPath}`"
detail = error.message
atom.notifications.addError(message, {detail, dismissable: true})
@notificationManager.addError(message, {detail, dismissable: true})
throw error
removeStylesheet: (stylesheetPath) ->
@styleSheetDisposablesBySourcePath[stylesheetPath]?.dispose()
applyStylesheet: (path, text) ->
@styleSheetDisposablesBySourcePath[path] = atom.styles.addStyleSheet(text, sourcePath: path)
@styleSheetDisposablesBySourcePath[path] = @styleManager.addStyleSheet(text, sourcePath: path)
stringToId: (string) ->
string.replace(/\\/g, '/')
activateThemes: ->
new Promise (resolve) =>
# atom.config.observe runs the callback once, then on subsequent changes.
atom.config.observe 'core.themes', =>
# @config.observe runs the callback once, then on subsequent changes.
@config.observe 'core.themes', =>
@deactivateThemes()
@warnForNonExistentThemes()
@@ -268,13 +270,13 @@ class ThemeManager
isInitialLoadComplete: -> @initialLoadComplete
addActiveThemeClasses: ->
if workspaceElement = atom.views.getView(atom.workspace)
if workspaceElement = @viewRegistry.getView(@workspace)
for pack in @getActiveThemes()
workspaceElement.classList.add("theme-#{pack.name}")
return
removeActiveThemeClasses: ->
workspaceElement = atom.views.getView(atom.workspace)
workspaceElement = @viewRegistry.getView(@workspace)
for pack in @getActiveThemes()
workspaceElement.classList.remove("theme-#{pack.name}")
return

View File

@@ -7,10 +7,10 @@ class ThemePackage extends Package
getStyleSheetPriority: -> 1
enable: ->
atom.config.unshiftAtKeyPath('core.themes', @name)
@config.unshiftAtKeyPath('core.themes', @name)
disable: ->
atom.config.removeAtKeyPath('core.themes', @name)
@config.removeAtKeyPath('core.themes', @name)
load: ->
@loadTime = 0

View File

@@ -1,19 +1,18 @@
{SoftTab, HardTab, PairedCharacter, SoftWrapIndent} = require './special-token-symbols'
{isDoubleWidthCharacter, isHalfWidthCharacter, isKoreanCharacter} = require './text-utils'
module.exports =
class TokenIterator
constructor: (line) ->
@reset(line) if line?
constructor: ({@grammarRegistry}, line, enableScopes) ->
@reset(line, enableScopes) if line?
reset: (@line) ->
reset: (@line, @enableScopes=true) ->
@index = null
@bufferStart = @line.startBufferColumn
@bufferEnd = @bufferStart
@screenStart = 0
@screenEnd = 0
@scopes = @line.openScopes.map (id) -> atom.grammars.scopeForId(id)
@scopeStarts = @scopes.slice()
@scopeEnds = []
@resetScopes() if @enableScopes
this
next: ->
@@ -21,26 +20,16 @@ class TokenIterator
if @index?
@index++
@scopeEnds.length = 0
@scopeStarts.length = 0
@bufferStart = @bufferEnd
@screenStart = @screenEnd
@clearScopeStartsAndEnds() if @enableScopes
else
@index = 0
while @index < tags.length
tag = tags[@index]
if tag < 0
scope = atom.grammars.scopeForId(tag)
if tag % 2 is 0
if @scopeStarts[@scopeStarts.length - 1] is scope
@scopeStarts.pop()
else
@scopeEnds.push(scope)
@scopes.pop()
else
@scopeStarts.push(scope)
@scopes.push(scope)
@handleScopeForTag(tag) if @enableScopes
@index++
else
if @isHardTab()
@@ -52,10 +41,33 @@ class TokenIterator
else
@screenEnd = @screenStart + tag
@bufferEnd = @bufferStart + tag
@text = @line.text.substring(@screenStart, @screenEnd)
return true
false
resetScopes: ->
@scopes = @line.openScopes.map (id) => @grammarRegistry.scopeForId(id)
@scopeStarts = @scopes.slice()
@scopeEnds = []
clearScopeStartsAndEnds: ->
@scopeEnds.length = 0
@scopeStarts.length = 0
handleScopeForTag: (tag) ->
scope = @grammarRegistry.scopeForId(tag)
if tag % 2 is 0
if @scopeStarts[@scopeStarts.length - 1] is scope
@scopeStarts.pop()
else
@scopeEnds.push(scope)
@scopes.pop()
else
@scopeStarts.push(scope)
@scopes.push(scope)
getBufferStart: -> @bufferStart
getBufferEnd: -> @bufferEnd
@@ -67,8 +79,7 @@ class TokenIterator
getScopes: -> @scopes
getText: ->
@line.text.substring(@screenStart, @screenEnd)
getText: -> @text
isSoftTab: ->
@line.specialTokens[@index] is SoftTab
@@ -82,5 +93,14 @@ class TokenIterator
isPairedCharacter: ->
@line.specialTokens[@index] is PairedCharacter
hasDoubleWidthCharacterAt: (charIndex) ->
isDoubleWidthCharacter(@getText()[charIndex])
hasHalfWidthCharacterAt: (charIndex) ->
isHalfWidthCharacter(@getText()[charIndex])
hasKoreanCharacterAt: (charIndex) ->
isKoreanCharacter(@getText()[charIndex])
isAtomic: ->
@isSoftTab() or @isHardTab() or @isSoftWrapIndentation() or @isPairedCharacter()

View File

@@ -21,17 +21,26 @@ class TokenizedBuffer extends Model
configSettings: null
changeCount: 0
@deserialize: (state) ->
state.buffer = atom.project.bufferForPathSync(state.bufferPath)
@deserialize: (state, atomEnvironment) ->
state.buffer = atomEnvironment.project.bufferForPathSync(state.bufferPath)
state.config = atomEnvironment.config
state.grammarRegistry = atomEnvironment.grammars
state.packageManager = atomEnvironment.packages
state.assert = atomEnvironment.assert
new this(state)
constructor: ({@buffer, @tabLength, @ignoreInvisibles, @largeFileMode}) ->
constructor: (params) ->
{
@buffer, @tabLength, @ignoreInvisibles, @largeFileMode, @config,
@grammarRegistry, @packageManager, @assert
} = params
@emitter = new Emitter
@disposables = new CompositeDisposable
@tokenIterator = new TokenIterator
@tokenIterator = new TokenIterator({@grammarRegistry})
@disposables.add atom.grammars.onDidAddGrammar(@grammarAddedOrUpdated)
@disposables.add atom.grammars.onDidUpdateGrammar(@grammarAddedOrUpdated)
@disposables.add @grammarRegistry.onDidAddGrammar(@grammarAddedOrUpdated)
@disposables.add @grammarRegistry.onDidUpdateGrammar(@grammarAddedOrUpdated)
@disposables.add @buffer.preemptDidChange (e) => @handleBufferChange(e)
@disposables.add @buffer.onDidChangePath (@bufferPath) => @reloadGrammar()
@@ -65,7 +74,7 @@ class TokenizedBuffer extends Model
if grammar.injectionSelector?
@retokenizeLines() if @hasTokenForSelector(grammar.injectionSelector)
else
newScore = atom.grammars.getGrammarScore(grammar, @buffer.getPath(), @getGrammarSelectionContent())
newScore = @grammarRegistry.getGrammarScore(grammar, @buffer.getPath(), @getGrammarSelectionContent())
@setGrammar(grammar, newScore) if newScore > @currentGrammarScore
setGrammar: (grammar, score) ->
@@ -73,7 +82,7 @@ class TokenizedBuffer extends Model
@grammar = grammar
@rootScopeDescriptor = new ScopeDescriptor(scopes: [@grammar.scopeName])
@currentGrammarScore = score ? atom.grammars.getGrammarScore(grammar, @buffer.getPath(), @getGrammarSelectionContent())
@currentGrammarScore = score ? @grammarRegistry.getGrammarScore(grammar, @buffer.getPath(), @getGrammarSelectionContent())
@grammarUpdateDisposable?.dispose()
@grammarUpdateDisposable = @grammar.onDidUpdate => @retokenizeLines()
@@ -81,33 +90,33 @@ class TokenizedBuffer extends Model
scopeOptions = {scope: @rootScopeDescriptor}
@configSettings =
tabLength: atom.config.get('editor.tabLength', scopeOptions)
invisibles: atom.config.get('editor.invisibles', scopeOptions)
showInvisibles: atom.config.get('editor.showInvisibles', scopeOptions)
tabLength: @config.get('editor.tabLength', scopeOptions)
invisibles: @config.get('editor.invisibles', scopeOptions)
showInvisibles: @config.get('editor.showInvisibles', scopeOptions)
if @configSubscriptions?
@configSubscriptions.dispose()
@disposables.remove(@configSubscriptions)
@configSubscriptions = new CompositeDisposable
@configSubscriptions.add atom.config.onDidChange 'editor.tabLength', scopeOptions, ({newValue}) =>
@configSubscriptions.add @config.onDidChange 'editor.tabLength', scopeOptions, ({newValue}) =>
@configSettings.tabLength = newValue
@retokenizeLines()
['invisibles', 'showInvisibles'].forEach (key) =>
@configSubscriptions.add atom.config.onDidChange "editor.#{key}", scopeOptions, ({newValue}) =>
@configSubscriptions.add @config.onDidChange "editor.#{key}", scopeOptions, ({newValue}) =>
oldInvisibles = @getInvisiblesToShow()
@configSettings[key] = newValue
@retokenizeLines() unless _.isEqual(@getInvisiblesToShow(), oldInvisibles)
@disposables.add(@configSubscriptions)
@retokenizeLines()
atom.packages.triggerActivationHook("#{grammar.packageName}:grammar-used")
@packageManager.triggerActivationHook("#{grammar.packageName}:grammar-used")
@emitter.emit 'did-change-grammar', grammar
getGrammarSelectionContent: ->
@buffer.getTextInRange([[0, 0], [10, 0]])
reloadGrammar: ->
if grammar = atom.grammars.selectGrammar(@buffer.getPath(), @getGrammarSelectionContent())
if grammar = @grammarRegistry.selectGrammar(@buffer.getPath(), @getGrammarSelectionContent())
@setGrammar(grammar)
else
throw new Error("No grammar found for path: #{path}")
@@ -155,7 +164,7 @@ class TokenizedBuffer extends Model
tokenizeNextChunk: ->
# Short circuit null grammar which can just use the placeholder tokens
if @grammar is atom.grammars.nullGrammar and @firstInvalidRow()?
if @grammar is @grammarRegistry.nullGrammar and @firstInvalidRow()?
@invalidRows = []
@markTokenizationComplete()
return
@@ -291,7 +300,7 @@ class TokenizedBuffer extends Model
# undefined. This should paper over the problem but we want to figure out
# what is happening:
tokenizedLine = @tokenizedLineForRow(row)
atom.assert tokenizedLine?, "TokenizedLine is undefined", (error) =>
@assert tokenizedLine?, "TokenizedLine is undefined", (error) =>
error.metadata = {
row: row
rowCount: @tokenizedLines.length
@@ -391,7 +400,7 @@ class TokenizedBuffer extends Model
loop
break if scopes.pop() is matchingStartTag
if scopes.length is 0
atom.assert false, "Encountered an unmatched scope end tag.", (error) =>
@assert false, "Encountered an unmatched scope end tag.", (error) =>
error.metadata = {
grammarScopeName: @grammar.scopeName
unmatchedEndTag: @grammar.scopeForId(tag)
@@ -472,13 +481,13 @@ class TokenizedBuffer extends Model
position = Point.fromObject(position)
{openScopes, tags} = @tokenizedLineForRow(position.row)
scopes = openScopes.map (tag) -> atom.grammars.scopeForId(tag)
scopes = openScopes.map (tag) => @grammarRegistry.scopeForId(tag)
startColumn = 0
for tag, tokenIndex in tags
if tag < 0
if tag % 2 is -1
scopes.push(atom.grammars.scopeForId(tag))
scopes.push(@grammarRegistry.scopeForId(tag))
else
scopes.pop()
else
@@ -498,7 +507,7 @@ class TokenizedBuffer extends Model
if tag % 2 is -1
startScopes.pop()
else
startScopes.push(atom.grammars.scopeForId(tag))
startScopes.push(@grammarRegistry.scopeForId(tag))
else
break unless selectorMatchesAnyScope(selector, startScopes)
startColumn -= tag
@@ -508,7 +517,7 @@ class TokenizedBuffer extends Model
tag = tags[endTokenIndex]
if tag < 0
if tag % 2 is -1
endScopes.push(atom.grammars.scopeForId(tag))
endScopes.push(@grammarRegistry.scopeForId(tag))
else
endScopes.pop()
else

View File

@@ -184,7 +184,7 @@ class TokenizedLine
@lineIsWhitespaceOnly = true
@firstTrailingWhitespaceIndex = 0
getTokenIterator: -> @tokenIterator.reset(this)
getTokenIterator: -> @tokenIterator.reset(this, arguments...)
Object.defineProperty @prototype, 'tokens', get: ->
iterator = @getTokenIterator()

View File

@@ -54,6 +54,8 @@ class TooltipManager
placement: 'auto top'
viewportPadding: 2
constructor: ({@keymapManager}) ->
# Essential: Add a tooltip to the given element.
#
# * `target` An `HTMLElement`
@@ -81,7 +83,7 @@ class TooltipManager
{keyBindingCommand, keyBindingTarget} = options
if keyBindingCommand?
bindings = atom.keymaps.findKeyBindings(command: keyBindingCommand, target: keyBindingTarget)
bindings = @keymapManager.findKeyBindings(command: keyBindingCommand, target: keyBindingTarget)
keystroke = getKeystroke(bindings)
if options.title? and keystroke?
options.title += " " + getKeystroke(bindings)

View File

@@ -49,15 +49,15 @@ class ViewRegistry
debouncedPerformDocumentPoll: null
minimumPollInterval: 200
constructor: ->
constructor: (@atomEnvironment) ->
@observer = new MutationObserver(@requestDocumentPoll)
@clear()
clear: ->
@views = new WeakMap
@providers = []
@documentWriters = []
@documentReaders = []
@documentPollers = []
@observer = new MutationObserver(@requestDocumentPoll)
@debouncedPerformDocumentPoll = _.throttle(@performDocumentPoll, @minimumPollInterval).bind(this)
@clearDocumentRequests()
# Essential: Add a provider that will be used to construct views in the
# workspace's view layer based on model objects in its model layer.
@@ -159,7 +159,7 @@ class ViewRegistry
else if object?.jquery
object[0]
else if provider = @findProvider(object)
element = provider.createView?(object)
element = provider.createView?(object, @atomEnvironment)
unless element?
element = new provider.viewConstructor
element.initialize?(object) ? element.setModel?(object)

View File

@@ -1,13 +0,0 @@
# Like sands through the hourglass, so are the days of our lives.
require './window'
Atom = require './atom'
window.atom = Atom.loadOrCreate('editor')
atom.initialize()
atom.startEditorWindow()
# Workaround for focus getting cleared upon window creation
windowFocused = ->
window.removeEventListener('focus', windowFocused)
setTimeout (-> document.querySelector('atom-workspace').focus()), 0
window.addEventListener('focus', windowFocused)

View File

@@ -1,44 +1,38 @@
path = require 'path'
{Disposable, CompositeDisposable} = require 'event-kit'
ipc = require 'ipc'
shell = require 'shell'
fs = require 'fs-plus'
listen = require './delegated-listener'
# Handles low-level events related to the window.
# Handles low-level events related to the @window.
module.exports =
class WindowEventHandler
constructor: ->
constructor: ({@atomEnvironment, @applicationDelegate, @window, @document}) ->
@reloadRequested = false
@subscriptions = new CompositeDisposable
@on(ipc, 'message', @handleIPCMessage)
@on(ipc, 'command', @handleIPCCommand)
@on(ipc, 'context-command', @handleIPCContextCommand)
@previousOnbeforeunloadHandler = @window.onbeforeunload
@window.onbeforeunload = @handleWindowBeforeunload
@addEventListener(@window, 'focus', @handleWindowFocus)
@addEventListener(@window, 'blur', @handleWindowBlur)
@addEventListener(window, 'focus', @handleWindowFocus)
@addEventListener(window, 'blur', @handleWindowBlur)
@addEventListener(window, 'beforeunload', @handleWindowBeforeunload)
@addEventListener(window, 'unload', @handleWindowUnload)
@addEventListener(@document, 'keydown', @handleDocumentKeydown)
@addEventListener(@document, 'drop', @handleDocumentDrop)
@addEventListener(@document, 'dragover', @handleDocumentDragover)
@addEventListener(@document, 'contextmenu', @handleDocumentContextmenu)
@subscriptions.add listen(@document, 'click', 'a', @handleLinkClick)
@subscriptions.add listen(@document, 'submit', 'form', @handleFormSubmit)
@addEventListener(document, 'keydown', @handleDocumentKeydown)
@addEventListener(document, 'drop', @handleDocumentDrop)
@addEventListener(document, 'dragover', @handleDocumentDragover)
@addEventListener(document, 'contextmenu', @handleDocumentContextmenu)
@subscriptions.add listen(document, 'click', 'a', @handleLinkClick)
@subscriptions.add listen(document, 'submit', 'form', @handleFormSubmit)
@subscriptions.add atom.commands.add window,
@subscriptions.add @atomEnvironment.commands.add @window,
'window:toggle-full-screen': @handleWindowToggleFullScreen
'window:close': @handleWindowClose
'window:reload': @handleWindowReload
'window:toggle-dev-tools': @handleWindowToggleDevTools
if process.platform in ['win32', 'linux']
@subscriptions.add atom.commands.add window,
@subscriptions.add @atomEnvironment.commands.add @window,
'window:toggle-menu-bar': @handleWindowToggleMenuBar
@subscriptions.add atom.commands.add document,
@subscriptions.add @atomEnvironment.commands.add @document,
'core:focus-next': @handleFocusNext
'core:focus-previous': @handleFocusPrevious
@@ -48,9 +42,9 @@ class WindowEventHandler
# `.native-key-bindings` class.
handleNativeKeybindings: ->
bindCommandToAction = (command, action) =>
@addEventListener document, command, (event) ->
@addEventListener @document, command, (event) =>
if event.target.webkitMatchesSelector('.native-key-bindings')
atom.getCurrentWindow().webContents[action]()
@applicationDelegate.getCurrentWindow().webContents[action]()
bindCommandToAction('core:copy', 'copy')
bindCommandToAction('core:paste', 'paste')
@@ -60,6 +54,7 @@ class WindowEventHandler
bindCommandToAction('core:cut', 'cut')
unsubscribe: ->
@window.onbeforeunload = @previousOnbeforeunloadHandler
@subscriptions.dispose()
on: (target, eventName, handler) ->
@@ -72,8 +67,8 @@ class WindowEventHandler
target.addEventListener(eventName, handler)
@subscriptions.add(new Disposable(-> target.removeEventListener(eventName, handler)))
handleDocumentKeydown: (event) ->
atom.keymaps.handleKeyboardEvent(event)
handleDocumentKeydown: (event) =>
@atomEnvironment.keymaps.handleKeyboardEvent(event)
event.stopImmediatePropagation()
handleDrop: (event) ->
@@ -86,14 +81,14 @@ class WindowEventHandler
event.dataTransfer.dropEffect = 'none'
eachTabIndexedElement: (callback) ->
for element in document.querySelectorAll('[tabindex]')
for element in @document.querySelectorAll('[tabindex]')
continue if element.disabled
continue unless element.tabIndex >= 0
callback(element, element.tabIndex)
return
handleFocusNext: =>
focusedTabIndex = document.activeElement.tabIndex ? -Infinity
focusedTabIndex = @document.activeElement.tabIndex ? -Infinity
nextElement = null
nextTabIndex = Infinity
@@ -114,7 +109,7 @@ class WindowEventHandler
lowestElement.focus()
handleFocusPrevious: =>
focusedTabIndex = document.activeElement.tabIndex ? Infinity
focusedTabIndex = @document.activeElement.tabIndex ? Infinity
previousElement = null
previousTabIndex = -Infinity
@@ -134,91 +129,61 @@ class WindowEventHandler
else if highestElement?
highestElement.focus()
handleIPCMessage: (message, detail) ->
switch message
when 'open-locations'
needsProjectPaths = atom.project?.getPaths().length is 0
for {pathToOpen, initialLine, initialColumn} in detail
if pathToOpen? and needsProjectPaths
if fs.existsSync(pathToOpen)
atom.project.addPath(pathToOpen)
else if fs.existsSync(path.dirname(pathToOpen))
atom.project.addPath(path.dirname(pathToOpen))
else
atom.project.addPath(pathToOpen)
unless fs.isDirectorySync(pathToOpen)
atom.workspace?.open(pathToOpen, {initialLine, initialColumn})
return
when 'update-available'
atom.updateAvailable(detail)
handleIPCCommand: (command, args...) ->
activeElement = document.activeElement
# Use the workspace element view if body has focus
if activeElement is document.body and workspaceElement = atom.views.getView(atom.workspace)
activeElement = workspaceElement
atom.commands.dispatch(activeElement, command, args[0])
handleIPCContextCommand: (command, args...) ->
atom.commands.dispatch(atom.contextMenu.activeElement, command, args)
handleWindowFocus: ->
document.body.classList.remove('is-blurred')
@document.body.classList.remove('is-blurred')
handleWindowBlur: ->
document.body.classList.add('is-blurred')
atom.storeDefaultWindowDimensions()
handleWindowBlur: =>
@document.body.classList.add('is-blurred')
@atomEnvironment.storeDefaultWindowDimensions()
handleWindowBeforeunload: =>
confirmed = atom.workspace?.confirmClose(windowCloseRequested: true)
atom.hide() if confirmed and not @reloadRequested and atom.getCurrentWindow().isWebViewFocused()
confirmed = @atomEnvironment.workspace?.confirmClose(windowCloseRequested: true)
if confirmed and not @reloadRequested and not @atomEnvironment.inSpecMode() and @atomEnvironment.getCurrentWindow().isWebViewFocused()
@atomEnvironment.hide()
@reloadRequested = false
atom.storeDefaultWindowDimensions()
atom.storeWindowDimensions()
@atomEnvironment.storeDefaultWindowDimensions()
@atomEnvironment.storeWindowDimensions()
if confirmed
atom.unloadEditorWindow()
@atomEnvironment.unloadEditorWindow()
else
ipc.send('cancel-window-close')
@applicationDelegate.didCancelWindowUnload()
confirmed
handleWindowUnload: ->
atom.removeEditorWindow()
handleWindowUnload: =>
@atomEnvironment.destroy()
handleWindowToggleFullScreen: ->
atom.toggleFullScreen()
handleWindowToggleFullScreen: =>
@atomEnvironment.toggleFullScreen()
handleWindowClose: ->
atom.close()
handleWindowClose: =>
@atomEnvironment.close()
handleWindowReload: ->
handleWindowReload: =>
@reloadRequested = true
atom.reload()
@atomEnvironment.reload()
handleWindowToggleDevTools: ->
atom.toggleDevTools()
handleWindowToggleDevTools: =>
@atomEnvironment.toggleDevTools()
handleWindowToggleMenuBar: ->
atom.config.set('core.autoHideMenuBar', not atom.config.get('core.autoHideMenuBar'))
handleWindowToggleMenuBar: =>
@atomEnvironment.config.set('core.autoHideMenuBar', not @atomEnvironment.config.get('core.autoHideMenuBar'))
if atom.config.get('core.autoHideMenuBar')
if @atomEnvironment.config.get('core.autoHideMenuBar')
detail = "To toggle, press the Alt key or execute the window:toggle-menu-bar command"
atom.notifications.addInfo('Menu bar hidden', {detail})
@atomEnvironment.notifications.addInfo('Menu bar hidden', {detail})
handleLinkClick: (event) ->
handleLinkClick: (event) =>
event.preventDefault()
location = event.currentTarget?.getAttribute('href')
if location and location[0] isnt '#' and /^https?:\/\//.test(location)
shell.openExternal(location)
uri = event.currentTarget?.getAttribute('href')
if uri and uri[0] isnt '#' and /^https?:\/\//.test(uri)
@applicationDelegate.openExternal(uri)
handleFormSubmit: (event) ->
# Prevent form submits from changing the current window's URL
event.preventDefault()
handleDocumentContextmenu: (event) ->
handleDocumentContextmenu: (event) =>
event.preventDefault()
atom.contextMenu.showForEvent(event)
@atomEnvironment.contextMenu.showForEvent(event)

View File

@@ -0,0 +1,20 @@
remote = require 'remote'
_ = require 'underscore-plus'
windowLoadSettings = null
exports.getWindowLoadSettings = ->
windowLoadSettings ?= JSON.parse(window.decodeURIComponent(window.location.hash.substr(1)))
clone = _.deepClone(windowLoadSettings)
# The windowLoadSettings.windowState could be large, request it only when needed.
clone.__defineGetter__ 'windowState', ->
remote.getCurrentWindow().loadSettings.windowState
clone.__defineSetter__ 'windowState', (value) ->
remote.getCurrentWindow().loadSettings.windowState = value
clone
exports.setWindowLoadSettings = (settings) ->
windowLoadSettings = settings
location.hash = encodeURIComponent(JSON.stringify(settings))

View File

@@ -8,18 +8,11 @@ module.exports =
class WorkspaceElement extends HTMLElement
globalTextEditorStyleSheet: null
createdCallback: ->
@subscriptions = new CompositeDisposable
@initializeContent()
@observeScrollbarStyle()
@observeTextEditorFontConfig()
attachedCallback: ->
@focus()
detachedCallback: ->
@subscriptions.dispose()
@model.destroy()
initializeContent: ->
@classList.add 'workspace'
@@ -46,31 +39,42 @@ class WorkspaceElement extends HTMLElement
observeTextEditorFontConfig: ->
@updateGlobalTextEditorStyleSheet()
@subscriptions.add atom.config.onDidChange 'editor.fontSize', @updateGlobalTextEditorStyleSheet.bind(this)
@subscriptions.add atom.config.onDidChange 'editor.fontFamily', @updateGlobalTextEditorStyleSheet.bind(this)
@subscriptions.add atom.config.onDidChange 'editor.lineHeight', @updateGlobalTextEditorStyleSheet.bind(this)
@subscriptions.add @config.onDidChange 'editor.fontSize', @updateGlobalTextEditorStyleSheet.bind(this)
@subscriptions.add @config.onDidChange 'editor.fontFamily', @updateGlobalTextEditorStyleSheet.bind(this)
@subscriptions.add @config.onDidChange 'editor.lineHeight', @updateGlobalTextEditorStyleSheet.bind(this)
updateGlobalTextEditorStyleSheet: ->
styleSheetSource = """
atom-text-editor {
font-size: #{atom.config.get('editor.fontSize')}px;
font-family: #{atom.config.get('editor.fontFamily')};
line-height: #{atom.config.get('editor.lineHeight')};
font-size: #{@config.get('editor.fontSize')}px;
font-family: #{@config.get('editor.fontFamily')};
line-height: #{@config.get('editor.lineHeight')};
}
"""
atom.styles.addStyleSheet(styleSheetSource, sourcePath: 'global-text-editor-styles')
@styles.addStyleSheet(styleSheetSource, sourcePath: 'global-text-editor-styles')
initialize: (@model) ->
@paneContainer = atom.views.getView(@model.paneContainer)
initialize: (@model, {@views, @workspace, @project, @config, @styles}) ->
throw new Error("Must pass a views parameter when initializing WorskpaceElements") unless @views?
throw new Error("Must pass a workspace parameter when initializing WorskpaceElements") unless @workspace?
throw new Error("Must pass a project parameter when initializing WorskpaceElements") unless @project?
throw new Error("Must pass a config parameter when initializing WorskpaceElements") unless @config?
throw new Error("Must pass a styles parameter when initializing WorskpaceElements") unless @styles?
@subscriptions = new CompositeDisposable
@initializeContent()
@observeScrollbarStyle()
@observeTextEditorFontConfig()
@paneContainer = @views.getView(@model.paneContainer)
@verticalAxis.appendChild(@paneContainer)
@addEventListener 'focus', @handleFocus.bind(this)
@panelContainers =
top: atom.views.getView(@model.panelContainers.top)
left: atom.views.getView(@model.panelContainers.left)
right: atom.views.getView(@model.panelContainers.right)
bottom: atom.views.getView(@model.panelContainers.bottom)
modal: atom.views.getView(@model.panelContainers.modal)
top: @views.getView(@model.panelContainers.top)
left: @views.getView(@model.panelContainers.left)
right: @views.getView(@model.panelContainers.right)
bottom: @views.getView(@model.panelContainers.bottom)
modal: @views.getView(@model.panelContainers.modal)
@horizontalAxis.insertBefore(@panelContainers.left, @verticalAxis)
@horizontalAxis.appendChild(@panelContainers.right)
@@ -96,59 +100,10 @@ class WorkspaceElement extends HTMLElement
focusPaneViewOnRight: -> @paneContainer.focusPaneViewOnRight()
runPackageSpecs: ->
if activePath = atom.workspace.getActivePaneItem()?.getPath?()
[projectPath] = atom.project.relativizePath(activePath)
if activePath = @workspace.getActivePaneItem()?.getPath?()
[projectPath] = @project.relativizePath(activePath)
else
[projectPath] = atom.project.getPaths()
[projectPath] = @project.getPaths()
ipc.send('run-package-specs', path.join(projectPath, 'spec')) if projectPath
atom.commands.add 'atom-workspace',
'window:increase-font-size': -> @getModel().increaseFontSize()
'window:decrease-font-size': -> @getModel().decreaseFontSize()
'window:reset-font-size': -> @getModel().resetFontSize()
'application:about': -> ipc.send('command', 'application:about')
'application:run-all-specs': -> ipc.send('command', 'application:run-all-specs')
'application:show-preferences': -> ipc.send('command', 'application:show-settings')
'application:show-settings': -> ipc.send('command', 'application:show-settings')
'application:quit': -> ipc.send('command', 'application:quit')
'application:hide': -> ipc.send('command', 'application:hide')
'application:hide-other-applications': -> ipc.send('command', 'application:hide-other-applications')
'application:install-update': -> ipc.send('command', 'application:install-update')
'application:unhide-all-applications': -> ipc.send('command', 'application:unhide-all-applications')
'application:new-window': -> ipc.send('command', 'application:new-window')
'application:new-file': -> ipc.send('command', 'application:new-file')
'application:open': -> ipc.send('command', 'application:open')
'application:open-file': -> ipc.send('command', 'application:open-file')
'application:open-folder': -> ipc.send('command', 'application:open-folder')
'application:open-dev': -> ipc.send('command', 'application:open-dev')
'application:open-safe': -> ipc.send('command', 'application:open-safe')
'application:add-project-folder': -> atom.addProjectFolder()
'application:minimize': -> ipc.send('command', 'application:minimize')
'application:zoom': -> ipc.send('command', 'application:zoom')
'application:bring-all-windows-to-front': -> ipc.send('command', 'application:bring-all-windows-to-front')
'application:open-your-config': -> ipc.send('command', 'application:open-your-config')
'application:open-your-init-script': -> ipc.send('command', 'application:open-your-init-script')
'application:open-your-keymap': -> ipc.send('command', 'application:open-your-keymap')
'application:open-your-snippets': -> ipc.send('command', 'application:open-your-snippets')
'application:open-your-stylesheet': -> ipc.send('command', 'application:open-your-stylesheet')
'application:open-license': -> @getModel().openLicense()
'window:run-package-specs': -> @runPackageSpecs()
'window:focus-next-pane': -> @getModel().activateNextPane()
'window:focus-previous-pane': -> @getModel().activatePreviousPane()
'window:focus-pane-above': -> @focusPaneViewAbove()
'window:focus-pane-below': -> @focusPaneViewBelow()
'window:focus-pane-on-left': -> @focusPaneViewOnLeft()
'window:focus-pane-on-right': -> @focusPaneViewOnRight()
'window:save-all': -> @getModel().saveAll()
'window:toggle-invisibles': -> atom.config.set("editor.showInvisibles", not atom.config.get("editor.showInvisibles"))
'window:log-deprecation-warnings': -> Grim.logDeprecations()
'window:toggle-auto-indent': -> atom.config.set("editor.autoIndent", not atom.config.get("editor.autoIndent"))
'pane:reopen-closed-item': -> @getModel().reopenItem()
'core:close': -> @getModel().destroyActivePaneItemOrEmptyPane()
'core:save': -> @getModel().saveActivePaneItem()
'core:save-as': -> @getModel().saveActivePaneItemAs()
if process.platform is 'darwin'
atom.commands.add 'atom-workspace', 'window:install-shell-commands', -> @getModel().installShellCommands()
module.exports = WorkspaceElement = document.registerElement 'atom-workspace', prototype: WorkspaceElement.prototype

View File

@@ -9,10 +9,7 @@ TextEditor = require './text-editor'
PaneContainer = require './pane-container'
Pane = require './pane'
Panel = require './panel'
PanelElement = require './panel-element'
PanelContainer = require './panel-container'
PanelContainerElement = require './panel-container-element'
WorkspaceElement = require './workspace-element'
Task = require './task'
# Essential: Represents the state of the user interface for the entire window.
@@ -26,36 +23,24 @@ Task = require './task'
#
module.exports =
class Workspace extends Model
atom.deserializers.add(this)
@deserialize: (state) ->
return unless state?
for packageName in state.packagesWithActiveGrammars ? []
atom.packages.getLoadedPackage(packageName)?.loadGrammarsSync()
state.paneContainer = PaneContainer.deserialize(state.paneContainer)
new this(state)
constructor: (params) ->
super
@paneContainer = params?.paneContainer
@fullScreen = params?.fullScreen ? false
@destroyedItemURIs = params?.destroyedItemURIs ? []
{
@packageManager, @config, @project, @grammarRegistry, @notificationManager,
@clipboard, @viewRegistry, @grammarRegistry, @applicationDelegate, @assert,
@deserializerManager
} = params
@emitter = new Emitter
@openers = []
@destroyedItemURIs = []
@paneContainer ?= new PaneContainer()
@paneContainer = new PaneContainer({@config, @applicationDelegate, @notificationManager, @deserializerManager})
@paneContainer.onDidDestroyPaneItem(@didDestroyPaneItem)
@directorySearchers = []
@defaultDirectorySearcher = new DefaultDirectorySearcher()
atom.packages.serviceHub.consume(
'atom.directory-searcher',
'^0.1.0',
(provider) => @directorySearchers.unshift(provider))
@consumeServices(@packageManager)
@panelContainers =
top: new PanelContainer({location: 'top'})
@@ -64,69 +49,80 @@ class Workspace extends Model
bottom: new PanelContainer({location: 'bottom'})
modal: new PanelContainer({location: 'modal'})
@subscribeToEvents()
reset: (@packageManager) ->
@emitter.dispose()
@emitter = new Emitter
@paneContainer.destroy()
panelContainer.destroy() for panelContainer in @panelContainers
@paneContainer = new PaneContainer({@config, @applicationDelegate, @notificationManager, @deserializerManager})
@paneContainer.onDidDestroyPaneItem(@didDestroyPaneItem)
@panelContainers =
top: new PanelContainer({location: 'top'})
left: new PanelContainer({location: 'left'})
right: new PanelContainer({location: 'right'})
bottom: new PanelContainer({location: 'bottom'})
modal: new PanelContainer({location: 'modal'})
@originalFontSize = null
@openers = []
@destroyedItemURIs = []
@consumeServices(@packageManager)
subscribeToEvents: ->
@subscribeToActiveItem()
@addOpener (filePath) ->
switch filePath
when 'atom://.atom/stylesheet'
atom.project.open(atom.styles.getUserStyleSheetPath())
when 'atom://.atom/keymap'
atom.project.open(atom.keymaps.getUserKeymapPath())
when 'atom://.atom/config'
atom.project.open(atom.config.getUserConfigPath())
when 'atom://.atom/init-script'
atom.project.open(atom.getUserInitScriptPath())
atom.views.addViewProvider Workspace, (model) ->
new WorkspaceElement().initialize(model)
atom.views.addViewProvider PanelContainer, (model) ->
new PanelContainerElement().initialize(model)
atom.views.addViewProvider Panel, (model) ->
new PanelElement().initialize(model)
@subscribeToFontSize()
consumeServices: ({serviceHub}) ->
@directorySearchers = []
serviceHub.consume(
'atom.directory-searcher',
'^0.1.0',
(provider) => @directorySearchers.unshift(provider))
# Called by the Serializable mixin during serialization.
serialize: ->
deserializer: 'Workspace'
paneContainer: @paneContainer.serialize()
fullScreen: atom.isFullScreen()
packagesWithActiveGrammars: @getPackageNamesWithActiveGrammars()
destroyedItemURIs: @destroyedItemURIs.slice()
deserialize: (state, deserializerManager) ->
for packageName in state.packagesWithActiveGrammars ? []
@packageManager.getLoadedPackage(packageName)?.loadGrammarsSync()
if state.destroyedItemURIs?
@destroyedItemURIs = state.destroyedItemURIs
@paneContainer.deserialize(state.paneContainer, deserializerManager)
getPackageNamesWithActiveGrammars: ->
packageNames = []
addGrammar = ({includedGrammarScopes, packageName}={}) ->
addGrammar = ({includedGrammarScopes, packageName}={}) =>
return unless packageName
# Prevent cycles
return if packageNames.indexOf(packageName) isnt -1
packageNames.push(packageName)
for scopeName in includedGrammarScopes ? []
addGrammar(atom.grammars.grammarForScopeName(scopeName))
addGrammar(@grammarRegistry.grammarForScopeName(scopeName))
return
editors = @getTextEditors()
addGrammar(editor.getGrammar()) for editor in editors
if editors.length > 0
for grammar in atom.grammars.getGrammars() when grammar.injectionSelector
for grammar in @grammarRegistry.getGrammars() when grammar.injectionSelector
addGrammar(grammar)
_.uniq(packageNames)
editorAdded: (editor) ->
installShellCommands: ->
CommandInstaller = require('./command-installer')
commandInstaller = new CommandInstaller(atom.getVersion())
commandInstaller.installShellCommandsInteractively()
subscribeToActiveItem: ->
@updateWindowTitle()
@updateDocumentEdited()
atom.project.onDidChangePaths @updateWindowTitle
@project.onDidChangePaths @updateWindowTitle
@observeActivePaneItem (item) =>
@updateWindowTitle()
@@ -156,7 +152,7 @@ class Workspace extends Model
# open.
updateWindowTitle: =>
appName = 'Atom'
projectPaths = atom.project?.getPaths() ? []
projectPaths = @project.getPaths() ? []
if item = @getActivePaneItem()
itemPath = item.getPath?()
itemTitle = item.getTitle?()
@@ -167,19 +163,19 @@ class Workspace extends Model
if item? and projectPath?
document.title = "#{itemTitle} - #{projectPath} - #{appName}"
atom.setRepresentedFilename(itemPath ? projectPath)
@applicationDelegate.setRepresentedFilename(itemPath ? projectPath)
else if projectPath?
document.title = "#{projectPath} - #{appName}"
atom.setRepresentedFilename(projectPath)
@applicationDelegate.setRepresentedFilename(projectPath)
else
document.title = "#{itemTitle} - #{appName}"
atom.setRepresentedFilename("")
@applicationDelegate.setRepresentedFilename("")
# On OS X, fades the application window's proxy icon when the current file
# has been modified.
updateDocumentEdited: =>
modified = @getActivePaneItem()?.isModified?() ? false
atom.setDocumentEdited(modified)
@applicationDelegate.setWindowDocumentEdited(modified)
###
Section: Event Subscription
@@ -209,11 +205,34 @@ class Workspace extends Model
# Essential: Invoke the given callback when the active pane item changes.
#
# Because observers are invoked synchronously, it's important not to perform
# any expensive operations via this method. Consider
# {::onDidStopChangingActivePaneItem} to delay operations until after changes
# stop occurring.
#
# * `callback` {Function} to be called when the active pane item changes.
# * `item` The active pane item.
#
# Returns a {Disposable} on which `.dispose()` can be called to unsubscribe.
onDidChangeActivePaneItem: (callback) -> @paneContainer.onDidChangeActivePaneItem(callback)
onDidChangeActivePaneItem: (callback) ->
@paneContainer.onDidChangeActivePaneItem(callback)
# Essential: Invoke the given callback when the active pane item stops
# changing.
#
# Observers are called asynchronously 100ms after the last active pane item
# change. Handling changes here rather than in the synchronous
# {::onDidChangeActivePaneItem} prevents unneeded work if the user is quickly
# changing or closing tabs and ensures critical UI feedback, like changing the
# highlighted tab, gets priority over work that can be done asynchronously.
#
# * `callback` {Function} to be called when the active pane item stopts
# changing.
# * `item` The active pane item.
#
# Returns a {Disposable} on which `.dispose()` can be called to unsubscribe.
onDidStopChangingActivePaneItem: (callback) ->
@paneContainer.onDidStopChangingActivePaneItem(callback)
# Essential: Invoke the given callback with the current active pane item and
# with all future active pane items in the workspace.
@@ -363,11 +382,15 @@ class Workspace extends Model
# initially. Defaults to `0`.
# * `initialColumn` A {Number} indicating which column to move the cursor to
# initially. Defaults to `0`.
# * `split` Either 'left' or 'right'. If 'left', the item will be opened in
# leftmost pane of the current active pane's row. If 'right', the
# item will be opened in the rightmost pane of the current active pane's row.
# * `split` Either 'left', 'right', 'top' or 'bottom'.
# If 'left', the item will be opened in leftmost pane of the current active pane's row.
# If 'right', the item will be opened in the rightmost pane of the current active pane's row.
# If 'up', the item will be opened in topmost pane of the current active pane's row.
# If 'down', the item will be opened in the bottommost pane of the current active pane's row.
# * `activatePane` A {Boolean} indicating whether to call {Pane::activate} on
# containing pane. Defaults to `true`.
# * `activateItem` A {Boolean} indicating whether to call {Pane::activateItem}
# on containing pane. Defaults to `true`.
# * `searchAllPanes` A {Boolean}. If `true`, the workspace will attempt to
# activate an existing item for the given URI on any pane.
# If `false`, only the active pane will be searched for
@@ -377,7 +400,7 @@ class Workspace extends Model
open: (uri, options={}) ->
searchAllPanes = options.searchAllPanes
split = options.split
uri = atom.project.resolvePath(uri)
uri = @project.resolvePath(uri)
pane = @paneContainer.paneForURI(uri) if searchAllPanes
pane ?= switch split
@@ -385,6 +408,10 @@ class Workspace extends Model
@getActivePane().findLeftmostSibling()
when 'right'
@getActivePane().findOrCreateRightmostSibling()
when 'up'
@getActivePane().findTopmostSibling()
when 'down'
@getActivePane().findOrCreateBottommostSibling()
else
@getActivePane()
@@ -406,50 +433,51 @@ class Workspace extends Model
# initially. Defaults to `0`.
# * `activatePane` A {Boolean} indicating whether to call {Pane::activate} on
# the containing pane. Defaults to `true`.
# * `activateItem` A {Boolean} indicating whether to call {Pane::activateItem}
# on containing pane. Defaults to `true`.
openSync: (uri='', options={}) ->
{initialLine, initialColumn} = options
activatePane = options.activatePane ? true
activateItem = options.activateItem ? true
uri = atom.project.resolvePath(uri)
uri = @project.resolvePath(uri)
item = @getActivePane().itemForURI(uri)
if uri
item ?= opener(uri, options) for opener in @getOpeners() when not item
item ?= atom.project.openSync(uri, {initialLine, initialColumn})
item ?= @project.openSync(uri, {initialLine, initialColumn})
@getActivePane().activateItem(item)
@getActivePane().activateItem(item) if activateItem
@itemOpened(item)
@getActivePane().activate() if activatePane
item
openURIInPane: (uri, pane, options={}) ->
activatePane = options.activatePane ? true
activateItem = options.activateItem ? true
if uri?
item = pane.itemForURI(uri)
item ?= opener(uri, options) for opener in @getOpeners() when not item
try
item ?= atom.project.open(uri, options)
item ?= @openTextFile(uri, options)
catch error
switch error.code
when 'CANCELLED'
return Promise.resolve()
when 'EACCES'
atom.notifications.addWarning("Permission denied '#{error.path}'")
@notificationManager.addWarning("Permission denied '#{error.path}'")
return Promise.resolve()
when 'EPERM', 'EBUSY', 'ENXIO', 'EIO', 'ENOTCONN', 'UNKNOWN', 'ECONNRESET', 'EINVAL'
atom.notifications.addWarning("Unable to open '#{error.path ? uri}'", detail: error.message)
@notificationManager.addWarning("Unable to open '#{error.path ? uri}'", detail: error.message)
return Promise.resolve()
else
throw error
Promise.resolve(item)
.then (item) =>
if not pane
pane = new Pane(items: [item])
@paneContainer.root = pane
@itemOpened(item)
pane.activateItem(item)
pane.activateItem(item) if activateItem
pane.activate() if activatePane
initialLine = initialColumn = 0
@@ -464,6 +492,42 @@ class Workspace extends Model
@emitter.emit 'did-open', {uri, pane, item, index}
item
openTextFile: (uri, options) ->
filePath = @project.resolvePath(uri)
if filePath?
try
fs.closeSync(fs.openSync(filePath, 'r'))
catch error
# allow ENOENT errors to create an editor for paths that dont exist
throw error unless error.code is 'ENOENT'
fileSize = fs.getSizeSync(filePath)
largeFileMode = fileSize >= 2 * 1048576 # 2MB
if fileSize >= 20 * 1048576 # 20MB
choice = @applicationDelegate.confirm
message: 'Atom will be unresponsive during the loading of very large files.'
detailedMessage: "Do you still want to load this file?"
buttons: ["Proceed", "Cancel"]
if choice is 1
error = new Error
error.code = 'CANCELLED'
throw error
@project.bufferForPath(filePath, options).then (buffer) =>
@buildTextEditor(_.extend({buffer, largeFileMode}, options))
# Extended: Create a new text editor.
#
# Returns a {TextEditor}.
buildTextEditor: (params) ->
params = _.extend({
@config, @notificationManager, @packageManager, @clipboard, @viewRegistry,
@grammarRegistry, @project, @assert, @applicationDelegate
}, params)
new TextEditor(params)
# Public: Asynchronously reopens the last-closed item's URI if it hasn't already been
# reopened.
#
@@ -617,20 +681,20 @@ class Workspace extends Model
# Increase the editor font size by 1px.
increaseFontSize: ->
atom.config.set("editor.fontSize", atom.config.get("editor.fontSize") + 1)
@config.set("editor.fontSize", @config.get("editor.fontSize") + 1)
# Decrease the editor font size by 1px.
decreaseFontSize: ->
fontSize = atom.config.get("editor.fontSize")
atom.config.set("editor.fontSize", fontSize - 1) if fontSize > 1
fontSize = @config.get("editor.fontSize")
@config.set("editor.fontSize", fontSize - 1) if fontSize > 1
# Restore to the window's original editor font size.
resetFontSize: ->
if @originalFontSize
atom.config.set("editor.fontSize", @originalFontSize)
@config.set("editor.fontSize", @originalFontSize)
subscribeToFontSize: ->
atom.config.onDidChange 'editor.fontSize', ({oldValue}) =>
@config.onDidChange 'editor.fontSize', ({oldValue}) =>
@originalFontSize ?= oldValue
# Removes the item's uri from the list of potential items to reopen.
@@ -789,13 +853,14 @@ class Workspace extends Model
Section: Searching and Replacing
###
# Public: Performs a search across all the files in the workspace.
# Public: Performs a search across all files in the workspace.
#
# * `regex` {RegExp} to search with.
# * `options` (optional) {Object} (default: {})
# * `paths` An {Array} of glob patterns to search within
# * `onPathsSearched` (optional) {Function}
# * `iterator` {Function} callback on each file found
# * `options` (optional) {Object}
# * `paths` An {Array} of glob patterns to search within.
# * `onPathsSearched` (optional) {Function} to be periodically called
# with number of paths searched.
# * `iterator` {Function} callback on each file found.
#
# Returns a `Promise` with a `cancel()` method that will cancel all
# of the underlying searches that were started as part of this scan.
@@ -807,7 +872,7 @@ class Workspace extends Model
# Find a searcher for every Directory in the project. Each searcher that is matched
# will be associated with an Array of Directory objects in the Map.
directoriesForSearcher = new Map()
for directory in atom.project.getDirectories()
for directory in @project.getDirectories()
searcher = @defaultDirectorySearcher
for directorySearcher in @directorySearchers
if directorySearcher.canSearchDirectory(directory)
@@ -838,15 +903,15 @@ class Workspace extends Model
# Kick off all of the searches and unify them into one Promise.
allSearches = []
directoriesForSearcher.forEach (directories, searcher) ->
directoriesForSearcher.forEach (directories, searcher) =>
searchOptions =
inclusions: options.paths or []
includeHidden: true
excludeVcsIgnores: atom.config.get('core.excludeVcsIgnoredPaths')
exclusions: atom.config.get('core.ignoredNames')
follow: atom.config.get('core.followSymlinks')
didMatch: (result) ->
iterator(result) unless atom.project.isPathModified(result.filePath)
excludeVcsIgnores: @config.get('core.excludeVcsIgnoredPaths')
exclusions: @config.get('core.ignoredNames')
follow: @config.get('core.followSymlinks')
didMatch: (result) =>
iterator(result) unless @project.isPathModified(result.filePath)
didError: (error) ->
iterator(null, error)
didSearchPaths: (count) -> onPathsSearched(searcher, count)
@@ -854,9 +919,9 @@ class Workspace extends Model
allSearches.push(directorySearcher)
searchPromise = Promise.all(allSearches)
for buffer in atom.project.getBuffers() when buffer.isModified()
for buffer in @project.getBuffers() when buffer.isModified()
filePath = buffer.getPath()
continue unless atom.project.contains(filePath)
continue unless @project.contains(filePath)
matches = []
buffer.scan regex, (match) -> matches.push match
iterator {filePath, matches} if matches.length > 0
@@ -895,15 +960,15 @@ class Workspace extends Model
# Public: Performs a replace across all the specified files in the project.
#
# * `regex` A {RegExp} to search with.
# * `replacementText` Text to replace all matches of regex with
# * `filePaths` List of file path strings to run the replace on.
# * `replacementText` {String} to replace all matches of regex with.
# * `filePaths` An {Array} 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`
# * `options` {Object} with keys `filePath` and `replacements`.
#
# Returns a `Promise`.
replace: (regex, replacementText, filePaths, iterator) ->
new Promise (resolve, reject) ->
openPaths = (buffer.getPath() for buffer in atom.project.getBuffers())
new Promise (resolve, reject) =>
openPaths = (buffer.getPath() for buffer in @project.getBuffers())
outOfProcessPaths = _.difference(filePaths, openPaths)
inProcessFinished = not openPaths.length
@@ -922,7 +987,7 @@ class Workspace extends Model
task.on 'replace:path-replaced', iterator
task.on 'replace:file-error', (error) -> iterator(null, error)
for buffer in atom.project.getBuffers()
for buffer in @project.getBuffers()
continue unless buffer.getPath() in filePaths
replacements = buffer.replace(regex, replacementText, iterator)
iterator({filePath: buffer.getPath(), replacements}) if replacements