diff --git a/package.json b/package.json index c2e024a22..1a50c58b0 100644 --- a/package.json +++ b/package.json @@ -104,19 +104,19 @@ "link": "0.30.0", "markdown-preview": "0.137.0", "metrics": "0.45.0", - "notifications": "0.28.0", - "open-on-github": "0.32.0", + "notifications": "0.31.0", + "open-on-github": "0.33.0", "package-generator": "0.38.0", "release-notes": "0.51.0", "settings-view": "0.183.0", - "snippets": "0.76.0", + "snippets": "0.77.0", "spell-check": "0.55.0", "status-bar": "0.60.0", "styleguide": "0.44.0", - "symbols-view": "0.84.0", + "symbols-view": "0.85.0", "tabs": "0.67.0", "timecop": "0.31.0", - "tree-view": "0.162.0", + "tree-view": "0.163.0", "update-package-dependencies": "0.8.0", "welcome": "0.24.0", "whitespace": "0.29.0", @@ -138,7 +138,7 @@ "language-make": "0.13.0", "language-mustache": "0.11.0", "language-objective-c": "0.15.0", - "language-perl": "0.10.0", + "language-perl": "0.11.0", "language-php": "0.21.0", "language-property-list": "0.8.0", "language-python": "0.32.0", diff --git a/spec/atom-spec.coffee b/spec/atom-spec.coffee index 2c1a40afc..12d094428 100644 --- a/spec/atom-spec.coffee +++ b/spec/atom-spec.coffee @@ -3,6 +3,8 @@ Exec = require('child_process').exec path = require 'path' Package = require '../src/package' ThemeManager = require '../src/theme-manager' +_ = require "underscore-plus" +temp = require "temp" describe "the `atom` global", -> describe 'window sizing methods', -> @@ -124,3 +126,29 @@ describe "the `atom` global", -> line: 2 column: 3 originalError: error + + describe "saving and loading", -> + afterEach -> atom.mode = "spec" + + it "selects the state based on the current project paths", -> + Atom = atom.constructor + [dir1, dir2] = [temp.mkdirSync("dir1-"), temp.mkdirSync("dir2-")] + + loadSettings = _.extend Atom.getLoadSettings(), + initialPaths: [dir1] + windowState: null + + spyOn(Atom, 'getLoadSettings').andCallFake -> loadSettings + spyOn(Atom, 'getStorageDirPath').andReturn(temp.mkdirSync("storage-dir-")) + + atom.mode = "editor" + atom.state.stuff = "cool" + atom.project.setPaths([dir1, dir2]) + atom.saveSync.originalValue.call(atom) + + atom1 = Atom.loadOrCreate("editor") + expect(atom1.state.stuff).toBeUndefined() + + loadSettings.initialPaths = [dir2, dir1] + atom2 = Atom.loadOrCreate("editor") + expect(atom2.state.stuff).toBe("cool") diff --git a/spec/display-buffer-spec.coffee b/spec/display-buffer-spec.coffee index 514a51625..23b658c04 100644 --- a/spec/display-buffer-spec.coffee +++ b/spec/display-buffer-spec.coffee @@ -80,6 +80,14 @@ describe "DisplayBuffer", -> atom.config.set('editor.softWrapAtPreferredLineLength', false) expect(displayBuffer.tokenizedLineForScreenRow(10).text).toBe ' return ' + describe "when editor width is negative", -> + it "does not hang while wrapping", -> + displayBuffer.setDefaultCharWidth(1) + displayBuffer.setWidth(-1) + + expect(displayBuffer.tokenizedLineForScreenRow(1).text).toBe " " + expect(displayBuffer.tokenizedLineForScreenRow(2).text).toBe " var sort = function(items) {" + describe "when the line is shorter than the max line length", -> it "renders the line unchanged", -> expect(displayBuffer.tokenizedLineForScreenRow(0).text).toBe buffer.lineForRow(0) diff --git a/spec/project-spec.coffee b/spec/project-spec.coffee index 466431faf..0d115f56f 100644 --- a/spec/project-spec.coffee +++ b/spec/project-spec.coffee @@ -438,6 +438,23 @@ describe "Project", -> randomPath = path.join("some", "random", "path") expect(atom.project.relativize(randomPath)).toBe randomPath + describe ".relativizePath(path)", -> + it "returns the root path that contains the given path, and the path relativized to that root path", -> + atom.project.addPath(temp.mkdirSync("another-path")) + + rootPath = atom.project.getPaths()[0] + childPath = path.join(rootPath, "some", "child", "directory") + expect(atom.project.relativizePath(childPath)).toEqual [rootPath, path.join("some", "child", "directory")] + + rootPath = atom.project.getPaths()[1] + childPath = path.join(rootPath, "some", "child", "directory") + expect(atom.project.relativizePath(childPath)).toEqual [rootPath, path.join("some", "child", "directory")] + + describe "when the given path isn't inside of any of the project's path", -> + it "returns null for the root path, and the given path unchanged", -> + randomPath = path.join("some", "random", "path") + expect(atom.project.relativizePath(randomPath)).toEqual [null, randomPath] + describe ".contains(path)", -> it "returns whether or not the given path is in one of the root directories", -> rootPath = atom.project.getPaths()[0] diff --git a/spec/text-editor-component-spec.coffee b/spec/text-editor-component-spec.coffee index d7b9ed2ff..a830efe04 100644 --- a/spec/text-editor-component-spec.coffee +++ b/spec/text-editor-component-spec.coffee @@ -794,6 +794,8 @@ describe "TextEditorComponent", -> it "blinks cursors when they aren't moving", -> cursorsNode = componentNode.querySelector('.cursors') + wrapperNode.focus() + nextAnimationFrame() expect(cursorsNode.classList.contains('blink-off')).toBe false advanceClock(component.cursorBlinkPeriod / 2) @@ -2481,8 +2483,7 @@ describe "TextEditorComponent", -> gutterWidth = componentNode.querySelector('.gutter').offsetWidth componentNode.style.width = gutterWidth + 14 * charWidth + editor.getVerticalScrollbarWidth() + 'px' advanceClock(atom.views.documentPollingInterval) - nextAnimationFrame() # won't poll until cursor blinks - nextAnimationFrame() # handle update requested by poll + nextAnimationFrame() expect(componentNode.querySelector('.line').textContent).toBe "var quicksort " it "accounts for the scroll view's padding when determining the wrap location", -> diff --git a/spec/text-editor-presenter-spec.coffee b/spec/text-editor-presenter-spec.coffee index 163521c32..2ee966630 100644 --- a/spec/text-editor-presenter-spec.coffee +++ b/spec/text-editor-presenter-spec.coffee @@ -1135,49 +1135,63 @@ describe "TextEditorPresenter", -> presenter = buildPresenter(explicitHeight: 20, scrollTop: 0) expect(stateForCursor(presenter, 0).width).toBe 10 - describe ".blinkCursorsOff", -> - it "alternates between true and false twice per ::cursorBlinkPeriod", -> + describe ".cursorsVisible", -> + it "alternates between true and false twice per ::cursorBlinkPeriod when the editor is focused", -> cursorBlinkPeriod = 100 cursorBlinkResumeDelay = 200 - presenter = buildPresenter({cursorBlinkPeriod, cursorBlinkResumeDelay}) + presenter = buildPresenter({cursorBlinkPeriod, cursorBlinkResumeDelay, focused: true}) - expect(presenter.state.content.blinkCursorsOff).toBe false + expect(presenter.state.content.cursorsVisible).toBe true expectStateUpdate presenter, -> advanceClock(cursorBlinkPeriod / 2) - expect(presenter.state.content.blinkCursorsOff).toBe true + expect(presenter.state.content.cursorsVisible).toBe false expectStateUpdate presenter, -> advanceClock(cursorBlinkPeriod / 2) - expect(presenter.state.content.blinkCursorsOff).toBe false + expect(presenter.state.content.cursorsVisible).toBe true expectStateUpdate presenter, -> advanceClock(cursorBlinkPeriod / 2) - expect(presenter.state.content.blinkCursorsOff).toBe true + expect(presenter.state.content.cursorsVisible).toBe false + expectStateUpdate presenter, -> advanceClock(cursorBlinkPeriod / 2) + expect(presenter.state.content.cursorsVisible).toBe true + + expectStateUpdate presenter, -> presenter.setFocused(false) + expect(presenter.state.content.cursorsVisible).toBe false + advanceClock(cursorBlinkPeriod / 2) + expect(presenter.state.content.cursorsVisible).toBe false + advanceClock(cursorBlinkPeriod / 2) + expect(presenter.state.content.cursorsVisible).toBe false + + expectStateUpdate presenter, -> presenter.setFocused(true) + expect(presenter.state.content.cursorsVisible).toBe true + expectStateUpdate presenter, -> advanceClock(cursorBlinkPeriod / 2) + expect(presenter.state.content.cursorsVisible).toBe false it "stops alternating for ::cursorBlinkResumeDelay when a cursor moves or a cursor is added", -> cursorBlinkPeriod = 100 cursorBlinkResumeDelay = 200 - presenter = buildPresenter({cursorBlinkPeriod, cursorBlinkResumeDelay}) + presenter = buildPresenter({cursorBlinkPeriod, cursorBlinkResumeDelay, focused: true}) - expect(presenter.state.content.blinkCursorsOff).toBe false + expect(presenter.state.content.cursorsVisible).toBe true expectStateUpdate presenter, -> advanceClock(cursorBlinkPeriod / 2) - expect(presenter.state.content.blinkCursorsOff).toBe true + expect(presenter.state.content.cursorsVisible).toBe false expectStateUpdate presenter, -> editor.moveRight() - expect(presenter.state.content.blinkCursorsOff).toBe false + expect(presenter.state.content.cursorsVisible).toBe true expectStateUpdate presenter, -> advanceClock(cursorBlinkResumeDelay) advanceClock(cursorBlinkPeriod / 2) - expect(presenter.state.content.blinkCursorsOff).toBe true + expect(presenter.state.content.cursorsVisible).toBe false expectStateUpdate presenter, -> advanceClock(cursorBlinkPeriod / 2) - expect(presenter.state.content.blinkCursorsOff).toBe false + expect(presenter.state.content.cursorsVisible).toBe true expectStateUpdate presenter, -> advanceClock(cursorBlinkPeriod / 2) - expect(presenter.state.content.blinkCursorsOff).toBe true + expect(presenter.state.content.cursorsVisible).toBe false expectStateUpdate presenter, -> editor.addCursorAtBufferPosition([1, 0]) - expect(presenter.state.content.blinkCursorsOff).toBe false + expect(presenter.state.content.cursorsVisible).toBe true expectStateUpdate presenter, -> advanceClock(cursorBlinkResumeDelay) advanceClock(cursorBlinkPeriod / 2) - expect(presenter.state.content.blinkCursorsOff).toBe true + expect(presenter.state.content.cursorsVisible).toBe false describe ".highlights", -> stateForHighlight = (presenter, decoration) -> diff --git a/src/atom.coffee b/src/atom.coffee index 385180dfd..303b0cfe7 100644 --- a/src/atom.coffee +++ b/src/atom.coffee @@ -73,7 +73,7 @@ class Atom extends Model # Loads and returns the serialized state corresponding to this window # if it exists; otherwise returns undefined. @loadState: (mode) -> - statePath = @getStatePath(mode) + statePath = @getStatePath(@getLoadSettings().initialPaths, mode) if fs.existsSync(statePath) try @@ -90,14 +90,13 @@ class Atom extends Model # Returns the path where the state for the current window will be # located if it exists. - @getStatePath: (mode) -> + @getStatePath: (paths, mode) -> switch mode when 'spec' filename = 'spec' when 'editor' - {initialPaths} = @getLoadSettings() - if initialPaths?.length > 0 - sha1 = crypto.createHash('sha1').update(initialPaths.join("\n")).digest('hex') + if paths?.length > 0 + sha1 = crypto.createHash('sha1').update(paths.slice().sort().join("\n")).digest('hex') filename = "editor-#{sha1}" if filename @@ -773,7 +772,7 @@ class Atom extends Model saveSync: -> stateString = JSON.stringify(@state) - if statePath = @constructor.getStatePath(@mode) + if statePath = @constructor.getStatePath(@project?.getPaths(), @mode) fs.writeFileSync(statePath, stateString, 'utf8') else @getCurrentWindow().loadSettings.windowState = stateString diff --git a/src/browser/auto-update-manager.coffee b/src/browser/auto-update-manager.coffee index dfbdacfc4..a0c1f2da8 100644 --- a/src/browser/auto-update-manager.coffee +++ b/src/browser/auto-update-manager.coffee @@ -22,6 +22,7 @@ class AutoUpdateManager # https://github.com/Squirrel/Squirrel.Windows/issues/132 @feedUrl = 'https://atom.io/api/updates' else + @iconPath = path.resolve(__dirname, '..', '..', 'resources', 'atom.png') @feedUrl = "https://atom.io/api/updates?version=#{@version}" process.nextTick => @setupAutoUpdater() @@ -89,7 +90,7 @@ class AutoUpdateManager dialog.showMessageBox type: 'info' buttons: ['OK'] - icon: path.resolve(__dirname, '..', '..', 'resources', 'atom.png') + icon: @iconPath message: 'No update available.' title: 'No Update Available' detail: "Version #{@version} is the latest version." @@ -100,7 +101,7 @@ class AutoUpdateManager dialog.showMessageBox type: 'warning' buttons: ['OK'] - icon: path.resolve(__dirname, '..', '..', 'resources', 'atom.png') + icon: @iconPath message: 'There was an error checking for updates.' title: 'Update Error' detail: message diff --git a/src/cursors-component.coffee b/src/cursors-component.coffee index e11019d6c..ae844bfe0 100644 --- a/src/cursors-component.coffee +++ b/src/cursors-component.coffee @@ -13,12 +13,12 @@ class CursorsComponent @oldState ?= {cursors: {}} # update blink class - if newState.blinkCursorsOff isnt @oldState.blinkCursorsOff - if newState.blinkCursorsOff - @domNode.classList.add 'blink-off' - else + if newState.cursorsVisible isnt @oldState.cursorsVisible + if newState.cursorsVisible @domNode.classList.remove 'blink-off' - @oldState.blinkCursorsOff = newState.blinkCursorsOff + else + @domNode.classList.add 'blink-off' + @oldState.cursorsVisible = newState.cursorsVisible # remove cursors for id of @oldState.cursors diff --git a/src/display-buffer.coffee b/src/display-buffer.coffee index 79eff09cd..e09f1580c 100644 --- a/src/display-buffer.coffee +++ b/src/display-buffer.coffee @@ -458,7 +458,7 @@ class DisplayBuffer extends Model width = @width ? @getScrollWidth() width -= @getVerticalScrollbarWidth() if width? and @defaultCharWidth > 0 - Math.floor(width / @defaultCharWidth) + Math.max(0, Math.floor(width / @defaultCharWidth)) else @editorWidthInChars diff --git a/src/project.coffee b/src/project.coffee index d3fbd88eb..c92462ead 100644 --- a/src/project.coffee +++ b/src/project.coffee @@ -266,15 +266,25 @@ class Project extends Model else undefined - # Public: Make the given path relative to the project directory. - # - # * `fullPath` {String} full path relativize: (fullPath) -> + @relativizePath(fullPath)[1] + + # Public: Get the path to the project directory that contains the given path, + # and the relative path from that project directory to the given path. + # + # * `fullPath` {String} An absolute path. + # + # Returns an {Array} with two elements: + # * `projectPath` The {String} path to the project directory that contains the + # given path, or `null` if none is found. + # * `relativePath` {String} The relative path from the project directory to + # the given path. + relativizePath: (fullPath) -> return fullPath if fullPath?.match(/[A-Za-z0-9+-.]+:\/\//) # leave path alone if it has a scheme for rootDirectory in @rootDirectories relativePath = rootDirectory.relativize(fullPath) - return relativePath if relativePath isnt fullPath - fullPath + return [rootDirectory.getPath(), relativePath] unless relativePath is fullPath + [null, fullPath] # Public: Determines whether the given path (real or symbolic) is inside the # project's directory. diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index 5d3cff62e..bf1eba3da 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -25,7 +25,7 @@ class TextEditorPresenter @observeModel() @observeConfig() @buildState() - @startBlinkingCursors() + @startBlinkingCursors() if @focused @enterBatchMode() destroy: -> @@ -160,7 +160,7 @@ class TextEditorPresenter hiddenInput: {} content: scrollingVertically: false - blinkCursorsOff: false + cursorsVisible: false lines: {} highlights: {} overlays: {} @@ -619,6 +619,10 @@ class TextEditorPresenter setFocused: (focused) -> unless @focused is focused @focused = focused + if @focused + @startBlinkingCursors() + else + @stopBlinkingCursors(false) @updateFocusedState() @updateHiddenInputState() @@ -1092,18 +1096,22 @@ class TextEditorPresenter @updateCursorState(cursor) startBlinkingCursors: -> - @toggleCursorBlinkHandle = setInterval(@toggleCursorBlink.bind(this), @getCursorBlinkPeriod() / 2) + unless @toggleCursorBlinkHandle + @state.content.cursorsVisible = true + @toggleCursorBlinkHandle = setInterval(@toggleCursorBlink.bind(this), @getCursorBlinkPeriod() / 2) - stopBlinkingCursors: -> - clearInterval(@toggleCursorBlinkHandle) + stopBlinkingCursors: (visible) -> + if @toggleCursorBlinkHandle + @state.content.cursorsVisible = visible + clearInterval(@toggleCursorBlinkHandle) + @toggleCursorBlinkHandle = null toggleCursorBlink: -> - @state.content.blinkCursorsOff = not @state.content.blinkCursorsOff + @state.content.cursorsVisible = not @state.content.cursorsVisible @emitter.emit 'did-update-state' pauseCursorBlinking: -> - @state.content.blinkCursorsOff = false - @stopBlinkingCursors() + @stopBlinkingCursors(true) @startBlinkingCursorsAfterDelay ?= _.debounce(@startBlinkingCursors, @getCursorBlinkResumeDelay()) @startBlinkingCursorsAfterDelay() @emitter.emit 'did-update-state'