Skip to content
Kward Search API index

Class: Kward::PromptInterface::EditorState

Inherits:
Object
  • Object
show all
Defined in:
lib/kward/prompt_interface/editor/state.rb

Overview

Mutable state for the built-in composer file editor.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(path:, content:, new_file: false, editor_mode: "modern", readonly: false, diff_view: false, virtual: false, display_path: nil, language: nil) ⇒ EditorState

Returns a new instance of EditorState.



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
# File 'lib/kward/prompt_interface/editor/state.rb', line 23

def initialize(path:, content:, new_file: false, editor_mode: "modern", readonly: false, diff_view: false, virtual: false, display_path: nil, language: nil)
  @path = virtual ? nil : path.to_s
  @display_path = display_path.to_s.empty? ? path.to_s : display_path.to_s
  @language = language&.to_sym
  @new_file = new_file
  @readonly = readonly
  @diff_view = diff_view
  @virtual = virtual == true
  @file_marker = EditorFileMarker.new(path: @path || @display_path, content: content, new_file: new_file || virtual?)
  @original_content = @file_marker.content
  @original_digest = @file_marker.digest
  @original_mtime = @file_marker.mtime
  @original_size = @file_marker.size
  @text_buffer = EditorBuffer.new(@original_content)
  @buffer = @text_buffer.text
  @cursor = 0
  @viewport_row = 0
  @viewport_column = 0
  @status = nil
  @overwrite_confirmed = false
  @quit_confirmed = false
  @search = EditorSearch.new
  @search_active = @search.active?
  @search_query = @search.query
  @search_direction = @search.direction
  @kill_state = EditorKillRing.new
  @kill_buffer = @kill_state.kill_buffer
  @selections = EditorSelections.new(cursor: @cursor, buffer_length: @buffer.length)
  sync_selection_state
  @editor_mode = normalize_editor_mode(editor_mode)
  @emacs_pending = nil
  @kill_ring = @kill_state.kill_ring
  @last_yank_range = @kill_state.last_yank_range
  @last_yank_index = @kill_state.last_yank_index
  @vibe_state = VibeEditorState.new(editor_mode: @editor_mode)
  sync_vibe_state
  @undo_history = EditorUndoHistory.new
  @undo_stack = @undo_history.undo_stack
  @redo_stack = @undo_history.redo_stack
  @status = default_status
end

Instance Attribute Details

#bufferObject

Returns the value of attribute buffer.



20
21
22
# File 'lib/kward/prompt_interface/editor/state.rb', line 20

def buffer
  @buffer
end

#diff_viewObject

Returns the value of attribute diff_view.



21
22
23
# File 'lib/kward/prompt_interface/editor/state.rb', line 21

def diff_view
  @diff_view
end

#display_pathObject (readonly)

Returns the value of attribute display_path.



19
20
21
# File 'lib/kward/prompt_interface/editor/state.rb', line 19

def display_path
  @display_path
end

#editor_modeObject

Returns the value of attribute editor_mode.



21
22
23
# File 'lib/kward/prompt_interface/editor/state.rb', line 21

def editor_mode
  @editor_mode
end

#emacs_pendingObject

Returns the value of attribute emacs_pending.



21
22
23
# File 'lib/kward/prompt_interface/editor/state.rb', line 21

def emacs_pending
  @emacs_pending
end

#kill_bufferObject

Returns the value of attribute kill_buffer.



20
21
22
# File 'lib/kward/prompt_interface/editor/state.rb', line 20

def kill_buffer
  @kill_buffer
end

#kill_ringObject

Returns the value of attribute kill_ring.



20
21
22
# File 'lib/kward/prompt_interface/editor/state.rb', line 20

def kill_ring
  @kill_ring
end

#languageObject (readonly)

Returns the value of attribute language.



19
20
21
# File 'lib/kward/prompt_interface/editor/state.rb', line 19

def language
  @language
end

#last_yank_indexObject

Returns the value of attribute last_yank_index.



20
21
22
# File 'lib/kward/prompt_interface/editor/state.rb', line 20

def last_yank_index
  @last_yank_index
end

#last_yank_rangeObject

Returns the value of attribute last_yank_range.



20
21
22
# File 'lib/kward/prompt_interface/editor/state.rb', line 20

def last_yank_range
  @last_yank_range
end

#new_fileObject

Returns the value of attribute new_file.



21
22
23
# File 'lib/kward/prompt_interface/editor/state.rb', line 21

def new_file
  @new_file
end

#original_contentObject (readonly)

Returns the value of attribute original_content.



19
20
21
# File 'lib/kward/prompt_interface/editor/state.rb', line 19

def original_content
  @original_content
end

#original_digestObject (readonly)

Returns the value of attribute original_digest.



19
20
21
# File 'lib/kward/prompt_interface/editor/state.rb', line 19

def original_digest
  @original_digest
end

#original_mtimeObject (readonly)

Returns the value of attribute original_mtime.



19
20
21
# File 'lib/kward/prompt_interface/editor/state.rb', line 19

def original_mtime
  @original_mtime
end

#original_sizeObject (readonly)

Returns the value of attribute original_size.



19
20
21
# File 'lib/kward/prompt_interface/editor/state.rb', line 19

def original_size
  @original_size
end

#overwrite_confirmedObject

Returns the value of attribute overwrite_confirmed.



21
22
23
# File 'lib/kward/prompt_interface/editor/state.rb', line 21

def overwrite_confirmed
  @overwrite_confirmed
end

#pathObject (readonly)

Returns the value of attribute path.



19
20
21
# File 'lib/kward/prompt_interface/editor/state.rb', line 19

def path
  @path
end

#quit_confirmedObject

Returns the value of attribute quit_confirmed.



21
22
23
# File 'lib/kward/prompt_interface/editor/state.rb', line 21

