ver 2009.11.29 → 2009.12.14

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. data/AUTHORS +6 -0
  2. data/CHANGELOG +353 -1
  3. data/LICENSE +18 -0
  4. data/MANIFEST +11 -1
  5. data/Rakefile +2 -1
  6. data/bin/ver +3 -12
  7. data/config/detect.rb +1 -1
  8. data/config/keymap/diakonos.rb +181 -0
  9. data/config/keymap/emacs.rb +24 -24
  10. data/config/keymap/vim.rb +162 -127
  11. data/config/rc.rb +29 -14
  12. data/config/syntax/Nemerle.json +3 -3
  13. data/lib/ver.rb +88 -134
  14. data/lib/ver/entry.rb +5 -0
  15. data/lib/ver/exception_view.rb +97 -0
  16. data/lib/ver/hover_completion.rb +14 -7
  17. data/lib/ver/keymap.rb +30 -1
  18. data/lib/ver/layout.rb +20 -14
  19. data/lib/ver/methods.rb +6 -15
  20. data/lib/ver/methods/bookmark.rb +189 -0
  21. data/lib/ver/methods/completion.rb +2 -2
  22. data/lib/ver/methods/control.rb +109 -26
  23. data/lib/ver/methods/ctags.rb +28 -4
  24. data/lib/ver/methods/delete.rb +85 -4
  25. data/lib/ver/methods/insert.rb +73 -52
  26. data/lib/ver/methods/move.rb +122 -35
  27. data/lib/ver/methods/open.rb +4 -43
  28. data/lib/ver/methods/search.rb +46 -17
  29. data/lib/ver/methods/select.rb +121 -24
  30. data/lib/ver/methods/undo.rb +23 -0
  31. data/lib/ver/methods/views.rb +5 -0
  32. data/lib/ver/mode.rb +18 -17
  33. data/lib/ver/status.rb +2 -2
  34. data/lib/ver/status/context.rb +166 -0
  35. data/lib/ver/text.rb +43 -81
  36. data/lib/ver/text/index.rb +24 -7
  37. data/lib/ver/undo.rb +289 -0
  38. data/lib/ver/vendor/sized_array.rb +70 -0
  39. data/lib/ver/vendor/textpow.rb +6 -1
  40. data/lib/ver/version.rb +3 -0
  41. data/lib/ver/view.rb +11 -8
  42. data/lib/ver/view/list/grep.rb +15 -4
  43. data/lib/ver/view/term.rb +9 -3
  44. data/spec/helper.rb +94 -0
  45. data/ver.gemspec +9 -6
  46. metadata +25 -5
  47. data/spec/keymap.rb +0 -224
@@ -6,6 +6,19 @@ module VER
6
6
  ctags_find(word)
7
7
  end
8
8
 
9
+ def ctags_go(name = nil)
10
+ if name
11
+ ctags_content do |tag_name, file_name, ex_cmd|
12
+ next unless tag_name == name
13
+ return ctags_execute(file_name, ex_cmd)
14
+ end
15
+ else
16
+ status_ask 'Tag name: ' do |tag_name|
17
+ ctags_go(tag_name)
18
+ end
19
+ end
20
+ end
21
+
9
22
  def ctags_find(needle)
10
23
  ctags_content do |tag_name, file_name, ex_cmd|
11
24
  next unless tag_name == needle
@@ -13,15 +26,26 @@ module VER
13
26
  end
14
27
  end
15
28
 
29
+ def ctags_prev
30
+ if bm = VER.ctag_stack.pop
31
+ bookmark_open(bm)
32
+ else
33
+ message("Tag stack empty.")
34
+ end
35
+ end
36
+
16
37
  def ctags_execute(file_name, ex_cmd)
17
38
  case ex_cmd
18
39
  when /^\d+$/
