ver 2009.11.29 → 2009.12.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/AUTHORS +6 -0
- data/CHANGELOG +353 -1
- data/LICENSE +18 -0
- data/MANIFEST +11 -1
- data/Rakefile +2 -1
- data/bin/ver +3 -12
- data/config/detect.rb +1 -1
- data/config/keymap/diakonos.rb +181 -0
- data/config/keymap/emacs.rb +24 -24
- data/config/keymap/vim.rb +162 -127
- data/config/rc.rb +29 -14
- data/config/syntax/Nemerle.json +3 -3
- data/lib/ver.rb +88 -134
- data/lib/ver/entry.rb +5 -0
- data/lib/ver/exception_view.rb +97 -0
- data/lib/ver/hover_completion.rb +14 -7
- data/lib/ver/keymap.rb +30 -1
- data/lib/ver/layout.rb +20 -14
- data/lib/ver/methods.rb +6 -15
- data/lib/ver/methods/bookmark.rb +189 -0
- data/lib/ver/methods/completion.rb +2 -2
- data/lib/ver/methods/control.rb +109 -26
- data/lib/ver/methods/ctags.rb +28 -4
- data/lib/ver/methods/delete.rb +85 -4
- data/lib/ver/methods/insert.rb +73 -52
- data/lib/ver/methods/move.rb +122 -35
- data/lib/ver/methods/open.rb +4 -43
- data/lib/ver/methods/search.rb +46 -17
- data/lib/ver/methods/select.rb +121 -24
- data/lib/ver/methods/undo.rb +23 -0
- data/lib/ver/methods/views.rb +5 -0
- data/lib/ver/mode.rb +18 -17
- data/lib/ver/status.rb +2 -2
- data/lib/ver/status/context.rb +166 -0
- data/lib/ver/text.rb +43 -81
- data/lib/ver/text/index.rb +24 -7
- data/lib/ver/undo.rb +289 -0
- data/lib/ver/vendor/sized_array.rb +70 -0
- data/lib/ver/vendor/textpow.rb +6 -1
- data/lib/ver/version.rb +3 -0
- data/lib/ver/view.rb +11 -8
- data/lib/ver/view/list/grep.rb +15 -4
- data/lib/ver/view/term.rb +9 -3
- data/spec/helper.rb +94 -0
- data/ver.gemspec +9 -6
- metadata +25 -5
- data/spec/keymap.rb +0 -224
data/lib/ver/undo.rb
ADDED
@@ -0,0 +1,289 @@
|
|
1
|
+
module VER
|
2
|
+
module Undo
|
3
|
+
# The Tree keeps track of the current Record and creates new records.
|
4
|
+
#
|
5
|
+
# It maintains a pointer to the widget and the current record in the tree.
|
6
|
+
# The current record is the record that was last applied.
|
7
|
+
#
|
8
|
+
# When a record is undone:
|
9
|
+
# If there is a parent, it becomes the new current record.
|
10
|
+
# If there is no parent, it stays the current record, only flagged as
|
11
|
+
# unapplied.
|
12
|
+
# it's parent (if there is one), becomes the new
|
13
|
+
# current record.
|
14
|
+
#
|
15
|
+
# When a record is redone, it's current child (if there is one), becomes the
|
16
|
+
# new current record.
|
17
|
+
#
|
18
|
+
# The Tree doesn't have a maximum depth at the moment, but this will be
|
19
|
+
# added, when old records are pruned, only the record at depth+1 that is
|
20
|
+
# current stays.
|
21
|
+
#
|
22
|
+
# Eventually we have to separate records and undo/redo stepping.
|
23
|
+
# Some operations involve more than one record, but should be undone/redone
|
24
|
+
# together.
|
25
|
+
# It would also be handy if these operations could be identified
|
26
|
+
# automagically (like multiple operations within one record block).
|
27
|
+
class Tree < Struct.new(:widget, :applied, :pending)
|
28
|
+
def record_multi
|
29
|
+
AutoSeparator.new self do |auto_separator|
|
30
|
+
yield auto_separator
|
31
|
+
end
|
32
|
+
|
33
|
+
compact!
|
34
|
+
end
|
35
|
+
|
36
|
+
def record
|
37
|
+
current = Record.new(widget, applied)
|
38
|
+
|
39
|
+
yield current
|
40
|
+
|
41
|
+
applied.next = current if applied = self.applied
|
42
|
+
|
43
|
+
self.applied = current
|
44
|
+
self.applied = current
|
45
|
+
self.pending = nil
|
46
|
+
end
|
47
|
+
|
48
|
+
# Undo last applied change so it becomes the next pending change.
|
49
|
+
# Parent of the applied change becomes the next applied change.
|
50
|
+
def undo
|
51
|
+
while applied = self.applied
|
52
|
+
applied.undo
|
53
|
+
|
54
|
+
self.pending = applied
|
55
|
+
self.applied = applied = applied.parent
|
56
|
+
|
57
|
+
break if applied && applied.separator
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# Redo pending change so it becomes the new applied change.
|
62
|
+
# If the pending change has a next child, it becomes the new pending one.
|
63
|
+
def redo
|
64
|
+
while pending = self.pending
|
65
|
+
pending.redo
|
66
|
+
|
67
|
+
self.applied = pending
|
68
|
+
self.pending = pending.next
|
69
|
+
|
70
|
+
break if pending && pending.separator
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# Join previous applied changes that have only one child and that modify
|
75
|
+
# data consecutive.
|
76
|
+
# This rewrites already applied history only.
|
77
|
+
def compact!
|
78
|
+
return unless applied
|
79
|
+
applied.compact!
|
80
|
+
self.pending = applied.next
|
81
|
+
end
|
82
|
+
|
83
|
+
def separate!
|
84
|
+
applied.separator = true if applied
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
class AutoSeparator < Struct.new(:tree, :records)
|
89
|
+
def initialize(tree)
|
90
|
+
self.tree = tree
|
91
|
+
self.records = []
|
92
|
+
|
93
|
+
yield(self) if block_given?
|
94
|
+
|
95
|
+
records.last.separator = true if records.any?
|
96
|
+
end
|
97
|
+
|
98
|
+
def insert(*args)
|
99
|
+
tree.record do |record|
|
100
|
+
record.insert(*args)
|
101
|
+
records << record
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def replace(*args)
|
106
|
+
tree.record do |record|
|
107
|
+
record.replace(*args)
|
108
|
+
records << record
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def delete(*args)
|
113
|
+
tree.record do |record|
|
114
|
+
record.delete(*args)
|
115
|
+
records << record
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
# Every Record is responsible for one change that it can apply or undo.
|
121
|
+
# There is only a very limited set of methods for modifications, as some of
|
122
|
+
# the destructive String methods in Ruby can have unpredictable results.
|
123
|
+
# In order to undo a change, we have to predict what the change will do
|
124
|
+
# before it happens to avoid expensive diff algorithms.
|
125
|
+
#
|
126
|
+
# A Record has one parent and a number of childs.
|
127
|
+
# If there are any childs, one of them is called current, and is the child
|
128
|
+
# that was last active, this way we can provide an intuitive way of choosing
|
129
|
+
# a child record to apply when a user wants to redo an undone change.
|
130
|
+
#
|
131
|
+
# Record has a direct pointer to the data in the tree, since it has to know
|
132
|
+
# about nothing else.
|
133
|
+
#
|
134
|
+
# Apart from that, Record also knows the time when it was created, this way
|
135
|
+
# you can move forward and backward in time.
|
136
|
+
#
|
137
|
+
# Revisions only keep the data necessary to undo/redo a change, not the whole
|
138
|
+
# data that was modified, that way it can keep overall memory-usage to a
|
139
|
+
# minimum.
|
140
|
+
#
|
141
|
+
# The applied property indicates whether or not this change has been applied
|
142
|
+
# already.
|
143
|
+
class Record < Struct.new(:widget, :parent, :ctime, :childs, :applied,
|
144
|
+
:undo_info, :redo_info, :separator)
|
145
|
+
|
146
|
+
def initialize(widget, parent = nil)
|
147
|
+
self.widget, self.parent = widget, parent
|
148
|
+
self.ctime = Time.now
|
149
|
+
self.childs = []
|
150
|
+
self.applied = false
|
151
|
+
self.separator = false
|
152
|
+
end
|
153
|
+
|
154
|
+
def insert(pos, string)
|
155
|
+
pos = widget.index(pos) unless pos.respond_to?(:to_index)
|
156
|
+
|
157
|
+
widget.execute_only(:insert, pos, string)
|
158
|
+
widget.touch!(pos)
|
159
|
+
|
160
|
+
self.redo_info = [:insert, pos, string]
|
161
|
+
self.undo_info = [pos, pos + string.size, '']
|
162
|
+
self.applied = true
|
163
|
+
end
|
164
|
+
|
165
|
+
def replace(from, to, string)
|
166
|
+
from = widget.index(from) unless from.respond_to?(:to_index)
|
167
|
+
to = widget.index(to) unless to.respond_to?(:to_index)
|
168
|
+
|
169
|
+
data = widget.get(from, to)
|
170
|
+
widget.execute_only(:replace, from, to, string)
|
171
|
+
widget.touch!(*from.upto(to))
|
172
|
+
|
173
|
+
self.redo_info = [:replace, from, to, string]
|
174
|
+
self.undo_info = [from, from + string.size, data]
|
175
|
+
self.applied = true
|
176
|
+
end
|
177
|
+
|
178
|
+
def delete(from, to)
|
179
|
+
from = widget.index(from) unless from.respond_to?(:to_index)
|
180
|
+
to = widget.index(to) unless to.respond_to?(:to_index)
|
181
|
+
|
182
|
+
data = widget.get(from, to)
|
183
|
+
widget.execute_only(:delete, from, to)
|
184
|
+
widget.touch!(*from.upto(to))
|
185
|
+
|
186
|
+
self.redo_info = [:delete, from, to]
|
187
|
+
self.undo_info = [from, from, data]
|
188
|
+
self.applied = true
|
189
|
+
end
|
190
|
+
|
191
|
+
def undo
|
192
|
+
return unless undo_info && applied
|
193
|
+
|
194
|
+
from, to, string = undo_info
|
195
|
+
widget.execute_only(:replace, from, to, string)
|
196
|
+
|
197
|
+
self.applied = false
|
198
|
+
end
|
199
|
+
|
200
|
+
def redo
|
201
|
+
return unless redo_info && !applied
|
202
|
+
send(*redo_info)
|
203
|
+
end
|
204
|
+
|
205
|
+
def compact!
|
206
|
+
return if separator
|
207
|
+
return unless parent = self.parent
|
208
|
+
|
209
|
+
pundo_from, pundo_to, pundo_string = parent.undo_info
|
210
|
+
sundo_from, sundo_to, sundo_string = undo_info
|
211
|
+
|
212
|
+
predo_name, *predo_args = parent.redo_info
|
213
|
+
sredo_name, *sredo_args = redo_info
|
214
|
+
|
215
|
+
# only compact identical methods
|
216
|
+
return unless predo_name == sredo_name
|
217
|
+
|
218
|
+
case predo_name
|
219
|
+
when :insert
|
220
|
+
predo_pos, predo_string = predo_args
|
221
|
+
sredo_pos, sredo_string = sredo_args
|
222
|
+
|
223
|
+
# the records have to be consecutive so they can still be applied by a
|
224
|
+
# single undo/redo
|
225
|
+
consecutive = (predo_pos + predo_string.size) == sredo_pos
|
226
|
+
return parent.compact! unless consecutive
|
227
|
+
|
228
|
+
redo_string = "#{predo_string}#{sredo_string}"
|
229
|
+
self.redo_info = [:insert, predo_pos, redo_string]
|
230
|
+
|
231
|
+
undo_string = "#{pundo_string}#{sundo_string}"
|
232
|
+
self.undo_info = [pundo_from, sundo_to, undo_string]
|
233
|
+
when :replace
|
234
|
+
predo_from, predo_to, predo_string = predo_args
|
235
|
+
sredo_from, sredo_to, sredo_string = sredo_args
|
236
|
+
|
237
|
+
# the records have to be consecutive so they can still be applied by a
|
238
|
+
# single undo/redo
|
239
|
+
consecutive = predo_to == sredo_from
|
240
|
+
return parent.compact! unless consecutive
|
241
|
+
|
242
|
+
redo_string = "#{predo_string}#{sredo_string}"
|
243
|
+
self.redo_info = [:replace, predo_from, sredo_to, undo_string]
|
244
|
+
|
245
|
+
undo_string = "#{pundo_string}#{sundo_string}"
|
246
|
+
self.undo_info = [pundo_from, sundo_to, undo_string]
|
247
|
+
when :delete
|
248
|
+
predo_from, predo_to = predo_args
|
249
|
+
sredo_from, sredo_to = sredo_args
|
250
|
+
|
251
|
+
consecutive = predo_to == sredo_from
|
252
|
+
return parent.compact! unless consecutive
|
253
|
+
|
254
|
+
self.redo_info = [:delete, predo_from, sredo_to]
|
255
|
+
|
256
|
+
undo_string = "#{sundo_string}#{pundo_string}"
|
257
|
+
self.undo_info = [pundo_from, sundo_to, undo_string]
|
258
|
+
else
|
259
|
+
return
|
260
|
+
end
|
261
|
+
|
262
|
+
# the parent of our parent (grandparent) becomes our parent
|
263
|
+
self.parent = grandparent = parent.parent
|
264
|
+
|
265
|
+
# recurse into a new compact cycle if we have a grandparent
|
266
|
+
if grandparent
|
267
|
+
grandparent.next = self
|
268
|
+
compact!
|
269
|
+
end
|
270
|
+
end
|
271
|
+
|
272
|
+
def next=(child)
|
273
|
+
childs.unshift(childs.delete(child) || child)
|
274
|
+
end
|
275
|
+
|
276
|
+
def next
|
277
|
+
childs.first
|
278
|
+
end
|
279
|
+
|
280
|
+
def applied?
|
281
|
+
applied
|
282
|
+
end
|
283
|
+
|
284
|
+
def inspect
|
285
|
+
"#<Undo::Record sep=%p undo=%p redo=%p>" % [separator, undo_info, redo_info]
|
286
|
+
end
|
287
|
+
end
|
288
|
+
end
|
289
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# The MIT Licence
|
2
|
+
#
|
3
|
+
# Copyright (c) 2004-2009 Pistos
|
4
|
+
#
|
5
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
# of this software and associated documentation files (the "Software"), to deal
|
7
|
+
# in the Software without restriction, including without limitation the rights
|
8
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
# copies of the Software, and to permit persons to whom the Software is
|
10
|
+
# furnished to do so, subject to the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be included in
|
13
|
+
# all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
# THE SOFTWARE.
|
22
|
+
|
23
|
+
class SizedArray < Array
|
24
|
+
attr_reader :capacity
|
25
|
+
|
26
|
+
def initialize( capacity = 10, *args )
|
27
|
+
@capacity = capacity
|
28
|
+
super( *args )
|
29
|
+
end
|
30
|
+
|
31
|
+
def resize
|
32
|
+
if size > @capacity
|
33
|
+
slice!( (0...-@capacity) )
|
34
|
+
end
|
35
|
+
end
|
36
|
+
private :resize
|
37
|
+
|
38
|
+
def concat( other_array )
|
39
|
+
super( other_array )
|
40
|
+
resize
|
41
|
+
self
|
42
|
+
end
|
43
|
+
|
44
|
+
def fill( *args )
|
45
|
+
retval = super( *args )
|
46
|
+
resize
|
47
|
+
self
|
48
|
+
end
|
49
|
+
|
50
|
+
def <<( item )
|
51
|
+
retval = super( item )
|
52
|
+
if size > @capacity
|
53
|
+
retval = shift
|
54
|
+
end
|
55
|
+
retval
|
56
|
+
end
|
57
|
+
|
58
|
+
def push( item )
|
59
|
+
self << item
|
60
|
+
end
|
61
|
+
|
62
|
+
def unshift( item )
|
63
|
+
retval = super( item )
|
64
|
+
if size > @capacity
|
65
|
+
retval = pop
|
66
|
+
end
|
67
|
+
retval
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
data/lib/ver/vendor/textpow.rb
CHANGED
data/lib/ver/version.rb
ADDED
data/lib/ver/view.rb
CHANGED
@@ -26,21 +26,25 @@ module VER
|
|
26
26
|
# | @status |
|
27
27
|
# +-----------+
|
28
28
|
def setup
|
29
|
-
|
30
|
-
# setup_scrollbars # enable if you really want some.
|
31
|
-
setup_status
|
29
|
+
setup_widgets
|
32
30
|
setup_grid
|
33
31
|
setup_misc
|
34
32
|
setup_events
|
35
33
|
end
|
36
34
|
|
35
|
+
def setup_widgets
|
36
|
+
setup_text
|
37
|
+
setup_vertical_scrollbar if VER.options.vertical_scrollbar
|
38
|
+
setup_horizontal_scrollbar if VER.options.horizontal_scrollbar
|
39
|
+
setup_status
|
40
|
+
end
|
41
|
+
|
37
42
|
def setup_text
|
38
43
|
font, tabstop = VER.options.font, VER.options.tabstop
|
39
44
|
tabs = font.measure('0') * tabstop
|
40
45
|
|
41
46
|
@text = VER::Text.new(
|
42
47
|
self,
|
43
|
-
autoseparators: true, # insert separators into the undo flow
|
44
48
|
borderwidth: 0,
|
45
49
|
exportselection: true, # copy into X11 buffer automatically
|
46
50
|
font: font,
|
@@ -50,17 +54,16 @@ module VER
|
|
50
54
|
takefocus: true,
|
51
55
|
tabs: tabs,
|
52
56
|
tabstyle: :wordprocessor,
|
53
|
-
undo: true, # enable undo capabilities
|
54
57
|
wrap: :word
|
55
58
|
)
|
56
59
|
end
|
57
60
|
|
58
|
-
def
|
59
|
-
# vertical scrollbar
|
61
|
+
def setup_vertical_scrollbar
|
60
62
|
@ybar = Tk::Tile::YScrollbar.new(self)
|
61
63
|
@text.yscrollbar(@ybar)
|
64
|
+
end
|
62
65
|
|
63
|
-
|
66
|
+
def setup_horizontal_scrollbar
|
64
67
|
@xbar = Tk::Tile::XScrollbar.new(self)
|
65
68
|
@text.xscrollbar(@xbar)
|
66
69
|
end
|
data/lib/ver/view/list/grep.rb
CHANGED
@@ -1,5 +1,12 @@
|
|
1
1
|
module VER
|
2
2
|
class View::List::Grep < View::List
|
3
|
+
def initialize(parent, glob = nil, &block)
|
4
|
+
super(parent, &block)
|
5
|
+
|
6
|
+
@glob = nil
|
7
|
+
@glob = glob.to_s unless glob.nil?
|
8
|
+
end
|
9
|
+
|
3
10
|
def update
|
4
11
|
list.clear
|
5
12
|
|
@@ -17,11 +24,15 @@ module VER
|
|
17
24
|
def grep(input)
|
18
25
|
@choices = []
|
19
26
|
|
20
|
-
|
21
|
-
|
22
|
-
|
27
|
+
if @glob
|
28
|
+
input, query = @glob, input
|
29
|
+
else
|
30
|
+
input, query = input.split(/ /, 2)
|
31
|
+
input, query = nil, input unless query
|
32
|
+
input ||= '*'
|
23
33
|
|
24
|
-
|
34
|
+
return [] if !query || query.size < 3 # protect a little
|
35
|
+
end
|
25
36
|
|
26
37
|
regex = /#{Regexp.escape(query)}/
|
27
38
|
|