def quit_confirmed
  @quit_confirmed
end

#readonlyObject

Returns the value of attribute readonly.



21
22
23
# File 'lib/kward/prompt_interface/editor/state.rb', line 21

def readonly
  @readonly
end

#redo_stackObject

Returns the value of attribute redo_stack.



20
21
22
# File 'lib/kward/prompt_interface/editor/state.rb', line 20

def redo_stack
  @redo_stack
end

#search_activeObject

Returns the value of attribute search_active.



21
22
23
# File 'lib/kward/prompt_interface/editor/state.rb', line 21

def search_active
  @search_active
end

#search_directionObject

Returns the value of attribute search_direction.



21
22
23
# File 'lib/kward/prompt_interface/editor/state.rb', line 21

def search_direction
  @search_direction
end

#search_queryObject

Returns the value of attribute search_query.



21
22
23
# File 'lib/kward/prompt_interface/editor/state.rb', line 21

def search_query
  @search_query
end

#statusObject

Returns the value of attribute status.



21
22
23
# File 'lib/kward/prompt_interface/editor/state.rb', line 21

def status
  @status
end

#undo_stackObject

Returns the value of attribute undo_stack.



20
21
22
# File 'lib/kward/prompt_interface/editor/state.rb', line 20

def undo_stack
  @undo_stack
end

#vibe_stateObject (readonly)

Returns the value of attribute vibe_state.



115
116
117
# File 'lib/kward/prompt_interface/editor/state.rb', line 115

def vibe_state
  @vibe_state
end

#viewport_columnObject

Returns the value of attribute viewport_column.



21
22
23
# File 'lib/kward/prompt_interface/editor/state.rb', line 21

def viewport_column
  @viewport_column
end

#viewport_rowObject

Returns the value of attribute viewport_row.



21
22
23
# File 'lib/kward/prompt_interface/editor/state.rb', line 21

def viewport_row
  @viewport_row
end

Instance Method Details

#add_next_occurrence_selectionObject



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
# File 'lib/kward/prompt_interface/editor/state.rb', line 836

def add_next_occurrence_selection
  range = selection_range || word_range_at(@cursor)
  unless range
    @status = "No word under cursor"
    return false
  end

  query = @buffer[range[0]...range[1]].to_s
  if query.empty?
    @status = "No word under cursor"
    return false
  end

  existing_ranges = selection_ranges
  existing_ranges = [range] if existing_ranges.empty?
  start_after = existing_ranges.map(&:last).max || range[1]
  if selection_range.nil?
    set_selections([{ anchor: range[0], cursor: range[1] }])
    @status = "Selected: #{query}"
    return true
  end

  match = next_occurrence_range(query, start_after, existing_ranges)
  unless match
    @status = "No more matches: #{query}"
    return false
  end

  add_selection(match[0], match[1])
  @status = "Added cursor for: #{query}"
  true
end

#add_selection(anchor, cursor = anchor) ⇒ Object



296
297
298
299
# File 'lib/kward/prompt_interface/editor/state.rb', line 296

def add_selection(anchor, cursor = anchor)
  @selections.add(anchor, cursor)
  sync_selection_state
end

#add_vertical_cursor(direction) ⇒ Object



869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
# File 'lib/kward/prompt_interface/editor/state.rb', line 869

def add_vertical_cursor(direction)
  source = direction == :up ? selections.min_by { |selection| selection[:cursor] } : selections.max_by { |selection| selection[:cursor] }
  line, column = cursor_line_and_column_for(source[:cursor])
  target_line = direction == :up ? line - 1 : line + 1
  if target_line.negative? || target_line >= lines.length
    @status = direction == :up ? "No line above" : "No line below"
    return false
  end

  target_column = [column, lines[target_line].to_s.length].min
  offset = line_start_offset(target_line) + target_column
  add_selection(offset, offset)
  @status = direction == :up ? "Added cursor above" : "Added cursor below"
  true
end

#append_search(text) ⇒ Object



923
924
925
926
927
# File 'lib/kward/prompt_interface/editor/state.rb', line 923

def append_search(text)
  @status = @search.append(text)
  sync_search_state
  true
end

#begin_search(direction = :forward) ⇒ Object



911
912
913
914
915
# File 'lib/kward/prompt_interface/editor/state.rb', line 911

def begin_search(direction = :forward)
  @status = @search.begin(direction)
  sync_search_state
  true
end

#begin_selectionObject



665
666
667
668
# File 'lib/kward/prompt_interface/editor/state.rb', line 665

def begin_selection
  self.selection_anchor = @cursor
  @status = "Selection started"
end

#bind_path(path) ⇒ Object



319
320
321
322
323
324
325
# File 'lib/kward/prompt_interface/editor/state.rb', line 319

def bind_path(path)
  @path = path.to_s
  @display_path = @path
  @virtual = false
  @new_file = !File.exist?(@path)
  @file_marker = EditorFileMarker.new(path: @path, content: @original_content, new_file: true)
end

#cancel_searchObject



917
918
919
920
921
# File 'lib/kward/prompt_interface/editor/state.rb', line 917

def cancel_search
  @status = @search.cancel
  sync_search_state
  true
end

#clear_selectionObject



670
671
672
673
# File 'lib/kward/prompt_interface/editor/state.rb', line 670

def clear_selection
  @selections.clear
  sync_selection_state
end

#collapse_to_primary_selectionObject



301
302
303
304
# File 'lib/kward/prompt_interface/editor/state.rb', line 301

def collapse_to_primary_selection
  @selections.collapse_to_primary
  sync_selection_state
end

#confirm_searchObject



935
936
937
# File 'lib/kward/prompt_interface/editor/state.rb', line 935

def confirm_search
  apply_search_result(@search.confirm(buffer: @buffer, cursor: @cursor))
end

