crypto = require 'crypto' path = require 'path' ipc = require 'ipc' _ = require 'underscore-plus' {deprecate} = require 'grim' {CompositeDisposable, Emitter} = require 'event-kit' fs = require 'fs-plus' {mapSourcePosition} = require 'source-map-support' 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 AtomEnvironment extends Model @version: 1 # Increment this when the serialization format changes lastUncaughtError: null ### Section: Properties ### # Public: A {CommandRegistry} instance commands: null # Public: A {Config} instance config: null # Public: A {Clipboard} instance clipboard: null # Public: A {ContextMenuManager} instance contextMenu: null # Public: A {MenuManager} instance menu: null # Public: A {KeymapManager} instance keymaps: null # Public: A {TooltipManager} instance tooltips: null # Public: A {NotificationManager} instance notifications: null # Public: A {Project} instance project: null # Public: A {GrammarRegistry} instance grammars: null # Public: A {PackageManager} instance packages: null # Public: A {ThemeManager} instance themes: null # Public: A {StyleManager} instance styles: null # Public: A {DeserializerManager} instance deserializers: null # Public: A {ViewRegistry} instance views: null # Public: A {Workspace} instance workspace: null ### Section: Construction and Destruction ### # Call .loadOrCreate instead constructor: (params={}) -> {@blobStore, @applicationDelegate, @window, @document, configDirPath, @enablePersistence} = params @state = {version: @constructor.version} @loadTime = null {devMode, safeMode, resourcePath} = @getLoadSettings() @emitter = new Emitter @disposables = new CompositeDisposable @deserializers = new DeserializerManager(this) @deserializeTimings = {} @views = new ViewRegistry(this) @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 @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() @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() checkPortableHomeWritable = -> responseChannel = "check-portable-home-writable-response" ipc.on responseChannel, (response) -> ipc.removeAllListeners(responseChannel) atom.notifications.addWarning("#{response.message.replace(/([\\\.+\\-_#!])/g, '\\$1')}") if not response.writable ipc.send('check-portable-home-writable', responseChannel) checkPortableHomeWritable() 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) registerDefaultCommands: -> registerDefaultCommands({commandRegistry: @commands, @config, @commandInstaller}) 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) 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() @config.unobserveUserConfig() @uninstallWindowEventHandler() ### Section: Event Subscription ### # Extended: Invoke the given callback whenever {::beep} is called. # # * `callback` {Function} to be called whenever {::beep} is called. # # Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. onDidBeep: (callback) -> @emitter.on 'did-beep', callback # Extended: Invoke the given callback when there is an unhandled error, but # before the devtools pop open # # * `callback` {Function} to be called whenever there is an unhandled error # * `event` {Object} # * `originalError` {Object} the original error object # * `message` {String} the original error object # * `url` {String} Url to the file where the error originated. # * `line` {Number} # * `column` {Number} # * `preventDefault` {Function} call this to avoid popping up the dev tools. # # Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. onWillThrowError: (callback) -> @emitter.on 'will-throw-error', callback # Extended: Invoke the given callback whenever there is an unhandled error. # # * `callback` {Function} to be called whenever there is an unhandled error # * `event` {Object} # * `originalError` {Object} the original error object # * `message` {String} the original error object # * `url` {String} Url to the file where the error originated. # * `line` {Number} # * `column` {Number} # # Returns a {Disposable} on which `.dispose()` can be called to unsubscribe. onDidThrowError: (callback) -> @emitter.on 'did-throw-error', callback # TODO: Make this part of the public API. We should make onDidThrowError # match the interface by only yielding an exception object to the handler # and deprecating the old behavior. onDidFailAssertion: (callback) -> @emitter.on 'did-fail-assertion', callback ### Section: Atom Details ### # Public: Returns a {Boolean} that is `true` if the current window is in development mode. inDevMode: -> @devMode ?= @getLoadSettings().devMode # Public: Returns a {Boolean} that is `true` if the current window is in safe mode. inSafeMode: -> @safeMode ?= @getLoadSettings().safeMode # Public: Returns a {Boolean} that is `true` if the current window is running specs. inSpecMode: -> @specMode ?= @getLoadSettings().isSpec # Public: Get the version of the Atom application. # # Returns the version text {String}. getVersion: -> @appVersion ?= @getLoadSettings().appVersion # 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 # Public: Get the time taken to completely load the current window. # # This time include things like loading and activating packages, creating # DOM elements for the editor, and reading the config. # # Returns the {Number} of milliseconds taken to load the window or null # if the window hasn't finished loading yet. getWindowLoadTime: -> @loadTime # Public: Get the load settings for the current window. # # Returns an {Object} containing all the load setting key/value pairs. getLoadSettings: -> getWindowLoadSettings() ### Section: Managing The Atom Window ### # Essential: Open a new Atom window using the given options. # # Calling this method without an options parameter will open a prompt to pick # a file/folder to open in the new window. # # * `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. # * `devMode` A {Boolean}, true to open the window in development mode. # Development mode loads the Atom source from the locally cloned # repository and also loads all the packages in ~/.atom/dev/packages # * `safeMode` A {Boolean}, true to open the window in safe mode. Safe # mode prevents all packages installed to ~/.atom/packages from loading. open: (params) -> @applicationDelegate.open(params) # Extended: Prompt the user to select one or more folders. # # * `callback` A {Function} to call once the user has confirmed the selection. # * `paths` An {Array} of {String} paths that the user selected, or `null` # if the user dismissed the dialog. pickFolder: (callback) -> @applicationDelegate.pickFolder(callback) # Essential: Close the current window. close: -> @applicationDelegate.closeWindow() # Essential: Get the size of current window. # # Returns an {Object} in the format `{width: 1000, height: 700}` getSize: -> @applicationDelegate.getWindowSize() # Essential: Set the size of current window. # # * `width` The {Number} of pixels. # * `height` The {Number} of pixels. 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: -> @applicationDelegate.getWindowPosition() # Essential: Set the position of current window. # # * `x` The {Number} of pixels. # * `y` The {Number} of pixels. setPosition: (x, y) -> @applicationDelegate.setWindowPosition(x, y) # Extended: Get the current window getCurrentWindow: -> @applicationDelegate.getCurrentWindow() # Extended: Move current window to the center of the screen. center: -> @applicationDelegate.centerWindow() # Extended: Focus the current window. focus: -> @applicationDelegate.focusWindow() @window.focus() # Extended: Show the current window. show: -> @applicationDelegate.showWindow() # Extended: Hide the current window. hide: -> @applicationDelegate.hideWindow() # Extended: Reload the current window. reload: -> @applicationDelegate.restartWindow() # Extended: Returns a {Boolean} that is `true` if the current window is maximized. isMaximized: -> @applicationDelegate.isWindowMaximized() maximize: -> @applicationDelegate.maximizeWindow() # Extended: Returns a {Boolean} that is `true` if the current window is in full screen mode. isFullScreen: -> @applicationDelegate.isWindowFullScreen() # Extended: Set the full screen state of the current window. setFullScreen: (fullScreen=false) -> @applicationDelegate.setWindowFullScreen(fullScreen) if fullScreen @document.body.classList.add("fullscreen") else @document.body.classList.remove("fullscreen") # Extended: Toggle the full screen state of the current window. toggleFullScreen: -> @setFullScreen(not @isFullScreen()) # Restore the window to its previous dimensions and show it. # # Also restores the full screen and maximized state on the next tick to # prevent resize glitches. displayWindow: -> dimensions = @restoreWindowDimensions() @show() @focus() setImmediate => @setFullScreen(true) if @workspace?.fullScreen @maximize() if dimensions?.maximized and process.platform isnt 'darwin' # Get the dimensions of this window. # # Returns an {Object} with the following keys: # * `x` The window's x-position {Number}. # * `y` The window's y-position {Number}. # * `width` The window's width {Number}. # * `height` The window's height {Number}. getWindowDimensions: -> browserWindow = @getCurrentWindow() [x, y] = browserWindow.getPosition() [width, height] = browserWindow.getSize() maximized = browserWindow.isMaximized() {x, y, width, height, maximized} # Set the dimensions of the window. # # The window will be centered if either the x or y coordinate is not set # in the dimensions parameter. If x or y are omitted the window will be # centered. If height or width are omitted only the position will be changed. # # * `dimensions` An {Object} with the following keys: # * `x` The new x coordinate. # * `y` The new y coordinate. # * `width` The new width. # * `height` The new height. setWindowDimensions: ({x, y, width, height}) -> if width? and height? @setSize(width, height) if x? and y? @setPosition(x, y) else @center() # Returns true if the dimensions are useable, false if they should be ignored. # Work around for https://github.com/atom/atom-shell/issues/473 isValidDimensions: ({x, y, width, height}={}) -> width > 0 and height > 0 and x + width > 0 and y + height > 0 storeDefaultWindowDimensions: -> dimensions = @getWindowDimensions() if @isValidDimensions(dimensions) localStorage.setItem("defaultWindowDimensions", JSON.stringify(dimensions)) getDefaultWindowDimensions: -> {windowDimensions} = @getLoadSettings() return windowDimensions if windowDimensions? dimensions = null try dimensions = JSON.parse(localStorage.getItem("defaultWindowDimensions")) catch error console.warn "Error parsing default window dimensions", error localStorage.removeItem("defaultWindowDimensions") if @isValidDimensions(dimensions) dimensions else {width, height} = @applicationDelegate.getPrimaryDisplayWorkAreaSize() {x: 0, y: 0, width: Math.min(1024, width), height} restoreWindowDimensions: -> dimensions = @state.windowDimensions unless @isValidDimensions(dimensions) dimensions = @getDefaultWindowDimensions() @setWindowDimensions(dimensions) dimensions storeWindowDimensions: -> dimensions = @getWindowDimensions() @state.windowDimensions = dimensions if @isValidDimensions(dimensions) storeWindowBackground: -> return if @inSpecMode() workspaceElement = @views.getView(@workspace) backgroundColor = @window.getComputedStyle(workspaceElement)['background-color'] @window.localStorage.setItem('atom:window-background-color', backgroundColor) # Call this method when establishing a real application window. startEditorWindow: -> @commandInstaller.installAtomCommand false, (error) -> console.warn error.message if error? @commandInstaller.installApmCommand false, (error) -> console.warn error.message if error? @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() @document.body.appendChild(@views.getView(@workspace)) @watchProjectPath() @packages.activate() @keymaps.loadUserKeymap() @requireUserInitScript() unless @getLoadSettings().safeMode @menu.update() @openInitialEmptyEditorIfNecessary() unloadEditorWindow: -> return if not @project @storeWindowBackground() @state.grammars = {grammarOverridesByPath: @grammars.grammarOverridesByPath} @state.project = @project.serialize() @state.workspace = @workspace.serialize() @packages.deactivatePackages() @state.packageStates = @packages.packageStates @state.fullScreen = @isFullScreen() @saveStateSync() @saveBlobStoreSync() 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: -> @applicationDelegate.playBeepSound() if @config.get('core.audioBeep') @emitter.emit 'did-beep' # Essential: A flexible way to open a dialog akin to an alert dialog. # # ## Examples # # ```coffee # atom.confirm # message: 'How you feeling?' # detailedMessage: 'Be honest.' # buttons: # Good: -> window.alert('good to hear') # Bad: -> window.alert('bummer') # ``` # # * `options` An {Object} with the following keys: # * `message` The {String} message to display. # * `detailedMessage` (optional) The {String} detailed message to display. # * `buttons` (optional) Either an array of strings or an object where keys are # button names and the values are callbacks to invoke when clicked. # # Returns the chosen button index {Number} if the buttons option was an array. confirm: (params={}) -> @applicationDelegate.confirm(params) ### Section: Managing the Dev Tools ### # Extended: Open the dev tools for the current window. openDevTools: -> @applicationDelegate.openWindowDevTools() # Extended: Toggle the visibility of the dev tools for the current window. toggleDevTools: -> @applicationDelegate.toggleWindowDevTools() # Extended: Execute code in dev tools. executeJavaScriptInDevTools: (code) -> @applicationDelegate.executeJavaScriptInWindowDevTools(code) ### Section: Private ### assert: (condition, message, callback) -> return true if condition error = new Error("Assertion failed: #{message}") Error.captureStackTrace(error, @assert) callback?(error) @emitter.emit 'did-fail-assertion', error false loadThemes: -> @themes.load() # Notify the browser project of the window's current project path watchProjectPath: -> @disposables.add @project.onDidChangePaths => @applicationDelegate.setRepresentedDirectoryPaths(@project.getPaths()) setDocumentEdited: (edited) -> @applicationDelegate.setWindowDocumentEdited?(edited) setRepresentedFilename: (filename) -> @applicationDelegate.setWindowRepresentedFilename?(filename) addProjectFolder: -> @pickFolder (selectedPaths = []) => @project.addPath(selectedPath) for selectedPath in selectedPaths showSaveDialog: (callback) -> callback(showSaveDialogSync()) showSaveDialogSync: (options={}) -> @applicationDelegate.showSaveDialog(options) saveBlobStoreSync: -> return unless @enablePersistence @blobStore.save() saveStateSync: -> return unless @enablePersistence if storageKey = @getStateKey(@project?.getPaths()) @getStorageFolder().store(storageKey, @state) else @getCurrentWindow().loadSettings.windowState = JSON.stringify(@state) loadStateSync: -> return unless @enablePersistence 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']) initScriptPath ? path.join(@getConfigDirPath(), 'init.coffee') requireUserInitScript: -> if userInitScriptPath = @getUserInitScriptPath() try require(userInitScriptPath) if fs.isFileSync(userInitScriptPath) catch error @notifications.addError "Failed to load `#{userInitScriptPath}`", detail: error.message dismissable: true 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}") setAutoHideMenuBar: (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) -> deprecate("Atom now uses ES6 Promises instead of Q. Call promise.then instead of promise.done") @then(callback)