Module: VER::Methods::Move

Defined in:
lib/ver/methods/move.rb

Constant Summary collapse

GO_MATCHING_RIGHT =
{
  '(' => ')',
  '{' => '}',
  '[' => ']',
  '<' => '>',
}
GO_MATCHING_LEFT =
GO_MATCHING_RIGHT.invert

Class Method Summary collapse

Class Method Details

.ask_go_line(text) ⇒ Object



113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
# File 'lib/ver/methods/move.rb', line 113

def ask_go_line(text)
  initial = $1 if text.event.unicode =~ /^(\d+)$/
  question = 'Go to [line number|+lines][,column number]: '
  text.ask question, value: initial do |answer, action|
    case action
    when :attempt
      parse_go_line text, answer do |index|
        text.mark_set(:insert, index)
        :abort
      end
    when :modified
      parse_go_line text, answer do |index|
        text.tag_configure(Search::TAG, Search::HIGHLIGHT)
        text.tag_add(Search::TAG, index, index.lineend)
        text.see(index)
        Tk::After.ms(3000){
          text.tag_remove(Search::TAG, index, index.lineend)
          text.see(:insert)
        }
      end
    end
  end
end

.backward_jump(text, count) ⇒ Object



315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
# File 'lib/ver/methods/move.rb', line 315

def backward_jump(text, count)
  count.times do
    original_type = type = yield(text.get(:insert))
    changed = 0

    begin
      original_pos = text.index(:insert)
      text.execute_only(:mark, :set, :insert, 'insert - 1 chars')
      break if text.index(:insert) == original_pos

      type = yield(text.get(:insert))
      changed += 1 if type != original_type
      original_type = type
    end until changed > 0 && type != :space

    type = yield(text.get('insert - 1 chars'))

    while type == original_type
      original_pos = text.index(:insert)
      text.execute_only(:mark, :set, :insert, 'insert - 1 chars')
      break if text.index(:insert) == original_pos

      type = yield(text.get('insert - 1 chars'))
    end
  end

  Tk::Event.generate(text, '<<Movement>>')
rescue => ex
  VER.error(ex)
end

.backward_scroll(text, count = text.prefix_count) ⇒ Object



205
206
207
208
209
# File 'lib/ver/methods/move.rb', line 205

def backward_scroll(text, count = text.prefix_count)
  count_abs = count.abs
  text.yview_scroll(-count_abs, :units)
  prev_line(text, count_abs)
end

.chunk_char_type(char) ⇒ Object



285
286
287
288
289
290
291
292
# File 'lib/ver/methods/move.rb', line 285

def chunk_char_type(char)
  case char
  when /\S/; :nonspace
  when /\s/; :space
  else
    Kernel.raise "No matching chunk type for: %p " % [char]
  end
end

.end_of_line(text, count_or_mode = nil) ⇒ Object

Move to the end of the line where insert mark is located.

With count it moves to the end of the display line, so when there is a line wrap it will move to the place where the line wraps instead of the real end of the line.



97
98
99
100
101
102
103
104
105
106
107
# File 'lib/ver/methods/move.rb', line 97

def end_of_line(text, count_or_mode = nil)
  case count_or_mode
  when Symbol
    text.mark_set(:insert, 'insert display lineend')
    text.minor_mode(:control, count_or_mode)
  when nil
    text.mark_set(:insert, 'insert lineend')
  else
    text.mark_set(:insert, 'insert display lineend')
  end
end

.end_of_sentence(text, count = text.prefix_count) ⇒ Object



166
167
168
169
170
171
172
173
# File 'lib/ver/methods/move.rb', line 166

def end_of_sentence(text, count = text.prefix_count)
  text.search_all(/\.\s/, 'insert') do |match, from, to|
    p match: match, from: from, to: to
    text.mark_set(:insert, "#{to} - 1 chars")
    count -= 1
    return if count <= 0
  end
end

.end_of_text(text, count = text.prefix_arg) ⇒ Object



158
159
160
161
162
163
164
# File 'lib/ver/methods/move.rb', line 158

def end_of_text(text, count = text.prefix_arg)
  if count
    text.mark_set(:insert, "#{count}.0")
  else
    text.mark_set(:insert, :end)
  end
end

.forward_jump(text, count) ⇒ Object



294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
# File 'lib/ver/methods/move.rb', line 294

def forward_jump(text, count)
  count.times do
    original_type = type = yield(text.get(:insert))
    changed = 0

    begin
      original_pos = text.index(:insert)
      text.execute_only(:mark, :set, :insert, 'insert + 1 chars')
      break if original_pos == text.index(:insert)

      type = yield(text.get(:insert))
      changed += 1 if type != original_type
      original_type = type
    end until changed > 0 && type != :space
  end

  Tk::Event.generate(text, '<<Movement>>')
