mui 0.1.0 → 0.2.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 (86) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +158 -0
  3. data/CHANGELOG.md +349 -0
  4. data/exe/mui +1 -2
  5. data/lib/mui/autocmd.rb +66 -0
  6. data/lib/mui/buffer.rb +275 -0
  7. data/lib/mui/buffer_word_cache.rb +131 -0
  8. data/lib/mui/buffer_word_completer.rb +77 -0
  9. data/lib/mui/color_manager.rb +136 -0
  10. data/lib/mui/color_scheme.rb +63 -0
  11. data/lib/mui/command_completer.rb +21 -0
  12. data/lib/mui/command_context.rb +90 -0
  13. data/lib/mui/command_line.rb +137 -0
  14. data/lib/mui/command_registry.rb +25 -0
  15. data/lib/mui/completion_renderer.rb +84 -0
  16. data/lib/mui/completion_state.rb +58 -0
  17. data/lib/mui/config.rb +56 -0
  18. data/lib/mui/editor.rb +319 -0
  19. data/lib/mui/error.rb +29 -0
  20. data/lib/mui/file_completer.rb +51 -0
  21. data/lib/mui/floating_window.rb +161 -0
  22. data/lib/mui/handler_result.rb +101 -0
  23. data/lib/mui/highlight.rb +22 -0
  24. data/lib/mui/highlighters/base.rb +23 -0
  25. data/lib/mui/highlighters/search_highlighter.rb +26 -0
  26. data/lib/mui/highlighters/selection_highlighter.rb +48 -0
  27. data/lib/mui/highlighters/syntax_highlighter.rb +105 -0
  28. data/lib/mui/input.rb +17 -0
  29. data/lib/mui/insert_completion_renderer.rb +92 -0
  30. data/lib/mui/insert_completion_state.rb +77 -0
  31. data/lib/mui/job.rb +81 -0
  32. data/lib/mui/job_manager.rb +113 -0
  33. data/lib/mui/key_code.rb +30 -0
  34. data/lib/mui/key_handler/base.rb +100 -0
  35. data/lib/mui/key_handler/command_mode.rb +443 -0
  36. data/lib/mui/key_handler/insert_mode.rb +354 -0
  37. data/lib/mui/key_handler/motions/motion_handler.rb +56 -0
  38. data/lib/mui/key_handler/normal_mode.rb +579 -0
  39. data/lib/mui/key_handler/operators/base_operator.rb +134 -0
  40. data/lib/mui/key_handler/operators/change_operator.rb +179 -0
  41. data/lib/mui/key_handler/operators/delete_operator.rb +176 -0
  42. data/lib/mui/key_handler/operators/paste_operator.rb +113 -0
  43. data/lib/mui/key_handler/operators/yank_operator.rb +127 -0
  44. data/lib/mui/key_handler/search_mode.rb +188 -0
  45. data/lib/mui/key_handler/visual_line_mode.rb +20 -0
  46. data/lib/mui/key_handler/visual_mode.rb +397 -0
  47. data/lib/mui/key_handler/window_command.rb +112 -0
  48. data/lib/mui/key_handler.rb +16 -0
  49. data/lib/mui/layout/calculator.rb +15 -0
  50. data/lib/mui/layout/leaf_node.rb +33 -0
  51. data/lib/mui/layout/node.rb +29 -0
  52. data/lib/mui/layout/split_node.rb +132 -0
  53. data/lib/mui/line_renderer.rb +122 -0
  54. data/lib/mui/mode.rb +13 -0
  55. data/lib/mui/mode_manager.rb +185 -0
  56. data/lib/mui/motion.rb +139 -0
  57. data/lib/mui/plugin.rb +35 -0
  58. data/lib/mui/plugin_manager.rb +106 -0
  59. data/lib/mui/register.rb +110 -0
  60. data/lib/mui/screen.rb +85 -0
  61. data/lib/mui/search_completer.rb +50 -0
  62. data/lib/mui/search_input.rb +40 -0
  63. data/lib/mui/search_state.rb +88 -0
  64. data/lib/mui/selection.rb +55 -0
  65. data/lib/mui/status_line_renderer.rb +40 -0
  66. data/lib/mui/syntax/language_detector.rb +74 -0
  67. data/lib/mui/syntax/lexer_base.rb +106 -0
  68. data/lib/mui/syntax/lexers/c_lexer.rb +127 -0
  69. data/lib/mui/syntax/lexers/ruby_lexer.rb +114 -0
  70. data/lib/mui/syntax/token.rb +42 -0
  71. data/lib/mui/syntax/token_cache.rb +91 -0
  72. data/lib/mui/tab_bar_renderer.rb +87 -0
  73. data/lib/mui/tab_manager.rb +96 -0
  74. data/lib/mui/tab_page.rb +35 -0
  75. data/lib/mui/terminal_adapter/base.rb +92 -0
  76. data/lib/mui/terminal_adapter/curses.rb +162 -0
  77. data/lib/mui/terminal_adapter.rb +4 -0
  78. data/lib/mui/themes/default.rb +315 -0
  79. data/lib/mui/undo_manager.rb +83 -0
  80. data/lib/mui/undoable_action.rb +175 -0
  81. data/lib/mui/unicode_width.rb +100 -0
  82. data/lib/mui/version.rb +1 -1
  83. data/lib/mui/window.rb +158 -0
  84. data/lib/mui/window_manager.rb +249 -0
  85. data/lib/mui.rb +156 -2
  86. metadata +98 -3
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mui
4
+ module TerminalAdapter
5
+ # Abstract base class for terminal adapters
6
+ class Base
7
+ attr_accessor :color_resolver
8
+
9
+ # Initialize terminal
10
+ def init
11
+ raise MethodNotOverriddenError, __method__
12
+ end
13
+
14
+ # Close terminal
15
+ def close
16
+ raise MethodNotOverriddenError, __method__
17
+ end
18
+
19
+ # Clear screen
20
+ def clear
21
+ raise MethodNotOverriddenError, __method__
22
+ end
23
+
24
+ # Refresh screen
25
+ def refresh
26
+ raise MethodNotOverriddenError, __method__
27
+ end
28
+
29
+ # Get terminal width
30
+ def width
31
+ raise MethodNotOverriddenError, __method__
32
+ end
33
+
34
+ # Get terminal height
35
+ def height
36
+ raise MethodNotOverriddenError, __method__
37
+ end
38
+
39
+ # Set cursor position
40
+ def setpos(_y, _x)
41
+ raise MethodNotOverriddenError, __method__
42
+ end
43
+
44
+ # Output string at current position
45
+ def addstr(_str)
46
+ raise MethodNotOverriddenError, __method__
47
+ end
48
+
49
+ # Execute block with highlight (reverse video)
50
+ def with_highlight
51
+ raise MethodNotOverriddenError, __method__
52
+ end
53
+
54
+ # Initialize color support
55
+ def init_colors
56
+ raise MethodNotOverriddenError, __method__
57
+ end
58
+
59
+ # Initialize a color pair
60
+ def init_color_pair(_pair_index, _fg, _bg)
61
+ raise MethodNotOverriddenError, __method__
62
+ end
63
+
64
+ # Execute block with specified color and attributes
65
+ def with_color(_pair_index, bold: false, underline: false)
66
+ raise MethodNotOverriddenError, __method__
67
+ end
68
+
69
+ # Read single key (blocking)
70
+ def getch
71
+ raise MethodNotOverriddenError, __method__
72
+ end
73
+
74
+ # Read single key (non-blocking)
75
+ def getch_nonblock
76
+ raise MethodNotOverriddenError, __method__
77
+ end
78
+
79
+ # Suspend terminal for external interactive command execution
80
+ # Implementations should save terminal state and exit raw mode
81
+ def suspend
82
+ raise MethodNotOverriddenError, __method__
83
+ end
84
+
85
+ # Resume terminal after external interactive command execution
86
+ # Implementations should restore terminal state and re-enter raw mode
87
+ def resume
88
+ raise MethodNotOverriddenError, __method__
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,162 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "curses"
4
+
5
+ module Mui
6
+ module TerminalAdapter
7
+ # Curses-based terminal adapter for production use
8
+ class Curses < Base
9
+ def init
10
+ ::Curses.init_screen
11
+ ::Curses.raw
12
+ ::Curses.noecho
13
+ ::Curses.curs_set(1)
14
+ ::Curses.stdscr.keypad(true)
15
+ init_colors
16
+ end
17
+
18
+ def init_colors
19
+ ::Curses.start_color
20
+ ::Curses.use_default_colors
21
+ end
22
+
23
+ def init_color_pair(pair_index, fg, bg)
24
+ fg_code = color_code(fg)
25
+ bg_code = color_code(bg)
26
+ ::Curses.init_pair(pair_index, fg_code, bg_code)
27
+ end
28
+
29
+ def with_color(pair_index, bold: false, underline: false)
30
+ attrs = ::Curses.color_pair(pair_index)
31
+ attrs |= ::Curses::A_BOLD if bold
32
+ attrs |= ::Curses::A_UNDERLINE if underline
33
+ ::Curses.attron(attrs)
34
+ result = yield
35
+ ::Curses.attroff(attrs)
36
+ result
37
+ end
38
+
39
+ private
40
+
41
+ def color_code(color)
42
+ return -1 if color.nil?
43
+ return color if color.is_a?(Integer)
44
+ return @color_resolver.resolve(color) if @color_resolver
45
+
46
+ -1
47
+ end
48
+
49
+ public
50
+
51
+ def close
52
+ ::Curses.close_screen
53
+ end
54
+
55
+ def clear
56
+ # Use erase instead of clear to avoid flicker
57
+ # erase marks the screen for clearing but doesn't immediately refresh
58
+ ::Curses.erase
59
+ end
60
+
61
+ def refresh
62
+ ::Curses.refresh
63
+ end
64
+
65
+ def width
66
+ ::Curses.cols
67
+ end
68
+
69
+ def height
70
+ ::Curses.lines
71
+ end
72
+
73
+ def setpos(y, x)
74
+ ::Curses.setpos(y, x)
75
+ end
76
+
77
+ def addstr(str)
78
+ ::Curses.addstr(str)
79
+ end
80
+
81
+ def with_highlight
82
+ ::Curses.attron(::Curses::A_REVERSE)
83
+ result = yield
84
+ ::Curses.attroff(::Curses::A_REVERSE)
85
+ result
86
+ end
87
+
88
+ def getch
89
+ key = ::Curses.getch
90
+ return key unless key.is_a?(Integer)
91
+ return key if key.negative? || key > 255 # Special keys (arrows, etc.)
92
+
93
+ # Handle UTF-8 multibyte sequences
94
+ if key >= 0x80
95
+ read_utf8_char(key)
96
+ else
97
+ key
98
+ end
99
+ end
100
+
101
+ private
102
+
103
+ def read_utf8_char(first_byte)
104
+ bytes = [first_byte]
105
+
106
+ # Determine expected byte count from first byte
107
+ if (first_byte & 0xE0) == 0xC0
108
+ # 2-byte sequence (110xxxxx)
109
+ expected = 2
110
+ elsif (first_byte & 0xF0) == 0xE0
111
+ # 3-byte sequence (1110xxxx) - CJK characters
112
+ expected = 3
113
+ elsif (first_byte & 0xF8) == 0xF0
114
+ # 4-byte sequence (11110xxx) - emoji, etc.
115
+ expected = 4
116
+ else
117
+ # Invalid or continuation byte, return as-is
118
+ return first_byte
119
+ end
120
+
121
+ # Read remaining bytes
122
+ (expected - 1).times do
123
+ next_byte = ::Curses.getch
124
+ break unless next_byte.is_a?(Integer) && (next_byte & 0xC0) == 0x80
125
+
126
+ bytes << next_byte
127
+ end
128
+
129
+ # Convert to UTF-8 string
130
+ bytes.pack("C*").force_encoding(Encoding::UTF_8)
131
+ end
132
+
133
+ public
134
+
135
+ def getch_nonblock
136
+ ::Curses.stdscr.nodelay = true
137
+ key = ::Curses.getch
138
+ return key.tap { ::Curses.stdscr.nodelay = false } unless key.is_a?(Integer)
139
+ return key.tap { ::Curses.stdscr.nodelay = false } if key.negative? || key > 255
140
+
141
+ result = if key >= 0x80
142
+ read_utf8_char(key)
143
+ else
144
+ key
145
+ end
146
+ ::Curses.stdscr.nodelay = false
147
+ result
148
+ end
149
+
150
+ def suspend
151
+ ::Curses.def_prog_mode
152
+ ::Curses.close_screen
153
+ end
154
+
155
+ def resume
156
+ ::Curses.reset_prog_mode
157
+ ::Curses.refresh
158
+ ::Curses.curs_set(1)
159
+ end
160
+ end
161
+ end
162
+ end
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "terminal_adapter/base"
4
+ require_relative "terminal_adapter/curses"
@@ -0,0 +1,315 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mui
4
+ module Themes
5
+ def self.mui
6
+ scheme = ColorScheme.new("mui")
7
+ scheme.define :normal, fg: :white, bg: :darkgray
8
+ scheme.define :status_line, fg: :white, bg: :blue
9
+ scheme.define :status_line_mode, fg: :white, bg: :magenta, bold: true
10
+ scheme.define :search_highlight, fg: :black, bg: :cyan
11
+ scheme.define :visual_selection, fg: :white, bg: :magenta
12
+ scheme.define :line_number, fg: :cyan, bg: :darkgray
13
+ scheme.define :message_error, fg: :red, bg: :darkgray, bold: true
14
+ scheme.define :message_info, fg: :cyan, bg: :darkgray
15
+ scheme.define :separator, fg: :white, bg: :blue
16
+ scheme.define :command_line, fg: :white, bg: :darkgray
17
+ scheme.define :tab_bar, fg: :white, bg: :blue
18
+ scheme.define :tab_bar_active, fg: :white, bg: :magenta, bold: true
19
+ scheme.define :completion_popup, fg: :white, bg: :blue
20
+ scheme.define :completion_popup_selected, fg: :white, bg: :magenta, bold: true
21
+ # Syntax highlighting
22
+ scheme.define :syntax_keyword, fg: :magenta, bold: true
23
+ scheme.define :syntax_string, fg: :green
24
+ scheme.define :syntax_comment, fg: :cyan
25
+ scheme.define :syntax_number, fg: :red
26
+ scheme.define :syntax_symbol, fg: :yellow
27
+ scheme.define :syntax_constant, fg: :yellow
28
+ scheme.define :syntax_operator, fg: :white
29
+ scheme.define :syntax_identifier, fg: :white
30
+ scheme.define :syntax_preprocessor, fg: :magenta
31
+ scheme.define :syntax_instance_variable, fg: :cyan
32
+ scheme.define :syntax_global_variable, fg: :red
33
+ scheme.define :syntax_method_call, fg: :blue
34
+ scheme.define :syntax_type, fg: :green
35
+ # Diff highlighting
36
+ scheme.define :diff_add, fg: :green
37
+ scheme.define :diff_delete, fg: :red
38
+ scheme.define :diff_hunk, fg: :cyan
39
+ scheme.define :diff_header, fg: :yellow, bold: true
40
+ # LSP diagnostics
41
+ scheme.define :diagnostic_error, fg: :red, underline: true
42
+ scheme.define :diagnostic_warning, fg: :yellow, underline: true
43
+ scheme.define :diagnostic_info, fg: :blue, underline: true
44
+ scheme.define :diagnostic_hint, fg: :cyan, underline: true
45
+ # Floating window
46
+ scheme.define :floating_window, fg: :white, bg: :blue
47
+ scheme
48
+ end
49
+
50
+ def self.solarized_dark
51
+ scheme = ColorScheme.new("solarized_dark")
52
+ scheme.define :normal, fg: :solarized_base0, bg: :solarized_base03
53
+ scheme.define :status_line, fg: :solarized_base1, bg: :solarized_base02
54
+ scheme.define :status_line_mode, fg: :solarized_base03, bg: :solarized_blue, bold: true
55
+ scheme.define :search_highlight, fg: :solarized_base03, bg: :solarized_yellow
56
+ scheme.define :visual_selection, fg: :solarized_base03, bg: :solarized_blue
57
+ scheme.define :line_number, fg: :solarized_base01, bg: :solarized_base03
58
+ scheme.define :message_error, fg: :solarized_red, bg: :solarized_base03, bold: true
59
+ scheme.define :message_info, fg: :solarized_cyan, bg: :solarized_base03
60
+ scheme.define :separator, fg: :solarized_base1, bg: :solarized_base02
61
+ scheme.define :command_line, fg: :solarized_base0, bg: :solarized_base03
62
+ scheme.define :tab_bar, fg: :solarized_base1, bg: :solarized_base02
63
+ scheme.define :tab_bar_active, fg: :solarized_base03, bg: :solarized_blue, bold: true
64
+ scheme.define :completion_popup, fg: :solarized_base1, bg: :solarized_base02
65
+ scheme.define :completion_popup_selected, fg: :solarized_base03, bg: :solarized_blue, bold: true
66
+ # Syntax highlighting
67
+ scheme.define :syntax_keyword, fg: :solarized_green, bold: true
68
+ scheme.define :syntax_string, fg: :solarized_cyan
69
+ scheme.define :syntax_comment, fg: :solarized_base01
70
+ scheme.define :syntax_number, fg: :solarized_magenta
71
+ scheme.define :syntax_symbol, fg: :solarized_blue
72
+ scheme.define :syntax_constant, fg: :solarized_yellow
73
+ scheme.define :syntax_operator, fg: :solarized_base0
74
+ scheme.define :syntax_identifier, fg: :solarized_base0
75
+ scheme.define :syntax_preprocessor, fg: :solarized_orange
76
+ scheme.define :syntax_instance_variable, fg: :solarized_blue
77
+ scheme.define :syntax_global_variable, fg: :solarized_red
78
+ scheme.define :syntax_method_call, fg: :solarized_blue
79
+ scheme.define :syntax_type, fg: :solarized_yellow
80
+ # Diff highlighting
81
+ scheme.define :diff_add, fg: :solarized_green
82
+ scheme.define :diff_delete, fg: :solarized_red
83
+ scheme.define :diff_hunk, fg: :solarized_cyan
84
+ scheme.define :diff_header, fg: :solarized_yellow, bold: true
85
+ scheme
86
+ end
87
+
88
+ def self.solarized_light
89
+ scheme = ColorScheme.new("solarized_light")
90
+ scheme.define :normal, fg: :solarized_base00, bg: :solarized_base3
91
+ scheme.define :status_line, fg: :solarized_base01, bg: :solarized_base2
92
+ scheme.define :status_line_mode, fg: :solarized_base3, bg: :solarized_blue, bold: true
93
+ scheme.define :search_highlight, fg: :solarized_base3, bg: :solarized_yellow
94
+ scheme.define :visual_selection, fg: :solarized_base3, bg: :solarized_blue
95
+ scheme.define :line_number, fg: :solarized_base1, bg: :solarized_base3
96
+ scheme.define :message_error, fg: :solarized_red, bg: :solarized_base3, bold: true
97
+ scheme.define :message_info, fg: :solarized_cyan, bg: :solarized_base3
98
+ scheme.define :separator, fg: :solarized_base01, bg: :solarized_base2
99
+ scheme.define :command_line, fg: :solarized_base00, bg: :solarized_base3
100
+ scheme.define :tab_bar, fg: :solarized_base01, bg: :solarized_base2
101
+ scheme.define :tab_bar_active, fg: :solarized_base3, bg: :solarized_blue, bold: true
102
+ scheme.define :completion_popup, fg: :solarized_base01, bg: :solarized_base2
103
+ scheme.define :completion_popup_selected, fg: :solarized_base3, bg: :solarized_blue, bold: true
104
+ # Syntax highlighting
105
+ scheme.define :syntax_keyword, fg: :solarized_green, bold: true
106
+ scheme.define :syntax_string, fg: :solarized_cyan
107
+ scheme.define :syntax_comment, fg: :solarized_base1
108
+ scheme.define :syntax_number, fg: :solarized_magenta
109
+ scheme.define :syntax_symbol, fg: :solarized_blue
110
+ scheme.define :syntax_constant, fg: :solarized_yellow
111
+ scheme.define :syntax_operator, fg: :solarized_base00
112
+ scheme.define :syntax_identifier, fg: :solarized_base00
113
+ scheme.define :syntax_preprocessor, fg: :solarized_orange
114
+ scheme.define :syntax_instance_variable, fg: :solarized_blue
115
+ scheme.define :syntax_global_variable, fg: :solarized_red
116
+ scheme.define :syntax_method_call, fg: :solarized_blue
117
+ scheme.define :syntax_type, fg: :solarized_yellow
118
+ # Diff highlighting
119
+ scheme.define :diff_add, fg: :solarized_green
120
+ scheme.define :diff_delete, fg: :solarized_red
121
+ scheme.define :diff_hunk, fg: :solarized_cyan
122
+ scheme.define :diff_header, fg: :solarized_yellow, bold: true
123
+ scheme
124
+ end
125
+
126
+ def self.monokai
127
+ scheme = ColorScheme.new("monokai")
128
+ scheme.define :normal, fg: :monokai_fg, bg: :monokai_bg
129
+ scheme.define :status_line, fg: :monokai_fg, bg: :monokai_purple
130
+ scheme.define :status_line_mode, fg: :monokai_bg, bg: :monokai_pink, bold: true
131
+ scheme.define :search_highlight, fg: :monokai_bg, bg: :monokai_yellow
132
+ scheme.define :visual_selection, fg: :monokai_fg, bg: :monokai_purple
133
+ scheme.define :line_number, fg: :monokai_cyan, bg: :monokai_bg
134
+ scheme.define :message_error, fg: :monokai_pink, bg: :monokai_bg, bold: true
135
+ scheme.define :message_info, fg: :monokai_green, bg: :monokai_bg
136
+ scheme.define :separator, fg: :monokai_fg, bg: :monokai_purple
137
+ scheme.define :command_line, fg: :monokai_fg, bg: :monokai_bg
138
+ scheme.define :tab_bar, fg: :monokai_fg, bg: :monokai_purple
139
+ scheme.define :tab_bar_active, fg: :monokai_bg, bg: :monokai_pink, bold: true
140
+ scheme.define :completion_popup, fg: :monokai_fg, bg: :monokai_purple
141
+ scheme.define :completion_popup_selected, fg: :monokai_bg, bg: :monokai_pink, bold: true
142
+ # Syntax highlighting
143
+ scheme.define :syntax_keyword, fg: :monokai_pink, bold: true
144
+ scheme.define :syntax_string, fg: :monokai_yellow
145
+ scheme.define :syntax_comment, fg: :monokai_cyan
146
+ scheme.define :syntax_number, fg: :monokai_purple
147
+ scheme.define :syntax_symbol, fg: :monokai_orange
148
+ scheme.define :syntax_constant, fg: :monokai_purple
149
+ scheme.define :syntax_operator, fg: :monokai_pink
150
+ scheme.define :syntax_identifier, fg: :monokai_fg
151
+ scheme.define :syntax_preprocessor, fg: :monokai_pink
152
+ scheme.define :syntax_instance_variable, fg: :monokai_orange
153
+ scheme.define :syntax_global_variable, fg: :monokai_pink
154
+ scheme.define :syntax_method_call, fg: :monokai_green
155
+ scheme.define :syntax_type, fg: :monokai_cyan
156
+ # Diff highlighting
157
+ scheme.define :diff_add, fg: :monokai_green
158
+ scheme.define :diff_delete, fg: :monokai_pink
159
+ scheme.define :diff_hunk, fg: :monokai_cyan
160
+ scheme.define :diff_header, fg: :monokai_yellow, bold: true
161
+ scheme
162
+ end
163
+
164
+ def self.nord
165
+ scheme = ColorScheme.new("nord")
166
+ scheme.define :normal, fg: :nord_snow0, bg: :nord_polar0
167
+ scheme.define :status_line, fg: :nord_snow0, bg: :nord_polar2
168
+ scheme.define :status_line_mode, fg: :nord_polar0, bg: :nord_frost1, bold: true
169
+ scheme.define :search_highlight, fg: :nord_polar0, bg: :nord_aurora_yellow
170
+ scheme.define :visual_selection, fg: :nord_snow0, bg: :nord_frost3
171
+ scheme.define :line_number, fg: :nord_polar3, bg: :nord_polar0
172
+ scheme.define :message_error, fg: :nord_aurora_red, bg: :nord_polar0, bold: true
173
+ scheme.define :message_info, fg: :nord_frost1, bg: :nord_polar0
174
+ scheme.define :separator, fg: :nord_snow0, bg: :nord_polar2
175
+ scheme.define :command_line, fg: :nord_snow0, bg: :nord_polar0
176
+ scheme.define :tab_bar, fg: :nord_snow0, bg: :nord_polar2
177
+ scheme.define :tab_bar_active, fg: :nord_polar0, bg: :nord_frost1, bold: true
178
+ scheme.define :completion_popup, fg: :nord_snow0, bg: :nord_polar2
179
+ scheme.define :completion_popup_selected, fg: :nord_polar0, bg: :nord_frost1, bold: true
180
+ # Syntax highlighting
181
+ scheme.define :syntax_keyword, fg: :nord_frost2, bold: true
182
+ scheme.define :syntax_string, fg: :nord_aurora_green
183
+ scheme.define :syntax_comment, fg: :nord_polar3
184
+ scheme.define :syntax_number, fg: :nord_aurora_purple
185
+ scheme.define :syntax_symbol, fg: :nord_aurora_yellow
186
+ scheme.define :syntax_constant, fg: :nord_aurora_yellow
187
+ scheme.define :syntax_operator, fg: :nord_frost2
188
+ scheme.define :syntax_identifier, fg: :nord_snow0
189
+ scheme.define :syntax_preprocessor, fg: :nord_aurora_purple
190
+ scheme.define :syntax_instance_variable, fg: :nord_frost0
191
+ scheme.define :syntax_global_variable, fg: :nord_aurora_red
192
+ scheme.define :syntax_method_call, fg: :nord_frost1
193
+ scheme.define :syntax_type, fg: :nord_frost3
194
+ # Diff highlighting
195
+ scheme.define :diff_add, fg: :nord_aurora_green
196
+ scheme.define :diff_delete, fg: :nord_aurora_red
197
+ scheme.define :diff_hunk, fg: :nord_frost1
198
+ scheme.define :diff_header, fg: :nord_aurora_yellow, bold: true
199
+ scheme
200
+ end
201
+
202
+ def self.gruvbox_dark
203
+ scheme = ColorScheme.new("gruvbox_dark")
204
+ scheme.define :normal, fg: :gruvbox_fg, bg: :gruvbox_bg
205
+ scheme.define :status_line, fg: :gruvbox_fg, bg: :gruvbox_gray
206
+ scheme.define :status_line_mode, fg: :gruvbox_bg, bg: :gruvbox_orange, bold: true
207
+ scheme.define :search_highlight, fg: :gruvbox_bg, bg: :gruvbox_yellow
208
+ scheme.define :visual_selection, fg: :gruvbox_fg, bg: :gruvbox_blue
209
+ scheme.define :line_number, fg: :gruvbox_gray, bg: :gruvbox_bg
210
+ scheme.define :message_error, fg: :gruvbox_red, bg: :gruvbox_bg, bold: true
211
+ scheme.define :message_info, fg: :gruvbox_aqua, bg: :gruvbox_bg
212
+ scheme.define :separator, fg: :gruvbox_fg, bg: :gruvbox_gray
213
+ scheme.define :command_line, fg: :gruvbox_fg, bg: :gruvbox_bg
214
+ scheme.define :tab_bar, fg: :gruvbox_fg, bg: :gruvbox_gray
215
+ scheme.define :tab_bar_active, fg: :gruvbox_bg, bg: :gruvbox_orange, bold: true
216
+ scheme.define :completion_popup, fg: :gruvbox_fg, bg: :gruvbox_gray
217
+ scheme.define :completion_popup_selected, fg: :gruvbox_bg, bg: :gruvbox_orange, bold: true
218
+ # Syntax highlighting
219
+ scheme.define :syntax_keyword, fg: :gruvbox_red, bold: true
220
+ scheme.define :syntax_string, fg: :gruvbox_green
221
+ scheme.define :syntax_comment, fg: :gruvbox_gray
222
+ scheme.define :syntax_number, fg: :gruvbox_purple
223
+ scheme.define :syntax_symbol, fg: :gruvbox_aqua
224
+ scheme.define :syntax_constant, fg: :gruvbox_yellow
225
+ scheme.define :syntax_operator, fg: :gruvbox_fg
226
+ scheme.define :syntax_identifier, fg: :gruvbox_fg
227
+ scheme.define :syntax_preprocessor, fg: :gruvbox_aqua
228
+ scheme.define :syntax_instance_variable, fg: :gruvbox_blue
229
+ scheme.define :syntax_global_variable, fg: :gruvbox_red
230
+ scheme.define :syntax_method_call, fg: :gruvbox_aqua
231
+ # Diff highlighting
232
+ scheme.define :diff_add, fg: :gruvbox_green
233
+ scheme.define :diff_delete, fg: :gruvbox_red
234
+ scheme.define :diff_hunk, fg: :gruvbox_aqua
235
+ scheme.define :diff_header, fg: :gruvbox_yellow, bold: true
236
+ scheme
237
+ end
238
+
239
+ def self.dracula
240
+ scheme = ColorScheme.new("dracula")
241
+ scheme.define :normal, fg: :dracula_fg, bg: :dracula_bg
242
+ scheme.define :status_line, fg: :dracula_fg, bg: :dracula_selection
243
+ scheme.define :status_line_mode, fg: :dracula_bg, bg: :dracula_purple, bold: true
244
+ scheme.define :search_highlight, fg: :dracula_bg, bg: :dracula_yellow
245
+ scheme.define :visual_selection, fg: :dracula_fg, bg: :dracula_purple
246
+ scheme.define :line_number, fg: :dracula_comment, bg: :dracula_bg
247
+ scheme.define :message_error, fg: :dracula_red, bg: :dracula_bg, bold: true
248
+ scheme.define :message_info, fg: :dracula_cyan, bg: :dracula_bg
249
+ scheme.define :separator, fg: :dracula_fg, bg: :dracula_selection
250
+ scheme.define :command_line, fg: :dracula_fg, bg: :dracula_bg
251
+ scheme.define :tab_bar, fg: :dracula_fg, bg: :dracula_selection
252
+ scheme.define :tab_bar_active, fg: :dracula_bg, bg: :dracula_purple, bold: true
253
+ scheme.define :completion_popup, fg: :dracula_fg, bg: :dracula_selection
254
+ scheme.define :completion_popup_selected, fg: :dracula_bg, bg: :dracula_purple, bold: true
255
+ # Syntax highlighting
256
+ scheme.define :syntax_keyword, fg: :dracula_pink, bold: true
257
+ scheme.define :syntax_string, fg: :dracula_yellow
258
+ scheme.define :syntax_comment, fg: :dracula_comment
259
+ scheme.define :syntax_number, fg: :dracula_purple
260
+ scheme.define :syntax_symbol, fg: :dracula_cyan
261
+ scheme.define :syntax_constant, fg: :dracula_cyan
262
+ scheme.define :syntax_operator, fg: :dracula_pink
263
+ scheme.define :syntax_identifier, fg: :dracula_fg
264
+ scheme.define :syntax_preprocessor, fg: :dracula_pink
265
+ scheme.define :syntax_instance_variable, fg: :dracula_purple
266
+ scheme.define :syntax_global_variable, fg: :dracula_red
267
+ scheme.define :syntax_method_call, fg: :dracula_green
268
+ scheme.define :syntax_type, fg: :dracula_cyan
269
+ # Diff highlighting
270
+ scheme.define :diff_add, fg: :dracula_green
271
+ scheme.define :diff_delete, fg: :dracula_red
272
+ scheme.define :diff_hunk, fg: :dracula_cyan
273
+ scheme.define :diff_header, fg: :dracula_yellow, bold: true
274
+ scheme
275
+ end
276
+
277
+ def self.tokyo_night
278
+ scheme = ColorScheme.new("tokyo_night")
279
+ scheme.define :normal, fg: :tokyo_fg, bg: :tokyo_bg
280
+ scheme.define :status_line, fg: :tokyo_fg, bg: :tokyo_comment
281
+ scheme.define :status_line_mode, fg: :tokyo_bg, bg: :tokyo_blue, bold: true
282
+ scheme.define :search_highlight, fg: :tokyo_bg, bg: :tokyo_yellow
283
+ scheme.define :visual_selection, fg: :tokyo_fg, bg: :tokyo_purple
284
+ scheme.define :line_number, fg: :tokyo_comment, bg: :tokyo_bg
285
+ scheme.define :message_error, fg: :tokyo_red, bg: :tokyo_bg, bold: true
286
+ scheme.define :message_info, fg: :tokyo_cyan, bg: :tokyo_bg
287
+ scheme.define :separator, fg: :tokyo_fg, bg: :tokyo_comment
288
+ scheme.define :command_line, fg: :tokyo_fg, bg: :tokyo_bg
289
+ scheme.define :tab_bar, fg: :tokyo_fg, bg: :tokyo_comment
290
+ scheme.define :tab_bar_active, fg: :tokyo_bg, bg: :tokyo_blue, bold: true
291
+ scheme.define :completion_popup, fg: :tokyo_fg, bg: :tokyo_comment
292
+ scheme.define :completion_popup_selected, fg: :tokyo_bg, bg: :tokyo_blue, bold: true
293
+ # Syntax highlighting
294
+ scheme.define :syntax_keyword, fg: :tokyo_purple, bold: true
295
+ scheme.define :syntax_string, fg: :tokyo_green
296
+ scheme.define :syntax_comment, fg: :tokyo_comment
297
+ scheme.define :syntax_number, fg: :tokyo_orange
298
+ scheme.define :syntax_symbol, fg: :tokyo_cyan
299
+ scheme.define :syntax_constant, fg: :tokyo_yellow
300
+ scheme.define :syntax_operator, fg: :tokyo_blue
301
+ scheme.define :syntax_identifier, fg: :tokyo_fg
302
+ scheme.define :syntax_preprocessor, fg: :tokyo_purple
303
+ scheme.define :syntax_instance_variable, fg: :tokyo_red
304
+ scheme.define :syntax_global_variable, fg: :tokyo_red
305
+ scheme.define :syntax_method_call, fg: :tokyo_blue
306
+ scheme.define :syntax_type, fg: :tokyo_cyan
307
+ # Diff highlighting
308
+ scheme.define :diff_add, fg: :tokyo_green
309
+ scheme.define :diff_delete, fg: :tokyo_red
310
+ scheme.define :diff_hunk, fg: :tokyo_cyan
311
+ scheme.define :diff_header, fg: :tokyo_yellow, bold: true
312
+ scheme
313
+ end
314
+ end
315
+ end
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mui
4
+ # Manages undo/redo stacks for the editor
5
+ class UndoManager
6
+ MAX_STACK_SIZE = 1000
7
+
8
+ def initialize
9
+ @undo_stack = []
10
+ @redo_stack = []
11
+ @current_group = nil
12
+ end
13
+
14
+ # Record a single action
15
+ def record(action)
16
+ if @current_group
17
+ @current_group << action
18
+ else
19
+ push_to_undo_stack(action)
20
+ end
21
+ @redo_stack.clear
22
+ end
23
+
24
+ # Begin a group (for Insert mode)
25
+ def begin_group
26
+ @current_group = []
27
+ end
28
+
29
+ # End a group (when leaving Insert mode)
30
+ def end_group
31
+ return unless @current_group
32
+
33
+ push_to_undo_stack(GroupAction.new(@current_group)) unless @current_group.empty?
34
+ @current_group = nil
35
+ end
36
+
37
+ # Check if currently in a group
38
+ def in_group?
39
+ !@current_group.nil?
40
+ end
41
+
42
+ def undo(buffer)
43
+ return false if @undo_stack.empty?
44
+
45
+ action = @undo_stack.pop
46
+ action.undo(buffer)
47
+ @redo_stack.push(action)
48
+ true
49
+ end
50
+
51
+ def redo(buffer)
52
+ return false if @redo_stack.empty?
53
+
54
+ action = @redo_stack.pop
55
+ action.execute(buffer)
56
+ @undo_stack.push(action)
57
+ true
58
+ end
59
+
60
+ def can_undo?
61
+ !@undo_stack.empty?
62
+ end
63
+
64
+ def can_redo?
65
+ !@redo_stack.empty?
66
+ end
67
+
68
+ def undo_stack_size
69
+ @undo_stack.size
70
+ end
71
+
72
+ def redo_stack_size
73
+ @redo_stack.size
74
+ end
75
+
76
+ private
77
+
78
+ def push_to_undo_stack(action)
79
+ @undo_stack.shift if @undo_stack.size >= MAX_STACK_SIZE
80
+ @undo_stack.push(action)
81
+ end
82
+ end
83
+ end