19
- view.find_or_create(file_name) do |view|
20
- view.text.go_line(ex_cmd.to_i)
21
- end
40
+ VER.ctag_stack << Bookmarks::Bookmark.new(nil, *bookmark_value)
41
+
42
+ view.find_or_create(file_name, ex_cmd.to_i)
22
43
  when /^\/(.*)\/$/
23
- source = $1.gsub!(/(?!\\)([()])/, '\\\\\\1')
44
+ VER.ctag_stack << Bookmarks::Bookmark.new(nil, *bookmark_value)
45
+
46
+ source = $1.gsub(/(?!\\)([()])/, '\\\\\\1')
24
47
  regexp = Regexp.new(source)
48
+
25
49
  self.view.find_or_create(file_name) do |view|
26
50
  view.text.tag_all_matching(:search, regexp, Search::SEARCH_HIGHLIGHT)
27
51
  view.text.search_next
@@ -6,25 +6,106 @@ module VER
6
6
  start_insert_mode
7
7
  end
8
8
 
9
+ # Given a +motion+, this method will execute a virtual movement with the
10
+ # +count+ argument and [delete] the corresponding indices.
11
+ #
12
+ # @param [Symbol String] motion name of a method acceptable for [virtual_movement]
13
+ # @param [#to_i] count
14
+ #
15
+ # @see delete
16
+ # @see VER::Move#virtual_movement
9
17
  def delete_motion(motion, count = 1)
10
18
  delete(*virtual_movement(motion, count))
11
19
  end
12
20
 
21
+ # Given a +motion+, this method will execute a virtual movement with the
22
+ # +count+ argument and [kill] the corresponding indices.
23
+ #
24
+ # @param [Symbol String] motion name of a method acceptable for [virtual_movement]
25
+ # @param [#to_i] count
26
+ #
27
+ # @see kill
28
+ # @see VER::Move#virtual_movement
29
+ def kill_motion(motion, count = 1)
30
+ kill(*virtual_movement(motion, count))
31
+ end
32
+
33
+ # Delete current line and upto +count+ subsequent lines.
34
+ #
35
+ # @param [#to_i] count Number of lines to delete
36
+ #
37
+ # @see delete
38
+ # @see kill_line
13
39
  def delete_line(count = 1)
14
- count.times do
15
- delete 'insert linestart', 'insert lineend + 1 char'
16
- end
40
+ from = index('insert linestart')
41
+ count = count.abs - 1
42
+ to = index("#{from.y + count}.#{from.x} lineend + 1 char")
43
+ delete(from, to)
17
44
  end
18
45
 
46
+ # Copy current line and upto +count+ subsequent lines and delete them.
47
+ #
48
+ # @param [#to_i] count Number of lines to kill
49
+ #
50
+ # @see kill
51
+ # @see delete_line
52
+ def kill_line(count = 1)
53
+ from = index('insert linestart')
54
+ count = count.abs - 1
55
+ to = index("#{from.y + count.to_i}.#{from.x} lineend + 1 char")
56
+ kill(from, to)
57
+ end
58
+
59
+ # Wrapper for [kill_line] that starts insert mode after killing +count+
60
+ # lines.
61
+ #
62
+ # @param [#to_i] count Number of lines to kill
63
+ #
64
+ # @see kill_line
65
+ # @see start_insert_mode
19
66
  def change_line(count = 1)
20
- delete_line(count)
67
+ kill_line(count)
21
68
  start_insert_mode
22
69
  end
23
70
 
71
+ # Tag and delete all trailing whitespace in the current buffer.
24
72
  def delete_trailing_whitespace
25
73
  tag_all_trailing_whitespace
26
74
  execute :delete, *tag_ranges('invalid.trailing-whitespace').flatten
27
75
  end
