From 40eec783eb1a498f9fc6976065b7492f8ef92b3a Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 9 Dec 2015 14:18:25 -0800 Subject: [PATCH 01/45] 1.3.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 48a1431f6..46be845bb 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "atom", "productName": "Atom", - "version": "1.3.0-beta7", + "version": "1.3.0", "description": "A hackable text editor for the 21st Century.", "main": "./src/browser/main.js", "repository": { From ed7283f23b503688984312d2d1cf167967a86c96 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 9 Dec 2015 14:18:26 -0800 Subject: [PATCH 02/45] 1.4.0-beta0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 50e4d4d09..9dc73dc24 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "atom", "productName": "Atom", - "version": "1.4.0-dev", + "version": "1.4.0-beta0", "description": "A hackable text editor for the 21st Century.", "main": "./src/browser/main.js", "repository": { From 9a2c3a49e79a65b67cd512446190d9b09830a5aa Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 10 Dec 2015 10:00:56 +0100 Subject: [PATCH 03/45] Expose a isCjkCharacter text utility --- spec/text-utils-spec.coffee | 20 ++++++++++++++++++++ src/text-utils.coffee | 7 ++++++- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/spec/text-utils-spec.coffee b/spec/text-utils-spec.coffee index dd528b37e..f4438cf8d 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/src/text-utils.coffee b/src/text-utils.coffee index 82bed4da5..3f283cfa3 100644 --- a/src/text-utils.coffee +++ b/src/text-utils.coffee @@ -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} From 3253c0d5cd22467360e4deb1579e0f251ae83c2f Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 10 Dec 2015 10:07:45 +0100 Subject: [PATCH 04/45] Don't rely on spaces to test korean characters' width behavior --- spec/display-buffer-spec.coffee | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/spec/display-buffer-spec.coffee b/spec/display-buffer-spec.coffee index a54c01198..fc2c76503 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", -> From c22cae451bb05532b510317f3572b47ec8d5fb59 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 10 Dec 2015 10:18:24 +0100 Subject: [PATCH 05/45] Wrap line at boundary if it includes a CJK character --- spec/display-buffer-spec.coffee | 6 ++++++ src/tokenized-line.coffee | 9 ++++++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/spec/display-buffer-spec.coffee b/spec/display-buffer-spec.coffee index fc2c76503..10df357e3 100644 --- a/spec/display-buffer-spec.coffee +++ b/spec/display-buffer-spec.coffee @@ -130,6 +130,12 @@ 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 boundary if it includes a CJK character", -> + buffer.setTextInRange([[0, 0], [1, 0]], 'abcd efg유私フ业余爱\n') + displayBuffer.setEditorWidthInChars(10) + expect(displayBuffer.tokenizedLineForScreenRow(0).text).toBe 'abcd efg유私' + expect(displayBuffer.tokenizedLineForScreenRow(1).text).toBe 'フ业余爱' + 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", -> buffer.setTextInRange([[0, 0], [1, 0]], 'abcdefghijklmnopqrstuvwxyz\n') diff --git a/src/tokenized-line.coffee b/src/tokenized-line.coffee index 2a27d3f12..2ff4c3b17 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' @@ -321,8 +321,11 @@ class TokenizedLine return unless maxColumn? return unless @text.length > maxColumn - if /\s/.test(@text[maxColumn]) - # search forward for the start of a word past the boundary + if isCjkCharacter(@text[maxColumn]) + # always wrap when a CJK character is at the wrap boundary + return maxColumn + else if /\s/.test(@text[maxColumn]) + # search forward for the start of a word past the boundary for column in [maxColumn..@text.length] return column if /\S/.test(@text[column]) From 3846a46c4c32989ccd34e9c60509e008bb2c5bf7 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 10 Dec 2015 09:50:33 -0800 Subject: [PATCH 06/45] Prepare 1.3.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 46be845bb..b34e6e6d2 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "atom", "productName": "Atom", - "version": "1.3.0", + "version": "1.3.1", "description": "A hackable text editor for the 21st Century.", "main": "./src/browser/main.js", "repository": { From 5a6fd06386580b3f92bb032ff477184383c22b0a Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 10 Dec 2015 09:51:53 -0800 Subject: [PATCH 07/45] There is no ES6 in compile-cache.js --- src/compile-cache.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/compile-cache.js b/src/compile-cache.js index fde04a2f4..1bbef4273 100644 --- a/src/compile-cache.js +++ b/src/compile-cache.js @@ -161,8 +161,7 @@ require('source-map-support').install({ Error.stackTraceLimit = 30 var prepareStackTraceWithSourceMapping = Error.prepareStackTrace - -let prepareStackTrace = prepareStackTraceWithSourceMapping +var prepareStackTrace = prepareStackTraceWithSourceMapping function prepareStackTraceWithRawStackAssignment (error, frames) { if (error.rawStack) { // avoid infinite recursion From e5c9a67056d96a484ff5dea33c6b004ea787dbbf Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 10 Dec 2015 10:00:39 -0800 Subject: [PATCH 08/45] Add a comment explaining why there's no es6 in compile-cache.js --- src/compile-cache.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/compile-cache.js b/src/compile-cache.js index 1bbef4273..679c7a769 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 From 3937312936f09c0279186c96423cc2b5a3be480f Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 24 Nov 2015 11:30:41 -0700 Subject: [PATCH 09/45] Merge pull request #9796 from atom/as-native-key-bindings Use CommandRegistry to listen for native-key-bindings --- spec/window-event-handler-spec.coffee | 31 +++++++++++++++++++++++++++ src/window-event-handler.coffee | 5 ++--- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/spec/window-event-handler-spec.coffee b/spec/window-event-handler-spec.coffee index 3148942b4..a988ae7de 100644 --- a/spec/window-event-handler-spec.coffee +++ b/spec/window-event-handler-spec.coffee @@ -200,3 +200,34 @@ describe "WindowEventHandler", -> expect(dispatchedCommands.length).toBe 1 expect(dispatchedCommands[0].type).toBe 'foo-command' + + describe "native key bindings", -> + it "correctly dispatches them to active elements with the '.native-key-bindings' class", -> + webContentsSpy = jasmine.createSpyObj("webContents", ["copy", "paste"]) + spyOn(atom.applicationDelegate, "getCurrentWindow").andReturn({ + webContents: webContentsSpy + }) + + nativeKeyBindingsInput = document.createElement("input") + nativeKeyBindingsInput.classList.add("native-key-bindings") + jasmine.attachToDOM(nativeKeyBindingsInput) + nativeKeyBindingsInput.focus() + + atom.dispatchApplicationMenuCommand("core:copy") + atom.dispatchApplicationMenuCommand("core:paste") + + expect(webContentsSpy.copy).toHaveBeenCalled() + expect(webContentsSpy.paste).toHaveBeenCalled() + + webContentsSpy.copy.reset() + webContentsSpy.paste.reset() + + normalInput = document.createElement("input") + jasmine.attachToDOM(normalInput) + normalInput.focus() + + atom.dispatchApplicationMenuCommand("core:copy") + atom.dispatchApplicationMenuCommand("core:paste") + + expect(webContentsSpy.copy).not.toHaveBeenCalled() + expect(webContentsSpy.paste).not.toHaveBeenCalled() diff --git a/src/window-event-handler.coffee b/src/window-event-handler.coffee index c786f7e9c..d3a231f77 100644 --- a/src/window-event-handler.coffee +++ b/src/window-event-handler.coffee @@ -42,9 +42,8 @@ class WindowEventHandler # `.native-key-bindings` class. handleNativeKeybindings: -> bindCommandToAction = (command, action) => - @addEventListener @document, command, (event) => - if event.target.webkitMatchesSelector('.native-key-bindings') - @applicationDelegate.getCurrentWindow().webContents[action]() + @subscriptions.add @atomEnvironment.commands.add '.native-key-bindings', command, (event) => + @applicationDelegate.getCurrentWindow().webContents[action]() bindCommandToAction('core:copy', 'copy') bindCommandToAction('core:paste', 'paste') From cdd4212400c9272ec392057c7f3fc5ac5f7f4513 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 11 Dec 2015 12:47:17 +0100 Subject: [PATCH 10/45] Pass an invalidation key in NativeCompileCache --- spec/native-compile-cache-spec.coffee | 13 +++++++------ src/native-compile-cache.js | 11 +++++++---- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/spec/native-compile-cache-spec.coffee b/spec/native-compile-cache-spec.coffee index dd720e84d..fceeba7c8 100644 --- a/spec/native-compile-cache-spec.coffee +++ b/spec/native-compile-cache-spec.coffee @@ -10,20 +10,20 @@ describe "NativeCompileCache", -> it "writes and reads from the cache storage when requiring files", -> fakeCacheStore.has.andReturn(false) - fakeCacheStore.set.andCallFake (filename, cacheBuffer) -> - cachedFiles.push({filename, cacheBuffer}) + fakeCacheStore.set.andCallFake (cacheKey, invalidationKey, cacheBuffer) -> + cachedFiles.push({cacheKey, 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) @@ -37,11 +37,12 @@ describe "NativeCompileCache", -> expect(fakeCacheStore.set).not.toHaveBeenCalled() expect(fn1()).toBe(1) - it "deletes previously cached code when the cache is not valid", -> + it "deletes previously cached code when the cache is an invalid file", -> fakeCacheStore.has.andReturn(true) fakeCacheStore.get.andCallFake -> new Buffer("an invalid cache") fn3 = require('./fixtures/native-cache/file-3') - expect(fakeCacheStore.delete).toHaveBeenCalledWith(require.resolve('./fixtures/native-cache/file-3')) + expect(fakeCacheStore.delete.calls.length).toBe(1) + expect(fakeCacheStore.delete.calls[0].args[0]).toBe(require.resolve('./fixtures/native-cache/file-3')) expect(fn3()).toBe(3) diff --git a/src/native-compile-cache.js b/src/native-compile-cache.js index a930466a5..6fb8d89c0 100644 --- a/src/native-compile-cache.js +++ b/src/native-compile-cache.js @@ -3,6 +3,7 @@ const Module = require('module') const path = require('path') const cachedVm = require('cached-run-in-this-context') +const crypto = require('crypto') class NativeCompileCache { constructor () { @@ -54,18 +55,20 @@ class NativeCompileCache { // create wrapper function let wrapper = Module.wrap(content) + let cacheKey = filename + let invalidationKey = crypto.createHash('sha1').update(wrapper, 'utf8').digest('hex') let compiledWrapper = null - if (cacheStore.has(filename)) { - let buffer = cacheStore.get(filename) + if (cacheStore.has(cacheKey, invalidationKey)) { + let buffer = cacheStore.get(cacheKey, invalidationKey) let compilationResult = cachedVm.runInThisContextCached(wrapper, filename, buffer) compiledWrapper = compilationResult.result if (compilationResult.wasRejected) { - cacheStore.delete(filename) + cacheStore.delete(cacheKey) } } else { let compilationResult = cachedVm.runInThisContext(wrapper, filename) if (compilationResult.cacheBuffer) { - cacheStore.set(filename, compilationResult.cacheBuffer) + cacheStore.set(cacheKey, invalidationKey, compilationResult.cacheBuffer) } compiledWrapper = compilationResult.result } From c5562d84466b3c7ff6e63cce241d73b34a44b941 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 11 Dec 2015 13:38:50 +0100 Subject: [PATCH 11/45] Allow to supply an invalidation key to FileSystemBlobStore --- spec/file-system-blob-store-spec.coffee | 62 ++++++++++++++----------- src/file-system-blob-store.js | 23 +++++++-- 2 files changed, 54 insertions(+), 31 deletions(-) diff --git a/spec/file-system-blob-store-spec.coffee b/spec/file-system-blob-store-spec.coffee index c947259f6..c1cc29449 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/src/file-system-blob-store.js b/src/file-system-blob-store.js index fc6bdddf3..e565a8857 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) } From 8a1984d1b74a1b6e5ce80d5240a8d65a7e29c927 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 11 Dec 2015 13:43:03 +0100 Subject: [PATCH 12/45] :arrow_up: cached-run-in-this-context --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 36ffb2dcb..26120d82d 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", From 173fbba02b8504f473c27fbcd7cc4d593ebace24 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 11 Dec 2015 14:26:51 +0100 Subject: [PATCH 13/45] Wrap at the first CJK character before the boundary --- spec/display-buffer-spec.coffee | 17 ++++++++++++++--- src/tokenized-line.coffee | 10 +++++----- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/spec/display-buffer-spec.coffee b/spec/display-buffer-spec.coffee index 10df357e3..8c4adca44 100644 --- a/spec/display-buffer-spec.coffee +++ b/spec/display-buffer-spec.coffee @@ -130,14 +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 boundary if it includes a CJK character", -> - buffer.setTextInRange([[0, 0], [1, 0]], 'abcd efg유私フ业余爱\n') + 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/src/tokenized-line.coffee b/src/tokenized-line.coffee index 2ff4c3b17..36802c2f7 100644 --- a/src/tokenized-line.coffee +++ b/src/tokenized-line.coffee @@ -321,19 +321,19 @@ class TokenizedLine return unless maxColumn? return unless @text.length > maxColumn - if isCjkCharacter(@text[maxColumn]) - # always wrap when a CJK character is at the wrap boundary - return maxColumn - else if /\s/.test(@text[maxColumn]) + if /\s/.test(@text[maxColumn]) # 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 From 12376039a9ad43664f4da62fb06e1e5e3625493a Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 11 Dec 2015 14:28:26 +0100 Subject: [PATCH 14/45] :art: cjk -> CJK --- spec/text-utils-spec.coffee | 28 ++++++++++++++-------------- src/text-utils.coffee | 8 ++++---- src/tokenized-line.coffee | 6 +++--- 3 files changed, 21 insertions(+), 21 deletions(-) diff --git a/spec/text-utils-spec.coffee b/spec/text-utils-spec.coffee index f4438cf8d..aa36c5003 100644 --- a/spec/text-utils-spec.coffee +++ b/spec/text-utils-spec.coffee @@ -75,21 +75,21 @@ describe 'text utilities', -> expect(textUtils.isKoreanCharacter("O")).toBe(false) - describe ".isCjkCharacter(character)", -> + 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.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) diff --git a/src/text-utils.coffee b/src/text-utils.coffee index 3f283cfa3..ce8fc864a 100644 --- a/src/text-utils.coffee +++ b/src/text-utils.coffee @@ -60,7 +60,7 @@ isPairedCharacter = (string, index=0) -> isJapaneseCharacter = (charCode) -> 0x3000 <= charCode <= 0x30FF -isCjkUnifiedIdeograph = (charCode) -> +isCJKUnifiedIdeograph = (charCode) -> 0x4E00 <= charCode <= 0x9FAF isFullWidthForm = (charCode) -> @@ -71,7 +71,7 @@ isDoubleWidthCharacter = (character) -> charCode = character.charCodeAt(0) isJapaneseCharacter(charCode) or - isCjkUnifiedIdeograph(charCode) or + isCJKUnifiedIdeograph(charCode) or isFullWidthForm(charCode) isHalfWidthCharacter = (character) -> @@ -89,7 +89,7 @@ isKoreanCharacter = (character) -> 0xA960 <= charCode <= 0xA97F or 0xD7B0 <= charCode <= 0xD7FF -isCjkCharacter = (character) -> +isCJKCharacter = (character) -> isDoubleWidthCharacter(character) or isHalfWidthCharacter(character) or isKoreanCharacter(character) @@ -107,4 +107,4 @@ hasPairedCharacter = (string) -> index++ false -module.exports = {isPairedCharacter, hasPairedCharacter, isDoubleWidthCharacter, isHalfWidthCharacter, isKoreanCharacter, isCjkCharacter} +module.exports = {isPairedCharacter, hasPairedCharacter, isDoubleWidthCharacter, isHalfWidthCharacter, isKoreanCharacter, isCJKCharacter} diff --git a/src/tokenized-line.coffee b/src/tokenized-line.coffee index 36802c2f7..c97a621ac 100644 --- a/src/tokenized-line.coffee +++ b/src/tokenized-line.coffee @@ -1,5 +1,5 @@ _ = require 'underscore-plus' -{isPairedCharacter, isCjkCharacter} = require './text-utils' +{isPairedCharacter, isCJKCharacter} = require './text-utils' Token = require './token' {SoftTab, HardTab, PairedCharacter, SoftWrapIndent} = require './special-token-symbols' @@ -327,12 +327,12 @@ class TokenizedLine return column if /\S/.test(@text[column]) return @text.length - else if isCjkCharacter(@text[maxColumn]) + else if isCJKCharacter(@text[maxColumn]) maxColumn else # search backward for the start of the word on the boundary for column in [maxColumn..@firstNonWhitespaceIndex] - if /\s/.test(@text[column]) or isCjkCharacter(@text[column]) + if /\s/.test(@text[column]) or isCJKCharacter(@text[column]) return column + 1 return maxColumn From 1f5473b2ddea515a85ed647d93f668600dbe2c0f Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 11 Dec 2015 15:37:56 +0100 Subject: [PATCH 15/45] :white_check_mark: Test cache invalidation --- spec/native-compile-cache-spec.coffee | 44 ++++++++++++++++++++++----- 1 file changed, 37 insertions(+), 7 deletions(-) diff --git a/spec/native-compile-cache-spec.coffee b/spec/native-compile-cache-spec.coffee index fceeba7c8..52571ddcb 100644 --- a/spec/native-compile-cache-spec.coffee +++ b/spec/native-compile-cache-spec.coffee @@ -1,3 +1,6 @@ +fs = require 'fs' +path = require 'path' + describe "NativeCompileCache", -> nativeCompileCache = require '../src/native-compile-cache' [fakeCacheStore, cachedFiles] = [] @@ -8,15 +11,30 @@ describe "NativeCompileCache", -> nativeCompileCache.setCacheStore(fakeCacheStore) nativeCompileCache.install() + fs.writeFileSync path.resolve('./spec/fixtures/native-cache/file-4'), """ + module.exports = function () { return "file-4" } + """ + + afterEach -> + fs.unlinkSync path.resolve('./spec/fixtures/native-cache/file-4') + it "writes and reads from the cache storage when requiring files", -> - fakeCacheStore.has.andReturn(false) + fakeCacheStore.has.andCallFake (cacheKey, invalidationKey) -> + fakeCacheStore.get(cacheKey, invalidationKey)? + fakeCacheStore.get.andCallFake (cacheKey, invalidationKey) -> + for entry in cachedFiles + 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, cacheBuffer}) + cachedFiles.push({cacheKey, invalidationKey, cacheBuffer}) fn1 = require('./fixtures/native-cache/file-1') fn2 = require('./fixtures/native-cache/file-2') + fn4 = require('./fixtures/native-cache/file-4') - expect(cachedFiles.length).toBe(2) + expect(cachedFiles.length).toBe(3) expect(cachedFiles[0].cacheKey).toBe(require.resolve('./fixtures/native-cache/file-1')) expect(cachedFiles[0].cacheBuffer).toBeInstanceOf(Uint8Array) @@ -28,14 +46,26 @@ describe "NativeCompileCache", -> expect(cachedFiles[1].cacheBuffer.length).toBeGreaterThan(0) expect(fn2()).toBe(2) - fakeCacheStore.has.andReturn(true) - fakeCacheStore.get.andReturn(cachedFiles[0].cacheBuffer) - fakeCacheStore.set.reset() + expect(cachedFiles[2].cacheKey).toBe(require.resolve('./fixtures/native-cache/file-4')) + expect(cachedFiles[2].cacheBuffer).toBeInstanceOf(Uint8Array) + expect(cachedFiles[2].cacheBuffer.length).toBeGreaterThan(0) + expect(fn4()).toBe("file-4") + fs.appendFileSync(require.resolve('./fixtures/native-cache/file-4'), "\n") + delete require('module')._cache[require.resolve('./fixtures/native-cache/file-1')] + delete require('module')._cache[require.resolve('./fixtures/native-cache/file-4')] fn1 = require('./fixtures/native-cache/file-1') + fn4 = require('./fixtures/native-cache/file-4') + + # file content has changed, ensure we create a new cache entry + expect(cachedFiles.length).toBe(4) + expect(cachedFiles[3].cacheKey).toBe(require.resolve('./fixtures/native-cache/file-4')) + expect(cachedFiles[3].invalidationKey).not.toBe(cachedFiles[2].invalidationKey) + expect(cachedFiles[3].cacheBuffer).toBeInstanceOf(Uint8Array) + expect(cachedFiles[3].cacheBuffer.length).toBeGreaterThan(0) - expect(fakeCacheStore.set).not.toHaveBeenCalled() expect(fn1()).toBe(1) + expect(fn4()).toBe("file-4") it "deletes previously cached code when the cache is an invalid file", -> fakeCacheStore.has.andReturn(true) From a63c294dfdb5062493edcbdacae442c44033936f Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 11 Dec 2015 15:39:29 +0100 Subject: [PATCH 16/45] :art: --- spec/native-compile-cache-spec.coffee | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/spec/native-compile-cache-spec.coffee b/spec/native-compile-cache-spec.coffee index 52571ddcb..f2078ec94 100644 --- a/spec/native-compile-cache-spec.coffee +++ b/spec/native-compile-cache-spec.coffee @@ -73,6 +73,5 @@ describe "NativeCompileCache", -> fn3 = require('./fixtures/native-cache/file-3') - expect(fakeCacheStore.delete.calls.length).toBe(1) - expect(fakeCacheStore.delete.calls[0].args[0]).toBe(require.resolve('./fixtures/native-cache/file-3')) + expect(fakeCacheStore.delete).toHaveBeenCalledWith(require.resolve('./fixtures/native-cache/file-3')) expect(fn3()).toBe(3) From fa48b2fbe097f405504ea6a0b113012cee79d031 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 11 Dec 2015 17:47:04 +0100 Subject: [PATCH 17/45] Make v8 version part of the key --- spec/fixtures/native-cache/file-4.js | 1 + spec/native-compile-cache-spec.coffee | 91 +++++++++++++++++---------- src/native-compile-cache.js | 30 +++++---- static/index.js | 1 + 4 files changed, 80 insertions(+), 43 deletions(-) create mode 100644 spec/fixtures/native-cache/file-4.js diff --git a/spec/fixtures/native-cache/file-4.js b/spec/fixtures/native-cache/file-4.js new file mode 100644 index 000000000..1b8fd4e15 --- /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 f2078ec94..9d6cb89b4 100644 --- a/spec/native-compile-cache-spec.coffee +++ b/spec/native-compile-cache-spec.coffee @@ -1,5 +1,6 @@ fs = require 'fs' path = require 'path' +Module = require 'module' describe "NativeCompileCache", -> nativeCompileCache = require '../src/native-compile-cache' @@ -8,21 +9,10 @@ describe "NativeCompileCache", -> beforeEach -> cachedFiles = [] fakeCacheStore = jasmine.createSpyObj("cache store", ["set", "get", "has", "delete"]) - nativeCompileCache.setCacheStore(fakeCacheStore) - nativeCompileCache.install() - - fs.writeFileSync path.resolve('./spec/fixtures/native-cache/file-4'), """ - module.exports = function () { return "file-4" } - """ - - afterEach -> - fs.unlinkSync path.resolve('./spec/fixtures/native-cache/file-4') - - it "writes and reads from the cache storage when requiring files", -> fakeCacheStore.has.andCallFake (cacheKey, invalidationKey) -> fakeCacheStore.get(cacheKey, invalidationKey)? fakeCacheStore.get.andCallFake (cacheKey, invalidationKey) -> - for entry in cachedFiles + for entry in cachedFiles by -1 continue if entry.cacheKey isnt cacheKey continue if entry.invalidationKey isnt invalidationKey return entry.cacheBuffer @@ -30,11 +20,15 @@ describe "NativeCompileCache", -> 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", -> fn1 = require('./fixtures/native-cache/file-1') fn2 = require('./fixtures/native-cache/file-2') - fn4 = require('./fixtures/native-cache/file-4') - expect(cachedFiles.length).toBe(3) + expect(cachedFiles.length).toBe(2) expect(cachedFiles[0].cacheKey).toBe(require.resolve('./fixtures/native-cache/file-1')) expect(cachedFiles[0].cacheBuffer).toBeInstanceOf(Uint8Array) @@ -46,26 +40,59 @@ describe "NativeCompileCache", -> expect(cachedFiles[1].cacheBuffer.length).toBeGreaterThan(0) expect(fn2()).toBe(2) - expect(cachedFiles[2].cacheKey).toBe(require.resolve('./fixtures/native-cache/file-4')) - expect(cachedFiles[2].cacheBuffer).toBeInstanceOf(Uint8Array) - expect(cachedFiles[2].cacheBuffer.length).toBeGreaterThan(0) - expect(fn4()).toBe("file-4") - - fs.appendFileSync(require.resolve('./fixtures/native-cache/file-4'), "\n") - delete require('module')._cache[require.resolve('./fixtures/native-cache/file-1')] - delete require('module')._cache[require.resolve('./fixtures/native-cache/file-4')] + delete Module._cache[require.resolve('./fixtures/native-cache/file-1')] fn1 = require('./fixtures/native-cache/file-1') - fn4 = require('./fixtures/native-cache/file-4') - - # file content has changed, ensure we create a new cache entry - expect(cachedFiles.length).toBe(4) - expect(cachedFiles[3].cacheKey).toBe(require.resolve('./fixtures/native-cache/file-4')) - expect(cachedFiles[3].invalidationKey).not.toBe(cachedFiles[2].invalidationKey) - expect(cachedFiles[3].cacheBuffer).toBeInstanceOf(Uint8Array) - expect(cachedFiles[3].cacheBuffer.length).toBeGreaterThan(0) - + expect(cachedFiles.length).toBe(2) expect(fn1()).toBe(1) - expect(fn4()).toBe("file-4") + + 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) diff --git a/src/native-compile-cache.js b/src/native-compile-cache.js index 6fb8d89c0..4af946342 100644 --- a/src/native-compile-cache.js +++ b/src/native-compile-cache.js @@ -5,6 +5,10 @@ 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 () { this.cacheStore = null @@ -15,6 +19,10 @@ class NativeCompileCache { this.cacheStore = store } + setV8Version (v8Version) { + this.v8Version = v8Version.toString() + } + install () { this.savePreviousModuleCompile() this.overrideModuleCompile() @@ -29,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 @@ -56,19 +64,19 @@ class NativeCompileCache { let wrapper = Module.wrap(content) let cacheKey = filename - let invalidationKey = crypto.createHash('sha1').update(wrapper, 'utf8').digest('hex') + let invalidationKey = computeHash(wrapper + self.v8Version) let compiledWrapper = null - if (cacheStore.has(cacheKey, invalidationKey)) { - let buffer = cacheStore.get(cacheKey, invalidationKey) + 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(cacheKey) + self.cacheStore.delete(cacheKey) } } else { let compilationResult = cachedVm.runInThisContext(wrapper, filename) if (compilationResult.cacheBuffer) { - cacheStore.set(cacheKey, invalidationKey, compilationResult.cacheBuffer) + self.cacheStore.set(cacheKey, invalidationKey, compilationResult.cacheBuffer) } compiledWrapper = compilationResult.result } @@ -91,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/static/index.js b/static/index.js index 2a5bcad3a..8a7761198 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 From 3e63197b51dc9d4ed7c9c145b6340fdf94317b4a Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 11 Dec 2015 19:05:49 +0100 Subject: [PATCH 18/45] :lipstick: Fix lint warnings --- src/native-compile-cache.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/native-compile-cache.js b/src/native-compile-cache.js index 4af946342..50fa71fc3 100644 --- a/src/native-compile-cache.js +++ b/src/native-compile-cache.js @@ -5,7 +5,7 @@ const path = require('path') const cachedVm = require('cached-run-in-this-context') const crypto = require('crypto') -function computeHash(contents) { +function computeHash (contents) { return crypto.createHash('sha1').update(contents, 'utf8').digest('hex') } From f6da4a9139bd08b4b89e85203caf20a4af72d195 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 11 Dec 2015 11:13:50 -0800 Subject: [PATCH 19/45] :arrow_up: find-and-replace --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 26120d82d..7281d41c0 100644 --- a/package.json +++ b/package.json @@ -87,7 +87,7 @@ "dev-live-reload": "0.47.0", "encoding-selector": "0.21.0", "exception-reporting": "0.37.0", - "find-and-replace": "0.194.0", + "find-and-replace": "0.195.0", "fuzzy-finder": "0.93.0", "git-diff": "0.57.0", "go-to-line": "0.30.0", From 5cfd3423bfc8c30f393d4535d0854b97ea41dee4 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 11 Dec 2015 13:34:46 -0700 Subject: [PATCH 20/45] Use internal scroll assignment methods when committing logical positions This prevents an addition pending assignment and cuts directly to adjusting the positions. --- spec/text-editor-presenter-spec.coffee | 17 ------------- src/text-editor-presenter.coffee | 35 ++++++++++++-------------- 2 files changed, 16 insertions(+), 36 deletions(-) diff --git a/spec/text-editor-presenter-spec.coffee b/spec/text-editor-presenter-spec.coffee index effa579b1..0fd3b377a 100644 --- a/spec/text-editor-presenter-spec.coffee +++ b/spec/text-editor-presenter-spec.coffee @@ -625,23 +625,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) diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index 5cfaeebcf..7652bbd61 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -866,10 +866,10 @@ class TextEditorPresenter @emitDidUpdateState() - setScrollTop: (scrollTop, overrideScroll=true) -> + setScrollTop: (scrollTop) -> return unless scrollTop? - @pendingScrollLogicalPosition = null if overrideScroll + @pendingScrollLogicalPosition = null @pendingScrollTop = scrollTop @shouldUpdateVerticalScrollState = true @@ -894,11 +894,8 @@ 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 @@ -907,10 +904,10 @@ class TextEditorPresenter @emitDidUpdateState() - setScrollLeft: (scrollLeft, overrideScroll=true) -> + setScrollLeft: (scrollLeft) -> return unless scrollLeft? - @pendingScrollLogicalPosition = null if overrideScroll + @pendingScrollLogicalPosition = null @pendingScrollLeft = scrollLeft @shouldUpdateHorizontalScrollState = true @@ -941,13 +938,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: -> @@ -1482,14 +1479,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 +1506,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? From 3233570c0dcf8e6835064acf826b46ef786d4d39 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 11 Dec 2015 10:41:50 -0700 Subject: [PATCH 21/45] Remove flags from presenter. Always perform full state update. Signed-off-by: Max Brunsfeld --- src/text-editor-presenter.coffee | 256 +++---------------------------- 1 file changed, 24 insertions(+), 232 deletions(-) diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index 5cfaeebcf..f1b1a28a5 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -34,7 +34,6 @@ class TextEditorPresenter @observeModel() @observeConfig() @buildState() - @invalidateState() @startBlinkingCursors() if @focused @startReflowing() if @continuousReflow @updating = false @@ -82,15 +81,10 @@ class TextEditorPresenter @updateCommonGutterState() @updateReflowState() - if @shouldUpdateDecorations - @fetchDecorations() - @updateLineDecorations() + @fetchDecorations() + @updateLineDecorations() - if @shouldUpdateLinesState or @shouldUpdateLineNumbersState - @updateTilesState() - @shouldUpdateLinesState = false - @shouldUpdateLineNumbersState = false - @shouldUpdateTilesState = true + @updateTilesState() @updating = false @state @@ -104,105 +98,31 @@ 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 - @updateHighlightDecorations() if @shouldUpdateDecorations - @updateTilesState() if @shouldUpdateTilesState - @updateCursorsState() if @shouldUpdateCursorsState - @updateOverlaysState() if @shouldUpdateOverlaysState - @updateLineNumberGutterState() if @shouldUpdateLineNumberGutterState - @updateGutterOrderState() if @shouldUpdateGutterOrderState - @updateCustomGutterDecorationState() if @shouldUpdateCustomGutterDecorationState + @updateFocusedState() + @updateHeightState() + @updateVerticalScrollState() + @updateHorizontalScrollState() + @updateScrollbarsState() + @updateHiddenInputState() + @updateContentState() + @updateHighlightDecorations() + @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.onDidChange => @emitDidUpdateState() + @disposables.add @model.onDidUpdateDecorations => @emitDidUpdateState() @disposables.add @model.onDidChangeGrammar(@didChangeGrammar.bind(this)) - @disposables.add @model.onDidChangePlaceholderText => - @shouldUpdateContentState = true - @emitDidUpdateState() - - @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.onDidChangePlaceholderText => @emitDidUpdateState() + @disposables.add @model.onDidChangeMini => @emitDidUpdateState() + @disposables.add @model.onDidChangeLineNumberGutterVisible => @emitDidUpdateState() @disposables.add @model.onDidAddCursor(@didAddCursor.bind(this)) @disposables.add @model.onDidRequestAutoscroll(@requestAutoscroll.bind(this)) @@ -227,29 +147,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: -> @@ -381,11 +291,7 @@ class TextEditorPresenter setScreenRowsToMeasure: (screenRows) -> return if not screenRows? or screenRows.length is 0 - @screenRowsToMeasure = screenRows - @shouldUpdateLinesState = true - @shouldUpdateLineNumbersState = true - @shouldUpdateDecorations = true clearScreenRowsToMeasure: -> @screenRowsToMeasure = [] @@ -425,8 +331,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++ @@ -552,23 +458,15 @@ class TextEditorPresenter didAddGutter: (gutter) -> gutterDisposables = new CompositeDisposable gutterDisposables.add gutter.onDidChangeVisible => - @shouldUpdateGutterOrderState = true - @shouldUpdateCustomGutterDecorationState = true - @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,9 +759,6 @@ class TextEditorPresenter @startBlinkingCursors() else @stopBlinkingCursors(false) - @shouldUpdateFocusedState = true - @shouldUpdateHiddenInputState = true - @emitDidUpdateState() setScrollTop: (scrollTop, overrideScroll=true) -> @@ -872,15 +767,6 @@ class TextEditorPresenter @pendingScrollLogicalPosition = null if overrideScroll @pendingScrollTop = scrollTop - @shouldUpdateVerticalScrollState = true - @shouldUpdateHiddenInputState = true - @shouldUpdateDecorations = true - @shouldUpdateLinesState = true - @shouldUpdateCursorsState = true - @shouldUpdateLineNumbersState = true - @shouldUpdateCustomGutterDecorationState = true - @shouldUpdateOverlaysState = true - @emitDidUpdateState() getScrollTop: -> @@ -901,9 +787,6 @@ class TextEditorPresenter @state.content.scrollingVertically = false if @mouseWheelScreenRow? @mouseWheelScreenRow = null - @shouldUpdateLinesState = true - @shouldUpdateLineNumbersState = true - @shouldUpdateCustomGutterDecorationState = true @emitDidUpdateState() @@ -913,13 +796,6 @@ class TextEditorPresenter @pendingScrollLogicalPosition = null if overrideScroll @pendingScrollLeft = scrollLeft - @shouldUpdateHorizontalScrollState = true - @shouldUpdateHiddenInputState = true - @shouldUpdateCursorsState = true - @shouldUpdateOverlaysState = true - @shouldUpdateDecorations = true - @shouldUpdateLinesState = true - @emitDidUpdateState() getScrollLeft: -> @@ -967,43 +843,23 @@ 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 +878,11 @@ 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 +896,16 @@ 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 +921,6 @@ 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 +939,6 @@ 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,30 +1179,21 @@ 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() didDestroyDisposable = cursor.onDidDestroy => @disposables.remove(didChangePositionDisposable) @disposables.remove(didChangeVisibilityDisposable) @disposables.remove(didDestroyDisposable) - @shouldUpdateHiddenInputState = true - @shouldUpdateCursorsState = true - @emitDidUpdateState() @disposables.add(didChangePositionDisposable) @@ -1397,8 +1202,6 @@ class TextEditorPresenter didAddCursor: (cursor) -> @observeCursor(cursor) - @shouldUpdateHiddenInputState = true - @shouldUpdateCursorsState = true @pauseCursorBlinking() @emitDidUpdateState() @@ -1433,17 +1236,6 @@ class TextEditorPresenter @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) -> From d5f67494abf0bd355c39274c8cd26fb784f821f6 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 11 Dec 2015 19:55:01 +0100 Subject: [PATCH 22/45] Add back `shouldUpdateDecorations` --- src/text-editor-presenter.coffee | 53 +++++++++++++++++++++++++------- 1 file changed, 42 insertions(+), 11 deletions(-) diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index f1b1a28a5..96417f039 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -34,6 +34,7 @@ class TextEditorPresenter @observeModel() @observeConfig() @buildState() + @invalidateState() @startBlinkingCursors() if @focused @startReflowing() if @continuousReflow @updating = false @@ -81,8 +82,9 @@ class TextEditorPresenter @updateCommonGutterState() @updateReflowState() - @fetchDecorations() - @updateLineDecorations() + if @shouldUpdateDecorations + @fetchDecorations() + @updateLineDecorations() @updateTilesState() @@ -105,7 +107,7 @@ class TextEditorPresenter @updateScrollbarsState() @updateHiddenInputState() @updateContentState() - @updateHighlightDecorations() + @updateHighlightDecorations() if @shouldUpdateDecorations @updateTilesState() @updateCursorsState() @updateOverlaysState() @@ -114,15 +116,34 @@ class TextEditorPresenter @updateCustomGutterDecorationState() @updating = false + @resetTrackedUpdates() @state + resetTrackedUpdates: -> + @shouldUpdateDecorations = false + + invalidateState: -> + @shouldUpdateDecorations = true + observeModel: -> - @disposables.add @model.onDidChange => @emitDidUpdateState() - @disposables.add @model.onDidUpdateDecorations => @emitDidUpdateState() + @disposables.add @model.onDidChange => + @shouldUpdateDecorations = true + @emitDidUpdateState() + + @disposables.add @model.onDidUpdateDecorations => + @shouldUpdateDecorations = true + @emitDidUpdateState() + @disposables.add @model.onDidChangeGrammar(@didChangeGrammar.bind(this)) - @disposables.add @model.onDidChangePlaceholderText => @emitDidUpdateState() - @disposables.add @model.onDidChangeMini => @emitDidUpdateState() - @disposables.add @model.onDidChangeLineNumberGutterVisible => @emitDidUpdateState() + @disposables.add @model.onDidChangePlaceholderText => + @emitDidUpdateState() + + @disposables.add @model.onDidChangeMini => + @shouldUpdateDecorations = true + @emitDidUpdateState() + + @disposables.add @model.onDidChangeLineNumberGutterVisible => + @emitDidUpdateState() @disposables.add @model.onDidAddCursor(@didAddCursor.bind(this)) @disposables.add @model.onDidRequestAutoscroll(@requestAutoscroll.bind(this)) @@ -291,7 +312,9 @@ class TextEditorPresenter setScreenRowsToMeasure: (screenRows) -> return if not screenRows? or screenRows.length is 0 + @screenRowsToMeasure = screenRows + @shouldUpdateDecorations = true clearScreenRowsToMeasure: -> @screenRowsToMeasure = [] @@ -457,8 +480,7 @@ class TextEditorPresenter didAddGutter: (gutter) -> gutterDisposables = new CompositeDisposable - gutterDisposables.add gutter.onDidChangeVisible => - @emitDidUpdateState() + gutterDisposables.add gutter.onDidChangeVisible => @emitDidUpdateState() gutterDisposables.add gutter.onDidDestroy => @disposables.remove(gutterDisposables) gutterDisposables.dispose() @@ -767,6 +789,7 @@ class TextEditorPresenter @pendingScrollLogicalPosition = null if overrideScroll @pendingScrollTop = scrollTop + @shouldUpdateDecorations = true @emitDidUpdateState() getScrollTop: -> @@ -860,6 +883,7 @@ class TextEditorPresenter unless @explicitHeight is explicitHeight @explicitHeight = explicitHeight @updateHeight() + @shouldUpdateDecorations = true @emitDidUpdateState() updateHeight: -> @@ -878,6 +902,7 @@ class TextEditorPresenter @editorWidthInChars = null @updateScrollbarDimensions() @updateClientWidth() + @shouldUpdateDecorations = true @emitDidUpdateState() setBoundingClientRect: (boundingClientRect) -> @@ -896,6 +921,7 @@ class TextEditorPresenter if @windowWidth isnt width or @windowHeight isnt height @windowWidth = width @windowHeight = height + @emitDidUpdateState() setBackgroundColor: (backgroundColor) -> @@ -921,6 +947,7 @@ class TextEditorPresenter @lineHeight = lineHeight @restoreScrollTopIfNeeded() @model.setLineHeightInPixels(lineHeight) + @shouldUpdateDecorations = true @emitDidUpdateState() setMouseWheelScreenRow: (screenRow) -> @@ -939,6 +966,7 @@ class TextEditorPresenter @characterWidthsChanged() characterWidthsChanged: -> + @shouldUpdateDecorations = true @emitDidUpdateState() hasPixelPositionRequirements: -> @@ -1179,6 +1207,7 @@ class TextEditorPresenter overlayState.itemWidth = itemWidth overlayState.itemHeight = itemHeight overlayState.contentMargin = contentMargin + @emitDidUpdateState() observeCursor: (cursor) -> @@ -1188,12 +1217,14 @@ class TextEditorPresenter @emitDidUpdateState() didChangeVisibilityDisposable = cursor.onDidChangeVisibility => + @emitDidUpdateState() didDestroyDisposable = cursor.onDidDestroy => @disposables.remove(didChangePositionDisposable) @disposables.remove(didChangeVisibilityDisposable) @disposables.remove(didDestroyDisposable) + @emitDidUpdateState() @disposables.add(didChangePositionDisposable) @@ -1235,7 +1266,7 @@ class TextEditorPresenter @pendingScrollLogicalPosition = position @pendingScrollTop = null @pendingScrollLeft = null - + @shouldUpdateDecorations = true @emitDidUpdateState() didChangeFirstVisibleScreenRow: (screenRow) -> From 20e8856df14f0aa293d109972da624e933fcc366 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 11 Dec 2015 19:59:14 +0100 Subject: [PATCH 23/45] :lipstick: Minor stylistic change --- src/text-editor-presenter.coffee | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index 96417f039..3d31f27ae 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -135,15 +135,12 @@ class TextEditorPresenter @emitDidUpdateState() @disposables.add @model.onDidChangeGrammar(@didChangeGrammar.bind(this)) - @disposables.add @model.onDidChangePlaceholderText => - @emitDidUpdateState() - + @disposables.add @model.onDidChangePlaceholderText(@emitDidUpdateState.bind(this)) @disposables.add @model.onDidChangeMini => @shouldUpdateDecorations = true @emitDidUpdateState() - @disposables.add @model.onDidChangeLineNumberGutterVisible => - @emitDidUpdateState() + @disposables.add @model.onDidChangeLineNumberGutterVisible(@emitDidUpdateState.bind(this)) @disposables.add @model.onDidAddCursor(@didAddCursor.bind(this)) @disposables.add @model.onDidRequestAutoscroll(@requestAutoscroll.bind(this)) From 3d8058a9a4de8fbb70b89a84ebb6913ecbfdb56a Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 11 Dec 2015 13:23:25 -0700 Subject: [PATCH 24/45] Remove failing redundant assertions MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The expectNoStateUpdate is sufficient to test the issue in question. Now that most flags are removed, we can’t count on this part of the state not being updated when we request a state recompilation. --- spec/text-editor-presenter-spec.coffee | 8 -------- 1 file changed, 8 deletions(-) diff --git a/spec/text-editor-presenter-spec.coffee b/spec/text-editor-presenter-spec.coffee index effa579b1..f20439d9c 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) From e16430accd7071266168b5562e33f1ee844f99c8 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 14 Dec 2015 11:58:05 +0100 Subject: [PATCH 25/45] :art: Extend isCJKUnifiedIdeograph charset --- src/text-utils.coffee | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/text-utils.coffee b/src/text-utils.coffee index ce8fc864a..af17335aa 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 + 0x4E00 <= charCode <= 0x9FFF isFullWidthForm = (charCode) -> 0xFF01 <= charCode <= 0xFF5E or @@ -70,7 +70,7 @@ isFullWidthForm = (charCode) -> isDoubleWidthCharacter = (character) -> charCode = character.charCodeAt(0) - isJapaneseCharacter(charCode) or + IsJapaneseKanaCharacter(charCode) or isCJKUnifiedIdeograph(charCode) or isFullWidthForm(charCode) From e1e72a85edb0cf2b0d9cbf3ac266bc162ceddae6 Mon Sep 17 00:00:00 2001 From: simurai Date: Mon, 14 Dec 2015 21:33:55 +0900 Subject: [PATCH 26/45] :arrow_up: base16-tomorrow-light-theme@v1.1.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7281d41c0..8220b2adf 100644 --- a/package.json +++ b/package.json @@ -63,7 +63,7 @@ "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-light-theme": "1.1.1", "one-dark-ui": "1.1.9", "one-dark-syntax": "1.1.1", "one-light-syntax": "1.1.1", From c90aabed923c49d2b0d5e9d601d806de41b98c6f Mon Sep 17 00:00:00 2001 From: simurai Date: Mon, 14 Dec 2015 21:34:46 +0900 Subject: [PATCH 27/45] :arrow_up: base16-tomorrow-dark-theme@v1.1.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8220b2adf..52137251c 100644 --- a/package.json +++ b/package.json @@ -62,7 +62,7 @@ "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-dark-theme": "1.1.0", "base16-tomorrow-light-theme": "1.1.1", "one-dark-ui": "1.1.9", "one-dark-syntax": "1.1.1", From 02ab7179ce553658a60719a7f9ca4cc2c8a8cf49 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 14 Dec 2015 16:56:53 -0800 Subject: [PATCH 28/45] Emit state update when model's scroll position is changed --- spec/text-editor-presenter-spec.coffee | 5 +++++ src/text-editor-presenter.coffee | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/spec/text-editor-presenter-spec.coffee b/spec/text-editor-presenter-spec.coffee index effa579b1..2d5a37886 100644 --- a/spec/text-editor-presenter-spec.coffee +++ b/spec/text-editor-presenter-spec.coffee @@ -807,6 +807,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/src/text-editor-presenter.coffee b/src/text-editor-presenter.coffee index 5cfaeebcf..a613ee403 100644 --- a/src/text-editor-presenter.coffee +++ b/src/text-editor-presenter.coffee @@ -1447,7 +1447,7 @@ class TextEditorPresenter @emitDidUpdateState() didChangeFirstVisibleScreenRow: (screenRow) -> - @updateScrollTop(screenRow * @lineHeight) + @setScrollTop(screenRow * @lineHeight) getVerticalScrollMarginInPixels: -> Math.round(@model.getVerticalScrollMargin() * @lineHeight) From c7bff1aec5335aa3e446d13671844f7998a27b52 Mon Sep 17 00:00:00 2001 From: Lee Dohm Date: Fri, 11 Dec 2015 18:07:13 -0800 Subject: [PATCH 29/45] :memo: Add tip about Keybinding Resolver to keymap.cson --- dot-atom/keymap.cson | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/dot-atom/keymap.cson b/dot-atom/keymap.cson index cc8a8228e..2e8b0e0ab 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/v1.3.1/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 From 5ab2594c0e348390a4e9d5d11597cf09ac9ebb98 Mon Sep 17 00:00:00 2001 From: Lee Dohm Date: Fri, 11 Dec 2015 18:47:48 -0800 Subject: [PATCH 30/45] :memo: Point to the latest documentation --- dot-atom/keymap.cson | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dot-atom/keymap.cson b/dot-atom/keymap.cson index 2e8b0e0ab..5b32a993a 100644 --- a/dot-atom/keymap.cson +++ b/dot-atom/keymap.cson @@ -24,7 +24,7 @@ # 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/v1.3.1/hacking-atom-debugging#check-the-keybindings +# * 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 From 56e93c64da47cfec4163a798ba21ad26c2c04045 Mon Sep 17 00:00:00 2001 From: Lee Dohm Date: Mon, 14 Dec 2015 17:52:21 -0800 Subject: [PATCH 31/45] :memo: Add backticks around keybindings --- dot-atom/keymap.cson | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dot-atom/keymap.cson b/dot-atom/keymap.cson index 5b32a993a..10ad345d4 100644 --- a/dot-atom/keymap.cson +++ b/dot-atom/keymap.cson @@ -22,7 +22,7 @@ # * 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 +# 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 # From 8db49fc08dcddaabaa804d5e7a839e0927c2828e Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 15 Dec 2015 17:20:01 +0100 Subject: [PATCH 32/45] :white_check_mark: Document existing behavior --- spec/tokenized-buffer-spec.coffee | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/spec/tokenized-buffer-spec.coffee b/spec/tokenized-buffer-spec.coffee index 76314681c..e617c7956 100644 --- a/spec/tokenized-buffer-spec.coffee +++ b/spec/tokenized-buffer-spec.coffee @@ -38,6 +38,21 @@ describe "TokenizedBuffer", -> expect(tokenizedBufferB.buffer).toBe(tokenizedBufferA.buffer) + it "does not serialize / deserialize the current grammar", -> + buffer = atom.project.bufferForPathSync('sample.js') + 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(autoSelectedGrammar) + describe "when the underlying buffer has no path", -> it "deserializes it searching among the buffers in the current project", -> buffer = atom.project.bufferForPathSync(null) From c1644452261b9e946d3843095cc81d16ab088482 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 15 Dec 2015 13:16:50 -0700 Subject: [PATCH 33/45] 1.4.0-beta1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9dc73dc24..1dffe045b 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "atom", "productName": "Atom", - "version": "1.4.0-beta0", + "version": "1.4.0-beta1", "description": "A hackable text editor for the 21st Century.", "main": "./src/browser/main.js", "repository": { From bef2dd0f277e0b701fddb9c710c7cae380eafa45 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 15 Dec 2015 13:17:05 -0700 Subject: [PATCH 34/45] :arrow_up: fuzzy-finder --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1dffe045b..6c9ab62e7 100644 --- a/package.json +++ b/package.json @@ -88,7 +88,7 @@ "encoding-selector": "0.21.0", "exception-reporting": "0.37.0", "find-and-replace": "0.194.0", - "fuzzy-finder": "0.93.0", + "fuzzy-finder": "0.94.0", "git-diff": "0.57.0", "go-to-line": "0.30.0", "grammar-selector": "0.48.0", From 7fd869df17534052937d257ea940218d7406d6cb Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 15 Dec 2015 13:46:04 -0800 Subject: [PATCH 35/45] :arrow_up: text-buffer --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6c9ab62e7..3fdbb9547 100644 --- a/package.json +++ b/package.json @@ -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.2", "typescript-simple": "1.0.0", "underscore-plus": "^1.6.6", "yargs": "^3.23.0" From e3473fd03cd764e9fa2a9d6fa4e3d64afdbd8971 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Tue, 15 Dec 2015 14:02:12 -0800 Subject: [PATCH 36/45] :arrow_up: command-palette --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3fdbb9547..49ca7f934 100644 --- a/package.json +++ b/package.json @@ -82,7 +82,7 @@ "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", From 19ff676c7bbcf2bf65ce4209340ece0c2ae3b943 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 16 Dec 2015 10:35:23 +0100 Subject: [PATCH 37/45] Serialize grammar for untitled buffers --- spec/tokenized-buffer-spec.coffee | 45 ++++++++++++++++++++++++++++--- src/tokenized-buffer.coffee | 30 ++++++++++++++------- 2 files changed, 62 insertions(+), 13 deletions(-) diff --git a/spec/tokenized-buffer-spec.coffee b/spec/tokenized-buffer-spec.coffee index e617c7956..9c53b3c07 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 }) @@ -39,7 +44,6 @@ describe "TokenizedBuffer", -> expect(tokenizedBufferB.buffer).toBe(tokenizedBufferA.buffer) it "does not serialize / deserialize the current grammar", -> - buffer = atom.project.bufferForPathSync('sample.js') tokenizedBufferA = new TokenizedBuffer({ buffer, config: atom.config, grammarRegistry: atom.grammars, packageManager: atom.packages, assert: atom.assert }) @@ -51,12 +55,13 @@ describe "TokenizedBuffer", -> atom ) - expect(tokenizedBufferB.grammar).toBe(autoSelectedGrammar) + 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 }) @@ -67,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/tokenized-buffer.coffee b/src/tokenized-buffer.coffee index cdafc2869..0fdf4eea8 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) From 92e16ee83531422823ef8749a355812a40bffff7 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 16 Dec 2015 10:18:32 -0800 Subject: [PATCH 38/45] Prepare 1.3.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b34e6e6d2..cc983ea08 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "atom", "productName": "Atom", - "version": "1.3.1", + "version": "1.3.2", "description": "A hackable text editor for the 21st Century.", "main": "./src/browser/main.js", "repository": { From 473e8856a8b209d3dba505024879a128b8777369 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 11 Dec 2015 12:13:17 -0700 Subject: [PATCH 39/45] Merge pull request #10017 from atom/as-sha1-v8-cache Cache v8 code by source file's SHA1 --- package.json | 2 +- spec/file-system-blob-store-spec.coffee | 62 ++++++++++-------- spec/fixtures/native-cache/file-4.js | 1 + spec/native-compile-cache-spec.coffee | 83 +++++++++++++++++++++---- src/file-system-blob-store.js | 23 +++++-- src/native-compile-cache.js | 31 ++++++--- static/index.js | 1 + 7 files changed, 148 insertions(+), 55 deletions(-) create mode 100644 spec/fixtures/native-cache/file-4.js diff --git a/package.json b/package.json index cc983ea08..5710de92d 100644 --- a/package.json +++ b/package.json @@ -18,7 +18,7 @@ "atom-keymap": "^6.1.1", "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", diff --git a/spec/file-system-blob-store-spec.coffee b/spec/file-system-blob-store-spec.coffee index c947259f6..c1cc29449 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 000000000..1b8fd4e15 --- /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 dd720e84d..9d6cb89b4 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/src/file-system-blob-store.js b/src/file-system-blob-store.js index fc6bdddf3..e565a8857 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 a930466a5..50fa71fc3 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/static/index.js b/static/index.js index 2a5bcad3a..8a7761198 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 From a8af1dae5a18fdc81cbed25325792a42c7e4cad9 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 16 Dec 2015 10:21:07 -0800 Subject: [PATCH 40/45] Prepare 1.4.0-beta2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 49ca7f934..a0020de9e 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "atom", "productName": "Atom", - "version": "1.4.0-beta1", + "version": "1.4.0-beta2", "description": "A hackable text editor for the 21st Century.", "main": "./src/browser/main.js", "repository": { From 2eb5eb40f737a549f1caa67cc625d254b0e9c90c Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 16 Dec 2015 15:55:00 -0800 Subject: [PATCH 41/45] :arrow_up: text-buffer (prerelease) --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 371347f4f..0d29dd23f 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,7 @@ "service-hub": "^0.7.0", "source-map-support": "^0.3.2", "temp": "0.8.1", - "text-buffer": "8.1.2", + "text-buffer": "8.1.3-0", "typescript-simple": "1.0.0", "underscore-plus": "^1.6.6", "yargs": "^3.23.0" From ec50eb91e8108d0ec4c38b5b5b1003a4efe0272b Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 16 Dec 2015 19:55:40 -0800 Subject: [PATCH 42/45] :arrow_up: text-buffer --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 0d29dd23f..21e960477 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,7 @@ "service-hub": "^0.7.0", "source-map-support": "^0.3.2", "temp": "0.8.1", - "text-buffer": "8.1.3-0", + "text-buffer": "8.1.3", "typescript-simple": "1.0.0", "underscore-plus": "^1.6.6", "yargs": "^3.23.0" From 78fc3863ef4f81c1c6545525e1c324d84094e077 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 18 Dec 2015 15:08:48 -0700 Subject: [PATCH 43/45] 1.4.0-beta3 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6e1a1390a..2fe3417fd 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "atom", "productName": "Atom", - "version": "1.4.0-beta2", + "version": "1.4.0-beta3", "description": "A hackable text editor for the 21st Century.", "main": "./src/browser/main.js", "repository": { From 385721af60d898854d15bcad37e1b587a3145cac Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 18 Dec 2015 15:09:25 -0700 Subject: [PATCH 44/45] :arrow_up: autocomplete-plus to default to fuzzaldrin-plus --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2fe3417fd..2ca0fc751 100644 --- a/package.json +++ b/package.json @@ -75,7 +75,7 @@ "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", From 6a1f311f394070acfdd35d1e932981973c9de949 Mon Sep 17 00:00:00 2001 From: Wliu <50Wliu@users.noreply.github.com> Date: Fri, 18 Dec 2015 17:18:19 -0500 Subject: [PATCH 45/45] :arrow_up: markdown-preview@0.157.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 42e21a673..f06588f68 100644 --- a/package.json +++ b/package.json @@ -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",