mui 0.1.0 → 0.3.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 +163 -0
- data/CHANGELOG.md +448 -0
- data/README.md +309 -6
- data/docs/_config.yml +56 -0
- data/docs/configuration.md +301 -0
- data/docs/getting-started.md +140 -0
- data/docs/index.md +55 -0
- data/docs/jobs.md +297 -0
- data/docs/keybindings.md +229 -0
- data/docs/plugins.md +285 -0
- data/docs/syntax-highlighting.md +149 -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 +30 -0
- data/lib/mui/command_context.rb +90 -0
- data/lib/mui/command_history.rb +89 -0
- data/lib/mui/command_line.rb +167 -0
- data/lib/mui/command_registry.rb +44 -0
- data/lib/mui/completion_renderer.rb +84 -0
- data/lib/mui/completion_state.rb +58 -0
- data/lib/mui/config.rb +58 -0
- data/lib/mui/editor.rb +395 -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 +107 -0
- data/lib/mui/highlight.rb +22 -0
- data/lib/mui/highlighters/base.rb +23 -0
- data/lib/mui/highlighters/search_highlighter.rb +27 -0
- data/lib/mui/highlighters/selection_highlighter.rb +48 -0
- data/lib/mui/highlighters/syntax_highlighter.rb +107 -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 +187 -0
- data/lib/mui/key_handler/command_mode.rb +511 -0
- data/lib/mui/key_handler/insert_mode.rb +323 -0
- data/lib/mui/key_handler/motions/motion_handler.rb +56 -0
- data/lib/mui/key_handler/normal_mode.rb +552 -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 +119 -0
- data/lib/mui/key_handler/operators/yank_operator.rb +127 -0
- data/lib/mui/key_handler/search_mode.rb +191 -0
- data/lib/mui/key_handler/visual_line_mode.rb +20 -0
- data/lib/mui/key_handler/visual_mode.rb +402 -0
- data/lib/mui/key_handler/window_command.rb +112 -0
- data/lib/mui/key_handler.rb +16 -0
- data/lib/mui/key_notation_parser.rb +152 -0
- data/lib/mui/key_sequence.rb +67 -0
- data/lib/mui/key_sequence_buffer.rb +85 -0
- data/lib/mui/key_sequence_handler.rb +163 -0
- data/lib/mui/key_sequence_matcher.rb +79 -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 +173 -0
- data/lib/mui/mode.rb +13 -0
- data/lib/mui/mode_manager.rb +186 -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 +103 -0
- data/lib/mui/search_completer.rb +50 -0
- data/lib/mui/search_input.rb +40 -0
- data/lib/mui/search_state.rb +121 -0
- data/lib/mui/selection.rb +55 -0
- data/lib/mui/status_line_renderer.rb +40 -0
- data/lib/mui/syntax/language_detector.rb +106 -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/css_lexer.rb +121 -0
- data/lib/mui/syntax/lexers/go_lexer.rb +205 -0
- data/lib/mui/syntax/lexers/html_lexer.rb +118 -0
- data/lib/mui/syntax/lexers/javascript_lexer.rb +197 -0
- data/lib/mui/syntax/lexers/markdown_lexer.rb +210 -0
- data/lib/mui/syntax/lexers/ruby_lexer.rb +114 -0
- data/lib/mui/syntax/lexers/rust_lexer.rb +148 -0
- data/lib/mui/syntax/lexers/typescript_lexer.rb +203 -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 +164 -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 +201 -0
- data/lib/mui/window_manager.rb +256 -0
- data/lib/mui/wrap_cache.rb +40 -0
- data/lib/mui/wrap_helper.rb +84 -0
- data/lib/mui.rb +171 -2
- metadata +123 -5
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Mui
|
|
4
|
+
class WindowManager
|
|
5
|
+
# Predicates for determining if a window is in a given direction from another
|
|
6
|
+
DIRECTION_PREDICATES = {
|
|
7
|
+
left: ->(w, active) { w.x + w.width <= active.x },
|
|
8
|
+
right: ->(w, active) { w.x >= active.x + active.width },
|
|
9
|
+
up: ->(w, active) { w.y + w.height <= active.y },
|
|
10
|
+
down: ->(w, active) { w.y >= active.y + active.height }
|
|
11
|
+
}.freeze
|
|
12
|
+
|
|
13
|
+
attr_reader :active_window, :layout_root
|
|
14
|
+
|
|
15
|
+
def initialize(screen, color_scheme: nil)
|
|
16
|
+
@screen = screen
|
|
17
|
+
@color_scheme = color_scheme
|
|
18
|
+
@active_window = nil
|
|
19
|
+
@layout_root = nil
|
|
20
|
+
@layout_calculator = Layout::Calculator.new
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def add_window(buffer)
|
|
24
|
+
window = create_window(buffer)
|
|
25
|
+
|
|
26
|
+
@layout_root = Layout::LeafNode.new(window) if @layout_root.nil?
|
|
27
|
+
|
|
28
|
+
@active_window ||= window
|
|
29
|
+
update_layout
|
|
30
|
+
window
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def remove_window(window)
|
|
34
|
+
return false if single_window?
|
|
35
|
+
|
|
36
|
+
node = @layout_root.find_window_node(window)
|
|
37
|
+
return false unless node
|
|
38
|
+
|
|
39
|
+
remove_node(node)
|
|
40
|
+
|
|
41
|
+
@active_window = windows.first if @active_window == window
|
|
42
|
+
update_layout
|
|
43
|
+
true
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def split_horizontal(buffer = nil)
|
|
47
|
+
split(:horizontal, buffer)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def split_vertical(buffer = nil)
|
|
51
|
+
split(:vertical, buffer)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def close_current_window
|
|
55
|
+
return false if single_window?
|
|
56
|
+
|
|
57
|
+
current_node = @layout_root.find_window_node(@active_window)
|
|
58
|
+
return false unless current_node
|
|
59
|
+
|
|
60
|
+
remove_node(current_node)
|
|
61
|
+
|
|
62
|
+
@active_window = windows.first
|
|
63
|
+
update_layout
|
|
64
|
+
true
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def close_all_except_current
|
|
68
|
+
return if single_window?
|
|
69
|
+
|
|
70
|
+
@layout_root = Layout::LeafNode.new(@active_window)
|
|
71
|
+
@layout_root.parent = nil
|
|
72
|
+
update_layout
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def render_all(screen, selection: nil, search_state: nil)
|
|
76
|
+
windows.each do |window|
|
|
77
|
+
window_selection = window == @active_window ? selection : nil
|
|
78
|
+
window.render(screen, selection: window_selection, search_state:)
|
|
79
|
+
end
|
|
80
|
+
render_separators(screen)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def separators
|
|
84
|
+
return [] unless @layout_root.respond_to?(:separators)
|
|
85
|
+
|
|
86
|
+
@layout_root.separators
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def update_sizes
|
|
90
|
+
update_layout
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def focus_next
|
|
94
|
+
focus_cycle(1)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def focus_previous
|
|
98
|
+
focus_cycle(-1)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def focus_direction(direction)
|
|
102
|
+
return unless windows.size > 1
|
|
103
|
+
|
|
104
|
+
target = find_window_in_direction(direction)
|
|
105
|
+
@active_window = target if target
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def focus_window(window)
|
|
109
|
+
return false unless windows.include?(window)
|
|
110
|
+
|
|
111
|
+
@active_window = window
|
|
112
|
+
true
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def windows
|
|
116
|
+
return [] unless @layout_root
|
|
117
|
+
|
|
118
|
+
@layout_root.windows
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def window_count
|
|
122
|
+
windows.size
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def single_window?
|
|
126
|
+
windows.size <= 1
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def update_layout(y_offset: 0)
|
|
130
|
+
return unless @layout_root
|
|
131
|
+
|
|
132
|
+
@layout_calculator.calculate(
|
|
133
|
+
@layout_root, 0, y_offset, @screen.width, @screen.height - 1 - y_offset
|
|
134
|
+
)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
private
|
|
138
|
+
|
|
139
|
+
def focus_cycle(offset)
|
|
140
|
+
all_windows = windows
|
|
141
|
+
return if all_windows.size <= 1
|
|
142
|
+
|
|
143
|
+
current_index = all_windows.index(@active_window) || 0
|
|
144
|
+
@active_window = all_windows[(current_index + offset) % all_windows.size]
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def split(direction, buffer)
|
|
148
|
+
return nil unless @active_window
|
|
149
|
+
|
|
150
|
+
target_buffer = buffer || @active_window.buffer
|
|
151
|
+
new_window = create_window(target_buffer)
|
|
152
|
+
|
|
153
|
+
current_node = @layout_root.find_window_node(@active_window)
|
|
154
|
+
return nil unless current_node
|
|
155
|
+
|
|
156
|
+
parent = current_node.parent
|
|
157
|
+
|
|
158
|
+
new_leaf = Layout::LeafNode.new(new_window)
|
|
159
|
+
split_node = Layout::SplitNode.new(
|
|
160
|
+
direction:,
|
|
161
|
+
children: [current_node, new_leaf],
|
|
162
|
+
ratio: 0.5
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
if parent
|
|
166
|
+
parent.replace_child(current_node, split_node)
|
|
167
|
+
else
|
|
168
|
+
@layout_root = split_node
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
@active_window = new_window
|
|
172
|
+
update_layout
|
|
173
|
+
new_window
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def remove_node(node)
|
|
177
|
+
parent = node.parent
|
|
178
|
+
return false unless parent
|
|
179
|
+
|
|
180
|
+
parent.remove_child(node)
|
|
181
|
+
|
|
182
|
+
if parent.children.size == 1
|
|
183
|
+
remaining_child = parent.children.first
|
|
184
|
+
grandparent = parent.parent
|
|
185
|
+
|
|
186
|
+
if grandparent
|
|
187
|
+
grandparent.replace_child(parent, remaining_child)
|
|
188
|
+
else
|
|
189
|
+
@layout_root = remaining_child
|
|
190
|
+
remaining_child.parent = nil
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
true
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
def find_window_in_direction(direction)
|
|
198
|
+
return nil unless @active_window
|
|
199
|
+
|
|
200
|
+
predicate = DIRECTION_PREDICATES[direction]
|
|
201
|
+
return nil unless predicate
|
|
202
|
+
|
|
203
|
+
current_x = @active_window.x + (@active_window.width / 2)
|
|
204
|
+
current_y = @active_window.y + (@active_window.height / 2)
|
|
205
|
+
|
|
206
|
+
candidates = windows.reject { |w| w == @active_window }
|
|
207
|
+
.select { |w| predicate.call(w, @active_window) }
|
|
208
|
+
|
|
209
|
+
candidates.min_by do |w|
|
|
210
|
+
wx = w.x + (w.width / 2)
|
|
211
|
+
wy = w.y + (w.height / 2)
|
|
212
|
+
Math.sqrt(((wx - current_x)**2) + ((wy - current_y)**2))
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
def create_window(buffer)
|
|
217
|
+
Window.new(
|
|
218
|
+
buffer,
|
|
219
|
+
x: 0,
|
|
220
|
+
y: 0,
|
|
221
|
+
width: @screen.width,
|
|
222
|
+
height: @screen.height,
|
|
223
|
+
color_scheme: @color_scheme
|
|
224
|
+
)
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
def render_separators(screen)
|
|
228
|
+
style = separator_style
|
|
229
|
+
separators.each do |sep|
|
|
230
|
+
case sep[:type]
|
|
231
|
+
when :horizontal
|
|
232
|
+
render_horizontal_separator(screen, sep, style)
|
|
233
|
+
when :vertical
|
|
234
|
+
render_vertical_separator(screen, sep, style)
|
|
235
|
+
end
|
|
236
|
+
end
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
def render_horizontal_separator(screen, sep, style)
|
|
240
|
+
line = "─" * sep[:length]
|
|
241
|
+
screen.put_with_style(sep[:y], sep[:x], line, style)
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
def render_vertical_separator(screen, sep, style)
|
|
245
|
+
sep[:length].times do |i|
|
|
246
|
+
screen.put_with_style(sep[:y] + i, sep[:x], "│", style)
|
|
247
|
+
end
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
def separator_style
|
|
251
|
+
return nil unless @color_scheme
|
|
252
|
+
|
|
253
|
+
@color_scheme[:separator]
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Mui
|
|
4
|
+
# Caches wrap calculation results for performance
|
|
5
|
+
# Cache key: [line_content, width]
|
|
6
|
+
class WrapCache
|
|
7
|
+
def initialize
|
|
8
|
+
@cache = {}
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def get(line, width)
|
|
12
|
+
key = cache_key(line, width)
|
|
13
|
+
@cache[key]
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def set(line, width, result)
|
|
17
|
+
key = cache_key(line, width)
|
|
18
|
+
@cache[key] = result
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def invalidate(line)
|
|
22
|
+
# Remove all entries for this line content
|
|
23
|
+
@cache.delete_if { |k, _| k.start_with?("#{line}\x00") }
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def clear
|
|
27
|
+
@cache.clear
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def size
|
|
31
|
+
@cache.size
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def cache_key(line, width)
|
|
37
|
+
"#{line}\x00#{width}"
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Mui
|
|
4
|
+
# Helper module for line wrapping calculations
|
|
5
|
+
module WrapHelper
|
|
6
|
+
class << self
|
|
7
|
+
# Wraps a line into screen lines based on display width
|
|
8
|
+
# Returns: [{ text:, start_col:, end_col: }, ...]
|
|
9
|
+
def wrap_line(line, width, cache: nil)
|
|
10
|
+
return [{ text: "", start_col: 0, end_col: 0 }] if line.nil? || line.empty?
|
|
11
|
+
return [{ text: line, start_col: 0, end_col: line.length }] if width <= 0
|
|
12
|
+
|
|
13
|
+
if cache
|
|
14
|
+
cached = cache.get(line, width)
|
|
15
|
+
return cached if cached
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
result = compute_wrap(line, width)
|
|
19
|
+
cache&.set(line, width, result)
|
|
20
|
+
result
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Converts logical column to screen position
|
|
24
|
+
# Returns: [screen_row_offset, screen_col]
|
|
25
|
+
def logical_to_screen(line, col, width, cache: nil)
|
|
26
|
+
return [0, 0] if line.nil? || line.empty? || col <= 0
|
|
27
|
+
|
|
28
|
+
wrapped = wrap_line(line, width, cache:)
|
|
29
|
+
|
|
30
|
+
wrapped.each_with_index do |segment, row|
|
|
31
|
+
next unless col <= segment[:end_col]
|
|
32
|
+
|
|
33
|
+
# Found the segment containing this column
|
|
34
|
+
relative_col = col - segment[:start_col]
|
|
35
|
+
prefix = segment[:text][0, relative_col] || ""
|
|
36
|
+
screen_col = UnicodeWidth.string_width(prefix)
|
|
37
|
+
return [row, screen_col]
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Column is past end of line, return last position
|
|
41
|
+
last_segment = wrapped.last
|
|
42
|
+
last_text = last_segment[:text]
|
|
43
|
+
[wrapped.size - 1, UnicodeWidth.string_width(last_text)]
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Returns the number of screen lines for a logical line
|
|
47
|
+
def screen_line_count(line, width, cache: nil)
|
|
48
|
+
wrap_line(line, width, cache:).size
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
private
|
|
52
|
+
|
|
53
|
+
def compute_wrap(line, width)
|
|
54
|
+
result = []
|
|
55
|
+
chars = line.chars
|
|
56
|
+
current_text = String.new
|
|
57
|
+
current_width = 0
|
|
58
|
+
start_col = 0
|
|
59
|
+
col = 0
|
|
60
|
+
|
|
61
|
+
chars.each do |char|
|
|
62
|
+
char_w = UnicodeWidth.char_width(char)
|
|
63
|
+
|
|
64
|
+
# Check if adding this character would exceed width
|
|
65
|
+
if current_width + char_w > width && !current_text.empty?
|
|
66
|
+
result << { text: current_text, start_col:, end_col: col }
|
|
67
|
+
current_text = String.new
|
|
68
|
+
current_width = 0
|
|
69
|
+
start_col = col
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
current_text << char
|
|
73
|
+
current_width += char_w
|
|
74
|
+
col += 1
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Add remaining text
|
|
78
|
+
result << { text: current_text, start_col:, end_col: col } unless current_text.empty?
|
|
79
|
+
|
|
80
|
+
result.empty? ? [{ text: String.new, start_col: 0, end_col: 0 }] : result
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
data/lib/mui.rb
CHANGED
|
@@ -1,8 +1,177 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "mui/version"
|
|
4
|
+
require_relative "mui/error"
|
|
5
|
+
require_relative "mui/key_code"
|
|
6
|
+
require_relative "mui/key_notation_parser"
|
|
7
|
+
require_relative "mui/key_sequence"
|
|
8
|
+
require_relative "mui/key_sequence_buffer"
|
|
9
|
+
require_relative "mui/key_sequence_matcher"
|
|
10
|
+
require_relative "mui/key_sequence_handler"
|
|
11
|
+
require_relative "mui/unicode_width"
|
|
12
|
+
require_relative "mui/wrap_cache"
|
|
13
|
+
require_relative "mui/wrap_helper"
|
|
14
|
+
require_relative "mui/config"
|
|
15
|
+
require_relative "mui/color_scheme"
|
|
16
|
+
require_relative "mui/color_manager"
|
|
17
|
+
require_relative "mui/themes/default"
|
|
18
|
+
require_relative "mui/terminal_adapter"
|
|
19
|
+
require_relative "mui/screen"
|
|
20
|
+
require_relative "mui/input"
|
|
21
|
+
require_relative "mui/undoable_action"
|
|
22
|
+
require_relative "mui/undo_manager"
|
|
23
|
+
require_relative "mui/buffer"
|
|
24
|
+
require_relative "mui/highlight"
|
|
25
|
+
require_relative "mui/highlighters/base"
|
|
26
|
+
require_relative "mui/highlighters/selection_highlighter"
|
|
27
|
+
require_relative "mui/highlighters/search_highlighter"
|
|
28
|
+
require_relative "mui/syntax/token"
|
|
29
|
+
require_relative "mui/syntax/lexer_base"
|
|
30
|
+
require_relative "mui/syntax/lexers/ruby_lexer"
|
|
31
|
+
require_relative "mui/syntax/lexers/c_lexer"
|
|
32
|
+
require_relative "mui/syntax/lexers/go_lexer"
|
|
33
|
+
require_relative "mui/syntax/lexers/rust_lexer"
|
|
34
|
+
require_relative "mui/syntax/lexers/javascript_lexer"
|
|
35
|
+
require_relative "mui/syntax/lexers/typescript_lexer"
|
|
36
|
+
require_relative "mui/syntax/lexers/markdown_lexer"
|
|
37
|
+
require_relative "mui/syntax/lexers/html_lexer"
|
|
38
|
+
require_relative "mui/syntax/lexers/css_lexer"
|
|
39
|
+
require_relative "mui/syntax/token_cache"
|
|
40
|
+
require_relative "mui/syntax/language_detector"
|
|
41
|
+
require_relative "mui/highlighters/syntax_highlighter"
|
|
42
|
+
require_relative "mui/line_renderer"
|
|
43
|
+
require_relative "mui/status_line_renderer"
|
|
44
|
+
require_relative "mui/layout/node"
|
|
45
|
+
require_relative "mui/layout/leaf_node"
|
|
46
|
+
require_relative "mui/layout/split_node"
|
|
47
|
+
require_relative "mui/layout/calculator"
|
|
48
|
+
require_relative "mui/window"
|
|
49
|
+
require_relative "mui/window_manager"
|
|
50
|
+
require_relative "mui/tab_page"
|
|
51
|
+
require_relative "mui/tab_manager"
|
|
52
|
+
require_relative "mui/tab_bar_renderer"
|
|
53
|
+
require_relative "mui/mode"
|
|
54
|
+
require_relative "mui/handler_result"
|
|
55
|
+
require_relative "mui/command_history"
|
|
56
|
+
require_relative "mui/command_line"
|
|
57
|
+
require_relative "mui/completion_state"
|
|
58
|
+
require_relative "mui/insert_completion_state"
|
|
59
|
+
require_relative "mui/command_completer"
|
|
60
|
+
require_relative "mui/file_completer"
|
|
61
|
+
require_relative "mui/buffer_word_completer"
|
|
62
|
+
require_relative "mui/buffer_word_cache"
|
|
63
|
+
require_relative "mui/completion_renderer"
|
|
64
|
+
require_relative "mui/insert_completion_renderer"
|
|
65
|
+
require_relative "mui/floating_window"
|
|
66
|
+
require_relative "mui/search_state"
|
|
67
|
+
require_relative "mui/search_input"
|
|
68
|
+
require_relative "mui/search_completer"
|
|
69
|
+
require_relative "mui/motion"
|
|
70
|
+
require_relative "mui/selection"
|
|
71
|
+
require_relative "mui/register"
|
|
72
|
+
require_relative "mui/key_handler"
|
|
73
|
+
require_relative "mui/mode_manager"
|
|
74
|
+
require_relative "mui/command_context"
|
|
75
|
+
require_relative "mui/command_registry"
|
|
76
|
+
require_relative "mui/autocmd"
|
|
77
|
+
require_relative "mui/plugin"
|
|
78
|
+
require_relative "mui/plugin_manager"
|
|
79
|
+
require_relative "mui/job"
|
|
80
|
+
require_relative "mui/job_manager"
|
|
81
|
+
require_relative "mui/editor"
|
|
4
82
|
|
|
83
|
+
# mui(無為) top level module
|
|
5
84
|
module Mui
|
|
6
|
-
class
|
|
7
|
-
|
|
85
|
+
class << self
|
|
86
|
+
def config
|
|
87
|
+
@config ||= Config.new
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def plugin_manager
|
|
91
|
+
@plugin_manager ||= PluginManager.new
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def register
|
|
95
|
+
@register ||= Register.new
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def set(key, value)
|
|
99
|
+
config.set(key, value)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Register gem for lazy installation via bundler/inline
|
|
103
|
+
def use(gem_name, version = nil)
|
|
104
|
+
plugin_manager.add_gem(gem_name, version)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def keymap(mode, key, &block)
|
|
108
|
+
config.add_keymap(mode, key, block)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def command(name, &block)
|
|
112
|
+
config.add_command(name, block)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def autocmd(event, pattern: nil, &block)
|
|
116
|
+
config.add_autocmd(event, pattern, block)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def define_plugin(name, dependencies: [], &block)
|
|
120
|
+
plugin_manager.register(name, block, dependencies:)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def load_config
|
|
124
|
+
config.load_file(File.expand_path("~/.muirc"))
|
|
125
|
+
config.load_file(".lmuirc")
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# Stub for LSP configuration DSL
|
|
129
|
+
# This allows .muirc to call Mui.lsp { ... } before mui-lsp gem is loaded
|
|
130
|
+
# When mui-lsp is loaded, it replaces this with the real ConfigDsl
|
|
131
|
+
def lsp(&block)
|
|
132
|
+
@lsp_config ||= LspConfigStub.new
|
|
133
|
+
@lsp_config.instance_eval(&block) if block
|
|
134
|
+
@lsp_config
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def lsp_server_configs
|
|
138
|
+
@lsp_config&.server_configs || []
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
# Stub class for LSP configuration before mui-lsp gem is loaded
|
|
143
|
+
# Stores configuration to be applied when the actual gem loads
|
|
144
|
+
class LspConfigStub
|
|
145
|
+
attr_reader :server_configs
|
|
146
|
+
|
|
147
|
+
def initialize
|
|
148
|
+
@server_configs = []
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# Stub methods that mirror Mui::Lsp::ConfigDsl
|
|
152
|
+
def use(name, **options)
|
|
153
|
+
@server_configs << { type: :preset, name: name.to_sym, options: }
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def server(name:, command:, language_ids:, file_patterns:, auto_start: true, sync_on_change: true)
|
|
157
|
+
@server_configs << {
|
|
158
|
+
type: :custom,
|
|
159
|
+
name:,
|
|
160
|
+
command:,
|
|
161
|
+
language_ids:,
|
|
162
|
+
file_patterns:,
|
|
163
|
+
auto_start:,
|
|
164
|
+
sync_on_change:
|
|
165
|
+
}
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
class << self
|
|
170
|
+
def reset_config!
|
|
171
|
+
@config = nil
|
|
172
|
+
@plugin_manager = nil
|
|
173
|
+
@register = nil
|
|
174
|
+
@lsp_config = nil
|
|
175
|
+
end
|
|
176
|
+
end
|
|
8
177
|
end
|