Class: Ruvi::EditorApp

Inherits:
Object
  • Object
show all
Defined in:
lib/debug.rb,
lib/front.rb,
lib/search.rb,
lib/shikaku.rb,
lib/widgets.rb,
lib/bindings.rb,
lib/commands.rb,
lib/movement.rb,
lib/curses-ui.rb,
lib/selections.rb,
lib/timemachine.rb,
lib/virtualbuffers.rb

Defined Under Namespace

Modules: BaseFakeBufferModule, BufferListExtension, DiffLogger, HierarchyListExtension Classes: BufferIdAllocator, CommandContext, SearchContext, Settings

Constant Summary collapse

ASCII_UPPER =

TODO we should replace all this bullshit - by translating all the possible curses keys

255
CR_KEY =
13
BACKSPACE_KEYS =
[8, 127, 263]
ESC_KEY =
27
KEYPRESS_COUNT_INTERVAL =

0.2 - the higher this is the higher LAST_INTERVAL_KEYPRESS_COUNT is relatively

0.1
FORCE_REDRAW_TIMEOUT =

0.1

0.005
LAST_INTERVAL_KEYPRESS_COUNT =

heavily related to KEYPRESS_COUNT_INTERVAL

2
SELECT_POLL_INTERVAL =
0.02
SUSPEND_KEYS =
[26, 407]

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeEditorApp

Returns a new instance of EditorApp.



118
119
120
# File 'lib/shikaku.rb', line 118

def initialize
    reset_state
end

Instance Attribute Details

#buffersObject (readonly)

Returns the value of attribute buffers.



69
70
71
# File 'lib/shikaku.rb', line 69

def buffers
  @buffers
end

#change_listenersObject (readonly)

Returns the value of attribute change_listeners.



73
74
75
# File 'lib/shikaku.rb', line 73

def change_listeners
  @change_listeners
end

#current_bufferObject (readonly)

too many attributes!!!!!



67
68
69
# File 'lib/shikaku.rb', line 67

def current_buffer
  @current_buffer
end

#current_docviewObject (readonly)

Returns the value of attribute current_docview.



411
412
413
# File 'lib/shikaku.rb', line 411

def current_docview
  @current_docview
end

#debug_bufferObject (readonly)

Returns the value of attribute debug_buffer.



68
69
70
# File 'lib/shikaku.rb', line 68

def debug_buffer
  @debug_buffer
end

#docview2Object

Returns the value of attribute docview2.



74
75
76
# File 'lib/shikaku.rb', line 74

def docview2
  @docview2
end

#docview_to_rulerObject

Returns the value of attribute docview_to_ruler.



74
75
76
# File 'lib/shikaku.rb', line 74

def docview_to_ruler
  @docview_to_ruler
end

#end_char_modeObject

Returns the value of attribute end_char_mode.



74
75
76
# File 'lib/shikaku.rb', line 74

def end_char_mode
  @end_char_mode
end

#history_bufferObject (readonly)

Returns the value of attribute history_buffer.



68
69
70
# File 'lib/shikaku.rb', line 68

def history_buffer
  @history_buffer
end

#hl_selectionObject (readonly)

Returns the value of attribute hl_selection.



70
71
72
# File 'lib/shikaku.rb', line 70

def hl_selection
  @hl_selection
end

#main_paste_bufferObject (readonly)

Returns the value of attribute main_paste_buffer.



68
69
70
# File 'lib/shikaku.rb', line 68

def main_paste_buffer
  @main_paste_buffer
end

#mutexObject

Returns the value of attribute mutex.



75
76
77
# File 'lib/shikaku.rb', line 75

def mutex
  @mutex
end

#selectionObject (readonly)

Returns the value of attribute selection.



70
71
72
# File 'lib/shikaku.rb', line 70

def selection
  @selection
end

#settingsObject

Returns the value of attribute settings.



74
75
76
# File 'lib/shikaku.rb', line 74

def settings
  @settings
end

#status_barObject (readonly)

Returns the value of attribute status_bar.



71
72
73
# File 'lib/shikaku.rb', line 71

def status_bar
  @status_bar
end

#widgetsObject (readonly)

Returns the value of attribute widgets.



72
73
74
# File 'lib/shikaku.rb', line 72

def widgets
  @widgets
end

Class Method Details

.alloc_new_bufferObject



301
302
303
304
# File 'lib/shikaku.rb', line 301

def self.alloc_new_buffer
    BufferIdAllocator::instance.max_buffer_idx += 1
    (BufferIdAllocator::instance.max_buffer_idx - 1)
end

.app_instanceObject



122
123
124
# File 'lib/shikaku.rb', line 122

def EditorApp.app_instance
    @@app
end

.each_lineslice_in_selection(buffer, selc) ⇒ Object

REQUIRES RIGHT WAY UP SELECTION



121
122
123
124
125
126
127
128
# File 'lib/selections.rb', line 121

def EditorApp.each_lineslice_in_selection buffer, selc
    ((selc.s.y)..(selc.e.y)).each {
       |currenty| 
       line = buffer.lines[currenty]
       break if currenty >= buffer.lines.length
       yield EditorApp.lineslice_for_selection_intersect_with_y(selc, currenty, line.length)
    }
end

.file_to_buffer_lines(fname) ⇒ Object



343
344
345
346
347
348
349
350
351
352
353
# File 'lib/shikaku.rb', line 343

def self.file_to_buffer_lines fname
    buffer_lines = []
    File.open(fname, "r") { 
        |fp|
        fp.each_line { 
            |l|
            buffer_lines << BufferLine.new(l.chomp)
        }
    }
    buffer_lines
end

.invalidate_buffer_line(buffer, y) ⇒ Object



399
400
401
# File 'lib/shikaku.rb', line 399

def EditorApp.invalidate_buffer_line buffer, y
    buffer.redraw_list << y
end

.join_next_line_onto_this(buffer, myy) ⇒ Object



773
774
775
776
777
778
779
780
781
782
783
784
785
# File 'lib/shikaku.rb', line 773

def EditorApp.join_next_line_onto_this buffer, myy
    # join current line onto previous line - move to old end of last line
    line_to_join = nil
    DiffLogger::RemoveLineChange.new(buffer, myy+1) {
        line_to_join = buffer.lines.delete_at(myy+1)
    }
    DiffLogger::ModifyLineChange.new(buffer, myy) {
        join_to = buffer.lines[myy]
        buffer.move_to_x join_to.length
        join_to << line_to_join
        EditorApp::app_instance.redraw buffer # should actually just redraw from this point on
    }
end

.lineslice_for_selection_intersect_with_y(selc, currenty, line_len) ⇒ Object

REQUIRES RIGHT WAY UP SELECTION - though it may not appear that is does :)



53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
# File 'lib/selections.rb', line 53

def EditorApp.lineslice_for_selection_intersect_with_y selc, currenty, line_len
    selc = selc.dup
    hlls      = LineSlice.new
    hlls.newline_at_start, hlls.newline_at_end = false, false
    hlls.y    = currenty
    lined     = (selc.mode == :selc_lined)
    box       = (selc.mode == :selc_boxed)
    multiline = (selc.e.y != selc.s.y)
    end_char_mode = (EditorApp::app_instance.end_char_mode and selc.s != selc.e)
    sorted_x_1, sorted_x_2 = *([selc.s.x, selc.e.x].sort)
    if not (((selc.s.y)..(selc.e.y)) === currenty)
        return nil
    elsif multiline and currenty == selc.s.y and !box
        # first line in selection
        hlls.x1 = lined ? 0 : sorted_x_1
        hlls.x2 = line_len
        hlls.newline_at_start = lined
        hlls.newline_at_end   = true
    elsif multiline and currenty == selc.e.y and !box
        # end line of selection, if in end_char_mode we wish to join with next line
        # to indicate a join, we want start + !end - app.end_char_mode
        # TODO - clean up the boolean logic!!!
        if lined
            hlls.newline_at_end   = lined
            hlls.newline_at_start = true
        elsif end_char_mode
            hlls.newline_at_end   = false
            hlls.newline_at_start = true
        else
            hlls.newline_at_end   = false
            hlls.newline_at_start = false
        end
        hlls.x1 = 0
        hlls.x2 = lined ? line_len : sorted_x_2
    elsif !multiline and currenty == selc.s.y and !box # implicit: and currenty == selc.e.y # "!=" surely???
        # single line selection - if its end_char_mode we wish to join to next line
        hlls.newline_at_end   = (lined || end_char_mode)
        hlls.newline_at_start = lined
        hlls.x1 = lined ? 0 : sorted_x_1
        hlls.x2 = lined ? line_len : sorted_x_2
    else
        hlls.newline_at_end   = !box
        hlls.newline_at_start = !box
        hlls.x1 = box ? sorted_x_1 : 0
        hlls.x2 = box ? sorted_x_2 : line_len
    end
    hlls.x2 += 1
    return hlls
end

.load_file_as_buffer(app, fname) ⇒ Object



355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
# File 'lib/shikaku.rb', line 355

def self.load_file_as_buffer app, fname
    buf = self.new_buffer app, fname, :no_blank_line, :need_bnum, :delay_edlog_load
    if $replaying
        buf.lines = $action_replay_log.buffers_loaded[fname]
    else
    begin
        buf.lines += self.file_to_buffer_lines(fname)
    rescue => e
        # wait for user to :w rather than failing now           
        status_bar_edit_line "file not found: creating empty buffer" unless @status_bar.nil?
    end
    $action_replay_log.buffers_loaded[fname] = buf.lines
    end
    buf.lines << "" if buf.lines.empty?
    buf.dlog.load_or_create_difflog
    buf.update_highlighter
    buf
end

.manip_selection(buffer, selc, manipulation, paste_buffer) ⇒ Object

USES right_way_up



131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
# File 'lib/selections.rb', line 131

def EditorApp.manip_selection buffer, selc, manipulation, paste_buffer
    raise  "manip_selection called for invalid selection object" if selc.invalid?
    paste_lines = BufferLineArray.new
    removed_lines = 0
    selc = selc.right_way_up
    lineslices = []
    EditorApp.each_lineslice_in_selection(buffer, selc) {
        |hlls|
        next if hlls.nil?
        lineslices << hlls
    }
    join_list = []
    lineslices.each_with_index {
        |hlls, index| 
        # NB - !newline_at_start +  newline_at_end == join previous line with this (only at start of a selection)
        #    -  newline_at_start +  newline_at_end == delete entire line
        #    - !newline_at_start + !newline_at_end == just copy/modify entire line
        #    -  newline_at_start + !newline_at_end == join next line with this     (only at end of a selection)
        lbuffer = nil
        case manipulation
        when :manip_cut
            updated_y = hlls.y - removed_lines
            if hlls.newline_at_start and hlls.newline_at_end
                DiffLogger::RemoveLineChange.new(buffer, updated_y) {
                    lbuffer = BufferLine.new buffer.lines.delete_at(updated_y)
                    removed_lines += 1
                }
            else
                DiffLogger::ModifyLineChange.new(buffer, updated_y) {
                    line = buffer.lines[updated_y]
                    lbuffer = BufferLine.new line.slice!((hlls.x1)...(hlls.x2)).dup
                }
            end
            if !hlls.newline_at_start and hlls.newline_at_end and index == 0
                join_list << updated_y
            end
            if hlls.newline_at_start and !hlls.newline_at_end and index == (lineslices.length - 1)
                join_list << updated_y - 1 if updated_y != buffer.lines.length - 1
            end
            EditorApp.invalidate_buffer_line buffer, updated_y
        when :manip_copy
            # test - Vjjy
            lbuffer = BufferLine.new buffer.lines[hlls.y].slice((hlls.x1)...(hlls.x2)).dup
        end
        lbuffer.newline_at_start, lbuffer.newline_at_end = hlls.newline_at_start, hlls.newline_at_end
        paste_lines << lbuffer 
    }
    # now we apply the joins post cut in order to prevent utter confusion
    join_list.each {
        |to_join|
        next if to_join == buffer.lines.length
        EditorApp.join_next_line_onto_this buffer, to_join
    }
    if manipulation == :manip_cut
        if buffer.lines.empty?
            DiffLogger::InsertLineAfterChange.new(buffer, -1) {
                buffer.lines << (BufferLine.new "") 
            }
        end
        DiffLogger::CursorPositionChange.new(buffer, buffer.y) {
            y = (selc.s.y).clamp_to 0, buffer.last_line_num
            x = (selc.s.x).clamp_to 0, buffer.last_char_on_line(y)
            buffer.move_to x, y
        }
    end
    paste_buffer.lines = paste_lines
end

.new_buffer(app, *options) ⇒ Object



323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
# File 'lib/shikaku.rb', line 323

def self.new_buffer app, *options
    fname         = options.detect { |opt| opt.is_a? String }
    no_blank_line = options.include? :no_blank_line
    fake_buffer   = options.include? :fake_buffer
    needs_bnum    = options.include? :need_bnum
    delay_edlog_load = options.include? :delay_edlog_load
    bnum = needs_bnum ? alloc_new_buffer : nil
    buf = DocumentBuffer.new app
    app.buffers << buf # TODO - sucky caller should do @buffers << ?
    buf.lines << BufferLine.new("") unless no_blank_line
    buf.fake_buffer = true if fake_buffer
    app.new_status_bar buf if needs_bnum
    fail "are you sure you want to have #{buf.fname} set and delay_edlog_load == #{delay_edlog_load}" \
      if !buf.fname.nil? and delay_edlog_load 
    buf.dlog.load_or_create_difflog unless delay_edlog_load
    buf.fname = fname unless fname.nil?
    buf.bnum = bnum
    buf
end

.perform_layoutObject

each status bar uses up 1 line height wise the remaining main window hbox gets the remaining space



