From 32bc8a6258a69d84815698e9ca6aae40d9a5ad2d Mon Sep 17 00:00:00 2001 From: probablycorey Date: Fri, 25 Oct 2013 10:21:11 -0700 Subject: [PATCH 01/10] Remove initialText option from TextBuffer constructor --- spec/tokenized-buffer-spec.coffee | 8 +++++--- src/project.coffee | 17 ++++++++--------- src/text-buffer.coffee | 4 ++-- 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/spec/tokenized-buffer-spec.coffee b/spec/tokenized-buffer-spec.coffee index dd36ec316..9493271a8 100644 --- a/spec/tokenized-buffer-spec.coffee +++ b/spec/tokenized-buffer-spec.coffee @@ -1,7 +1,7 @@ TokenizedBuffer = require '../src/tokenized-buffer' {_} = require 'atom' -describe "TokenizedBuffer", -> +fdescribe "TokenizedBuffer", -> [tokenizedBuffer, buffer, changeHandler] = [] beforeEach -> @@ -350,7 +350,8 @@ describe "TokenizedBuffer", -> describe "when the buffer contains surrogate pairs", -> beforeEach -> atom.activatePackage('language-javascript', sync: true) - buffer = project.buildBufferSync 'sample-with-pairs.js', """ + buffer = project.buildBufferSync 'sample-with-pairs.js' + buffer.setText """ 'abc\uD835\uDF97def' //\uD835\uDF97xyz """ @@ -389,7 +390,8 @@ describe "TokenizedBuffer", -> atom.activatePackage('language-ruby-on-rails', sync: true) atom.activatePackage('language-ruby', sync: true) - buffer = project.bufferForPathSync(null, "
<%= User.find(2).full_name %>
") + buffer = project.bufferForPathSync() + buffer.setText "
<%= User.find(2).full_name %>
" tokenizedBuffer = new TokenizedBuffer({buffer}) tokenizedBuffer.setGrammar(syntax.selectGrammar('test.erb')) fullyTokenize(tokenizedBuffer) diff --git a/src/project.coffee b/src/project.coffee index 3c5456472..b11d5d937 100644 --- a/src/project.coffee +++ b/src/project.coffee @@ -219,13 +219,13 @@ class Project new Array(@buffers...) # Private: Only to be used in specs - bufferForPathSync: (filePath, text) -> + bufferForPathSync: (filePath) -> absoluteFilePath = @resolve(filePath) if filePath existingBuffer = _.find @buffers, (buffer) -> buffer.getPath() == absoluteFilePath - existingBuffer ? @buildBufferSync(absoluteFilePath, text) + existingBuffer ? @buildBufferSync(absoluteFilePath) # Private: Given a file path, this retrieves or creates a new {TextBuffer}. # @@ -233,23 +233,22 @@ class Project # `text` is used as the contents of the new buffer. # # filePath - A {String} representing a path. If `null`, an "Untitled" buffer is created. - # text - The {String} text to use as a buffer, if the file doesn't have any contents # # Returns a promise that resolves to the {TextBuffer}. - bufferForPath: (filePath, text) -> + bufferForPath: (filePath) -> absoluteFilePath = @resolve(filePath) if absoluteFilePath existingBuffer = _.find @buffers, (buffer) -> buffer.getPath() == absoluteFilePath - Q(existingBuffer ? @buildBuffer(absoluteFilePath, text)) + Q(existingBuffer ? @buildBuffer(absoluteFilePath)) # Private: bufferForId: (id) -> _.find @buffers, (buffer) -> buffer.id is id # Private: DEPRECATED - buildBufferSync: (absoluteFilePath, initialText) -> - buffer = new TextBuffer({project: this, filePath: absoluteFilePath, initialText}) + buildBufferSync: (absoluteFilePath) -> + buffer = new TextBuffer({project: this, filePath: absoluteFilePath}) buffer.loadSync() @addBuffer(buffer) buffer @@ -260,8 +259,8 @@ class Project # text - The {String} text to use as a buffer # # Returns a promise that resolves to the {TextBuffer}. - buildBuffer: (absoluteFilePath, initialText) -> - buffer = new TextBuffer({project: this, filePath: absoluteFilePath, initialText}) + buildBuffer: (absoluteFilePath) -> + buffer = new TextBuffer({project: this, filePath: absoluteFilePath}) buffer.load().then (buffer) => @addBuffer(buffer) buffer diff --git a/src/text-buffer.coffee b/src/text-buffer.coffee index 99fa698a7..43912dd1a 100644 --- a/src/text-buffer.coffee +++ b/src/text-buffer.coffee @@ -49,7 +49,7 @@ class TextBuffer @text = @state.get('text') @loadFromDisk = @state.get('isModified') == false else - {@project, filePath, initialText} = optionsOrState + {@project, filePath} = optionsOrState @text = site.createDocument(initialText ? '', shareStrings: true) @id = guid.create().toString() @state = site.createDocument @@ -57,7 +57,7 @@ class TextBuffer deserializer: @constructor.name version: @constructor.version text: @text - @loadFromDisk = not initialText + @loadFromDisk = true @loaded = false @subscribe @text, 'changed', @handleTextChange From 8956881192b08cdc20999dbc12c6c29518b2683b Mon Sep 17 00:00:00 2001 From: probablycorey Date: Fri, 25 Oct 2013 10:58:21 -0700 Subject: [PATCH 02/10] Only load serialized content if the disk content's have not changed. --- spec/text-buffer-spec.coffee | 52 ++++++++++++++++++++++++++---------- src/file.coffee | 11 ++++++++ src/text-buffer.coffee | 7 ++--- 3 files changed, 53 insertions(+), 17 deletions(-) diff --git a/spec/text-buffer-spec.coffee b/spec/text-buffer-spec.coffee index 660309386..365ca20d9 100644 --- a/spec/text-buffer-spec.coffee +++ b/spec/text-buffer-spec.coffee @@ -949,24 +949,48 @@ describe 'TextBuffer', -> expect(buffer2.getText()).toBe(buffer.getText()) describe "when the serialized buffer had unsaved changes", -> - it "restores the previous unsaved state of the buffer", -> - previousText = buffer.getText() - buffer.setText("abc") + describe "when the disk contents were changed since serialization", -> + it "loads the disk contents instead of the previous unsaved state", -> + buffer.release() - state = buffer.serialize() - expect(state.getObject('text')).toBe 'abc' + filePath = temp.openSync('atom').path + fs.writeSync(filePath, "words") + buffer = project.openSync(filePath).buffer + buffer.setText("BUFFER CHANGE") - buffer2 = deserialize(state, {project}) + state = buffer.serialize() + expect(state.getObject('text')).toBe 'BUFFER CHANGE' + fs.writeSync(filePath, "DISK CHANGE") - waitsFor -> - buffer2.cachedDiskContents + buffer2 = deserialize(state, {project}) - runs -> - expect(buffer2.getPath()).toBe(buffer.getPath()) - expect(buffer2.getText()).toBe(buffer.getText()) - expect(buffer2.isModified()).toBeTruthy() - buffer2.setText(previousText) - expect(buffer2.isModified()).toBeFalsy() + waitsFor -> + buffer2.cachedDiskContents + + runs -> + expect(buffer2.getPath()).toBe(buffer.getPath()) + expect(buffer2.getText()).toBe("DISK CHANGE") + expect(buffer2.isModified()).toBeFalsy() + + describe "when the disk contents are the same since serialization", -> + it "restores the previous unsaved state of the buffer", -> + previousText = buffer.getText() + buffer.setText("abc") + + state = buffer.serialize() + expect(state.getObject('text')).toBe 'abc' + + buffer2 = deserialize(state, {project}) + + waitsFor -> + buffer2.cachedDiskContents + + runs -> + expect(buffer2.getPath()).toBe(buffer.getPath()) + expect(buffer2.getText()).toBe(buffer.getText()) + expect(buffer2.isModified()).toBeTruthy() + buffer2.setText(previousText) + expect(buffer2.isModified()).toBeFalsy() describe "when the serialized buffer was unsaved and had no path", -> it "restores the previous unsaved state of the buffer", -> diff --git a/src/file.coffee b/src/file.coffee index 5e7895410..9ea1f5a0a 100644 --- a/src/file.coffee +++ b/src/file.coffee @@ -1,3 +1,4 @@ +crypto = require 'crypto' path = require 'path' pathWatcher = require 'pathwatcher' Q = require 'q' @@ -68,6 +69,9 @@ class File else @cachedContents + @setDigest(@cachedContents) + @cachedContents + # Public: Reads the contents of the file. # # * flushCache: @@ -101,12 +105,19 @@ class File promise = Q(@cachedContents) promise.then (contents) => + @setDigest(contents) @cachedContents = contents # Public: Returns whether the file exists. exists: -> fsUtils.exists(@getPath()) + setDigest: (contents)-> + @digest = crypto.createHash('sha1').update(contents ? '').digest('hex') + + getDigest: -> + @digest ? @setDigest(@readSync()) + # Private: handleNativeChangeEvent: (eventType, path) -> if eventType is "delete" diff --git a/src/text-buffer.coffee b/src/text-buffer.coffee index 43912dd1a..767abfa6b 100644 --- a/src/text-buffer.coffee +++ b/src/text-buffer.coffee @@ -1,4 +1,5 @@ {Emitter, Subscriber} = require 'emissary' +crypto = require 'crypto' guid = require 'guid' Q = require 'q' {P} = require 'scandal' @@ -57,7 +58,6 @@ class TextBuffer deserializer: @constructor.name version: @constructor.version text: @text - @loadFromDisk = true @loaded = false @subscribe @text, 'changed', @handleTextChange @@ -68,12 +68,12 @@ class TextBuffer loadSync: -> @updateCachedDiskContentsSync() - @reload() if @loadFromDisk + @reload() if @loadFromDisk or @state.get('diskContentsDigest') != @file?.getDigest() @text.clearUndoStack() load: -> @updateCachedDiskContents().then => - @reload() if @loadFromDisk + @reload() if @loadFromDisk or @state.get('diskContentsDigest') != @file?.getDigest() @text.clearUndoStack() this @@ -108,6 +108,7 @@ class TextBuffer serialize: -> state = @state.clone() state.set('isModified', @isModified()) + state.set('diskContentsDigest', @file.getDigest()) if @file for marker in state.get('text').getMarkers() when marker.isRemote() marker.destroy() state From 2d40cb09d15574c3de2fde0a74dc44663aaf9242 Mon Sep 17 00:00:00 2001 From: probablycorey Date: Fri, 25 Oct 2013 14:03:27 -0700 Subject: [PATCH 03/10] Remove focused spec --- spec/tokenized-buffer-spec.coffee | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/tokenized-buffer-spec.coffee b/spec/tokenized-buffer-spec.coffee index 9493271a8..096060bec 100644 --- a/spec/tokenized-buffer-spec.coffee +++ b/spec/tokenized-buffer-spec.coffee @@ -1,7 +1,7 @@ TokenizedBuffer = require '../src/tokenized-buffer' {_} = require 'atom' -fdescribe "TokenizedBuffer", -> +describe "TokenizedBuffer", -> [tokenizedBuffer, buffer, changeHandler] = [] beforeEach -> From 5cee47c20797fa9c39742d9e2bc16d2cc4ec2138 Mon Sep 17 00:00:00 2001 From: probablycorey Date: Fri, 25 Oct 2013 14:21:23 -0700 Subject: [PATCH 04/10] Fix replication spec Don't rely on serialization to handle modifications after documents are connected. --- spec/text-buffer-replication-spec.coffee | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/spec/text-buffer-replication-spec.coffee b/spec/text-buffer-replication-spec.coffee index 4bbff2e5e..eb8da8c7c 100644 --- a/spec/text-buffer-replication-spec.coffee +++ b/spec/text-buffer-replication-spec.coffee @@ -5,7 +5,6 @@ describe "TextBuffer replication", -> beforeEach -> buffer1 = project.buildBufferSync('sample.js') - buffer1.insert([0, 0], 'changed\n') doc1 = buffer1.getState() doc2 = doc1.clone(new Site(2)) doc1.connect(doc2) @@ -14,6 +13,9 @@ describe "TextBuffer replication", -> waitsFor -> buffer1.loaded and buffer2.loaded + runs -> + buffer1.insert([0, 0], 'changed\n') + afterEach -> buffer1.destroy() buffer2.destroy() From feed72b68e48785009f718adcf9d8ee5b5bdf74d Mon Sep 17 00:00:00 2001 From: probablycorey Date: Fri, 25 Oct 2013 14:21:39 -0700 Subject: [PATCH 05/10] Change @loadFromDisk to @useSerializedText --- src/text-buffer.coffee | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/text-buffer.coffee b/src/text-buffer.coffee index 767abfa6b..81d328b73 100644 --- a/src/text-buffer.coffee +++ b/src/text-buffer.coffee @@ -48,7 +48,7 @@ class TextBuffer @id = @state.get('id') filePath = @state.get('relativePath') @text = @state.get('text') - @loadFromDisk = @state.get('isModified') == false + @useSerializedText = @state.get('isModified') != false else {@project, filePath} = optionsOrState @text = site.createDocument(initialText ? '', shareStrings: true) @@ -68,12 +68,12 @@ class TextBuffer loadSync: -> @updateCachedDiskContentsSync() - @reload() if @loadFromDisk or @state.get('diskContentsDigest') != @file?.getDigest() + @reload() unless @useSerializedText and @state.get('diskContentsDigest') == @file?.getDigest() @text.clearUndoStack() load: -> @updateCachedDiskContents().then => - @reload() if @loadFromDisk or @state.get('diskContentsDigest') != @file?.getDigest() + @reload() unless @useSerializedText and @state.get('diskContentsDigest') == @file?.getDigest() @text.clearUndoStack() this From c77b44d5ef5aabd5da98b725bf7f348195c3a0c1 Mon Sep 17 00:00:00 2001 From: probablycorey Date: Fri, 25 Oct 2013 14:29:59 -0700 Subject: [PATCH 06/10] Move variable assignment --- src/text-buffer.coffee | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/text-buffer.coffee b/src/text-buffer.coffee index 81d328b73..182c66167 100644 --- a/src/text-buffer.coffee +++ b/src/text-buffer.coffee @@ -70,11 +70,13 @@ class TextBuffer @updateCachedDiskContentsSync() @reload() unless @useSerializedText and @state.get('diskContentsDigest') == @file?.getDigest() @text.clearUndoStack() + @loaded = true load: -> @updateCachedDiskContents().then => @reload() unless @useSerializedText and @state.get('diskContentsDigest') == @file?.getDigest() @text.clearUndoStack() + @loaded = true this ### Internal ### @@ -159,13 +161,11 @@ class TextBuffer # Private: Rereads the contents of the file, and stores them in the cache. updateCachedDiskContentsSync: -> - @loaded = true @cachedDiskContents = @file?.readSync() ? "" # Private: Rereads the contents of the file, and stores them in the cache. updateCachedDiskContents: -> Q(@file?.read() ? "").then (contents) => - @loaded = true @cachedDiskContents = contents # Gets the file's basename--that is, the file without any directory information. From b5b07c10520982945de0b8dfeb654de3f9fe1049 Mon Sep 17 00:00:00 2001 From: probablycorey Date: Fri, 25 Oct 2013 14:40:53 -0700 Subject: [PATCH 07/10] Emit modified-status-changed event when using serialized text --- src/text-buffer.coffee | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/src/text-buffer.coffee b/src/text-buffer.coffee index 182c66167..964f42ae7 100644 --- a/src/text-buffer.coffee +++ b/src/text-buffer.coffee @@ -68,15 +68,21 @@ class TextBuffer loadSync: -> @updateCachedDiskContentsSync() - @reload() unless @useSerializedText and @state.get('diskContentsDigest') == @file?.getDigest() - @text.clearUndoStack() @loaded = true + if @useSerializedText and @state.get('diskContentsDigest') == @file?.getDigest() + @emitModifiedStatusChanged(true) + else + @reload() + @text.clearUndoStack() load: -> @updateCachedDiskContents().then => - @reload() unless @useSerializedText and @state.get('diskContentsDigest') == @file?.getDigest() - @text.clearUndoStack() @loaded = true + if @useSerializedText and @state.get('diskContentsDigest') == @file?.getDigest() + @emitModifiedStatusChanged(true) + else + @reload() + @text.clearUndoStack() this ### Internal ### From bb281ce5afe1972db3e1a7e9b08a85e922259a53 Mon Sep 17 00:00:00 2001 From: probablycorey Date: Fri, 25 Oct 2013 15:19:20 -0700 Subject: [PATCH 08/10] :lipstick: --- spec/text-buffer-spec.coffee | 2 +- src/file.coffee | 2 +- src/text-buffer.coffee | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/text-buffer-spec.coffee b/spec/text-buffer-spec.coffee index 365ca20d9..e2911d087 100644 --- a/spec/text-buffer-spec.coffee +++ b/spec/text-buffer-spec.coffee @@ -955,7 +955,7 @@ describe 'TextBuffer', -> filePath = temp.openSync('atom').path fs.writeSync(filePath, "words") - buffer = project.openSync(filePath).buffer + {buffer} = project.openSync(filePath) buffer.setText("BUFFER CHANGE") state = buffer.serialize() diff --git a/src/file.coffee b/src/file.coffee index 9ea1f5a0a..2a671ed35 100644 --- a/src/file.coffee +++ b/src/file.coffee @@ -112,7 +112,7 @@ class File exists: -> fsUtils.exists(@getPath()) - setDigest: (contents)-> + setDigest: (contents) -> @digest = crypto.createHash('sha1').update(contents ? '').digest('hex') getDigest: -> diff --git a/src/text-buffer.coffee b/src/text-buffer.coffee index 964f42ae7..9cd63361c 100644 --- a/src/text-buffer.coffee +++ b/src/text-buffer.coffee @@ -1,5 +1,5 @@ -{Emitter, Subscriber} = require 'emissary' crypto = require 'crypto' +{Emitter, Subscriber} = require 'emissary' guid = require 'guid' Q = require 'q' {P} = require 'scandal' From 7002b4e5f69fcfc22e33d18db06cc6c8ff8bfe4f Mon Sep 17 00:00:00 2001 From: probablycorey Date: Fri, 25 Oct 2013 15:26:56 -0700 Subject: [PATCH 09/10] Factor out common buffer loading code --- src/text-buffer.coffee | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/src/text-buffer.coffee b/src/text-buffer.coffee index 9cd63361c..176d6b63b 100644 --- a/src/text-buffer.coffee +++ b/src/text-buffer.coffee @@ -68,22 +68,19 @@ class TextBuffer loadSync: -> @updateCachedDiskContentsSync() + @finishLoading() + + load: -> + @updateCachedDiskContents().then => @finishLoading() + + finishLoading: -> @loaded = true if @useSerializedText and @state.get('diskContentsDigest') == @file?.getDigest() @emitModifiedStatusChanged(true) else @reload() @text.clearUndoStack() - - load: -> - @updateCachedDiskContents().then => - @loaded = true - if @useSerializedText and @state.get('diskContentsDigest') == @file?.getDigest() - @emitModifiedStatusChanged(true) - else - @reload() - @text.clearUndoStack() - this + this ### Internal ### From 0dd0c3991846c659462114176397285c86e0f1c2 Mon Sep 17 00:00:00 2001 From: probablycorey Date: Fri, 25 Oct 2013 15:29:52 -0700 Subject: [PATCH 10/10] Update comment --- src/text-buffer.coffee | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/text-buffer.coffee b/src/text-buffer.coffee index 176d6b63b..d4cc854f8 100644 --- a/src/text-buffer.coffee +++ b/src/text-buffer.coffee @@ -39,8 +39,8 @@ class TextBuffer # Creates a new buffer. # - # path - A {String} representing the file path - # initialText - A {String} setting the starting text + # * optionsOrState - An {Object} or a telepath.Document + # + filePath - A {String} representing the file path constructor: (optionsOrState={}, params={}) -> if optionsOrState instanceof telepath.Document {@project} = params