76
+
77
+ # Delete text between +indices+
78
+ def delete(*indices)
79
+ indices_size = indices.size
80
+ return if indices_size == 0
81
+
82
+ undo_record do |record|
83
+ if indices_size == 1
84
+ record.delete(indices.first)
85
+ else
86
+ indices.each_slice(2) do |from, to|
87
+ next if from == to
88
+ record.delete(from, to)
89
+ end
90
+ end
91
+ end
92
+ end
93
+
94
+ # Copy text between +indices+ and delete it.
95
+ #
96
+ # @param [Array<Text::Index, String, Symbol>] indices
97
+ # one or more indices within the buffer, must be an even number of
98
+ # indices if more than one.
99
+ def kill(*indices)
100
+ if indices.size > 2
101
+ deleted = indices.each_slice(2).map{|left, right| get(left, right) }
102
+ else
103
+ deleted = get(*indices)
104
+ end
105
+
106
+ copy(deleted)
107
+ delete(*indices)
108
+ end
28
109
  end
29
110
  end
30
111
  end
@@ -17,32 +17,35 @@ module VER
17
17
  end
18
18
 
19
19
  def insert_indented_newline_below
20
- if VER.options.autoindent
21
- line = get('insert linestart', 'insert lineend')
20
+ undo_record do |record|
21
+ if options.autoindent
22
+ line = get('insert linestart', 'insert lineend')
22
23
 
23
- indent = line.empty? ? "" : (line[/^\s+/] || '')
24
- mark_set :insert, 'insert lineend'
25
- insert :insert, "\n#{indent}"
26
- else
27
- mark_set :insert, 'insert lineend'
28
- insert :insert, "\n"
29
- end
24
+ indent = line.empty? ? "" : (line[/^\s+/] || '')
25
+ mark_set :insert, 'insert lineend'
26
+ record.insert :insert, "\n#{indent}"
27
+ else
28
+ mark_set :insert, 'insert lineend'
29
+ record.insert :insert, "\n"
30
+ end
30
31
 
31
- clean_line('insert - 1 line')
32
- start_insert_mode
32
+ clean_line('insert - 1 line', record)
33
+ start_insert_mode
34
+ end
33
35
  end
34
36
 
35
37
  def insert_indented_newline_above
36
- if index(:insert).y > 1
37
- previous_line
38
- insert_indented_newline_below
39
- else
40
- insert('insert linestart', "\n")
41
- mark_set(:insert, 'insert - 1 line')
38
+ undo_record do |record|
39
+ if index(:insert).y > 1
40
+ previous_line
41
+ insert_indented_newline_below
42
+ else
43
+ record.insert('insert linestart', "\n")
44
+ mark_set(:insert, 'insert - 1 line')
45
+ clean_line('insert - 1 line', record)
46
+ start_insert_mode
47
+ end
42
48
  end
43
-
44
- clean_line('insert + 1 line')
45
- start_insert_mode
46
49
  end
47
50
 
48
51
  def insert_newline
@@ -50,7 +53,7 @@ module VER
50
53
  end
51
54
 
52
55
  def insert_indented_newline
53
- if VER.options.autoindent
56
+ if options.autoindent
54
57
  fallback_insert_indented_newline
55
58
  else
56
59
  insert_newline
@@ -136,26 +139,28 @@ module VER
136
139
  end
137
140
 
138
141
  def fallback_insert_indented_newline
139
- line1 = get('insert linestart', 'insert lineend')
140
- indentation1 = line1[/^\s+/] || ''
141
- insert :insert, "\n"
142
+ undo_record do |record|
143
+ line1 = get('insert linestart', 'insert lineend')
144
+ indentation1 = line1[/^\s+/] || ''
145
+ record.insert :insert, "\n"
142
146
 
143
- line2 = get('insert linestart', 'insert lineend')
144
- indentation2 = line2[/^\s+/] || ''
147
+ line2 = get('insert linestart', 'insert lineend')
148
+ indentation2 = line2[/^\s+/] || ''
145
149
 
