mirror of
https://github.com/atom/atom.git
synced 2026-04-28 03:01:47 -04:00
Merge branch 'master' into pr/10930
This commit is contained in:
@@ -166,8 +166,7 @@ class ApplicationDelegate
|
||||
|
||||
onDidOpenLocations: (callback) ->
|
||||
outerCallback = (event, message, detail) ->
|
||||
if message is 'open-locations'
|
||||
callback(detail)
|
||||
callback(detail) if message is 'open-locations'
|
||||
|
||||
ipcRenderer.on('message', outerCallback)
|
||||
new Disposable ->
|
||||
@@ -175,8 +174,38 @@ class ApplicationDelegate
|
||||
|
||||
onUpdateAvailable: (callback) ->
|
||||
outerCallback = (event, message, detail) ->
|
||||
if message is 'update-available'
|
||||
callback(detail)
|
||||
# TODO: Yes, this is strange that `onUpdateAvailable` is listening for
|
||||
# `did-begin-downloading-update`. We currently have no mechanism to know
|
||||
# if there is an update, so begin of downloading is a good proxy.
|
||||
callback(detail) if message is 'did-begin-downloading-update'
|
||||
|
||||
ipcRenderer.on('message', outerCallback)
|
||||
new Disposable ->
|
||||
ipcRenderer.removeListener('message', outerCallback)
|
||||
|
||||
onDidBeginDownloadingUpdate: (callback) ->
|
||||
@onUpdateAvailable(callback)
|
||||
|
||||
onDidBeginCheckingForUpdate: (callback) ->
|
||||
outerCallback = (event, message, detail) ->
|
||||
callback(detail) if message is 'checking-for-update'
|
||||
|
||||
ipcRenderer.on('message', outerCallback)
|
||||
new Disposable ->
|
||||
ipcRenderer.removeListener('message', outerCallback)
|
||||
|
||||
onDidCompleteDownloadingUpdate: (callback) ->
|
||||
outerCallback = (event, message, detail) ->
|
||||
# TODO: We could rename this event to `did-complete-downloading-update`
|
||||
callback(detail) if message is 'update-available'
|
||||
|
||||
ipcRenderer.on('message', outerCallback)
|
||||
new Disposable ->
|
||||
ipcRenderer.removeListener('message', outerCallback)
|
||||
|
||||
onUpdateNotAvailable: (callback) ->
|
||||
outerCallback = (event, message, detail) ->
|
||||
callback(detail) if message is 'update-not-available'
|
||||
|
||||
ipcRenderer.on('message', outerCallback)
|
||||
new Disposable ->
|
||||
@@ -206,3 +235,12 @@ class ApplicationDelegate
|
||||
|
||||
disablePinchToZoom: ->
|
||||
webFrame.setZoomLevelLimits(1, 1)
|
||||
|
||||
checkForUpdate: ->
|
||||
ipcRenderer.send('check-for-update')
|
||||
|
||||
restartAndInstallUpdate: ->
|
||||
ipcRenderer.send('install-update')
|
||||
|
||||
getAutoUpdateManagerState: ->
|
||||
ipcRenderer.sendSync('get-auto-update-manager-state')
|
||||
|
||||
@@ -40,6 +40,8 @@ Project = require './project'
|
||||
TextEditor = require './text-editor'
|
||||
TextBuffer = require 'text-buffer'
|
||||
Gutter = require './gutter'
|
||||
TextEditorRegistry = require './text-editor-registry'
|
||||
AutoUpdateManager = require './auto-update-manager'
|
||||
|
||||
WorkspaceElement = require './workspace-element'
|
||||
PanelContainerElement = require './panel-container-element'
|
||||
@@ -111,6 +113,12 @@ class AtomEnvironment extends Model
|
||||
# Public: A {Workspace} instance
|
||||
workspace: null
|
||||
|
||||
# Public: A {TextEditorRegistry} instance
|
||||
textEditors: null
|
||||
|
||||
# Private: An {AutoUpdateManager} instance
|
||||
autoUpdater: null
|
||||
|
||||
saveStateDebounceInterval: 1000
|
||||
|
||||
###
|
||||
@@ -121,6 +129,7 @@ class AtomEnvironment extends Model
|
||||
constructor: (params={}) ->
|
||||
{@blobStore, @applicationDelegate, @window, @document, configDirPath, @enablePersistence, onlyLoadBaseStyleSheets} = params
|
||||
|
||||
@unloaded = false
|
||||
@loadTime = null
|
||||
{devMode, safeMode, resourcePath, clearWindowState} = @getLoadSettings()
|
||||
|
||||
@@ -183,6 +192,9 @@ class AtomEnvironment extends Model
|
||||
})
|
||||
@themes.workspace = @workspace
|
||||
|
||||
@textEditors = new TextEditorRegistry
|
||||
@autoUpdater = new AutoUpdateManager({@applicationDelegate})
|
||||
|
||||
@config.load()
|
||||
|
||||
@themes.loadBaseStylesheets()
|
||||
@@ -219,7 +231,8 @@ class AtomEnvironment extends Model
|
||||
checkPortableHomeWritable()
|
||||
|
||||
attachSaveStateListeners: ->
|
||||
debouncedSaveState = _.debounce((=> @saveState()), @saveStateDebounceInterval)
|
||||
saveState = => @saveState({isUnloading: false}) unless @unloaded
|
||||
debouncedSaveState = _.debounce(saveState, @saveStateDebounceInterval)
|
||||
@document.addEventListener('mousedown', debouncedSaveState, true)
|
||||
@document.addEventListener('keydown', debouncedSaveState, true)
|
||||
@disposables.add new Disposable =>
|
||||
@@ -254,8 +267,6 @@ class AtomEnvironment extends Model
|
||||
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)
|
||||
|
||||
registerDefaultOpeners: ->
|
||||
@@ -327,6 +338,7 @@ class AtomEnvironment extends Model
|
||||
@commands.clear()
|
||||
@stylesElement.remove()
|
||||
@config.unobserveUserConfig()
|
||||
@autoUpdater.destroy()
|
||||
|
||||
@uninstallWindowEventHandler()
|
||||
|
||||
@@ -405,6 +417,16 @@ class AtomEnvironment extends Model
|
||||
getVersion: ->
|
||||
@appVersion ?= @getLoadSettings().appVersion
|
||||
|
||||
# Returns the release channel as a {String}. Will return one of `'dev', 'beta', 'stable'`
|
||||
getReleaseChannel: ->
|
||||
version = @getVersion()
|
||||
if version.indexOf('beta') > -1
|
||||
'beta'
|
||||
else if version.indexOf('dev') > -1
|
||||
'dev'
|
||||
else
|
||||
'stable'
|
||||
|
||||
# Public: Returns a {Boolean} that is `true` if the current version is an official release.
|
||||
isReleasedVersion: ->
|
||||
not /\w{7}/.test(@getVersion()) # Check if the release is a 7-character SHA prefix
|
||||
@@ -654,7 +676,7 @@ class AtomEnvironment extends Model
|
||||
@document.body.appendChild(@views.getView(@workspace))
|
||||
@backgroundStylesheet?.remove()
|
||||
|
||||
@watchProjectPath()
|
||||
@watchProjectPaths()
|
||||
|
||||
@packages.activate()
|
||||
@keymaps.loadUserKeymap()
|
||||
@@ -664,9 +686,9 @@ class AtomEnvironment extends Model
|
||||
|
||||
@openInitialEmptyEditorIfNecessary()
|
||||
|
||||
serialize: ->
|
||||
serialize: (options) ->
|
||||
version: @constructor.version
|
||||
project: @project.serialize()
|
||||
project: @project.serialize(options)
|
||||
workspace: @workspace.serialize()
|
||||
packageStates: @packages.serialize()
|
||||
grammars: {grammarOverridesByPath: @grammars.grammarOverridesByPath}
|
||||
@@ -676,9 +698,11 @@ class AtomEnvironment extends Model
|
||||
unloadEditorWindow: ->
|
||||
return if not @project
|
||||
|
||||
@saveState({isUnloading: true})
|
||||
@storeWindowBackground()
|
||||
@packages.deactivatePackages()
|
||||
@saveBlobStoreSync()
|
||||
@unloaded = true
|
||||
|
||||
openInitialEmptyEditorIfNecessary: ->
|
||||
return unless @config.get('core.openEmptyEditorOnStart')
|
||||
@@ -786,7 +810,7 @@ class AtomEnvironment extends Model
|
||||
@themes.load()
|
||||
|
||||
# Notify the browser project of the window's current project path
|
||||
watchProjectPath: ->
|
||||
watchProjectPaths: ->
|
||||
@disposables.add @project.onDidChangePaths =>
|
||||
@applicationDelegate.setRepresentedDirectoryPaths(@project.getPaths())
|
||||
|
||||
@@ -811,14 +835,20 @@ class AtomEnvironment extends Model
|
||||
|
||||
@blobStore.save()
|
||||
|
||||
saveState: ->
|
||||
saveState: (options) ->
|
||||
return Promise.resolve() unless @enablePersistence
|
||||
state = @serialize()
|
||||
|
||||
if storageKey = @getStateKey(@project?.getPaths())
|
||||
@stateStore.save(storageKey, state)
|
||||
else
|
||||
@applicationDelegate.setTemporaryWindowState(state)
|
||||
new Promise (resolve, reject) =>
|
||||
window.requestIdleCallback =>
|
||||
return if not @project
|
||||
|
||||
state = @serialize(options)
|
||||
savePromise =
|
||||
if storageKey = @getStateKey(@project?.getPaths())
|
||||
@stateStore.save(storageKey, state)
|
||||
else
|
||||
@applicationDelegate.setTemporaryWindowState(state)
|
||||
savePromise.catch(reject).then(resolve)
|
||||
|
||||
loadState: ->
|
||||
if @enablePersistence
|
||||
@@ -868,6 +898,7 @@ class AtomEnvironment extends Model
|
||||
detail: error.message
|
||||
dismissable: true
|
||||
|
||||
# TODO: We should deprecate the update events here, and use `atom.autoUpdater` instead
|
||||
onUpdateAvailable: (callback) ->
|
||||
@emitter.on 'update-available', callback
|
||||
|
||||
@@ -875,7 +906,8 @@ class AtomEnvironment extends Model
|
||||
@emitter.emit 'update-available', details
|
||||
|
||||
listenForUpdates: ->
|
||||
@disposables.add(@applicationDelegate.onUpdateAvailable(@updateAvailable.bind(this)))
|
||||
# listen for updates available locally (that have been successfully downloaded)
|
||||
@disposables.add(@autoUpdater.onDidCompleteDownloadingUpdate(@updateAvailable.bind(this)))
|
||||
|
||||
setBodyPlatformClass: ->
|
||||
@document.body.classList.add("platform-#{process.platform}")
|
||||
@@ -897,8 +929,8 @@ class AtomEnvironment extends Model
|
||||
openLocations: (locations) ->
|
||||
needsProjectPaths = @project?.getPaths().length is 0
|
||||
|
||||
for {pathToOpen, initialLine, initialColumn} in locations
|
||||
if pathToOpen? and needsProjectPaths
|
||||
for {pathToOpen, initialLine, initialColumn, forceAddToWindow} in locations
|
||||
if pathToOpen? and (needsProjectPaths or forceAddToWindow)
|
||||
if fs.existsSync(pathToOpen)
|
||||
@project.addPath(pathToOpen)
|
||||
else if fs.existsSync(path.dirname(pathToOpen))
|
||||
|
||||
73
src/auto-update-manager.js
Normal file
73
src/auto-update-manager.js
Normal file
@@ -0,0 +1,73 @@
|
||||
'use babel'
|
||||
|
||||
import {Emitter, CompositeDisposable} from 'event-kit'
|
||||
|
||||
export default class AutoUpdateManager {
|
||||
constructor ({applicationDelegate}) {
|
||||
this.applicationDelegate = applicationDelegate
|
||||
this.subscriptions = new CompositeDisposable()
|
||||
this.emitter = new Emitter()
|
||||
|
||||
this.subscriptions.add(
|
||||
applicationDelegate.onDidBeginCheckingForUpdate(() => {
|
||||
this.emitter.emit('did-begin-checking-for-update')
|
||||
}),
|
||||
applicationDelegate.onDidBeginDownloadingUpdate(() => {
|
||||
this.emitter.emit('did-begin-downloading-update')
|
||||
}),
|
||||
applicationDelegate.onDidCompleteDownloadingUpdate((details) => {
|
||||
this.emitter.emit('did-complete-downloading-update', details)
|
||||
}),
|
||||
applicationDelegate.onUpdateNotAvailable(() => {
|
||||
this.emitter.emit('update-not-available')
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
destroy () {
|
||||
this.subscriptions.dispose()
|
||||
this.emitter.dispose()
|
||||
}
|
||||
|
||||
checkForUpdate () {
|
||||
this.applicationDelegate.checkForUpdate()
|
||||
}
|
||||
|
||||
restartAndInstallUpdate () {
|
||||
this.applicationDelegate.restartAndInstallUpdate()
|
||||
}
|
||||
|
||||
getState () {
|
||||
return this.applicationDelegate.getAutoUpdateManagerState()
|
||||
}
|
||||
|
||||
platformSupportsUpdates () {
|
||||
return atom.getReleaseChannel() !== 'dev' && this.getState() !== 'unsupported'
|
||||
}
|
||||
|
||||
onDidBeginCheckingForUpdate (callback) {
|
||||
return this.emitter.on('did-begin-checking-for-update', callback)
|
||||
}
|
||||
|
||||
onDidBeginDownloadingUpdate (callback) {
|
||||
return this.emitter.on('did-begin-downloading-update', callback)
|
||||
}
|
||||
|
||||
onDidCompleteDownloadingUpdate (callback) {
|
||||
return this.emitter.on('did-complete-downloading-update', callback)
|
||||
}
|
||||
|
||||
// TODO: When https://github.com/atom/electron/issues/4587 is closed, we can
|
||||
// add an update-available event.
|
||||
// onUpdateAvailable (callback) {
|
||||
// return this.emitter.on('update-available', callback)
|
||||
// }
|
||||
|
||||
onUpdateNotAvailable (callback) {
|
||||
return this.emitter.on('update-not-available', callback)
|
||||
}
|
||||
|
||||
getPlatform () {
|
||||
return process.platform
|
||||
}
|
||||
}
|
||||
@@ -85,16 +85,16 @@ class AtomApplication
|
||||
else
|
||||
@loadState(options) or @openPath(options)
|
||||
|
||||
openWithOptions: ({pathsToOpen, executedFrom, urlsToOpen, test, pidToKillWhenClosed, devMode, safeMode, newWindow, logFile, profileStartup, timeout, clearWindowState}) ->
|
||||
openWithOptions: ({initialPaths, pathsToOpen, executedFrom, urlsToOpen, test, pidToKillWhenClosed, devMode, safeMode, newWindow, logFile, profileStartup, timeout, clearWindowState, addToLastWindow}) ->
|
||||
if test
|
||||
@runTests({headless: true, devMode, @resourcePath, executedFrom, pathsToOpen, logFile, timeout})
|
||||
else if pathsToOpen.length > 0
|
||||
@openPaths({pathsToOpen, executedFrom, pidToKillWhenClosed, newWindow, devMode, safeMode, profileStartup, clearWindowState})
|
||||
@openPaths({initialPaths, pathsToOpen, executedFrom, pidToKillWhenClosed, newWindow, devMode, safeMode, profileStartup, clearWindowState, addToLastWindow})
|
||||
else if urlsToOpen.length > 0
|
||||
@openUrl({urlToOpen, devMode, safeMode}) for urlToOpen in urlsToOpen
|
||||
else
|
||||
# Always open a editor window if this is the first instance of Atom.
|
||||
@openPath({pidToKillWhenClosed, newWindow, devMode, safeMode, profileStartup, clearWindowState})
|
||||
@openPath({initialPaths, pidToKillWhenClosed, newWindow, devMode, safeMode, profileStartup, clearWindowState, addToLastWindow})
|
||||
|
||||
# Public: Removes the {AtomWindow} from the global window list.
|
||||
removeWindow: (window) ->
|
||||
@@ -304,6 +304,15 @@ class AtomApplication
|
||||
ipcMain.on 'execute-javascript-in-dev-tools', (event, code) ->
|
||||
event.sender.devToolsWebContents?.executeJavaScript(code)
|
||||
|
||||
ipcMain.on 'check-for-update', =>
|
||||
@autoUpdateManager.check()
|
||||
|
||||
ipcMain.on 'get-auto-update-manager-state', (event) =>
|
||||
event.returnValue = @autoUpdateManager.getState()
|
||||
|
||||
ipcMain.on 'execute-javascript-in-dev-tools', (event, code) ->
|
||||
event.sender.devToolsWebContents?.executeJavaScript(code)
|
||||
|
||||
setupDockMenu: ->
|
||||
if process.platform is 'darwin'
|
||||
dockMenu = Menu.buildFromTemplate [
|
||||
@@ -408,8 +417,9 @@ class AtomApplication
|
||||
# :safeMode - Boolean to control the opened window's safe mode.
|
||||
# :profileStartup - Boolean to control creating a profile of the startup time.
|
||||
# :window - {AtomWindow} to open file paths in.
|
||||
openPath: ({pathToOpen, pidToKillWhenClosed, newWindow, devMode, safeMode, profileStartup, window, clearWindowState} = {}) ->
|
||||
@openPaths({pathsToOpen: [pathToOpen], pidToKillWhenClosed, newWindow, devMode, safeMode, profileStartup, window, clearWindowState})
|
||||
# :addToLastWindow - Boolean of whether this should be opened in last focused window.
|
||||
openPath: ({initialPaths, pathToOpen, pidToKillWhenClosed, newWindow, devMode, safeMode, profileStartup, window, clearWindowState, addToLastWindow} = {}) ->
|
||||
@openPaths({initialPaths, pathsToOpen: [pathToOpen], pidToKillWhenClosed, newWindow, devMode, safeMode, profileStartup, window, clearWindowState, addToLastWindow})
|
||||
|
||||
# Public: Opens multiple paths, in existing windows if possible.
|
||||
#
|
||||
@@ -421,11 +431,12 @@ class AtomApplication
|
||||
# :safeMode - Boolean to control the opened window's safe mode.
|
||||
# :windowDimensions - Object with height and width keys.
|
||||
# :window - {AtomWindow} to open file paths in.
|
||||
openPaths: ({pathsToOpen, executedFrom, pidToKillWhenClosed, newWindow, devMode, safeMode, windowDimensions, profileStartup, window, clearWindowState}={}) ->
|
||||
# :addToLastWindow - Boolean of whether this should be opened in last focused window.
|
||||
openPaths: ({initialPaths, pathsToOpen, executedFrom, pidToKillWhenClosed, newWindow, devMode, safeMode, windowDimensions, profileStartup, window, clearWindowState, addToLastWindow}={}) ->
|
||||
devMode = Boolean(devMode)
|
||||
safeMode = Boolean(safeMode)
|
||||
clearWindowState = Boolean(clearWindowState)
|
||||
locationsToOpen = (@locationForPathToOpen(pathToOpen, executedFrom) for pathToOpen in pathsToOpen)
|
||||
locationsToOpen = (@locationForPathToOpen(pathToOpen, executedFrom, addToLastWindow) for pathToOpen in pathsToOpen)
|
||||
pathsToOpen = (locationToOpen.pathToOpen for locationToOpen in locationsToOpen)
|
||||
|
||||
unless pidToKillWhenClosed or newWindow
|
||||
@@ -434,6 +445,7 @@ class AtomApplication
|
||||
unless existingWindow?
|
||||
if currentWindow = window ? @lastFocusedWindow
|
||||
existingWindow = currentWindow if (
|
||||
addToLastWindow or
|
||||
currentWindow.devMode is devMode and
|
||||
(
|
||||
stats.every((stat) -> stat.isFile?()) or
|
||||
@@ -457,7 +469,7 @@ class AtomApplication
|
||||
windowInitializationScript ?= require.resolve('../initialize-application-window')
|
||||
resourcePath ?= @resourcePath
|
||||
windowDimensions ?= @getDimensionsForNewWindow()
|
||||
openedWindow = new AtomWindow({locationsToOpen, windowInitializationScript, resourcePath, devMode, safeMode, windowDimensions, profileStartup, clearWindowState})
|
||||
openedWindow = new AtomWindow({initialPaths, locationsToOpen, windowInitializationScript, resourcePath, devMode, safeMode, windowDimensions, profileStartup, clearWindowState})
|
||||
|
||||
if pidToKillWhenClosed?
|
||||
@pidsToOpenWindows[pidToKillWhenClosed] = openedWindow
|
||||
@@ -500,7 +512,8 @@ class AtomApplication
|
||||
if (states = @storageFolder.load('application.json'))?.length > 0
|
||||
for state in states
|
||||
@openWithOptions(_.extend(options, {
|
||||
pathsToOpen: state.initialPaths
|
||||
initialPaths: state.initialPaths
|
||||
pathsToOpen: state.initialPaths.filter (directoryPath) -> fs.isDirectorySync(directoryPath)
|
||||
urlsToOpen: []
|
||||
devMode: @devMode
|
||||
safeMode: @safeMode
|
||||
@@ -602,7 +615,7 @@ class AtomApplication
|
||||
catch error
|
||||
require.resolve(path.resolve(__dirname, '..', '..', 'spec', 'jasmine-test-runner'))
|
||||
|
||||
locationForPathToOpen: (pathToOpen, executedFrom='') ->
|
||||
locationForPathToOpen: (pathToOpen, executedFrom='', forceAddToWindow) ->
|
||||
return {pathToOpen} unless pathToOpen
|
||||
|
||||
pathToOpen = pathToOpen.replace(/[:\s]+$/, '')
|
||||
@@ -618,7 +631,7 @@ class AtomApplication
|
||||
unless url.parse(pathToOpen).protocol?
|
||||
pathToOpen = path.resolve(executedFrom, fs.normalize(pathToOpen))
|
||||
|
||||
{pathToOpen, initialLine, initialColumn}
|
||||
{pathToOpen, initialLine, initialColumn, forceAddToWindow}
|
||||
|
||||
# Opens a native dialog to prompt the user for a path.
|
||||
#
|
||||
|
||||
@@ -17,7 +17,7 @@ class AtomWindow
|
||||
isSpec: null
|
||||
|
||||
constructor: (settings={}) ->
|
||||
{@resourcePath, pathToOpen, locationsToOpen, @isSpec, @headless, @safeMode, @devMode} = settings
|
||||
{@resourcePath, initialPaths, pathToOpen, locationsToOpen, @isSpec, @headless, @safeMode, @devMode} = settings
|
||||
locationsToOpen ?= [{pathToOpen}] if pathToOpen
|
||||
locationsToOpen ?= []
|
||||
|
||||
@@ -47,13 +47,7 @@ class AtomWindow
|
||||
loadSettings.safeMode ?= false
|
||||
loadSettings.atomHome = process.env.ATOM_HOME
|
||||
loadSettings.clearWindowState ?= false
|
||||
|
||||
# Only send to the first non-spec window created
|
||||
if @constructor.includeShellLoadTime and not @isSpec
|
||||
@constructor.includeShellLoadTime = false
|
||||
loadSettings.shellLoadTime ?= Date.now() - global.shellStartTime
|
||||
|
||||
loadSettings.initialPaths =
|
||||
loadSettings.initialPaths ?=
|
||||
for {pathToOpen} in locationsToOpen when pathToOpen
|
||||
if fs.statSyncNoException(pathToOpen).isFile?()
|
||||
path.dirname(pathToOpen)
|
||||
@@ -62,6 +56,13 @@ class AtomWindow
|
||||
|
||||
loadSettings.initialPaths.sort()
|
||||
|
||||
# Only send to the first non-spec window created
|
||||
if @constructor.includeShellLoadTime and not @isSpec
|
||||
@constructor.includeShellLoadTime = false
|
||||
loadSettings.shellLoadTime ?= Date.now() - global.shellStartTime
|
||||
|
||||
@browserWindow.loadSettings = loadSettings
|
||||
|
||||
@browserWindow.once 'window:loaded', =>
|
||||
@emit 'window:loaded'
|
||||
@loaded = true
|
||||
|
||||
@@ -39,16 +39,24 @@ class AutoUpdateManager
|
||||
|
||||
autoUpdater.on 'checking-for-update', =>
|
||||
@setState(CheckingState)
|
||||
@emitWindowEvent('checking-for-update')
|
||||
|
||||
autoUpdater.on 'update-not-available', =>
|
||||
@setState(NoUpdateAvailableState)
|
||||
@emitWindowEvent('update-not-available')
|
||||
|
||||
autoUpdater.on 'update-available', =>
|
||||
@setState(DownladingState)
|
||||
# We use sendMessage to send an event called 'update-available' in 'update-downloaded'
|
||||
# once the update download is complete. This mismatch between the electron
|
||||
# autoUpdater events is unfortunate but in the interest of not changing the
|
||||
# one existing event handled by applicationDelegate
|
||||
@emitWindowEvent('did-begin-downloading-update')
|
||||
@emit('did-begin-download')
|
||||
|
||||
autoUpdater.on 'update-downloaded', (event, releaseNotes, @releaseVersion) =>
|
||||
@setState(UpdateAvailableState)
|
||||
@emitUpdateAvailableEvent(@getWindows()...)
|
||||
@emitUpdateAvailableEvent()
|
||||
|
||||
@config.onDidChange 'core.automaticallyUpdate', ({newValue}) =>
|
||||
if newValue
|
||||
@@ -64,10 +72,14 @@ class AutoUpdateManager
|
||||
when 'linux'
|
||||
@setState(UnsupportedState)
|
||||
|
||||
emitUpdateAvailableEvent: (windows...) ->
|
||||
emitUpdateAvailableEvent: ->
|
||||
return unless @releaseVersion?
|
||||
for atomWindow in windows
|
||||
atomWindow.sendMessage('update-available', {@releaseVersion})
|
||||
@emitWindowEvent('update-available', {@releaseVersion})
|
||||
return
|
||||
|
||||
emitWindowEvent: (eventName, payload) ->
|
||||
for atomWindow in @getWindows()
|
||||
atomWindow.sendMessage(eventName, payload)
|
||||
return
|
||||
|
||||
setState: (state) ->
|
||||
|
||||
@@ -132,6 +132,7 @@ parseCommandLine = ->
|
||||
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 information.')
|
||||
options.alias('w', 'wait').boolean('w').describe('w', 'Wait for window to be closed before returning.')
|
||||
options.alias('a', 'add').boolean('a').describe('add', 'Open path as a new project in last used window.')
|
||||
options.string('socket-path')
|
||||
options.string('user-data-dir')
|
||||
options.boolean('clear-window-state').describe('clear-window-state', 'Delete all Atom environment state.')
|
||||
@@ -146,6 +147,7 @@ parseCommandLine = ->
|
||||
writeFullVersion()
|
||||
process.exit(0)
|
||||
|
||||
addToLastWindow = args['add']
|
||||
executedFrom = args['executed-from']?.toString() ? process.cwd()
|
||||
devMode = args['dev']
|
||||
safeMode = args['safe']
|
||||
@@ -183,6 +185,6 @@ parseCommandLine = ->
|
||||
{resourcePath, devResourcePath, pathsToOpen, urlsToOpen, executedFrom, test,
|
||||
version, pidToKillWhenClosed, devMode, safeMode, newWindow,
|
||||
logFile, socketPath, userDataDir, profileStartup, timeout, setPortable,
|
||||
clearWindowState}
|
||||
clearWindowState, addToLastWindow}
|
||||
|
||||
start()
|
||||
|
||||
@@ -11,9 +11,11 @@ exeName = path.basename(process.execPath)
|
||||
if process.env.SystemRoot
|
||||
system32Path = path.join(process.env.SystemRoot, 'System32')
|
||||
regPath = path.join(system32Path, 'reg.exe')
|
||||
powershellPath = path.join(system32Path, 'WindowsPowerShell', 'v1.0', 'powershell.exe')
|
||||
setxPath = path.join(system32Path, 'setx.exe')
|
||||
else
|
||||
regPath = 'reg.exe'
|
||||
powershellPath = 'powershell.exe'
|
||||
setxPath = 'setx.exe'
|
||||
|
||||
# Registry keys used for context menu
|
||||
@@ -44,11 +46,31 @@ spawn = (command, args, callback) ->
|
||||
error?.code ?= code
|
||||
error?.stdout ?= stdout
|
||||
callback?(error, stdout)
|
||||
# This is necessary if using Powershell 2 on Windows 7 to get the events to raise
|
||||
# http://stackoverflow.com/questions/9155289/calling-powershell-from-nodejs
|
||||
spawnedProcess.stdin.end()
|
||||
|
||||
|
||||
# Spawn reg.exe and callback when it completes
|
||||
spawnReg = (args, callback) ->
|
||||
spawn(regPath, args, callback)
|
||||
|
||||
# Spawn powershell.exe and callback when it completes
|
||||
spawnPowershell = (args, callback) ->
|
||||
# set encoding and execute the command, capture the output, and return it via .NET's console in order to have consistent UTF-8 encoding
|
||||
# http://stackoverflow.com/questions/22349139/utf-8-output-from-powershell
|
||||
# to address https://github.com/atom/atom/issues/5063
|
||||
args[0] = """
|
||||
[Console]::OutputEncoding=[System.Text.Encoding]::UTF8
|
||||
$output=#{args[0]}
|
||||
[Console]::WriteLine($output)
|
||||
"""
|
||||
args.unshift('-command')
|
||||
args.unshift('RemoteSigned')
|
||||
args.unshift('-ExecutionPolicy')
|
||||
args.unshift('-noprofile')
|
||||
spawn(powershellPath, args, callback)
|
||||
|
||||
# Spawn setx.exe and callback when it completes
|
||||
spawnSetx = (args, callback) ->
|
||||
spawn(setxPath, args, callback)
|
||||
@@ -82,46 +104,14 @@ installContextMenu = (callback) ->
|
||||
installMenu backgroundKeyPath, '%V', ->
|
||||
installFileHandler(callback)
|
||||
|
||||
isAscii = (text) ->
|
||||
index = 0
|
||||
while index < text.length
|
||||
return false if text.charCodeAt(index) > 127
|
||||
index++
|
||||
true
|
||||
|
||||
# Get the user's PATH environment variable registry value.
|
||||
getPath = (callback) ->
|
||||
spawnReg ['query', environmentKeyPath, '/v', 'Path'], (error, stdout) ->
|
||||
spawnPowershell ['[environment]::GetEnvironmentVariable(\'Path\',\'User\')'], (error, stdout) ->
|
||||
if error?
|
||||
if error.code is 1
|
||||
# FIXME Don't overwrite path when reading value is disabled
|
||||
# https://github.com/atom/atom/issues/5092
|
||||
if stdout.indexOf('ERROR: Registry editing has been disabled by your administrator.') isnt -1
|
||||
return callback(error)
|
||||
return callback(error)
|
||||
|
||||
# The query failed so the Path does not exist yet in the registry
|
||||
return callback(null, '')
|
||||
else
|
||||
return callback(error)
|
||||
|
||||
# Registry query output is in the form:
|
||||
#
|
||||
# HKEY_CURRENT_USER\Environment
|
||||
# Path REG_SZ C:\a\folder\on\the\path;C\another\folder
|
||||
#
|
||||
|
||||
lines = stdout.split(/[\r\n]+/).filter (line) -> line
|
||||
segments = lines[lines.length - 1]?.split(' ')
|
||||
if segments[1] is 'Path' and segments.length >= 3
|
||||
pathEnv = segments?[3..].join(' ')
|
||||
if isAscii(pathEnv)
|
||||
callback(null, pathEnv)
|
||||
else
|
||||
# FIXME Don't corrupt non-ASCII PATH values
|
||||
# https://github.com/atom/atom/issues/5063
|
||||
callback(new Error('PATH contains non-ASCII values'))
|
||||
else
|
||||
callback(new Error('Registry query for PATH failed'))
|
||||
pathOutput = stdout.replace(/^\s+|\s+$/g, '')
|
||||
callback(null, pathOutput)
|
||||
|
||||
# Uninstall the Open with Atom explorer context menu items via the registry.
|
||||
uninstallContextMenu = (callback) ->
|
||||
|
||||
@@ -46,7 +46,7 @@ class BufferedNodeProcess extends BufferedProcess
|
||||
|
||||
options ?= {}
|
||||
options.env ?= Object.create(process.env)
|
||||
options.env['ATOM_SHELL_INTERNAL_RUN_AS_NODE'] = 1
|
||||
options.env['ELECTRON_RUN_AS_NODE'] = 1
|
||||
|
||||
args = args?.slice() ? []
|
||||
args.unshift(command)
|
||||
|
||||
@@ -85,5 +85,5 @@ parseAlpha = (alpha) ->
|
||||
|
||||
numberToHexString = (number) ->
|
||||
hex = number.toString(16)
|
||||
hex = "0#{hex}" if number < 10
|
||||
hex = "0#{hex}" if number < 16
|
||||
hex
|
||||
|
||||
@@ -319,6 +319,23 @@ ScopeDescriptor = require './scope-descriptor'
|
||||
# * line breaks - `line breaks<br/>`
|
||||
# * ~~strikethrough~~ - `~~strikethrough~~`
|
||||
#
|
||||
# #### order
|
||||
#
|
||||
# The settings view orders your settings alphabetically. You can override this
|
||||
# ordering with the order key.
|
||||
#
|
||||
# ```coffee
|
||||
# config:
|
||||
# zSetting:
|
||||
# type: 'integer'
|
||||
# default: 4
|
||||
# order: 1
|
||||
# aSetting:
|
||||
# type: 'integer'
|
||||
# default: 4
|
||||
# order: 2
|
||||
# ```
|
||||
#
|
||||
# ## Best practices
|
||||
#
|
||||
# * Don't depend on (or write to) configuration keys outside of your keypath.
|
||||
|
||||
@@ -596,7 +596,15 @@ export default class GitRepositoryAsync {
|
||||
.then(([repo, headCommit]) => Promise.all([repo, headCommit.getTree()]))
|
||||
.then(([repo, tree]) => {
|
||||
const options = new Git.DiffOptions()
|
||||
options.contextLines = 0
|
||||
options.flags = Git.Diff.OPTION.DISABLE_PATHSPEC_MATCH
|
||||
options.pathspec = this.relativize(_path, repo.workdir())
|
||||
if (process.platform === 'win32') {
|
||||
// Ignore eol of line differences on windows so that files checked in
|
||||
// as LF don't report every line modified when the text contains CRLF
|
||||
// endings.
|
||||
options.flags |= Git.Diff.OPTION.IGNORE_WHITESPACE_EOL
|
||||
}
|
||||
return Git.Diff.treeToWorkdir(repo, tree, options)
|
||||
})
|
||||
.then(diff => this._getDiffLines(diff))
|
||||
@@ -775,12 +783,17 @@ export default class GitRepositoryAsync {
|
||||
|
||||
// Get the current branch and update this.branch.
|
||||
//
|
||||
// Returns a {Promise} which resolves to the {String} branch name.
|
||||
// Returns a {Promise} which resolves to a {boolean} indicating whether the
|
||||
// branch name changed.
|
||||
_refreshBranch () {
|
||||
return this.getRepo()
|
||||
.then(repo => repo.getCurrentBranch())
|
||||
.then(ref => ref.name())
|
||||
.then(branchName => this.branch = branchName)
|
||||
.then(branchName => {
|
||||
const changed = branchName !== this.branch
|
||||
this.branch = branchName
|
||||
return changed
|
||||
})
|
||||
}
|
||||
|
||||
// Refresh the cached ahead/behind count with the given branch.
|
||||
@@ -788,10 +801,15 @@ export default class GitRepositoryAsync {
|
||||
// * `branchName` The {String} name of the branch whose ahead/behind should be
|
||||
// used for the refresh.
|
||||
//
|
||||
// Returns a {Promise} which will resolve to {null}.
|
||||
// Returns a {Promise} which will resolve to a {boolean} indicating whether
|
||||
// the ahead/behind count changed.
|
||||
_refreshAheadBehindCount (branchName) {
|
||||
return this.getAheadBehindCount(branchName)
|
||||
.then(counts => this.upstream = counts)
|
||||
.then(counts => {
|
||||
const changed = !_.isEqual(counts, this.upstream)
|
||||
this.upstream = counts
|
||||
return changed
|
||||
})
|
||||
}
|
||||
|
||||
// Get the status for this repository.
|
||||
@@ -897,15 +915,15 @@ export default class GitRepositoryAsync {
|
||||
|
||||
// Refresh the cached status.
|
||||
//
|
||||
// Returns a {Promise} which will resolve to {null}.
|
||||
// Returns a {Promise} which will resolve to a {boolean} indicating whether
|
||||
// any statuses changed.
|
||||
_refreshStatus () {
|
||||
return Promise.all([this._getRepositoryStatus(), this._getSubmoduleStatuses()])
|
||||
.then(([repositoryStatus, submoduleStatus]) => {
|
||||
const statusesByPath = _.extend({}, repositoryStatus, submoduleStatus)
|
||||
if (!_.isEqual(this.pathStatusCache, statusesByPath) && this.emitter != null) {
|
||||
this.emitter.emit('did-change-statuses')
|
||||
}
|
||||
const changed = !_.isEqual(this.pathStatusCache, statusesByPath)
|
||||
this.pathStatusCache = statusesByPath
|
||||
return changed
|
||||
})
|
||||
}
|
||||
|
||||
@@ -915,11 +933,17 @@ export default class GitRepositoryAsync {
|
||||
refreshStatus () {
|
||||
const status = this._refreshStatus()
|
||||
const branch = this._refreshBranch()
|
||||
const aheadBehind = branch.then(branchName => this._refreshAheadBehindCount(branchName))
|
||||
const aheadBehind = branch.then(() => this._refreshAheadBehindCount(this.branch))
|
||||
|
||||
this._refreshingPromise = this._refreshingPromise.then(_ => {
|
||||
return Promise.all([status, branch, aheadBehind])
|
||||
.then(_ => null)
|
||||
.then(([statusChanged, branchChanged, aheadBehindChanged]) => {
|
||||
if (this.emitter && (statusChanged || branchChanged || aheadBehindChanged)) {
|
||||
this.emitter.emit('did-change-statuses')
|
||||
}
|
||||
|
||||
return null
|
||||
})
|
||||
// Because all these refresh steps happen asynchronously, it's entirely
|
||||
// possible the repository was destroyed while we were working. In which
|
||||
// case we should just swallow the error.
|
||||
|
||||
@@ -147,13 +147,11 @@ class LanguageMode
|
||||
|
||||
if bufferRow > 0
|
||||
for currentRow in [bufferRow-1..0] by -1
|
||||
break if @buffer.isRowBlank(currentRow)
|
||||
break unless @editor.displayBuffer.tokenizedBuffer.tokenizedLineForRow(currentRow).isComment()
|
||||
startRow = currentRow
|
||||
|
||||
if bufferRow < @buffer.getLastRow()
|
||||
for currentRow in [bufferRow+1..@buffer.getLastRow()] by 1
|
||||
break if @buffer.isRowBlank(currentRow)
|
||||
break unless @editor.displayBuffer.tokenizedBuffer.tokenizedLineForRow(currentRow).isComment()
|
||||
endRow = currentRow
|
||||
|
||||
|
||||
@@ -128,8 +128,12 @@ class PackageManager
|
||||
|
||||
# Public: Get the path to the apm command.
|
||||
#
|
||||
# Uses the value of the `core.apmPath` config setting if it exists.
|
||||
#
|
||||
# Return a {String} file path to apm.
|
||||
getApmPath: ->
|
||||
configPath = atom.config.get('core.apmPath')
|
||||
return configPath if configPath
|
||||
return @apmPath if @apmPath?
|
||||
|
||||
commandName = 'apm'
|
||||
@@ -541,11 +545,12 @@ class PackageManager
|
||||
unless typeof metadata.name is 'string' and metadata.name.length > 0
|
||||
metadata.name = packageName
|
||||
|
||||
if metadata.repository?.type is 'git' and typeof metadata.repository.url is 'string'
|
||||
metadata.repository.url = metadata.repository.url.replace(/(^git\+)|(\.git$)/g, '')
|
||||
|
||||
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\+/, '')
|
||||
|
||||
145
src/pane.coffee
145
src/pane.coffee
@@ -1,5 +1,6 @@
|
||||
Grim = require 'grim'
|
||||
{find, compact, extend, last} = require 'underscore-plus'
|
||||
{Emitter} = require 'event-kit'
|
||||
{CompositeDisposable, Emitter} = require 'event-kit'
|
||||
Model = require './model'
|
||||
PaneAxis = require './pane-axis'
|
||||
TextEditor = require './text-editor'
|
||||
@@ -8,6 +9,11 @@ TextEditor = require './text-editor'
|
||||
# Panes can contain multiple items, one of which is *active* at a given time.
|
||||
# The view corresponding to the active item is displayed in the interface. In
|
||||
# the default configuration, tabs are also displayed for each item.
|
||||
#
|
||||
# Each pane may also contain one *pending* item. When a pending item is added
|
||||
# to a pane, it will replace the currently pending item, if any, instead of
|
||||
# simply being added. In the default configuration, the text in the tab for
|
||||
# pending items is shown in italics.
|
||||
module.exports =
|
||||
class Pane extends Model
|
||||
container: undefined
|
||||
@@ -15,7 +21,7 @@ class Pane extends Model
|
||||
focused: false
|
||||
|
||||
@deserialize: (state, {deserializers, applicationDelegate, config, notifications}) ->
|
||||
{items, activeItemURI, activeItemUri} = state
|
||||
{items, itemStackIndices, activeItemURI, activeItemUri} = state
|
||||
activeItemURI ?= activeItemUri
|
||||
state.items = compact(items.map (itemState) -> deserializers.deserialize(itemState))
|
||||
state.activeItem = find state.items, (item) ->
|
||||
@@ -37,20 +43,25 @@ class Pane extends Model
|
||||
} = params
|
||||
|
||||
@emitter = new Emitter
|
||||
@itemSubscriptions = new WeakMap
|
||||
@subscriptionsPerItem = new WeakMap
|
||||
@items = []
|
||||
@itemStack = []
|
||||
|
||||
@addItems(compact(params?.items ? []))
|
||||
@setActiveItem(@items[0]) unless @getActiveItem()?
|
||||
@addItemsToStack(params?.itemStackIndices ? [])
|
||||
@setFlexScale(params?.flexScale ? 1)
|
||||
|
||||
serialize: ->
|
||||
if typeof @activeItem?.getURI is 'function'
|
||||
activeItemURI = @activeItem.getURI()
|
||||
itemsToBeSerialized = compact(@items.map((item) -> item if typeof item.serialize is 'function'))
|
||||
itemStackIndices = (itemsToBeSerialized.indexOf(item) for item in @itemStack when typeof item.serialize is 'function')
|
||||
|
||||
deserializer: 'Pane'
|
||||
id: @id
|
||||
items: compact(@items.map((item) -> item.serialize?()))
|
||||
items: itemsToBeSerialized.map((item) -> item.serialize())
|
||||
itemStackIndices: itemStackIndices
|
||||
activeItemURI: activeItemURI
|
||||
focused: @focused
|
||||
flexScale: @flexScale
|
||||
@@ -260,8 +271,8 @@ class Pane extends Model
|
||||
getPanes: -> [this]
|
||||
|
||||
unsubscribeFromItem: (item) ->
|
||||
@itemSubscriptions.get(item)?.dispose()
|
||||
@itemSubscriptions.delete(item)
|
||||
@subscriptionsPerItem.get(item)?.dispose()
|
||||
@subscriptionsPerItem.delete(item)
|
||||
|
||||
###
|
||||
Section: Items
|
||||
@@ -278,12 +289,30 @@ class Pane extends Model
|
||||
# Returns a pane item.
|
||||
getActiveItem: -> @activeItem
|
||||
|
||||
setActiveItem: (activeItem) ->
|
||||
setActiveItem: (activeItem, options) ->
|
||||
{modifyStack} = options if options?
|
||||
unless activeItem is @activeItem
|
||||
@addItemToStack(activeItem) unless modifyStack is false
|
||||
@activeItem = activeItem
|
||||
@emitter.emit 'did-change-active-item', @activeItem
|
||||
@activeItem
|
||||
|
||||
# Build the itemStack after deserializing
|
||||
addItemsToStack: (itemStackIndices) ->
|
||||
if @items.length > 0
|
||||
if itemStackIndices.length is 0 or itemStackIndices.length isnt @items.length or itemStackIndices.indexOf(-1) >= 0
|
||||
itemStackIndices = (i for i in [0..@items.length-1])
|
||||
for itemIndex in itemStackIndices
|
||||
@addItemToStack(@items[itemIndex])
|
||||
return
|
||||
|
||||
# Add item (or move item) to the end of the itemStack
|
||||
addItemToStack: (newItem) ->
|
||||
return unless newItem?
|
||||
index = @itemStack.indexOf(newItem)
|
||||
@itemStack.splice(index, 1) unless index is -1
|
||||
@itemStack.push(newItem)
|
||||
|
||||
# Return an {TextEditor} if the pane item is an {TextEditor}, or null otherwise.
|
||||
getActiveEditor: ->
|
||||
@activeItem if @activeItem instanceof TextEditor
|
||||
@@ -296,6 +325,29 @@ class Pane extends Model
|
||||
itemAtIndex: (index) ->
|
||||
@items[index]
|
||||
|
||||
# Makes the next item in the itemStack active.
|
||||
activateNextRecentlyUsedItem: ->
|
||||
if @items.length > 1
|
||||
@itemStackIndex = @itemStack.length - 1 unless @itemStackIndex?
|
||||
@itemStackIndex = @itemStack.length if @itemStackIndex is 0
|
||||
@itemStackIndex = @itemStackIndex - 1
|
||||
nextRecentlyUsedItem = @itemStack[@itemStackIndex]
|
||||
@setActiveItem(nextRecentlyUsedItem, modifyStack: false)
|
||||
|
||||
# Makes the previous item in the itemStack active.
|
||||
activatePreviousRecentlyUsedItem: ->
|
||||
if @items.length > 1
|
||||
if @itemStackIndex + 1 is @itemStack.length or not @itemStackIndex?
|
||||
@itemStackIndex = -1
|
||||
@itemStackIndex = @itemStackIndex + 1
|
||||
previousRecentlyUsedItem = @itemStack[@itemStackIndex]
|
||||
@setActiveItem(previousRecentlyUsedItem, modifyStack: false)
|
||||
|
||||
# Moves the active item to the end of the itemStack once the ctrl key is lifted
|
||||
moveActiveItemToTopOfStack: ->
|
||||
delete @itemStackIndex
|
||||
@addItemToStack(@activeItem)
|
||||
|
||||
# Public: Makes the next item active.
|
||||
activateNextItem: ->
|
||||
index = @getActiveItemIndex()
|
||||
@@ -342,43 +394,81 @@ class Pane extends Model
|
||||
|
||||
# Public: Make the given item *active*, causing it to be displayed by
|
||||
# the pane's view.
|
||||
activateItem: (item) ->
|
||||
#
|
||||
# * `options` (optional) {Object}
|
||||
# * `pending` (optional) {Boolean} indicating that the item should be added
|
||||
# in a pending state if it does not yet exist in the pane. Existing pending
|
||||
# items in a pane are replaced with new pending items when they are opened.
|
||||
activateItem: (item, options={}) ->
|
||||
if item?
|
||||
if @activeItem?.isPending?()
|
||||
if @getPendingItem() is @activeItem
|
||||
index = @getActiveItemIndex()
|
||||
else
|
||||
index = @getActiveItemIndex() + 1
|
||||
@addItem(item, index, false)
|
||||
@addItem(item, extend({}, options, {index: index}))
|
||||
@setActiveItem(item)
|
||||
|
||||
# Public: Add the given item to the pane.
|
||||
#
|
||||
# * `item` The item to add. It can be a model with an associated view or a
|
||||
# view.
|
||||
# * `index` (optional) {Number} indicating the index at which to add the item.
|
||||
# If omitted, the item is added after the current active item.
|
||||
# * `options` (optional) {Object}
|
||||
# * `index` (optional) {Number} indicating the index at which to add the item.
|
||||
# If omitted, the item is added after the current active item.
|
||||
# * `pending` (optional) {Boolean} indicating that the item should be
|
||||
# added in a pending state. Existing pending items in a pane are replaced with
|
||||
# new pending items when they are opened.
|
||||
#
|
||||
# Returns the added item.
|
||||
addItem: (item, index=@getActiveItemIndex() + 1, moved=false) ->
|
||||
addItem: (item, options={}) ->
|
||||
# Backward compat with old API:
|
||||
# addItem(item, index=@getActiveItemIndex() + 1)
|
||||
if typeof options is "number"
|
||||
Grim.deprecate("Pane::addItem(item, #{options}) is deprecated in favor of Pane::addItem(item, {index: #{options}})")
|
||||
options = index: options
|
||||
|
||||
index = options.index ? @getActiveItemIndex() + 1
|
||||
moved = options.moved ? false
|
||||
pending = options.pending ? false
|
||||
|
||||
throw new Error("Pane items must be objects. Attempted to add item #{item}.") unless item? and typeof item is 'object'
|
||||
throw new Error("Adding a pane item with URI '#{item.getURI?()}' that has already been destroyed") if item.isDestroyed?()
|
||||
|
||||
return if item in @items
|
||||
|
||||
if item.isPending?()
|
||||
for existingItem, i in @items
|
||||
if existingItem.isPending?()
|
||||
@destroyItem(existingItem)
|
||||
break
|
||||
|
||||
if typeof item.onDidDestroy is 'function'
|
||||
@itemSubscriptions.set item, item.onDidDestroy => @removeItem(item, false)
|
||||
itemSubscriptions = new CompositeDisposable
|
||||
itemSubscriptions.add item.onDidDestroy => @removeItem(item, false)
|
||||
if typeof item.onDidTerminatePendingState is "function"
|
||||
itemSubscriptions.add item.onDidTerminatePendingState =>
|
||||
@clearPendingItem() if @getPendingItem() is item
|
||||
itemSubscriptions.add item.onDidDestroy => @removeItem(item, false)
|
||||
@subscriptionsPerItem.set item, itemSubscriptions
|
||||
|
||||
@items.splice(index, 0, item)
|
||||
lastPendingItem = @getPendingItem()
|
||||
@setPendingItem(item) if pending
|
||||
|
||||
@emitter.emit 'did-add-item', {item, index, moved}
|
||||
@destroyItem(lastPendingItem) if lastPendingItem? and not moved
|
||||
@setActiveItem(item) unless @getActiveItem()?
|
||||
item
|
||||
|
||||
setPendingItem: (item) =>
|
||||
if @pendingItem isnt item
|
||||
mostRecentPendingItem = @pendingItem
|
||||
@pendingItem = item
|
||||
@emitter.emit 'item-did-terminate-pending-state', mostRecentPendingItem
|
||||
|
||||
getPendingItem: =>
|
||||
@pendingItem or null
|
||||
|
||||
clearPendingItem: =>
|
||||
@setPendingItem(null)
|
||||
|
||||
onItemDidTerminatePendingState: (callback) =>
|
||||
@emitter.on 'item-did-terminate-pending-state', callback
|
||||
|
||||
# Public: Add the given items to the pane.
|
||||
#
|
||||
# * `items` An {Array} of items to add. Items can be views or models with
|
||||
@@ -390,13 +480,14 @@ class Pane extends Model
|
||||
# Returns an {Array} of added items.
|
||||
addItems: (items, index=@getActiveItemIndex() + 1) ->
|
||||
items = items.filter (item) => not (item in @items)
|
||||
@addItem(item, index + i, false) for item, i in items
|
||||
@addItem(item, {index: index + i}) for item, i in items
|
||||
items
|
||||
|
||||
removeItem: (item, moved) ->
|
||||
index = @items.indexOf(item)
|
||||
return if index is -1
|
||||
|
||||
@pendingItem = null if @getPendingItem() is item
|
||||
@removeItemFromStack(item)
|
||||
@emitter.emit 'will-remove-item', {item, index, destroyed: not moved, moved}
|
||||
@unsubscribeFromItem(item)
|
||||
|
||||
@@ -412,6 +503,14 @@ class Pane extends Model
|
||||
@container?.didDestroyPaneItem({item, index, pane: this}) unless moved
|
||||
@destroy() if @items.length is 0 and @config.get('core.destroyEmptyPanes')
|
||||
|
||||
# Remove the given item from the itemStack.
|
||||
#
|
||||
# * `item` The item to remove.
|
||||
# * `index` {Number} indicating the index to which to remove the item from the itemStack.
|
||||
removeItemFromStack: (item) ->
|
||||
index = @itemStack.indexOf(item)
|
||||
@itemStack.splice(index, 1) unless index is -1
|
||||
|
||||
# Public: Move the given item to the given index.
|
||||
#
|
||||
# * `item` The item to move.
|
||||
@@ -430,7 +529,7 @@ class Pane extends Model
|
||||
# given pane.
|
||||
moveItemToPane: (item, pane, index) ->
|
||||
@removeItem(item, true)
|
||||
pane.addItem(item, index, true)
|
||||
pane.addItem(item, {index: index, moved: true})
|
||||
|
||||
# Public: Destroy the active item and activate the next item.
|
||||
destroyActiveItem: ->
|
||||
|
||||
@@ -54,8 +54,9 @@ class Project extends Model
|
||||
Section: Serialization
|
||||
###
|
||||
|
||||
deserialize: (state, deserializerManager) ->
|
||||
deserialize: (state) ->
|
||||
state.paths = [state.path] if state.path? # backward compatibility
|
||||
state.paths = state.paths.filter (directoryPath) -> fs.isDirectorySync(directoryPath)
|
||||
|
||||
@buffers = _.compact state.buffers.map (bufferState) ->
|
||||
# Check that buffer's file path is accessible
|
||||
@@ -65,15 +66,15 @@ class Project extends Model
|
||||
fs.closeSync(fs.openSync(bufferState.filePath, 'r'))
|
||||
catch error
|
||||
return unless error.code is 'ENOENT'
|
||||
deserializerManager.deserialize(bufferState)
|
||||
TextBuffer.deserialize(bufferState)
|
||||
|
||||
@subscribeToBuffer(buffer) for buffer in @buffers
|
||||
@setPaths(state.paths)
|
||||
|
||||
serialize: ->
|
||||
serialize: (options={}) ->
|
||||
deserializer: 'Project'
|
||||
paths: @getPaths()
|
||||
buffers: _.compact(@buffers.map (buffer) -> buffer.serialize() if buffer.isRetained())
|
||||
buffers: _.compact(@buffers.map (buffer) -> buffer.serialize({markerLayers: options.isUnloading is true}) if buffer.isRetained())
|
||||
|
||||
###
|
||||
Section: Event Subscription
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
|
||||
module.exports = ({commandRegistry, commandInstaller, config}) ->
|
||||
commandRegistry.add 'atom-workspace',
|
||||
'pane:show-next-recently-used-item': -> @getModel().getActivePane().activateNextRecentlyUsedItem()
|
||||
'pane:show-previous-recently-used-item': -> @getModel().getActivePane().activatePreviousRecentlyUsedItem()
|
||||
'pane:move-active-item-to-top-of-stack': -> @getModel().getActivePane().moveActiveItemToTopOfStack()
|
||||
'pane:show-next-item': -> @getModel().getActivePane().activateNextItem()
|
||||
'pane:show-previous-item': -> @getModel().getActivePane().activatePreviousItem()
|
||||
'pane:show-item-1': -> @getModel().getActivePane().activateItemAtIndex(0)
|
||||
|
||||
@@ -810,11 +810,11 @@ class Selection extends Model
|
||||
@wordwise = false
|
||||
@linewise = false
|
||||
|
||||
autoscroll: ->
|
||||
autoscroll: (options) ->
|
||||
if @marker.hasTail()
|
||||
@editor.scrollToScreenRange(@getScreenRange(), reversed: @isReversed())
|
||||
@editor.scrollToScreenRange(@getScreenRange(), Object.assign({reversed: @isReversed()}, options))
|
||||
else
|
||||
@cursor.autoscroll()
|
||||
@cursor.autoscroll(options)
|
||||
|
||||
clearAutoscroll: ->
|
||||
|
||||
|
||||
@@ -24,10 +24,10 @@ class StateStore {
|
||||
}
|
||||
|
||||
save (key, value) {
|
||||
return this.dbPromise.then(db => {
|
||||
if (!db) return
|
||||
return new Promise((resolve, reject) => {
|
||||
this.dbPromise.then(db => {
|
||||
if (db == null) resolve()
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
var request = db.transaction(['states'], 'readwrite')
|
||||
.objectStore('states')
|
||||
.put({value: value, storedAt: new Date().toString()}, key)
|
||||
@@ -49,7 +49,11 @@ class StateStore {
|
||||
|
||||
request.onsuccess = (event) => {
|
||||
let result = event.target.result
|
||||
resolve(result ? result.value : null)
|
||||
if (result && !result.isJSON) {
|
||||
resolve(result.value)
|
||||
} else {
|
||||
resolve(null)
|
||||
}
|
||||
}
|
||||
|
||||
request.onerror = (event) => reject(event)
|
||||
|
||||
@@ -43,7 +43,7 @@ class TextEditorComponent
|
||||
@assert domNode?, "TextEditorComponent::domNode was set to null."
|
||||
@domNodeValue = domNode
|
||||
|
||||
constructor: ({@editor, @hostElement, @rootElement, @stylesElement, @useShadowDOM, tileSize, @views, @themes, @config, @workspace, @assert, @grammars}) ->
|
||||
constructor: ({@editor, @hostElement, @rootElement, @stylesElement, @useShadowDOM, tileSize, @views, @themes, @config, @workspace, @assert, @grammars, scrollPastEnd}) ->
|
||||
@tileSize = tileSize if tileSize?
|
||||
@disposables = new CompositeDisposable
|
||||
|
||||
@@ -61,6 +61,7 @@ class TextEditorComponent
|
||||
stoppedScrollingDelay: 200
|
||||
config: @config
|
||||
lineTopIndex: lineTopIndex
|
||||
scrollPastEnd: scrollPastEnd
|
||||
|
||||
@presenter.onDidUpdateState(@requestUpdate)
|
||||
|
||||
|
||||
@@ -17,6 +17,8 @@ class TextEditorElement extends HTMLElement
|
||||
focusOnAttach: false
|
||||
hasTiledRendering: true
|
||||
logicalDisplayBuffer: true
|
||||
scrollPastEnd: true
|
||||
autoHeight: true
|
||||
|
||||
createdCallback: ->
|
||||
# Use globals when the following instance variables aren't set.
|
||||
@@ -38,6 +40,9 @@ class TextEditorElement extends HTMLElement
|
||||
@setAttribute('tabindex', -1)
|
||||
|
||||
initializeContent: (attributes) ->
|
||||
unless @autoHeight
|
||||
@style.height = "100%"
|
||||
|
||||
if @config.get('editor.useShadowDOM')
|
||||
@useShadowDOM = true
|
||||
|
||||
@@ -86,7 +91,7 @@ class TextEditorElement extends HTMLElement
|
||||
@subscriptions.add @component.onDidChangeScrollLeft =>
|
||||
@emitter.emit("did-change-scroll-left", arguments...)
|
||||
|
||||
initialize: (model, {@views, @config, @themes, @workspace, @assert, @styles, @grammars}) ->
|
||||
initialize: (model, {@views, @config, @themes, @workspace, @assert, @styles, @grammars}, @autoHeight = true, @scrollPastEnd = true) ->
|
||||
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?
|
||||
@@ -143,6 +148,7 @@ class TextEditorElement extends HTMLElement
|
||||
workspace: @workspace
|
||||
assert: @assert
|
||||
grammars: @grammars
|
||||
scrollPastEnd: @scrollPastEnd
|
||||
)
|
||||
@rootElement.appendChild(@component.getDomNode())
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ class TextEditorPresenter
|
||||
minimumReflowInterval: 200
|
||||
|
||||
constructor: (params) ->
|
||||
{@model, @config, @lineTopIndex} = params
|
||||
{@model, @config, @lineTopIndex, scrollPastEnd} = params
|
||||
{@cursorBlinkPeriod, @cursorBlinkResumeDelay, @stoppedScrollingDelay, @tileSize} = params
|
||||
{@contentFrameWidth} = params
|
||||
|
||||
@@ -42,6 +42,8 @@ class TextEditorPresenter
|
||||
@startReflowing() if @continuousReflow
|
||||
@updating = false
|
||||
|
||||
@scrollPastEndOverride = scrollPastEnd ? true
|
||||
|
||||
setLinesYardstick: (@linesYardstick) ->
|
||||
|
||||
getLinesYardstick: -> @linesYardstick
|
||||
@@ -661,7 +663,7 @@ class TextEditorPresenter
|
||||
return unless @contentHeight? and @clientHeight?
|
||||
|
||||
contentHeight = @contentHeight
|
||||
if @scrollPastEnd
|
||||
if @scrollPastEnd and @scrollPastEndOverride
|
||||
extraScrollHeight = @clientHeight - (@lineHeight * 3)
|
||||
contentHeight += extraScrollHeight if extraScrollHeight > 0
|
||||
scrollHeight = Math.max(contentHeight, @height)
|
||||
|
||||
40
src/text-editor-registry.coffee
Normal file
40
src/text-editor-registry.coffee
Normal file
@@ -0,0 +1,40 @@
|
||||
{Emitter, Disposable} = require 'event-kit'
|
||||
|
||||
# Experimental: This global registry tracks registered `TextEditors`.
|
||||
#
|
||||
# If you want to add functionality to a wider set of text editors than just
|
||||
# those appearing within workspace panes, use `atom.textEditors.observe` to
|
||||
# invoke a callback for all current and future registered text editors.
|
||||
#
|
||||
# If you want packages to be able to add functionality to your non-pane text
|
||||
# editors (such as a search field in a custom user interface element), register
|
||||
# them for observation via `atom.textEditors.add`. **Important:** When you're
|
||||
# done using your editor, be sure to call `dispose` on the returned disposable
|
||||
# to avoid leaking editors.
|
||||
module.exports =
|
||||
class TextEditorRegistry
|
||||
constructor: ->
|
||||
@editors = new Set
|
||||
@emitter = new Emitter
|
||||
|
||||
# Register a `TextEditor`.
|
||||
#
|
||||
# * `editor` The editor to register.
|
||||
#
|
||||
# Returns a {Disposable} on which `.dispose()` can be called to remove the
|
||||
# added editor. To avoid any memory leaks this should be called when the
|
||||
# editor is destroyed.
|
||||
add: (editor) ->
|
||||
@editors.add(editor)
|
||||
@emitter.emit 'did-add-editor', editor
|
||||
new Disposable => @editors.delete(editor)
|
||||
|
||||
# Invoke the given callback with all the current and future registered
|
||||
# `TextEditors`.
|
||||
#
|
||||
# * `callback` {Function} to be called with current and future text editors.
|
||||
#
|
||||
# Returns a {Disposable} on which `.dispose()` can be called to unsubscribe.
|
||||
observe: (callback) ->
|
||||
@editors.forEach(callback)
|
||||
@emitter.on 'did-add-editor', callback
|
||||
@@ -11,6 +11,7 @@ Selection = require './selection'
|
||||
TextMateScopeSelector = require('first-mate').ScopeSelector
|
||||
{Directory} = require "pathwatcher"
|
||||
GutterContainer = require './gutter-container'
|
||||
TextEditorElement = require './text-editor-element'
|
||||
|
||||
# Essential: This class represents all essential editing state for a single
|
||||
# {TextBuffer}, including cursor and selection positions, folds, and soft wraps.
|
||||
@@ -61,6 +62,10 @@ class TextEditor extends Model
|
||||
suppressSelectionMerging: false
|
||||
selectionFlashDuration: 500
|
||||
gutterContainer: null
|
||||
editorElement: null
|
||||
|
||||
Object.defineProperty @prototype, "element",
|
||||
get: -> @getElement()
|
||||
|
||||
@deserialize: (state, atomEnvironment) ->
|
||||
try
|
||||
@@ -82,7 +87,10 @@ class TextEditor extends Model
|
||||
state.project = atomEnvironment.project
|
||||
state.assert = atomEnvironment.assert.bind(atomEnvironment)
|
||||
state.applicationDelegate = atomEnvironment.applicationDelegate
|
||||
new this(state)
|
||||
editor = new this(state)
|
||||
disposable = atomEnvironment.textEditors.add(editor)
|
||||
editor.onDidDestroy -> disposable.dispose()
|
||||
editor
|
||||
|
||||
constructor: (params={}) ->
|
||||
super
|
||||
@@ -92,7 +100,7 @@ class TextEditor extends Model
|
||||
softWrapped, @displayBuffer, @selectionsMarkerLayer, buffer, suppressCursorCreation,
|
||||
@mini, @placeholderText, lineNumberGutterVisible, largeFileMode, @config,
|
||||
@notificationManager, @packageManager, @clipboard, @viewRegistry, @grammarRegistry,
|
||||
@project, @assert, @applicationDelegate, @pending
|
||||
@project, @assert, @applicationDelegate, grammar, showInvisibles, @autoHeight, @scrollPastEnd
|
||||
} = params
|
||||
|
||||
throw new Error("Must pass a config parameter when constructing TextEditors") unless @config?
|
||||
@@ -111,10 +119,15 @@ class TextEditor extends Model
|
||||
@cursors = []
|
||||
@cursorsByMarkerId = new Map
|
||||
@selections = []
|
||||
@autoHeight ?= true
|
||||
@scrollPastEnd ?= true
|
||||
@hasTerminatedPendingState = false
|
||||
|
||||
showInvisibles ?= true
|
||||
|
||||
buffer ?= new TextBuffer
|
||||
@displayBuffer ?= new DisplayBuffer({
|
||||
buffer, tabLength, softWrapped, ignoreInvisibles: @mini, largeFileMode,
|
||||
buffer, tabLength, softWrapped, ignoreInvisibles: @mini or not showInvisibles, largeFileMode,
|
||||
@config, @assert, @grammarRegistry, @packageManager
|
||||
})
|
||||
@buffer = @displayBuffer.buffer
|
||||
@@ -143,6 +156,9 @@ class TextEditor extends Model
|
||||
priority: 0
|
||||
visible: lineNumberGutterVisible
|
||||
|
||||
if grammar?
|
||||
@setGrammar(grammar)
|
||||
|
||||
serialize: ->
|
||||
deserializer: 'TextEditor'
|
||||
id: @id
|
||||
@@ -151,7 +167,6 @@ class TextEditor extends Model
|
||||
firstVisibleScreenColumn: @getFirstVisibleScreenColumn()
|
||||
displayBuffer: @displayBuffer.serialize()
|
||||
selectionsMarkerLayerId: @selectionsMarkerLayer.id
|
||||
pending: @isPending()
|
||||
|
||||
subscribeToBuffer: ->
|
||||
@buffer.retain()
|
||||
@@ -163,12 +178,18 @@ class TextEditor extends Model
|
||||
@disposables.add @buffer.onDidChangeEncoding =>
|
||||
@emitter.emit 'did-change-encoding', @getEncoding()
|
||||
@disposables.add @buffer.onDidDestroy => @destroy()
|
||||
if @pending
|
||||
@disposables.add @buffer.onDidChangeModified =>
|
||||
@terminatePendingState() if @buffer.isModified()
|
||||
@disposables.add @buffer.onDidChangeModified =>
|
||||
@terminatePendingState() if not @hasTerminatedPendingState and @buffer.isModified()
|
||||
|
||||
@preserveCursorPositionOnBufferReload()
|
||||
|
||||
terminatePendingState: ->
|
||||
@emitter.emit 'did-terminate-pending-state' if not @hasTerminatedPendingState
|
||||
@hasTerminatedPendingState = true
|
||||
|
||||
onDidTerminatePendingState: (callback) ->
|
||||
@emitter.on 'did-terminate-pending-state', callback
|
||||
|
||||
subscribeToDisplayBuffer: ->
|
||||
@disposables.add @selectionsMarkerLayer.onDidCreateMarker @addSelection.bind(this)
|
||||
@disposables.add @displayBuffer.onDidChangeGrammar @handleGrammarChange.bind(this)
|
||||
@@ -575,13 +596,6 @@ class TextEditor extends Model
|
||||
getEditorWidthInChars: ->
|
||||
@displayBuffer.getEditorWidthInChars()
|
||||
|
||||
onDidTerminatePendingState: (callback) ->
|
||||
@emitter.on 'did-terminate-pending-state', callback
|
||||
|
||||
terminatePendingState: ->
|
||||
return if not @pending
|
||||
@pending = false
|
||||
@emitter.emit 'did-terminate-pending-state'
|
||||
|
||||
###
|
||||
Section: File Details
|
||||
@@ -666,9 +680,6 @@ class TextEditor extends Model
|
||||
# Essential: Returns {Boolean} `true` if this editor has no content.
|
||||
isEmpty: -> @buffer.isEmpty()
|
||||
|
||||
# Returns {Boolean} `true` if this editor is pending and `false` if it is permanent.
|
||||
isPending: -> Boolean(@pending)
|
||||
|
||||
# Copies the current file path to the native clipboard.
|
||||
copyPathToClipboard: (relative = false) ->
|
||||
if filePath = @getPath()
|
||||
@@ -2470,6 +2481,7 @@ class TextEditor extends Model
|
||||
selections = @getSelections()
|
||||
if selections.length > 1
|
||||
selection.destroy() for selection in selections[1...(selections.length)]
|
||||
selections[0].autoscroll(center: true)
|
||||
true
|
||||
else
|
||||
false
|
||||
@@ -2924,6 +2936,7 @@ class TextEditor extends Model
|
||||
# Extended: Unfold all existing folds.
|
||||
unfoldAll: ->
|
||||
@languageMode.unfoldAll()
|
||||
@scrollToCursorPosition()
|
||||
|
||||
# Extended: Fold all foldable lines at the given indent level.
|
||||
#
|
||||
@@ -3142,6 +3155,10 @@ class TextEditor extends Model
|
||||
Section: TextEditor Rendering
|
||||
###
|
||||
|
||||
# Get the Element for the editor.
|
||||
getElement: ->
|
||||
@editorElement ?= new TextEditorElement().initialize(this, atom, @autoHeight, @scrollPastEnd)
|
||||
|
||||
# Essential: Retrieves the greyed out placeholder of a mini editor.
|
||||
#
|
||||
# Returns a {String}.
|
||||
@@ -3215,8 +3232,8 @@ class TextEditor extends Model
|
||||
# top of the visible area.
|
||||
setFirstVisibleScreenRow: (screenRow, fromView) ->
|
||||
unless fromView
|
||||
maxScreenRow = @getLineCount() - 1
|
||||
unless @config.get('editor.scrollPastEnd')
|
||||
maxScreenRow = @getScreenLineCount() - 1
|
||||
unless @config.get('editor.scrollPastEnd') and @scrollPastEnd
|
||||
height = @displayBuffer.getHeight()
|
||||
lineHeightInPixels = @displayBuffer.getLineHeightInPixels()
|
||||
if height? and lineHeightInPixels?
|
||||
@@ -3233,7 +3250,7 @@ class TextEditor extends Model
|
||||
height = @displayBuffer.getHeight()
|
||||
lineHeightInPixels = @displayBuffer.getLineHeightInPixels()
|
||||
if height? and lineHeightInPixels?
|
||||
Math.min(@firstVisibleScreenRow + Math.floor(height / lineHeightInPixels), @getLineCount() - 1)
|
||||
Math.min(@firstVisibleScreenRow + Math.floor(height / lineHeightInPixels), @getScreenLineCount() - 1)
|
||||
else
|
||||
null
|
||||
|
||||
|
||||
@@ -498,7 +498,6 @@ class TokenizedLine
|
||||
while iterator.next()
|
||||
scopes = iterator.getScopes()
|
||||
continue if scopes.length is 1
|
||||
continue unless NonWhitespaceRegex.test(iterator.getText())
|
||||
for scope in scopes
|
||||
if CommentScopeRegex.test(scope)
|
||||
@isCommentLine = true
|
||||
|
||||
@@ -63,6 +63,8 @@ class TooltipManager
|
||||
# full list of options. You can also supply the following additional options:
|
||||
# * `title` A {String} or {Function} to use for the text in the tip. If
|
||||
# given a function, `this` will be set to the `target` element.
|
||||
# * `trigger` A {String} that's the same as Bootstrap 'click | hover | focus
|
||||
# | manual', except 'manual' will show the tooltip immediately.
|
||||
# * `keyBindingCommand` A {String} containing a command name. If you specify
|
||||
# this option and a key binding exists that matches the command, it will
|
||||
# be appended to the title or rendered alone if no title is specified.
|
||||
|
||||
@@ -64,7 +64,9 @@ Tooltip.prototype.init = function (element, options) {
|
||||
|
||||
if (trigger === 'click') {
|
||||
this.disposables.add(listen(this.element, 'click', this.options.selector, this.toggle.bind(this)))
|
||||
} else if (trigger !== 'manual') {
|
||||
} else if (trigger === 'manual') {
|
||||
this.show()
|
||||
} else {
|
||||
var eventIn, eventOut
|
||||
|
||||
if (trigger === 'hover') {
|
||||
|
||||
@@ -171,6 +171,11 @@ class ViewRegistry
|
||||
if object instanceof HTMLElement
|
||||
return object
|
||||
|
||||
if typeof object?.getElement is 'function'
|
||||
element = object.getElement()
|
||||
if element instanceof HTMLElement
|
||||
return element
|
||||
|
||||
if object?.element instanceof HTMLElement
|
||||
return object.element
|
||||
|
||||
|
||||
@@ -15,7 +15,8 @@ class WindowEventHandler
|
||||
@addEventListener(@window, 'focus', @handleWindowFocus)
|
||||
@addEventListener(@window, 'blur', @handleWindowBlur)
|
||||
|
||||
@addEventListener(@document, 'keydown', @handleDocumentKeydown)
|
||||
@addEventListener(@document, 'keyup', @handleDocumentKeyEvent)
|
||||
@addEventListener(@document, 'keydown', @handleDocumentKeyEvent)
|
||||
@addEventListener(@document, 'drop', @handleDocumentDrop)
|
||||
@addEventListener(@document, 'dragover', @handleDocumentDragover)
|
||||
@addEventListener(@document, 'contextmenu', @handleDocumentContextmenu)
|
||||
@@ -66,7 +67,7 @@ class WindowEventHandler
|
||||
target.addEventListener(eventName, handler)
|
||||
@subscriptions.add(new Disposable(-> target.removeEventListener(eventName, handler)))
|
||||
|
||||
handleDocumentKeydown: (event) =>
|
||||
handleDocumentKeyEvent: (event) =>
|
||||
@atomEnvironment.keymaps.handleKeyboardEvent(event)
|
||||
event.stopImmediatePropagation()
|
||||
|
||||
@@ -142,7 +143,6 @@ class WindowEventHandler
|
||||
@reloadRequested = false
|
||||
|
||||
@atomEnvironment.storeWindowDimensions()
|
||||
@atomEnvironment.saveState()
|
||||
if confirmed
|
||||
@atomEnvironment.unloadEditorWindow()
|
||||
else
|
||||
|
||||
@@ -43,6 +43,12 @@ class Workspace extends Model
|
||||
@defaultDirectorySearcher = new DefaultDirectorySearcher()
|
||||
@consumeServices(@packageManager)
|
||||
|
||||
# One cannot simply .bind here since it could be used as a component with
|
||||
# Etch, in which case it'd be `new`d. And when it's `new`d, `this` is always
|
||||
# the newly created object.
|
||||
realThis = this
|
||||
@buildTextEditor = -> Workspace.prototype.buildTextEditor.apply(realThis, arguments)
|
||||
|
||||
@panelContainers =
|
||||
top: new PanelContainer({location: 'top'})
|
||||
left: new PanelContainer({location: 'left'})
|
||||
@@ -403,6 +409,9 @@ class Workspace extends Model
|
||||
# containing pane. Defaults to `true`.
|
||||
# * `activateItem` A {Boolean} indicating whether to call {Pane::activateItem}
|
||||
# on containing pane. Defaults to `true`.
|
||||
# * `pending` A {Boolean} indicating whether or not the item should be opened
|
||||
# in a pending state. Existing pending items in a pane are replaced with
|
||||
# new pending items when they are opened.
|
||||
# * `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
|
||||
@@ -477,7 +486,7 @@ class Workspace extends Model
|
||||
|
||||
if uri?
|
||||
if item = pane.itemForURI(uri)
|
||||
item.terminatePendingState?() if item.isPending?() and not options.pending
|
||||
pane.clearPendingItem() if not options.pending and pane.getPendingItem() is item
|
||||
item ?= opener(uri, options) for opener in @getOpeners() when not item
|
||||
|
||||
try
|
||||
@@ -500,7 +509,7 @@ class Workspace extends Model
|
||||
return item if pane.isDestroyed()
|
||||
|
||||
@itemOpened(item)
|
||||
pane.activateItem(item) if activateItem
|
||||
pane.activateItem(item, {pending: options.pending}) if activateItem
|
||||
pane.activate() if activatePane
|
||||
|
||||
initialLine = initialColumn = 0
|
||||
@@ -555,7 +564,10 @@ class Workspace extends Model
|
||||
@config, @notificationManager, @packageManager, @clipboard, @viewRegistry,
|
||||
@grammarRegistry, @project, @assert, @applicationDelegate
|
||||
}, params)
|
||||
new TextEditor(params)
|
||||
editor = new TextEditor(params)
|
||||
disposable = atom.textEditors.add(editor)
|
||||
editor.onDidDestroy -> disposable.dispose()
|
||||
editor
|
||||
|
||||
# Public: Asynchronously reopens the last-closed item's URI if it hasn't already been
|
||||
# reopened.
|
||||
|
||||
Reference in New Issue
Block a user