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.
- checksums.yaml +4 -4
- data/.rubocop_todo.yml +158 -0
- data/CHANGELOG.md +349 -0
- data/exe/mui +1 -2
- data/lib/mui/autocmd.rb +66 -0
- data/lib/mui/buffer.rb +275 -0
- data/lib/mui/buffer_word_cache.rb +131 -0
- data/lib/mui/buffer_word_completer.rb +77 -0
- data/lib/mui/color_manager.rb +136 -0
- data/lib/mui/color_scheme.rb +63 -0
- data/lib/mui/command_completer.rb +21 -0
- data/lib/mui/command_context.rb +90 -0
- data/lib/mui/command_line.rb +137 -0
- data/lib/mui/command_registry.rb +25 -0
- data/lib/mui/completion_renderer.rb +84 -0
- data/lib/mui/completion_state.rb +58 -0
- data/lib/mui/config.rb +56 -0
- data/lib/mui/editor.rb +319 -0
- data/lib/mui/error.rb +29 -0
- data/lib/mui/file_completer.rb +51 -0
- data/lib/mui/floating_window.rb +161 -0
- data/lib/mui/handler_result.rb +101 -0
- data/lib/mui/highlight.rb +22 -0
- data/lib/mui/highlighters/base.rb +23 -0
- data/lib/mui/highlighters/search_highlighter.rb +26 -0
- data/lib/mui/highlighters/selection_highlighter.rb +48 -0
- data/lib/mui/highlighters/syntax_highlighter.rb +105 -0
- data/lib/mui/input.rb +17 -0
- data/lib/mui/insert_completion_renderer.rb +92 -0
- data/lib/mui/insert_completion_state.rb +77 -0
- data/lib/mui/job.rb +81 -0
- data/lib/mui/job_manager.rb +113 -0
- data/lib/mui/key_code.rb +30 -0
- data/lib/mui/key_handler/base.rb +100 -0
- data/lib/mui/key_handler/command_mode.rb +443 -0
- data/lib/mui/key_handler/insert_mode.rb +354 -0
- data/lib/mui/key_handler/motions/motion_handler.rb +56 -0
- data/lib/mui/key_handler/normal_mode.rb +579 -0
- data/lib/mui/key_handler/operators/base_operator.rb +134 -0
- data/lib/mui/key_handler/operators/change_operator.rb +179 -0
- data/lib/mui/key_handler/operators/delete_operator.rb +176 -0
- data/lib/mui/key_handler/operators/paste_operator.rb +113 -0
- data/lib/mui/key_handler/operators/yank_operator.rb +127 -0
- data/lib/mui/key_handler/search_mode.rb +188 -0
- data/lib/mui/key_handler/visual_line_mode.rb +20 -0
- data/lib/mui/key_handler/visual_mode.rb +397 -0
- data/lib/mui/key_handler/window_command.rb +112 -0
- data/lib/mui/key_handler.rb +16 -0
- data/lib/mui/layout/calculator.rb +15 -0
- data/lib/mui/layout/leaf_node.rb +33 -0
- data/lib/mui/layout/node.rb +29 -0
- data/lib/mui/layout/split_node.rb +132 -0
- data/lib/mui/line_renderer.rb +122 -0
- data/lib/mui/mode.rb +13 -0
- data/lib/mui/mode_manager.rb +185 -0
- data/lib/mui/motion.rb +139 -0
- data/lib/mui/plugin.rb +35 -0
- data/lib/mui/plugin_manager.rb +106 -0
- data/lib/mui/register.rb +110 -0
- data/lib/mui/screen.rb +85 -0
- data/lib/mui/search_completer.rb +50 -0
- data/lib/mui/search_input.rb +40 -0
- data/lib/mui/search_state.rb +88 -0
- data/lib/mui/selection.rb +55 -0
- data/lib/mui/status_line_renderer.rb +40 -0
- data/lib/mui/syntax/language_detector.rb +74 -0
- data/lib/mui/syntax/lexer_base.rb +106 -0
- data/lib/mui/syntax/lexers/c_lexer.rb +127 -0
- data/lib/mui/syntax/lexers/ruby_lexer.rb +114 -0
- data/lib/mui/syntax/token.rb +42 -0
- data/lib/mui/syntax/token_cache.rb +91 -0
- data/lib/mui/tab_bar_renderer.rb +87 -0
- data/lib/mui/tab_manager.rb +96 -0
- data/lib/mui/tab_page.rb +35 -0
- data/lib/mui/terminal_adapter/base.rb +92 -0
- data/lib/mui/terminal_adapter/curses.rb +162 -0
- data/lib/mui/terminal_adapter.rb +4 -0
- data/lib/mui/themes/default.rb +315 -0
- data/lib/mui/undo_manager.rb +83 -0
- data/lib/mui/undoable_action.rb +175 -0
- data/lib/mui/unicode_width.rb +100 -0
- data/lib/mui/version.rb +1 -1
- data/lib/mui/window.rb +158 -0
- data/lib/mui/window_manager.rb +249 -0
- data/lib/mui.rb +156 -2
- 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,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
|