Class: Rvim::Editor

Inherits:
Reline::LineEditor
  • Object
show all
Defined in:
lib/rvim/editor.rb

Defined Under Namespace

Classes: RedirFile, RedirRegister

Constant Summary collapse

MESSAGES_MAX =
200
EX_HISTORY_MAX =
100
SOURCE_MAX_DEPTH =
10
HELP_PATH =
File.expand_path(File.join(__dir__, 'help', 'help.txt')).freeze
MAXMAPDEPTH =
1000
JUMP_LIST_LIMIT =
100
MOUSE_SGR_RE =
/\A\e\[<(\d+);(\d+);(\d+)([Mm])\z/
INCLUSIVE_MOTION_CHARS =
%w[$ e E f F t T].freeze
RVIMRC_PATH =
'~/.rvimrc'

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(config) ⇒ Editor

Returns a new instance of Editor.



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
# File 'lib/rvim/editor.rb', line 24

def initialize(config)
  super
  @config.editing_mode = :vi_command
  multiline_on
  # Never terminate the multiline buffer — Enter always inserts a newline.
  self.confirm_multiline_termination_proc = ->(_buffer) { false }
  @filepath = nil
  @modified = false
  @quit = false
  @prompt_mode = nil
  @prompt_buffer = +''
  @status_message = nil
  @messages = []
  @redir_sink = nil
  @visual_mode = nil
  @visual_anchor = nil
  @last_visual = nil
  @rvim_text_object_pending = nil
  @rvim_visual_textobj_pending = nil
  @search_pattern = nil
  @search_direction = :forward
  @search_matches = []
  @change_keys = []
  @last_change_keys = []
  @replaying = false
  @macros = {}
  @recording_macro = nil
  @macro_keys = []
  @last_macro_register = nil
  @registers = Rvim::Registers.new
  @pending_register = nil
  @last_register_op = nil
  @marks = Rvim::Marks.new
  @global_marks = Rvim::GlobalMarks.new
  @last_change_pos = nil
  @last_insert_pos = nil
  @last_yank_range = nil
  @jump_list = []
  @jump_index = 0
  @buffers = {}
  @buffer_order = []
  @next_buffer_id = 1
  @current_buffer = nil
  @windows = []
  @current_window = nil
  @split_orientation = nil
  @tabs = []
  @current_tab_index = 0
  @settings = Rvim::Settings.new
  @settings.editor = self
  @ex_history = []
  @history_cursor = nil
  @history_pending = nil
  @keymap = Rvim::Keymap.new
  @abbreviations = Rvim::Abbreviations.new
  @user_commands = {}
  @block_insert_state = nil
  @map_pending_keys = +''
  @map_recursion_depth = 0
  @map_noremap_active = false
  @let_vars = {}
  @folds = Rvim::Folds.new
  @completion_active = false
  @completion_candidates = []
  @completion_index = 0
  @completion_base = +''
  @completion_base_byte = 0
  @completion_line_index = 0
  @completion_popup = nil
  @cmdline_popup = nil
  @cmdline_completion_context = nil
  @digraph_pending = false
  @digraph_chars = +''
  @completion_chain_pending = false
  @tag_stack = []
  @tag_matches = []
  @tag_match_index = 0
  @last_bang_cmd = nil
  @arg_list = []
  @arg_index = 0
  @alternate_filepath = nil
  @rvim_pending_format_op = false
  @rvim_pending_filter_op = false
  @rvim_pending_equal_op = false
  @confirm_question = nil
  @confirm_options = nil
  @confirm_callback = nil
  @autocommands = Rvim::Autocommands.new
  @quickfix = Rvim::Quickfix.new
  install_key_bindings
end

Instance Attribute Details

#abbreviationsObject (readonly)

Returns the value of attribute abbreviations.



132
133
134
# File 'lib/rvim/editor.rb', line 132

def abbreviations
  @abbreviations
end

#alternate_filepathObject

Returns the value of attribute alternate_filepath.



668
669
670
# File 'lib/rvim/editor.rb', line 668

def alternate_filepath
  @alternate_filepath
end

#arg_indexObject

Returns the value of attribute arg_index.



670
671
672
# File 'lib/rvim/editor.rb', line 670

def arg_index
  @arg_index
end

#arg_listObject (readonly)

Returns the value of attribute arg_list.



669
670
671
# File 'lib/rvim/editor.rb', line 669

def arg_list
  @arg_list
end

#autocommandsObject (readonly)

Returns the value of attribute autocommands.



116
117
118
# File 'lib/rvim/editor.rb', line 116

def autocommands
  @autocommands
end

#block_insert_stateObject (readonly)

Returns the value of attribute block_insert_state.



136
137
138
# File 'lib/rvim/editor.rb', line 136

def block_insert_state
  @block_insert_state
end

#buffer_orderObject (readonly)

Returns the value of attribute buffer_order.



1611
1612
1613
# File 'lib/rvim/editor.rb', line 1611

def buffer_order
  @buffer_order
end

#buffersObject (readonly)

Returns the value of attribute buffers.



1611
1612
1613
# File 'lib/rvim/editor.rb', line 1611

def buffers
  @buffers
end

#cmdline_popupObject (readonly)

Returns the value of attribute cmdline_popup.



2863
2864
2865
# File 'lib/rvim/editor.rb', line 2863

def cmdline_popup
  @cmdline_popup
end

#completion_activeObject (readonly)

Returns the value of attribute completion_active.



1098
1099
1100
# File 'lib/rvim/editor.rb', line 1098

def completion_active
  @completion_active
end

#completion_base_byteObject (readonly)

Returns the value of attribute completion_base_byte.



1091
1092
1093
# File 'lib/rvim/editor.rb', line 1091

def completion_base_byte
  @completion_base_byte
end

#completion_candidatesObject (readonly)

Returns the value of attribute completion_candidates.



1098
1099
1100
# File 'lib/rvim/editor.rb', line 1098

def completion_candidates
  @completion_candidates
end

#completion_indexObject (readonly)

Returns the value of attribute completion_index.



1098
1099
1100
# File 'lib/rvim/editor.rb', line 1098

def completion_index
  @completion_index
end

#completion_line_indexObject (readonly)

Returns the value of attribute completion_line_index.



1091
1092
1093
# File 'lib/rvim/editor.rb', line 1091

def completion_line_index
  @completion_line_index
end

#completion_popupObject (readonly)

Returns the value of attribute completion_popup.



1091
1092
1093
# File 'lib/rvim/editor.rb', line 1091

def completion_popup
  @completion_popup
end

#confirm_optionsObject (readonly)

Returns the value of attribute confirm_options.



3323
3324
3325
# File 'lib/rvim/editor.rb', line 3323

def confirm_options
  @confirm_options
end

#confirm_questionObject (readonly)

Returns the value of attribute confirm_question.



3323
3324
3325
# File 'lib/rvim/editor.rb', line 3323

def confirm_question
  @confirm_question
end

#current_bufferObject (readonly)

Returns the value of attribute current_buffer.



1611
1612
1613
# File 'lib/rvim/editor.rb', line 1611

def current_buffer
  @current_buffer
end

#current_tab_indexObject (readonly)

Returns the value of attribute current_tab_index.



499
500
501
# File 'lib/rvim/editor.rb', line 499

def current_tab_index
  @current_tab_index
end

#current_windowObject (readonly)

Returns the value of attribute current_window.



533
534
535
# File 'lib/rvim/editor.rb', line 533

def current_window
  @current_window
end

#ex_historyObject (readonly)

Returns the value of attribute ex_history.



128
129
130
# File 'lib/rvim/editor.rb', line 128

def ex_history
  @ex_history
end

#filepathObject (readonly)

Returns the value of attribute filepath.



8
9
10
# File 'lib/rvim/editor.rb', line 8

def filepath
  @filepath
end

#foldsObject (readonly)

Returns the value of attribute folds.



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

def folds
  @folds
end

#jump_indexObject (readonly)

Returns the value of attribute jump_index.



2298
2299
2300
# File 'lib/rvim/editor.rb', line 2298

def jump_index
  @jump_index
end

#jump_listObject (readonly)

Returns the value of attribute jump_list.



2298
2299
2300
# File 'lib/rvim/editor.rb', line 2298

def jump_list
  @jump_list
end

#keymapObject (readonly)

Returns the value of attribute keymap.



130
131
132
# File 'lib/rvim/editor.rb', line 130

def keymap
  @keymap
end

#last_bang_cmdObject

Returns the value of attribute last_bang_cmd.



668
669
670
# File 'lib/rvim/editor.rb', line 668

def last_bang_cmd
  @last_bang_cmd
end

#last_change_keysObject (readonly)

Returns the value of attribute last_change_keys.



2177
2178
2179
# File 'lib/rvim/editor.rb', line 2177

def last_change_keys
  @last_change_keys
end

#last_change_posObject (readonly)

Returns the value of attribute last_change_pos.



2307
2308
2309
# File 'lib/rvim/editor.rb', line 2307

def last_change_pos
  @last_change_pos
end

#last_insert_posObject (readonly)

Returns the value of attribute last_insert_pos.



2307
2308
2309
# File 'lib/rvim/editor.rb', line 2307

def last_insert_pos
  @last_insert_pos
end

#let_varsObject (readonly)

Returns the value of attribute let_vars.



120
121
122
# File 'lib/rvim/editor.rb', line 120

def let_vars
  @let_vars
end

#list_viewObject (readonly)

Returns the value of attribute list_view.



533
534
535
# File 'lib/rvim/editor.rb', line 533

def list_view
  @list_view
end

#messagesObject (readonly)

Returns the value of attribute messages.



10
11
12
# File 'lib/rvim/editor.rb', line 10

def messages
  @messages
end

#modifiedObject

Returns the value of attribute modified.



9
10
11
# File 'lib/rvim/editor.rb', line 9

def modified
  @modified
end

#prompt_bufferObject (readonly)

Returns the value of attribute prompt_buffer.



8
9
10
# File 'lib/rvim/editor.rb', line 8

def prompt_buffer
  @prompt_buffer
end

#prompt_modeObject (readonly)

Returns the value of attribute prompt_mode.



8
9
10
# File 'lib/rvim/editor.rb', line 8

def prompt_mode
  @prompt_mode
end

#quickfixObject (readonly)

Returns the value of attribute quickfix.



116
117
118
# File 'lib/rvim/editor.rb', line 116

def quickfix
  @quickfix
end

#recording_macroObject (readonly)

Returns the value of attribute recording_macro.



2177
2178
2179
# File 'lib/rvim/editor.rb', line 2177

def recording_macro
  @recording_macro
end

#replace_modeObject (readonly)

Returns the value of attribute replace_mode.



2140
2141
2142
# File 'lib/rvim/editor.rb', line 2140

def replace_mode
  @replace_mode
end

#search_directionObject (readonly)

Returns the value of attribute search_direction.



157
158
159
# File 'lib/rvim/editor.rb', line 157

def search_direction
  @search_direction
end

#search_matchesObject (readonly)

Returns the value of attribute search_matches.



157
158
159
# File 'lib/rvim/editor.rb', line 157

def search_matches
  @search_matches
end

#search_patternObject (readonly)

Returns the value of attribute search_pattern.



157
158
159
# File 'lib/rvim/editor.rb', line 157

def search_pattern
  @search_pattern
end

#settingsObject (readonly)

Returns the value of attribute settings.



155
156
157
# File 'lib/rvim/editor.rb', line 155

def settings
  @settings
end

#split_orientationObject (readonly)

Returns the value of attribute split_orientation.



533
534
535
# File 'lib/rvim/editor.rb', line 533

def split_orientation
  @split_orientation
end

#status_messageObject

Returns the value of attribute status_message.



10
11
12
# File 'lib/rvim/editor.rb', line 10

def status_message
  @status_message
end

#tabsObject (readonly)

Returns the value of attribute tabs.



499
500
501
# File 'lib/rvim/editor.rb', line 499

def tabs
  @tabs
end

#tag_match_indexObject (readonly)

Returns the value of attribute tag_match_index.



667
668
669
# File 'lib/rvim/editor.rb', line 667

def tag_match_index
  @tag_match_index
end

#tag_matchesObject (readonly)

Returns the value of attribute tag_matches.



667
668
669
# File 'lib/rvim/editor.rb', line 667

def tag_matches
  @tag_matches
end

#tag_stackObject (readonly)

Returns the value of attribute tag_stack.



667
668
669
# File 'lib/rvim/editor.rb', line 667

def tag_stack
  @tag_stack
end

#undo_timestampsObject (readonly)

Returns the value of attribute undo_timestamps.



371
372
373
# File 'lib/rvim/editor.rb', line 371

def undo_timestamps
  @undo_timestamps
end

#user_commandsObject (readonly)

Returns the value of attribute user_commands.



134
135
136
# File 'lib/rvim/editor.rb', line 134

def user_commands
  @user_commands
end

#visual_anchorObject (readonly)

Returns the value of attribute visual_anchor.



8
9
10
# File 'lib/rvim/editor.rb', line 8

def visual_anchor
  @visual_anchor
end

#visual_modeObject (readonly)

Returns the value of attribute visual_mode.



8
9
10
# File 'lib/rvim/editor.rb', line 8

def visual_mode
  @visual_mode
end

#windowsObject (readonly)

Returns the value of attribute windows.



533
534
535
# File 'lib/rvim/editor.rb', line 533

def windows
  @windows
end

Class Method Details

.ensure_user_runtimepath(editor) ⇒ Object

NeoVim auto-prepends $XDG_CONFIG_HOME/nvim and its after/ to runtimepath so plugins authored as ‘~/.config/nvim/lua/<mod>.lua` are reachable via require(’<mod>‘). Mirror that for rvim.



4053
4054
4055
4056
4057
4058
4059
4060
4061
4062
4063
4064
4065
4066
4067
# File 'lib/rvim/editor.rb', line 4053

def self.ensure_user_runtimepath(editor)
  cfg = user_config_dir
  after = File.join(cfg, 'after')
  rtp = editor.settings.get(:runtimepath).to_s.split(',')
  changed = false
  unless rtp.include?(cfg)
    rtp.unshift(cfg)
    changed = true
  end
  unless rtp.include?(after)
    rtp.push(after)
    changed = true
  end
  editor.settings.set(:runtimepath, rtp.join(',')) if changed
end

.init_lua_pathObject



4038
4039
4040
4041
4042
# File 'lib/rvim/editor.rb', line 4038

def self.init_lua_path
  base = ENV['XDG_CONFIG_HOME']
  base = File.expand_path('~/.config') if base.nil? || base.empty?
  File.join(base, 'rvim', 'init.lua')
end

.init_vim_pathObject



4032
4033
4034
4035
4036
# File 'lib/rvim/editor.rb', line 4032

def self.init_vim_path
  base = ENV['XDG_CONFIG_HOME']
  base = File.expand_path('~/.config') if base.nil? || base.empty?
  File.join(base, 'rvim', 'init.vim')
end

.start(*filepaths, norc: false) ⇒ Object



4069
4070
4071
4072
4073
4074
4075
4076
4077
4078
4079
4080
4081
4082
4083
4084
4085
4086
4087
4088
4089
4090
4091
4092
4093
4094
4095
4096
4097
4098
4099
4100
4101
4102
4103
4104
4105
4106
4107
4108
4109
4110
4111
4112
4113
4114
4115
4116
4117
4118
4119
4120
4121
4122
4123
# File 'lib/rvim/editor.rb', line 4069

def self.start(*filepaths, norc: false)
  editor = new(Reline.core.config)
  filepaths = filepaths.flatten.compact
  editor.set_arg_list(filepaths)
  if filepaths.empty?
    # vim's [No Name] buffer: create an empty unnamed buffer and swap to it
    # so the screen has a current window/buffer to render into.
    editor.open(nil)
  else
    filepaths.each { |path| editor.open(path) }
  end
  ensure_user_runtimepath(editor) unless norc
  unless norc
    [File.expand_path(RVIMRC_PATH), init_vim_path, init_lua_path].each do |rc|
      editor.source(rc) if File.exist?(rc)
    end
  end
  editor.autocommands.fire(:vimenter, '*', editor)
  # Land on the first file the user passed, mirroring vim's `vim a b c`
  # behavior of opening all into the buffer list with the first active.
  if (first = filepaths.first) && (buf = editor.buffers.values.find { |b| b.filepath == first })
    editor.swap_to_buffer(buf)
  end
  screen = Rvim::Screen.new(editor)
  editor.screen = screen

  Reline.core.line_editor = editor
  otio = nil
  begin
    otio = Reline::IOGate.prep
    screen.setup
    editor.set_signal_handlers
    Reline::IOGate.with_raw_input do
      loop do
        screen.render
        begin
          Reline.core.send(:read_io, Reline.core.config.keyseq_timeout) do |inputs|
            inputs.each do |key|
              editor.set_pasting_state(Reline::IOGate.in_pasting?)
              editor.update(key)
            end
          end
        rescue Interrupt
          editor.quit!
        end
        break if editor.quit?
      end
    end
  ensure
    editor&.finalize
    screen&.teardown
    Reline::IOGate.deprep(otio) if otio
  end
  editor&.exit_code || 0
end

.user_config_dirObject



4044
4045
4046
4047
4048
# File 'lib/rvim/editor.rb', line 4044

def self.user_config_dir
  base = ENV['XDG_CONFIG_HOME']
  base = File.expand_path('~/.config') if base.nil? || base.empty?
  File.join(base, 'rvim')
end

Instance Method Details

#add_arg(path) ⇒ Object



751
752
753
# File 'lib/rvim/editor.rb', line 751

def add_arg(path)
  @arg_list << path.to_s
end

#add_buffer(path) ⇒ Object



288
289
290
# File 'lib/rvim/editor.rb', line 288

def add_buffer(path)
  find_or_create_buffer(path)
end

#apply_equal_to_lines(start_line, end_line) ⇒ Object



3307
3308
3309
3310
3311
3312
3313
3314
3315
3316
3317
3318
3319
3320
3321
# File 'lib/rvim/editor.rb', line 3307

def apply_equal_to_lines(start_line, end_line)
  lo = start_line.clamp(0, [@buffer_of_lines.size - 1, 0].max)
  hi = end_line.clamp(0, [@buffer_of_lines.size - 1, 0].max)
  lines = @buffer_of_lines[lo..hi].map(&:to_s)
  prg = @settings.get(:equalprg).to_s
  return if prg.empty?

  result = Rvim::Filter.run(prg, input: lines.join("\n"), shell: @settings.get(:shell), shellcmdflag: @settings.get(:shellcmdflag))
  if result.success?
    out_lines = result.stdout.chomp("\n").split("\n", -1)
    replace_line_range(lo, hi, out_lines)
  else
    @status_message = "equalprg: #{result.stderr.lines.first&.chomp || 'failed'}"
  end
end

#apply_fold_levelObject



1373
1374
1375
1376
1377
1378
# File 'lib/rvim/editor.rb', line 1373

def apply_fold_level
  level = @settings.get(:foldlevel).to_i
  @folds.each do |f|
    f.closed = (f.level || 1) > level
  end
end

#apply_format_to_lines(start_line, end_line) ⇒ Object



3465
3466
3467
3468
3469
3470
3471
3472
3473
3474
3475
3476
3477
3478
3479
3480
3481
3482
3483
3484
3485
3486
# File 'lib/rvim/editor.rb', line 3465

def apply_format_to_lines(start_line, end_line)
  width = @settings.get(:textwidth).to_i
  width = 78 if width <= 0

  lo = start_line.clamp(0, [@buffer_of_lines.size - 1, 0].max)
  hi = end_line.clamp(0, [@buffer_of_lines.size - 1, 0].max)
  lines = @buffer_of_lines[lo..hi].map(&:to_s)

  formatprg = @settings.get(:formatprg).to_s
  reformatted = if formatprg.empty?
                  Rvim::Reformat.wrap(lines, width)
                else
                  result = Rvim::Filter.run(formatprg, input: lines.join("\n"), shell: @settings.get(:shell), shellcmdflag: @settings.get(:shellcmdflag))
                  if result.success?
                    result.stdout.chomp("\n").split("\n", -1)
                  else
                    @status_message = "formatprg: #{result.stderr.lines.first&.chomp || 'failed'}"
                    return
                  end
                end
  replace_line_range(lo, hi, reformatted)
end

#autowrite_if_modifiedObject



1635
1636
1637
1638
1639
1640
1641
# File 'lib/rvim/editor.rb', line 1635

def autowrite_if_modified
  return unless @settings.get(:autowrite)
  return unless @current_buffer && @modified
  return unless @filepath

  save
end

#backup_path(target) ⇒ Object



1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
# File 'lib/rvim/editor.rb', line 1710

def backup_path(target)
  ext = @settings.get(:backupext).to_s
  ext = '~' if ext.empty?
  dir = @settings.get(:backupdir).to_s
  dir = '.' if dir.empty?

  basename = File.basename(target.to_s) + ext
  if dir == '.'
    # Vim's '.' means alongside the file
    File.join(File.dirname(target.to_s), basename)
  else
    FileUtils.mkdir_p(File.expand_path(dir))
    File.join(File.expand_path(dir), basename)
  end
end

#buffer_of_linesObject



1776
1777
1778
# File 'lib/rvim/editor.rb', line 1776

def buffer_of_lines
  @buffer_of_lines
end

#byte_pointerObject



1784
1785
1786
# File 'lib/rvim/editor.rb', line 1784

def byte_pointer
  @byte_pointer
end

#cancel_confirm_promptObject



3346
3347
3348
3349
3350
# File 'lib/rvim/editor.rb', line 3346

def cancel_confirm_prompt
  @confirm_question = nil
  @confirm_options = nil
  @confirm_callback = nil
end

#close_current_windowObject



1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
# File 'lib/rvim/editor.rb', line 1555

def close_current_window
  return if @windows.size < 2

  victim = @current_window
  idx = @windows.index(victim)
  @windows.delete(victim)
  @current_window = @windows[idx] || @windows.last
  @split_orientation = nil if @windows.size == 1
  activate_window(@current_window) if @current_window
  equalize_windows if @settings.get(:equalalways)
end

#close_help_bufferObject



423
424
425
426
427
428
# File 'lib/rvim/editor.rb', line 423

def close_help_buffer
  buf = @buffers.values.find { |b| b.filepath == HELP_PATH }
  return unless buf

  remove_buffer(buf) if respond_to?(:remove_buffer)
end

#close_redirObject



366
367
368
369
# File 'lib/rvim/editor.rb', line 366

def close_redir
  @redir_sink&.close
  @redir_sink = nil
end

#command_bufferObject



3081
3082
3083
# File 'lib/rvim/editor.rb', line 3081

def command_buffer
  @prompt_buffer
end

#command_modeObject

Backcompat readers used by Screen for the prompt-mode rendering.



3077
3078
3079
# File 'lib/rvim/editor.rb', line 3077

def command_mode
  @prompt_mode == :ex
end

#configObject



2594
2595
2596
# File 'lib/rvim/editor.rb', line 2594

def config
  @config
end

#confirm_prompt(question, options, &block) ⇒ Object



3325
3326
3327
3328
3329
# File 'lib/rvim/editor.rb', line 3325

def confirm_prompt(question, options, &block)
  @confirm_question = question.to_s
  @confirm_options = Array(options).map { |o| o.to_s.downcase }
  @confirm_callback = block
end

#consume_pending_registerObject



4003
4004
4005
# File 'lib/rvim/editor.rb', line 4003

def consume_pending_register
  @pending_register = nil
end

#create_fold_at_cursor(line_count) ⇒ Object



1393
1394
1395
1396
1397
1398
1399
# File 'lib/rvim/editor.rb', line 1393

def create_fold_at_cursor(line_count)
  start_line = @line_index
  end_line = [start_line + line_count - 1, @buffer_of_lines.size - 1].min
  return if end_line < start_line

  @folds.add(start_line, end_line, closed: true)
end

#create_fold_over(start_line, end_line) ⇒ Object



1401
1402
1403
1404
1405
# File 'lib/rvim/editor.rb', line 1401

def create_fold_over(start_line, end_line)
  lo = [start_line, end_line].min.clamp(0, @buffer_of_lines.size - 1)
  hi = [start_line, end_line].max.clamp(0, @buffer_of_lines.size - 1)
  @folds.add(lo, hi, closed: true)
end

#current_tabObject



501
502
503
# File 'lib/rvim/editor.rb', line 501

def current_tab
  @tabs[@current_tab_index]
end

#cycle_buffer(direction) ⇒ Object



1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
# File 'lib/rvim/editor.rb', line 1621

def cycle_buffer(direction)
  return if @buffer_order.size <= 1

  if @modified && !@settings.get(:hidden) && !@settings.get(:autowrite)
    @status_message = 'E37: No write since last change (add ! to override)'
    return
  end
  autowrite_if_modified

  idx = @buffer_order.index(@current_buffer.id) || 0
  target_id = @buffer_order[(idx + direction) % @buffer_order.size]
  swap_to_buffer(@buffers[target_id])
end

#delete_current_buffer(force: false) ⇒ Object



1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
# File 'lib/rvim/editor.rb', line 1643

def delete_current_buffer(force: false)
  return unless @current_buffer

  save_current_buffer
  if @current_buffer.modified && !force
    @status_message = 'E89: No write since last change (add ! to override)'
    return
  end

  victim_id = @current_buffer.id
  idx = @buffer_order.index(victim_id)
  @buffer_order.delete(victim_id)
  @buffers.delete(victim_id)

  if @buffer_order.empty?
    # Open an empty scratch buffer so we always have something to display.
    @current_buffer = nil
    scratch = Rvim::Buffer.new(@next_buffer_id, nil, encoding: encoding)
    @next_buffer_id += 1
    @buffers[scratch.id] = scratch
    @buffer_order << scratch.id
    swap_to_buffer(scratch)
  else
    fallback_id = @buffer_order[idx - 1] || @buffer_order.first
    @current_buffer = nil
    swap_to_buffer(@buffers[fallback_id])
  end
end

#diff_buffersObject



1320
1321
1322
# File 'lib/rvim/editor.rb', line 1320

def diff_buffers
  @buffers.values.select { |b| b.diff_active }
end

#diff_jump(direction) ⇒ Object



1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
# File 'lib/rvim/editor.rb', line 1335

def diff_jump(direction)
  buf = @current_buffer
  return unless buf && buf.diff_status

  starts = Rvim::Diff.hunk_starts(buf.diff_status)
  return if starts.empty?

  target = if direction == :next
             starts.find { |s| s > @line_index }
           else
             starts.reverse.find { |s| s < @line_index }
           end
  return unless target

  push_jump
  @line_index = target
  @byte_pointer = 0
end

#dismiss_listObject



540
541
542
543
# File 'lib/rvim/editor.rb', line 540

def dismiss_list
  @list_view = nil
  @prompt_mode = nil
end

#display_line_motion(direction) ⇒ Object



3497
3498
3499
3500
3501
3502
3503
3504
3505
3506
3507
3508
3509
3510
3511
3512
3513
3514
3515
3516
3517
# File 'lib/rvim/editor.rb', line 3497

def display_line_motion(direction)
  unless @settings.get(:wrap) && @screen && @current_window
    plain_line_step(direction)
    return
  end

  width = @screen.content_width_for(@current_window)
  target = Rvim::DisplayMotion.next_position(
    @buffer_of_lines,
    @line_index,
    @byte_pointer,
    width,
    direction,
    splitter: ->(line, w) { @screen.split_segments_public(line, w) },
  )
  if target
    @line_index, @byte_pointer = target
  else
    plain_line_step(direction)
  end
end

#editing_mode_labelObject



1788
1789
1790
# File 'lib/rvim/editor.rb', line 1788

def editing_mode_label
  @config.instance_variable_get(:@editing_mode_label)
end

#equalize_windowsObject



621
622
623
624
625
626
627
628
629
630
631
632
633
634
# File 'lib/rvim/editor.rb', line 621

def equalize_windows
  ead = @settings.get(:eadirection).to_s
  @windows.each do |w|
    case ead
    when 'hor'
      w.extra_rows = 0
    when 'ver'
      w.extra_cols = 0
    else
      w.extra_rows = 0
      w.extra_cols = 0
    end
  end
end

#exit_codeObject



1772
1773
1774
# File 'lib/rvim/editor.rb', line 1772

def exit_code
  @exit_code || 0
end

#expand_abbreviation_in_buffer(buf, mode) ⇒ Object



2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
# File 'lib/rvim/editor.rb', line 2117

def expand_abbreviation_in_buffer(buf, mode)
  return buf if @abbreviations.nil? || @abbreviations.empty?(mode)

  detection = @abbreviations.detect(buf, buf.bytesize, mode)
  return buf unless detection

  word_start, word_end, entry = detection
  head = buf.byteslice(0, word_start) || +''
  tail = buf.byteslice(word_end, buf.bytesize - word_end) || +''
  head + entry.rhs + tail
end

#focus_next_windowObject



1548
1549
1550
1551
1552
1553
# File 'lib/rvim/editor.rb', line 1548

def focus_next_window
  return if @windows.size < 2

  idx = @windows.index(@current_window) || 0
  activate_window(@windows[(idx + 1) % @windows.size])
end

#focus_window(direction) ⇒ Object



1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
# File 'lib/rvim/editor.rb', line 1535

def focus_window(direction)
  return if @windows.size < 2

  idx = @windows.index(@current_window) || 0
  target_idx = case direction
               when :down, :right then idx + 1
               when :up, :left    then idx - 1
               end
  return if target_idx.nil? || target_idx < 0 || target_idx >= @windows.size

  activate_window(@windows[target_idx])
end

#global_mark(name) ⇒ Object



2199
2200
2201
2202
2203
2204
2205
# File 'lib/rvim/editor.rb', line 2199

def global_mark(name)
  entry = @global_marks.get(name)
  return nil unless entry

  buffer_id, line, col = entry
  [buffer_id, line, col]
end

#handle_signalObject



4022
4023
4024
4025
4026
4027
4028
# File 'lib/rvim/editor.rb', line 4022

def handle_signal
  if @interrupted
    @interrupted = false
    @quit = true
    raise Interrupt
  end
end

#insert_at_cursor(s) ⇒ Object



876
877
878
879
880
881
882
883
# File 'lib/rvim/editor.rb', line 876

def insert_at_cursor(s)
  line = @buffer_of_lines[@line_index] || +''
  head = line.byteslice(0, @byte_pointer) || +''
  tail = line.byteslice(@byte_pointer, line.bytesize - @byte_pointer) || +''
  @buffer_of_lines[@line_index] = String.new(head + s + tail, encoding: encoding)
  @byte_pointer += s.bytesize
  @modified = true
end

#insert_lines_after(line_index, new_lines) ⇒ Object



1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
# File 'lib/rvim/editor.rb', line 1698

def insert_lines_after(line_index, new_lines)
  return if new_lines.empty?

  idx = line_index.clamp(-1, @buffer_of_lines.size - 1)
  additions = new_lines.map { |l| String.new(l, encoding: encoding) }
  @buffer_of_lines.insert(idx + 1, *additions)
  @line_index = idx + 1
  @byte_pointer = 0
  @modified = true
  @folds&.shift_after(idx, additions.size)
end

#jump_to_fold(direction) ⇒ Object



1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
# File 'lib/rvim/editor.rb', line 1304

def jump_to_fold(direction)
  collected = []
  @folds.each { |f| collected << f }
  sorted = collected.sort_by(&:start_line)
  target = if direction == :next
             sorted.find { |f| f.start_line > @line_index }
           else
             sorted.reverse.find { |f| f.start_line < @line_index }
           end
  return unless target

  push_jump
  @line_index = target.start_line
  @byte_pointer = 0
end

#jump_to_misspelling(direction) ⇒ Object



1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
# File 'lib/rvim/editor.rb', line 1122

def jump_to_misspelling(direction)
  return unless @settings.get(:spell)

  bp = @byte_pointer
  li = @line_index
  lines = @buffer_of_lines
  total = lines.size

  forward = direction == :next
  step = forward ? +1 : -1
  cur_li = li
  cur_bp = bp

  visited = 0
  while visited < total
    line = lines[cur_li] || ''
    positions = scan_word_positions(line)
    positions.sort_by! { |s, _| s }
    positions.reverse! unless forward
    positions.each do |start_byte, end_byte|
      if cur_li == li
        next if forward && start_byte <= bp
        next if !forward && start_byte >= bp
      end

      word = line.byteslice(start_byte, end_byte - start_byte)
      if Rvim::Spell.misspelled?(word)
        push_jump
        @line_index = cur_li
        @byte_pointer = start_byte
        return
      end
    end

    cur_li += step
    break if cur_li.negative? || cur_li >= total

    visited += 1
  end
end

#last_yank_range_endObject



2313
2314
2315
# File 'lib/rvim/editor.rb', line 2313

def last_yank_range_end
  @last_yank_range&.dig(:end)
end

#last_yank_range_startObject



2309
2310
2311
# File 'lib/rvim/editor.rb', line 2309

def last_yank_range_start
  @last_yank_range&.dig(:start)
end

#line_indexObject



1780
1781
1782
# File 'lib/rvim/editor.rb', line 1780

def line_index
  @line_index
end

#list_rowsObject



545
546
547
548
# File 'lib/rvim/editor.rb', line 545

def list_rows
  base = @screen ? @screen.list_overlay_rows : 6
  [base, 4].max
end

#load_filetype_scripts(ft) ⇒ Object



442
443
444
445
446
447
448
449
450
451
452
453
454
455
# File 'lib/rvim/editor.rb', line 442

def load_filetype_scripts(ft)
  return unless ft

  ft = ft.to_s
  rtp = @settings.get(:runtimepath).to_s.split(',').map { |p| File.expand_path(p.strip) }.reject(&:empty?)
  return if rtp.empty?

  %w[ftplugin indent syntax].each do |kind|
    rtp.each do |dir|
      path = File.join(dir, kind, "#{ft}.vim")
      source(path) if File.file?(path)
    end
  end
end

#luaObject



138
139
140
# File 'lib/rvim/editor.rb', line 138

def lua
  @lua ||= Rvim::Lua::Runtime.new(self)
end

#lua_loader_refreshObject

Re-sync Lua’s package.path with the current &runtimepath. Called by Settings#set when the user (or a plugin) mutates :runtimepath.



149
150
151
152
153
# File 'lib/rvim/editor.rb', line 149

def lua_loader_refresh
  return unless @lua && @lua.available? && @lua.state

  Rvim::Lua::Loader.refresh(@lua.state, self)
end

#mapleaderObject



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

def mapleader
  @let_vars['mapleader'] || '\\'
end

#move_cursor_to(line, byte) ⇒ Object



2650
2651
2652
2653
2654
# File 'lib/rvim/editor.rb', line 2650

def move_cursor_to(line, byte)
  @line_index = line.clamp(0, [@buffer_of_lines.size - 1, 0].max)
  target = @buffer_of_lines[@line_index] || ''
  @byte_pointer = byte.clamp(0, target.bytesize)
end

#next_bufferObject



1613
1614
1615
# File 'lib/rvim/editor.rb', line 1613

def next_buffer
  cycle_buffer(+1)
end

#open(path) ⇒ Object



221
222
223
224
225
226
227
228
229
230
231
232
# File 'lib/rvim/editor.rb', line 221

def open(path)
  is_new = path && !@buffers.values.find { |b| b.filepath == path }
  buf = find_or_create_buffer(path)
  swap_to_buffer(buf)
  if is_new
    Rvim::Modeline.apply(self, buf) if path
    load_persistent_undo(path) if path && @settings.get(:undofile)
    @autocommands&.fire(:bufread, path.to_s, self)
    ft = Rvim::Syntax.detect_language(path)
    @autocommands&.fire(:filetype, ft.to_s, self) if ft
  end
end

#open_help_buffer(topic = nil) ⇒ Object



413
414
415
416
417
418
419
420
421
# File 'lib/rvim/editor.rb', line 413

def open_help_buffer(topic = nil)
  unless File.file?(HELP_PATH)
    @status_message = 'E149: No help available'
    return
  end

  open(HELP_PATH)
  jump_to_help_tag(topic) if topic
end

#open_redir_file(path, mode) ⇒ Object



354
355
356
357
358
359
# File 'lib/rvim/editor.rb', line 354

def open_redir_file(path, mode)
  close_redir
  @redir_sink = RedirFile.new(path, mode)
rescue => e
  @status_message = "E484: Can't open file #{path}: #{e.message}"
end

#open_redir_register(name) ⇒ Object



361
362
363
364
# File 'lib/rvim/editor.rb', line 361

def open_redir_register(name)
  close_redir
  @redir_sink = RedirRegister.new(self, name)
end

#open_terminal_buffer(name, lines, status: 0) ⇒ Object



292
293
294
295
296
297
298
299
300
301
302
303
# File 'lib/rvim/editor.rb', line 292

def open_terminal_buffer(name, lines, status: 0)
  buf = Rvim::Buffer.new(@next_buffer_id, name, encoding: encoding)
  @next_buffer_id += 1
  buf.lines = lines.map { |l| l.dup.force_encoding(encoding) }
  buf.line_index = 0
  buf.byte_pointer = 0
  buf.modified = false
  @buffers[buf.id] = buf
  @buffer_order << buf.id
  swap_to_buffer(buf)
  @status_message = "[terminal] #{name} exited #{status}"
end

#pending_registerObject



3999
4000
4001
# File 'lib/rvim/editor.rb', line 3999

def pending_register
  @pending_register
end

#prev_bufferObject



1617
1618
1619
# File 'lib/rvim/editor.rb', line 1617

def prev_buffer
  cycle_buffer(-1)
end

#previous_jump_positionObject

Special-mark hooks (Stage 5 fills these in).



2301
2302
2303
2304
2305
# File 'lib/rvim/editor.rb', line 2301

def previous_jump_position
  return nil if @jump_index <= 0

  @jump_list[@jump_index - 1]
end

#pump_lua_loopObject



142
143
144
145
# File 'lib/rvim/editor.rb', line 142

def pump_lua_loop
  sched = @lua_scheduler
  sched&.pump || 0
end

#push_jump(line = @line_index, col = @byte_pointer) ⇒ Object



2291
2292
2293
2294
2295
2296
# File 'lib/rvim/editor.rb', line 2291

def push_jump(line = @line_index, col = @byte_pointer)
  @jump_list = @jump_list.first(@jump_index) if @jump_index < @jump_list.size
  @jump_list << [line, col]
  @jump_list.shift if @jump_list.size > JUMP_LIST_LIMIT
  @jump_index = @jump_list.size
end

#push_undo_redo(modified) ⇒ Object



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

def push_undo_redo(modified)
  super
  @undo_timestamps ||= []
  if modified
    # Reline replaces history past @undo_redo_index then pushes one.
    # Mirror that for our timestamps array.
    @undo_timestamps = @undo_timestamps[0..(@undo_redo_index - 1)] || []
    @undo_timestamps.push(Time.now)
  else
    # Same-index update — refresh that timestamp.
    @undo_timestamps[@undo_redo_index] = Time.now
  end
end

#quit!(exit_code: 0) ⇒ Object



1767
1768
1769
1770
# File 'lib/rvim/editor.rb', line 1767

def quit!(exit_code: 0)
  @quit = true
  @exit_code = exit_code
end

#quit?Boolean

Returns:

  • (Boolean)


1763
1764
1765
# File 'lib/rvim/editor.rb', line 1763

def quit?
  @quit
end

#read_register(name = nil) ⇒ Object



2615
2616
2617
2618
2619
2620
2621
2622
2623
2624
2625
2626
# File 'lib/rvim/editor.rb', line 2615

def read_register(name = nil)
  n = name || '"'
  if n == '+'
    text = Rvim::SystemClipboard.read
    kind = text.end_with?("\n") ? :line : :char
    return Rvim::RegisterEntry.new(text.chomp, kind)
  end
  if n == '%'
    return Rvim::RegisterEntry.new(@filepath.to_s, :char)
  end
  @registers.read(n)
end

#rebuild_folds_for_methodObject



1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
# File 'lib/rvim/editor.rb', line 1354

def rebuild_folds_for_method
  method = @settings.get(:foldmethod).to_s
  case method
  when 'marker'
    @folds.clear
    Rvim::Folds.from_markers(@buffer_of_lines).each do |s, e|
      @folds.add(s, e, closed: true)
    end
  when 'indent'
    @folds.clear
    sw = @settings.get(:shiftwidth).to_i
    sw = 2 if sw <= 0
    Rvim::Folds.from_indent(@buffer_of_lines, sw).each do |s, e|
      @folds.add(s, e, closed: true)
    end
  end
  apply_fold_level
end

#recompute_diff_statusObject



1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
# File 'lib/rvim/editor.rb', line 1324

def recompute_diff_status
  bufs = diff_buffers
  bufs.each { |b| b.diff_status = nil }
  return if bufs.size < 2

  a, b = bufs[0], bufs[1]
  a_status, b_status = Rvim::Diff.compute(a.lines, b.lines)
  a.diff_status = a_status
  b.diff_status = b_status
end

#reload_tags_if_neededObject



755
756
757
758
759
760
# File 'lib/rvim/editor.rb', line 755

def reload_tags_if_needed
  paths = @settings.get(:tags).to_s.split(',').map(&:strip).reject(&:empty?)
  return if paths == Rvim::Tags.loaded_paths

  Rvim::Tags.load(paths)
end

#renderObject



1796
1797
1798
1799
1800
# File 'lib/rvim/editor.rb', line 1796

def render
  return if @settings.get(:lazyredraw) && @replaying

  @screen&.render
end

#replace_chars_at_cursor(ch, count) ⇒ Object



959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
# File 'lib/rvim/editor.rb', line 959

def replace_chars_at_cursor(ch, count)
  line = @buffer_of_lines[@line_index] || +''
  pos = @byte_pointer
  count.times do
    break if pos >= line.bytesize

    size = Reline::Unicode.get_next_mbchar_size(line, pos)
    size = 1 if size <= 0
    line = String.new(line.byteslice(0, pos).to_s + ch + line.byteslice(pos + size, line.bytesize - pos - size).to_s, encoding: encoding)
    pos += ch.bytesize
  end
  @buffer_of_lines[@line_index] = line
  @byte_pointer = [pos - ch.bytesize, 0].max
  @modified = true
end

#replace_line_range(start_line, end_line, new_lines) ⇒ Object



1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
# File 'lib/rvim/editor.rb', line 1685

def replace_line_range(start_line, end_line, new_lines)
  return if @buffer_of_lines.empty?

  lo = start_line.clamp(0, @buffer_of_lines.size - 1)
  hi = end_line.clamp(0, @buffer_of_lines.size - 1)
  replacement = new_lines.map { |l| String.new(l, encoding: encoding) }
  @buffer_of_lines[lo..hi] = replacement
  @line_index = lo.clamp(0, [@buffer_of_lines.size - 1, 0].max)
  @byte_pointer = 0
  @modified = true
  @folds&.shift_after(lo, replacement.size - (hi - lo + 1))
end

#replace_overwrite_at_cursor(s) ⇒ Object



975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
# File 'lib/rvim/editor.rb', line 975

def replace_overwrite_at_cursor(s)
  line = @buffer_of_lines[@line_index] || +''
  pos = @byte_pointer
  if pos >= line.bytesize
    @replace_originals << :extend
    @buffer_of_lines[@line_index] = String.new(line + s, encoding: encoding)
  else
    size = Reline::Unicode.get_next_mbchar_size(line, pos)
    size = 1 if size <= 0
    original = line.byteslice(pos, size)
    @replace_originals << original
    @buffer_of_lines[@line_index] = String.new(line.byteslice(0, pos).to_s + s + line.byteslice(pos + size, line.bytesize - pos - size).to_s, encoding: encoding)
  end
  @byte_pointer += s.bytesize
  @modified = true
end

#replace_undo_at_cursorObject



992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
# File 'lib/rvim/editor.rb', line 992

def replace_undo_at_cursor
  return false if @replace_originals.empty?

  original = @replace_originals.pop
  line = @buffer_of_lines[@line_index] || +''
  if original == :extend
    # Char was appended past the original EOL — drop the last byte.
    return false if @byte_pointer <= 0

    size = Reline::Unicode.get_prev_mbchar_size(line, @byte_pointer)
    @buffer_of_lines[@line_index] = String.new(line.byteslice(0, @byte_pointer - size).to_s + line.byteslice(@byte_pointer, line.bytesize - @byte_pointer).to_s, encoding: encoding)
    @byte_pointer -= size
  else
    # Step back over the inserted char and restore the original byte(s).
    return false if @byte_pointer <= 0

    size = Reline::Unicode.get_prev_mbchar_size(line, @byte_pointer)
    before = line.byteslice(0, @byte_pointer - size).to_s
    after = line.byteslice(@byte_pointer, line.bytesize - @byte_pointer).to_s
    @buffer_of_lines[@line_index] = String.new(before + original + after, encoding: encoding)
    @byte_pointer -= size
  end
  true
end

#resize_current(axis, delta) ⇒ Object



608
609
610
611
612
613
614
615
616
617
618
619
# File 'lib/rvim/editor.rb', line 608

def resize_current(axis, delta)
  return if @windows.size < 2

  attr = axis == :height ? :extra_rows : :extra_cols
  cur = @current_window
  idx = @windows.index(cur) || 0
  neighbor = @windows[idx + 1] || @windows[idx - 1]
  return unless neighbor

  cur.send("#{attr}=", cur.send(attr) + delta)
  neighbor.send("#{attr}=", neighbor.send(attr) - delta)
end

#resize_to(axis, target) ⇒ Object



636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
# File 'lib/rvim/editor.rb', line 636

def resize_to(axis, target)
  return if @windows.size < 2

  attr = axis == :height ? :extra_rows : :extra_cols
  total = axis == :height ? content_rows_total : @cols_for_resize
  total ||= @windows.sum { |w| axis == :height ? w.height : w.width }
  baseline = total / @windows.size
  delta = target - baseline
  cur = @current_window
  neighbor = @windows[(@windows.index(cur) || 0) + 1] || @windows[(@windows.index(cur) || 0) - 1]
  return unless neighbor

  old_extra = cur.send(attr)
  cur.send("#{attr}=", delta)
  neighbor.send("#{attr}=", neighbor.send(attr) - (delta - old_extra))
end

#save(path = nil) ⇒ Object



1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
# File 'lib/rvim/editor.rb', line 1726

def save(path = nil)
  target = path || @filepath
  raise 'no file path' unless target

  @autocommands&.fire(:bufwritepre, target.to_s, self)
  keep_backup = @settings.get(:backup)
  writebackup = @settings.get(:writebackup)
  backup = nil
  if (keep_backup || writebackup) && File.exist?(target)
    backup = backup_path(target)
    FileUtils.cp(target, backup)
  end
  ff = @current_buffer&.fileformat || @settings.get(:fileformat) || 'unix'
  sep = case ff
        when 'dos' then "\r\n"
        when 'mac' then "\r"
        else "\n"
        end
  enc = @settings.get(:fileencoding).to_s
  enc = 'utf-8' if enc.empty?
  # fixendofline forces a trailing separator regardless of endofline.
  add_eol = @settings.get(:fixendofline) || @settings.get(:endofline)
  content = @buffer_of_lines.join(sep)
  content += sep if add_eol
  content = content.encode(enc, invalid: :replace, undef: :replace) if enc.downcase != 'utf-8'
  File.binwrite(target, content)
  @filepath = target
  @modified = false
  # Successful write: remove the transient writebackup if backup setting
  # didn't ask us to keep it.
  if backup && !keep_backup
    File.delete(backup) if File.exist?(backup)
  end
  Rvim::UndoFile.write(target, @undo_redo_history, @undo_redo_index) if @settings.get(:undofile)
  @autocommands&.fire(:bufwritepost, target.to_s, self)
end

#screen=(screen) ⇒ Object



1792
1793
1794
# File 'lib/rvim/editor.rb', line 1792

def screen=(screen)
  @screen = screen
end

#select_next_search_match(direction) ⇒ Object



3636
3637
3638
3639
3640
3641
3642
3643
3644
3645
3646
3647
3648
3649
3650
3651
3652
3653
3654
3655
3656
3657
3658
# File 'lib/rvim/editor.rb', line 3636

def select_next_search_match(direction)
  return unless @search_pattern && !@search_matches.empty?

  target = Rvim::Search.next_match(@search_matches, @line_index, @byte_pointer, direction, wrapscan: @settings.get(:wrapscan))
  return unless target

  line, byte_start, byte_end = target

  if operator_pending?
    sel = Rvim::Selection.from(:char, [line, byte_start], [line, byte_end], @buffer_of_lines)
    before = @buffer_of_lines.map(&:dup)
    apply_pending_operator_to_range(sel)
    @modified = true if @buffer_of_lines != before
    @vi_waiting_operator = nil
    @vi_waiting_operator_arg = nil
    return
  end

  push_jump
  @visual_mode = :char
  @visual_anchor = [line, byte_start]
  move_cursor_to(line, byte_end)
end

#selectionObject



2431
2432
2433
2434
2435
2436
2437
2438
2439
2440
# File 'lib/rvim/editor.rb', line 2431

def selection
  return nil unless @visual_mode

  Rvim::Selection.from(
    @visual_mode,
    @visual_anchor,
    [@line_index, @byte_pointer],
    @buffer_of_lines,
  )
end

#set_arg_list(paths) ⇒ Object



746
747
748
749
# File 'lib/rvim/editor.rb', line 746

def set_arg_list(paths)
  @arg_list = Array(paths).map(&:to_s)
  @arg_index = 0
end

#set_clipboard(content, kind, op: :yank) ⇒ Object

Used by every operator (yank/delete/change) to record captured text. Routes to @pending_register if set, else to the unnamed register, and updates numbered registers (“0 on yank, ”1-“9 ring on delete/change). Also records the last yank/change region for the ‘[’ and ‘]’ marks.



2632
2633
2634
2635
2636
2637
2638
2639
2640
2641
2642
2643
2644
2645
2646
2647
2648
# File 'lib/rvim/editor.rb', line 2632

def set_clipboard(content, kind, op: :yank)
  write_register(content, kind, register: @pending_register)
  case op
  when :yank
    @registers.write_yank_history(content, kind)
  when :delete, :change
    @registers.write_delete_history(content, kind)
  end
  # Record region: cursor at this point is the start of the affected area
  # (operators move the cursor to the start). We approximate end with the
  # same point — refined when we have explicit region tracking.
  @last_yank_range = {
    start: [@line_index, @byte_pointer],
    end: [@line_index, @byte_pointer],
  }
  consume_pending_register
end

#show_list(lines) ⇒ Object



535
536
537
538
# File 'lib/rvim/editor.rb', line 535

def show_list(lines)
  @list_view = Rvim::ListView.new(lines.compact)
  @prompt_mode = :listing
end

#source(path, depth: 0) ⇒ Object



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
# File 'lib/rvim/editor.rb', line 252

def source(path, depth: 0)
  expanded = File.expand_path(path.to_s)
  unless File.exist?(expanded)
    @status_message = "E484: Can't open file #{path}"
    return false
  end
  if depth > SOURCE_MAX_DEPTH
    @status_message = "E22: Scripts nested too deep"
    return false
  end

  if expanded.end_with?('.lua')
    lua.load_file(expanded)
    return true
  end

  File.foreach(expanded) do |line|
    line = line.chomp
    next if line.strip.empty?
    next if line.lstrip.start_with?('"', '#')

    parsed = Rvim::Command.parse(line)
    next unless parsed

    if parsed.verb == :source || parsed.verb == :so
      source(parsed.arg.to_s, depth: depth + 1) unless parsed.arg.to_s.empty?
    else
      Rvim::Command.execute(self, parsed)
    end
  end
  true
rescue => e
  @status_message = "E: source #{path}: #{e.message}"
  false
end

#spell_add_word_at_cursor(kind) ⇒ Object



1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
# File 'lib/rvim/editor.rb', line 1291

def spell_add_word_at_cursor(kind)
  word = word_at_cursor
  return unless word

  if kind == :good
    Rvim::Spell.add_good(word)
    @status_message = "Added '#{word}' to good spell list"
  else
    Rvim::Spell.add_bad(word)
    @status_message = "Added '#{word}' to bad spell list"
  end
end

#spell_show_suggestionsObject



1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
# File 'lib/rvim/editor.rb', line 1276

def spell_show_suggestions
  word = word_at_cursor
  return unless word
  return unless Rvim::Spell.misspelled?(word)

  suggestions = Rvim::Spell.suggest(word)
  if suggestions.empty?
    @status_message = "Sorry, no suggestions"
    return
  end

  lines = ["Suggestions for '#{word}':"] + suggestions.each_with_index.map { |s, i| "  #{i + 1}. #{s}" }
  show_list(lines)
end

#split_horizontal(buffer = nil) ⇒ Object



550
551
552
553
554
555
# File 'lib/rvim/editor.rb', line 550

def split_horizontal(buffer = nil)
  return mixed_split_error if @split_orientation == :vertical && @windows.size > 1

  @split_orientation = :horizontal
  add_split(buffer)
end

#split_vertical(buffer = nil) ⇒ Object



557
558
559
560
561
562
# File 'lib/rvim/editor.rb', line 557

def split_vertical(buffer = nil)
  return mixed_split_error if @split_orientation == :horizontal && @windows.size > 1

  @split_orientation = :vertical
  add_split(buffer)
end

#start_filter_prompt(start_line, end_line) ⇒ Object



3488
3489
3490
3491
3492
3493
3494
3495
# File 'lib/rvim/editor.rb', line 3488

def start_filter_prompt(start_line, end_line)
  lo = start_line + 1
  hi = end_line + 1
  @prompt_mode = :ex
  @prompt_buffer = +"#{lo},#{hi}!"
  @status_message = nil
  clear_history_cursor
end

#swap_to_buffer(buf) ⇒ Object



457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
# File 'lib/rvim/editor.rb', line 457

def swap_to_buffer(buf)
  @alternate_filepath = @filepath if @current_buffer && @filepath != buf.filepath
  save_current_buffer if @current_buffer
  if @settings.get(:autoread) && buf.file_changed_externally? && !buf.modified
    buf.reload(encoding: encoding)
  end
  @current_buffer = buf
  @filepath = buf.filepath
  @buffer_of_lines = buf.lines
  @line_index = buf.line_index
  @byte_pointer = buf.byte_pointer
  @modified = buf.modified
  @marks = buf.marks
  @last_visual = buf.last_visual
  @undo_redo_history = buf.undo_redo_history
  @undo_redo_index = buf.undo_redo_index
  @folds = buf.folds
  ensure_current_window(buf)
  @autocommands&.fire(:bufenter, buf.filepath.to_s, self)
end

#swap_to_tab(idx) ⇒ Object



505
506
507
508
509
510
511
512
# File 'lib/rvim/editor.rb', line 505

def swap_to_tab(idx)
  return if idx < 0 || idx >= @tabs.size || idx == @current_tab_index

  autowrite_if_modified
  save_current_tab_state
  @current_tab_index = idx
  load_current_tab_state
end

#switch_buffer_by(arg) ⇒ Object



1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
# File 'lib/rvim/editor.rb', line 1672

def switch_buffer_by(arg)
  target = if arg =~ /\A\d+\z/
             @buffers[arg.to_i]
           else
             @buffers.values.find { |b| b.display_name.include?(arg) }
           end
  if target
    swap_to_buffer(target)
  else
    @status_message = "E94: No matching buffer for #{arg}"
  end
end

#tab_advance(arg = nil) ⇒ Object



3722
3723
3724
3725
3726
3727
3728
3729
3730
3731
# File 'lib/rvim/editor.rb', line 3722

def tab_advance(arg = nil)
  return if @tabs.size < 2

  target = if arg.is_a?(Integer) && arg > 0
             (arg - 1).clamp(0, @tabs.size - 1)
           else
             (@current_tab_index + 1) % @tabs.size
           end
  swap_to_tab(target)
end

#tab_closeObject



3700
3701
3702
3703
3704
3705
3706
3707
3708
3709
3710
# File 'lib/rvim/editor.rb', line 3700

def tab_close
  if @tabs.size <= 1
    @status_message = 'E784: Cannot close last tab page'
    return
  end

  save_current_tab_state
  @tabs.delete_at(@current_tab_index)
  @current_tab_index = [@current_tab_index, @tabs.size - 1].min.clamp(0, @tabs.size - 1)
  load_current_tab_state
end

#tab_move(target) ⇒ Object



3686
3687
3688
3689
3690
3691
3692
3693
3694
3695
3696
3697
3698
# File 'lib/rvim/editor.rb', line 3686

def tab_move(target)
  return if @tabs.size <= 1

  src = @current_tab_index
  dst = target.clamp(0, @tabs.size - 1)
  return if src == dst

  save_current_tab_state
  tab = @tabs.delete_at(src)
  @tabs.insert(dst, tab)
  @current_tab_index = dst
  load_current_tab_state
end

#tab_new(path = nil) ⇒ Object



3660
3661
3662
3663
3664
3665
3666
3667
3668
3669
3670
3671
3672
3673
3674
3675
3676
# File 'lib/rvim/editor.rb', line 3660

def tab_new(path = nil)
  buf = if path && !path.empty?
          find_or_create_buffer(path)
        else
          create_empty_buffer
        end
  win = Rvim::Window.new(buf)
  tab = Rvim::Tab.new(win)

  save_current_tab_state
  @tabs.insert(@current_tab_index + 1, tab)
  @current_tab_index += 1
  @windows = tab.windows
  @current_window = win
  @split_orientation = nil
  activate_window(win)
end

#tab_onlyObject



3712
3713
3714
3715
3716
3717
3718
3719
3720
# File 'lib/rvim/editor.rb', line 3712

def tab_only
  return if @tabs.size <= 1

  save_current_tab_state
  keeper = @tabs[@current_tab_index]
  @tabs = [keeper]
  @current_tab_index = 0
  load_current_tab_state
end

#tab_retreat(arg = nil) ⇒ Object



3733
3734
3735
3736
3737
3738
3739
3740
3741
3742
# File 'lib/rvim/editor.rb', line 3733

def tab_retreat(arg = nil)
  return if @tabs.size < 2

  target = if arg.is_a?(Integer) && arg > 0
             (@current_tab_index - arg) % @tabs.size
           else
             (@current_tab_index - 1) % @tabs.size
           end
  swap_to_tab(target)
end

#tag_jump(name) ⇒ Object



683
684
685
686
687
688
689
690
691
692
693
694
695
# File 'lib/rvim/editor.rb', line 683

def tag_jump(name)
  reload_tags_if_needed
  matches = Rvim::Tags.find(name)
  if matches.empty?
    @status_message = "E426: tag not found: #{name}"
    return
  end

  push_tag_stack(name)
  @tag_matches = matches
  @tag_match_index = 0
  jump_to_tag_entry(matches.first)
end

#tag_nextObject



710
711
712
713
714
715
# File 'lib/rvim/editor.rb', line 710

def tag_next
  return @status_message = 'E73: no more matches' if @tag_matches.empty?

  @tag_match_index = (@tag_match_index + 1).clamp(0, @tag_matches.size - 1)
  jump_to_tag_entry(@tag_matches[@tag_match_index])
end

#tag_popObject



697
698
699
700
701
702
703
704
705
706
707
708
# File 'lib/rvim/editor.rb', line 697

def tag_pop
  if @tag_stack.empty?
    @status_message = 'E555: at bottom of tag stack'
    return
  end

  entry = @tag_stack.pop
  open(entry[:file]) if entry[:file] && entry[:file] != @filepath
  @line_index = entry[:line_index].clamp(0, [@buffer_of_lines.size - 1, 0].max)
  target = @buffer_of_lines[@line_index] || ''
  @byte_pointer = entry[:byte_pointer].clamp(0, target.bytesize)
end

#tag_prevObject



717
718
719
720
721
722
# File 'lib/rvim/editor.rb', line 717

def tag_prev
  return @status_message = 'E73: no more matches' if @tag_matches.empty?

  @tag_match_index = (@tag_match_index - 1).clamp(0, @tag_matches.size - 1)
  jump_to_tag_entry(@tag_matches[@tag_match_index])
end

#travel_undo_seconds(delta) ⇒ Object



387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
# File 'lib/rvim/editor.rb', line 387

def travel_undo_seconds(delta)
  stamps = @undo_timestamps || []
  return if stamps.empty?

  target_time = Time.now + delta
  # Find the nearest history entry whose timestamp is <= target_time when
  # going earlier, or >= target_time when going later. Default to clamping.
  target_index = if delta < 0
                   i = stamps.size - 1
                   i -= 1 while i > 0 && stamps[i] && stamps[i] > target_time
                   i
                 else
                   i = 0
                   i += 1 while i < stamps.size - 1 && stamps[i] && stamps[i] < target_time
                   i
                 end
  diff = target_index - @undo_redo_index
  if diff < 0
    diff.abs.times { send(:undo, nil) }
  elsif diff > 0
    diff.times { send(:redo, nil) }
  end
end

#update(key) ⇒ Object



1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
# File 'lib/rvim/editor.rb', line 1802

def update(key)
  if @prompt_mode == :listing
    process_listing_key(key)
    return
  end

  if @confirm_question
    handle_confirm_key(key)
    return
  end

  if mouse_event?(key)
    handle_mouse_event(key)
    return
  end

  if @digraph_pending
    capture_digraph_key(key)
    return
  end

  if @completion_chain_pending
    @completion_chain_pending = false
    case key.char
    when "\x06" then start_completion_with_source(:filenames, +1)
    when "\x0B" then start_completion_with_source(:dictionary, +1)
    when "\x0C" then start_completion_with_source(:lines, +1)
    else
      @status_message = 'unknown completion source'
    end
    return
  end

  if @completion_active && !completion_key?(key)
    cancel_completion
  end

  if @rvim_pending_case_op
    return if dispatch_case_pending(key)

    # Motion path: dispatch the key normally and apply the op to the delta.
    pre = [@line_index, @byte_pointer]
    saved_kind = @rvim_pending_case_op
    inclusive = inclusive_motion_key?(key)
    clear_case_pending
    super
    post = [@line_index, @byte_pointer]
    apply_case_motion(saved_kind, pre, post, inclusive: inclusive)
    return
  end

  if @rvim_pending_format_op
    # 'gqq' = current line; otherwise capture motion.
    ch = key.char
    if ch == 'q'
      @rvim_pending_format_op = false
      apply_format_to_lines(@line_index, @line_index)
      return
    end

    pre = [@line_index, @byte_pointer]
    @rvim_pending_format_op = false
    super
    post = [@line_index, @byte_pointer]
    start_line, end_line = [pre[0], post[0]].minmax
    apply_format_to_lines(start_line, end_line)
    return
  end

  if @rvim_pending_filter_op
    # '!!' = current line; otherwise capture motion and prefill ex prompt.
    ch = key.char
    if ch == '!'
      @rvim_pending_filter_op = false
      start_filter_prompt(@line_index, @line_index)
      return
    end

    pre = [@line_index, @byte_pointer]
    @rvim_pending_filter_op = false
    super
    post = [@line_index, @byte_pointer]
    start_line, end_line = [pre[0], post[0]].minmax
    start_filter_prompt(start_line, end_line)
    return
  end

  if @rvim_pending_equal_op
    ch = key.char
    if ch == '='
      @rvim_pending_equal_op = false
      apply_equal_to_lines(@line_index, @line_index)
      return
    end

    pre = [@line_index, @byte_pointer]
    @rvim_pending_equal_op = false
    super
    post = [@line_index, @byte_pointer]
    start_line, end_line = [pre[0], post[0]].minmax
    apply_equal_to_lines(start_line, end_line)
    return
  end

  if mapping_eligible?
    decision = route_through_mappings(key)
    return if decision == :consumed
  end

  pre_idle = idle_for_recording?
  pre_buffer = @buffer_of_lines.map(&:dup)
  pre_mode = editing_mode_label
  record_change_key(key, pre_idle) unless @replaying
  record_macro_key(key) unless @replaying

  if @prompt_mode
    process_prompt_key(key)
  elsif @visual_mode
    result = intercept_visual_key(key)
    unless result
      super
      @modified = true if pre_buffer != @buffer_of_lines
      extend_visual_cursor_past_eol_if(key)
    end
  elsif @rvim_text_object_pending
    consume_text_object_key(key)
  elsif operator_pending? && text_object_prefix?(key)
    @rvim_text_object_pending = key.char == 'a' ? :around : :inner
  elsif @replace_mode && editing_mode_label == :vi_insert && replace_mode_self_insert?(key)
    ch = key.char.is_a?(Integer) ? key.char.chr : key.char.to_s
    if ch == "\b" || ch == "\x7F" # Backspace / DEL
      replace_undo_at_cursor
    else
      replace_overwrite_at_cursor(ch)
    end
  else
    @status_message = nil
    super
    @modified = true if pre_buffer != @buffer_of_lines
    maybe_expand_insert_abbreviation(key)
  end

  sync_current_buffer_lines
  capture_special_marks(pre_buffer, pre_mode)
  freeze_change_if_settled(pre_buffer) unless @replaying
end

#viewport_scroll_to(position) ⇒ Object



1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
# File 'lib/rvim/editor.rb', line 1380

def viewport_scroll_to(position)
  win = @current_window
  return unless win

  content_rows = [win.height - 1, 1].max
  cl = @line_index
  win.scroll_top = case position
                   when :center then [cl - (content_rows / 2), 0].max
                   when :top then cl
                   when :bottom then [cl - content_rows + 1, 0].max
                   end
end

#visual_position(name) ⇒ Object



2317
2318
2319
2320
2321
# File 'lib/rvim/editor.rb', line 2317

def visual_position(name)
  return nil unless @last_visual

  name == '<' ? @last_visual[:anchor] : @last_visual[:last_end]
end

#write_register(text, kind, register: nil) ⇒ Object



2598
2599
2600
2601
2602
2603
2604
2605
2606
2607
2608
2609
2610
2611
2612
2613
# File 'lib/rvim/editor.rb', line 2598

def write_register(text, kind, register: nil)
  name = register || '"'
  if name == '+'
    Rvim::SystemClipboard.write(text.is_a?(Array) ? text.join("\n") : text.to_s)
  end
  if name == '%'
    @status_message = 'E354: Invalid register name: %'
    return
  end
  @registers.write(name, text, kind)
  # When 'clipboard' includes 'unnamedplus', mirror unnamed yanks to the
  # system clipboard register too.
  if register.nil? && @settings.get(:clipboard).to_s.split(',').include?('unnamedplus')
    Rvim::SystemClipboard.write(text.is_a?(Array) ? text.join("\n") : text.to_s)
  end
end