#copy_for_kill_ring(start_index, end_index) ⇒ Object



827
828
829
830
# File 'lib/kward/prompt_interface/editor/state.rb', line 827

def copy_for_kill_ring(start_index, end_index)
  start_index, end_index = [start_index, end_index].minmax
  push_kill(@buffer[start_index...end_index].to_s)
end

#copy_range(start_index, end_index) ⇒ Object



822
823
824
825
# File 'lib/kward/prompt_interface/editor/state.rb', line 822

def copy_range(start_index, end_index)
  start_index, end_index = [start_index, end_index].minmax
  self.kill_buffer = @buffer[start_index...end_index].to_s
end

#current_line_rangeObject



774
775
776
777
# File 'lib/kward/prompt_interface/editor/state.rb', line 774

def current_line_range
  line, = cursor_line_and_column
  line_range(line)
end

#cursorObject



261
262
263
# File 'lib/kward/prompt_interface/editor/state.rb', line 261

def cursor
  @cursor
end

#cursor=(value) ⇒ Object



265
266
267
268
269
# File 'lib/kward/prompt_interface/editor/state.rb', line 265

def cursor=(value)
  @cursor = clamp_offset(value)
  @selections.cursor = @cursor
  sync_selection_state
end

#cursor_line_and_columnObject



351
352
353
# File 'lib/kward/prompt_interface/editor/state.rb', line 351

def cursor_line_and_column
  cursor_line_and_column_for(@cursor)
end

#cursor_line_and_column_for(offset) ⇒ Object



743
744
745
746
# File 'lib/kward/prompt_interface/editor/state.rb', line 743

def cursor_line_and_column_for(offset)
  before_cursor = @buffer[0...offset].to_s
  [before_cursor.count("\n"), (before_cursor.split("\n", -1).last || "").length]
end

#cut_range(start_index, end_index) ⇒ Object



832
833
834
# File 'lib/kward/prompt_interface/editor/state.rb', line 832

def cut_range(start_index, end_index)
  kill_range(start_index, end_index)
end

#delete_at_cursorObject



417
418
419
420
421
422
423
424
425
426
# File 'lib/kward/prompt_interface/editor/state.rb', line 417

def delete_at_cursor
  return delete_at_selections if multi_cursor?
  return false unless @cursor < @buffer.length

  @text_buffer.delete_range(@cursor, @cursor + 1)
  @buffer = @text_buffer.text
  sync_selection_state
  changed!
  true
end

#delete_at_selectionsObject



810
811
812
813
814
815
816
817
818
819
820
# File 'lib/kward/prompt_interface/editor/state.rb', line 810

def delete_at_selections
  apply_selection_edits do |selection|
    range = selection_range_for(selection)
    if range[0] != range[1]
      ""
    elsif range[1] < @buffer.length
      selection[:cursor] = range[1] + 1
      ""
    end
  end
end

#delete_before_cursorObject



405
406
407
408
409
410
411
412
413
414
415
# File 'lib/kward/prompt_interface/editor/state.rb', line 405

def delete_before_cursor
  return delete_before_selections if multi_cursor?
  return false if @cursor.zero?

  @text_buffer.delete_range(@cursor - 1, @cursor)
  @cursor -= 1
  @buffer = @text_buffer.text
  sync_selection_state
  changed!
  true
end

#delete_before_selectionsObject



798
799
800
801
802
803
804
805
806
807
808
# File 'lib/kward/prompt_interface/editor/state.rb', line 798

def delete_before_selections
  apply_selection_edits do |selection|
    range = selection_range_for(selection)
    if range[0] != range[1]
      ""
    elsif range[0].positive?
      selection[:anchor] = range[0] - 1
      ""
    end
  end
end

#delete_search_characterObject



929
930
931
932
933
# File 'lib/kward/prompt_interface/editor/state.rb', line 929

def delete_search_character
  @status = @search.delete_character
  sync_search_state
  true
end

#delete_word_after_cursorObject



601
602
603
604
605
# File 'lib/kward/prompt_interface/editor/state.rb', line 601

def delete_word_after_cursor
  return apply_selection_edits { |selection| selection[:cursor] = next_word_boundary(selection_range_for(selection)[1]); "" } if multi_cursor?

  kill_range(@cursor, next_word_boundary(@cursor))
end

#delete_word_before_cursorObject



595
596
597
598
599
# File 'lib/kward/prompt_interface/editor/state.rb', line 595

def delete_word_before_cursor
  return apply_selection_edits { |selection| selection[:anchor] = previous_word_boundary(selection_range_for(selection)[0]); "" } if multi_cursor?

  kill_range(previous_word_boundary(@cursor), @cursor)
end

#diff_view?Boolean

Returns:

  • (Boolean)


327
328
329
# File 'lib/kward/prompt_interface/editor/state.rb', line 327

def diff_view?
  @diff_view == true
end

#dirty?Boolean

Returns:

  • (Boolean)


343
344
345
# File 'lib/kward/prompt_interface/editor/state.rb', line 343

def dirty?
  @buffer != @original_content
end

#emacs?Boolean

Returns:

  • (Boolean)


335
336
337
# File 'lib/kward/prompt_interface/editor/state.rb', line 335

def emacs?
  @editor_mode == "emacs"
end

#extending_selectionsObject



903
904
905
906
907
908
909
# File 'lib/kward/prompt_interface/editor/state.rb', line 903

def extending_selections
  previous = @extending_selections
  @extending_selections = true
  yield
ensure
  @extending_selections = previous
end

#file_changed_on_disk?Boolean

Returns:

  • (Boolean)


971
972
973
974
975
# File 'lib/kward/prompt_interface/editor/state.rb', line 971

def file_changed_on_disk?
  return false if virtual?

  @file_marker.changed_on_disk?(new_file: new_file)
end