146
- replace(
147
- 'insert linestart',
148
- "insert linestart + #{indentation2.size} chars",
149
- indentation1
150
- )
150
+ record.replace(
151
+ 'insert linestart',
152
+ "insert linestart + #{indentation2.size} chars",
153
+ indentation1
154
+ )
151
155
 
152
- clean_line('insert - 1 line')
156
+ clean_line('insert - 1 line', record)
157
+ end
153
158
  end
154
159
 
155
160
  # Most of the input will be in US-ASCII, but an encoding can be set per view for the input.
156
161
  # For just about all purposes, UTF-8 should be what you want to input, and it's what Tk
157
162
  # can handle best.
158
- def insert_string(string)
163
+ def insert_string(string, record = self)
159
164
  return if string.empty?
160
165
 
161
166
  if !string.frozen? && string.encoding == Encoding::ASCII_8BIT
@@ -167,7 +172,20 @@ module VER
167
172
  end
168
173
 
169
174
  # puts "Insert %p in mode %p" % [string, keymap.mode]
170
- insert :insert, string
175
+ record.insert(:insert, string)
176
+ end
177
+
178
+ def start_replace_mode
179
+ self.mode = :replace
180
+ end
181
+
182
+ def replace_string(string)
183
+ return if string.empty?
184
+
185
+ undo_record do |record|
186
+ record.delete(:insert, 'insert + 1 chars')
187
+ insert_string(string, record)
188
+ end
171
189
  end
172
190
 
173
191
  def auto_indent_line
@@ -212,24 +230,27 @@ module VER
212
230
 
213
231
  empty_line = /^\s*$/
214
232
  indent = 0
215
-
216
- index('1.0').upto(index('end')) do |pos|
217
- pos_lineend = pos.lineend
218
- line = get(pos, pos_lineend).strip
219
-
220
- if increase && decrease && line =~ increase && line =~ decrease
221
- indent -= 1
222
- replace(pos, pos_lineend, (' ' * indent) << line)
223
- indent += 1
224
- elsif decrease && line =~ decrease
225
- indent -= 1
226
- replace(pos, pos_lineend, (' ' * indent) << line)
227
- elsif increase && line =~ increase
228
- replace(pos, pos_lineend, (' ' * indent) << line)
229
- indent += 1
230
- elsif line =~ empty_line
231
- else
232
- replace(pos, pos_lineend, (' ' * indent) << line)
233
+ indent_token = ' ' * options.shiftwidth
234
+
235
+ undo_record do |record|
236
+ index('1.0').upto(index('end')) do |pos|
237
+ pos_lineend = pos.lineend
238
+ line = get(pos, pos_lineend).strip
239
+
240
+ if increase && decrease && line =~ increase && line =~ decrease
241
+ indent -= 1
242
+ record.replace(pos, pos_lineend, (indent_token * indent) << line)
243
+ indent += 1
244
+ elsif decrease && line =~ decrease
245
+ indent -= 1
246
+ record.replace(pos, pos_lineend, (indent_token * indent) << line)
247
+ elsif increase && line =~ increase
248
+ record.replace(pos, pos_lineend, (indent_token * indent) << line)
249
+ indent += 1
250
+ elsif line =~ empty_line
251
+ else
252
+ record.replace(pos, pos_lineend, (indent_token * indent) << line)
253
+ end
233
254
  end
234
255
  end
235
256
  end
@@ -42,34 +42,51 @@ module VER
42
42
 
43
43
  # Move cursor +count+ characters left.
44
44
  def backward_char(count = 1)
45
- mark_set :insert, "insert - #{count} char"
45
+ mark_set :insert, "insert - #{count} displaychars"
46
46
  end
47
47
 
48
48
  # Move cursor +count+ characters right.
49
49
  def forward_char(count = 1)
50
- mark_set :insert, "insert + #{count} char"
50
+ mark_set :insert, "insert + #{count} displaychars"
51
51
  end
52
52
 
