mui 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +18 -10
  3. data/CHANGELOG.md +162 -0
  4. data/README.md +309 -6
  5. data/docs/_config.yml +56 -0
  6. data/docs/configuration.md +314 -0
  7. data/docs/getting-started.md +140 -0
  8. data/docs/index.md +55 -0
  9. data/docs/jobs.md +297 -0
  10. data/docs/keybindings.md +229 -0
  11. data/docs/plugins.md +285 -0
  12. data/docs/syntax-highlighting.md +155 -0
  13. data/lib/mui/color_manager.rb +140 -6
  14. data/lib/mui/color_scheme.rb +1 -0
  15. data/lib/mui/command_completer.rb +11 -2
  16. data/lib/mui/command_history.rb +89 -0
  17. data/lib/mui/command_line.rb +32 -2
  18. data/lib/mui/command_registry.rb +21 -2
  19. data/lib/mui/config.rb +3 -1
  20. data/lib/mui/editor.rb +90 -2
  21. data/lib/mui/floating_window.rb +53 -1
  22. data/lib/mui/handler_result.rb +13 -7
  23. data/lib/mui/highlighters/search_highlighter.rb +2 -1
  24. data/lib/mui/highlighters/syntax_highlighter.rb +4 -1
  25. data/lib/mui/insert_completion_state.rb +15 -2
  26. data/lib/mui/key_handler/base.rb +87 -0
  27. data/lib/mui/key_handler/command_mode.rb +68 -0
  28. data/lib/mui/key_handler/insert_mode.rb +10 -41
  29. data/lib/mui/key_handler/normal_mode.rb +24 -51
  30. data/lib/mui/key_handler/operators/paste_operator.rb +9 -3
  31. data/lib/mui/key_handler/search_mode.rb +10 -7
  32. data/lib/mui/key_handler/visual_mode.rb +15 -10
  33. data/lib/mui/key_notation_parser.rb +159 -0
  34. data/lib/mui/key_sequence.rb +67 -0
  35. data/lib/mui/key_sequence_buffer.rb +85 -0
  36. data/lib/mui/key_sequence_handler.rb +163 -0
  37. data/lib/mui/key_sequence_matcher.rb +79 -0
  38. data/lib/mui/line_renderer.rb +52 -1
  39. data/lib/mui/mode_manager.rb +3 -2
  40. data/lib/mui/screen.rb +30 -6
  41. data/lib/mui/search_state.rb +74 -27
  42. data/lib/mui/syntax/language_detector.rb +33 -1
  43. data/lib/mui/syntax/lexers/c_lexer.rb +2 -0
  44. data/lib/mui/syntax/lexers/css_lexer.rb +121 -0
  45. data/lib/mui/syntax/lexers/go_lexer.rb +207 -0
  46. data/lib/mui/syntax/lexers/html_lexer.rb +118 -0
  47. data/lib/mui/syntax/lexers/javascript_lexer.rb +219 -0
  48. data/lib/mui/syntax/lexers/markdown_lexer.rb +210 -0
  49. data/lib/mui/syntax/lexers/ruby_lexer.rb +3 -0
  50. data/lib/mui/syntax/lexers/rust_lexer.rb +150 -0
  51. data/lib/mui/syntax/lexers/typescript_lexer.rb +225 -0
  52. data/lib/mui/terminal_adapter/base.rb +21 -0
  53. data/lib/mui/terminal_adapter/curses.rb +37 -11
  54. data/lib/mui/themes/default.rb +263 -132
  55. data/lib/mui/version.rb +1 -1
  56. data/lib/mui/window.rb +105 -39
  57. data/lib/mui/window_manager.rb +7 -0
  58. data/lib/mui/wrap_cache.rb +40 -0
  59. data/lib/mui/wrap_helper.rb +84 -0
  60. data/lib/mui.rb +15 -0
  61. metadata +26 -3
@@ -15,6 +15,10 @@ module Mui
15
15
  end