#initialize_copy(other) ⇒ Object



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
# File 'lib/kward/prompt_interface/editor/state.rb', line 65

def initialize_copy(other)
  super
  @path = other.path&.dup
  @display_path = other.display_path.dup
  @language = other.language
  @virtual = other.virtual?
  @original_content = other.original_content.dup
  @file_marker = EditorFileMarker.new(path: @path || @display_path, content: @original_content, new_file: other.new_file || @virtual)
  @original_digest = other.original_digest.dup
  @original_mtime = other.original_mtime
  @original_size = other.original_size
  @text_buffer = EditorBuffer.new(other.buffer)
  @buffer = @text_buffer.text
  @status = other.status.dup
  @search = EditorSearch.new(direction: other.search_direction)
  @search_active = other.search_active
  @search_query = other.search_query.dup
  @search_direction = other.search_direction
  @kill_state = EditorKillRing.new(
    kill_buffer: other.kill_buffer.dup,
    kill_ring: other.kill_ring.map(&:dup),
    last_yank_range: other.last_yank_range&.dup,
    last_yank_index: other.last_yank_index
  )
  @kill_buffer = @kill_state.kill_buffer
  @quit_confirmed = other.quit_confirmed
  @viewport_column = other.viewport_column
  @selections = EditorSelections.new(
    cursor: @cursor,
    buffer_length: @buffer.length,
    anchor: other.selection_anchor,
    secondary: other.selections.drop(1).map(&:dup)
  )
  sync_selection_state
  @editor_mode = other.editor_mode.dup
  @emacs_pending = other.emacs_pending&.dup
  @kill_ring = @kill_state.kill_ring
  @last_yank_range = @kill_state.last_yank_range
  @last_yank_index = @kill_state.last_yank_index
  @vibe_state = VibeEditorState.copy(other.vibe_state)
  sync_vibe_state
  @undo_history = EditorUndoHistory.new
  other.undo_stack.each { |entry| @undo_history.undo_stack << duplicate_editor_snapshot(entry) }
  other.redo_stack.each { |entry| @undo_history.redo_stack << duplicate_editor_snapshot(entry) }
  @undo_stack = @undo_history.undo_stack
  @redo_stack = @undo_history.redo_stack
  @readonly = other.readonly
  @diff_view = other.diff_view
end

#insert(text) ⇒ Object



393
394
395
396
397
398
399
400
401
402
403
# File 'lib/kward/prompt_interface/editor/state.rb', line 393

def insert(text)
  text = text.to_s
  return if text.empty?
  return replace_selections(text) if multi_cursor?

  @text_buffer.insert(@cursor, text)
  @cursor += text.length
  @buffer = @text_buffer.text
  sync_selection_state
  changed!
end

#kill_line_after_cursorObject



613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
# File 'lib/kward/prompt_interface/editor/state.rb', line 613

def kill_line_after_cursor
  if multi_cursor?
    return apply_selection_edits do |selection|
      range = selection_range_for(selection)
      selection[:cursor] = line_end_for_offset(range[1])
      selection[:cursor] += 1 if range[0] == selection[:cursor] && selection[:cursor] < @buffer.length
      ""
    end
  end

  if current_line_empty?
    kill_range(empty_line_start, empty_line_end)
  else
    kill_range(@cursor, current_line_end)
  end
end

#kill_line_before_cursorObject



607
608
609
610
611
# File 'lib/kward/prompt_interface/editor/state.rb', line 607

def kill_line_before_cursor
  return apply_selection_edits { |selection| selection[:anchor] = line_start_for_offset(selection_range_for(selection)[0]); "" } if multi_cursor?

  kill_range(current_line_start, @cursor)
end

#line_range(line_index) ⇒ Object



754
755
756
757
758
759
# File 'lib/kward/prompt_interface/editor/state.rb', line 754

def line_range(line_index)
  start_index = line_start_offset(line_index)
  end_index = start_index + lines[line_index].to_s.length
  end_index += 1 if end_index < @buffer.length
  [start_index, end_index]
end

#line_start_offset(line_index) ⇒ Object



748
749
750
751
752
# File 'lib/kward/prompt_interface/editor/state.rb', line 748

def line_start_offset(line_index)
  values = lines
  line_index = [[line_index.to_i, 0].max, values.length - 1].min
  values.first(line_index).sum { |line| line.length + 1 }
end

#linesObject



347
348
349
# File 'lib/kward/prompt_interface/editor/state.rb', line 347

def lines
  @text_buffer.lines
end

#modern?Boolean

Returns:

  • (Boolean)


331
332
333
# File 'lib/kward/prompt_interface/editor/state.rb', line 331

def modern?
  @editor_mode == "modern"
end

#move_downObject



459
460
461
462
463
464
465
466
467
468
# File 'lib/kward/prompt_interface/editor/state.rb', line 459

def move_down
  if multi_cursor?
    return move_selection_cursors { |selection| move_offset_vertically(selection[:cursor], 1) } if extending_selections?

    return move_selections { |selection| move_offset_vertically(selection[:cursor], 1) }
  end

  line, column = cursor_line_and_column
  set_cursor_line_and_column(line + 1, column)
end

#move_file_endObject



783
784
785
# File 'lib/kward/prompt_interface/editor/state.rb', line 783

def move_file_end
  @cursor = @buffer.length
end

#move_file_startObject



779
780
781
# File 'lib/kward/prompt_interface/editor/state.rb', line 779

def move_file_start
  @cursor = 0
end

#move_indentation_downObject



567
568
569
570
571
572
573
574
575
576
577
# File 'lib/kward/prompt_interface/editor/state.rb', line 567