rescue => ex
  VER.error(ex)
end

.forward_scroll(text, count = text.prefix_count) ⇒ Object



199
200
201
202
203
# File 'lib/ver/methods/move.rb', line 199

def forward_scroll(text, count = text.prefix_count)
  count_abs = count.abs
  text.yview_scroll(count_abs, :units)
  next_line(text, count_abs)
end

.go_column(text, number = text.prefix_count) ⇒ Object



149
150
151
# File 'lib/ver/methods/move.rb', line 149

def go_column(text, number = text.prefix_count)
  text.mark_set(:insert, "insert linestart + #{number} chars")
end

.go_line(text, number = text.prefix_count) ⇒ Object



109
110
111
# File 'lib/ver/methods/move.rb', line 109

def go_line(text, number = text.prefix_count)
  text.mark_set(:insert, "#{number}.0")
end

.home_of_line(text, count = text.prefix_arg) ⇒ Object

Move to the first character of the line in which insert mark is located.

With count it will move to the linestart of the displayed, taking linewraps into account.



82
83
84
85
86
87
88
89
90
# File 'lib/ver/methods/move.rb', line 82

def home_of_line(text, count = text.prefix_arg)
  if count
    start_of_line(text, true)
  else
    x = text.get('insert linestart', 'insert lineend').index(/\S/) || 0
    y = text.index('insert').y
    text.mark_set(:insert, "#{y}.#{x}")
  end
end

.index_at_word_right_end(text, count = text.prefix_count) ⇒ Object



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
271
272
273
# File 'lib/ver/methods/move.rb', line 242

def index_at_word_right_end(text, count = text.prefix_count)
  offset = 1
  last = text.index('end')

  count.times do
    pos  = text.index("insert + #{offset} chars")

    return if pos == last

    type = word_char_type(text.get(pos))

    while type == :space
      offset += 1
      pos = text.index("insert + #{offset} chars")
      break if pos == last
      type = word_char_type(text.get(pos))
    end

    lock = type

    while type == lock && type != :space
      offset += 1
      pos = text.index("insert + #{offset} chars")
      break if pos == last
      type = word_char_type(text.get(pos))
    end
  end

  text.index("insert + #{offset - 1} chars")
rescue => ex
  VER.error(ex)
end

.matching_brace(text) ⇒ Object



19
20
21
22
23
24
25
# File 'lib/ver/methods/move.rb', line 19

def matching_brace(text)
  if pos = matching_brace_pos(text, :insert)
    text.mark_set(:insert, pos)
  else
    VER.warn "No matching brace"
  end
end

.matching_brace_pos(text, index = :insert) ⇒ Object



27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# File 'lib/ver/methods/move.rb', line 27

def matching_brace_pos(text, index = :insert)
  opening = text.get(index)

  if closing = GO_MATCHING_RIGHT[opening]
    start = "#{index} + 1 chars"
    search = text.method(:search_all)
  elsif closing = GO_MATCHING_LEFT[opening]
    start = index
    search = text.method(:rsearch_all)
  else
    return
  end

  balance = 1
  needle = Regexp.union(opening, closing)
  search.call needle, start do |match, from, to|
    case match
    when opening
      balance += 1
    when closing
      balance -= 1
    end

    if balance == 0
      return from
    end
  end
end

.next_char(text, count = text.prefix_count) ⇒ Object

Move cursor count characters right.



62
63
64
# File 'lib/ver/methods/move.rb', line 62

def next_char(text, count = text.prefix_count)
  text.mark_set(:insert, "insert + #{count} displaychars")
end

.next_chunk(text, count = text.prefix_count) ⇒ Object



215
216
217
# File 'lib/ver/methods/move.rb', line 215

def next_chunk(text, count = text.prefix_count)
  forward_jump(text, count, &method(:chunk_char_type))
end

.next_line(text, count = text.prefix_count) ⇒ Object



195
196
197
# File 'lib/ver/methods/move.rb', line 195

def next_line(text, count = text.prefix_count)
  up_down_line(text, count.abs)
end

.next_page(text, count = text.prefix_count) ⇒ Object



238
239
240
# File 'lib/ver/methods/move.rb', line 238

def next_page(text, count = text.prefix_count)
  text.mark_set(:insert, text.tk_next_page_pos(count))
end

.next_word(text, count = text.prefix_count) ⇒ Object



211
212
213
# File 'lib/ver/methods/move.rb', line 211

def next_word(text, count = text.prefix_count)
  forward_jump(text, count, &method(:word_char_type))
end

.next_word_end(text, count = text.prefix_count) ⇒ Object

Jump to the last character of the word the insert cursor is over currently.



220
221
222
# File 'lib/ver/methods/move.rb', line 220