22
23
24
25
26
# File 'lib/widgets.rb', line 22

def self.perform_layout
    scr = WinDescs::instance.stdscr
    WinDescs::instance.descs.clear
    @@app.blub # TODO rename
end

Instance Method Details

#add_binding(initial_options, *keys_a, &block) ⇒ Object



20
21
22
23
24
25
26
# File 'lib/front.rb', line 20

def add_binding initial_options, *keys_a, &block
    keys_a.each {
        |keys|
        hash, key = get_binding_hash_key_pair initial_options, keys
        hash[key] = block
    }
end

#add_command_binding(*keys, &block) ⇒ Object



35
# File 'lib/front.rb', line 35

def add_command_binding *keys, &block; add_binding @stored_command_bindings, *keys, &block; end

#add_curses_key_translators(*params) ⇒ Object



206
207
208
209
# File 'lib/front.rb', line 206

def add_curses_key_translators *params
    hash = *params
    @curses_key_translators = @curses_key_translators.merge hash
end

#add_insert_binding(*keys, &block) ⇒ Object



36
# File 'lib/front.rb', line 36

def add_insert_binding *keys, &block;  add_binding @stored_insert_bindings, *keys, &block;  end

#begin_insert_mode(buffer) ⇒ Object



81
82
83
84
85
# File 'lib/front.rb', line 81

def begin_insert_mode buffer
    buffer.dlog.partial_flush_mode = true
    @mode_stack = [:insert]
    @da_command = ""
end

#begin_normal_mode(buffer) ⇒ Object



87
88
89
90
91
92
# File 'lib/front.rb', line 87

def begin_normal_mode buffer
    @last_command = @da_command
    buffer.dlog.partial_flush_mode = false
    buffer.dlog.flush
    @mode_stack = [:normal]
end

#blah(maxx, maxy) ⇒ Object

TODO



84
85
86
87
88
89
90
91
# File 'lib/widgets.rb', line 84

def blah maxx, maxy # TODO
    @widgets.each { 
        |w|
        next if w.kind_of? Box
        desc = WinDesc.new(*(final_widget_position(w) + final_widget_size(w)))
            WinDescs::instance.descs[w] = desc
    }
end

#blubObject

TODO



93
94
95
96
97
98
# File 'lib/widgets.rb', line 93

def blub # TODO
    return if @root.nil?
    stdscr = WinDescs::instance.stdscr
    blah stdscr.maxx, stdscr.maxy
    @needs_full_redraw = true
end

#buffer_changed_to(buffer) ⇒ Object



413
414
415
416
# File 'lib/shikaku.rb', line 413

def buffer_changed_to buffer
    current_docview.buffer = buffer
    @docview_to_ruler[current_docview].buffer = buffer
end

#buffers_to_saveObject



691
692
693
694
695
696
697
698
# File 'lib/shikaku.rb', line 691

def buffers_to_save
    buffers = []
    each_real_buffer {
        |buffer| 
        buffers << buffer if buffer.dlog.invalidated?
    }
    buffers
end

#calculate_autoindent(buffer, line, my_y, deindent_block) ⇒ Object



633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
# File 'lib/shikaku.rb', line 633

def calculate_autoindent buffer, line, my_y, deindent_block
    current_line = line
    current_line =~ /^(\s*)/
    indent_level = $1 ? $1.length : 0
    buffer.ensure_line_highlight my_y # ummm does current_line need to be used?
    # list of indenting keywords
    last_hlstack = (my_y == 0) ? [] : buffer.hlstacks[my_y - 1]
    hlstack      = buffer.hlstacks[my_y]
    last_hlstack = [] if last_hlstack.nil?
    hlstack      = [] if      hlstack.nil?
    # if hlstack is bigger than previous hlstack then an indent is required
    if hlstack.length > last_hlstack.length
        bras = hlstack.reject { |k| k.str != "(" }
        kets = hlstack.reject { |k| k.str != ")" }
        if bras.length > kets.length
            indent_level = current_line.rindex("(") + 1
        else
            indent_level += config_get_sw
        end
    elsif hlstack.length < last_hlstack.length
        diff = -config_get_sw
        was_bra = (last_hlstack[-1].str == "(")
        if was_bra
            da_y = my_y
            ridx = nil
            while true
                ridx = buffer.lines[da_y].rindex "("
                break if !ridx.nil?
                da_y -= 1
            end
            indent_level = get_indent_level(buffer.lines[da_y])
        elsif deindent_block
            y = my_y
            DiffLogger::ModifyLineChange.new(buffer, y) {
                line = buffer.lines[y]
                line.slice! 0, indent_level
                indent_level = [(indent_level + diff), 0].max
                ecm = @end_char_mode
                buffer.move_to_x [(buffer.x + diff), 0].max
                @end_char_mode = ecm
                line[0, 0] = (" " * indent_level)
            }
        end
    end
    (" " * indent_level)
end

#clear_buffers_with_extension(extension) ⇒ Object



547
548
549
550
551
552
# File 'lib/shikaku.rb', line 547

def clear_buffers_with_extension extension
    @buffers.delete_if {
        |b| 
        b.kind_of? extension
    }
end

#cmd_execute(buffer, cmd_line) ⇒ Object



330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
# File 'lib/front.rb', line 330

def cmd_execute buffer, cmd_line
    @cmd_overrides.each_pair {
        |re, block|
        if cmd_line =~ re
            ctx = CommandContext.new cmd_line, re, buffer
            block.call ctx
            return
        end
    }
    @cmds.each_pair {
        |re, block|
        if cmd_line =~ re
            ctx = CommandContext.new cmd_line, re, buffer
            block.call ctx
            return
        end
    }
    status_bar_edit_line "Unknown command: #{cmd_line}"
end

#config_get_nuObject

TODO these config routines are ugly… make them generic somehow!



82
83
84
# File 'lib/shikaku.rb', line 82

def config_get_nu
    @settings[:nu] == "true"
end

#config_get_splitObject



86
87
88
# File 'lib/shikaku.rb', line 86

def config_get_split
    @settings[:split] == "true"
end

#config_get_swObject



94
95
96
# File 'lib/shikaku.rb', line 94

def config_get_sw
    @settings[:sw].to_i
end

#config_get_tab_sizeObject



98
99
100
# File 'lib/shikaku.rb', line 98

def config_get_tab_size
    @settings[:ts].to_i
end

#config_get_twObject



90
91
92
# File 'lib/shikaku.rb', line 90

def config_get_tw
    @settings[:tw].to_i
end

#create_selection(buffer, type, *options, &block) ⇒ Object



94
95
96
97
98
99
100
101
102
103
104
# File 'lib/front.rb', line 94

def create_selection buffer, type, *options, &block
    selc = Selection.new
    selc.mode = type
    selc.s = Point.new(type == :selc_lined ? 0 : buffer.x, buffer.y)
    block.call
    selc.e = Point.new(type == :selc_lined ? buffer.lines[buffer.y].length : buffer.x, buffer.y)
    if options.include? :restore
        buffer.move_to selc.s.x, selc.s.y
    end
    selc
end

#curs_xObject



403
404
405
# File 'lib/shikaku.rb', line 403

def curs_x
    current_docview.cursor.x
end

#curs_yObject



407
408
409
# File 'lib/shikaku.rb', line 407

def curs_y
    current_docview.cursor.y
end

#dbg_selc(buffer, selc) ⇒ Object



24
25
26
27
28
# File 'lib/debug.rb', line 24

def dbg_selc buffer, selc
    dbg(:selc) { selc}
    dbg(:selc) { show_line_with_marker buffer, selc.s }
    dbg(:selc) { show_line_with_marker buffer, selc.e }
end

#delete_binding(initial_options, key) ⇒ Object



28
29
30
31
# File 'lib/front.rb', line 28

def delete_binding initial_options, key
    hash, key = get_binding_hash_key_pair initial_options, key
    hash.delete key # returns also
end

#delete_command_binding(key) ⇒ Object



33
# File 'lib/front.rb', line 33

def delete_command_binding key;        delete_binding @stored_command_bindings, key;        end

#delete_insert_binding(key) ⇒ Object



34
# File 'lib/front.rb', line 34

def delete_insert_binding key;         delete_binding @stored_insert_bindings, key;         end

#display_cursor(buffer, docview) ⇒ Object



520
521
522
523
# File 'lib/shikaku.rb', line 520

def display_cursor buffer, docview
    @focus.canvas.setpos docview.cursor.y, docview.cursor.x # y, x
    @focus.canvas.refresh
end

#do_action(buffer, selc) ⇒ Object



106
107
108
109
110
111
112
113
114
115
116
117
118
# File 'lib/front.rb', line 106

def do_action buffer, selc
    case @command
    when "c", "d", "y"
        is_y = (@command == "y")
        is_c = (@command == "c")
        EditorApp.manip_selection buffer, selc, is_y ? :manip_copy : :manip_cut, pop_paste_buffer
        redraw buffer # - optimize!!!
        # EditorApp.invalidate_buffer_line buffer, buffer.y unless is_y
        @end_char_mode = true if selc.s.x == buffer.lines[buffer.y].length
        begin_insert_mode(buffer) if is_c 
        @command = ""
    end
end

#do_cmd_mode(c, current_string, &block) ⇒ Object



373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
# File 'lib/front.rb', line 373

def do_cmd_mode c, current_string, &block
    possible_completions_string = ""
    had_completions = (@status_bar.text =~ /:.*\(.*\)/)
    case c
    when CR_KEY, ESC_KEY
        if c == CR_KEY
            block.call true
        else
            status_bar_edit_line "Command mode cancelled."
            block.call false
        end
        return
    when 9
        completions, to_cut, total_set, to_complete, to_skip_when_showing_options = nil, nil, nil, nil, nil
        if (current_string =~ /\s([^ \t]*)$/) 
            # attempt filename completion
            to_complete = $1
            dir = File.dirname(to_complete)
            completions = Dir[to_complete + "*"]
            to_cut = to_complete.length
            parent_dir = (to_complete =~ %r{/$}) ? to_complete : (File.dirname(to_complete) + "/")
            total_set = Dir[parent_dir + "*"]
            to_skip_when_showing_options = parent_dir.length
        else
            # do command completion
            completions = []
            @commands.each_key {
                |key|
                completions << key if key.index(current_string) == 0 or current_string.empty?
            }
            to_cut = current_string.length
            total_set = @commands.keys
            to_complete = current_string
            to_skip_when_showing_options = 0
        end
        completions = completions.sort_by { |b| b.length }
        if completions.length > 0
            prefix = find_prefix completions
            cmd_prefix = current_string[0, current_string.length - to_cut]
            if !prefix.empty? and (cmd_prefix + prefix) != current_string
                possible_completions_string = " (#{completions.sort.join ","})"
                current_string.replace cmd_prefix + prefix
            else
                new_string = completions.sort.first.dup
                if new_string == to_complete and had_completions
                    idx = total_set.sort.index(new_string) || 0 # default to the first item
                    to_complete = total_set.sort[(idx + 1) % total_set.length]
                    current_string.replace cmd_prefix + to_complete
                else
                    current_string.replace cmd_prefix + new_string
                    to_complete = new_string
                end
                sorted_total_set = total_set.sort
                selected = to_complete
                current_idx = sorted_total_set.index selected
                if current_idx.nil?
                    selected = completions.first
                    current_idx = sorted_total_set.index selected
                end
                my_subset = nil
                unless current_idx.nil?
                    top = limit_to_positive(sorted_total_set.length - 1)
                    start_idx = (current_idx - 2).clamp_to(0, top)
                    end_idx =   (current_idx + 2).clamp_to(0, top)
                    my_subset = sorted_total_set.slice(start_idx..end_idx)
                    my_subset.unshift nil if (start_idx != 0)
                    my_subset.push    nil if (end_idx != top)
                else
                    my_subset = sorted_total_set
                end
                options = my_subset.map { 
                    |s| 
                    t = s.slice(to_skip_when_showing_options..-1) rescue ""
                    (s == nil) ? "..." : ( (s == selected) ? "[#{t}]" : t )
                }.join(",")
                possible_completions_string = " (#{options})"
            end
            if !had_completions and completions.length == 1
                possible_completions_string = ""
            end
        end
    when *BACKSPACE_KEYS
        if current_string.length > 0
            current_string.slice!(-1) 
        else
            status_bar_edit_line "Command mode cancelled."
            block.call false
            return
        end
    else
        if c > ASCII_UPPER
            status_bar_edit_line "unhandled non ascii entry in do_cmd_mode: #{c}"
        else
            current_string << c.chr
        end
    end
    status_bar_edit_line ":#{current_string}#{possible_completions_string}", current_string.length + 1
end

#do_command(buffer, c) ⇒ Object



138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
# File 'lib/front.rb', line 138

def do_command buffer, c
    number_char = nil
    char_string = (c > 256) ? @curses_key_translators[c] : c.chr
    @current_command_binding_state = @stored_command_bindings if @current_command_binding_state.nil?
    if (c > 256) or !@current_command_binding_state[c].nil?
        if @command_context.nil?
            @command_context = Struct.new(:input).new
            @command_context.input = ""
        end
        char_string.each_byte {
          |c|
          options = @current_command_binding_state[c]
          if options.is_a? Hash
              @current_command_binding_state = options
              @command_context.input << c.chr
          else
              @command_context.input << c.chr
              case options.arity
              when 2
                  options.call c, @command_context
              else
                  options.call c
              end
              @current_command_binding_state = nil
              @command_context = nil
          end
        }
    else
        @command_context = nil
        @current_command_binding_state = nil
        case c
        when ?0..?9
            number_char = c
        else
            raise "[unknown key `#{Curses.keyname(c)}'=#{c}]" if $test_case
            status_bar_edit_line "[unknown key `#{Curses.keyname(c)}'=#{c}] "
        end
        # what a freaking haccckk :(
        if number_char.nil?
            unless @number.nil? or !@command.nil?
                @number = nil
                status_bar_edit_line "number ended..."
            end
        else
            if @number.nil? and number_char == ?0
                buffer.move_to_x 0
            else
                @number = "" if @number.nil?
                @number << number_char.chr
            end
        end
    end
    if    !@selection.nil? and (@selection.mode == :selc_normal || @selection.mode == :selc_boxed)
        @selection.e = Point.new(buffer.x, buffer.y)
    elsif !@selection.nil? and @selection.mode == :selc_lined
        @selection.e = Point.new(buffer.lines[buffer.y].length, buffer.y)
    end
    @da_command << c.chr unless @mode_stack.last == :command or !@changed or c > 255
    if @command.empty? and @mode_stack.last == :normal and !@da_command.empty? and @changed
        @changed = false
        @last_command = @da_command
        @da_command = ""
    end
    buffer.dlog.flush
    update_selection buffer
    @status_bar.invalidate