def move_indentation_down
  if multi_cursor?
    return move_selection_cursors { |selection| indentation_offset_for(selection[:cursor], :down) } if extending_selections?

    return move_selections { |selection| indentation_offset_for(selection[:cursor], :down) }
  end

  line, column = cursor_line_and_column
  target_line = next_indentation_line(line, indentation_level_for_line(line))
  move_to_indentation_line(target_line, column)
end

#move_indentation_rightObject



579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
# File 'lib/kward/prompt_interface/editor/state.rb', line 579

def move_indentation_right
  if multi_cursor?
    return move_selection_cursors { |selection| indentation_right_offset_for(selection[:cursor]) } if extending_selections?

    return move_selections { |selection| indentation_right_offset_for(selection[:cursor]) }
  end

  line, column = cursor_line_and_column
  indentation = indentation_level_for_line(line)
  if column < indentation
    set_cursor_line_and_column(line, indentation)
  else
    move_to_word_end
  end
end

#move_indentation_upObject



555
556
557
558
559
560
561
562
563
564
565
# File 'lib/kward/prompt_interface/editor/state.rb', line 555

def move_indentation_up
  if multi_cursor?
    return move_selection_cursors { |selection| indentation_offset_for(selection[:cursor], :up) } if extending_selections?

    return move_selections { |selection| indentation_offset_for(selection[:cursor], :up) }
  end

  line, column = cursor_line_and_column
  target_line = previous_indentation_line(line, indentation_level_for_line(line))
  move_to_indentation_line(target_line, column)
end

#move_leftObject



428
429
430
431
432
433
434
435
436
# File 'lib/kward/prompt_interface/editor/state.rb', line 428

def move_left
  if multi_cursor?
    return move_selection_cursors { |selection| [selection[:cursor] - 1, 0].max } if extending_selections?

    return move_selections { |selection| collapse_or_move_left(selection) }
  end

  @cursor -= 1 if @cursor.positive?
end

#move_line_endObject



492
493
494
495
496
497
498
499
500
501
# File 'lib/kward/prompt_interface/editor/state.rb', line 492

def move_line_end
  if multi_cursor?
    return move_selection_cursors { |selection| line_end_for_offset(selection[:cursor]) } if extending_selections?

    return move_selections { |selection| line_end_for_offset(selection[:cursor]) }
  end

  line, = cursor_line_and_column
  set_cursor_line_and_column(line, lines[line].length)
end

#move_line_first_non_blankObject



481
482
483
484
# File 'lib/kward/prompt_interface/editor/state.rb', line 481

def move_line_first_non_blank
  line, = cursor_line_and_column
  move_to_line_first_non_blank(line)
end

#move_line_startObject



470
471
472
473
474
475
476
477
478
479
# File 'lib/kward/prompt_interface/editor/state.rb', line 470

def move_line_start
  if multi_cursor?
    return move_selection_cursors { |selection| line_start_for_offset(selection[:cursor]) } if extending_selections?

    return move_selections { |selection| line_start_for_offset(selection[:cursor]) }
  end

  line, = cursor_line_and_column
  set_cursor_line_and_column(line, 0)
end

#move_rightObject



438
439
440
441
442
443
444
445
446
# File 'lib/kward/prompt_interface/editor/state.rb', line 438

def move_right
  if multi_cursor?
    return move_selection_cursors { |selection| [selection[:cursor] + 1, @buffer.length].min } if extending_selections?

    return move_selections { |selection| collapse_or_move_right(selection) }
  end

  @cursor += 1 if @cursor < @buffer.length
end

#move_to_line_first_non_blank(line_index) ⇒ Object



486
487
488
489
490
# File 'lib/kward/prompt_interface/editor/state.rb', line 486

def move_to_line_first_non_blank(line_index)
  line = [[line_index.to_i, 0].max, lines.length - 1].min
  column = lines[line].to_s.index(/\S/) || 0
  set_cursor_line_and_column(line, column)
end

#move_to_next_wordObject



535
536
537
538
539
540
541
542
543
# File 'lib/kward/prompt_interface/editor/state.rb', line 535

def move_to_next_word
  if multi_cursor?
    return move_selection_cursors { |selection| next_word_boundary(selection[:cursor]) } if extending_selections?

    return move_selections { |selection| next_word_boundary(selection[:cursor]) }
  end

  @cursor = next_word_boundary(@cursor)
end

#move_to_previous_wordObject



525
526
527
528
529
530
531
532
533
# File 'lib/kward/prompt_interface/editor/state.rb', line 525

def move_to_previous_word
  if multi_cursor?
    return move_selection_cursors { |selection| previous_word_boundary(selection[:cursor]) } if extending_selections?

    return move_selections { |selection| previous_word_boundary(selection[:cursor]) }
  end

  @cursor = previous_word_boundary(@cursor)
end

#move_to_word_endObject



545
546
547
548
549
550
551
552
553
# File 'lib/kward/prompt_interface/editor/state.rb', line 545

def move_to_word_end
  if multi_cursor?
    return move_selection_cursors { |selection| next_word_end(selection[:cursor]) } if extending_selections?

    return move_selections { |selection| next_word_end(selection[:cursor]) }
  end

  @cursor = next_word_end(@cursor)
end

#move_upObject



448
449
450
451
452
453
454
455
456
457
# File 'lib/kward/prompt_interface/editor/state.rb', line 448

def move_up
  if multi_cursor?
    return move_selection_cursors { |selection| move_offset_vertically(selection[:cursor], -1) } if extending_selections?

    return move_selections { |selection| move_offset_vertically(selection[:cursor], -1) }
  end

  line, column = cursor_line_and_column
  set_cursor_line_and_column(line - 1, column)
end

#multi_cursor?Boolean

Returns:

  • (Boolean)


285
286
287
288
# File 'lib/kward/prompt_interface/editor/state.rb', line 285

def multi_cursor?
  sync_selection_state
  @selections.multi_cursor?
end

#offset_for_line_and_column(line_index, column) ⇒ Object