def next_word_end(text, count = text.prefix_count)
  text.mark_set(:insert, index_at_word_right_end(text, count))
end

.parse_go_line(text, input) ⇒ Object



137
138
139
140
141
142
143
144
145
146
147
# File 'lib/ver/methods/move.rb', line 137

def parse_go_line(text, input)
  case input.to_s.strip
  when /^\+(\d+)(?:,(\d+))?$/
    line, column = $1.to_i, $2.to_i
    current = text.index(:insert)
    yield text.index("#{current.line + line}.#{column}")
  when /^(\d+)(?:,(\d+))?$/
    line, column = $1.to_i, $2.to_i
    yield text.index("#{line}.#{column}")
  end
end

.prefix_arg_sol(text) ⇒ Object



14
15
16
17
# File 'lib/ver/methods/move.rb', line 14

def prefix_arg_sol(text)
  return if text.update_prefix_arg(text)
  start_of_line(text)
end

.prev_char(text, count = text.prefix_count) ⇒ Object

Move cursor count characters left.



57
58
59
# File 'lib/ver/methods/move.rb', line 57

def prev_char(text, count = text.prefix_count)
  text.mark_set(:insert, "insert - #{count} displaychars")
end

.prev_chunk(text, count = text.prefix_count) ⇒ Object



228
229
230
# File 'lib/ver/methods/move.rb', line 228

def prev_chunk(text, count = text.prefix_count)
  backward_jump(text, count, &method(:chunk_char_type))
end

.prev_line(text, count = text.prefix_count) ⇒ Object



191
192
193
# File 'lib/ver/methods/move.rb', line 191

def prev_line(text, count = text.prefix_count)
  up_down_line(text, -count.abs)
end

.prev_page(text, count = text.prefix_count) ⇒ Object

HACK: but it’s just too good to do it manually



234
235
236
# File 'lib/ver/methods/move.rb', line 234

def prev_page(text, count = text.prefix_count)
  text.mark_set(:insert, text.tk_prev_page_pos(count))
end

.prev_word(text, count = text.prefix_count) ⇒ Object



224
225
226
# File 'lib/ver/methods/move.rb', line 224

def prev_word(text, count = text.prefix_count)
  backward_jump(text, count, &method(:word_char_type))
end

.start_of_line(text, count = text.prefix_count) ⇒ Object

Move to the beginning of the line in which insert mark is located.

With count it will move to the beginning of the display line, which takes line wraps into account.



70
71
72
73
74
75
76
# File 'lib/ver/methods/move.rb', line 70

def start_of_line(text, count = text.prefix_count)
  if count
    text.mark_set(:insert, 'insert display linestart')
  else
    text.mark_set(:insert, 'insert linestart')
  end
end

.start_of_text(text) ⇒ Object

Basically like [go_line] without arguments, but much nicer name.



154
155
156
# File 'lib/ver/methods/move.rb', line 154

def start_of_text(text)
  text.mark_set(:insert, "1.0")
end

.up_down_line(text, count) ⇒ Object

OK, finally found the issue.

the implementation of tk::TextUpDownLine is smart, but not smart enough. It doesn’t assume large deltas between the start of up/down movement and the last other modification of the insert mark.

This means that, after scrolling with up/down for a few hundred lines, it has to calculate the amount of display lines in between, which is a very expensive calculation and time increases O(delta_lines).

We’ll try to solve this another way, by assuming that there are at least a few other lines of same or greater length in between, we simply compare against a closer position and make delta_lines as small as possible.

Now, if you go to, like column 100 of a line, and there is never a line as long for the rest of the file, the scrolling will still slow down a lot. This is an issue we can fix if we “forget” the @udl_pos_orig after a user-defined maximum delta (something around 200 should do), will implement that on demand.



366
367
368
369
# File 'lib/ver/methods/move.rb', line 366

def up_down_line(text, count)
  target = text.up_down_line(count)
  text.mark_set(:insert, target)
end

.virtual(text, action, count = text.prefix_count) ⇒ Object



175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
# File 'lib/ver/methods/move.rb', line 175

def virtual(text, action, count = text.prefix_count)
  pos = text.index(:insert)

  if action.respond_to?(:call)
    action.call(text, count)
  else
    send(action, text, count)
  end

  mark = text.index(:insert)
  text.mark_set(:insert, pos)
  return [pos, mark].sort
rescue => ex
  VER.error(ex)
end

.word_char_type(char) ⇒ Object



275
276
277
278
279
280
281
282
283
# File 'lib/ver/methods/move.rb', line 275

def word_char_type(char)
  case char
  when /\w/; :word
  when /\S/; :special
  when /\s/; :space
  else
    Kernel.raise "No matching char type for: %p" % [char]
  end
end