mirror of
https://github.com/atom/atom.git
synced 2026-01-24 06:18:03 -05:00
283 lines
9.4 KiB
CoffeeScript
283 lines
9.4 KiB
CoffeeScript
AtomWindow = require './atom-window'
|
|
BrowserWindow = require 'browser-window'
|
|
Menu = require 'menu'
|
|
autoUpdater = require 'auto-updater'
|
|
app = require 'app'
|
|
ipc = require 'ipc'
|
|
dialog = require 'dialog'
|
|
fs = require 'fs'
|
|
path = require 'path'
|
|
net = require 'net'
|
|
url = require 'url'
|
|
|
|
socketPath = '/tmp/atom.sock'
|
|
|
|
module.exports =
|
|
class AtomApplication
|
|
@open: (options) ->
|
|
createAtomApplication = -> new AtomApplication(options)
|
|
|
|
# FIXME: Sometimes when socketPath doesn't exist, net.connect would strangely
|
|
# take a few seconds to trigger 'error' event, it could be a bug of node
|
|
# or atom-shell, before it's fixed we check the existence of socketPath to
|
|
# speedup startup.
|
|
if not fs.existsSync socketPath
|
|
createAtomApplication()
|
|
return
|
|
|
|
client = net.connect {path: socketPath}, ->
|
|
client.write JSON.stringify(options), ->
|
|
client.end()
|
|
app.terminate()
|
|
|
|
client.on 'error', createAtomApplication
|
|
|
|
windows: null
|
|
configWindow: null
|
|
menu: null
|
|
resourcePath: null
|
|
installUpdate: null
|
|
version: null
|
|
|
|
constructor: ({@resourcePath, pathsToOpen, urlsToOpen, @version, test, pidToKillWhenClosed, @dev, newWindow}) ->
|
|
global.atomApplication = this
|
|
|
|
@pidsToOpenWindows = {}
|
|
@pathsToOpen ?= []
|
|
@windows = []
|
|
|
|
@listenForArgumentsFromNewProcess()
|
|
@setupJavaScriptArguments()
|
|
@buildApplicationMenu()
|
|
@handleEvents()
|
|
|
|
@checkForUpdates()
|
|
|
|
if test
|
|
@runSpecs({exitWhenDone: true, @resourcePath})
|
|
else if pathsToOpen.length > 0
|
|
@openPaths({pathsToOpen, pidToKillWhenClosed, newWindow})
|
|
else if urlsToOpen.length > 0
|
|
@openUrl(urlToOpen) for urlToOpen in urlsToOpen
|
|
else
|
|
# Always open a editor window if this is the first instance of Atom.
|
|
@openPath({pidToKillWhenClosed, newWindow})
|
|
|
|
removeWindow: (window) ->
|
|
@windows.splice @windows.indexOf(window), 1
|
|
|
|
addWindow: (window) ->
|
|
@windows.push window
|
|
|
|
listenForArgumentsFromNewProcess: ->
|
|
fs.unlinkSync socketPath if fs.existsSync(socketPath)
|
|
server = net.createServer (connection) =>
|
|
connection.on 'data', (data) =>
|
|
{pathsToOpen, pidToKillWhenClosed, newWindow} = JSON.parse(data)
|
|
@openPaths({pathsToOpen, pidToKillWhenClosed, newWindow})
|
|
|
|
server.listen socketPath
|
|
server.on 'error', (error) -> console.error 'Application server failed', error
|
|
|
|
setupJavaScriptArguments: ->
|
|
app.commandLine.appendSwitch 'js-flags', '--harmony_collections'
|
|
|
|
checkForUpdates: ->
|
|
return if /\w{7}/.test @version # Don't check for updates if version is a short sha
|
|
|
|
autoUpdater.setAutomaticallyChecksForUpdates true
|
|
autoUpdater.checkForUpdatesInBackground()
|
|
|
|
buildApplicationMenu: (version, continueUpdate) ->
|
|
menus = []
|
|
menus.push
|
|
label: 'Atom'
|
|
submenu: [
|
|
{ label: 'About Atom', selector: 'orderFrontStandardAboutPanel:' }
|
|
{ type: 'separator' }
|
|
{ label: 'Preferences...', accelerator: 'Command+,', click: => @openConfig() }
|
|
{ type: 'separator' }
|
|
{ label: 'Hide Atom', accelerator: 'Command+H', selector: 'hide:' }
|
|
{ label: 'Hide Others', accelerator: 'Command+Shift+H', selector: 'hideOtherApplications:' }
|
|
{ label: 'Show All', selector: 'unhideAllApplications:' }
|
|
{ type: 'separator' }
|
|
{
|
|
label: 'Run Specs'
|
|
accelerator: 'Command+MacCtrl+Alt+S'
|
|
click: =>
|
|
@runSpecs(exitWhenDone: false, resourcePath: global.devResourcePath)
|
|
}
|
|
{ type: 'separator' }
|
|
{ label: 'Quit', accelerator: 'Command+Q', click: -> app.quit() }
|
|
]
|
|
|
|
menus[0].submenu[1..0] =
|
|
if version
|
|
label: "Update to #{version}"
|
|
click: continueUpdate
|
|
else
|
|
label: "Version #{@version}"
|
|
enabled: false
|
|
|
|
if @dev
|
|
menus.push
|
|
label: '\uD83D\uDC80' # Skull emoji
|
|
submenu: [ { label: 'In Development Mode', enabled: false } ]
|
|
|
|
menus.push
|
|
label: 'File'
|
|
submenu: [
|
|
{ label: 'New Window', accelerator: 'Command+N', click: => @openPath() }
|
|
{ label: 'Open...', accelerator: 'Command+O', click: => @promptForPath() }
|
|
{ label: 'Open In Dev Mode...', accelerator: 'Command+Shift+O', click: => @promptForPath(devMode: true) }
|
|
]
|
|
|
|
menus.push
|
|
label: 'Edit'
|
|
submenu:[
|
|
{ label: 'Undo', accelerator: 'Command+Z', selector: 'undo:' }
|
|
{ label: 'Redo', accelerator: 'Command+Shift+Z', selector: 'redo:' }
|
|
{ type: 'separator' }
|
|
{ label: 'Cut', accelerator: 'Command+X', selector: 'cut:' }
|
|
{ label: 'Copy', accelerator: 'Command+C', selector: 'copy:' }
|
|
{ label: 'Paste', accelerator: 'Command+V', selector: 'paste:' }
|
|
{ label: 'Select All', accelerator: 'Command+A', selector: 'selectAll:' }
|
|
]
|
|
|
|
menus.push
|
|
label: 'View'
|
|
submenu:[
|
|
{ label: 'Reload', accelerator: 'Command+R', click: => BrowserWindow.getFocusedWindow()?.restart() }
|
|
{ label: 'Toggle Full Screen', accelerator: 'Command+MacCtrl+F', click: => BrowserWindow.getFocusedWindow()?.setFullScreen(!BrowserWindow.getFocusedWindow().isFullScreen()) }
|
|
{ label: 'Toggle Developer Tools', accelerator: 'Alt+Command+I', click: => BrowserWindow.getFocusedWindow()?.toggleDevTools() }
|
|
]
|
|
|
|
menus.push
|
|
label: 'Window'
|
|
submenu: [
|
|
{ label: 'Minimize', accelerator: 'Command+M', selector: 'performMiniaturize:' }
|
|
{ label: 'Zoom', accelerator: 'Alt+Command+MacCtrl+M', selector: 'zoom:' }
|
|
{ label: 'Close', accelerator: 'Command+W', selector: 'performClose:' }
|
|
{ type: 'separator' }
|
|
{ label: 'Bring All to Front', selector: 'arrangeInFront:' }
|
|
]
|
|
|
|
@menu = Menu.buildFromTemplate menus
|
|
Menu.setApplicationMenu @menu
|
|
|
|
handleEvents: ->
|
|
# Clean the socket file when quit normally.
|
|
app.on 'will-quit', =>
|
|
fs.unlinkSync socketPath if fs.existsSync(socketPath)
|
|
|
|
app.on 'open-file', (event, pathToOpen) =>
|
|
event.preventDefault()
|
|
@openPath({pathToOpen})
|
|
|
|
app.on 'open-url', (event, urlToOpen) =>
|
|
event.preventDefault()
|
|
@openUrl(urlToOpen)
|
|
|
|
autoUpdater.on 'ready-for-update-on-quit', (event, version, quitAndUpdate) =>
|
|
event.preventDefault()
|
|
@installUpdate = quitAndUpdate
|
|
@buildApplicationMenu version, quitAndUpdate
|
|
|
|
ipc.on 'open-config', =>
|
|
@openConfig()
|
|
|
|
ipc.on 'open', (processId, routingId, pathsToOpen) =>
|
|
if pathsToOpen?.length > 0
|
|
@openPaths({pathsToOpen})
|
|
else
|
|
@promptForPath()
|
|
|
|
ipc.on 'open-window', (processId, routingId, windowSettings) ->
|
|
new AtomWindow(windowSettings)
|
|
|
|
ipc.on 'open-dev', (processId, routingId, pathsToOpen) =>
|
|
if pathsToOpen?.length > 0
|
|
@openPaths({pathsToOpen, devMode: true})
|
|
else
|
|
@promptForPath(devMode: true)
|
|
|
|
ipc.on 'new-window', =>
|
|
@openPath()
|
|
|
|
ipc.on 'install-update', =>
|
|
@installUpdate?()
|
|
|
|
ipc.on 'get-version', (event) =>
|
|
event.result = @version
|
|
|
|
sendCommand: (command, args...) ->
|
|
for atomWindow in @windows when atomWindow.isFocused()
|
|
atomWindow.sendCommand(command, args...)
|
|
|
|
windowForPath: (pathToOpen) ->
|
|
for atomWindow in @windows
|
|
return atomWindow if atomWindow.containsPath(pathToOpen)
|
|
|
|
openPaths: ({pathsToOpen, pidToKillWhenClosed, newWindow, devMode}) ->
|
|
@openPath({pathToOpen, pidToKillWhenClosed, newWindow, devMode}) for pathToOpen in pathsToOpen ? []
|
|
|
|
openPath: ({pathToOpen, pidToKillWhenClosed, newWindow, devMode}={}) ->
|
|
unless devMode
|
|
existingWindow = @windowForPath(pathToOpen) unless pidToKillWhenClosed or newWindow
|
|
if existingWindow
|
|
openedWindow = existingWindow
|
|
openedWindow.openPath(pathToOpen)
|
|
else
|
|
bootstrapScript = 'window-bootstrap'
|
|
if devMode
|
|
resourcePath = global.devResourcePath
|
|
else
|
|
resourcePath = @resourcePath
|
|
openedWindow = new AtomWindow({pathToOpen, bootstrapScript, resourcePath})
|
|
|
|
if pidToKillWhenClosed?
|
|
@pidsToOpenWindows[pidToKillWhenClosed] = openedWindow
|
|
|
|
openedWindow.browserWindow.on 'destroyed', =>
|
|
for pid, trackedWindow of @pidsToOpenWindows when trackedWindow is openedWindow
|
|
try
|
|
process.kill(pid)
|
|
catch error
|
|
if error.code isnt 'ESRCH'
|
|
console.log("Killing process #{pid} failed: #{error.code}")
|
|
delete @pidsToOpenWindows[pid]
|
|
|
|
openUrl: (urlToOpen) ->
|
|
parsedUrl = url.parse(urlToOpen)
|
|
if parsedUrl.host is 'session'
|
|
sessionId = parsedUrl.path.split('/')[1]
|
|
console.log "Joining session #{sessionId}"
|
|
if sessionId
|
|
bootstrapScript = 'collaboration/lib/bootstrap'
|
|
new AtomWindow({bootstrapScript, @resourcePath, sessionId})
|
|
else
|
|
console.log "Opening unknown url #{urlToOpen}"
|
|
|
|
openConfig: ->
|
|
if @configWindow
|
|
@configWindow.focus()
|
|
return
|
|
|
|
@configWindow = new AtomWindow
|
|
bootstrapScript: 'config-bootstrap'
|
|
resourcePath: @resourcePath
|
|
@configWindow.browserWindow.on 'destroyed', =>
|
|
@configWindow = null
|
|
|
|
runSpecs: ({exitWhenDone, resourcePath}) ->
|
|
if resourcePath isnt @resourcePath and not fs.existsSync(resourcePath)
|
|
resourcePath = @resourcePath
|
|
|
|
bootstrapScript = 'spec-bootstrap'
|
|
isSpec = true
|
|
new AtomWindow({bootstrapScript, resourcePath, exitWhenDone, isSpec})
|
|
|
|
promptForPath: ({devMode}={}) ->
|
|
pathsToOpen = dialog.showOpenDialog title: 'Open', properties: ['openFile', 'openDirectory', 'multiSelections', 'createDirectory']
|
|
@openPaths({pathsToOpen, devMode})
|