359
360
361
# File 'lib/kward/prompt_interface/editor/state.rb', line 359

def offset_for_line_and_column(line_index, column)
  @text_buffer.offset_for_line_and_column(line_index, column)
end

#page_down(rows) ⇒ Object



514
515
516
517
518
519
520
521
522
523
# File 'lib/kward/prompt_interface/editor/state.rb', line 514

def page_down(rows)
  if multi_cursor?
    return move_selection_cursors { |selection| move_offset_vertically(selection[:cursor], rows.to_i) } if extending_selections?

    return move_selections { |selection| move_offset_vertically(selection[:cursor], rows.to_i) }
  end

  line, column = cursor_line_and_column
  set_cursor_line_and_column(line + rows.to_i, column)
end

#page_up(rows) ⇒ Object



503
504
505
506
507
508
509
510
511
512
# File 'lib/kward/prompt_interface/editor/state.rb', line 503

def page_up(rows)
  if multi_cursor?
    return move_selection_cursors { |selection| move_offset_vertically(selection[:cursor], -rows.to_i) } if extending_selections?

    return move_selections { |selection| move_offset_vertically(selection[:cursor], -rows.to_i) }
  end

  line, column = cursor_line_and_column
  set_cursor_line_and_column(line - rows.to_i, column)
end

#push_kill(text) ⇒ Object



634
635
636
637
638
639
# File 'lib/kward/prompt_interface/editor/state.rb', line 634

def push_kill(text)
  return false unless @kill_state.push(text)

  sync_kill_state
  true
end

#push_undoObject



363
364
365
# File 'lib/kward/prompt_interface/editor/state.rb', line 363

def push_undo
  @undo_history.push(editor_snapshot)
end

#readonly?Boolean

Returns:

  • (Boolean)


311
312
313
# File 'lib/kward/prompt_interface/editor/state.rb', line 311

def readonly?
  @readonly == true
end

#redoObject



380
381
382
383
384
385
386
387
388
389
390
391
# File 'lib/kward/prompt_interface/editor/state.rb', line 380

def redo
  snapshot = @undo_history.redo(editor_snapshot)
  unless snapshot
    @status = "Already at newest change"
    return false
  end

  restore_editor_snapshot(snapshot)
  changed!(clear_selections: false)
  @status = "Redo"
  true
end

#refresh_after_save(content) ⇒ Object



957
958
959
960
961
962
963
964
965
966
967
968
969
# File 'lib/kward/prompt_interface/editor/state.rb', line 957

def refresh_after_save(content)
  @new_file = false
  @virtual = false
  @display_path = @path.to_s
  @file_marker.refresh(content)
  @original_content = @file_marker.content
  @original_digest = @file_marker.digest
  @original_mtime = @file_marker.mtime
  @original_size = @file_marker.size
  @overwrite_confirmed = false
  @quit_confirmed = false
  @status = "Saved #{@path}"
end

#repeat_search(direction = @search_direction, query = @search_query) ⇒ Object



939
940
941
# File 'lib/kward/prompt_interface/editor/state.rb', line 939

def repeat_search(direction = @search_direction, query = @search_query)
  apply_search_result(@search.repeat(buffer: @buffer, cursor: @cursor, direction: direction, query: query))
end

#replace_range(start_index, end_index, text) ⇒ Object



787
788
789
790
791
792
# File 'lib/kward/prompt_interface/editor/state.rb', line 787

def replace_range(start_index, end_index, text)
  start_index, = @text_buffer.replace_range(start_index, end_index, text)
  @buffer = @text_buffer.text
  @cursor = [start_index, @buffer.length].min
  changed!
end

#replace_selections(text) ⇒ Object



794
795
796
# File 'lib/kward/prompt_interface/editor/state.rb', line 794

def replace_selections(text)
  apply_selection_edits { |_selection| text.to_s }
end

#secondary_cursor_offsetsObject



306
307
308
309
# File 'lib/kward/prompt_interface/editor/state.rb', line 306

def secondary_cursor_offsets
  sync_selection_state
  @selections.secondary_cursor_offsets
end

#selected_textObject



732
733
734
735
736
737
738
739
740
741
# File 'lib/kward/prompt_interface/editor/state.rb', line 732

def selected_text
  if vibe? && @vibe_mode == "visual_block"
    return selection_ranges.map { |range| @buffer[range[0]...range[1]].to_s }.join("\n")
  end

  ranges = selection_ranges
  return "" if ranges.empty?

  ranges.map { |range| @buffer[range[0]...range[1]].to_s }.join("\n")
end

#selection_active?Boolean

Returns:

  • (Boolean)


675
676
677
678
679
680
# File 'lib/kward/prompt_interface/editor/state.rb', line 675

def selection_active?
  return false if @selection_anchor.nil?
  return true if vibe? && %w[visual visual_line visual_block].include?(@vibe_mode)

  @selection_anchor != @cursor
end

#selection_anchorObject



271
272
273
# File 'lib/kward/prompt_interface/editor/state.rb', line 271

def selection_anchor
  @selection_anchor
end

#selection_anchor=(value) ⇒ Object



275
276
277
278
# File 'lib/kward/prompt_interface/editor/state.rb', line 275

def selection_anchor=(value)
  @selections.anchor = value
  sync_selection_state
end

#selection_rangeObject



682
683
684
685
686
687
688
689
# File 'lib/kward/prompt_interface/editor/state.rb', line 682

def selection_range
  return visual_line_selection_range if vibe? && @vibe_mode == "visual_line" && selection_active?
  return visual_character_selection_range if vibe? && @vibe_mode == "visual" && selection_active?
  return visual_block_selection_ranges.first if vibe? && @vibe_mode == "visual_block" && selection_active?
  return nil unless primary_selection_active?

  [@selection_anchor, @cursor].minmax
