Skip to content
This repository was archived by the owner on Dec 15, 2022. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
"fs-plus": "^2.0.0",
"grim": "^1.2.1",
"interval-skip-list": "^2.0.1",
"pathwatcher": "^5.0.0",
"pathwatcher": "^5.0.1",
"serializable": "^1.0.0",
"span-skip-list": "~0.2.0",
"underscore-plus": "^1.0.0"
Expand Down
35 changes: 35 additions & 0 deletions spec/text-buffer-spec.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,35 @@ describe "TextBuffer", ->
buffer.undo()
expect(buffer.getText()).toBe "hello\nworld\r\nhow are you doing?"

it "does not allow the undo stack to grow without bound", ->
buffer = new TextBuffer(maxUndoEntries: 12)

# A transaction with 1 change uses 3 undo entries, so we can undo 4 of
# these transactions.
for i in [1...10]
buffer.append("#{i}\n")
expect(buffer.getLineCount()).toBe 10

undoCount = 0
undoCount++ while buffer.undo()
expect(undoCount).toBe 4
expect(buffer.getLineCount()).toBe 6

# A transaction with 2 changes uses 4 undo entries, so we can undo 3 of
# these transactions.
buffer.setText("")
buffer.clearUndoStack()
for i in [1...10]
buffer.transact ->
buffer.append(String(i))
buffer.append("\n")
expect(buffer.getLineCount()).toBe 10

undoCount = 0
undoCount++ while buffer.undo()
expect(undoCount).toBe 3
expect(buffer.getLineCount()).toBe 7

describe "transactions", ->
now = null

Expand Down Expand Up @@ -866,6 +895,8 @@ describe "TextBuffer", ->

describe "when the serialized buffer had no unsaved changes", ->
it "loads the current contents of the file at the serialized path", ->
buffer.append("!")
buffer.save()
expect(buffer.isModified()).toBeFalsy()
buffer2 = buffer.testSerialization()

Expand All @@ -877,6 +908,10 @@ describe "TextBuffer", ->
expect(buffer2.getPath()).toBe(buffer.getPath())
expect(buffer2.getText()).toBe(buffer.getText())

buffer.undo()
buffer2.undo()
expect(buffer2.getText()).toBe(buffer.getText())

describe "when the serialized buffer had unsaved changes", ->
describe "when the disk contents were changed since serialization", ->
it "loads the disk contents instead of the previous unsaved state", ->
Expand Down
23 changes: 21 additions & 2 deletions src/history.coffee
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
SerializationVersion = 2
SerializationVersion = 3

class Checkpoint
constructor: (@id, @snapshot, @isBoundary) ->
Expand All @@ -22,7 +22,7 @@ class History
history.deserialize(state)
history

constructor: (@delegate) ->
constructor: (@delegate, @maxUndoEntries) ->
@nextCheckpointId = 0
@undoStack = []
@redoStack = []
Expand Down Expand Up @@ -93,6 +93,24 @@ class History
@undoStack.push(change)
@clearRedoStack()

if @undoStack.length - @maxUndoEntries > 0
spliceIndex = null
withinGroup = false
for entry, i in @undoStack
break if spliceIndex?
switch entry.constructor
when GroupStart
if withinGroup
throw new Error("Invalid undo stack state")
else
withinGroup = true
when GroupEnd
if withinGroup
spliceIndex = i
else
throw new Error("Invalid undo stack state")
@undoStack.splice(0, spliceIndex + 1) if spliceIndex?

popUndoStack: ->
snapshotBelow = null
spliceIndex = null
Expand Down Expand Up @@ -225,6 +243,7 @@ class History
deserialize: (state) ->
return unless state.version is SerializationVersion
@nextCheckpointId = state.nextCheckpointId
@maxUndoEntries = state.maxUndoEntries
@undoStack = @deserializeStack(state.undoStack)
@redoStack = @deserializeStack(state.redoStack)

Expand Down
21 changes: 10 additions & 11 deletions src/text-buffer.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ class TextBuffer
refcount: 0
fileSubscriptions: null
backwardsScanChunkSize: 8000
defaultMaxUndoEntries: 10000
changeCount: 0

###
Expand All @@ -97,19 +98,18 @@ class TextBuffer
@lineEndings = ['']
@offsetIndex = new SpanSkipList('rows', 'characters')
@setTextInRange([[0, 0], [0, 0]], text ? params?.text ? '', normalizeLineEndings: false)
@history = params?.history ? new History(this)
maxUndoEntries = params?.maxUndoEntries ? @defaultMaxUndoEntries
@history = params?.history ? new History(this, maxUndoEntries)
@markerStore = params?.markerStore ? new MarkerStore(this)
@setEncoding(params?.encoding)
@setPreferredLineEnding(params?.preferredLineEnding)

@loaded = false
@transactCallDepth = 0
@digestWhenLastPersisted = params?.digestWhenLastPersisted ? false
@modifiedWhenLastPersisted = params?.modifiedWhenLastPersisted ? false
@useSerializedText = @modifiedWhenLastPersisted isnt false

@setPath(params.filePath) if params?.filePath
@load(true) if params?.load
@load() if params?.load

# Called by {Serializable} mixin during deserialization.
deserializeParams: (params) ->
Expand All @@ -125,7 +125,6 @@ class TextBuffer
history: @history.serialize()
encoding: @getEncoding()
filePath: @getPath()
modifiedWhenLastPersisted: @isModified()
digestWhenLastPersisted: @file?.getDigestSync()
preferredLineEnding: @preferredLineEnding

Expand Down Expand Up @@ -1254,7 +1253,7 @@ class TextBuffer
@emit 'will-reload' if Grim.includeDeprecatedAPIs
if clearHistory
@clearUndoStack()
@setTextInRange(@getRange(), @cachedDiskContents, normalizeLineEndings: false, undo: 'skip')
@setTextInRange(@getRange(), @cachedDiskContents ? "", normalizeLineEndings: false, undo: 'skip')
else
@setTextViaDiff(@cachedDiskContents)
@emitModifiedStatusChanged(false)
Expand Down Expand Up @@ -1329,16 +1328,16 @@ class TextBuffer
@updateCachedDiskContentsSync()
@finishLoading()

load: (clearHistory=false) ->
@updateCachedDiskContents().then => @finishLoading(clearHistory)
load: ->
@updateCachedDiskContents().then => @finishLoading()

finishLoading: (clearHistory) ->
finishLoading: ->
if @isAlive()
@loaded = true
if @useSerializedText and @digestWhenLastPersisted is @file?.getDigestSync()
if @digestWhenLastPersisted is @file?.getDigestSync()
@emitModifiedStatusChanged(true)
else
@reload(clearHistory)
@reload(true)
this

destroy: ->
Expand Down