16
16
 
17
17
  def handle(key)
18
+ # Check plugin keymaps first
19
+ plugin_result = check_plugin_keymap(key, :command)
20
+ return plugin_result if plugin_result
21
+
18
22
  case key
19
23
  when KeyCode::ESCAPE
20
24
  handle_escape
@@ -30,6 +34,10 @@ module Mui
30
34
  handle_cursor_left
31
35
  when Curses::KEY_RIGHT
32
36
  handle_cursor_right
37
+ when Curses::KEY_UP
38
+ handle_history_up
39
+ when Curses::KEY_DOWN
40
+ handle_history_down
33
41
  else
34
42
  handle_character_input(key)
35
43
  end
@@ -82,6 +90,18 @@ module Mui
82
90
  result
83
91
  end
84
92
 
93
+ def handle_history_up
94
+ @completion_state.reset
95
+ @command_line.history_previous
96
+ result
97
+ end
98
+
99
+ def handle_history_down
100
+ @completion_state.reset
101
+ @command_line.history_next
102
+ result
103
+ end
104
+
85
105
  def update_completion
86
106
  context = @command_line.completion_context
87
107
  unless context
@@ -176,6 +196,10 @@ module Mui
176
196
  handle_tab_move(command_result[:position])
177
197
  when :goto_line
178
198
  handle_goto_line(command_result[:line_number])
199
+ when :shell_command
200
+ handle_shell_command(command_result[:command])
201
+ when :shell_command_error
202
+ result(message: command_result[:message])
179
203
  when :unknown
180
204
  # Check plugin commands before reporting unknown
181
205
  plugin_result = try_plugin_command(command_result[:command])
@@ -264,7 +288,9 @@ module Mui
264
288
 
265
289
  def open_new_buffer(path)
266
290
  new_buffer = create_buffer_from_path(path)
291
+ new_buffer.undo_manager = UndoManager.new
267
292
  window.buffer = new_buffer
293
+
268
294
  result(message: "\"#{path}\" opened")
269
295
  rescue SystemCallError => e
270
296
  result(message: "Error: #{e.message}")
@@ -303,6 +329,7 @@ module Mui
303
329
  def handle_split_horizontal(path = nil)
304
330
  with_window_manager do |wm|
305
331
  buffer = path ? create_buffer_from_path(path) : nil
332
+ buffer&.undo_manager = UndoManager.new
306
333
  wm.split_horizontal(buffer)
307
334
  result
308
335
  end
@@ -311,6 +338,7 @@ module Mui
311
338
  def handle_split_vertical(path = nil)
312
339
  with_window_manager do |wm|
313
340
  buffer = path ? create_buffer_from_path(path) : nil
341
+ buffer&.undo_manager = UndoManager.new
314
342
  wm.split_vertical(buffer)
315
343
  result
316
344
  end
@@ -352,6 +380,7 @@ module Mui
352
380
  with_tab_manager do |tm|
353
381
  new_tab = tm.add
354
382
  buffer = path ? create_buffer_from_path(path) : Buffer.new
383
+ buffer.undo_manager = UndoManager.new
355
384
  new_tab.window_manager.add_window(buffer)
356
385
  result
357
386
  end
@@ -424,6 +453,45 @@ module Mui
424
453
  result
425
454
  end
426
455
 