end

#do_letter(buffer, c) ⇒ Object



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
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
# File 'lib/front.rb', line 211

def do_letter buffer, c
    @current_insert_binding_state = @stored_insert_bindings if @current_insert_binding_state.nil?
    c = 8 if BACKSPACE_KEYS.include? c
    char_string = (c > 256) ? @curses_key_translators[c] : c.chr
    if (c > 256) or !@current_insert_binding_state[c].nil?
        char_string.each_byte {
            |c|
            options = @current_insert_binding_state[c]
            if options.is_a? Hash
                @current_insert_binding_state = options
            else
                options.call c
                @current_insert_binding_state = nil
            end
        }
    else
        @current_insert_binding_state = nil
        if c > ASCII_UPPER
            status_bar_edit_line "unhandled non ascii entry in do_letter: #{c}"
        else
            if !@settings[:tw].nil? and buffer.x > config_get_tw
                current_line = buffer.lines[buffer.y]
                x_was = buffer.x
                ecm = @end_char_mode
                next_word(buffer, :skip_space, :letter_after, :reverse, :greedy) if buffer.x > 0
                if buffer.x == 0
                    buffer.x = x_was
                else
                    indent_string = calculate_autoindent buffer, current_line, buffer.y, true
                    line = nil
                    DiffLogger::ModifyLineChange.new(buffer, buffer.y) {
                        line = BufferLine.new(indent_string + current_line.slice!(buffer.x..-1)).dup
                        current_line.gsub!(/\s*$/, '')
                    }
                    DiffLogger::InsertLineAfterChange.new(buffer, buffer.y+1) {
                        buffer.lines.insert_after buffer.y+1, line 
                    }
                    buffer.y += 1
                    buffer.move_to_x line.length
                    @end_char_mode = ecm
                end
            end
            line = buffer.lines[buffer.y]
            ch = (c == 9) ? (" " * config_get_sw) : c.chr
            if !@end_char_mode and buffer.out_of_bounds buffer.y, buffer.x
                raise "Out of bounds while inserting character in do_letter!" if $test_case
                status_bar_edit_line "Can't insert character, cursor at invalid position on line!!!"
                return
            end
            DiffLogger::ModifyLineChange.new(buffer, buffer.y) {
                if @end_char_mode
                    # how to prevent replace_mode in this code path?
                    line << ch
                    buffer.move_to_x limit_to_positive(line.length - 1)
                    @end_char_mode = true
                else
                    line[buffer.x, @replace_mode ? 1 : 0] = ch
                    buffer.move_to_x(buffer.x + ch.length)
                end
            }
            if @settings[:autopair] == "true"
             catch(:done) {
                end_pair = nil
                case c
                when ?\ 
                    end_pair = " " if @last_inserted_char == ?{
                when ?{ 
                    end_pair = "}"
                end
                if !end_pair.nil?
                    DiffLogger::ModifyLineChange.new(buffer, buffer.y) {
                        if @end_char_mode
                            line << end_pair
                            buffer.x += 1
                            @end_char_mode = false
                        else
                            line[buffer.x, 0] = end_pair
                        end
                    }
                end
             }
            end
            EditorApp.invalidate_buffer_line buffer, buffer.y
        end
        if @replace_single_letter
            buffer.dlog.flush
            begin_normal_mode buffer
            @replace_mode = false
            @replace_single_letter = false
            buffer.x -= 1
        end
    end
    @last_inserted_char = c
    @da_command << c.chr unless @mode_stack.last == :normal or c > 255
    buffer.dlog.flush
    @status_bar.invalidate
end

#do_movement_key(buffer, c, *options) ⇒ Object



51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
# File 'lib/front.rb', line 51

def do_movement_key buffer, c, *options
    c = normalize_cursor_key c
    insert_mode = options.include? :insert_mode
    raise "ummm a @command number when in insert_mode are u insane???" if insert_mode and !@command.empty?
    if insert_mode and [?h, ?l].include? c
        eol_x = buffer.last_char_on_line(buffer.y)
        if buffer.x == eol_x and ( (@end_char_mode and c == ?h) or (!@end_char_mode and c == ?l) )
            @end_char_mode = !@end_char_mode
            return
        elsif buffer.x == 0  and c == ?h
            move_x buffer, -1
            @end_char_mode = !@end_char_mode
            return
        end
    end
    (@number || "1").to_i.times { 
        case c
        when ?k
            move_y buffer, -1
        when ?j
            move_y buffer, +1
        when ?h
            move_x buffer, -1
        when ?l
            move_x buffer, +1
        end
        @number = nil
    }
end

#do_search(buffer, c, word = nil) ⇒ Object



5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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
55
56
57
58
59
# File 'lib/search.rb', line 5

def do_search buffer, c, word = nil
    forced_next = (c == Curses::KEY_CTRL_N || c == Curses::KEY_CTRL_P)
    @search.reverse = (c == Curses::KEY_CTRL_P) if forced_next
    if !@search.already_performed
        @case_sensitive = false
        @search.history_pos = @search_history.length
        if !word.nil?
            @search_history << word.dup
        elsif forced_next
            @search.history_pos -=  1
        else
            @search_history << ""
        end
        @search.string = @search_history.last # rename
    end
    case c
    when Curses::KEY_CTRL_I
        @case_sensitive = !@case_sensitive 
    when Curses::KEY_UP
        @search.history_pos -= 1 if @search.history_pos > 0
        @search.string = @search_history[@search.history_pos].dup
    when Curses::KEY_DOWN
        @search.history_pos += 1 if @search.history_pos < (@search_history.length - 1)
        @search.string = @search_history[@search.history_pos].dup
    when ESC_KEY, CR_KEY
        if c == CR_KEY and !@hl_selection.nil?
            buffer.move_to @hl_selection.s.x, @hl_selection.s.y
        end
        @hl_selection = nil
        @search.history_pos = @search_history.length - 1
        @search.already_performed = false
        if @search.got_no_key
            @search.history_pos = @search_history.length - 2
            @search.string = @search_history[@search.history_pos]
            @search_history.pop
            perform_search buffer, true
            buffer.move_to @hl_selection.s.x, @hl_selection.s.y
        end
        @mode_stack.pop
        @search = nil
        return
    when *BACKSPACE_KEYS
        @search.string.slice!(-1)
    when Curses::KEY_CTRL_N, Curses::KEY_CTRL_P
        ;
    else
        if c > ASCII_UPPER
            status_bar_edit_line "unhandled non ascii entry in do_search: #{c}"
        else
            @search.string << c.chr
            @search.got_no_key = false
        end
    end
    perform_search buffer, forced_next
end

#each_real_bufferObject



280
281
282
283
284
285
286
# File 'lib/shikaku.rb', line 280

def each_real_buffer
    @buffers.each {
        |buffer|
        next if buffer.fake_buffer
        yield buffer
    }
end

#end_selection(buffer) ⇒ Object



199
200
201
202
203
204
# File 'lib/selections.rb', line 199

def end_selection buffer
    # test - Vjjy from above should cover this
    @selection_for_last_command = @selection
    @selection = nil
    redraw buffer
end

#ensure_buffer_highlighted(buffer, pass = false) ⇒ Object



732
733
734
735
736
737
738
739
740
741
742
743
744
# File 'lib/shikaku.rb', line 732

def ensure_buffer_highlighted buffer, pass = false
    while true
        @mutex.synchronize {
            line_num = buffer.first_non_highlighted_line
            if line_num.nil? or line_num > buffer.lines.length
                @status_bar.highlight_progress = nil
                return
            end
            buffer.ensure_line_highlight line_num
        }
        Thread.pass if pass
    end
end

#exec_into_buffer_lines(cmd) ⇒ Object



680
681
682
683
684
685
686
687
688
689
# File 'lib/shikaku.rb', line 680

def exec_into_buffer_lines cmd
    buffer_lines = []
    IO.popen(cmd) {
        |io|
        while not io.eof?
            buffer_lines << BufferLine.new(io.gets)
        end
    }
    buffer_lines
end

#final_widget_position(widget) ⇒ Object



63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
# File 'lib/widgets.rb', line 63

def final_widget_position widget
    last_parent = widget
    current_box = widget.parent
    offset_x, offset_y = 0, 0
    while !current_box.nil?
        idx = @widgets.index last_parent
        raise "blub?" if !current_box.is_a? VBox and !current_box.is_a? HBox
        children = @widgets.slice(0...idx).find_all { |w| w.parent == current_box }
        if current_box.is_a? HBox
            size = children.inject(0) { |sum, w| sum += final_widget_size(w)[0] }
            offset_x += size
        else
            size = children.inject(0) { |sum, w| sum += final_widget_size(w)[1] }
            offset_y += size
        end
        last_parent = current_box
        current_box = current_box.parent
    end
    [offset_x, offset_y]
end

#final_widget_size(widget) ⇒ Object



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
55
56
57
58
59
60
61
# File 'lib/widgets.rb', line 28

def final_widget_size widget
    # !FIXME! - this is crap and hardcoded - !FIXME!
    docviews = @widgets.find_all { |w| w.is_a? DocView }
    number_of_horiz_splits = docviews.length
    status_bars = @widgets.find_all { |w| w.is_a? StatusBar }
    total_size_status_bars = status_bars.inject(0) { |sum, item| sum += item.height }
    rulers = @widgets.find_all { |w| w.is_a? Ruler }
    first_size_ruler = rulers.empty? ? 0 : rulers.first.width
    sx, sy = WinDescs::instance.stdscr.maxx, WinDescs::instance.stdscr.maxy
    case widget
    when @doc_with_ruler, @doc_with_ruler2
        children = @widgets.find_all { |w| w.parent == widget }
        dx, dy = nil, nil
        if widget.is_a? HBox
            dx = children.inject(0) { |sum, w| sum += final_widget_size(w)[0] }
            # we assume that all the widths will be the same... maybe we should assert?
            dy = final_widget_size(children.first)[1]
        else
            # we assume that all the heights will be the same... maybe we should assert?
            dx = final_widget_size(children.first)[0]
            dy = children.inject(0) { |sum, w| sum += final_widget_size(w)[1] }
        end
        return [dx, dy]
    when Ruler
        return [first_size_ruler, (sy - total_size_status_bars) / number_of_horiz_splits]
    when DocView
        return [sx - first_size_ruler, (sy - total_size_status_bars) / number_of_horiz_splits]
    when StatusBar
        # we no parent and we are width.is_nil therefore we use the entire width
        # we have .height.is_not_nil so we take this height
        return [sx, widget.height]
    end
    raise "oops"
end

#find_match_in_line(re, line, myx, go_backwards, forced_next) ⇒ Object



61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/search.rb', line 61

def find_match_in_line re, line, myx, go_backwards, forced_next
    line_to_compare = line.dup
    start_pos, end_pos = nil, nil
    if !go_backwards
        skiplen = myx
        skiplen += 1 if forced_next
        line_to_compare.slice! 0, skiplen
        matches = (line_to_compare =~ re)
        matches = false if $1.nil? or $2.nil?
        start_pos, end_pos = ($1.length) + skiplen, ($1.length + $2.length - 1) + skiplen if matches
    else
        # chop off end - the bit after myx
        skiplen = myx
        line_to_compare.slice! skiplen...line_to_compare.length
        x = 0
        # scan all matches until the last match of the leftover string
        line_to_compare.scan(re) { 
            matches   = true 
            start_pos = x + $1.length
            end_pos   = start_pos + $2.length - 1
            x += ($1.length + $2.length)
        }
    end
    return matches, start_pos, end_pos
end

#find_prefix(completions) ⇒ Object



356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
# File 'lib/front.rb', line 356

def find_prefix completions
    prefix = completions.first.dup
    completions.slice(1..-1).each {
        |str|
        idx = 0
        prefix.each_byte {
            |byte|
            if str.length < idx or str[idx] != byte
                prefix = str.slice(0,idx)
                break
            end
            idx += 1
        }
    }
    prefix
end

#finishObject



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

def finish
    Curses.int_finish # ext
end

#flush_finish_redraw(buffer) ⇒ Object



418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
# File 'lib/shikaku.rb', line 418

def flush_finish_redraw buffer
    buffer = nil # we *totally* ignore buffer now!, so lets remove it from the method prototype soon!
    position_cursor current_docview.buffer, current_docview
    watched_buffers = []
    @widgets.each { 
        |w| 
        next unless w.is_a? DocView
        watched_buffers << w.watch_buffer unless watched_buffers.include? w.watch_buffer
    }
    nts = nil
    redraw_was_needed = false
unwatching_buffers = @widgets.dup
watched_buffers.each {
    |buffer| 
    watching_buffer = @widgets.find_all { |w| w.watch_buffer == buffer }
    watching_buffer.each {
        |widget|
        unwatching_buffers.delete widget
    } 
    if buffer.needs_redraw or @needs_full_redraw
        redraw_was_needed = true
        redraw buffer
    elsif 
        buffer.redraw_list = buffer.redraw_list.sort.uniq
        if buffer.got_a_scroll_already
            watching_buffer.each {
                |widget|
                widget.canvas.scrl buffer.need_to_scroll
            }
            nts = buffer.need_to_scroll
            buffer.need_to_scroll = 0
        end
    end
    buffer.needs_redraw = false
    buffer.got_a_scroll_already = false
}
    @needs_full_redraw = false
    unwatching_buffers.each {
        |widget|
        next if widget.kind_of? Box
        (0...widget.height).each {
           |y|
           widget.render y
        }
    }
watched_buffers.each {
    |buffer|
    buffer.redraw_list.delete_if { |y| (y < buffer.top) or (y > buffer.top + screen_height) }
    last_dirty_list     = buffer.dirty_list || []
    new_y               = buffer.y
    buffer.dirty_list   = [new_y, new_y - (nts || 0)]
    buffer.redraw_list += last_dirty_list
    buffer.redraw_list += buffer.dirty_list
    buffer.redraw_list = buffer.redraw_list.sort.uniq
    watching_buffer = @widgets.find_all { |w| w.watch_buffer == buffer }
    buffer.redraw_list.each {
        |y| 
        watching_buffer.each {
            |widget|
            dbg(:dbg_highlight) { "rendering #{y} for widget type #{widget.type}" }
            widget.render y - buffer.top
        }
    }
    buffer.redraw_list = []
}
    if !nts.nil? or redraw_was_needed
        # we clear the bottom line of the buffer 
        # as the text scrolling appears to draw 
        # outside its draw area

      descs = [WinDescs::instance.descs[@docview]]
      descs << WinDescs::instance.descs[@docview2] if @widgets.include? @docview2
      descs.each {
        |desc|
        scr = WinDescs::instance.stdscr
        scr.set_attr false, Curses::COLOR_WHITE, Curses::COLOR_BLACK
        scr.setpos(desc.y + desc.sy - 1, 0) # y, x
        scr.addstr " " * (scr.maxx - 1)
        scr.refresh
      }
    end
    refresh_widgets
    display_cursor current_docview.buffer, current_docview
end

#get_binding_hash_key_pair(initial_options, keys) ⇒ Object



5
6
7
8
9
10
11
12
13
14
15
16
17
18
# File 'lib/front.rb', line 5

def get_binding_hash_key_pair initial_options, keys
    if keys.is_a? String
        last_char = keys.slice!(-1)
        options = initial_options
        keys.each_byte {
            |b|
            options[b] ||= {}
            options = options[b]
        }
        return options, last_char
    else
        return initial_options, keys
    end
end

#get_cmd_lineObject



351
352
353
354
# File 'lib/front.rb', line 351

def get_cmd_line
    # used by testcases.rb
    @cmd_line
end

#get_indent_level(line) ⇒ Object



542
543
544
545
# File 'lib/shikaku.rb', line 542

def get_indent_level line
    line =~ /^(\s*)/
    return $1 ? $1.length : 0
end

#get_user_input(question = "") ⇒ Object



121
122
123
124
125
126
127
128
129
130
131
132
133
134
# File 'lib/curses-ui.rb', line 121

def get_user_input question = ""
    current_string = ""
    status_bar_edit_line "#{question}:"
    flush_finish_redraw current_buffer
    while true
        c = Curses.getch
        do_cmd_mode(c, current_string) {
           |ok|
           return ok ? current_string : nil
        }
        flush_finish_redraw current_buffer
    end
    fail "impossible"
end

#get_word_under_cursor(buffer, *options) ⇒ Object



554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
# File 'lib/shikaku.rb', line 554

def get_word_under_cursor buffer, *options
    ruby_vars = (options.include? :look_for_ruby_variables)
    x_was = buffer.x
    next_word(buffer, :letter_after, :reverse)
    # begin
    x_pos_test_1 = buffer.x
    next_word(buffer, :letter_after) # test to see if we were start of string
    if buffer.x == x_was
        # we were already at the start, so we went too far
        # pass as we are back where we should be now anyway
    else
        # we are now at the start of a word
        buffer.x = x_pos_test_1
    end
    # end
    x_pos1 = buffer.x
    next_word(buffer, :letter_after)
    x_pos2 = buffer.x
    buffer.x = x_was
    if ruby_vars
        prev_char = (buffer.lines[buffer.y][x_pos1-1] rescue 0)
        x_pos1 -= 1 if [?$, ?@].include? prev_char
        if  prev_char == ?@ \
        and (buffer.lines[buffer.y][x_pos1-1] rescue 0) \
         == ?@
            x_pos1 -= 1
        end
    end
    return x_pos1...x_pos2 
end

#handle_generic_command(buffer, c) ⇒ Object



120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
# File 'lib/front.rb', line 120

def handle_generic_command buffer, c
    got_selection = !@selection.nil?
    if got_selection
        @command = c.chr
        do_action buffer, @selection
        @command = ""
        end_selection buffer
    else
        @command << c.chr
        if @command == (c.chr * 2)
            selc = Selection.new Point.new(0, buffer.y), Point.new(buffer.lines[buffer.y].length, buffer.y), :selc_lined
            @command = c.chr
            do_action buffer, selc
            @command = ""
        end
    end
end

#input_loopObject



136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
# File 'lib/curses-ui.rb', line 136

def input_loop
    pagedown_count = 0
    last_draw = Time.now
    flush_finish_redraw current_buffer
    last_count_end = Time.now
    last_count = 0
    count = 0
    timeout = nil
    # main loop
    while true
        read_fds = IO.select([$stdin], nil, nil, timeout)
        # got input? if so. lets read
        unless read_fds.nil?
            c = Curses.getch
            if SUSPEND_KEYS.include? c
                Curses.close_screen
                Process.kill "SIGTSTP", $$
                Curses.init_screen 
                redraw current_buffer
                flush_finish_redraw current_buffer
                next
            end
            if Time.now > last_count_end + KEYPRESS_COUNT_INTERVAL
                last_count = count
                last_count_end = Time.now
                count = 0
            else
                count += 1
            end
            send_key(c)
        end
        if last_count > LAST_INTERVAL_KEYPRESS_COUNT                
            if (Time.now - last_draw) > FORCE_REDRAW_TIMEOUT
                flush_finish_redraw current_buffer
                last_draw = Time.now
            end
            timeout = SELECT_POLL_INTERVAL
        else
            flush_finish_redraw current_buffer
            last_draw = Time.now
            timeout = nil
        end
    end
end

#invalidate_screen_line(buffer, y) ⇒ Object



395
396
397
# File 'lib/shikaku.rb', line 395

def invalidate_screen_line buffer, y
    buffer.redraw_list << y + buffer.top
end

#itr_backwards(buffer, tok, y) ⇒ Object



597
598
599
600
601
602
603
604
605
606
607
# File 'lib/shikaku.rb', line 597

def itr_backwards buffer, tok, y
    buffer.ensure_line_highlight y
    idx = buffer.tokens[y].index tok
    buffer.tokens[y].slice(0...idx).reverse_each { |tok| yield y, tok }
    return if y == 0
    (y-1).downto(0) {
        |ny|
        buffer.ensure_line_highlight ny
        buffer.tokens[ny].reverse_each { |tok| yield ny, tok }
    }
end

#itr_forewards(buffer, tok, y) ⇒ Object



585
586
587
588
589
590
591
592
593
594
595
# File 'lib/shikaku.rb', line 585

def itr_forewards buffer, tok, y
    buffer.ensure_line_highlight y
    idx = buffer.tokens[y].index tok
    buffer.tokens[y].slice(idx..-1).each { |tok| yield y, tok }
    return if buffer.lines.length == y
    (y+1).upto(buffer.last_line_num) {
        |ny|
        buffer.ensure_line_highlight ny
        buffer.tokens[ny].each { |tok| yield ny, tok }
    }
end

#line_displayed?(buffer, y) ⇒ Boolean

Returns:

  • (Boolean)


538
539
540
# File 'lib/shikaku.rb', line 538

def line_displayed? buffer, y
    (((buffer.top)..(buffer.top + screen_height - 1)) === y)
end

#load_script_file(scriptfname) ⇒ Object



746
747
748
749
750
751
752
# File 'lib/shikaku.rb', line 746

def load_script_file scriptfname
    File.open(scriptfname) { 
        |file| 
        content = file.gets nil
        eval content
    }
end

#make_selection_exclusive_given_current_position(buffer, selc) ⇒ Object

SELECTION RELATED STUFF #



43
44
45
46
47
48
49
50
# File 'lib/selections.rb', line 43

def make_selection_exclusive_given_current_position buffer, selc
    pos = Point.new(buffer.x, buffer.y)
    if selc.e == pos
        selc.e.x = limit_to_positive(selc.e.x - 1)
    elsif selc.s == pos
        selc.s.x = (selc.s.x + 1).clamp_to 0, buffer.last_char_on_line(selc.s.y)
    end
end

#move_x(buffer, x_diff, allow_y_change = true) ⇒ Object

WRAPPING MOVEMENT #



94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
# File 'lib/movement.rb', line 94

def move_x buffer, x_diff, allow_y_change = true
    return if buffer.lines.empty?
    # need to limit max buffer.y change to 1
    return if x_diff == 0
    if x_diff > 0
        left_on_this_line = buffer.lines[buffer.y].length - buffer.x
        # we don't want to wrap, so capability states that we decrease the possible by one
        wanted_and_capable_on_this_line = (left_on_this_line - 1).clamp_to(0, x_diff)
        if left_on_this_line == 1 # the wrap == 1
            return unless allow_y_change
            x_diff -= 1
            move_y buffer, 1
            buffer.x = 0
            move_x buffer, x_diff
        else
            x_diff -= wanted_and_capable_on_this_line
            buffer.x += wanted_and_capable_on_this_line
            # we always move to end, and then wrap if needed
        end
    else
        left_on_this_line = buffer.x
        # in this case left_on_this_line == 0 when we can wrap so no need to adjust
        wanted_and_capable_on_this_line = [x_diff.abs, left_on_this_line].min
        if left_on_this_line == 0 # the wrap is at position 0
            return unless allow_y_change
            x_diff += 1 # its a negative
            move_y buffer, -1
            buffer.x = buffer.last_char_on_line(buffer.y)
            move_x buffer, x_diff
        else
            x_diff += wanted_and_capable_on_this_line # its negative
            buffer.x -= wanted_and_capable_on_this_line
            # we always move to the start, and then wrap if needed
        end
    end
end

#move_y(buffer, y_diff) ⇒ Object



143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
# File 'lib/movement.rb', line 143

def move_y buffer, y_diff
    return if buffer.lines.empty?
    new_doc_y = buffer.y + y_diff
    if new_doc_y < 0
        scroll_to_bottom buffer
    elsif new_doc_y > buffer.lines.length - 1
        scroll_to_top buffer
    elsif new_doc_y < buffer.top
        diff = buffer.y - new_doc_y
        diff.times { scroll_up(buffer) }
    elsif new_doc_y > (buffer.top + screen_height - 1)
        diff = new_doc_y - buffer.y
        diff.times { scroll_down(buffer) }
    else
        buffer.y = new_doc_y
    end
    if @end_char_mode
        # in insert mode in vim, last char is indicated visually...
        buffer.x = buffer.last_char_on_line(buffer.y)
        # buffer.x accessor resets the end_char_mod, so lets restore :)
        @end_char_mode = true
    else
        buffer.x = buffer.x.clamp_to 0, buffer.last_char_on_line(buffer.y)
    end
end

#new_status_bar(buf) ⇒ Object



315
316
317
318
319
320
321
# File 'lib/shikaku.rb', line 315

def new_status_bar buf
    status_bar = StatusBar.new self, buf
    status_bar.parent = @root
    @widgets << status_bar
    EditorApp.perform_layout
    status_bar
end

#next_word(buffer, *options) ⇒ Object

CLEVER WORD SKIP STUFF #



41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
# File 'lib/movement.rb', line 41

def next_word buffer, *options
    letter_after   = options.include? :letter_after
    rev            = options.include? :reverse
    space_flag     = options.include? :skip_space
    greedy         = options.include? :greedy
    sub_identifier = options.include? :sub_identifier
    line = buffer.lines[buffer.y].dup
    pos = rev ? (line.length-buffer.x+1) : buffer.x
    space = space_flag ? '\s*' : ''
    jump_over_re = nil
    if !rev
        jump_over_re =   (greedy ? '[^\s]*\s*'       \
                                 : '( (\s+)'         \
                                   +
                 (sub_identifier ? '| ([A-Z][a-z]+)' \
                                   '| ([a-zA-Z]+_?)' \
                                   '| ([A-Z]+)'      \
                                   '| ([\d]+)'       \
                                 : '| (\w+)'
                                )  + 
                                   '| ([^\s\w])'     \
                                   '| (.)'           \
                                   ')'               \
                                   + space )
        # test - b
    else
        jump_over_re =   (greedy ? '\s*[^\s]*'       \
                                 : space             \
                                   + '( (\s+)'       \
                                   +
                 (sub_identifier ? '| ([a-z]+[A-Z])' \
                                   '| (_?[a-zA-Z]+)' \
                                   '| ([A-Z]+)'      \
                                   '| ([\d]+)'       \
                                 : '| (\w+)'
                                )  + 
                                   '| ([^\s\w])'     \
                                   '| (.)'           \
                                   ')' )
    end
    sub = rev ? line[0..(buffer.x-1)].reverse : line[buffer.x..(line.length-1)]
    sub =~ /^(#{jump_over_re})/x
    return if $1.nil?
    len = $1.length
    len += (!rev ? -1 : 1) unless letter_after or rev
    x_diff = rev ? (-len) : len
    move_x buffer, x_diff, (buffer.x == (line.length - 1) || buffer.x == 0) ? true : false
end

#normalize_cursor_key(c) ⇒ Object



37
38
39
40
41
42
43
44
45
46
47
48
49
# File 'lib/front.rb', line 37

def normalize_cursor_key c
    case c
    when Curses::KEY_RIGHT
        return ?l
    when Curses::KEY_UP
        return ?k
    when Curses::KEY_DOWN
        return ?j
    when Curses::KEY_LEFT
        return ?h
    end
    return c
end

#notify_of_change(the_change, direction) ⇒ Object



629
630
631
# File 'lib/shikaku.rb', line 629

def notify_of_change the_change, direction
    @changed = true
end

#perform_search(buffer, forced_next) ⇒ Object



92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
# File 'lib/search.rb', line 92

def perform_search buffer, forced_next
    @search.already_performed = true
    re = nil
    begin
        re = @case_sensitive ? /(.*?)(#{@search.string})/ : /(.*?)(#{@search.string})/i
    rescue RegexpError
        status_bar_edit_line ":( - #{@search.string}"
        return
    end
    cur_x, cur_y = buffer.x, buffer.y
    last_match_y = buffer.y
    moved, matches, search_wrapped = false, false, false
    dbg(:search) { "started! with search == #{@search.string}" }
    while true
        matches, start_pos, end_pos = find_match_in_line re, buffer.lines[cur_y], cur_x, @search.reverse, (forced_next and cur_y == buffer.y)
        last_match_y = cur_y if matches
        dbg(:search) { "B: #{start_pos.inspect} - #{end_pos.inspect}" }
        if  matches && !moved && ( (!@search.reverse && start_pos > cur_x) \
                                 || (@search.reverse && cur_x > start_pos) )
            moved = true
            cur_x = start_pos
        end
        if search_wrapped and moved and !matches && (cur_y == last_match_y)
            status_bar_edit_line "sorry, unable to find #{@search.string}"
            break
        end
        forced_next_but_unmoved = forced_next && !moved
        if matches and !forced_next_but_unmoved
            status_bar_edit_line ":) - #{@search.string}"
            break
        end
        cur_y += (@search.reverse ? -1 : +1)
        cur_x = @search.reverse ? buffer.lines[cur_y].length : 0
        if cur_y > (buffer.lines.length-1) or cur_y < 0
            status_bar_edit_line "wrapped!"
            search_wrapped = true 
        end
        cur_y = cur_y % buffer.lines.length
        moved = true
    end
    @hl_selection = matches ? Selection.new(Point.new(start_pos, cur_y),
                                            Point.new(end_pos,   cur_y), 
                                            :selc_normal) \
                            : nil
    redraw buffer
end

#pop_paste_bufferObject



617
618
619
620
621
622
623
624
625
626
627
# File 'lib/shikaku.rb', line 617

def pop_paste_buffer
    choice = @current_paste_buffer
    @current_paste_buffer = nil
    buf = choice.nil? ? @main_paste_buffer : @paste_buffers[choice]
    if buf.nil?
        buf = EditorApp.new_buffer self, :no_blank_line, :fake_buffer
        buf.is_paste_buffer = true
        @paste_buffers[choice] = buf
    end
    buf
end

#position_cursor(buffer, docview) ⇒ Object



511
512
513
514
515
516
517
518
# File 'lib/shikaku.rb', line 511

def position_cursor buffer, docview
    current_line = buffer.lines[buffer.y]
    string_upto_now = current_line.slice(0...buffer.x)
    x = string_upto_now.unpack("c*").inject(0) { |width, char| width += (char == ?\t ? config_get_tab_size : 1) }
    ecm_diff = (@end_char_mode and @mode_stack.last == :insert and !current_line.empty?) ? 1 : 0
    docview.absolute_cursor.x, docview.absolute_cursor.y = buffer.x + ecm_diff, buffer.y - buffer.top
    docview.cursor.x, docview.cursor.y = x + ecm_diff, buffer.y - buffer.top
end

#real_buffersObject



288
289
290
291
292
293
294
# File 'lib/shikaku.rb', line 288

def real_buffers
    buffs = []
    each_real_buffer { 
       |b| buffs << b 
    }
    buffs
end

#redraw(buffer) ⇒ Object



532
533
534
535
536
# File 'lib/shikaku.rb', line 532

def redraw buffer
    @widgets.each { |sb| sb.invalidate if sb.is_a? StatusBar }
    buffer.redraw_list = [] # no point invalidating everything twice so lets clear the list!
    redraw_from_and_including buffer, 0
end

#redraw_from_and_including(buffer, y) ⇒ Object



525
526
527
528
529
530
# File 'lib/shikaku.rb', line 525

def redraw_from_and_including buffer, y
    buffer.invalidate_line_highlight y
    (y...screen_height).each { |y| 
        invalidate_screen_line buffer, y
    }
end

#refresh_widgetsObject



503
504
505
506
507
508
509
# File 'lib/shikaku.rb', line 503

def refresh_widgets
    @widgets.each { 
        |widget| 
        next if widget.kind_of? Box
        widget.canvas.refresh 
    }
end

#remove_unneeded_statusbarsObject



306
307
308
309
310
311
312
313
# File 'lib/shikaku.rb', line 306

def remove_unneeded_statusbars
    count_was = @widgets.length
    @widgets.delete_if {
       |sb|
       (sb.is_a? StatusBar) and !(@buffers.include? sb.buffer)
    }
    EditorApp.perform_layout if count_was != @widgets.length
end

#reset_stateObject

TODO this is hecka big, can’t we seperate out more stuff?



127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
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
# File 'lib/shikaku.rb', line 127

def reset_state
    @@app = self

    @needs_full_redraw = false

    BufferIdAllocator::instance.max_buffer_idx = 1

    BufferListing::instance.clear

    @mutex = Mutex.new

    @current_paste_buffer = nil

    @doing_macro = nil
    @macros = []

    @change_listeners = []
    @change_listeners << self.method(:notify_of_change)

    @changed = false

    @mode_stack = [:normal]
    
    Curses.int_init

    @curses_key_translators = {}

    @current_command_binding_state = nil
    @current_insert_binding_state = nil
    @stored_command_bindings = {}
    @stored_insert_bindings = {}
    
    setup_bindings
    
    @search_history = []
    
    @buffers = []
    @current_buffer = nil
    
    @number = nil
    
    @end_char_mode = false
    @replace_mode  = false
    @replace_single_letter = false
    @search        = nil

    @da_command    = ""
    
    @status_bar = nil
    
    @selection = nil
    @hl_selection = nil
    
    @command, @cmd_line = "", ""
    
    @paste_buffers = {}
    @main_paste_buffer = EditorApp.new_buffer self, :no_blank_line, :fake_buffer
    @main_paste_buffer.is_paste_buffer = true
    @history_buffer = EditorApp.new_buffer self, :no_blank_line, :fake_buffer
    @debug_buffer   = EditorApp.new_buffer self, :no_blank_line, :fake_buffer
    Debug::instance.register_debug_buffer @debug_buffer
    dbg(nil) { "--- beginning debug output #{Time.now.to_s} ---" } unless $test_case

  # root

    @root           = VBox.new self, nil

  # window 1

    @doc_with_ruler = HBox.new self, @root

    @docview        = DocView.new self, nil
    @docview.parent = @doc_with_ruler

    @ruler          = Ruler.new self, nil
    @ruler.parent   = @doc_with_ruler

  # window 2

    @doc_with_ruler2 = HBox.new self, @root

    @docview2        = DocView.new self, nil
    @docview2.parent = @doc_with_ruler2

    @ruler2          = Ruler.new self, nil
    @ruler2.parent   = @doc_with_ruler2

    @docview_to_ruler = {
        @docview  => @ruler,
        @docview2 => @ruler2,
    }

    @current_docview = @docview

    @focus = @docview

    @widgets =  [@root, @doc_with_ruler, @docview]
    
    # DEFAULTS
    @settings = Settings.new # maybe arguments should be registered with types?????, or use a callback system to verify typing???
    @settings[:sw] = "4"
    @settings[:ts] = "8"
    @settings[:tw] = nil
    @settings[:nu] = "false"
    @settings[:split] = "false"
    @settings[:autopair] = "false"

    @settings.procs[:nu] = proc {
        update_split_state
        update_ruler_state
    }

    update_split_state
    update_ruler_state

    @settings.procs[:split] = proc {
        update_split_state
        update_ruler_state
    }

    setup_default_command_set
end

#save_buffer_as_file(buffer, fname = nil) ⇒ Object



386
387
388
389
390
391
392
393
# File 'lib/shikaku.rb', line 386

def save_buffer_as_file buffer, fname = nil
    fname = buffer.fname if fname.nil?
    raise "save_buffer_as_file called for buffer without a valid fname" if fname.nil?
    File.open(fname, "w") { |fp|
        buffer.dlog.saved
        buffer.lines.each { |line| fp.puts line }
    }
end

#screen_heightObject



726
727
728
729
730
# File 'lib/shikaku.rb', line 726

def screen_height
    # when using the actual screen height the last char drawn on the last line 
    # appears to scroll the screen up one thus completely messing up the rendering
    WinDescs::instance.descs[@docview].sy - 1
end

#scroll_down(buffer) ⇒ Object

test - scroll up and down in large buffer, needs visual test really



24
25
26
27
28
29
30
31
32
33
34
35
# File 'lib/movement.rb', line 24

def scroll_down buffer
    b = (buffer.top + screen_height <= buffer.lines.length)
    if b
        buffer.need_to_scroll = 1 unless buffer.needs_redraw 
        invalidate_screen_line buffer, screen_height - 1 unless buffer.needs_redraw
        buffer.y += 1
        buffer.top += 1
        buffer.needs_redraw = true if buffer.got_a_scroll_already
        buffer.got_a_scroll_already = true
    end
    return b
end

#scroll_to_bottom(buffer) ⇒ Object



131
132
133
134
135
# File 'lib/movement.rb', line 131

def scroll_to_bottom buffer
    while scroll_down(buffer); end
    buffer.y = buffer.last_line_num
    redraw buffer
end

#scroll_to_top(buffer) ⇒ Object



137
138
139
140
141
# File 'lib/movement.rb', line 137

def scroll_to_top buffer
    while scroll_up(buffer); end
    buffer.y = 0
    redraw buffer
end

#scroll_up(buffer) ⇒ Object

test - scroll up and down in large buffer, needs visual test really



10
11
12
13
14
15
16
17
18
19
20
21
# File 'lib/movement.rb', line 10

def scroll_up buffer
    b = (buffer.top > 0)
    if b
        buffer.need_to_scroll = -1 unless buffer.needs_redraw 
        invalidate_screen_line buffer, 0 unless buffer.needs_redraw
        buffer.y -= 1
        buffer.top -= 1
        buffer.needs_redraw = true if buffer.got_a_scroll_already
        buffer.got_a_scroll_already = true
    end
    return b
end

#send_key(c) ⇒ Object



472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
# File 'lib/front.rb', line 472

def send_key c
    dbg(nil) { "key press: #{(c == 27) ? "<esc>" : (c > 256 ? Curses.keyname(c) : c.chr)}" }
    $action_replay_log.keys_pressed << c unless $replaying
    update_focus
    @doing_macro << c.chr unless @doing_macro.nil?
    case @mode_stack.last
    when :search
        do_search @current_buffer, c
    when :normal
        do_command @current_buffer, c
    when :get_letter
        @mode_stack.pop
        @get_letter_pop_proc.call c
    when :command
        do_cmd_mode(c, @cmd_line) {
           |ok|
           cmd_execute @current_buffer, @cmd_line if ok
           @mode_stack = [:normal]
           @cmd_line = ""
        }
    when :insert
        do_letter @current_buffer, c
    end
    update_focus
end

#setup_bindingsObject



5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
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
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
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
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
# File 'lib/bindings.rb', line 5

def setup_bindings

    look_for_letter_block = proc {
        |c|
        buffer = self.current_buffer
        @mode_stack.push :get_letter
        @get_letter_pop_proc = proc {
            |to_find|
            has_action = !@command.empty?
            selc_options = []
            selc_options << :restore if has_action
            selc = create_selection(buffer, :selc_normal, *selc_options) { 
                upcase = (c.chr == c.chr.upcase)
                letter_before = (c.chr.downcase == "t")
                diff = letter_before ? 1 : 0
                line = buffer.lines[buffer.y]
                if upcase
                    # backwards search...
                    if buffer.x != 0
                        index = line.rindex(to_find, buffer.x-1)
                        buffer.move_to_x(index + diff) unless index.nil?
                    end
                else
                    # forwards search
                    if buffer.x != line.length - 1
                        index = line.index(to_find, buffer.x+1)
                        buffer.move_to_x(index - diff) unless index.nil?
                    end
                end
            }
            do_action buffer, selc
        }
    }

    goto_first_real_letter_on_line_block = proc {
        |c|
        buffer = self.current_buffer
        buffer.move_to_x get_indent_level(buffer.lines[buffer.y])
    }

    skip_block_block = proc {
        |c|
        buffer = self.current_buffer
        backwards = (c == ?{)
        oy = buffer.y
        while true
            move_y buffer, backwards ? -1 : +1
            break if buffer.lines[buffer.y] =~ /^\s*$/ \
                  or    (buffer.y != oy \
                    and ([buffer.last_line_num, 0].include? buffer.y))
        end
        if buffer.y == buffer.last_line_num and !backwards
           buffer.move_to_x buffer.last_char_on_line(buffer.last_line_num)
        else
           # used in the { at start of file case, and the default case
           buffer.move_to_x 0
        end
    }

=begin
:: attribution - vim help - motion.txt
Special case: "cw" and "cW" are treated like "ce" and "cE" if the cursor is
on a non-blank.  This is because "cw" is interpreted as change-word, and a
word does not include the following white space.  {Vi: "cw" when on a blank
followed by other blanks changes only the first blank; this is probably a
bug, because "dw" deletes all the blanks}
=end
    word_movement_block = proc {
        |c, context|
        options = [ ]
        case context.input
        when "zh"
            options << :sub_identifier
            c = ?b
        when "zl"
            options << :sub_identifier
            c = ?w
        end
        buffer = self.current_buffer
        cmd = c.chr.downcase
        if_e = (cmd == "e")
        if_b = (cmd == "b")
        is_upcase = (c.chr.upcase == c.chr)
        has_action = !@command.empty?
        on_blank = (buffer.lines[buffer.y][buffer.x] =~ /\s/)
        letterafter = (cmd == "w")
        letterafter = false if (cmd == "w" and @command == "c" and !on_blank)
        options << :letter_after if letterafter
        options << :skip_space   if letterafter or (if_b and !has_action)
        options << :reverse      if if_b
        options << :greedy       if is_upcase
        ox, oy = buffer.x, buffer.y
        selc = create_selection(buffer, :selc_normal) { 
            next_word(buffer, *options)
        }
        if has_action
            selc = selc.right_way_up
            make_selection_exclusive_given_current_position(buffer, selc) if (letterafter or (@command == "c" and context.input == "zl"))
        end
        buffer.x, buffer.y = ox, oy if has_action
        should_make_exclusive = (if_b and has_action)
        make_selection_exclusive_given_current_position(buffer, selc) if should_make_exclusive
        do_action buffer, selc
    }

    goto_top_of_screen_block = proc {
        |c|
        buffer = self.current_buffer
        buffer.move_to_y buffer.top
    }

    goto_end_of_screen_block = proc {
        |c|
        buffer = self.current_buffer
        y = buffer.top + screen_height - 1
        y = [y, buffer.lines.length - 1].min
        buffer.move_to_y y
    }

    goto_mid_screen_line_block = proc {
        |c|
        buffer = self.current_buffer
        y = buffer.top + ([screen_height, buffer.last_line_num].min / 2)
        buffer.move_to_y y
    }

    vi_g_block = proc {
        |c|
        buffer = self.current_buffer
        @command << c.chr
        if @command == "gg"
            if @number =~ /[0-9]+/
                buffer.move_to_y(@number.to_i - 1)
                redraw buffer
                @number = nil
            else
                scroll_to_top buffer
            end
            @command = ""
        end
    }

    vi_cap_g_block = proc {
        |c|
        buffer = self.current_buffer
        if @number =~ /[0-9]+/
            buffer.move_to_y(@number.to_i - 1)
            redraw buffer
            @number = nil
        else
            selc = create_selection(buffer, :selc_lined) { 
                scroll_to_bottom buffer 
            }
            do_action buffer, selc
        end
    }

    next_prev_search_block = proc {
        |c|
        buffer = self.current_buffer
        @search = SearchContext.new
        @search.history_pos = 0
        @search.reverse = (c.chr.upcase == c.chr)
        do_search(buffer, @search.reverse ? Curses::KEY_CTRL_P : Curses::KEY_CTRL_N)
        if !hl_selection.nil?
            buffer.move_to @hl_selection.s.x, @hl_selection.s.y
        end
        @search = nil
    }

    find_next_word_block = proc {
        |c|
        buffer = self.current_buffer
        @search = SearchContext.new
        @search.history_pos = 0
        @search.reverse = (c == ?#)
        word_range = get_word_under_cursor buffer
        word = buffer.lines[buffer.y].slice(word_range)
        status_bar_edit_line "* matched: #{word}"
        @search.already_performed = false
        do_search(buffer, Curses::KEY_CTRL_N, word) # see above
        buffer.move_to @hl_selection.s.x, @hl_selection.s.y
        @search = nil
    }

    match_paren_block = proc {
        |c|
        buffer = self.current_buffer
        # make movements work, e.g d% -> delete between ()s
        buffer.ensure_line_highlight buffer.y
        row = buffer.tokens[buffer.y]
        token = row.find { 
                    |tok| 
                    (tok == row.last) || (row[row.index(tok) + 1].x > buffer.x)
                }
        matches_hash = buffer.highlighter.matches_hash
        inv_matches_hash = {}
        matches_hash.values.sort.uniq.each {
            |val| 
            inv_matches_hash[val] = matches_hash.collect { 
                                |dom, rng| 
                                rng == val ? dom : nil
                            }.compact
        }
        looking_backwards = matches_hash.values.include? token.str
        matches_hash_values, matches_hash_keys = matches_hash.values, matches_hash.keys
        we_want_a = looking_backwards ? inv_matches_hash[token.str] : [matches_hash[token.str]]
        level = 0
        val = looking_backwards ? -1 : 1
        token_itr(buffer, token, buffer.y, !looking_backwards) {
            |y, tok|
            level += val if matches_hash_values.include? tok.str
            if we_want_a.include?(tok.str) and level == 0
                buffer.move_to tok.x, y
                break
            end
            level -= val if matches_hash_keys.include? tok.str
        }
    }

    insert_mode_cursor_block = proc {
        |c|
        buffer = self.current_buffer
         = [?j, ?k].include?(c)
        selc = create_selection(buffer,  ? :selc_lined : :selc_normal) { 
            do_movement_key buffer, c 
        }
        make_selection_exclusive_given_current_position(buffer, selc) unless 
        do_action buffer, selc
    }

    end_block = proc {
        |c|
        buffer = self.current_buffer
        has_action = !@command.empty?
        selc = create_selection(buffer, :selc_normal) { 
            buffer.move_to_x buffer.last_char_on_line(buffer.y)
            @end_char_mode = true unless has_action
        }
        make_selection_exclusive_given_current_position(buffer, selc) unless has_action
        do_action buffer, selc
    }

    home_block = proc {
        |c|
        buffer = self.current_buffer
        buffer.move_to_x 0
    }

    selection_block = proc {
        |c|
        buffer = self.current_buffer
        if !@selection.nil?
            @selection = nil
        else
            @selection = Selection.new
            @selection.mode = (c == ?\C-v) ? :selc_boxed : ((c == ?v) ? :selc_normal : :selc_lined)
            if    @selection.mode == :selc_normal || @selection.mode == :selc_boxed
                @selection.s = Point.new(buffer.x, buffer.y)
            elsif @selection.mode == :selc_lined
                @selection.s = Point.new(0, buffer.y)
            end
        end
    }

    search_block = proc {
        |c|
        buffer = self.current_buffer
        @mode_stack.push :search
        @search = SearchContext.new
        @search.history_pos = 0
        @search.already_performed = false
        @search.reverse = (c == ??)
        @search.got_no_key = true
        status_bar_edit_line "enter search term: "
    }

    join_line_block = proc {
        |c|
        buffer = self.current_buffer
        if buffer.y == buffer.last_line_num
           # beep :)
           # fixme - needs test
        else
        get_indent_level(buffer.lines[buffer.y])
        DiffLogger::ModifyLineChange.new(buffer, buffer.y + 1) {
           line = buffer.lines[buffer.y + 1]
           line.slice! 0, get_indent_level(line)
           line[0,0] = " "
        }
        EditorApp.join_next_line_onto_this buffer, buffer.y
        end
    }

    macro_block = proc {
        |c|
        buffer = self.current_buffer
        if @doing_macro.nil?
            @mode_stack.push :get_letter
            @get_letter_pop_proc = proc {
                |c|
                @doing_macro = ""
                @macro_letter = c
            }
        else
            raise "er. how the fuck else did u exit macro mode thingy???" if @doing_macro[-1] != ?q
            @doing_macro.slice!(-1)
            @macros[@macro_letter] = @doing_macro
            @doing_macro = nil
        end
    }

    play_macro_block = proc {
        |c|
        buffer = self.current_buffer
        @mode_stack.push :get_letter
        @get_letter_pop_proc = proc {
            |c|
            @macros[c].each_byte { |c| send_key c }
        }
    }

    repeat_previous_command_block = proc {
        |c|
        buffer = self.current_buffer
        if !@last_command.nil?
            status_bar_edit_line "executing #{@last_command}"
            was_nil_selection = @selection.nil?
            @selection = @selection_for_last_command if was_nil_selection
            cmd = @last_command
            cmd.each_byte { |key| send_key key }
            @last_command = cmd
            @selection = nil if was_nil_selection
        end
    }

    select_paste_buffer_block = proc {
        @mode_stack.push :get_letter
        @get_letter_pop_proc = proc {
            |c|
            @current_paste_buffer = c.chr
        }
    }

    undo_block = proc {
        |c|
        buffer = self.current_buffer
        buffer.dlog.undo
        redraw buffer
    }

    insert_block = proc {
        |c|
        buffer = self.current_buffer
        line = buffer.lines[buffer.y]
        buffer.move_to_x get_indent_level(line) if (c.chr == c.chr.upcase)
        @end_char_mode = true if line.empty?
        begin_insert_mode buffer
    }

    replace_block = proc {
        |c|
        buffer = self.current_buffer
        begin_insert_mode buffer
        @replace_mode = true
    }

    replace_letter_block = proc {
        |c|
        buffer = self.current_buffer
        begin_insert_mode buffer
        @replace_mode = true
        @replace_single_letter = true
    }

    enter_command_mode_block = proc {
        |c|
        buffer = self.current_buffer
        begin_insert_mode buffer
        @mode_stack = [:command]
        status_bar_edit_line ":"
    }

    vi_y_block = proc {
        |c|
        buffer = self.current_buffer
        handle_generic_command buffer, c
    }

    debug_buffer_block = proc {
        |c|
        buffer = self.current_buffer
        switch_to_buffer @debug_buffer
    }

    goto_classstack_or_buffer_block = proc {
        |c|
        buffer = self.current_buffer
        # buffer specific... some kind of subclass or so?
        line = buffer.lines[buffer.y]
        idx_s = nil
        if    line =~ /^\[:b([0-9]+?)\]/
            idx = $1.to_i # due to to_i sucking we need to never have 1 numbered buffers
            switch_to_buffer @buffers.detect { |b| b.bnum == idx }
            BufferListExtension.clear_extension_buffers self
        elsif line =~  /^\[:([0-9]+?)\]/
            line_no = $1.to_i
            tbuffer = buffer.old_buf
            tbuffer.x      = 0
            tbuffer.y      = line_no
            unclamped_top  = line_no - (screen_height / 2)
            tbuffer.top    = unclamped_top.clamp_to 0, tbuffer.last_line_num
            switch_to_buffer buffer.old_buf
            HierarchyListExtension.clear_extension_buffers self
        end
    }

    show_classstack_block = proc {
        |c|
        buffer = self.current_buffer
        ensure_buffer_highlighted buffer
        class_list = []
        done = []
        buffer.classstacks.each_with_index {
            |stack, idx| 
            next if stack.empty?
            str = stack.join "::"
            unless done.include? str
                done << str
                class_list << Struct.new(:y, :s).new(idx, str)
            end
        }
        cur_class_y = 0
        b = HierarchyListExtension.create_buffer self
        class_list.each_with_index {
            |c, idx| 
            b.lines << BufferLine.new("[:#{c.y}] #{c.s}")
            next_one = class_list.at(idx + 1)
            if (cur_class_y == 0) and (next_one.nil? \
                                    || next_one.y > buffer.y)
                cur_class_y = idx
            end
        }
        b.y   = cur_class_y
        b.top = b.y
        switch_to_buffer b
    }

    show_buffer_list_block = proc {
        |c|
        buffer = self.current_buffer
        b = BufferListExtension.create_buffer self
        @buffers.each {
            |buffer|
            visible_idx = buffer.fake_buffer ? " " : buffer.bnum
            b.lines << BufferLine.new("[:b#{visible_idx}] - #{buffer.fname} (length: #{buffer.lines.length}) (x:#{buffer.x}, y:#{buffer.y})")
        }
        switch_to_buffer b
    }

    previous_buffer_block = proc {
        |c|
        buffer = self.current_buffer
        switch_to_buffer
    }

    other_buffer_block = proc {
        |c|
        @current_docview = @docview2
    }

    redraw_block = proc {
        |c|
        buffer = self.current_buffer
        redraw buffer
    }

    redo_block = proc {
        |c|
        buffer = self.current_buffer
        buffer.dlog.redo
        redraw buffer
    }

    paste_buffer_block = proc {
        |c|
        buffer = self.current_buffer
        myy = buffer.y
        after = !(c.chr.upcase == c.chr)
        done_something = false            
        diff = 0
        paste_buffer = pop_paste_buffer
        paste_buffer.lines.each_with_index {
            |lbuffer, idx| 
            if after and !done_something
                if lbuffer.newline_at_start
                    myy += 1 
                    diff = +1
                else # !newline_at_start
                    buffer.x += 1 if buffer.x < buffer.lines[myy].length 
                end
            end
            if lbuffer.newline_at_start and lbuffer.newline_at_end
                # add totally new line
                DiffLogger::InsertLineAfterChange.new(buffer, myy) {
                    buffer.lines.insert_after myy, lbuffer.dup
                }
            elsif !lbuffer.newline_at_start and !lbuffer.newline_at_end 
                # add in place
                DiffLogger::ModifyLineChange.new(buffer, myy) {
                    buffer.lines[myy][buffer.x, 0] = lbuffer.dup
                }
            elsif lbuffer.newline_at_start and !lbuffer.newline_at_end 
                # append to the start of the next line - equiv to new line, then join
                DiffLogger::ModifyLineChange.new(buffer, myy+1) {
                    buffer.lines[myy+1][0, 0] = lbuffer.dup
                }
            elsif !lbuffer.newline_at_start and lbuffer.newline_at_end 
                # add in place and split rest of line onto next line
                rest_of_line  = nil
                DiffLogger::ModifyLineChange.new(buffer, myy) {
                    rest_of_line = buffer.lines[myy].slice!(buffer.x..-1)
                }
                DiffLogger::ModifyLineChange.new(buffer, myy) {
                    buffer.lines[myy][buffer.x, 0] = lbuffer.dup
                }
                DiffLogger::InsertLineAfterChange.new(buffer, myy) {
                    buffer.lines.insert_after myy, rest_of_line
                }
            end
            done_something = true
            myy += 1
        }
        buffer.y += diff
        redraw buffer
    }

    generic_vi_c_d_block = proc {
        |c|
        buffer = self.current_buffer
        handle_generic_command buffer, c
    }

    modify_indent_block = proc {
        |c|
        buffer = self.current_buffer
        @command << c.chr
        got_selection = !@selection.nil?
        selc = nil
        if got_selection
            selc = @selection.right_way_up
        elsif @command == c.chr*2
            selc = Selection.new Point.new(0, buffer.y), Point.new(buffer.lines[buffer.y].length, buffer.y), :selc_lined
            @command = ""
        elsif @command.length > 2
            @command = ""
        end
        if !selc.nil?
            tab = (" " * config_get_sw)
            backwards = (c == ?<)
            EditorApp.each_lineslice_in_selection(buffer, selc) {
                |hlls| 
                DiffLogger::ModifyLineChange.new(buffer, hlls.y) {
                    line = buffer.lines[hlls.y]
                    if backwards
                        line.slice! 0, [get_indent_level(line), config_get_sw].min
                    else
                        line[0, 0] = tab
                    end
                }
                EditorApp.invalidate_buffer_line buffer, hlls.y
            }
        end
    }

    reindent_block = proc {
        |c|
        buffer = self.current_buffer
        @command << c.chr
        got_selection = !@selection.nil?
        selc = nil
        if got_selection
            @command = ""
            selc = @selection.right_way_up
        elsif @command == c.chr*2
            selc = Selection.new Point.new(0, buffer.y), Point.new(buffer.lines[buffer.y].length, buffer.y), :selc_lined
            @command = ""
        end
        EditorApp.each_lineslice_in_selection(buffer, selc) {
            |hlls| 
            y = hlls.y
            next if y == 0
            current_line = buffer.lines[y-1]
            indent_string = calculate_autoindent buffer, current_line, y-1, true
            DiffLogger::ModifyLineChange.new(buffer, y) {
                line = buffer.lines[y]
                line.slice! 0, get_indent_level(buffer.lines[y])
                line[0, 0] = indent_string
            }
            EditorApp.invalidate_buffer_line buffer, y
        }
    }

    vi_insert_after_block = proc {
        |c|
        buffer = self.current_buffer
        upcase = (c.chr.upcase == c.chr)
        cur_line_len = buffer.lines[buffer.y].length
        buffer.move_to_x(upcase ? buffer.last_char_on_line(buffer.y) : [buffer.x + 1, cur_line_len].min)
        @end_char_mode = true if buffer.x == buffer.last_char_on_line(buffer.y)
        begin_insert_mode buffer
    }

    vi_insert_new_line_block = proc {
        |c|
        buffer = self.current_buffer
        upcase = (c.chr.upcase == c.chr)
        insert_line = upcase ? buffer.y : buffer.y + 1
        indent_string = calculate_autoindent buffer, "", insert_line - 1, false
        line = BufferLine.new(indent_string)
        DiffLogger::InsertLineAfterChange.new(buffer, insert_line) {
            buffer.lines.insert_after insert_line, line
        }
        buffer.move_to(indent_string.length, insert_line)
        @end_char_mode = true
        begin_insert_mode buffer
        redraw buffer
    }

    vi_escape_mode_block = proc {
        |c|
        buffer = self.current_buffer
        @selection = nil
        @command = ""
    }

    change_line_block = proc {
        |c|
        buffer = self.current_buffer
        buffer.move_to_x 0
        DiffLogger::ModifyLineChange.new(buffer, buffer.y) {
            buffer.lines[buffer.y].replace BufferLine.new("")
        }
        EditorApp.invalidate_buffer_line buffer, buffer.y
        @end_char_mode = true
        begin_insert_mode buffer
    }

    edit_or_delete_till_end_block = proc {
        |c|
        buffer = self.current_buffer
        cmd = c.chr.downcase
        is_c = (cmd == "c")
        selc = Selection.new Point.new(buffer.x, buffer.y), Point.new(buffer.last_char_on_line(buffer.y), buffer.y), :selc_normal
        EditorApp.manip_selection buffer, selc, :manip_cut, pop_paste_buffer
        @end_char_mode = true
        EditorApp.invalidate_buffer_line buffer, buffer.y
        begin_insert_mode(buffer) if is_c
    }

    change_letter_block = proc {
        |c|
        buffer = self.current_buffer
        selc = Selection.new Point.new(buffer.x, buffer.y), Point.new(buffer.x, buffer.y), :selc_normal
        EditorApp.manip_selection buffer, selc, :manip_cut, pop_paste_buffer
        if buffer.lines[buffer.y].empty?
            @end_char_mode = true
        else
        buffer.x -= 1 if buffer.x >= buffer.lines[buffer.y].length
        end
        EditorApp.invalidate_buffer_line buffer, buffer.y
        begin_insert_mode buffer
    }

    vi_delete_char_block = proc {
        |c|
        buffer = self.current_buffer
        got_selection = !@selection.nil?
        if got_selection
            EditorApp.manip_selection buffer, @selection, :manip_cut, pop_paste_buffer
            end_selection buffer
        elsif buffer.lines[buffer.y].length > 0
            upcase = (c.chr.upcase == c.chr)
            buffer.x -= 1 if upcase
            selc = Selection.new Point.new(buffer.x, buffer.y), Point.new(buffer.x, buffer.y), :selc_normal
            EditorApp.manip_selection buffer, selc, :manip_cut, pop_paste_buffer
            buffer.x -= 1 if buffer.x >= buffer.lines[buffer.y].length
            EditorApp.invalidate_buffer_line buffer, buffer.y
        end
    }

    scroll_up_line_no_cursor_move_block = proc {
        |c|
        buffer = self.current_buffer
        scroll_up(buffer)
    }

    scroll_down_line_no_cursor_move_block = proc {
        |c|
        buffer = self.current_buffer
        scroll_down(buffer)
    }

    page_down_move_cursor_half_page_block = proc {
        |c|
        buffer = self.current_buffer
        (screen_height / 2).times {
            break if not scroll_down(buffer)
        }
        redraw buffer
    }

    page_down_move_cursor_block = proc {
        |c|
        buffer = self.current_buffer
        screen_height.times {
            break if not scroll_down(buffer)
        }
        redraw buffer
    }

    page_up_move_cursor_half_page_block = proc {
        |c|
        buffer = self.current_buffer
        (screen_height / 2).times {
            break if not scroll_up(buffer)
        }
        redraw buffer
    }

    page_up_move_down_cursor_block = proc {
        |c|
        buffer = self.current_buffer
        screen_height.times {
            break if not scroll_up(buffer)
        }
        redraw buffer
    }

    insert_escape_block = proc {
        buffer = self.current_buffer
        begin_normal_mode buffer
        buffer.dlog.flush
        @end_char_mode = false
        @replace_mode = false
        @replace_single_letter = false
    }

    insert_backspace_block = proc {
        buffer = self.current_buffer
        if buffer.x == 0 and buffer.y == 0
            # ignore backspace on first char in buffer
        elsif buffer.x == 0 and (!@end_char_mode or  \
                                 (@end_char_mode and buffer.lines[buffer.y].empty?))
            # join current line onto previous line and move to old end of last line
            line_to_join = nil
            DiffLogger::RemoveLineChange.new(buffer, buffer.y) {
                line_to_join = buffer.lines.delete_at(buffer.y)
            }
            buffer.y -= 1
            DiffLogger::ModifyLineChange.new(buffer, buffer.y) {
                join_to = buffer.lines[buffer.y]
                if line_to_join.length == 0
                    buffer.move_to_x join_to.length - 1
                    @end_char_mode = true
                else
                    buffer.move_to_x join_to.length
                end
                join_to << line_to_join
                redraw_from_and_including buffer, buffer.y
            }
        else
            raise "out_of_bounds error!" if buffer.out_of_bounds buffer.y, buffer.x
            current_line = buffer.lines[buffer.y]
            current_indent = get_indent_level current_line
            bx = buffer.ax
            len = (current_indent == bx ? config_get_sw : 1)
            len = len.clamp_to 0, bx
            DiffLogger::ModifyLineChange.new(buffer, buffer.y) {
                buffer.lines[buffer.y].slice! bx - len, len
            }
            ecm = @end_char_mode
            buffer.move_to_x(limit_to_positive(buffer.x - len))
            @end_char_mode = ecm
            EditorApp.invalidate_buffer_line buffer, buffer.y
        end
    }

    insert_cr_key_block = proc {
        buffer = self.current_buffer
        current_line = buffer.lines[buffer.y]
        indent_string = calculate_autoindent buffer, current_line, buffer.y, true
        should_fixup_end_pair = (current_line[buffer.x - 1,2] == "{}") and (@settings[:autopair] == "true")
        if buffer.ax == current_line.length
            line = BufferLine.new(indent_string)
        else
            # split the rest of the current line onto the next line
            DiffLogger::ModifyLineChange.new(buffer, buffer.y) {
                line = BufferLine.new(indent_string + current_line.slice!(buffer.x, current_line.length)).dup
            }
        end
        DiffLogger::InsertLineAfterChange.new(buffer, buffer.y+1) {
            buffer.lines.insert_after buffer.y+1, line 
        }
        if should_fixup_end_pair 
            indent_string = calculate_autoindent buffer, buffer.lines[buffer.y], buffer.y, false
            DiffLogger::InsertLineAfterChange.new(buffer, buffer.y+1) {
                buffer.lines.insert_after buffer.y+1, indent_string
            }
        end
        buffer.y += 1
        if indent_string.length > buffer.last_char_on_line(buffer.y)
            buffer.move_to_x indent_string.length - 1
            @end_char_mode = true
        else
            buffer.move_to_x indent_string.length
        end
        if buffer.lines[buffer.y].empty?
            buffer.move_to_x 0
            @end_char_mode = true
        end
        redraw buffer
    }

    insert_end_key_block = proc {
        buffer = self.current_buffer
        buffer.move_to_x buffer.last_char_on_line(buffer.y)
        @end_char_mode = true
    }

    insert_home_key_block = proc {
        buffer = self.current_buffer
        buffer.move_to_x 0
    }

    insert_movement_block = proc {
        |c|
        buffer = self.current_buffer
        do_movement_key buffer, c, :insert_mode
    }

    # INSERT BINDINGS
    add_insert_binding("\e", &insert_escape_block)
    add_insert_binding("\b", &insert_backspace_block)
    add_insert_binding("\r", &insert_cr_key_block)

    add_curses_key_translators(Curses::KEY_END  => "\C-x\C-h$", Curses::KEY_HOME  => "\C-x\C-h0")
    add_insert_binding("\C-x\C-h$", &insert_end_key_block)
    add_insert_binding("\C-x\C-h0", &insert_home_key_block)

    add_curses_key_translators(Curses::KEY_UP   => "\C-x\C-hk", Curses::KEY_DOWN  => "\C-x\C-hj", 
                               Curses::KEY_LEFT => "\C-x\C-hh", Curses::KEY_RIGHT => "\C-x\C-hl")
    add_insert_binding("\C-x\C-hh", "\C-x\C-hj", "\C-x\C-hk", "\C-x\C-hl", &insert_movement_block)

    # COMMAND BINDINGS
    add_command_binding("t", "T", "f", "F", &look_for_letter_block)
    add_command_binding("^", &goto_first_real_letter_on_line_block)
    add_command_binding("}", "{", &skip_block_block)
    add_command_binding("w", "W", "e", "E", "b", "B", "zh", "zl", &word_movement_block)
    add_command_binding("H", &goto_top_of_screen_block)
    add_command_binding("L", &goto_end_of_screen_block)
    add_command_binding("M", &goto_mid_screen_line_block)
    add_command_binding("g", &vi_g_block)
    add_command_binding("G", &vi_cap_g_block)
    add_command_binding("n", "N", &next_prev_search_block)
    add_command_binding("*", "#", &find_next_word_block)
    add_command_binding("%", &match_paren_block)
    add_command_binding("k", "j", "h", "l", "\C-x\C-hh", "\C-x\C-hj", "\C-x\C-hk", "\C-x\C-hl", &insert_mode_cursor_block)
    add_command_binding("$", "\C-x\C-h$", &end_block)
    add_command_binding("\C-x\C-h0", &home_block)
    add_command_binding("v", "V", "\C-V", &selection_block)
    add_command_binding("/", "?", &search_block)
    add_command_binding("J", &join_line_block )
    add_command_binding("q", &macro_block)
    add_command_binding("@", &play_macro_block)
    add_command_binding(".", &repeat_previous_command_block)
    add_command_binding("i", "I", &insert_block)
    add_command_binding("u", &undo_block)
    add_command_binding("\"", &select_paste_buffer_block)
    add_curses_key_translators(Curses::KEY_PPAGE => "\C-b", Curses::KEY_NPAGE => "\C-f")
    add_command_binding("\C-b", &page_up_move_down_cursor_block)
    add_command_binding("\C-f", &page_down_move_cursor_block)
    add_command_binding("\C-e", &scroll_down_line_no_cursor_move_block)
    add_command_binding("s", &change_letter_block)
    add_command_binding("D", "C", &edit_or_delete_till_end_block)
    add_command_binding("S", &change_line_block)
    add_command_binding("\e", &vi_escape_mode_block)
    add_command_binding("o", "O", &vi_insert_new_line_block)
    add_command_binding("a", "A", &vi_insert_after_block)
    add_command_binding("=", &reindent_block)
    add_command_binding("<", ">", &modify_indent_block)
    add_command_binding("c", "d", &generic_vi_c_d_block)
    add_command_binding("\C-r", &redo_block)
    add_command_binding("\C-l", &redraw_block)
    add_command_binding("\C-w\C-o", &previous_buffer_block)
    add_command_binding("\C-w\C-w", &other_buffer_block)
    add_command_binding("\C-s", &show_buffer_list_block)
    add_command_binding("\C-x\C-c", &show_classstack_block)
    # TODO - following is a "bit" of a hack...
    add_command_binding("\r", &goto_classstack_or_buffer_block)
    add_command_binding("\C-x\C-d", &debug_buffer_block)
    add_command_binding("\C-d", &page_up_move_cursor_half_page_block)
    add_command_binding("\C-u", &page_down_move_cursor_half_page_block)
    add_command_binding("y", &vi_y_block)
    add_command_binding("R", &replace_block)
    add_command_binding("r", &replace_letter_block)
    add_command_binding(":", &enter_command_mode_block)
    add_command_binding("p", "P", &paste_buffer_block)
    add_command_binding("x", "X", &vi_delete_char_block)
    add_command_binding("\C-y", &scroll_up_line_no_cursor_move_block)

end

#setup_cmd(cmd_string, re, &block) ⇒ Object



309
310
311
312
313
314
315
316
317
318
319
320
321
322
# File 'lib/front.rb', line 309

def setup_cmd cmd_string, re, &block
    possibles = []
    cmd_string.split(",").each {
        |word| 
        word << "'" if word.index('').nil?
        possibles += [word.sub(/'.*$/,""), word.sub(/'/, "")]
    }
    # @commands should be used in a shortest first fashion ... TODO
    possibles.sort.uniq.each {
        |possible|
        @commands[possible] = block
    }
    @cmds[re] = block
end

#setup_cmd_override(re, &block) ⇒ Object



324
325
326
# File 'lib/front.rb', line 324

def setup_cmd_override re, &block
    @cmd_overrides[re] = block
end

#setup_default_command_setObject



5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
# File 'lib/commands.rb', line 5

def setup_default_command_set

    @cmd_overrides = {}
    @commands = {}
    @cmds = {}

    setup_cmd("set", /^set\s+(.*?)=(.*)/) {
        |ctx|
        ctx.cmd_line =~ ctx.re
        key, val = $1, $2
        @settings[key.to_sym] = val
    }

    setup_cmd("sp'lit", /^sp(lit)?/) {
        |ctx|
        buffer_copy = ctx.buffer.make_copy
        @docview_to_ruler[@docview2].buffer = buffer_copy
                           @docview2.buffer = buffer_copy
        @current_docview = @docview
        @settings[:split] = "true"
        status_bar_edit_line "you have to be insane to use this. split is totally messed up atm"
    }

    setup_cmd("q!", /^q!$/) {
        |ctx|
        bufs = buffers_to_save
        buffer = ctx.buffer
        if bufs.length > 1
            dbg(:quit) { "u11" }
            if !buffer.dlog.invalidated?
                dbg(:quit) { "u12" }
                status_bar_edit_line "closing reverted buffer!"
                @buffers.delete buffer
                switch_to_buffer
                remove_unneeded_statusbars
                redraw buffer
            else
                dbg(:quit) { "u13" }
                status_bar_edit_line "more than one file unsaved! use :qa! to revert all!"
            end
        elsif bufs.length == 1 and bufs.first == buffer
            dbg(:quit) { "u14" }
            bufs.first.dlog.revert_to_save_point # there can be only one!!! :P
            Curses.end_it_all
        elsif !buffer.dlog.invalidated?
            dbg(:quit) { "u15" }
            @buffers.delete buffer
            Curses.end_it_all if buffers_to_save.length == 0
            switch_to_buffer
            remove_unneeded_statusbars
            redraw buffer
        end
    }

    setup_cmd("qk", /^qk$/) {
        |ctx|
        Curses.end_it_all
    }

    setup_cmd("q'uit", /^q$/) {
        |ctx|
        buffer = ctx.buffer
        num_real_bufs = real_buffers.length
        if num_real_bufs > 0 and buffer.dlog.invalidated?
            dbg(:quit) { "u01" }
            status_bar_edit_line "file unsaved!"
        elsif num_real_bufs > 0
            dbg(:quit) { "u02" }
            @buffers.delete buffer
            switch_to_buffer nil
            remove_unneeded_statusbars
        end
        unless real_buffers.length > 0 
            dbg(:quit) { "u03" }
            Curses.end_it_all 
        else
            dbg(:quit) { "u04" }
            redraw buffer
        end
    }

    setup_cmd("new", /^new$/) {
        |ctx|
        ctx.cmd_line =~ ctx.re
        buf = EditorApp.new_buffer self, :need_bnum
        switch_to_buffer buf
        redraw buf
    }

    setup_cmd("e,ed,edit", /^e(?:d(?:it)?)?\s+(.+)$/) {
        |ctx|
        ctx.cmd_line =~ ctx.re
        fname = $1
        fname.sub! "~", ENV["HOME"]
        switch_to_buffer EditorApp.load_file_as_buffer(self, fname)
    }

    setup_cmd("r,r!", /r(!\s*(.*$)|\s+(.*$))/) {
        |ctx|
        # TODO - test
        ctx.cmd_line =~ ctx.re
        lines = nil
        is_cmd = !$2.nil?
        lines = is_cmd ? exec_into_buffer_lines($2) : EditorApp.file_to_buffer_lines($3)
        ctx.buffer.lines[ctx.buffer.y, 0] = lines
        redraw ctx.buffer
    }

    setup_cmd("b'uffer", /^b(.+)$/) {
        |ctx|
        ctx.cmd_line =~ ctx.re
        buf = @buffers.detect { |b| b.bnum == $1.to_i }
        if buf.nil?
           status_bar_edit_line "No such buffer"
        else
           switch_to_buffer buf
        end
    }

    setup_cmd("w,wa,wq", /^w(q|a|\s+(.+))?$/) { # TODO - add in waq?
        |ctx|
        ctx.cmd_line =~ ctx.re
        buffer = ctx.buffer
        case $1
        when "a"
            error = false
            each_real_buffer {
                |buffer| 
                next if !buffer.dlog.invalidated? # don't save out untouched files
                if buffer.fname.nil?
                    status_bar_edit_line "ERROR - unnamed file on buffer #{buffer.bnum}"
                    error = true
                    break
                end
                save_buffer_as_file buffer
            }
            status_bar_edit_line "saved all files" unless error
        when "q"
            if !buffer.fname.nil?
                # TODO - notify when other files are unsaved!!!
                save_buffer_as_file buffer
                Curses.end_it_all
            else
                status_bar_edit_line "ERROR - unnamed file on buffer #{buffer.bnum}"
            end
        when nil
            # TODO - add a test case for this..
            if !buffer.fname.nil?
                save_buffer_as_file buffer
                status_bar_edit_line "saved"
            else
                status_bar_edit_line "ERROR - unnamed file on buffer #{buffer.bnum}"
            end
        else
            fname = $2
            fname.sub! "~", ENV["HOME"] unless fname.nil?
            if fname.nil?
                status_bar_edit_line "ERROR - unnamed file on buffer #{buffer.bnum}"
            else
                save_buffer_as_file buffer, fname # write it to a file with a suffix
                status_bar_edit_line "written out to: #{fname}"
            end
        end
    }

    # NOTE - this one just doesn't fit in with any completion plan i can come up with!!! - *has* to be a override
    setup_cmd_override(/^([0-9]+)$/) {
        |ctx|
        ctx.cmd_line =~ ctx.re
        buffer = ctx.buffer
        x = get_indent_level buffer.lines[buffer.y]
        buffer.move_to(x, $1.to_i - 1)
        buffer.top = buffer.y
        redraw buffer
    }
end

#setup_extensionsObject



700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
# File 'lib/shikaku.rb', line 700

def setup_extensions
    setup_cmd("sc", /^sc$/) {
        |ctx|
        ctx.cmd_line =~ ctx.re
        fname = ctx.buffer.fname
        buf = EditorApp.new_buffer self, :no_blank_line
        buf.lines += exec_into_buffer_lines "ruby -wc #{fname} 2>&1"
        switch_to_buffer buf
    }
    setup_cmd("tlaci,diff", /^((tlaci)|(diff))$/) {
        |ctx|
        ctx.cmd_line =~ ctx.re
        diff_only = $2.nil?
        cmd = "tla changes --diffs 2>&1"
        buf = nil
        if diff_only
            buf = EditorApp.new_buffer self, :no_blank_line
        else
            log_file_fname = `tla make-log`.chomp
            buf = EditorApp.load_file_as_buffer self, log_file_fname
        end
        buf.lines += exec_into_buffer_lines cmd
        switch_to_buffer buf
    }
end

#show_line_with_marker(buffer, point, line = nil) ⇒ Object



13
14
15
16
17
18
19
20
21
22
# File 'lib/debug.rb', line 13

def show_line_with_marker buffer, point, line = nil
    line = buffer.lines[point.y] if line.nil?
    n = 0
    line.unpack("c*").collect { 
                        |c| 
                        t = (n == point.x) ? "[#{c.chr}]"  \
                                            : "#{c.chr}"
                        n += 1; t 
                        }.join
end

#start_background_highlighterObject



754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
# File 'lib/shikaku.rb', line 754

def start_background_highlighter
# TODO enable the background highlighter
   return
    Thread.new {
        while true
           # FIXME - 
           #  maybe we should include some of the buffers listed by
           #  a call to BufferListing::instance.open_buffers once we
           #  are finished with the current_buffer?
           [current_buffer].each {
              |buffer|
              next if buffer != current_buffer
              ensure_buffer_highlighted buffer
           }
           sleep 5
        end
    }
end

#status_bar_edit_line(status, pos = -1) ⇒ Object



87
88
89
90
# File 'lib/search.rb', line 87

def status_bar_edit_line status, pos = -1
    @status_bar.text = status
    @status_bar.cursor.x, @status_bar.cursor.y = ((pos == -1) ? status.length : pos), 0
end

#switch_to_buffer(buf = @last_buffer) ⇒ Object



374
375
376
377
378
379
380
381
382
383
384
# File 'lib/shikaku.rb', line 374

def switch_to_buffer buf = @last_buffer
    @last_buffer = @current_buffer
    buf = real_buffers.first if (!@buffers.include? buf) or buf.nil?
    @current_buffer = buf
    buffer_changed_to buf
    return if buf.nil?
    # TODO need to fully clear screen here in case of short buffers?
    redraw buf
    @status_bar = @widgets.detect { |sb| (sb.is_a? StatusBar) and (sb.buffer == buf) }
    @status_bar = new_status_bar(buf) if @status_bar.nil?
end

#token_itr(buffer, tok, y, forwards, &block) ⇒ Object



609
610
611
612
613
614
615
# File 'lib/shikaku.rb', line 609

def token_itr buffer, tok, y, forwards, &block
    if forwards
        itr_forewards buffer, tok, y, &block
    else
        itr_backwards buffer, tok, y, &block
    end
end

#update_focusObject



498
499
500
501
502
503
504
505
506
507
508
509
# File 'lib/front.rb', line 498

def update_focus
    case @mode_stack.last
    when :search
        @focus = @status_bar
    when :normal, :insert, :get_letter
        @focus = current_docview
    when :command
        @focus = @status_bar
    when 
        @focus = current_docview
    end
end

#update_ruler_stateObject



265
266
267
268
269
270
271
272
273
274
# File 'lib/shikaku.rb', line 265

def update_ruler_state
    @widgets.delete @ruler
    @widgets.insert_after @widgets.index(@doc_with_ruler), @ruler if config_get_nu
    idx = @widgets.index(@doc_with_ruler2)
    if !idx.nil?
    @widgets.delete @ruler2
    @widgets.insert_after idx, @ruler2 if config_get_nu
    end
    EditorApp.perform_layout
end

#update_selection(buffer) ⇒ Object

USES right_way_up



104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
# File 'lib/selections.rb', line 104

def update_selection buffer
    EditorApp.each_lineslice_in_selection(buffer, @last_selection) {
        |hlls| 
        EditorApp.invalidate_buffer_line buffer, hlls.y
    } unless @last_selection.nil?
    
    no_selection = @selection.nil? || @selection.invalid?
    
    EditorApp.each_lineslice_in_selection(buffer, @selection.right_way_up) {
        |hlls| 
        EditorApp.invalidate_buffer_line buffer, hlls.y
    } unless no_selection
    
    @last_selection = no_selection ? nil : @selection.dup.right_way_up
end

#update_split_stateObject



250
251
252
253
254
255
256
257
258
259
260
261
262
263
# File 'lib/shikaku.rb', line 250

def update_split_state
    [@doc_with_ruler2, @docview2].each { |w| @widgets.delete w }
    status_bars = @widgets.find_all { |sb| sb.is_a? StatusBar }
    idx_of_first_status_bar = @widgets.index status_bars.first
if config_get_split
  if idx_of_first_status_bar.nil?
    @widgets += [@doc_with_ruler2, @docview2] if config_get_split
  else
    @widgets.insert_after(idx_of_first_status_bar - 1, @docview2)
    @widgets.insert_after(idx_of_first_status_bar - 1, @doc_with_ruler2)
  end
end
    EditorApp.perform_layout
end