53
+ # Move to the beginning of the line where insert mark is located.
54
+ #
55
+ # With +count+ it will move to the beginning of the display line, which
56
+ # takes line wraps into account.
53
57
  def beginning_of_line(count = nil)
54
- mark_set :insert, 'insert display linestart'
58
+ if count
59
+ mark_set :insert, 'insert display linestart'
60
+ else
61
+ mark_set :insert, 'insert linestart'
62
+ end
55
63
  end
56
64
 
65
+ # Move to the end of the line where insert mark is located.
66
+ #
67
+ # With +count+ it moves to the end of the display line, so when there is
68
+ # a line wrap it will move to the place where the line wraps instead of the
69
+ # real end of the line.
57
70
  def end_of_line(count = nil)
58
- mark_set :insert, 'insert display lineend'
71
+ if count
72
+ mark_set :insert, 'insert display lineend'
73
+ else
74
+ mark_set :insert, 'insert lineend'
75
+ end
59
76
  end
60
77
 
61
78
  def eol_then_insert_mode(count = nil)
62
- end_of_line
79
+ end_of_line(count)
63
80
  start_insert_mode
64
81
  end
65
82
 
66
- def sol_then_insert_mode
67
- beginning_of_line
83
+ def sol_then_insert_mode(count = nil)
84
+ beginning_of_line(count)
68
85
  start_insert_mode
69
86
  end
70
87
 
71
- def forward_char_then_insert_mode
72
- forward_char
88
+ def forward_char_then_insert_mode(count = 1)
89
+ forward_char(count)
73
90
  start_insert_mode
74
91
  end
75
92
 
@@ -106,16 +123,103 @@ module VER
106
123
  end
107
124
 
108
125
  def previous_line(count = 1)
109
- mark_set :insert, tk_prev_line_pos(count)
126
+ up_down_line(-count.abs)
127
+ refresh_selection
110
128
  end
111
129
 
112
130
  def next_line(count = 1)
113
- mark_set :insert, tk_next_line_pos(count)
131
+ up_down_line(count.abs)
132
+ refresh_selection
133
+ end
134
+
135
+ # OK, finally found the issue.
136
+ #
137
+ # the implementation of tk::TextUpDownLine is smart, but not smart enough.
138
+ # It doesn't assume large deltas between the start of up/down movement and
139
+ # the last other modification of the insert mark.
140
+ #
141
+ # This means that, after scrolling with up/down for a few hundred lines,
142
+ # it has to calculate the amount of display lines in between, which is a
143
+ # very expensive calculation and time increases O(delta_lines).
144
+ #
145
+ # We'll try to solve this another way, by assuming that there are at least
146
+ # a few other lines of same or greater length in between, we simply
147
+ # compare against a closer position and make delta_lines as small as
148
+ # possible.
149
+ #
150
+ # Now, if you go to, like column 100 of a line, and there is never a line
151
+ # as long for the rest of the file, the scrolling will still slow down a
152
+ # lot. This is an issue we can fix if we "forget" the @udl_pos_orig after
153
+ # a user-defined maximum delta (something around 200 should do), will
154
+ # implement that on demand.
155
+ def up_down_line(count)
156
+ insert = index(:insert)
157
+
158
+ @udl_pos_orig = insert if @udl_pos_prev != insert
159
+
160
+ lines = count(@udl_pos_orig, insert, :displaylines)
161
+ target = index("#@udl_pos_orig + #{lines + count} displaylines")
162
+ @udl_pos_prev = target
163
+ mark_set :insert, target
164
+ @udl_pos_orig = target if target.x == @udl_pos_orig.x
165
+ end
166
+
167
+ def forward_scroll(count = 1)
168
+ count_abs = count.abs
169
+ yview_scroll(count_abs, :units)
170
+ next_line(count_abs)
171
+ end
172
+
173
+ def backward_scroll(count = 1)
174
+ count_abs = count.abs
175
+ yview_scroll(-count_abs, :units)
176
+ previous_line(count_abs)
114
177
  end