end

#selection_rangesObject



691
692
693
694
695
696
697
698
699
700
701
702
# File 'lib/kward/prompt_interface/editor/state.rb', line 691

def selection_ranges
  if vibe? && %w[visual visual_line visual_block].include?(@vibe_mode) && selection_active?
    return visual_block_selection_ranges if @vibe_mode == "visual_block"

    return [selection_range]
  end

  selections.filter_map do |selection|
    range = selection_range_for(selection)
    range if range[0] != range[1]
  end
end

#selection_to_line_start_cursorsObject



885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
# File 'lib/kward/prompt_interface/editor/state.rb', line 885

def selection_to_line_start_cursors
  range = selection_range
  unless range
    @status = "No selection"
    return false
  end

  start_line, = cursor_line_and_column_for(range[0])
  end_line, end_column = cursor_line_and_column_for(range[1])
  end_line -= 1 if end_column.zero? && end_line > start_line
  set_selections((start_line..end_line).map do |line_index|
    offset = line_start_offset(line_index)
    { anchor: offset, cursor: offset }
  end)
  @status = "Created #{selections.length} cursors"
  true
end

#selectionsObject



280
281
282
283
# File 'lib/kward/prompt_interface/editor/state.rb', line 280

def selections
  sync_selection_state
  @selections.all
end

#set_cursor_line_and_column(line_index, column) ⇒ Object



355
356
357
# File 'lib/kward/prompt_interface/editor/state.rb', line 355

def set_cursor_line_and_column(line_index, column)
  @cursor = offset_for_line_and_column(line_index, column)
end

#set_selections(values) ⇒ Object



290
291
292
293
294
# File 'lib/kward/prompt_interface/editor/state.rb', line 290

def set_selections(values)
  @selections.set(values)
  @cursor = @selections.primary[:cursor]
  sync_selection_state
end

#undoObject



367
368
369
370
371
372
373
374
375
376
377
378
# File 'lib/kward/prompt_interface/editor/state.rb', line 367

def undo
  snapshot = @undo_history.undo(editor_snapshot)
  unless snapshot
    @status = "Already at oldest change"
    return false
  end

  restore_editor_snapshot(snapshot)
  changed!(clear_selections: false)
  @status = "Undo"
  true
end

#vibe?Boolean

Returns:

  • (Boolean)


339
340
341
# File 'lib/kward/prompt_interface/editor/state.rb', line 339

def vibe?
  @editor_mode == "vibe"
end

#vibe_commandObject



171
172
173
# File 'lib/kward/prompt_interface/editor/state.rb', line 171

def vibe_command
  @vibe_state.command
end

#vibe_command=(value) ⇒ Object



175
176
177
178
# File 'lib/kward/prompt_interface/editor/state.rb', line 175

def vibe_command=(value)
  @vibe_state.command = value
  sync_vibe_state
end

#vibe_last_changeObject



180
181
182
# File 'lib/kward/prompt_interface/editor/state.rb', line 180

def vibe_last_change
  @vibe_state.last_change
end

#vibe_last_change=(value) ⇒ Object



184
185
186
187
# File 'lib/kward/prompt_interface/editor/state.rb', line 184

def vibe_last_change=(value)
  @vibe_state.last_change = value
  sync_vibe_state
end

#vibe_last_findObject



189
190
191
# File 'lib/kward/prompt_interface/editor/state.rb', line 189

def vibe_last_find
  @vibe_state.last_find
end

#vibe_last_find=(value) ⇒ Object



193
194
195
196
# File 'lib/kward/prompt_interface/editor/state.rb', line 193

def vibe_last_find=(value)
  @vibe_state.last_find = value
  sync_vibe_state
end

#vibe_last_macroObject



252
253
254
# File 'lib/kward/prompt_interface/editor/state.rb', line 252

def vibe_last_macro
  @vibe_state.last_macro
end

#vibe_last_macro=(value) ⇒ Object



256
257
258
259
# File 'lib/kward/prompt_interface/editor/state.rb', line 256

def vibe_last_macro=(value)
  @vibe_state.last_macro = value
  sync_vibe_state
end

#vibe_last_visual_selectionObject



198
199
200
# File 'lib/kward/prompt_interface/editor/state.rb', line 198

def vibe_last_visual_selection
  @vibe_state.last_visual_selection
end

#vibe_last_visual_selection=(value) ⇒ Object



202
203
204
205
# File 'lib/kward/prompt_interface/editor/state.rb', line 202

def vibe_last_visual_selection=(value)
  @vibe_state.last_visual_selection = value
  sync_vibe_state
end

#vibe_macrosObject



234
235
236
# File 'lib/kward/prompt_interface/editor/state.rb', line 234

def vibe_macros
  @vibe_state.macros
end

#vibe_macros=(value) ⇒ Object



238
239
240
241
# File 'lib/kward/prompt_interface/editor/state.rb', line 238

def vibe_macros=(value)
  @vibe_state.macros = value
  sync_vibe_state
end

#vibe_marksObject



216
217
218
# File 'lib/kward/prompt_interface/editor/state.rb', line 216

def vibe_marks
  @vibe_state.marks
end

#vibe_marks=(value) ⇒ Object



220
221
222
223
# File 'lib/kward/prompt_interface/editor/state.rb', line 220

def vibe_marks=(value)
  @vibe_state.marks = value
  sync_vibe_state
end

#vibe_modeObject



153
154
155
# File 'lib/kward/prompt_interface/editor/state.rb', line 153

def vibe_mode
  @vibe_state.mode
end

#vibe_mode=(value) ⇒ Object



157
158
159
160
# File 'lib/kward/prompt_interface/editor/state.rb', line 157

def vibe_mode=(value)
  @vibe_state.mode = value
  sync_vibe_state
end

#vibe_pendingObject



