Class: VER::Undo::Record

Inherits:
Struct
  • Object
show all
Defined in:
lib/ver/undo.rb

Overview

Every Record is responsible for one change that it can apply or undo. There is only a very limited set of methods for modifications, as some of the destructive String methods in Ruby can have unpredictable results. In order to undo a change, we have to predict what the change will do before it happens to avoid expensive diff algorithms.

A Record has one parent and a number of childs. If there are any childs, one of them is called current, and is the child that was last active, this way we can provide an intuitive way of choosing a child record to apply when a user wants to redo an undone change.

Record has a direct pointer to the data in the tree, since it has to know about nothing else.

Apart from that, Record also knows the time when it was created, this way you can move forward and backward in time.

Revisions only keep the data necessary to undo/redo a change, not the whole data that was modified, that way it can keep overall memory-usage to a minimum.

The applied property indicates whether or not this change has been applied already.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(widget, parent = nil) ⇒ Record

Returns a new instance of Record.



146
147
148
149
150
151
152
# File 'lib/ver/undo.rb', line 146

def initialize(widget, parent = nil)
  self.widget, self.parent = widget, parent
  self.ctime = Time.now
  self.childs = []
  self.applied = false
  self.separator = false
end

Instance Attribute Details

#appliedObject

Returns the value of attribute applied

Returns:

  • (Object)

    the current value of applied



143
144
145
# File 'lib/ver/undo.rb', line 143

def applied
  @applied
end

#childsObject

Returns the value of attribute childs

Returns:

  • (Object)

    the current value of childs



143
144
145
# File 'lib/ver/undo.rb', line 143

def childs
  @childs
end

#ctimeObject

Returns the value of attribute ctime

Returns:

  • (Object)

    the current value of ctime



143
144
145
# File 'lib/ver/undo.rb', line 143

def ctime
  @ctime
end

#parentObject

Returns the value of attribute parent

Returns:

  • (Object)

    the current value of parent



143
144
145
# File 'lib/ver/undo.rb', line 143

def parent
  @parent
end

#redo_infoObject

Returns the value of attribute redo_info

Returns:

  • (Object)

    the current value of redo_info



143
144
145
# File 'lib/ver/undo.rb', line 143

def redo_info
  @redo_info
end

#separatorObject

Returns the value of attribute separator

Returns:

  • (Object)

    the current value of separator



143
144
145
# File 'lib/ver/undo.rb', line 143

def separator
  @separator
end

#undo_infoObject

Returns the value of attribute undo_info

Returns:

  • (Object)

    the current value of undo_info



143
144
145
# File 'lib/ver/undo.rb', line 143

def undo_info
  @undo_info
end

#widgetObject

Returns the value of attribute widget

Returns:

  • (Object)

    the current value of widget



143
144
145
# File 'lib/ver/undo.rb', line 143

def widget
  @widget
end

Instance Method Details

#applied?Boolean

Returns:

  • (Boolean)


280
281
282
# File 'lib/ver/undo.rb', line 280

def applied?
  applied
end

#compact!Object



205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
# File 'lib/ver/undo.rb', line 205

def compact!
  return if separator
  return unless parent = self.parent

  pundo_from, pundo_to, pundo_string = parent.undo_info
  sundo_from, sundo_to, sundo_string = undo_info

  predo_name, *predo_args = parent.redo_info
  sredo_name, *sredo_args = redo_info

  # only compact identical methods
  return unless predo_name == sredo_name

  case predo_name
  when :insert
    predo_pos, predo_string = predo_args
    sredo_pos, sredo_string = sredo_args

    # the records have to be consecutive so they can still be applied by a
    # single undo/redo
    consecutive = (predo_pos + predo_string.size) == sredo_pos
    return parent.compact! unless consecutive

    redo_string = "#{predo_string}#{sredo_string}"
    self.redo_info = [:insert, predo_pos, redo_string]

    undo_string = "#{pundo_string}#{sundo_string}"
    self.undo_info = [pundo_from, sundo_to, undo_string]
  when :replace
    predo_from, predo_to, predo_string = predo_args
    sredo_from, sredo_to, sredo_string = sredo_args

    # the records have to be consecutive so they can still be applied by a
    # single undo/redo
    consecutive = predo_to == sredo_from
    return parent.compact! unless consecutive

    redo_string = "#{predo_string}#{sredo_string}"
    self.redo_info = [:replace, predo_from, sredo_to, undo_string]

    undo_string = "#{pundo_string}#{sundo_string}"
    self.undo_info = [pundo_from, sundo_to, undo_string]
  when :delete
    predo_from, predo_to = predo_args
    sredo_from, sredo_to = sredo_args

    consecutive = predo_to == sredo_from
    return parent.compact! unless consecutive

    self.redo_info = [:delete, predo_from, sredo_to]

    undo_string = "#{sundo_string}#{pundo_string}"
    self.undo_info = [pundo_from, sundo_to, undo_string]
  else
    return
  end

  # the parent of our parent (grandparent) becomes our parent
  self.parent = grandparent = parent.parent

  # recurse into a new compact cycle if we have a grandparent
  if grandparent
    grandparent.next = self
    compact!
  end
end

#delete(from, to) ⇒ Object



178
179
180
181
182
183
184
185
186
187
188
189
# File 'lib/ver/undo.rb', line 178

def delete(from, to)
  from = widget.index(from) unless from.respond_to?(:to_index)
  to = widget.index(to) unless to.respond_to?(:to_index)

  data = widget.get(from, to)
  widget.execute_only(:delete, from, to)
  widget.touch!(*from.upto(to))

  self.redo_info = [:delete, from, to]
  self.undo_info = [from, from, data]
  self.applied = true
end

#insert(pos, string) ⇒ Object



154
155
156
157
158
159
160
161
162
163
# File 'lib/ver/undo.rb', line 154

def insert(pos, string)
  pos = widget.index(pos) unless pos.respond_to?(:to_index)

  widget.execute_only(:insert, pos, string)
  widget.touch!(pos)

  self.redo_info = [:insert, pos, string]
  self.undo_info = [pos, pos + string.size, '']
  self.applied = true
end

#inspectObject



284
285
286
# File 'lib/ver/undo.rb', line 284

def inspect
  "#<Undo::Record sep=%p undo=%p redo=%p>" % [separator, undo_info, redo_info]
end

#nextObject



276
277
278
# File 'lib/ver/undo.rb', line 276

def next
  childs.first
end

#next=(child) ⇒ Object



272
273
274
# File 'lib/ver/undo.rb', line 272

def next=(child)
  childs.unshift(childs.delete(child) || child)
end

#redoObject



200
201
202
203
# File 'lib/ver/undo.rb', line 200

def redo
  return unless redo_info && !applied
  send(*redo_info)
end

#replace(from, to, string) ⇒ Object



165
166
167
168
169
170
171
172
173
174
175
176
# File 'lib/ver/undo.rb', line 165

def replace(from, to, string)
  from = widget.index(from) unless from.respond_to?(:to_index)
  to = widget.index(to) unless to.respond_to?(:to_index)

  data = widget.get(from, to)
  widget.execute_only(:replace, from, to, string)
  widget.touch!(*from.upto(to))

  self.redo_info = [:replace, from, to, string]
  self.undo_info = [from, from + string.size, data]
  self.applied = true
end

#undoObject



191
192
193
194
195
196
197
198
# File 'lib/ver/undo.rb', line 191

def undo
  return unless undo_info && applied

  from, to, string = undo_info
  widget.execute_only(:replace, from, to, string)

  self.applied = false
end