456
+ def handle_shell_command(cmd)
457
+ return result(message: "Shell commands not available") unless @mode_manager&.editor
458
+
459
+ context = CommandContext.new(
460
+ editor: @mode_manager.editor,
461
+ buffer:,
462
+ window:
463
+ )
464
+
465
+ context.run_shell_command(cmd, on_complete: lambda { |job|
466
+ display_shell_result(job, cmd)
467
+ })
468
+
469
+ result(message: "Running: #{cmd}")
470
+ end
471
+
472
+ def display_shell_result(job, cmd)
473
+ return unless @mode_manager&.editor
474
+
475
+ output = build_shell_output(job, cmd)
476
+ @mode_manager.editor.update_or_create_scratch_buffer("[Shell Output]", output)
477
+ end
478
+
479
+ def build_shell_output(job, cmd)
480
+ res = job.result
481
+ lines = ["$ #{cmd}", ""]
482
+
483
+ lines << res[:stdout].chomp unless res[:stdout].empty?
484
+
485
+ unless res[:stderr].empty?
486
+ lines << "[stderr]"
487
+ lines << res[:stderr].chomp
488
+ end
489
+
490
+ lines << "" << "[Exit status: #{res[:exit_status]}]" unless res[:success]
491
+
492
+ lines.join("\n")
493
+ end
494
+
427
495
  def create_buffer_from_path(path)
428
496
  new_buffer = Buffer.new
429
497
  new_buffer.load(path)
@@ -8,7 +8,8 @@ module Mui
8
8
  super(mode_manager, buffer)
9
9
  @undo_manager = undo_manager
10
10
  # Start undo group unless already started (e.g., by change operator)
11
- @undo_manager&.begin_group unless group_started
11
+ # Use dynamic undo_manager to support buffer changes via :e/:sp/:vs/:tabnew
12
+ self.undo_manager&.begin_group unless group_started
12
13
  # Build word cache for fast completion (use active window's buffer)
13
14
  @word_cache = BufferWordCache.new(self.buffer)
14
15
  end
@@ -44,51 +45,13 @@ module Mui
44
45
  end
45
46
  end
46
47
 
47
- def check_plugin_keymap(key, mode_symbol)
48
- return nil unless @mode_manager&.editor
49
-
50
- key_str = convert_key_to_string(key)
51
- return nil unless key_str
52
-
53
- plugin_handler = Mui.config.keymaps[mode_symbol]&.[](key_str)
54
- return nil unless plugin_handler
55
-
56
- context = CommandContext.new(
57
- editor: @mode_manager.editor,
58
- buffer:,
59
- window:
60
- )
61
- handler_result = plugin_handler.call(context)
62
-
63
- # If handler returns nil/false, let built-in handle it
64
- return nil unless handler_result
65
-
66
- # Return a valid result to indicate the key was handled
67
- handler_result.is_a?(HandlerResult::InsertModeResult) ? handler_result : result
68
- end
69
-
70
- def convert_key_to_string(key)
71
- return key if key.is_a?(String)
72
-
73
- # Handle special Curses keys
74
- case key
75
- when KeyCode::ENTER_CR, KeyCode::ENTER_LF, Curses::KEY_ENTER
76
- "\r"
77
- else
78
- key.chr
79
- end
80
- rescue RangeError
81
- # Key code out of char range (e.g., special function keys)
82
- nil
83
- end
84
-
85
48
  private
86
49
 
87
50
  def handle_escape
88
51
  # Cancel completion if active
89
52
  editor.insert_completion_state.reset if completion_active?
90
53
 
91
- @undo_manager&.end_group
54
+ undo_manager&.end_group
92
55
  # Remove trailing whitespace from current line if it's whitespace-only (Vim behavior)
93
56
  stripped = strip_trailing_whitespace_if_empty_line
94
57
  # Move cursor back one position unless we just stripped whitespace (cursor already at 0)
@@ -107,11 +70,13 @@ module Mui
107
70
  end
108
71
 
109
72
  def handle_move_left
73
+ reset_insert_completion_state
110
74
  self.cursor_col = cursor_col - 1 if cursor_col.positive?
111
75
  result
112
76
  end
113
77
 
114
78
  def handle_move_right
79
+ reset_insert_completion_state
115
80
  self.cursor_col = cursor_col + 1 if cursor_col < current_line_length
116
81
  result
117
82
  end
@@ -134,7 +99,7 @@ module Mui
134
99
  update_completion_list if completion_active?
135
100
  elsif cursor_row.positive?