115
178
 
116
179
  def forward_word(count = 1)
180
+ forward_jump(count, &method(:word_char_type))
181
+ end
182
+
183
+ def forward_chunk(count = 1)
184
+ forward_jump(count, &method(:chunk_char_type))
185
+ end
186
+
187
+ def word_right_end(count = 1)
188
+ count.times do
189
+ mark_set :insert, tk_next_word_pos_end('insert')
190
+ end
191
+ end
192
+
193
+ def backward_word(count = 1)
194
+ backward_jump(count, &method(:word_char_type))
195
+ end
196
+
197
+ def backward_chunk(count = 1)
198
+ backward_jump(count, &method(:chunk_char_type))
199
+ end
200
+
201
+ private
202
+
203
+ def word_char_type(char)
204
+ case char
205
+ when /\w/; :word
206
+ when /\S/; :special
207
+ when /\s/; :space
208
+ else; raise "You cannot get here"
209
+ end
210
+ end
211
+
212
+ def chunk_char_type(char)
213
+ case char
214
+ when /\S/; :nonspace
215
+ when /\s/; :space
216
+ else; raise "You cannot get here"
217
+ end
218
+ end
219
+
220
+ def forward_jump(count)
117
221
  count.times do
118
- original_type = type = char_type(get(:insert))
222
+ original_type = type = yield(get(:insert))
119
223
  changed = 0
120
224
 
121
225
  begin
@@ -123,7 +227,7 @@ module VER
123
227
  execute :mark, :set, :insert, 'insert + 1 chars'
124
228
  break if original_pos == index(:insert)
125
229
 
126
- type = char_type(get(:insert))
230
+ type = yield(get(:insert))
127
231
  changed += 1 if type != original_type
128
232
  original_type = type
129
233
  end until changed > 0 && type != :space
@@ -134,15 +238,9 @@ module VER
134
238
  VER.error(ex)
135
239
  end
136
240
 
137
- def word_right_end(count = 1)
138
- count.times do
139
- mark_set :insert, tk_next_word_pos_end('insert')
140
- end
141
- end
142
-
143
- def backward_word(count = 1)
241
+ def backward_jump(count = 1)
144
242
  count.times do
145
- original_type = type = char_type(get(:insert))
243
+ original_type = type = yield(get(:insert))
146
244
  changed = 0
147
245
 
148
246
  begin
@@ -150,19 +248,19 @@ module VER
150
248
  execute :mark, :set, :insert, 'insert - 1 chars'
151
249
  break if index(:insert) == original_pos
152
250
 
153
- type = char_type(get(:insert))
251
+ type = yield(get(:insert))
154
252
  changed += 1 if type != original_type
155
253
  original_type = type
156
254
  end until changed > 0 && type != :space
157
255
 
158
- type = char_type(get('insert - 1 chars'))
256
+ type = yield(get('insert - 1 chars'))
159
257
 
160
258
  while type == original_type
161
259
  original_pos = index(:insert)
162
260
  execute :mark, :set, :insert, 'insert - 1 chars'
163
261
  break if index(:insert) == original_pos
164
262
 
165
- type = char_type(get('insert - 1 chars'))
263
+ type = yield(get('insert - 1 chars'))
166
264
  end
167
265
  end
168
266
 
@@ -171,17 +269,6 @@ module VER
171
269
  VER.error(ex)
172
270
  end
173
271
 
174
- private
175
-
176
- def char_type(char)
177
- case char
178
- when /\w/; :word
179
- when /\S/; :special
180
- when /\s/; :space
181
- else; raise "You cannot get here"
182
- end
183
- end
184
-
185
272
  def tk_prev_word_pos(start)
186
273
  Tk.execute('tk::TextPrevPos', tk_pathname, start, 'tcl_startOfPreviousWord').to_s
187
274
  end