diff --git a/apm/package.json b/apm/package.json index 423e31a12..1dce7e90b 100644 --- a/apm/package.json +++ b/apm/package.json @@ -6,6 +6,6 @@ "url": "https://github.com/atom/atom.git" }, "dependencies": { - "atom-package-manager": "0.166.0" + "atom-package-manager": "0.167.0" } } diff --git a/package.json b/package.json index 026c5dfbb..cf4634cf6 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ "fstream": "0.1.24", "fuzzaldrin": "^2.1", "git-utils": "^3.0.0", - "grim": "1.4.0", + "grim": "1.4.1", "jasmine-json": "~0.0", "jasmine-tagged": "^1.1.4", "jquery": "^2.1.1", @@ -84,19 +84,24 @@ "solarized-dark-syntax": "0.35.0", "solarized-light-syntax": "0.21.0", "archive-view": "0.57.0", - "autocomplete": "0.47.0", + "autocomplete-atom-api": "0.9.0", + "autocomplete-css": "0.7.2", + "autocomplete-emojis": "2.2.2", + "autocomplete-html": "0.7.2", + "autocomplete-plus": "2.15.2", + "autocomplete-snippets": "1.6.1", "autoflow": "0.23.0", "autosave": "0.20.0", "background-tips": "0.24.0", "bookmarks": "0.35.0", "bracket-matcher": "0.74.0", "command-palette": "0.35.0", - "deprecation-cop": "0.46.0", + "deprecation-cop": "0.47.0", "dev-live-reload": "0.46.0", "encoding-selector": "0.20.0", "exception-reporting": "0.24.0", - "find-and-replace": "0.161.0", - "fuzzy-finder": "0.83.0", + "find-and-replace": "0.162.0", + "fuzzy-finder": "0.84.0", "git-diff": "0.55.0", "go-to-line": "0.30.0", "grammar-selector": "0.47.0", @@ -105,15 +110,15 @@ "keybinding-resolver": "0.32.0", "link": "0.30.0", "markdown-preview": "0.148.0", - "metrics": "0.45.0", - "notifications": "0.43.0", + "metrics": "0.47.0", + "notifications": "0.44.0", "open-on-github": "0.36.0", "package-generator": "0.39.0", "release-notes": "0.52.0", "settings-view": "0.199.0", "snippets": "0.89.0", "spell-check": "0.58.0", - "status-bar": "0.71.0", + "status-bar": "0.72.0", "styleguide": "0.44.0", "symbols-view": "0.96.0", "tabs": "0.68.0", @@ -128,7 +133,7 @@ "language-coffee-script": "0.40.0", "language-csharp": "0.5.0", "language-css": "0.29.0", - "language-gfm": "0.73.0", + "language-gfm": "0.74.0", "language-git": "0.10.0", "language-go": "0.26.0", "language-html": "0.37.0", diff --git a/spec/package-manager-spec.coffee b/spec/package-manager-spec.coffee index 92dc21a13..1244ab984 100644 --- a/spec/package-manager-spec.coffee +++ b/spec/package-manager-spec.coffee @@ -824,3 +824,37 @@ describe "PackageManager", -> expect(atom.config.get('core.themes')).not.toContain packageName expect(atom.config.get('core.themes')).not.toContain packageName expect(atom.config.get('core.disabledPackages')).not.toContain packageName + + describe "deleting non-bundled autocomplete packages", -> + [autocompleteCSSPath, autocompletePlusPath] = [] + fs = require 'fs-plus' + path = require 'path' + + beforeEach -> + fixturePath = path.resolve(__dirname, './fixtures/packages') + autocompleteCSSPath = path.join(fixturePath, 'autocomplete-css') + autocompletePlusPath = path.join(fixturePath, 'autocomplete-plus') + + try + fs.mkdirSync(autocompleteCSSPath) + fs.writeFileSync(path.join(autocompleteCSSPath, 'package.json'), '{}') + fs.symlinkSync(path.join(fixturePath, 'package-with-main'), autocompletePlusPath, 'dir') + + expect(fs.isDirectorySync(autocompleteCSSPath)).toBe true + expect(fs.isSymbolicLinkSync(autocompletePlusPath)).toBe true + + jasmine.unspy(atom.packages, 'uninstallAutocompletePlus') + + afterEach -> + try + fs.unlink autocompletePlusPath, -> + + it "removes the packages", -> + atom.packages.loadPackages() + + waitsFor -> + not fs.isDirectorySync(autocompleteCSSPath) + + runs -> + expect(fs.isDirectorySync(autocompleteCSSPath)).toBe false + expect(fs.isSymbolicLinkSync(autocompletePlusPath)).toBe true diff --git a/spec/spec-helper.coffee b/spec/spec-helper.coffee index e21876e8d..15874ff6f 100644 --- a/spec/spec-helper.coffee +++ b/spec/spec-helper.coffee @@ -140,6 +140,8 @@ beforeEach -> spyOn(clipboard, 'writeText').andCallFake (text) -> clipboardContent = text spyOn(clipboard, 'readText').andCallFake -> clipboardContent + spyOn(atom.packages, 'uninstallAutocompletePlus') + addCustomMatchers(this) afterEach -> diff --git a/spec/text-editor-spec.coffee b/spec/text-editor-spec.coffee index dd6b69f26..d1d311088 100644 --- a/spec/text-editor-spec.coffee +++ b/spec/text-editor-spec.coffee @@ -36,23 +36,21 @@ describe "TextEditor", -> it "preserves the invisibles setting", -> atom.config.set('editor.showInvisibles', true) - previousInvisibles = editor.displayBuffer.invisibles + previousInvisibles = editor.tokenizedLineForScreenRow(0).invisibles editor2 = editor.testSerialization() - expect(editor2.displayBuffer.invisibles).toEqual previousInvisibles - expect(editor2.displayBuffer.tokenizedBuffer.invisibles).toEqual previousInvisibles + expect(previousInvisibles).toBeDefined() + expect(editor2.displayBuffer.tokenizedLineForScreenRow(0).invisibles).toEqual previousInvisibles it "updates invisibles if the settings have changed between serialization and deserialization", -> atom.config.set('editor.showInvisibles', true) - previousInvisibles = editor.displayBuffer.invisibles state = editor.serialize() atom.config.set('editor.invisibles', eol: '?') editor2 = TextEditor.deserialize(state) - expect(editor2.displayBuffer.invisibles.eol).toBe '?' - expect(editor2.displayBuffer.tokenizedBuffer.invisibles.eol).toBe '?' + expect(editor.tokenizedLineForScreenRow(0).invisibles.eol).toBe '?' describe "when the editor is constructed with an initialLine option", -> it "positions the cursor on the specified line", -> diff --git a/spec/tokenized-buffer-spec.coffee b/spec/tokenized-buffer-spec.coffee index 3cd776c2b..9d92335af 100644 --- a/spec/tokenized-buffer-spec.coffee +++ b/spec/tokenized-buffer-spec.coffee @@ -611,7 +611,8 @@ describe "TokenizedBuffer", -> tokenizedBuffer = new TokenizedBuffer({buffer}) fullyTokenize(tokenizedBuffer) - tokenizedBuffer.setInvisibles(space: 'S', tab: 'T') + atom.config.set("editor.showInvisibles", true) + atom.config.set("editor.invisibles", space: 'S', tab: 'T') fullyTokenize(tokenizedBuffer) expect(tokenizedBuffer.tokenizedLineForRow(0).text).toBe "SST Sa line with tabsTand T spacesSTS" @@ -623,7 +624,7 @@ describe "TokenizedBuffer", -> tokenizedBuffer = new TokenizedBuffer({buffer}) atom.config.set('editor.showInvisibles', true) - tokenizedBuffer.setInvisibles(cr: 'R', eol: 'N') + atom.config.set("editor.invisibles", cr: 'R', eol: 'N') fullyTokenize(tokenizedBuffer) expect(tokenizedBuffer.tokenizedLineForRow(0).endOfLineInvisibles).toEqual ['R', 'N'] @@ -634,7 +635,7 @@ describe "TokenizedBuffer", -> expect(left.endOfLineInvisibles).toBe null expect(right.endOfLineInvisibles).toEqual ['R', 'N'] - tokenizedBuffer.setInvisibles(cr: 'R', eol: false) + atom.config.set("editor.invisibles", cr: 'R', eol: false) expect(tokenizedBuffer.tokenizedLineForRow(0).endOfLineInvisibles).toEqual ['R'] expect(tokenizedBuffer.tokenizedLineForRow(1).endOfLineInvisibles).toEqual [] @@ -688,7 +689,8 @@ describe "TokenizedBuffer", -> it "sets leading and trailing whitespace correctly on a line with invisible characters that is copied", -> buffer.setText(" \t a line with tabs\tand \tspaces \t ") - tokenizedBuffer.setInvisibles(space: 'S', tab: 'T') + atom.config.set("editor.showInvisibles", true) + atom.config.set("editor.invisibles", space: 'S', tab: 'T') fullyTokenize(tokenizedBuffer) line = tokenizedBuffer.tokenizedLineForRow(0).copy() @@ -696,7 +698,8 @@ describe "TokenizedBuffer", -> expect(line.tokens[line.tokens.length - 1].firstTrailingWhitespaceIndex).toBe 0 it "sets the ::firstNonWhitespaceIndex and ::firstTrailingWhitespaceIndex correctly when tokens are split for soft-wrapping", -> - tokenizedBuffer.setInvisibles(space: 'S') + atom.config.set("editor.showInvisibles", true) + atom.config.set("editor.invisibles", space: 'S') buffer.setText(" token ") fullyTokenize(tokenizedBuffer) token = tokenizedBuffer.tokenizedLines[0].tokens[0] diff --git a/src/atom.coffee b/src/atom.coffee index 1cf63b397..0d029b61d 100644 --- a/src/atom.coffee +++ b/src/atom.coffee @@ -223,6 +223,8 @@ class Atom extends Model @disposables?.dispose() @disposables = new CompositeDisposable + @displayWindow() unless @inSpecMode() + @setBodyPlatformClass() @loadTime = null @@ -483,22 +485,27 @@ class Atom extends Model # Extended: Set the full screen state of the current window. setFullScreen: (fullScreen=false) -> ipc.send('call-window-method', 'setFullScreen', fullScreen) - if fullScreen then document.body.classList.add("fullscreen") else document.body.classList.remove("fullscreen") + 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()) - # Schedule the window to be shown and focused on the next tick. + # Restore the window to its previous dimensions and show it. # - # This is done in a next tick to prevent a white flicker from occurring - # if called synchronously. - displayWindow: ({maximize}={}) -> + # Also restores the full screen and maximized state on the next tick to + # prevent resize glitches. + displayWindow: -> + dimensions = @restoreWindowDimensions() + @show() + setImmediate => - @show() @focus() - @setFullScreen(true) if @workspace.fullScreen - @maximize() if maximize + @setFullScreen(true) if @workspace?.fullScreen + @maximize() if dimensions?.maximized and process.platform isnt 'darwin' # Get the dimensions of this window. # @@ -572,6 +579,13 @@ class Atom extends Model 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: -> {safeMode} = @getLoadSettings() @@ -582,7 +596,6 @@ class Atom extends Model CommandInstaller.installApmCommand false, (error) -> console.warn error.message if error? - dimensions = @restoreWindowDimensions() @loadConfig() @keymaps.loadBundledKeymaps() @themes.loadBaseStylesheets() @@ -602,12 +615,10 @@ class Atom extends Model @openInitialEmptyEditorIfNecessary() - maximize = dimensions?.maximized and process.platform isnt 'darwin' - @displayWindow({maximize}) - unloadEditorWindow: -> return if not @project + @storeWindowBackground() @state.grammars = @grammars.serialize() @state.project = @project.serialize() @state.workspace = @workspace.serialize() @@ -747,7 +758,7 @@ class Atom extends Model # Only reload stylesheets from non-theme packages for pack in @packages.getActivePackages() when pack.getType() isnt 'theme' pack.reloadStylesheets?() - null + return # Notify the browser project of the window's current project path watchProjectPath: -> diff --git a/src/browser/application-menu.coffee b/src/browser/application-menu.coffee index a845c682f..74da80e43 100644 --- a/src/browser/application-menu.coffee +++ b/src/browser/application-menu.coffee @@ -9,11 +9,10 @@ _ = require 'underscore-plus' # and maintain the state of all menu items. module.exports = class ApplicationMenu - constructor: (@version) -> + constructor: (@version, @autoUpdateManager) -> @windowTemplates = new WeakMap() @setActiveTemplate(@getDefaultTemplate()) - global.atomApplication.autoUpdateManager.on 'state-changed', (state) => - @showUpdateMenuItem(state) + @autoUpdateManager.on 'state-changed', (state) => @showUpdateMenuItem(state) # Public: Updates the entire menu with the given keybindings. # @@ -33,7 +32,7 @@ class ApplicationMenu @menu = Menu.buildFromTemplate(_.deepClone(template)) Menu.setApplicationMenu(@menu) - @showUpdateMenuItem(global.atomApplication.autoUpdateManager.getState()) + @showUpdateMenuItem(@autoUpdateManager.getState()) # Register a BrowserWindow with this application menu. addWindow: (window) -> diff --git a/src/browser/atom-application.coffee b/src/browser/atom-application.coffee index 0e7ff9f4d..afc811144 100644 --- a/src/browser/atom-application.coffee +++ b/src/browser/atom-application.coffee @@ -71,8 +71,8 @@ class AtomApplication @pathsToOpen ?= [] @windows = [] - @autoUpdateManager = new AutoUpdateManager(@version) - @applicationMenu = new ApplicationMenu(@version) + @autoUpdateManager = new AutoUpdateManager(@version, options.test) + @applicationMenu = new ApplicationMenu(@version, @autoUpdateManager) @atomProtocolHandler = new AtomProtocolHandler(@resourcePath, @safeMode) @listenForArgumentsFromNewProcess() @@ -99,7 +99,12 @@ class AtomApplication # Public: Removes the {AtomWindow} from the global window list. removeWindow: (window) -> @windows.splice @windows.indexOf(window), 1 - @applicationMenu?.enableWindowSpecificItems(false) if @windows.length is 0 + if @windows.length is 0 + @applicationMenu?.enableWindowSpecificItems(false) + if process.platform in ['win32', 'linux'] + app.quit() + return + @saveState() unless window.isSpec # Public: Adds the {AtomWindow} to the global window list. addWindow: (window) -> @@ -110,10 +115,14 @@ class AtomApplication unless window.isSpec focusHandler = => @lastFocusedWindow = window + blurHandler = => @saveState() window.browserWindow.on 'focus', focusHandler + window.browserWindow.on 'blur', blurHandler window.browserWindow.once 'closed', => @lastFocusedWindow = null if window is @lastFocusedWindow window.browserWindow.removeListener 'focus', focusHandler + window.browserWindow.removeListener 'blur', blurHandler + window.browserWindow.webContents.once 'did-finish-load', => @saveState() # Creates server to listen for additional atom application launches. # @@ -176,7 +185,7 @@ class AtomApplication @on 'application:report-issue', -> require('shell').openExternal('https://github.com/atom/atom/blob/master/CONTRIBUTING.md#submitting-issues') @on 'application:search-issues', -> require('shell').openExternal('https://github.com/issues?q=+is%3Aissue+user%3Aatom') - @on 'application:install-update', -> @autoUpdateManager.install() + @on 'application:install-update', => @autoUpdateManager.install() @on 'application:check-for-update', => @autoUpdateManager.check() if process.platform is 'darwin' @@ -199,17 +208,12 @@ class AtomApplication @openPathOnEvent('application:open-your-stylesheet', 'atom://.atom/stylesheet') @openPathOnEvent('application:open-license', path.join(process.resourcesPath, 'LICENSE.md')) - app.on 'window-all-closed', -> - app.quit() if process.platform in ['win32', 'linux'] - - app.on 'before-quit', => - @saveState() - app.on 'will-quit', => @killAllProcesses() @deleteSocketFile() app.on 'will-exit', => + @saveState() unless @windows.every (window) -> window.isSpec @killAllProcesses() @deleteSocketFile() @@ -422,8 +426,8 @@ class AtomApplication saveState: -> states = [] for window in @windows - if loadSettings = window.getLoadSettings() - unless loadSettings.isSpec + unless window.isSpec + if loadSettings = window.getLoadSettings() states.push(initialPaths: loadSettings.initialPaths) @storageFolder.store('application.json', states) diff --git a/src/browser/auto-update-manager.coffee b/src/browser/auto-update-manager.coffee index 358390889..654a8de67 100644 --- a/src/browser/auto-update-manager.coffee +++ b/src/browser/auto-update-manager.coffee @@ -15,7 +15,7 @@ module.exports = class AutoUpdateManager _.extend @prototype, EventEmitter.prototype - constructor: (@version) -> + constructor: (@version, @testMode) -> @state = IdleState if process.platform is 'win32' # Squirrel for Windows can't handle query params @@ -80,10 +80,10 @@ class AutoUpdateManager autoUpdater.once 'update-not-available', @onUpdateNotAvailable autoUpdater.once 'error', @onUpdateError - autoUpdater.checkForUpdates() + autoUpdater.checkForUpdates() unless @testMode install: -> - autoUpdater.quitAndInstall() + autoUpdater.quitAndInstall() unless @testMode onUpdateNotAvailable: => autoUpdater.removeListener 'error', @onUpdateError diff --git a/src/display-buffer.coffee b/src/display-buffer.coffee index f4c078b17..d5a893fe3 100644 --- a/src/display-buffer.coffee +++ b/src/display-buffer.coffee @@ -24,13 +24,13 @@ class DisplayBuffer extends Model horizontalScrollMargin: 6 scopedCharacterWidthsChangeCount: 0 - constructor: ({tabLength, @editorWidthInChars, @tokenizedBuffer, buffer, @invisibles}={}) -> + constructor: ({tabLength, @editorWidthInChars, @tokenizedBuffer, buffer, ignoreInvisibles}={}) -> super @emitter = new Emitter @disposables = new CompositeDisposable - @tokenizedBuffer ?= new TokenizedBuffer({tabLength, buffer, @invisibles}) + @tokenizedBuffer ?= new TokenizedBuffer({tabLength, buffer, ignoreInvisibles}) @buffer = @tokenizedBuffer.buffer @charWidthsByScope = {} @markers = {} @@ -42,6 +42,7 @@ class DisplayBuffer extends Model @disposables.add @buffer.onDidUpdateMarkers @handleBufferMarkersUpdated @disposables.add @buffer.onDidCreateMarker @handleBufferMarkerCreated @updateAllScreenLines() + @foldMarkerAttributes = Object.freeze({class: 'fold', displayBufferId: @id}) @createFoldForMarker(marker) for marker in @buffer.findMarkers(@getFoldMarkerAttributes()) subscribeToScopedConfigSettings: => @@ -86,14 +87,13 @@ class DisplayBuffer extends Model scrollTop: @scrollTop scrollLeft: @scrollLeft tokenizedBuffer: @tokenizedBuffer.serialize() - invisibles: _.clone(@invisibles) deserializeParams: (params) -> params.tokenizedBuffer = TokenizedBuffer.deserialize(params.tokenizedBuffer) params copy: -> - newDisplayBuffer = new DisplayBuffer({@buffer, tabLength: @getTabLength(), @invisibles}) + newDisplayBuffer = new DisplayBuffer({@buffer, tabLength: @getTabLength()}) newDisplayBuffer.setScrollTop(@getScrollTop()) newDisplayBuffer.setScrollLeft(@getScrollLeft()) @@ -428,8 +428,8 @@ class DisplayBuffer extends Model setTabLength: (tabLength) -> @tokenizedBuffer.setTabLength(tabLength) - setInvisibles: (@invisibles) -> - @tokenizedBuffer.setInvisibles(@invisibles) + setIgnoreInvisibles: (ignoreInvisibles) -> + @tokenizedBuffer.setIgnoreInvisibles(ignoreInvisibles) setSoftWrapped: (softWrapped) -> if softWrapped isnt @softWrapped @@ -1075,8 +1075,11 @@ class DisplayBuffer extends Model findFoldMarkers: (attributes) -> @buffer.findMarkers(@getFoldMarkerAttributes(attributes)) - getFoldMarkerAttributes: (attributes={}) -> - _.extend(attributes, class: 'fold', displayBufferId: @id) + getFoldMarkerAttributes: (attributes) -> + if attributes + _.extend(attributes, @foldMarkerAttributes) + else + @foldMarkerAttributes pauseMarkerChangeEvents: -> marker.pauseChangeEvents() for marker in @getMarkers() diff --git a/src/package-manager.coffee b/src/package-manager.coffee index 8527188d3..606f72eda 100644 --- a/src/package-manager.coffee +++ b/src/package-manager.coffee @@ -303,6 +303,9 @@ class PackageManager # of the first package isn't skewed by being the first to require atom require '../exports/atom' + # TODO: remove after a few atom versions. + @uninstallAutocompletePlus() + packagePaths = @getAvailablePackagePaths() packagePaths = packagePaths.filter (packagePath) => not @isPackageDisabled(path.basename(packagePath)) packagePaths = _.uniq packagePaths, (packagePath) -> path.basename(packagePath) @@ -409,6 +412,40 @@ class PackageManager message = "Failed to load the #{path.basename(packagePath)} package" atom.notifications.addError(message, {stack, detail, dismissable: true}) + # TODO: remove these autocomplete-plus specific helpers after a few versions. + uninstallAutocompletePlus: -> + packageDir = null + devDir = path.join("dev", "packages") + for packageDirPath in @packageDirPaths + if not packageDirPath.endsWith(devDir) + packageDir = packageDirPath + break + + if packageDir? + dirsToRemove = [ + path.join(packageDir, 'autocomplete-plus') + path.join(packageDir, 'autocomplete-atom-api') + path.join(packageDir, 'autocomplete-css') + path.join(packageDir, 'autocomplete-html') + path.join(packageDir, 'autocomplete-emojis') + path.join(packageDir, 'autocomplete-snippets') + ] + for dirToRemove in dirsToRemove + @uninstallDirectory(dirToRemove) + return + + uninstallDirectory: (directory) -> + symlinkPromise = new Promise (resolve) -> + fs.isSymbolicLink directory, (isSymLink) -> resolve(isSymLink) + + dirPromise = new Promise (resolve) -> + fs.isDirectory directory, (isDir) -> resolve(isDir) + + Promise.all([symlinkPromise, dirPromise]).then (values) -> + [isSymLink, isDir] = values + if not isSymLink and isDir + fs.remove directory, -> + if Grim.includeDeprecatedAPIs EmitterMixin = require('emissary').Emitter EmitterMixin.includeInto(PackageManager) diff --git a/src/text-editor.coffee b/src/text-editor.coffee index ac7c7d4f2..d2bd77522 100644 --- a/src/text-editor.coffee +++ b/src/text-editor.coffee @@ -84,12 +84,10 @@ class TextEditor extends Model @selections = [] buffer ?= new TextBuffer - @displayBuffer ?= new DisplayBuffer({buffer, tabLength, softWrapped}) + @displayBuffer ?= new DisplayBuffer({buffer, tabLength, softWrapped, ignoreInvisibles: @mini}) @buffer = @displayBuffer.buffer @softTabs = @usesSoftTabs() ? @softTabs ? atom.config.get('editor.softTabs') ? true - @updateInvisibles() - for marker in @findMarkers(@getSelectionMarkerAttributes()) marker.setProperties(preserveFolds: true) @addSelection(marker) @@ -170,21 +168,9 @@ class TextEditor extends Model @subscribe @displayBuffer.onDidAddDecoration (decoration) => @emit 'decoration-added', decoration @subscribe @displayBuffer.onDidRemoveDecoration (decoration) => @emit 'decoration-removed', decoration - @subscribeToScopedConfigSettings() - - subscribeToScopedConfigSettings: -> - @scopedConfigSubscriptions?.dispose() - @scopedConfigSubscriptions = subscriptions = new CompositeDisposable - - scopeDescriptor = @getRootScopeDescriptor() - - subscriptions.add atom.config.onDidChange 'editor.showInvisibles', scope: scopeDescriptor, => @updateInvisibles() - subscriptions.add atom.config.onDidChange 'editor.invisibles', scope: scopeDescriptor, => @updateInvisibles() - destroyed: -> @unsubscribe() if includeDeprecatedAPIs @disposables.dispose() - @scopedConfigSubscriptions.dispose() selection.destroy() for selection in @getSelections() @buffer.release() @displayBuffer.destroy() @@ -488,7 +474,7 @@ class TextEditor extends Model setMini: (mini) -> if mini isnt @mini @mini = mini - @updateInvisibles() + @displayBuffer.setIgnoreInvisibles(@mini) @emitter.emit 'did-change-mini', @mini @mini @@ -2779,15 +2765,6 @@ class TextEditor extends Model shouldAutoIndentOnPaste: -> atom.config.get("editor.autoIndentOnPaste", scope: @getRootScopeDescriptor()) - shouldShowInvisibles: -> - not @mini and atom.config.get('editor.showInvisibles', scope: @getRootScopeDescriptor()) - - updateInvisibles: -> - if @shouldShowInvisibles() - @displayBuffer.setInvisibles(atom.config.get('editor.invisibles', scope: @getRootScopeDescriptor())) - else - @displayBuffer.setInvisibles(null) - ### Section: Event Handlers ### @@ -2796,8 +2773,6 @@ class TextEditor extends Model @softTabs = @usesSoftTabs() ? @softTabs handleGrammarChange: -> - @updateInvisibles() - @subscribeToScopedConfigSettings() @unfoldAll() @emit 'grammar-changed' if includeDeprecatedAPIs @emitter.emit 'did-change-grammar', @getGrammar() diff --git a/src/tokenized-buffer.coffee b/src/tokenized-buffer.coffee index d9f1bcba7..6d8f0c018 100644 --- a/src/tokenized-buffer.coffee +++ b/src/tokenized-buffer.coffee @@ -20,8 +20,9 @@ class TokenizedBuffer extends Model chunkSize: 50 invalidRows: null visible: false + configSettings: null - constructor: ({@buffer, @tabLength, @invisibles}) -> + constructor: ({@buffer, @tabLength, @ignoreInvisibles}) -> @emitter = new Emitter @disposables = new CompositeDisposable @@ -39,7 +40,7 @@ class TokenizedBuffer extends Model serializeParams: -> bufferPath: @buffer.getPath() tabLength: @tabLength - invisibles: _.clone(@invisibles) + ignoreInvisibles: @ignoreInvisibles deserializeParams: (params) -> params.buffer = atom.project.bufferForPathSync(params.bufferPath) @@ -76,13 +77,25 @@ class TokenizedBuffer extends Model @grammarUpdateDisposable = @grammar.onDidUpdate => @retokenizeLines() @disposables.add(@grammarUpdateDisposable) - @configSettings = tabLength: atom.config.get('editor.tabLength', scope: @rootScopeDescriptor) + scopeOptions = {scope: @rootScopeDescriptor} + @configSettings = + tabLength: atom.config.get('editor.tabLength', scopeOptions) + invisibles: atom.config.get('editor.invisibles', scopeOptions) + showInvisibles: atom.config.get('editor.showInvisibles', scopeOptions) - @grammarTabLengthSubscription?.dispose() - @grammarTabLengthSubscription = atom.config.onDidChange 'editor.tabLength', scope: @rootScopeDescriptor, ({newValue}) => + if @configSubscriptions? + @configSubscriptions.dispose() + @disposables.remove(@configSubscriptions) + @configSubscriptions = new CompositeDisposable + @configSubscriptions.add atom.config.onDidChange 'editor.tabLength', scopeOptions, ({newValue}) => @configSettings.tabLength = newValue @retokenizeLines() - @disposables.add(@grammarTabLengthSubscription) + ['invisibles', 'showInvisibles'].forEach (key) => + @configSubscriptions.add atom.config.onDidChange "editor.#{key}", scopeOptions, ({newValue}) => + oldInvisibles = @getInvisiblesToShow() + @configSettings[key] = newValue + @retokenizeLines() unless _.isEqual(@getInvisiblesToShow(), oldInvisibles) + @disposables.add(@configSubscriptions) @retokenizeLines() @@ -123,10 +136,11 @@ class TokenizedBuffer extends Model @tabLength = tabLength @retokenizeLines() - setInvisibles: (invisibles) -> - unless _.isEqual(invisibles, @invisibles) - @invisibles = invisibles - @retokenizeLines() + setIgnoreInvisibles: (ignoreInvisibles) -> + if ignoreInvisibles isnt @ignoreInvisibles + @ignoreInvisibles = ignoreInvisibles + if @configSettings.showInvisibles and @configSettings.invisibles? + @retokenizeLines() tokenizeInBackground: -> return if not @visible or @pendingChunk or not @isAlive() @@ -302,7 +316,7 @@ class TokenizedBuffer extends Model tabLength = @getTabLength() indentLevel = @indentLevelForRow(row) lineEnding = @buffer.lineEndingForRow(row) - new TokenizedLine({tokens, tabLength, indentLevel, @invisibles, lineEnding}) + new TokenizedLine({tokens, tabLength, indentLevel, invisibles: @getInvisiblesToShow(), lineEnding}) buildTokenizedLineForRow: (row, ruleStack) -> @buildTokenizedLineForRowWithText(row, @buffer.lineForRow(row), ruleStack) @@ -312,7 +326,13 @@ class TokenizedBuffer extends Model tabLength = @getTabLength() indentLevel = @indentLevelForRow(row) {tokens, ruleStack} = @grammar.tokenizeLine(line, ruleStack, row is 0) - new TokenizedLine({tokens, ruleStack, tabLength, lineEnding, indentLevel, @invisibles}) + new TokenizedLine({tokens, ruleStack, tabLength, lineEnding, indentLevel, invisibles: @getInvisiblesToShow()}) + + getInvisiblesToShow: -> + if @configSettings.showInvisibles and not @ignoreInvisibles + @configSettings.invisibles + else + null tokenizedLineForRow: (bufferRow) -> @tokenizedLines[bufferRow] diff --git a/static/index.html b/static/index.html index 5559058dc..84e8d57d4 100644 --- a/static/index.html +++ b/static/index.html @@ -1,8 +1,6 @@ - + - - diff --git a/static/index.js b/static/index.js index 0a377044e..ef61e8bce 100644 --- a/static/index.js +++ b/static/index.js @@ -1,6 +1,9 @@ var fs = require('fs'); var path = require('path'); +var loadSettings = null; +var loadSettingsError = null; + window.onload = function() { try { var startTime = Date.now(); @@ -12,30 +15,19 @@ window.onload = function() { // Ensure ATOM_HOME is always set before anything else is required setupAtomHome(); - var cacheDir = path.join(process.env.ATOM_HOME, 'compile-cache'); - // Use separate compile cache when sudo'ing as root to avoid permission issues - if (process.env.USER === 'root' && process.env.SUDO_USER && process.env.SUDO_USER !== process.env.USER) { - cacheDir = path.join(cacheDir, 'root'); - } - - var rawLoadSettings = decodeURIComponent(location.hash.substr(1)); - var loadSettings; - try { - loadSettings = JSON.parse(rawLoadSettings); - } catch (error) { - console.error("Failed to parse load settings: " + rawLoadSettings); - throw error; - } - // Normalize to make sure drive letter case is consistent on Windows process.resourcesPath = path.normalize(process.resourcesPath); + if (loadSettingsError) { + throw loadSettingsError; + } + var devMode = loadSettings.devMode || !loadSettings.resourcePath.startsWith(process.resourcesPath + path.sep); if (loadSettings.profileStartup) { - profileStartup(cacheDir, loadSettings, Date.now() - startTime); + profileStartup(loadSettings, Date.now() - startTime); } else { - setupWindow(cacheDir, loadSettings); + setupWindow(loadSettings); setLoadTime(Date.now() - startTime); } } catch (error) { @@ -43,6 +35,15 @@ window.onload = function() { } } +var getCacheDirectory = function() { + var cacheDir = path.join(process.env.ATOM_HOME, 'compile-cache'); + // Use separate compile cache when sudo'ing as root to avoid permission issues + if (process.env.USER === 'root' && process.env.SUDO_USER && process.env.SUDO_USER !== process.env.USER) { + cacheDir = path.join(cacheDir, 'root'); + } + return cacheDir; +} + var setLoadTime = function(loadTime) { if (global.atom) { global.atom.loadTime = loadTime; @@ -59,7 +60,9 @@ var handleSetupError = function(error) { console.error(error.stack || error); } -var setupWindow = function(cacheDir, loadSettings) { +var setupWindow = function(loadSettings) { + var cacheDir = getCacheDirectory(); + setupCoffeeCache(cacheDir); ModuleCache = require('../src/module-cache'); @@ -133,16 +136,17 @@ var setupSourceMapCache = function(cacheDir) { var setupVmCompatibility = function() { var vm = require('vm'); - if (!vm.Script.createContext) + if (!vm.Script.createContext) { vm.Script.createContext = vm.createContext; + } } -var profileStartup = function(cacheDir, loadSettings, initialTime) { +var profileStartup = function(loadSettings, initialTime) { var profile = function() { console.profile('startup'); try { var startTime = Date.now() - setupWindow(cacheDir, loadSettings); + setupWindow(loadSettings); setLoadTime(Date.now() - startTime + initialTime); } catch (error) { handleSetupError(error); @@ -162,3 +166,41 @@ var profileStartup = function(cacheDir, loadSettings, initialTime) { }); } } + +var parseLoadSettings = function() { + var rawLoadSettings = decodeURIComponent(location.hash.substr(1)); + try { + loadSettings = JSON.parse(rawLoadSettings); + } catch (error) { + console.error("Failed to parse load settings: " + rawLoadSettings); + loadSettingsError = error; + } +} + +var setupWindowBackground = function() { + if (loadSettings && loadSettings.isSpec) { + return; + } + + var backgroundColor = window.localStorage.getItem('atom:window-background-color'); + if (!backgroundColor) { + return; + } + + var backgroundStylesheet = document.createElement('style'); + backgroundStylesheet.type = 'text/css'; + backgroundStylesheet.innerText = 'html, body { background: ' + backgroundColor + '; }'; + document.head.appendChild(backgroundStylesheet); + + // Remove once the page loads + window.addEventListener("load", function loadWindow() { + window.removeEventListener("load", loadWindow, false); + setTimeout(function() { + backgroundStylesheet.remove(); + backgroundStylesheet = null; + }, 1000); + }, false); +} + +parseLoadSettings(); +setupWindowBackground();