136
101
  join_with_previous_line
137
- editor.insert_completion_state.reset if completion_active?
102
+ reset_insert_completion_state
138
103
  end
139
104
  result
140
105
  end
@@ -346,6 +311,10 @@ module Mui
346
311
  end
347
312
  end
348
313
 
314
+ def reset_insert_completion_state
315
+ editor.insert_completion_state.reset if completion_active?
316
+ end
317
+
349
318
  def result(mode: nil, message: nil, quit: false)
350
319
  HandlerResult::InsertModeResult.new(mode:, message:, quit:)
351
320
  end
@@ -21,8 +21,9 @@ module Mui
21
21
  # Sync operators with current buffer/window (may have changed via tab switch)
22
22
  sync_operators
23
23
 
24
- # Check plugin keymaps first (only when no pending motion)
25
- unless @pending_motion
24
+ # Check plugin keymaps first
25
+ # Skip if: has pending motion OR (is builtin key AND no pending sequence)
26
+ unless @pending_motion || (builtin_operator_key?(key) && !pending_sequence?)
26
27
  plugin_result = check_plugin_keymap(key, :normal)
27
28
  return plugin_result if plugin_result
28
29
  end
@@ -34,49 +35,22 @@ module Mui
34
35
  end
35
36
  end
36
37
 
37
- def check_plugin_keymap(key, mode_symbol)
38
- return nil unless @mode_manager&.editor
39
-
40
- key_str = convert_key_to_string(key)
41
- return nil unless key_str
42
-
43
- plugin_handler = Mui.config.keymaps[mode_symbol]&.[](key_str)
44
- return nil unless plugin_handler
45
-
46
- context = CommandContext.new(
47
- editor: @mode_manager.editor,
48
- buffer:,
49
- window:
50
- )
51
- handler_result = plugin_handler.call(context)
38
+ # Check if there's a pending multi-key sequence in progress
39
+ def pending_sequence?
40
+ key_sequence_handler&.pending?
41
+ end
52
42
 
53
- # If handler returns nil/false, let built-in handle it
54
- # This allows buffer-specific keymaps to pass through for other buffers
55
- return nil unless handler_result
43
+ # Check if key starts a built-in operator (takes priority over plugins)
44
+ def builtin_operator_key?(key)
45
+ char = key_to_char(key)
46
+ return false unless char
56
47
 
57
- # Return a valid result to indicate the key was handled
58
- handler_result.is_a?(HandlerResult::NormalModeResult) ? handler_result : result
48
+ # Built-in operators and motions that set pending_motion
49
+ %w[d c y g f F t T].include?(char) || key == KeyCode::CTRL_W
59
50
  end
60
51
 
61
52
  private
62
53
 
63
- # Convert key to string for keymap lookup
64
- # Handles special keys like Enter that have Curses constants
65
- def convert_key_to_string(key)
66
- return key if key.is_a?(String)
67
-
68
- # Handle special Curses keys
69
- case key
70
- when KeyCode::ENTER_CR, KeyCode::ENTER_LF, Curses::KEY_ENTER
71
- "\r"
72
- else
73
- key.chr
74
- end
75
- rescue RangeError
76
- # Key code out of char range (e.g., special function keys)
77
- nil
78
- end
79
-
80
54
  def initialize_operators
