diff --git a/dot-atom/keymap.cson b/dot-atom/keymap.cson index cc8a8228e36..10ad345d4b0 100644 --- a/dot-atom/keymap.cson +++ b/dot-atom/keymap.cson @@ -21,7 +21,12 @@ # * https://atom.io/docs/latest/using-atom-basic-customization#customizing-key-bindings # * https://atom.io/docs/latest/behind-atom-keymaps-in-depth # +# If you're having trouble with your keybindings not working, try the +# Keybinding Resolver: `Cmd+.` on OS X and `Ctrl+.` on other platforms. See the +# Debugging Guide for more information: +# * https://atom.io/docs/latest/hacking-atom-debugging#check-the-keybindings +# # This file uses CoffeeScript Object Notation (CSON). -# If you are unfamiliar with CSON, you can read more about it in the +# If you are unfamiliar with CSON, you can read more about it in the # Atom Flight Manual: # https://atom.io/docs/latest/using-atom-basic-customization#cson diff --git a/package.json b/package.json index 1ff8721c04b..1cca017b414 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "atom-keymap": "^6.2.0", "babel-core": "^5.8.21", "bootstrap": "^3.3.4", - "cached-run-in-this-context": "0.4.0", + "cached-run-in-this-context": "0.4.1", "clear-cut": "^2.0.1", "coffee-script": "1.8.0", "color": "^0.7.3", @@ -52,7 +52,7 @@ "service-hub": "^0.7.0", "source-map-support": "^0.3.2", "temp": "0.8.1", - "text-buffer": "8.1.1", + "text-buffer": "8.1.3", "typescript-simple": "1.0.0", "underscore-plus": "^1.6.6", "yargs": "^3.23.0" @@ -62,8 +62,8 @@ "atom-dark-ui": "0.51.0", "atom-light-syntax": "0.28.0", "atom-light-ui": "0.43.0", - "base16-tomorrow-dark-theme": "1.0.0", - "base16-tomorrow-light-theme": "1.0.0", + "base16-tomorrow-dark-theme": "1.1.0", + "base16-tomorrow-light-theme": "1.1.1", "one-dark-ui": "1.1.9", "one-dark-syntax": "1.1.1", "one-light-syntax": "1.1.1", @@ -75,20 +75,20 @@ "autocomplete-atom-api": "0.9.2", "autocomplete-css": "0.11.0", "autocomplete-html": "0.7.2", - "autocomplete-plus": "2.24.0", + "autocomplete-plus": "2.25.0", "autocomplete-snippets": "1.9.0", "autoflow": "0.26.0", "autosave": "0.23.0", "background-tips": "0.26.0", "bookmarks": "0.38.0", "bracket-matcher": "0.79.0", - "command-palette": "0.37.0", + "command-palette": "0.38.0", "deprecation-cop": "0.54.0", "dev-live-reload": "0.47.0", "encoding-selector": "0.21.0", "exception-reporting": "0.37.0", - "find-and-replace": "0.194.0", - "fuzzy-finder": "0.93.0", + "find-and-replace": "0.195.0", + "fuzzy-finder": "0.94.0", "git-diff": "0.57.0", "go-to-line": "0.30.0", "grammar-selector": "0.48.0", @@ -97,7 +97,7 @@ "keybinding-resolver": "0.33.0", "line-ending-selector": "0.3.0", "link": "0.31.0", - "markdown-preview": "0.157.0", + "markdown-preview": "0.157.1", "metrics": "0.53.1", "notifications": "0.62.1", "open-on-github": "0.40.0", diff --git a/spec/display-buffer-spec.coffee b/spec/display-buffer-spec.coffee index a54c011988e..8c4adca440a 100644 --- a/spec/display-buffer-spec.coffee +++ b/spec/display-buffer-spec.coffee @@ -88,13 +88,13 @@ describe "DisplayBuffer", -> describe "when there are korean characters", -> it "takes them into account when finding the soft wrap column", -> displayBuffer.setDefaultCharWidth(1, 0, 0, 10) - buffer.setText("1234세계를 향한 대화, 유니코 제10회유니코드국제") + buffer.setText("1234세계를향한대화,유니코제10회유니코드국제") - expect(displayBuffer.tokenizedLineForScreenRow(0).text).toBe("1234세계를 ") - expect(displayBuffer.tokenizedLineForScreenRow(1).text).toBe("향한 대화, ") - expect(displayBuffer.tokenizedLineForScreenRow(2).text).toBe("유니코 ") - expect(displayBuffer.tokenizedLineForScreenRow(3).text).toBe("제10회유니") - expect(displayBuffer.tokenizedLineForScreenRow(4).text).toBe("코드국제") + expect(displayBuffer.tokenizedLineForScreenRow(0).text).toBe("1234세계를향") + expect(displayBuffer.tokenizedLineForScreenRow(1).text).toBe("한대화,유") + expect(displayBuffer.tokenizedLineForScreenRow(2).text).toBe("니코제10회") + expect(displayBuffer.tokenizedLineForScreenRow(3).text).toBe("유니코드국") + expect(displayBuffer.tokenizedLineForScreenRow(4).text).toBe("제") describe "when editor.softWrapAtPreferredLineLength is set", -> it "uses the preferred line length as the soft wrap column when it is less than the configured soft wrap column", -> @@ -130,8 +130,25 @@ describe "DisplayBuffer", -> expect(displayBuffer.tokenizedLineForScreenRow(10).text).toBe ' return ' expect(displayBuffer.tokenizedLineForScreenRow(11).text).toBe ' sort(left).concat(pivot).concat(sort(right));' + it "wraps the line at the first CJK character before the boundary", -> + displayBuffer.setEditorWidthInChars(10) + + buffer.setTextInRange([[0, 0], [1, 0]], 'abcd efg유私フ业余爱\n') + expect(displayBuffer.tokenizedLineForScreenRow(0).text).toBe 'abcd efg유私' + expect(displayBuffer.tokenizedLineForScreenRow(1).text).toBe 'フ业余爱' + + buffer.setTextInRange([[0, 0], [1, 0]], 'abcd ef유gef业余爱\n') + expect(displayBuffer.tokenizedLineForScreenRow(0).text).toBe 'abcd ef유' + expect(displayBuffer.tokenizedLineForScreenRow(1).text).toBe 'gef业余爱' + describe "when there is no whitespace before the boundary", -> - it "wraps the line exactly at the boundary since there's no more graceful place to wrap it", -> + it "wraps the line at the first CJK character before the boundary", -> + buffer.setTextInRange([[0, 0], [1, 0]], '私たちのabcdefghij\n') + displayBuffer.setEditorWidthInChars(10) + expect(displayBuffer.tokenizedLineForScreenRow(0).text).toBe '私たちの' + expect(displayBuffer.tokenizedLineForScreenRow(1).text).toBe 'abcdefghij' + + it "wraps the line exactly at the boundary when no CJK character is found, since there's no more graceful place to wrap it", -> buffer.setTextInRange([[0, 0], [1, 0]], 'abcdefghijklmnopqrstuvwxyz\n') displayBuffer.setEditorWidthInChars(10) expect(displayBuffer.tokenizedLineForScreenRow(0).text).toBe 'abcdefghij' diff --git a/spec/file-system-blob-store-spec.coffee b/spec/file-system-blob-store-spec.coffee index c947259f64b..c1cc29449a0 100644 --- a/spec/file-system-blob-store-spec.coffee +++ b/spec/file-system-blob-store-spec.coffee @@ -9,61 +9,71 @@ describe "FileSystemBlobStore", -> blobStore = FileSystemBlobStore.load(storageDirectory) it "is empty when the file doesn't exist", -> - expect(blobStore.get("foo")).toBeUndefined() - expect(blobStore.get("bar")).toBeUndefined() + expect(blobStore.get("foo", "invalidation-key-1")).toBeUndefined() + expect(blobStore.get("bar", "invalidation-key-2")).toBeUndefined() it "allows to read and write buffers from/to memory without persisting them", -> - blobStore.set("foo", new Buffer("foo")) - blobStore.set("bar", new Buffer("bar")) + blobStore.set("foo", "invalidation-key-1", new Buffer("foo")) + blobStore.set("bar", "invalidation-key-2", new Buffer("bar")) - expect(blobStore.get("foo")).toEqual(new Buffer("foo")) - expect(blobStore.get("bar")).toEqual(new Buffer("bar")) + expect(blobStore.get("foo", "invalidation-key-1")).toEqual(new Buffer("foo")) + expect(blobStore.get("bar", "invalidation-key-2")).toEqual(new Buffer("bar")) + + expect(blobStore.get("foo", "unexisting-key")).toBeUndefined() + expect(blobStore.get("bar", "unexisting-key")).toBeUndefined() it "persists buffers when saved and retrieves them on load, giving priority to in-memory ones", -> - blobStore.set("foo", new Buffer("foo")) - blobStore.set("bar", new Buffer("bar")) + blobStore.set("foo", "invalidation-key-1", new Buffer("foo")) + blobStore.set("bar", "invalidation-key-2", new Buffer("bar")) blobStore.save() blobStore = FileSystemBlobStore.load(storageDirectory) - expect(blobStore.get("foo")).toEqual(new Buffer("foo")) - expect(blobStore.get("bar")).toEqual(new Buffer("bar")) + expect(blobStore.get("foo", "invalidation-key-1")).toEqual(new Buffer("foo")) + expect(blobStore.get("bar", "invalidation-key-2")).toEqual(new Buffer("bar")) + expect(blobStore.get("foo", "unexisting-key")).toBeUndefined() + expect(blobStore.get("bar", "unexisting-key")).toBeUndefined() - blobStore.set("foo", new Buffer("changed")) + blobStore.set("foo", "new-key", new Buffer("changed")) - expect(blobStore.get("foo")).toEqual(new Buffer("changed")) + expect(blobStore.get("foo", "new-key")).toEqual(new Buffer("changed")) + expect(blobStore.get("foo", "invalidation-key-1")).toBeUndefined() it "persists both in-memory and previously stored buffers when saved", -> - blobStore.set("foo", new Buffer("foo")) - blobStore.set("bar", new Buffer("bar")) + blobStore.set("foo", "invalidation-key-1", new Buffer("foo")) + blobStore.set("bar", "invalidation-key-2", new Buffer("bar")) blobStore.save() blobStore = FileSystemBlobStore.load(storageDirectory) - blobStore.set("bar", new Buffer("changed")) - blobStore.set("qux", new Buffer("qux")) + blobStore.set("bar", "invalidation-key-3", new Buffer("changed")) + blobStore.set("qux", "invalidation-key-4", new Buffer("qux")) blobStore.save() blobStore = FileSystemBlobStore.load(storageDirectory) - expect(blobStore.get("foo")).toEqual(new Buffer("foo")) - expect(blobStore.get("bar")).toEqual(new Buffer("changed")) - expect(blobStore.get("qux")).toEqual(new Buffer("qux")) + expect(blobStore.get("foo", "invalidation-key-1")).toEqual(new Buffer("foo")) + expect(blobStore.get("bar", "invalidation-key-3")).toEqual(new Buffer("changed")) + expect(blobStore.get("qux", "invalidation-key-4")).toEqual(new Buffer("qux")) + expect(blobStore.get("foo", "unexisting-key")).toBeUndefined() + expect(blobStore.get("bar", "invalidation-key-2")).toBeUndefined() + expect(blobStore.get("qux", "unexisting-key")).toBeUndefined() it "allows to delete keys from both memory and stored buffers", -> - blobStore.set("a", new Buffer("a")) - blobStore.set("b", new Buffer("b")) + blobStore.set("a", "invalidation-key-1", new Buffer("a")) + blobStore.set("b", "invalidation-key-2", new Buffer("b")) blobStore.save() blobStore = FileSystemBlobStore.load(storageDirectory) - blobStore.set("b", new Buffer("b")) - blobStore.set("c", new Buffer("c")) + blobStore.set("b", "invalidation-key-3", new Buffer("b")) + blobStore.set("c", "invalidation-key-4", new Buffer("c")) blobStore.delete("b") blobStore.delete("c") blobStore.save() blobStore = FileSystemBlobStore.load(storageDirectory) - expect(blobStore.get("a")).toEqual(new Buffer("a")) - expect(blobStore.get("b")).toBeUndefined() - expect(blobStore.get("c")).toBeUndefined() + expect(blobStore.get("a", "invalidation-key-1")).toEqual(new Buffer("a")) + expect(blobStore.get("b", "invalidation-key-2")).toBeUndefined() + expect(blobStore.get("b", "invalidation-key-3")).toBeUndefined() + expect(blobStore.get("c", "invalidation-key-4")).toBeUndefined() diff --git a/spec/fixtures/native-cache/file-4.js b/spec/fixtures/native-cache/file-4.js new file mode 100644 index 00000000000..1b8fd4e1545 --- /dev/null +++ b/spec/fixtures/native-cache/file-4.js @@ -0,0 +1 @@ +module.exports = function () { return "file-4" } diff --git a/spec/native-compile-cache-spec.coffee b/spec/native-compile-cache-spec.coffee index dd720e84d67..9d6cb89b4b8 100644 --- a/spec/native-compile-cache-spec.coffee +++ b/spec/native-compile-cache-spec.coffee @@ -1,3 +1,7 @@ +fs = require 'fs' +path = require 'path' +Module = require 'module' + describe "NativeCompileCache", -> nativeCompileCache = require '../src/native-compile-cache' [fakeCacheStore, cachedFiles] = [] @@ -5,39 +9,92 @@ describe "NativeCompileCache", -> beforeEach -> cachedFiles = [] fakeCacheStore = jasmine.createSpyObj("cache store", ["set", "get", "has", "delete"]) + fakeCacheStore.has.andCallFake (cacheKey, invalidationKey) -> + fakeCacheStore.get(cacheKey, invalidationKey)? + fakeCacheStore.get.andCallFake (cacheKey, invalidationKey) -> + for entry in cachedFiles by -1 + continue if entry.cacheKey isnt cacheKey + continue if entry.invalidationKey isnt invalidationKey + return entry.cacheBuffer + return + fakeCacheStore.set.andCallFake (cacheKey, invalidationKey, cacheBuffer) -> + cachedFiles.push({cacheKey, invalidationKey, cacheBuffer}) + nativeCompileCache.setCacheStore(fakeCacheStore) + nativeCompileCache.setV8Version("a-v8-version") nativeCompileCache.install() it "writes and reads from the cache storage when requiring files", -> - fakeCacheStore.has.andReturn(false) - fakeCacheStore.set.andCallFake (filename, cacheBuffer) -> - cachedFiles.push({filename, cacheBuffer}) - fn1 = require('./fixtures/native-cache/file-1') fn2 = require('./fixtures/native-cache/file-2') expect(cachedFiles.length).toBe(2) - expect(cachedFiles[0].filename).toBe(require.resolve('./fixtures/native-cache/file-1')) + expect(cachedFiles[0].cacheKey).toBe(require.resolve('./fixtures/native-cache/file-1')) expect(cachedFiles[0].cacheBuffer).toBeInstanceOf(Uint8Array) expect(cachedFiles[0].cacheBuffer.length).toBeGreaterThan(0) expect(fn1()).toBe(1) - expect(cachedFiles[1].filename).toBe(require.resolve('./fixtures/native-cache/file-2')) + expect(cachedFiles[1].cacheKey).toBe(require.resolve('./fixtures/native-cache/file-2')) expect(cachedFiles[1].cacheBuffer).toBeInstanceOf(Uint8Array) expect(cachedFiles[1].cacheBuffer.length).toBeGreaterThan(0) expect(fn2()).toBe(2) - fakeCacheStore.has.andReturn(true) - fakeCacheStore.get.andReturn(cachedFiles[0].cacheBuffer) - fakeCacheStore.set.reset() - + delete Module._cache[require.resolve('./fixtures/native-cache/file-1')] fn1 = require('./fixtures/native-cache/file-1') - - expect(fakeCacheStore.set).not.toHaveBeenCalled() + expect(cachedFiles.length).toBe(2) expect(fn1()).toBe(1) - it "deletes previously cached code when the cache is not valid", -> + describe "when v8 version changes", -> + it "updates the cache of previously required files", -> + nativeCompileCache.setV8Version("version-1") + fn4 = require('./fixtures/native-cache/file-4') + + expect(cachedFiles.length).toBe(1) + expect(cachedFiles[0].cacheKey).toBe(require.resolve('./fixtures/native-cache/file-4')) + expect(cachedFiles[0].cacheBuffer).toBeInstanceOf(Uint8Array) + expect(cachedFiles[0].cacheBuffer.length).toBeGreaterThan(0) + expect(fn4()).toBe("file-4") + + nativeCompileCache.setV8Version("version-2") + delete Module._cache[require.resolve('./fixtures/native-cache/file-4')] + fn4 = require('./fixtures/native-cache/file-4') + + expect(cachedFiles.length).toBe(2) + expect(cachedFiles[1].cacheKey).toBe(require.resolve('./fixtures/native-cache/file-4')) + expect(cachedFiles[1].invalidationKey).not.toBe(cachedFiles[0].invalidationKey) + expect(cachedFiles[1].cacheBuffer).toBeInstanceOf(Uint8Array) + expect(cachedFiles[1].cacheBuffer.length).toBeGreaterThan(0) + + describe "when a previously required and cached file changes", -> + beforeEach -> + fs.writeFileSync path.resolve('./spec/fixtures/native-cache/file-5'), """ + module.exports = function () { return "file-5" } + """ + + afterEach -> + fs.unlinkSync path.resolve('./spec/fixtures/native-cache/file-5') + + it "removes it from the store and re-inserts it with the new cache", -> + fn5 = require('./fixtures/native-cache/file-5') + + expect(cachedFiles.length).toBe(1) + expect(cachedFiles[0].cacheKey).toBe(require.resolve('./fixtures/native-cache/file-5')) + expect(cachedFiles[0].cacheBuffer).toBeInstanceOf(Uint8Array) + expect(cachedFiles[0].cacheBuffer.length).toBeGreaterThan(0) + expect(fn5()).toBe("file-5") + + delete Module._cache[require.resolve('./fixtures/native-cache/file-5')] + fs.appendFileSync(require.resolve('./fixtures/native-cache/file-5'), "\n\n") + fn5 = require('./fixtures/native-cache/file-5') + + expect(cachedFiles.length).toBe(2) + expect(cachedFiles[1].cacheKey).toBe(require.resolve('./fixtures/native-cache/file-5')) + expect(cachedFiles[1].invalidationKey).not.toBe(cachedFiles[0].invalidationKey) + expect(cachedFiles[1].cacheBuffer).toBeInstanceOf(Uint8Array) + expect(cachedFiles[1].cacheBuffer.length).toBeGreaterThan(0) + + it "deletes previously cached code when the cache is an invalid file", -> fakeCacheStore.has.andReturn(true) fakeCacheStore.get.andCallFake -> new Buffer("an invalid cache") diff --git a/spec/text-editor-presenter-spec.coffee b/spec/text-editor-presenter-spec.coffee index effa579b166..380f1a5fcb7 100644 --- a/spec/text-editor-presenter-spec.coffee +++ b/spec/text-editor-presenter-spec.coffee @@ -136,14 +136,6 @@ describe "TextEditorPresenter", -> # clearing additional rows won't trigger a state update expectNoStateUpdate presenter, -> presenter.clearScreenRowsToMeasure() - expect(stateFn(presenter).tiles[0]).toBeDefined() - expect(stateFn(presenter).tiles[2]).toBeDefined() - expect(stateFn(presenter).tiles[4]).toBeDefined() - expect(stateFn(presenter).tiles[6]).toBeDefined() - expect(stateFn(presenter).tiles[8]).toBeUndefined() - expect(stateFn(presenter).tiles[10]).toBeDefined() - expect(stateFn(presenter).tiles[12]).toBeDefined() - # when another change triggers a state update we remove useless lines expectStateUpdate presenter, -> presenter.setScrollTop(1) @@ -625,23 +617,6 @@ describe "TextEditorPresenter", -> expect(getState(presenter).hiddenInput.width).toBe 2 describe ".content", -> - describe ".scrollingVertically", -> - it "is true for ::stoppedScrollingDelay milliseconds following a changes to ::scrollTop", -> - presenter = buildPresenter(scrollTop: 10, stoppedScrollingDelay: 200, explicitHeight: 100) - expect(getState(presenter).content.scrollingVertically).toBe true - advanceClock(300) - expect(getState(presenter).content.scrollingVertically).toBe false - expectStateUpdate presenter, -> presenter.setScrollTop(0) - expect(getState(presenter).content.scrollingVertically).toBe true - advanceClock(100) - expect(getState(presenter).content.scrollingVertically).toBe true - presenter.setScrollTop(10) - getState(presenter) # commits scroll position - advanceClock(100) - expect(getState(presenter).content.scrollingVertically).toBe true - expectStateUpdate presenter, -> advanceClock(100) - expect(getState(presenter).content.scrollingVertically).toBe false - describe ".maxHeight", -> it "changes based on boundingClientRect", -> presenter = buildPresenter(scrollTop: 0, lineHeight: 10) @@ -807,6 +782,11 @@ describe "TextEditorPresenter", -> getState(presenter) # commits scroll position expect(editor.getFirstVisibleScreenRow()).toBe 6 + it "updates when the model's scroll position is changed directly", -> + presenter = buildPresenter(scrollTop: 0, explicitHeight: 20, horizontalScrollbarHeight: 10, lineHeight: 10) + expectStateUpdate presenter, -> editor.setFirstVisibleScreenRow(1) + expect(getState(presenter).content.scrollTop).toBe 10 + it "reassigns the scrollTop if it exceeds the max possible value after lines are removed", -> presenter = buildPresenter(scrollTop: 80, lineHeight: 10, explicitHeight: 50, horizontalScrollbarHeight: 0) expect(getState(presenter).content.scrollTop).toBe(80) diff --git a/spec/text-utils-spec.coffee b/spec/text-utils-spec.coffee index dd528b37e32..aa36c5003dd 100644 --- a/spec/text-utils-spec.coffee +++ b/spec/text-utils-spec.coffee @@ -74,3 +74,23 @@ describe 'text utilities', -> expect(textUtils.isKoreanCharacter("ㄼ")).toBe(true) expect(textUtils.isKoreanCharacter("O")).toBe(false) + + describe ".isCJKCharacter(character)", -> + it "returns true when the character is either a korean, half-width or double-width character", -> + expect(textUtils.isCJKCharacter("我")).toBe(true) + expect(textUtils.isCJKCharacter("私")).toBe(true) + expect(textUtils.isCJKCharacter("B")).toBe(true) + expect(textUtils.isCJKCharacter(",")).toBe(true) + expect(textUtils.isCJKCharacter("¢")).toBe(true) + expect(textUtils.isCJKCharacter("ハ")).toBe(true) + expect(textUtils.isCJKCharacter("ヒ")).toBe(true) + expect(textUtils.isCJKCharacter("ᆲ")).toBe(true) + expect(textUtils.isCJKCharacter("■")).toBe(true) + expect(textUtils.isCJKCharacter("우")).toBe(true) + expect(textUtils.isCJKCharacter("가")).toBe(true) + expect(textUtils.isCJKCharacter("ㅢ")).toBe(true) + expect(textUtils.isCJKCharacter("ㄼ")).toBe(true) + + expect(textUtils.isDoubleWidthCharacter("a")).toBe(false) + expect(textUtils.isDoubleWidthCharacter("O")).toBe(false) + expect(textUtils.isDoubleWidthCharacter("z")).toBe(false) diff --git a/spec/tokenized-buffer-spec.coffee b/spec/tokenized-buffer-spec.coffee index 76314681c37..9c53b3c07fb 100644 --- a/spec/tokenized-buffer-spec.coffee +++ b/spec/tokenized-buffer-spec.coffee @@ -26,8 +26,13 @@ describe "TokenizedBuffer", -> describe "serialization", -> describe "when the underlying buffer has a path", -> - it "deserializes it searching among the buffers in the current project", -> + beforeEach -> buffer = atom.project.bufferForPathSync('sample.js') + + waitsForPromise -> + atom.packages.activatePackage('language-coffee-script') + + it "deserializes it searching among the buffers in the current project", -> tokenizedBufferA = new TokenizedBuffer({ buffer, config: atom.config, grammarRegistry: atom.grammars, packageManager: atom.packages, assert: atom.assert }) @@ -38,10 +43,25 @@ describe "TokenizedBuffer", -> expect(tokenizedBufferB.buffer).toBe(tokenizedBufferA.buffer) + it "does not serialize / deserialize the current grammar", -> + tokenizedBufferA = new TokenizedBuffer({ + buffer, config: atom.config, grammarRegistry: atom.grammars, packageManager: atom.packages, assert: atom.assert + }) + autoSelectedGrammar = tokenizedBufferA.grammar + + tokenizedBufferA.setGrammar(atom.grammars.grammarForScopeName('source.coffee')) + tokenizedBufferB = TokenizedBuffer.deserialize( + JSON.parse(JSON.stringify(tokenizedBufferA.serialize())), + atom + ) + + expect(tokenizedBufferB.grammar).toBe(atom.grammars.grammarForScopeName('source.js')) + describe "when the underlying buffer has no path", -> - it "deserializes it searching among the buffers in the current project", -> + beforeEach -> buffer = atom.project.bufferForPathSync(null) + it "deserializes it searching among the buffers in the current project", -> tokenizedBufferA = new TokenizedBuffer({ buffer, config: atom.config, grammarRegistry: atom.grammars, packageManager: atom.packages, assert: atom.assert }) @@ -52,6 +72,38 @@ describe "TokenizedBuffer", -> expect(tokenizedBufferB.buffer).toBe(tokenizedBufferA.buffer) + it "deserializes the previously selected grammar as soon as it's added when not available in the grammar registry", -> + tokenizedBufferA = new TokenizedBuffer({ + buffer, config: atom.config, grammarRegistry: atom.grammars, packageManager: atom.packages, assert: atom.assert + }) + + tokenizedBufferA.setGrammar(atom.grammars.grammarForScopeName("source.js")) + atom.grammars.removeGrammarForScopeName(tokenizedBufferA.grammar.scopeName) + tokenizedBufferB = TokenizedBuffer.deserialize( + JSON.parse(JSON.stringify(tokenizedBufferA.serialize())), + atom + ) + + expect(tokenizedBufferB.grammar).not.toBeFalsy() + expect(tokenizedBufferB.grammar).not.toBe(tokenizedBufferA.grammar) + + atom.grammars.addGrammar(tokenizedBufferA.grammar) + + expect(tokenizedBufferB.grammar).toBe(tokenizedBufferA.grammar) + + it "deserializes the previously selected grammar on construction when available in the grammar registry", -> + tokenizedBufferA = new TokenizedBuffer({ + buffer, config: atom.config, grammarRegistry: atom.grammars, packageManager: atom.packages, assert: atom.assert + }) + + tokenizedBufferA.setGrammar(atom.grammars.grammarForScopeName("source.js")) + tokenizedBufferB = TokenizedBuffer.deserialize( + JSON.parse(JSON.stringify(tokenizedBufferA.serialize())), + atom + ) + + expect(tokenizedBufferB.grammar).toBe(tokenizedBufferA.grammar) + describe "when the buffer is destroyed", -> beforeEach -> buffer = atom.project.bufferForPathSync('sample.js') diff --git a/src/compile-cache.js b/src/compile-cache.js index 79984ccee99..32fa32c340e 100644 --- a/src/compile-cache.js +++ b/src/compile-cache.js @@ -1,5 +1,10 @@ 'use strict' +// For now, we're not using babel or ES6 features like `let` and `const` in +// this file, because `apm` requires this file directly in order to pre-warm +// Atom's compile-cache when installing or updating packages, using an older +// version of node.js + var path = require('path') var fs = require('fs-plus') var CSON = null @@ -159,8 +164,7 @@ require('source-map-support').install({ }) var prepareStackTraceWithSourceMapping = Error.prepareStackTrace - -let prepareStackTrace = prepareStackTraceWithSourceMapping +var prepareStackTrace = prepareStackTraceWithSourceMapping function prepareStackTraceWithRawStackAssignment (error, frames) { if (error.rawStack) { // avoid infinite recursion diff --git a/src/file-system-blob-store.js b/src/file-system-blob-store.js index fc6bdddf3c2..e565a8857b6 100644 --- a/src/file-system-blob-store.js +++ b/src/file-system-blob-store.js @@ -13,8 +13,10 @@ class FileSystemBlobStore { constructor (directory) { this.inMemoryBlobs = new Map() + this.invalidationKeys = {} this.blobFilename = path.join(directory, 'BLOB') this.blobMapFilename = path.join(directory, 'MAP') + this.invalidationKeysFilename = path.join(directory, 'INVKEYS') this.lockFilename = path.join(directory, 'LOCK') this.storedBlob = new Buffer(0) this.storedBlobMap = {} @@ -27,14 +29,19 @@ class FileSystemBlobStore { if (!fs.existsSync(this.blobFilename)) { return } + if (!fs.existsSync(this.invalidationKeysFilename)) { + return + } this.storedBlob = fs.readFileSync(this.blobFilename) this.storedBlobMap = JSON.parse(fs.readFileSync(this.blobMapFilename)) + this.invalidationKeys = JSON.parse(fs.readFileSync(this.invalidationKeysFilename)) } save () { let dump = this.getDump() let blobToStore = Buffer.concat(dump[0]) let mapToStore = JSON.stringify(dump[1]) + let invalidationKeysToStore = JSON.stringify(this.invalidationKeys) let acquiredLock = false try { @@ -43,6 +50,7 @@ class FileSystemBlobStore { fs.writeFileSync(this.blobFilename, blobToStore) fs.writeFileSync(this.blobMapFilename, mapToStore) + fs.writeFileSync(this.invalidationKeysFilename, invalidationKeysToStore) } catch (error) { // Swallow the exception silently only if we fail to acquire the lock. if (error.code !== 'EEXIST') { @@ -55,15 +63,20 @@ class FileSystemBlobStore { } } - has (key) { - return this.inMemoryBlobs.hasOwnProperty(key) || this.storedBlobMap.hasOwnProperty(key) + has (key, invalidationKey) { + let containsKey = this.inMemoryBlobs.has(key) || this.storedBlobMap.hasOwnProperty(key) + let isValid = this.invalidationKeys[key] === invalidationKey + return containsKey && isValid } - get (key) { - return this.getFromMemory(key) || this.getFromStorage(key) + get (key, invalidationKey) { + if (this.has(key, invalidationKey)) { + return this.getFromMemory(key) || this.getFromStorage(key) + } } - set (key, buffer) { + set (key, invalidationKey, buffer) { + this.invalidationKeys[key] = invalidationKey return this.inMemoryBlobs.set(key, buffer) } diff --git a/src/native-compile-cache.js b/src/native-compile-cache.js index a930466a552..50fa71fc3aa 100644 --- a/src/native-compile-cache.js +++ b/src/native-compile-cache.js @@ -3,6 +3,11 @@ const Module = require('module') const path = require('path') const cachedVm = require('cached-run-in-this-context') +const crypto = require('crypto') + +function computeHash (contents) { + return crypto.createHash('sha1').update(contents, 'utf8').digest('hex') +} class NativeCompileCache { constructor () { @@ -14,6 +19,10 @@ class NativeCompileCache { this.cacheStore = store } + setV8Version (v8Version) { + this.v8Version = v8Version.toString() + } + install () { this.savePreviousModuleCompile() this.overrideModuleCompile() @@ -28,20 +37,20 @@ class NativeCompileCache { } overrideModuleCompile () { - let cacheStore = this.cacheStore + let self = this let resolvedArgv = null // Here we override Node's module.js // (https://github.com/atom/node/blob/atom/lib/module.js#L378), changing // only the bits that affect compilation in order to use the cached one. Module.prototype._compile = function (content, filename) { - let self = this + let moduleSelf = this // remove shebang content = content.replace(/^\#\!.*/, '') function require (path) { - return self.require(path) + return moduleSelf.require(path) } require.resolve = function (request) { - return Module._resolveFilename(request, self) + return Module._resolveFilename(request, moduleSelf) } require.main = process.mainModule @@ -54,18 +63,20 @@ class NativeCompileCache { // create wrapper function let wrapper = Module.wrap(content) + let cacheKey = filename + let invalidationKey = computeHash(wrapper + self.v8Version) let compiledWrapper = null - if (cacheStore.has(filename)) { - let buffer = cacheStore.get(filename) + if (self.cacheStore.has(cacheKey, invalidationKey)) { + let buffer = self.cacheStore.get(cacheKey, invalidationKey) let compilationResult = cachedVm.runInThisContextCached(wrapper, filename, buffer) compiledWrapper = compilationResult.result if (compilationResult.wasRejected) { - cacheStore.delete(filename) + self.cacheStore.delete(cacheKey) } } else { let compilationResult = cachedVm.runInThisContext(wrapper, filename) if (compilationResult.cacheBuffer) { - cacheStore.set(filename, compilationResult.cacheBuffer) + self.cacheStore.set(cacheKey, invalidationKey, compilationResult.cacheBuffer) } compiledWrapper = compilationResult.result } @@ -88,8 +99,8 @@ class NativeCompileCache { global.v8debug.Debug.setBreakPoint(compiledWrapper, 0, 0) } } - let args = [self.exports, require, self, filename, dirname, process, global] - return compiledWrapper.apply(self.exports, args) + let args = [moduleSelf.exports, require, moduleSelf, filename, dirname, process, global] + return compiledWrapper.apply(moduleSelf.exports, args) } } diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index 5cfaeebcfdc..1912f4c2ee8 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -86,11 +86,7 @@ class TextEditorPresenter @fetchDecorations() @updateLineDecorations() - if @shouldUpdateLinesState or @shouldUpdateLineNumbersState - @updateTilesState() - @shouldUpdateLinesState = false - @shouldUpdateLineNumbersState = false - @shouldUpdateTilesState = true + @updateTilesState() @updating = false @state @@ -104,105 +100,47 @@ class TextEditorPresenter @clearPendingScrollPosition() @updateRowsPerPage() - @updateFocusedState() if @shouldUpdateFocusedState - @updateHeightState() if @shouldUpdateHeightState - @updateVerticalScrollState() if @shouldUpdateVerticalScrollState - @updateHorizontalScrollState() if @shouldUpdateHorizontalScrollState - @updateScrollbarsState() if @shouldUpdateScrollbarsState - @updateHiddenInputState() if @shouldUpdateHiddenInputState - @updateContentState() if @shouldUpdateContentState + @updateFocusedState() + @updateHeightState() + @updateVerticalScrollState() + @updateHorizontalScrollState() + @updateScrollbarsState() + @updateHiddenInputState() + @updateContentState() @updateHighlightDecorations() if @shouldUpdateDecorations - @updateTilesState() if @shouldUpdateTilesState - @updateCursorsState() if @shouldUpdateCursorsState - @updateOverlaysState() if @shouldUpdateOverlaysState - @updateLineNumberGutterState() if @shouldUpdateLineNumberGutterState - @updateGutterOrderState() if @shouldUpdateGutterOrderState - @updateCustomGutterDecorationState() if @shouldUpdateCustomGutterDecorationState + @updateTilesState() + @updateCursorsState() + @updateOverlaysState() + @updateLineNumberGutterState() + @updateGutterOrderState() + @updateCustomGutterDecorationState() @updating = false @resetTrackedUpdates() @state resetTrackedUpdates: -> - @shouldUpdateFocusedState = false - @shouldUpdateHeightState = false - @shouldUpdateVerticalScrollState = false - @shouldUpdateHorizontalScrollState = false - @shouldUpdateScrollbarsState = false - @shouldUpdateHiddenInputState = false - @shouldUpdateContentState = false @shouldUpdateDecorations = false - @shouldUpdateLinesState = false - @shouldUpdateTilesState = false - @shouldUpdateCursorsState = false - @shouldUpdateOverlaysState = false - @shouldUpdateLineNumberGutterState = false - @shouldUpdateLineNumbersState = false - @shouldUpdateGutterOrderState = false - @shouldUpdateCustomGutterDecorationState = false invalidateState: -> - @shouldUpdateFocusedState = true - @shouldUpdateHeightState = true - @shouldUpdateVerticalScrollState = true - @shouldUpdateHorizontalScrollState = true - @shouldUpdateScrollbarsState = true - @shouldUpdateHiddenInputState = true - @shouldUpdateContentState = true @shouldUpdateDecorations = true - @shouldUpdateLinesState = true - @shouldUpdateTilesState = true - @shouldUpdateCursorsState = true - @shouldUpdateOverlaysState = true - @shouldUpdateLineNumberGutterState = true - @shouldUpdateLineNumbersState = true - @shouldUpdateGutterOrderState = true - @shouldUpdateCustomGutterDecorationState = true observeModel: -> @disposables.add @model.onDidChange => - @shouldUpdateHeightState = true - @shouldUpdateVerticalScrollState = true - @shouldUpdateHorizontalScrollState = true - @shouldUpdateScrollbarsState = true - @shouldUpdateContentState = true @shouldUpdateDecorations = true - @shouldUpdateCursorsState = true - @shouldUpdateLinesState = true - @shouldUpdateLineNumberGutterState = true - @shouldUpdateLineNumbersState = true - @shouldUpdateGutterOrderState = true - @shouldUpdateCustomGutterDecorationState = true @emitDidUpdateState() @disposables.add @model.onDidUpdateDecorations => - @shouldUpdateLinesState = true - @shouldUpdateLineNumbersState = true @shouldUpdateDecorations = true - @shouldUpdateOverlaysState = true - @shouldUpdateCustomGutterDecorationState = true @emitDidUpdateState() @disposables.add @model.onDidChangeGrammar(@didChangeGrammar.bind(this)) - @disposables.add @model.onDidChangePlaceholderText => - @shouldUpdateContentState = true - @emitDidUpdateState() - + @disposables.add @model.onDidChangePlaceholderText(@emitDidUpdateState.bind(this)) @disposables.add @model.onDidChangeMini => - @shouldUpdateScrollbarsState = true - @shouldUpdateContentState = true @shouldUpdateDecorations = true - @shouldUpdateLinesState = true - @shouldUpdateLineNumberGutterState = true - @shouldUpdateLineNumbersState = true - @shouldUpdateGutterOrderState = true - @shouldUpdateCustomGutterDecorationState = true @emitDidUpdateState() - @disposables.add @model.onDidChangeLineNumberGutterVisible => - @shouldUpdateLineNumberGutterState = true - @shouldUpdateGutterOrderState = true - @emitDidUpdateState() + @disposables.add @model.onDidChangeLineNumberGutterVisible(@emitDidUpdateState.bind(this)) @disposables.add @model.onDidAddCursor(@didAddCursor.bind(this)) @disposables.add @model.onDidRequestAutoscroll(@requestAutoscroll.bind(this)) @@ -227,29 +165,19 @@ class TextEditorPresenter @configDisposables.add @config.onDidChange 'editor.showIndentGuide', configParams, ({newValue}) => @showIndentGuide = newValue - @shouldUpdateContentState = true @emitDidUpdateState() @configDisposables.add @config.onDidChange 'editor.scrollPastEnd', configParams, ({newValue}) => @scrollPastEnd = newValue - @shouldUpdateVerticalScrollState = true - @shouldUpdateScrollbarsState = true @updateScrollHeight() @emitDidUpdateState() @configDisposables.add @config.onDidChange 'editor.showLineNumbers', configParams, ({newValue}) => @showLineNumbers = newValue - @shouldUpdateLineNumberGutterState = true - @shouldUpdateGutterOrderState = true - @emitDidUpdateState() didChangeGrammar: -> @observeConfig() - @shouldUpdateContentState = true - @shouldUpdateLineNumberGutterState = true - @shouldUpdateGutterOrderState = true - @emitDidUpdateState() buildState: -> @@ -383,8 +311,6 @@ class TextEditorPresenter return if not screenRows? or screenRows.length is 0 @screenRowsToMeasure = screenRows - @shouldUpdateLinesState = true - @shouldUpdateLineNumbersState = true @shouldUpdateDecorations = true clearScreenRowsToMeasure: -> @@ -425,8 +351,8 @@ class TextEditorPresenter gutterTile.display = "block" gutterTile.zIndex = zIndex - @updateLinesState(tile, rowsWithinTile) if @shouldUpdateLinesState - @updateLineNumbersState(gutterTile, rowsWithinTile) if @shouldUpdateLineNumbersState + @updateLinesState(tile, rowsWithinTile) + @updateLineNumbersState(gutterTile, rowsWithinTile) visibleTiles[tileStartRow] = true zIndex++ @@ -551,24 +477,15 @@ class TextEditorPresenter didAddGutter: (gutter) -> gutterDisposables = new CompositeDisposable - gutterDisposables.add gutter.onDidChangeVisible => - @shouldUpdateGutterOrderState = true - @shouldUpdateCustomGutterDecorationState = true - - @emitDidUpdateState() + gutterDisposables.add gutter.onDidChangeVisible => @emitDidUpdateState() gutterDisposables.add gutter.onDidDestroy => @disposables.remove(gutterDisposables) gutterDisposables.dispose() - @shouldUpdateGutterOrderState = true - @emitDidUpdateState() # It is not necessary to @updateCustomGutterDecorationState here. # The destroyed gutter will be removed from the list of gutters in @state, # and thus will be removed from the DOM. @disposables.add(gutterDisposables) - @shouldUpdateGutterOrderState = true - @shouldUpdateCustomGutterDecorationState = true - @emitDidUpdateState() updateGutterOrderState: -> @@ -861,26 +778,15 @@ class TextEditorPresenter @startBlinkingCursors() else @stopBlinkingCursors(false) - @shouldUpdateFocusedState = true - @shouldUpdateHiddenInputState = true - @emitDidUpdateState() - setScrollTop: (scrollTop, overrideScroll=true) -> + setScrollTop: (scrollTop) -> return unless scrollTop? - @pendingScrollLogicalPosition = null if overrideScroll + @pendingScrollLogicalPosition = null @pendingScrollTop = scrollTop - @shouldUpdateVerticalScrollState = true - @shouldUpdateHiddenInputState = true @shouldUpdateDecorations = true - @shouldUpdateLinesState = true - @shouldUpdateCursorsState = true - @shouldUpdateLineNumbersState = true - @shouldUpdateCustomGutterDecorationState = true - @shouldUpdateOverlaysState = true - @emitDidUpdateState() getScrollTop: -> @@ -894,32 +800,19 @@ class TextEditorPresenter clearTimeout(@stoppedScrollingTimeoutId) @stoppedScrollingTimeoutId = null @stoppedScrollingTimeoutId = setTimeout(@didStopScrolling.bind(this), @stoppedScrollingDelay) - @state.content.scrollingVertically = true - @emitDidUpdateState() didStopScrolling: -> - @state.content.scrollingVertically = false if @mouseWheelScreenRow? @mouseWheelScreenRow = null - @shouldUpdateLinesState = true - @shouldUpdateLineNumbersState = true - @shouldUpdateCustomGutterDecorationState = true @emitDidUpdateState() - setScrollLeft: (scrollLeft, overrideScroll=true) -> + setScrollLeft: (scrollLeft) -> return unless scrollLeft? - @pendingScrollLogicalPosition = null if overrideScroll + @pendingScrollLogicalPosition = null @pendingScrollLeft = scrollLeft - @shouldUpdateHorizontalScrollState = true - @shouldUpdateHiddenInputState = true - @shouldUpdateCursorsState = true - @shouldUpdateOverlaysState = true - @shouldUpdateDecorations = true - @shouldUpdateLinesState = true - @emitDidUpdateState() getScrollLeft: -> @@ -941,13 +834,13 @@ class TextEditorPresenter @contentFrameWidth - @verticalScrollbarWidth getScrollBottom: -> @getScrollTop() + @getClientHeight() - setScrollBottom: (scrollBottom, overrideScroll) -> - @setScrollTop(scrollBottom - @getClientHeight(), overrideScroll) + setScrollBottom: (scrollBottom) -> + @setScrollTop(scrollBottom - @getClientHeight()) @getScrollBottom() getScrollRight: -> @getScrollLeft() + @getClientWidth() - setScrollRight: (scrollRight, overrideScroll) -> - @setScrollLeft(scrollRight - @getClientWidth(), overrideScroll) + setScrollRight: (scrollRight) -> + @setScrollLeft(scrollRight - @getClientWidth()) @getScrollRight() getScrollHeight: -> @@ -967,43 +860,24 @@ class TextEditorPresenter unless @measuredHorizontalScrollbarHeight is horizontalScrollbarHeight oldHorizontalScrollbarHeight = @measuredHorizontalScrollbarHeight @measuredHorizontalScrollbarHeight = horizontalScrollbarHeight - @shouldUpdateScrollbarsState = true - @shouldUpdateVerticalScrollState = true - @shouldUpdateHorizontalScrollState = true - @shouldUpdateCursorsState = true unless oldHorizontalScrollbarHeight? - @emitDidUpdateState() setVerticalScrollbarWidth: (verticalScrollbarWidth) -> unless @measuredVerticalScrollbarWidth is verticalScrollbarWidth oldVerticalScrollbarWidth = @measuredVerticalScrollbarWidth @measuredVerticalScrollbarWidth = verticalScrollbarWidth - @shouldUpdateScrollbarsState = true - @shouldUpdateVerticalScrollState = true - @shouldUpdateHorizontalScrollState = true - @shouldUpdateCursorsState = true unless oldVerticalScrollbarWidth? - @emitDidUpdateState() setAutoHeight: (autoHeight) -> unless @autoHeight is autoHeight @autoHeight = autoHeight - @shouldUpdateHeightState = true - @emitDidUpdateState() setExplicitHeight: (explicitHeight) -> unless @explicitHeight is explicitHeight @explicitHeight = explicitHeight @updateHeight() - @shouldUpdateVerticalScrollState = true - @shouldUpdateScrollbarsState = true @shouldUpdateDecorations = true - @shouldUpdateLinesState = true - @shouldUpdateCursorsState = true - @shouldUpdateLineNumbersState = true - @shouldUpdateCustomGutterDecorationState = true - @emitDidUpdateState() updateHeight: -> @@ -1022,22 +896,12 @@ class TextEditorPresenter @editorWidthInChars = null @updateScrollbarDimensions() @updateClientWidth() - @shouldUpdateVerticalScrollState = true - @shouldUpdateHorizontalScrollState = true - @shouldUpdateScrollbarsState = true - @shouldUpdateContentState = true @shouldUpdateDecorations = true - @shouldUpdateLinesState = true - @shouldUpdateCursorsState = true unless oldContentFrameWidth? - @emitDidUpdateState() setBoundingClientRect: (boundingClientRect) -> unless @clientRectsEqual(@boundingClientRect, boundingClientRect) @boundingClientRect = boundingClientRect - @shouldUpdateOverlaysState = true - @shouldUpdateContentState = true - @emitDidUpdateState() clientRectsEqual: (clientRectA, clientRectB) -> @@ -1051,25 +915,17 @@ class TextEditorPresenter if @windowWidth isnt width or @windowHeight isnt height @windowWidth = width @windowHeight = height - @shouldUpdateOverlaysState = true @emitDidUpdateState() setBackgroundColor: (backgroundColor) -> unless @backgroundColor is backgroundColor @backgroundColor = backgroundColor - @shouldUpdateContentState = true - @shouldUpdateLineNumberGutterState = true - @shouldUpdateGutterOrderState = true - @emitDidUpdateState() setGutterBackgroundColor: (gutterBackgroundColor) -> unless @gutterBackgroundColor is gutterBackgroundColor @gutterBackgroundColor = gutterBackgroundColor - @shouldUpdateLineNumberGutterState = true - @shouldUpdateGutterOrderState = true - @emitDidUpdateState() setGutterWidth: (gutterWidth) -> @@ -1085,18 +941,7 @@ class TextEditorPresenter @lineHeight = lineHeight @restoreScrollTopIfNeeded() @model.setLineHeightInPixels(lineHeight) - @shouldUpdateHeightState = true - @shouldUpdateHorizontalScrollState = true - @shouldUpdateVerticalScrollState = true - @shouldUpdateScrollbarsState = true - @shouldUpdateHiddenInputState = true @shouldUpdateDecorations = true - @shouldUpdateLinesState = true - @shouldUpdateCursorsState = true - @shouldUpdateLineNumbersState = true - @shouldUpdateCustomGutterDecorationState = true - @shouldUpdateOverlaysState = true - @emitDidUpdateState() setMouseWheelScreenRow: (screenRow) -> @@ -1115,16 +960,7 @@ class TextEditorPresenter @characterWidthsChanged() characterWidthsChanged: -> - @shouldUpdateHorizontalScrollState = true - @shouldUpdateVerticalScrollState = true - @shouldUpdateScrollbarsState = true - @shouldUpdateHiddenInputState = true - @shouldUpdateContentState = true @shouldUpdateDecorations = true - @shouldUpdateLinesState = true - @shouldUpdateCursorsState = true - @shouldUpdateOverlaysState = true - @emitDidUpdateState() hasPixelPositionRequirements: -> @@ -1365,20 +1201,16 @@ class TextEditorPresenter overlayState.itemWidth = itemWidth overlayState.itemHeight = itemHeight overlayState.contentMargin = contentMargin - @shouldUpdateOverlaysState = true @emitDidUpdateState() observeCursor: (cursor) -> didChangePositionDisposable = cursor.onDidChangePosition => - @shouldUpdateHiddenInputState = true if cursor.isLastCursor() - @shouldUpdateCursorsState = true @pauseCursorBlinking() @emitDidUpdateState() didChangeVisibilityDisposable = cursor.onDidChangeVisibility => - @shouldUpdateCursorsState = true @emitDidUpdateState() @@ -1386,8 +1218,6 @@ class TextEditorPresenter @disposables.remove(didChangePositionDisposable) @disposables.remove(didChangeVisibilityDisposable) @disposables.remove(didDestroyDisposable) - @shouldUpdateHiddenInputState = true - @shouldUpdateCursorsState = true @emitDidUpdateState() @@ -1397,8 +1227,6 @@ class TextEditorPresenter didAddCursor: (cursor) -> @observeCursor(cursor) - @shouldUpdateHiddenInputState = true - @shouldUpdateCursorsState = true @pauseCursorBlinking() @emitDidUpdateState() @@ -1432,22 +1260,11 @@ class TextEditorPresenter @pendingScrollLogicalPosition = position @pendingScrollTop = null @pendingScrollLeft = null - - @shouldUpdateCursorsState = true - @shouldUpdateCustomGutterDecorationState = true @shouldUpdateDecorations = true - @shouldUpdateHiddenInputState = true - @shouldUpdateHorizontalScrollState = true - @shouldUpdateLinesState = true - @shouldUpdateLineNumbersState = true - @shouldUpdateOverlaysState = true - @shouldUpdateScrollPosition = true - @shouldUpdateVerticalScrollState = true - @emitDidUpdateState() didChangeFirstVisibleScreenRow: (screenRow) -> - @updateScrollTop(screenRow * @lineHeight) + @setScrollTop(screenRow * @lineHeight) getVerticalScrollMarginInPixels: -> Math.round(@model.getVerticalScrollMargin() * @lineHeight) @@ -1482,14 +1299,14 @@ class TextEditorPresenter if options?.reversed ? true if desiredScrollBottom > @getScrollBottom() - @setScrollBottom(desiredScrollBottom, false) + @updateScrollTop(desiredScrollBottom - @getClientHeight()) if desiredScrollTop < @getScrollTop() - @setScrollTop(desiredScrollTop, false) + @updateScrollTop(desiredScrollTop) else if desiredScrollTop < @getScrollTop() - @setScrollTop(desiredScrollTop, false) + @updateScrollTop(desiredScrollTop) if desiredScrollBottom > @getScrollBottom() - @setScrollBottom(desiredScrollBottom, false) + @updateScrollTop(desiredScrollBottom - @getClientHeight()) commitPendingLogicalScrollLeftPosition: -> return unless @pendingScrollLogicalPosition? @@ -1509,14 +1326,14 @@ class TextEditorPresenter if options?.reversed ? true if desiredScrollRight > @getScrollRight() - @setScrollRight(desiredScrollRight, false) + @updateScrollLeft(desiredScrollRight - @getClientWidth()) if desiredScrollLeft < @getScrollLeft() - @setScrollLeft(desiredScrollLeft, false) + @updateScrollLeft(desiredScrollLeft) else if desiredScrollLeft < @getScrollLeft() - @setScrollLeft(desiredScrollLeft, false) + @updateScrollLeft(desiredScrollLeft) if desiredScrollRight > @getScrollRight() - @setScrollRight(desiredScrollRight, false) + @updateScrollLeft(desiredScrollRight - @getClientWidth()) commitPendingScrollLeftPosition: -> if @pendingScrollLeft? diff --git a/src/text-utils.coffee b/src/text-utils.coffee index 82bed4da599..af17335aad3 100644 --- a/src/text-utils.coffee +++ b/src/text-utils.coffee @@ -57,11 +57,11 @@ isPairedCharacter = (string, index=0) -> isVariationSequence(charCodeA, charCodeB) or isCombinedCharacter(charCodeA, charCodeB) -isJapaneseCharacter = (charCode) -> +IsJapaneseKanaCharacter = (charCode) -> 0x3000 <= charCode <= 0x30FF -isCjkUnifiedIdeograph = (charCode) -> - 0x4E00 <= charCode <= 0x9FAF +isCJKUnifiedIdeograph = (charCode) -> + 0x4E00 <= charCode <= 0x9FFF isFullWidthForm = (charCode) -> 0xFF01 <= charCode <= 0xFF5E or @@ -70,8 +70,8 @@ isFullWidthForm = (charCode) -> isDoubleWidthCharacter = (character) -> charCode = character.charCodeAt(0) - isJapaneseCharacter(charCode) or - isCjkUnifiedIdeograph(charCode) or + IsJapaneseKanaCharacter(charCode) or + isCJKUnifiedIdeograph(charCode) or isFullWidthForm(charCode) isHalfWidthCharacter = (character) -> @@ -89,6 +89,11 @@ isKoreanCharacter = (character) -> 0xA960 <= charCode <= 0xA97F or 0xD7B0 <= charCode <= 0xD7FF +isCJKCharacter = (character) -> + isDoubleWidthCharacter(character) or + isHalfWidthCharacter(character) or + isKoreanCharacter(character) + # Does the given string contain at least surrogate pair, variation sequence, # or combined character? # @@ -102,4 +107,4 @@ hasPairedCharacter = (string) -> index++ false -module.exports = {isPairedCharacter, hasPairedCharacter, isDoubleWidthCharacter, isHalfWidthCharacter, isKoreanCharacter} +module.exports = {isPairedCharacter, hasPairedCharacter, isDoubleWidthCharacter, isHalfWidthCharacter, isKoreanCharacter, isCJKCharacter} diff --git a/src/tokenized-buffer.coffee b/src/tokenized-buffer.coffee index cdafc286964..0fdf4eea8a0 100644 --- a/src/tokenized-buffer.coffee +++ b/src/tokenized-buffer.coffee @@ -36,7 +36,7 @@ class TokenizedBuffer extends Model constructor: (params) -> { @buffer, @tabLength, @ignoreInvisibles, @largeFileMode, @config, - @grammarRegistry, @packageManager, @assert + @grammarRegistry, @packageManager, @assert, grammarScopeName } = params @emitter = new Emitter @@ -49,18 +49,26 @@ class TokenizedBuffer extends Model @disposables.add @buffer.preemptDidChange (e) => @handleBufferChange(e) @disposables.add @buffer.onDidChangePath (@bufferPath) => @reloadGrammar() - @reloadGrammar() + if grammar = @grammarRegistry.grammarForScopeName(grammarScopeName) + @setGrammar(grammar) + else + @reloadGrammar() + @grammarToRestoreScopeName = grammarScopeName destroyed: -> @disposables.dispose() serialize: -> - deserializer: 'TokenizedBuffer' - bufferPath: @buffer.getPath() - bufferId: @buffer.getId() - tabLength: @tabLength - ignoreInvisibles: @ignoreInvisibles - largeFileMode: @largeFileMode + state = { + deserializer: 'TokenizedBuffer' + bufferPath: @buffer.getPath() + bufferId: @buffer.getId() + tabLength: @tabLength + ignoreInvisibles: @ignoreInvisibles + largeFileMode: @largeFileMode + } + state.grammarScopeName = @grammar?.scopeName unless @buffer.getPath() + state observeGrammar: (callback) -> callback(@grammar) @@ -76,7 +84,9 @@ class TokenizedBuffer extends Model @emitter.on 'did-tokenize', callback grammarAddedOrUpdated: (grammar) => - if grammar.injectionSelector? + if @grammarToRestoreScopeName is grammar.scopeName + @setGrammar(grammar) + else if grammar.injectionSelector? @retokenizeLines() if @hasTokenForSelector(grammar.injectionSelector) else newScore = @grammarRegistry.getGrammarScore(grammar, @buffer.getPath(), @getGrammarSelectionContent()) @@ -89,6 +99,8 @@ class TokenizedBuffer extends Model @rootScopeDescriptor = new ScopeDescriptor(scopes: [@grammar.scopeName]) @currentGrammarScore = score ? @grammarRegistry.getGrammarScore(grammar, @buffer.getPath(), @getGrammarSelectionContent()) + @grammarToRestoreScopeName = null + @grammarUpdateDisposable?.dispose() @grammarUpdateDisposable = @grammar.onDidUpdate => @retokenizeLines() @disposables.add(@grammarUpdateDisposable) diff --git a/src/tokenized-line.coffee b/src/tokenized-line.coffee index 2a27d3f1267..c97a621acd0 100644 --- a/src/tokenized-line.coffee +++ b/src/tokenized-line.coffee @@ -1,5 +1,5 @@ _ = require 'underscore-plus' -{isPairedCharacter} = require './text-utils' +{isPairedCharacter, isCJKCharacter} = require './text-utils' Token = require './token' {SoftTab, HardTab, PairedCharacter, SoftWrapIndent} = require './special-token-symbols' @@ -322,15 +322,18 @@ class TokenizedLine return unless @text.length > maxColumn if /\s/.test(@text[maxColumn]) - # search forward for the start of a word past the boundary + # search forward for the start of a word past the boundary for column in [maxColumn..@text.length] return column if /\S/.test(@text[column]) return @text.length + else if isCJKCharacter(@text[maxColumn]) + maxColumn else # search backward for the start of the word on the boundary for column in [maxColumn..@firstNonWhitespaceIndex] - return column + 1 if /\s/.test(@text[column]) + if /\s/.test(@text[column]) or isCJKCharacter(@text[column]) + return column + 1 return maxColumn diff --git a/static/index.js b/static/index.js index a55f31b5cc5..d7f5096350e 100644 --- a/static/index.js +++ b/static/index.js @@ -19,6 +19,7 @@ path.join(process.env.ATOM_HOME, 'blob-store/') ) NativeCompileCache.setCacheStore(blobStore) + NativeCompileCache.setV8Version(process.versions.v8) NativeCompileCache.install() // Normalize to make sure drive letter case is consistent on Windows