162
163
164
# File 'lib/kward/prompt_interface/editor/state.rb', line 162

def vibe_pending
  @vibe_state.pending
end

#vibe_pending=(value) ⇒ Object



166
167
168
169
# File 'lib/kward/prompt_interface/editor/state.rb', line 166

def vibe_pending=(value)
  @vibe_state.pending = value
  sync_vibe_state
end

#vibe_recording_macroObject



243
244
245
# File 'lib/kward/prompt_interface/editor/state.rb', line 243

def vibe_recording_macro
  @vibe_state.recording_macro
end

#vibe_recording_macro=(value) ⇒ Object



247
248
249
250
# File 'lib/kward/prompt_interface/editor/state.rb', line 247

def vibe_recording_macro=(value)
  @vibe_state.recording_macro = value
  sync_vibe_state
end

#vibe_registersObject



225
226
227
# File 'lib/kward/prompt_interface/editor/state.rb', line 225

def vibe_registers
  @vibe_state.registers
end

#vibe_registers=(value) ⇒ Object



229
230
231
232
# File 'lib/kward/prompt_interface/editor/state.rb', line 229

def vibe_registers=(value)
  @vibe_state.registers = value
  sync_vibe_state
end

#vibe_visual_block_insertObject



207
208
209
# File 'lib/kward/prompt_interface/editor/state.rb', line 207

def vibe_visual_block_insert
  @vibe_state.visual_block_insert
end

#vibe_visual_block_insert=(value) ⇒ Object



211
212
213
214
# File 'lib/kward/prompt_interface/editor/state.rb', line 211

def vibe_visual_block_insert=(value)
  @vibe_state.visual_block_insert = value
  sync_vibe_state
end

#virtual?Boolean

Returns:

  • (Boolean)


315
316
317
# File 'lib/kward/prompt_interface/editor/state.rb', line 315

def virtual?
  @virtual == true
end

#visual_block_selection_rangesObject



718
719
720
721
722
723
724
725
726
727
728
729
730
# File 'lib/kward/prompt_interface/editor/state.rb', line 718

def visual_block_selection_ranges
  anchor_line, anchor_column = cursor_line_and_column_for(@selection_anchor)
  cursor_line, cursor_column = cursor_line_and_column
  start_line, end_line = [anchor_line, cursor_line].minmax
  start_column, end_column = [anchor_column, cursor_column].minmax
  (start_line..end_line).map do |line_index|
    line_start = line_start_offset(line_index)
    line_length = lines[line_index].to_s.length
    range_start = line_start + [start_column, line_length].min
    range_end = line_start + [end_column + 1, line_length].min
    [range_start, range_end]
  end
end

#visual_character_selection_rangeObject



704
705
706
707
# File 'lib/kward/prompt_interface/editor/state.rb', line 704

def visual_character_selection_range
  start_index, end_index = [@selection_anchor, @cursor].minmax
  [start_index, [end_index + 1, @buffer.length].min]
end

#visual_line_selection_rangeObject



709
710
711
712
713
714
715
716
# File 'lib/kward/prompt_interface/editor/state.rb', line 709

def visual_line_selection_range
  anchor_line, = cursor_line_and_column_for(@selection_anchor)
  cursor_line, = cursor_line_and_column
  start_line, end_line = [anchor_line, cursor_line].minmax
  start_index, = line_range(start_line)
  _, end_index = line_range(end_line)
  [start_index, end_index]
end

#word_range_at(offset) ⇒ Object



761
762
763
764
765
766
767
768
769
770
771
772
# File 'lib/kward/prompt_interface/editor/state.rb', line 761

def word_range_at(offset)
  return nil if @buffer.empty?

  index = [[offset.to_i, 0].max, @buffer.length - 1].min
  return nil if word_separator?(@buffer[index])

  start_index = index
  start_index -= 1 while start_index.positive? && !word_separator?(@buffer[start_index - 1])
  end_index = index + 1
  end_index += 1 while end_index < @buffer.length && !word_separator?(@buffer[end_index])
  [start_index, end_index]
end

#word_under_cursorObject



943
944
945
946
947
948
949
950
951
952
953
954
955
# File 'lib/kward/prompt_interface/editor/state.rb', line 943

def word_under_cursor
  return "" if @buffer.empty?

  index = [[@cursor, 0].max, @buffer.length - 1].min
  index -= 1 while index.positive? && word_separator?(@buffer[index])
  return "" if word_separator?(@buffer[index])

  start_index = index
  start_index -= 1 while start_index.positive? && !word_separator?(@buffer[start_index - 1])
  end_index = index + 1
  end_index += 1 while end_index < @buffer.length && !word_separator?(@buffer[end_index])
  @buffer[start_index...end_index].to_s
end

#yank_from_kill_ringObject



641
642
643
644
645
646
647
648
649
650
# File 'lib/kward/prompt_interface/editor/state.rb', line 641

def yank_from_kill_ring
  text = @kill_state.first_yank
  return false unless text

  start_index = @cursor
  insert(text)
  @kill_state.record_yank(start_index, @cursor)
  sync_kill_state
  true
end

#yank_kill_bufferObject



630
631
632
# File 'lib/kward/prompt_interface/editor/state.rb', line 630

def yank_kill_buffer
  replace_selections(@kill_buffer.to_s) unless @kill_buffer.to_s.empty?
end

#yank_popObject



652
653
654
655
656
657
658
659
660
661
662
663
# File 'lib/kward/prompt_interface/editor/state.rb', line 652

def yank_pop
  yank = @kill_state.next_yank_pop
  return false unless yank

  start_index, end_index = yank[:range]
  text = yank[:text]
  replace_range(start_index, end_index, text)
  @cursor = start_index + text.length
  @kill_state.record_yank_pop(start_index, @cursor)
  sync_kill_state
  true
end