81
55
  @operators = {
82
56
  delete: Operators::DeleteOperator.new(
@@ -95,7 +69,7 @@ module Mui
95
69
  end
96
70
 
97
71
  def sync_operators
98
- @operators.each_value { |op| op.update(buffer:, window:) }
72
+ @operators.each_value { |op| op.update(buffer:, window:, undo_manager:) }
99
73
  end
100
74
 
101
75
  def handle_normal_key(key)
@@ -298,7 +272,7 @@ module Mui
298
272
  end
299
273
 
300
274
  def handle_open_below
301
- @undo_manager&.begin_group
275
+ undo_manager&.begin_group
302
276
  buffer.insert_line(cursor_row + 1)
303
277
  self.cursor_row = cursor_row + 1
304
278
  self.cursor_col = 0
@@ -306,7 +280,7 @@ module Mui
306
280
  end
307
281
 
308
282
  def handle_open_above
309
- @undo_manager&.begin_group
283
+ undo_manager&.begin_group
310
284
  buffer.insert_line(cursor_row)
311
285
  self.cursor_col = 0
312
286
  result(mode: Mode::INSERT, group_started: true)
@@ -410,7 +384,7 @@ module Mui
410
384
 
411
385
  # Undo/Redo handlers
412
386
  def handle_undo
413
- if @undo_manager&.undo(buffer)
387
+ if undo_manager&.undo(buffer)
414
388
  window.clamp_cursor_to_line(buffer)
415
389
  result
416
390
  else
@@ -419,7 +393,7 @@ module Mui
419
393
  end
420
394
 
421
395
  def handle_redo
422
- if @undo_manager&.redo(buffer)
396
+ if undo_manager&.redo(buffer)
423
397
  window.clamp_cursor_to_line(buffer)
424
398
  result
425
399
  else
@@ -500,9 +474,9 @@ module Mui
500
474
  return result(message: "No previous search pattern") unless @search_state&.has_pattern?
501
475
 
502
476
  match = if @search_state.direction == :forward
503
- @search_state.find_next(cursor_row, cursor_col)
477
+ @search_state.find_next(cursor_row, cursor_col, buffer:)
504
478
  else
505
- @search_state.find_previous(cursor_row, cursor_col)
479
+ @search_state.find_previous(cursor_row, cursor_col, buffer:)
506
480
  end
507
481
 
508
482
  if match
@@ -517,9 +491,9 @@ module Mui
517
491
  return result(message: "No previous search pattern") unless @search_state&.has_pattern?
518
492
 
519
493
  match = if @search_state.direction == :forward
520
- @search_state.find_previous(cursor_row, cursor_col)
494
+ @search_state.find_previous(cursor_row, cursor_col, buffer:)
521
495
  else
522
- @search_state.find_next(cursor_row, cursor_col)
496
+ @search_state.find_next(cursor_row, cursor_col, buffer:)
523
497
  end
524
498
 
525
499
  if match
@@ -538,13 +512,12 @@ module Mui
538
512
  escaped_pattern = "\\b#{Regexp.escape(word)}\\b"
539
513
 
540
514
  @search_state.set_pattern(escaped_pattern, direction)
541
- @search_state.find_all_matches(buffer)
542
515
 
543
516
  # Find next/previous match from current position
544
517
  match = if direction == :forward
545
- @search_state.find_next(cursor_row, cursor_col)
518
+ @search_state.find_next(cursor_row, cursor_col, buffer:)
546
519
  else
547
- @search_state.find_previous(cursor_row, cursor_col)
520
+ @search_state.find_previous(cursor_row, cursor_col, buffer:)
548
521
  end
549
522
 
550
523
  if match
@@ -37,6 +37,7 @@ module Mui
37
37
  private
38
38
 
39
39
  def paste_line_after(name: nil)
40
+ undo_manager&.begin_group
40
41
  text = @register.get(name:)
41
42
  lines = text.split("\n", -1)
42
43
  lines.reverse_each do |line|
@@ -44,15 +45,18 @@ module Mui
44
45
  end
45
46
  self.cursor_row = cursor_row + 1
46
47
  self.cursor_col = 0
48
+ undo_manager&.end_group
47
49
  end
48
50
 
49
51
  def paste_line_before(name: nil)
52
+ undo_manager&.begin_group
50
53
  text = @register.get(name:)
51
54
  lines = text.split("\n", -1)
52
55
  lines.reverse_each do |line|
53
56
  @buffer.insert_line(cursor_row, line)
54
57
  end
55
58
  self.cursor_col = 0
59
+ undo_manager&.end_group
56
60
  end
57
61
 
58
62
  def paste_char_after(name: nil)
@@ -63,7 +67,7 @@ module Mui
63
67
  if text.include?("\n")
64
68
  paste_multiline_char(text, line, insert_col)
65
69
  else
66
- @buffer.lines[cursor_row] = line[0...insert_col].to_s + text + line[insert_col..].to_s
70
+ @buffer.replace_line(cursor_row, line[0...insert_col].to_s + text + line[insert_col..].to_s)
67
71
  self.cursor_col = insert_col + text.length - 1
68
72
  @window.clamp_cursor_to_line(@buffer)
69
73
  end
@@ -76,19 +80,20 @@ module Mui
76
80
  if text.include?("\n")
77
81
  paste_multiline_char(text, line, cursor_col)
78
82
  else
79
- @buffer.lines[cursor_row] = line[0...cursor_col].to_s + text + line[cursor_col..].to_s
83
+ @buffer.replace_line(cursor_row, line[0...cursor_col].to_s + text + line[cursor_col..].to_s)
80
84
  self.cursor_col = cursor_col + text.length - 1
81
85
  @window.clamp_cursor_to_line(@buffer)
82
86
  end
83
87
  end
84
88
 
85
89
  def paste_multiline_char(text, line, insert_col)
90
+ undo_manager&.begin_group
86
91
  lines = text.split("\n", -1)
87
92
  before = line[0...insert_col].to_s
88
93
  after = line[insert_col..].to_s
89
94
 
90
95
  # First line: before + first part of pasted text
91
- @buffer.lines[cursor_row] = before + lines.first
96
+ @buffer.replace_line(cursor_row, before + lines.first)
92
97
 
93
98
  # Middle lines: insert as new lines
94
99
  lines[1...-1].each_with_index do |pasted_line, idx|
@@ -106,6 +111,7 @@ module Mui
106
111
  self.cursor_col = lines.last.length - 1
107
112
  self.cursor_col = 0 if cursor_col.negative?
108
113
  @window.clamp_cursor_to_line(@buffer)
114
+ undo_manager&.end_group
109
115
  end
110
116
  end
111
117
  end
@@ -23,6 +23,10 @@ module Mui
23
23
  end
24
24
 
25
25
  def handle(key)
26
+ # Check plugin keymaps first
27
+ plugin_result = check_plugin_keymap(key, :search)
28
+ return plugin_result if plugin_result
29
+
26
30
  case key
27
31
  when KeyCode::ESCAPE
28
32
  handle_escape
@@ -136,19 +140,19 @@ module Mui
136
140
 
137
141
  direction = @search_input.prompt == "/" ? :forward : :backward
138
142
  @search_state.set_pattern(pattern, direction)
139
- @search_state.find_all_matches(buffer)
140
143
 
141
144
  # Move cursor to first match from original position
142
- return if @search_state.matches.empty?
145
+ matches = @search_state.find_all_matches(buffer)
146
+ return if matches.empty?
143
147
 
144
148
  # Use original position if set, otherwise use current cursor position
145
149
  search_row = @original_cursor_row || cursor_row
146
150
  search_col = @original_cursor_col || cursor_col
147
151
 
148
152
  match = if direction == :forward
149
- @search_state.find_next(search_row, search_col)
153
+ @search_state.find_next(search_row, search_col, buffer:)
150
154
  else
151
- @search_state.find_previous(search_row, search_col)
155
+ @search_state.find_previous(search_row, search_col, buffer:)
152
156
  end
153
157
 
154
158
  return unless match
@@ -163,12 +167,11 @@ module Mui
163
167
 
164
168
  direction = @search_input.prompt == "/" ? :forward : :backward
165
169
  @search_state.set_pattern(pattern, direction)
166
- @search_state.find_all_matches(@buffer)
167
170
 
168
171
  match = if direction == :forward
169
- @search_state.find_next(cursor_row, cursor_col)
172
+ @search_state.find_next(cursor_row, cursor_col, buffer:)
170
173
  else
171
- @search_state.find_previous(cursor_row, cursor_col)
174
+ @search_state.find_previous(cursor_row, cursor_col, buffer:)
172
175
  end
173
176
 
174
177
  if match
@@ -18,6 +18,12 @@ module Mui
18
18
  end
19
19
 
20
20
  def handle(key)
21
+ # Check plugin keymaps first (only when no pending motion)
22
+ unless @pending_motion
23
+ plugin_result = check_plugin_keymap(key, :visual)
24
+ return plugin_result if plugin_result
25
+ end
26
+
21
27
  if @pending_motion
22
28
  handle_pending_motion(key)
23
29
  else
@@ -120,7 +126,7 @@ module Mui
120
126
  if @selection.line_mode
121
127
  change_lines(range)
122
128
  else
123
- @undo_manager&.begin_group
129
+ undo_manager&.begin_group
124
130
  change_range(range)
125
131
  end
126
132
  @pending_register = nil
@@ -154,16 +160,15 @@ module Mui
154
160
  # Escape special regex characters for literal search
155
161
  escaped_pattern = Regexp.escape(text)
156
162
 
157
- # Set search state and find matches
163
+ # Set search state
158
164
  search_state = @mode_manager.search_state
159
165
  search_state.set_pattern(escaped_pattern, direction)
160
- search_state.find_all_matches(buffer)
161
166
 
162
167
  # Find next/previous match from current position
163
168
  match = if direction == :forward
164
- search_state.find_next(cursor_row, cursor_col)
169
+ search_state.find_next(cursor_row, cursor_col, buffer:)
165
170
  else
166
- search_state.find_previous(cursor_row, cursor_col)
171
+ search_state.find_previous(cursor_row, cursor_col, buffer:)
167
172
  end
168
173
 
169
174
  if match
@@ -207,7 +212,7 @@ module Mui
207
212
  def change_lines(range)
208
213
  lines = (range[:start_row]..range[:end_row]).map { |r| buffer.line(r) }
209
214
  @register.delete(lines.join("\n"), linewise: true, name: @pending_register)
210
- @undo_manager&.begin_group
215
+ undo_manager&.begin_group
211
216
  (range[:end_row] - range[:start_row] + 1).times do
212
217
  buffer.delete_line(range[:start_row])
213
218
  end
@@ -229,11 +234,11 @@ module Mui
229
234
  def delete_lines(range)
230
235
  lines = (range[:start_row]..range[:end_row]).map { |r| buffer.line(r) }
231
236
  @register.delete(lines.join("\n"), linewise: true, name: @pending_register)
232
- @undo_manager&.begin_group unless @undo_manager&.in_group?
237
+ undo_manager&.begin_group unless undo_manager&.in_group?
233
238
  (range[:end_row] - range[:start_row] + 1).times do
234
239
  buffer.delete_line(range[:start_row])
235
240
  end
236
- @undo_manager&.end_group
241
+ undo_manager&.end_group
237
242
  self.cursor_row = [range[:start_row], buffer.line_count - 1].min
238
243
  self.cursor_col = 0
239
244
  window.clamp_cursor_to_line(buffer)
@@ -268,7 +273,7 @@ module Mui
268
273
  def indent_lines(start_row, end_row, direction)
269
274
  indent_string = build_indent_string
270
275
 
271
- @undo_manager&.begin_group unless @undo_manager&.in_group?
276
+ undo_manager&.begin_group unless undo_manager&.in_group?
272
277
 
273
278
  (start_row..end_row).each do |row|
274
279
  if direction == :right
@@ -278,7 +283,7 @@ module Mui
278
283
  end
279
284
  end
280
285
 
281
- @undo_manager&.end_group
286
+ undo_manager&.end_group
282
287
  end
283
288
 
284
289
  def build_indent_string