mui 0.3.0 → 0.4.1
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 +8 -5
- data/CHANGELOG.md +71 -0
- data/docs/configuration.md +22 -9
- data/docs/syntax-highlighting.md +6 -0
- data/lib/mui/color_manager.rb +140 -6
- data/lib/mui/color_scheme.rb +1 -0
- data/lib/mui/editor.rb +12 -0
- data/lib/mui/floating_window.rb +53 -1
- data/lib/mui/highlighters/syntax_highlighter.rb +1 -0
- data/lib/mui/insert_completion_state.rb +15 -2
- data/lib/mui/key_handler/normal_mode.rb +1 -2
- data/lib/mui/key_notation_parser.rb +10 -3
- data/lib/mui/screen.rb +6 -0
- data/lib/mui/search_state.rb +25 -11
- data/lib/mui/syntax/lexers/c_lexer.rb +2 -0
- data/lib/mui/syntax/lexers/go_lexer.rb +2 -0
- data/lib/mui/syntax/lexers/javascript_lexer.rb +22 -0
- data/lib/mui/syntax/lexers/ruby_lexer.rb +3 -0
- data/lib/mui/syntax/lexers/rust_lexer.rb +2 -0
- data/lib/mui/syntax/lexers/typescript_lexer.rb +22 -0
- data/lib/mui/terminal_adapter/base.rb +21 -0
- data/lib/mui/terminal_adapter/curses.rb +24 -0
- data/lib/mui/themes/default.rb +263 -132
- data/lib/mui/version.rb +1 -1
- data/lib/mui/window.rb +23 -0
- metadata +1 -1
data/lib/mui/search_state.rb
CHANGED
|
@@ -62,49 +62,62 @@ module Mui
|
|
|
62
62
|
end
|
|
63
63
|
|
|
64
64
|
# Get matches for a specific row in a specific buffer
|
|
65
|
+
# O(1) lookup using row_index
|
|
65
66
|
def matches_for_row(row, buffer: nil)
|
|
66
67
|
return [] if buffer.nil?
|
|
67
68
|
|
|
68
|
-
|
|
69
|
-
|
|
69
|
+
cache = get_or_calculate_cache(buffer)
|
|
70
|
+
cache[:row_index][row] || []
|
|
70
71
|
end
|
|
71
72
|
|
|
72
73
|
private
|
|
73
74
|
|
|
74
75
|
def get_or_calculate_matches(buffer)
|
|
76
|
+
get_or_calculate_cache(buffer)[:matches]
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def get_or_calculate_cache(buffer)
|
|
75
80
|
buffer_id = buffer.object_id
|
|
76
81
|
cached = @buffer_matches[buffer_id]
|
|
77
82
|
|
|
78
|
-
# Return cached
|
|
79
|
-
return cached
|
|
83
|
+
# Return cached data if valid (same pattern version and buffer hasn't changed)
|
|
84
|
+
return cached if cached && cached[:version] == @pattern_version && cached[:change_count] == buffer.change_count
|
|
80
85
|
|
|
81
86
|
# Calculate and cache matches for this buffer
|
|
82
|
-
matches = calculate_matches(buffer)
|
|
87
|
+
matches, row_index = calculate_matches(buffer)
|
|
83
88
|
@buffer_matches[buffer_id] = {
|
|
84
89
|
version: @pattern_version,
|
|
85
90
|
change_count: buffer.change_count,
|
|
86
|
-
matches
|
|
91
|
+
matches:,
|
|
92
|
+
row_index:
|
|
87
93
|
}
|
|
88
|
-
|
|
94
|
+
@buffer_matches[buffer_id]
|
|
89
95
|
end
|
|
90
96
|
|
|
91
97
|
def calculate_matches(buffer)
|
|
92
|
-
|
|
98
|
+
empty_result = [[], {}]
|
|
99
|
+
return empty_result if @pattern.nil? || @pattern.empty?
|
|
93
100
|
|
|
94
101
|
matches = []
|
|
102
|
+
row_index = {}
|
|
95
103
|
begin
|
|
96
104
|
regex = Regexp.new(@pattern)
|
|
97
105
|
buffer.line_count.times do |row|
|
|
98
106
|
line = buffer.line(row)
|
|
99
|
-
scan_line_matches(
|
|
107
|
+
row_matches = scan_line_matches(line, row, regex)
|
|
108
|
+
unless row_matches.empty?
|
|
109
|
+
matches.concat(row_matches)
|
|
110
|
+
row_index[row] = row_matches
|
|
111
|
+
end
|
|
100
112
|
end
|
|
101
113
|
rescue RegexpError
|
|
102
114
|
# Invalid regex pattern - no matches
|
|
103
115
|
end
|
|
104
|
-
matches
|
|
116
|
+
[matches, row_index]
|
|
105
117
|
end
|
|
106
118
|
|
|
107
|
-
def scan_line_matches(
|
|
119
|
+
def scan_line_matches(line, row, regex)
|
|
120
|
+
matches = []
|
|
108
121
|
offset = 0
|
|
109
122
|
while (match_data = line.match(regex, offset))
|
|
110
123
|
col = match_data.begin(0)
|
|
@@ -116,6 +129,7 @@ module Mui
|
|
|
116
129
|
offset += 1 if match_data[0].empty?
|
|
117
130
|
break if offset >= line.length
|
|
118
131
|
end
|
|
132
|
+
matches
|
|
119
133
|
end
|
|
120
134
|
end
|
|
121
135
|
end
|
|
@@ -30,6 +30,8 @@ module Mui
|
|
|
30
30
|
[:type, /\G\b(?:char|double|float|int|long|short|signed|unsigned|void|_Bool|_Complex|_Imaginary)\b/],
|
|
31
31
|
# Other keywords (if, for, return, const, static, etc.)
|
|
32
32
|
[:keyword, /\G\b(?:auto|break|case|const|continue|default|do|else|enum|extern|for|goto|if|register|return|sizeof|static|struct|switch|typedef|union|volatile|while|inline|restrict|_Alignas|_Alignof|_Atomic|_Generic|_Noreturn|_Static_assert|_Thread_local)\b/],
|
|
33
|
+
# Function call/definition (identifier followed by parenthesis)
|
|
34
|
+
[:function_definition, /\G\b[a-zA-Z_][a-zA-Z0-9_]*(?=\s*\()/],
|
|
33
35
|
# Identifiers
|
|
34
36
|
[:identifier, /\G\b[a-zA-Z_][a-zA-Z0-9_]*\b/],
|
|
35
37
|
# Operators
|
|
@@ -51,6 +51,8 @@ module Mui
|
|
|
51
51
|
[:type, /\G\b(?:bool|byte|complex64|complex128|error|float32|float64|int|int8|int16|int32|int64|rune|string|uint|uint8|uint16|uint32|uint64|uintptr|any|comparable)\b/],
|
|
52
52
|
# Keywords
|
|
53
53
|
[:keyword, /\G\b(?:break|case|chan|const|continue|default|defer|else|fallthrough|for|func|go|goto|if|import|interface|map|package|range|return|select|struct|switch|type|var)\b/],
|
|
54
|
+
# Function definition names (func の後)
|
|
55
|
+
[:function_definition, /\G(?<=func )[a-z_][a-zA-Z0-9_]*/],
|
|
54
56
|
# Exported identifiers (start with uppercase)
|
|
55
57
|
[:constant, /\G\b[A-Z][a-zA-Z0-9_]*\b/],
|
|
56
58
|
# Regular identifiers
|
|
@@ -61,6 +61,28 @@ module Mui
|
|
|
61
61
|
TEMPLATE_LITERAL_START = /\A`/
|
|
62
62
|
TEMPLATE_LITERAL_END = /`/
|
|
63
63
|
|
|
64
|
+
# Override tokenize to post-process function definitions
|
|
65
|
+
def tokenize(line, state = nil)
|
|
66
|
+
tokens, new_state = super
|
|
67
|
+
|
|
68
|
+
# Convert identifiers after 'function' keyword to function_definition
|
|
69
|
+
tokens.each_with_index do |token, i|
|
|
70
|
+
next unless i.positive? &&
|
|
71
|
+
tokens[i - 1].type == :keyword &&
|
|
72
|
+
tokens[i - 1].text == "function" &&
|
|
73
|
+
token.type == :identifier
|
|
74
|
+
|
|
75
|
+
tokens[i] = Token.new(
|
|
76
|
+
type: :function_definition,
|
|
77
|
+
start_col: token.start_col,
|
|
78
|
+
end_col: token.end_col,
|
|
79
|
+
text: token.text
|
|
80
|
+
)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
[tokens, new_state]
|
|
84
|
+
end
|
|
85
|
+
|
|
64
86
|
protected
|
|
65
87
|
|
|
66
88
|
def compiled_patterns
|
|
@@ -47,6 +47,9 @@ module Mui
|
|
|
47
47
|
[:global_variable, /\G\$[a-zA-Z_][a-zA-Z0-9_]*/],
|
|
48
48
|
# Method calls (.to_i, .each, .map!, .empty?, etc.)
|
|
49
49
|
[:method_call, /\G\.[a-z_][a-zA-Z0-9_]*[?!]?/],
|
|
50
|
+
# Function/method definition names (def の後)
|
|
51
|
+
# Note: def self.method_name is handled by method_call pattern (same color)
|
|
52
|
+
[:function_definition, /\G(?<=def )[a-z_][a-zA-Z0-9_]*[?!=]?/],
|
|
50
53
|
# Identifiers (including method names with ? or !)
|
|
51
54
|
[:identifier, /\G\b[a-z_][a-zA-Z0-9_]*[?!]?/],
|
|
52
55
|
# Operators
|
|
@@ -59,6 +59,8 @@ module Mui
|
|
|
59
59
|
[:type, /\G\b(?:bool|char|str|i8|i16|i32|i64|i128|isize|u8|u16|u32|u64|u128|usize|f32|f64)\b/],
|
|
60
60
|
# Keywords
|
|
61
61
|
[:keyword, /\G\b(?:as|async|await|break|const|continue|crate|dyn|else|enum|extern|false|fn|for|if|impl|in|let|loop|match|mod|move|mut|pub|ref|return|self|Self|static|struct|super|trait|true|type|unsafe|use|where|while)\b/],
|
|
62
|
+
# Function definition names (fn の後)
|
|
63
|
+
[:function_definition, /\G(?<=fn )[a-z_][a-zA-Z0-9_]*/],
|
|
62
64
|
# Type names (start with uppercase)
|
|
63
65
|
[:constant, /\G\b[A-Z][a-zA-Z0-9_]*\b/],
|
|
64
66
|
# Regular identifiers
|
|
@@ -67,6 +67,28 @@ module Mui
|
|
|
67
67
|
TEMPLATE_LITERAL_START = /\A`/
|
|
68
68
|
TEMPLATE_LITERAL_END = /`/
|
|
69
69
|
|
|
70
|
+
# Override tokenize to post-process function definitions
|
|
71
|
+
def tokenize(line, state = nil)
|
|
72
|
+
tokens, new_state = super
|
|
73
|
+
|
|
74
|
+
# Convert identifiers after 'function' keyword to function_definition
|
|
75
|
+
tokens.each_with_index do |token, i|
|
|
76
|
+
next unless i.positive? &&
|
|
77
|
+
tokens[i - 1].type == :keyword &&
|
|
78
|
+
tokens[i - 1].text == "function" &&
|
|
79
|
+
token.type == :identifier
|
|
80
|
+
|
|
81
|
+
tokens[i] = Token.new(
|
|
82
|
+
type: :function_definition,
|
|
83
|
+
start_col: token.start_col,
|
|
84
|
+
end_col: token.end_col,
|
|
85
|
+
text: token.text
|
|
86
|
+
)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
[tokens, new_state]
|
|
90
|
+
end
|
|
91
|
+
|
|
70
92
|
protected
|
|
71
93
|
|
|
72
94
|
def compiled_patterns
|
|
@@ -87,6 +87,27 @@ module Mui
|
|
|
87
87
|
def resume
|
|
88
88
|
raise MethodNotOverriddenError, __method__
|
|
89
89
|
end
|
|
90
|
+
|
|
91
|
+
# Check if terminal supports colors
|
|
92
|
+
def has_colors?
|
|
93
|
+
raise MethodNotOverriddenError, __method__
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Get available color count (8, 256, etc.)
|
|
97
|
+
def colors
|
|
98
|
+
raise MethodNotOverriddenError, __method__
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Get available color pair count
|
|
102
|
+
def color_pairs
|
|
103
|
+
raise MethodNotOverriddenError, __method__
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Force a complete redraw of the screen
|
|
107
|
+
# This is needed when multibyte characters may have been corrupted
|
|
108
|
+
def touchwin
|
|
109
|
+
# Default implementation does nothing (for mock adapters)
|
|
110
|
+
end
|
|
90
111
|
end
|
|
91
112
|
end
|
|
92
113
|
end
|
|
@@ -17,8 +17,25 @@ module Mui
|
|
|
17
17
|
end
|
|
18
18
|
|
|
19
19
|
def init_colors
|
|
20
|
+
return unless ::Curses.has_colors?
|
|
21
|
+
|
|
20
22
|
::Curses.start_color
|
|
21
23
|
::Curses.use_default_colors
|
|
24
|
+
@has_colors = true
|
|
25
|
+
@available_colors = ::Curses.respond_to?(:colors) ? ::Curses.colors : 8
|
|
26
|
+
@max_pairs = ::Curses.respond_to?(:color_pairs) ? ::Curses.color_pairs : 64
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def has_colors?
|
|
30
|
+
@has_colors || false
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def colors
|
|
34
|
+
@available_colors || 0
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def color_pairs
|
|
38
|
+
@max_pairs || 0
|
|
22
39
|
end
|
|
23
40
|
|
|
24
41
|
def init_color_pair(pair_index, fg, bg)
|
|
@@ -159,6 +176,13 @@ module Mui
|
|
|
159
176
|
::Curses.refresh
|
|
160
177
|
::Curses.curs_set(1)
|
|
161
178
|
end
|
|
179
|
+
|
|
180
|
+
# Force a complete redraw of the screen
|
|
181
|
+
# This is needed when multibyte characters may have been corrupted
|
|
182
|
+
def touchwin
|
|
183
|
+
::Curses.stdscr.redraw
|
|
184
|
+
::Curses.refresh
|
|
185
|
+
end
|
|
162
186
|
end
|
|
163
187
|
end
|
|
164
188
|
end
|