mui 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +18 -10
  3. data/CHANGELOG.md +162 -0
  4. data/README.md +309 -6
  5. data/docs/_config.yml +56 -0
  6. data/docs/configuration.md +314 -0
  7. data/docs/getting-started.md +140 -0
  8. data/docs/index.md +55 -0
  9. data/docs/jobs.md +297 -0
  10. data/docs/keybindings.md +229 -0
  11. data/docs/plugins.md +285 -0
  12. data/docs/syntax-highlighting.md +155 -0
  13. data/lib/mui/color_manager.rb +140 -6
  14. data/lib/mui/color_scheme.rb +1 -0
  15. data/lib/mui/command_completer.rb +11 -2
  16. data/lib/mui/command_history.rb +89 -0
  17. data/lib/mui/command_line.rb +32 -2
  18. data/lib/mui/command_registry.rb +21 -2
  19. data/lib/mui/config.rb +3 -1
  20. data/lib/mui/editor.rb +90 -2
  21. data/lib/mui/floating_window.rb +53 -1
  22. data/lib/mui/handler_result.rb +13 -7
  23. data/lib/mui/highlighters/search_highlighter.rb +2 -1
  24. data/lib/mui/highlighters/syntax_highlighter.rb +4 -1
  25. data/lib/mui/insert_completion_state.rb +15 -2
  26. data/lib/mui/key_handler/base.rb +87 -0
  27. data/lib/mui/key_handler/command_mode.rb +68 -0
  28. data/lib/mui/key_handler/insert_mode.rb +10 -41
  29. data/lib/mui/key_handler/normal_mode.rb +24 -51
  30. data/lib/mui/key_handler/operators/paste_operator.rb +9 -3
  31. data/lib/mui/key_handler/search_mode.rb +10 -7
  32. data/lib/mui/key_handler/visual_mode.rb +15 -10
  33. data/lib/mui/key_notation_parser.rb +159 -0
  34. data/lib/mui/key_sequence.rb +67 -0
  35. data/lib/mui/key_sequence_buffer.rb +85 -0
  36. data/lib/mui/key_sequence_handler.rb +163 -0
  37. data/lib/mui/key_sequence_matcher.rb +79 -0
  38. data/lib/mui/line_renderer.rb +52 -1
  39. data/lib/mui/mode_manager.rb +3 -2
  40. data/lib/mui/screen.rb +30 -6
  41. data/lib/mui/search_state.rb +74 -27
  42. data/lib/mui/syntax/language_detector.rb +33 -1
  43. data/lib/mui/syntax/lexers/c_lexer.rb +2 -0
  44. data/lib/mui/syntax/lexers/css_lexer.rb +121 -0
  45. data/lib/mui/syntax/lexers/go_lexer.rb +207 -0
  46. data/lib/mui/syntax/lexers/html_lexer.rb +118 -0
  47. data/lib/mui/syntax/lexers/javascript_lexer.rb +219 -0
  48. data/lib/mui/syntax/lexers/markdown_lexer.rb +210 -0
  49. data/lib/mui/syntax/lexers/ruby_lexer.rb +3 -0
  50. data/lib/mui/syntax/lexers/rust_lexer.rb +150 -0
  51. data/lib/mui/syntax/lexers/typescript_lexer.rb +225 -0
  52. data/lib/mui/terminal_adapter/base.rb +21 -0
  53. data/lib/mui/terminal_adapter/curses.rb +37 -11
  54. data/lib/mui/themes/default.rb +263 -132
  55. data/lib/mui/version.rb +1 -1
  56. data/lib/mui/window.rb +105 -39
  57. data/lib/mui/window_manager.rb +7 -0
  58. data/lib/mui/wrap_cache.rb +40 -0
  59. data/lib/mui/wrap_helper.rb +84 -0
  60. data/lib/mui.rb +15 -0
  61. metadata +26 -3
@@ -0,0 +1,118 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mui
4
+ module Syntax
5
+ module Lexers
6
+ # Lexer for HTML source files
7
+ class HtmlLexer < LexerBase
8
+ # Pre-compiled patterns with \G anchor for position-specific matching
9
+ COMPILED_PATTERNS = [
10
+ # HTML comment (single line)
11
+ [:comment, /\G<!--.*?-->/],
12
+ # DOCTYPE declaration
13
+ [:preprocessor, /\G<!DOCTYPE[^>]*>/i],
14
+ # CDATA section
15
+ [:string, /\G<!\[CDATA\[.*?\]\]>/],
16
+ # Closing tag
17
+ [:keyword, %r{\G</[a-zA-Z][a-zA-Z0-9-]*\s*>}],
18
+ # Self-closing tag
19
+ [:keyword, %r{\G<[a-zA-Z][a-zA-Z0-9-]*(?:\s+[^>]*)?/>}],
20
+ # Opening tag with attributes
21
+ [:keyword, /\G<[a-zA-Z][a-zA-Z0-9-]*(?=[\s>])/],
22
+ # Tag closing bracket
23
+ [:keyword, /\G>/],
24
+ # Attribute name
25
+ [:type, /\G[a-zA-Z][a-zA-Z0-9_-]*(?==)/],
26
+ # Double quoted attribute value
27
+ [:string, /\G"[^"]*"/],
28
+ # Single quoted attribute value
29
+ [:string, /\G'[^']*'/],
30
+ # Unquoted attribute value (limited characters)
31
+ [:string, /\G=[^\s>"']+/],
32
+ # HTML entities
33
+ [:constant, /\G&(?:#\d+|#x[0-9a-fA-F]+|[a-zA-Z]+);/],
34
+ # Equal sign (for attributes)
35
+ [:operator, /\G=/]
36
+ ].freeze
37
+
38
+ # Multiline comment patterns
39
+ COMMENT_START = /<!--/
40
+ COMMENT_END = /-->/
41
+ COMMENT_START_ANCHOR = /\G<!--/
42
+
43
+ protected
44
+
45
+ def compiled_patterns
46
+ COMPILED_PATTERNS
47
+ end
48
+
49
+ # Handle multiline HTML comments
50
+ def handle_multiline_state(line, pos, state)
51
+ return [nil, nil, pos] unless state == :html_comment
52
+
53
+ end_match = line[pos..].match(COMMENT_END)
54
+ if end_match
55
+ end_pos = pos + end_match.begin(0) + 2 # --> is 3 chars
56
+ text = line[pos..end_pos]
57
+ token = Token.new(
58
+ type: :comment,
59
+ start_col: pos,
60
+ end_col: end_pos,
61
+ text:
62
+ )
63
+ [token, nil, end_pos + 1]
64
+ else
65
+ text = line[pos..]
66
+ token = if text.empty?
67
+ nil
68
+ else
69
+ Token.new(
70
+ type: :comment,
71
+ start_col: pos,
72
+ end_col: line.length - 1,
73
+ text:
74
+ )
75
+ end
76
+ [token, :html_comment, line.length]
77
+ end
78
+ end
79
+
80
+ def check_multiline_start(line, pos)
81
+ rest = line[pos..]
82
+
83
+ # Check for <!-- that doesn't have --> on this line
84
+ start_match = rest.match(COMMENT_START)
85
+ return [nil, nil, pos] unless start_match
86
+
87
+ start_pos = pos + start_match.begin(0)
88
+ after_start = line[(start_pos + 4)..] # Skip <!--
89
+
90
+ if after_start&.include?("-->")
91
+ [nil, nil, pos]
92
+ else
93
+ text = line[start_pos..]
94
+ token = Token.new(
95
+ type: :comment,
96
+ start_col: start_pos,
97
+ end_col: line.length - 1,
98
+ text:
99
+ )
100
+ [:html_comment, token, line.length]
101
+ end
102
+ end
103
+
104
+ private
105
+
106
+ def match_token(line, pos)
107
+ # Check for start of multiline comment
108
+ if line[pos..].match?(COMMENT_START_ANCHOR)
109
+ rest = line[(pos + 4)..]
110
+ return nil unless rest&.include?("-->")
111
+ end
112
+
113
+ super
114
+ end
115
+ end
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,219 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mui
4
+ module Syntax
5
+ module Lexers
6
+ # Lexer for JavaScript source code
7
+ class JavaScriptLexer < LexerBase
8
+ # JavaScript keywords
9
+ KEYWORDS = %w[
10
+ async await break case catch class const continue debugger default
11
+ delete do else export extends finally for function if import in
12
+ instanceof let new return static switch throw try typeof var void
13
+ while with yield
14
+ ].freeze
15
+
16
+ # JavaScript built-in types and values
17
+ CONSTANTS = %w[true false null undefined NaN Infinity this super].freeze
18
+
19
+ # Pre-compiled patterns with \G anchor for position-specific matching
20
+ COMPILED_PATTERNS = [
21
+ # Single line comment
22
+ [:comment, %r{\G//.*}],
23
+ # Single-line block comment /* ... */ on one line
24
+ [:comment, %r{\G/\*.*?\*/}],
25
+ # Template literal (single line)
26
+ [:string, /\G`[^`]*`/],
27
+ # Double quoted string (with escape handling)
28
+ [:string, /\G"(?:[^"\\]|\\.)*"/],
29
+ # Single quoted string (with escape handling)
30
+ [:string, /\G'(?:[^'\\]|\\.)*'/],
31
+ # Regular expression literal
32
+ [:regex, %r{\G/(?:[^/\\]|\\.)+/[gimsuy]*}],
33
+ # Float numbers (must be before integer)
34
+ [:number, /\G\b\d+\.\d+(?:e[+-]?\d+)?\b/i],
35
+ # Hexadecimal
36
+ [:number, /\G\b0x[0-9a-fA-F]+n?\b/i],
37
+ # Octal
38
+ [:number, /\G\b0o[0-7]+n?\b/i],
39
+ # Binary
40
+ [:number, /\G\b0b[01]+n?\b/i],
41
+ # Integer (with optional BigInt suffix)
42
+ [:number, /\G\b\d+n?\b/],
43
+ # Constants (true, false, null, undefined, NaN, Infinity, this, super)
44
+ [:constant, /\G\b(?:true|false|null|undefined|NaN|Infinity|this|super)\b/],
45
+ # Keywords
46
+ [:keyword, /\G\b(?:async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|export|extends|finally|for|function|if|import|in|instanceof|let|new|return|static|switch|throw|try|typeof|var|void|while|with|yield)\b/],
47
+ # Class/constructor names (start with uppercase)
48
+ [:constant, /\G\b[A-Z][a-zA-Z0-9_]*\b/],
49
+ # Regular identifiers
50
+ [:identifier, /\G\b[a-zA-Z_$][a-zA-Z0-9_$]*\b/],
51
+ # Operators (=>must come before = patterns)
52
+ [:operator, %r{\G(?:\.{3}|=>|&&=?|\|\|=?|\?\?=?|===?|!==?|>>>?=?|<<=?|\+\+|--|\?\.?|[+\-*/%&|^<>=!]=?)}]
53
+ ].freeze
54
+
55
+ # Multiline patterns (pre-compiled)
56
+ BLOCK_COMMENT_END = %r{\*/}
57
+ BLOCK_COMMENT_START = %r{/\*}
58
+ BLOCK_COMMENT_START_ANCHOR = %r{\A/\*}
59
+
60
+ # Template literal patterns
61
+ TEMPLATE_LITERAL_START = /\A`/
62
+ TEMPLATE_LITERAL_END = /`/
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
+
86
+ protected
87
+
88
+ def compiled_patterns
89
+ COMPILED_PATTERNS
90
+ end
91
+
92
+ # Handle multiline constructs
93
+ def handle_multiline_state(line, pos, state)
94
+ case state
95
+ when :block_comment
96
+ handle_block_comment(line, pos)
97
+ when :template_literal
98
+ handle_template_literal(line, pos)
99
+ else
100
+ [nil, nil, pos]
101
+ end
102
+ end
103
+
104
+ def check_multiline_start(line, pos)
105
+ rest = line[pos..]
106
+
107
+ # Check for template literal start
108
+ if rest.match?(TEMPLATE_LITERAL_START)
109
+ after_start = line[(pos + 1)..]
110
+ unless after_start&.include?("`")
111
+ text = line[pos..]
112
+ token = Token.new(
113
+ type: :string,
114
+ start_col: pos,
115
+ end_col: line.length - 1,
116
+ text:
117
+ )
118
+ return [:template_literal, token, line.length]
119
+ end
120
+ end
121
+
122
+ # Check for /* that doesn't have a matching */ on this line
123
+ start_match = rest.match(BLOCK_COMMENT_START)
124
+ return [nil, nil, pos] unless start_match
125
+
126
+ start_pos = pos + start_match.begin(0)
127
+ after_start = line[(start_pos + 2)..]
128
+
129
+ if after_start&.include?("*/")
130
+ [nil, nil, pos]
131
+ else
132
+ text = line[start_pos..]
133
+ token = Token.new(
134
+ type: :comment,
135
+ start_col: start_pos,
136
+ end_col: line.length - 1,
137
+ text:
138
+ )
139
+ [:block_comment, token, line.length]
140
+ end
141
+ end
142
+
143
+ private
144
+
145
+ def handle_block_comment(line, pos)
146
+ end_match = line[pos..].match(BLOCK_COMMENT_END)
147
+ if end_match
148
+ end_pos = pos + end_match.begin(0) + 1
149
+ text = line[pos..end_pos]
150
+ token = Token.new(
151
+ type: :comment,
152
+ start_col: pos,
153
+ end_col: end_pos,
154
+ text:
155
+ )
156
+ [token, nil, end_pos + 1]
157
+ else
158
+ text = line[pos..]
159
+ token = if text.empty?
160
+ nil
161
+ else
162
+ Token.new(
163
+ type: :comment,
164
+ start_col: pos,
165
+ end_col: line.length - 1,
166
+ text:
167
+ )
168
+ end
169
+ [token, :block_comment, line.length]
170
+ end
171
+ end
172
+
173
+ def handle_template_literal(line, pos)
174
+ end_match = line[pos..].match(TEMPLATE_LITERAL_END)
175
+ if end_match
176
+ end_pos = pos + end_match.begin(0)
177
+ text = line[pos..end_pos]
178
+ token = Token.new(
179
+ type: :string,
180
+ start_col: pos,
181
+ end_col: end_pos,
182
+ text:
183
+ )
184
+ [token, nil, end_pos + 1]
185
+ else
186
+ text = line[pos..]
187
+ token = if text.empty?
188
+ nil
189
+ else
190
+ Token.new(
191
+ type: :string,
192
+ start_col: pos,
193
+ end_col: line.length - 1,
194
+ text:
195
+ )
196
+ end
197
+ [token, :template_literal, line.length]
198
+ end
199
+ end
200
+
201
+ def match_token(line, pos)
202
+ # Check for start of template literal
203
+ if line[pos..].match?(TEMPLATE_LITERAL_START)
204
+ rest = line[(pos + 1)..]
205
+ return nil unless rest&.include?("`")
206
+ end
207
+
208
+ # Check for start of multiline comment
209
+ if line[pos..].match?(BLOCK_COMMENT_START_ANCHOR)
210
+ rest = line[(pos + 2)..]
211
+ return nil unless rest&.include?("*/")
212
+ end
213
+
214
+ super
215
+ end
216
+ end
217
+ end
218
+ end
219
+ end
@@ -0,0 +1,210 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mui
4
+ module Syntax
5
+ module Lexers
6
+ # Lexer for Markdown source files
7
+ class MarkdownLexer < LexerBase
8
+ # Pre-compiled patterns with \G anchor for position-specific matching
9
+ # Markdown is line-oriented, so we check line-start patterns separately
10
+ COMPILED_PATTERNS = [
11
+ # Inline code (backtick)
12
+ [:string, /\G`[^`]+`/],
13
+ # Bold with asterisks
14
+ [:keyword, /\G\*\*[^*]+\*\*/],
15
+ # Bold with underscores
16
+ [:keyword, /\G__[^_]+__/],
17
+ # Italic with asterisks
18
+ [:comment, /\G\*[^*]+\*/],
19
+ # Italic with underscores
20
+ [:comment, /\G_[^_]+_/],
21
+ # Strikethrough
22
+ [:comment, /\G~~[^~]+~~/],
23
+ # Link [text](url)
24
+ [:constant, /\G\[[^\]]+\]\([^)]+\)/],
25
+ # Image ![alt](url)
26
+ [:constant, /\G!\[[^\]]*\]\([^)]+\)/],
27
+ # Reference link [text][ref]
28
+ [:constant, /\G\[[^\]]+\]\[[^\]]*\]/],
29
+ # Autolink <url> or <email>
30
+ [:constant, /\G<[a-zA-Z][a-zA-Z0-9+.-]*:[^>]+>/],
31
+ # HTML tags
32
+ [:preprocessor, %r{\G</?[a-zA-Z][a-zA-Z0-9]*[^>]*>}]
33
+ ].freeze
34
+
35
+ # Line-start patterns (checked at beginning of line)
36
+ HEADING_PATTERN = /\A(\#{1,6})\s+(.*)$/
37
+ BLOCKQUOTE_PATTERN = /\A>\s*/
38
+ UNORDERED_LIST_PATTERN = /\A\s*[-*+]\s+/
39
+ ORDERED_LIST_PATTERN = /\A\s*\d+\.\s+/
40
+ HORIZONTAL_RULE_PATTERN = /\A([-*_])\s*\1\s*\1[\s\1]*$/
41
+ CODE_FENCE_START = /\A```(\w*)/
42
+ CODE_FENCE_END = /\A```\s*$/
43
+ LINK_DEFINITION_PATTERN = /\A\s*\[[^\]]+\]:\s+\S+/
44
+
45
+ # Override tokenize to handle line-start patterns
46
+ def tokenize(line, state = nil)
47
+ tokens = []
48
+ pos = 0
49
+
50
+ # Handle code fence state
51
+ if state == :code_fence
52
+ if line.match?(CODE_FENCE_END)
53
+ token = Token.new(
54
+ type: :string,
55
+ start_col: 0,
56
+ end_col: line.length - 1,
57
+ text: line
58
+ )
59
+ return [[token], nil]
60
+ else
61
+ unless line.empty?
62
+ token = Token.new(
63
+ type: :string,
64
+ start_col: 0,
65
+ end_col: line.length - 1,
66
+ text: line
67
+ )
68
+ end
69
+ return [token ? [token] : [], :code_fence]
70
+ end
71
+ end
72
+
73
+ # Check for code fence start
74
+ fence_match = line.match(CODE_FENCE_START)
75
+ if fence_match
76
+ token = Token.new(
77
+ type: :string,
78
+ start_col: 0,
79
+ end_col: line.length - 1,
80
+ text: line
81
+ )
82
+ return [[token], :code_fence]
83
+ end
84
+
85
+ # Check line-start patterns
86
+ line_start_token = check_line_start(line)
87
+ if line_start_token
88
+ tokens << line_start_token
89
+ pos = line_start_token.end_col + 1
90
+ end
91
+
92
+ # Process rest of line with inline patterns
93
+ while pos < line.length
94
+ # Skip whitespace
95
+ if line[pos] =~ /\s/
96
+ pos += 1
97
+ next
98
+ end
99
+
100
+ # Try to match a token
101
+ token = match_token(line, pos)
102
+ if token
103
+ tokens << token
104
+ pos = token.end_col + 1
105
+ else
106
+ pos += 1
107
+ end
108
+ end
109
+
110
+ [tokens, nil]
111
+ end
112
+
113
+ protected
114
+
115
+ def compiled_patterns
116
+ COMPILED_PATTERNS
117
+ end
118
+
119
+ private
120
+
121
+ def check_line_start(line)
122
+ # Heading
123
+ heading_match = line.match(HEADING_PATTERN)
124
+ if heading_match
125
+ level = heading_match[1].length
126
+ return Token.new(
127
+ type: :keyword,
128
+ start_col: 0,
129
+ end_col: level - 1,
130
+ text: heading_match[1]
131
+ )
132
+ end
133
+
134
+ # Horizontal rule
135
+ if line.match?(HORIZONTAL_RULE_PATTERN)
136
+ return Token.new(
137
+ type: :comment,
138
+ start_col: 0,
139
+ end_col: line.length - 1,
140
+ text: line
141
+ )
142
+ end
143
+
144
+ # Link definition
145
+ if line.match?(LINK_DEFINITION_PATTERN)
146
+ return Token.new(
147
+ type: :constant,
148
+ start_col: 0,
149
+ end_col: line.length - 1,
150
+ text: line
151
+ )
152
+ end
153
+
154
+ # Blockquote
155
+ blockquote_match = line.match(BLOCKQUOTE_PATTERN)
156
+ if blockquote_match
157
+ return Token.new(
158
+ type: :comment,
159
+ start_col: 0,
160
+ end_col: blockquote_match[0].length - 1,
161
+ text: blockquote_match[0]
162
+ )
163
+ end
164
+
165
+ # Unordered list
166
+ list_match = line.match(UNORDERED_LIST_PATTERN)
167
+ if list_match
168
+ return Token.new(
169
+ type: :operator,
170
+ start_col: 0,
171
+ end_col: list_match[0].length - 1,
172
+ text: list_match[0]
173
+ )
174
+ end
175
+
176
+ # Ordered list
177
+ ordered_match = line.match(ORDERED_LIST_PATTERN)
178
+ if ordered_match
179
+ return Token.new(
180
+ type: :number,
181
+ start_col: 0,
182
+ end_col: ordered_match[0].length - 1,
183
+ text: ordered_match[0]
184
+ )
185
+ end
186
+
187
+ nil
188
+ end
189
+
190
+ def match_token(line, pos)
191
+ rest = line[pos..]
192
+
193
+ COMPILED_PATTERNS.each do |type, pattern|
194
+ match = rest.match(pattern)
195
+ next unless match&.begin(0)&.zero?
196
+
197
+ return Token.new(
198
+ type:,
199
+ start_col: pos,
200
+ end_col: pos + match[0].length - 1,
201
+ text: match[0]
202
+ )
203
+ end
204
+
205
+ nil
206
+ end
207
+ end
208
+ end
209
+ end
210
+ end
@@ -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
@@ -0,0 +1,150 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mui
4
+ module Syntax
5
+ module Lexers
6
+ # Lexer for Rust source code
7
+ class RustLexer < LexerBase
8
+ # Rust keywords
9
+ KEYWORDS = %w[
10
+ as async await break const continue crate dyn else enum extern
11
+ false fn for if impl in let loop match mod move mut pub ref
12
+ return self Self static struct super trait true type unsafe use
13
+ where while
14
+ ].freeze
15
+
16
+ # Rust primitive types
17
+ TYPES = %w[
18
+ bool char str
19
+ i8 i16 i32 i64 i128 isize
20
+ u8 u16 u32 u64 u128 usize
21
+ f32 f64
22
+ ].freeze
23
+
24
+ # Pre-compiled patterns with \G anchor for position-specific matching
25
+ COMPILED_PATTERNS = [
26
+ # Doc comments (must be before regular comments)
27
+ [:comment, %r{\G///.*}],
28
+ [:comment, %r{\G//!.*}],
29
+ # Single line comment
30
+ [:comment, %r{\G//.*}],
31
+ # Single-line block comment /* ... */ on one line
32
+ [:comment, %r{\G/\*.*?\*/}],
33
+ # Attributes
34
+ [:preprocessor, /\G#!\[[^\]]*\]/],
35
+ [:preprocessor, /\G#\[[^\]]*\]/],
36
+ # Character literal - single char or escape sequence (must be before lifetime)
37
+ [:char, /\G'(?:[^'\\]|\\.)'/],
38
+ # Lifetime
39
+ [:symbol, /\G'[a-z_][a-zA-Z0-9_]*/],
40
+ # Raw string r#"..."#
41
+ [:string, /\Gr#+"[^"]*"#+/],
42
+ # Byte string
43
+ [:string, /\Gb"(?:[^"\\]|\\.)*"/],
44
+ # Regular string
45
+ [:string, /\G"(?:[^"\\]|\\.)*"/],
46
+ # Float numbers (must be before integer)
47
+ [:number, /\G\b\d+\.\d+(?:e[+-]?\d+)?(?:f32|f64)?\b/i],
48
+ # Hexadecimal
49
+ [:number, /\G\b0x[0-9a-fA-F_]+(?:i8|i16|i32|i64|i128|isize|u8|u16|u32|u64|u128|usize)?\b/],
50
+ # Octal
51
+ [:number, /\G\b0o[0-7_]+(?:i8|i16|i32|i64|i128|isize|u8|u16|u32|u64|u128|usize)?\b/],
52
+ # Binary
53
+ [:number, /\G\b0b[01_]+(?:i8|i16|i32|i64|i128|isize|u8|u16|u32|u64|u128|usize)?\b/],
54
+ # Integer
55
+ [:number, /\G\b\d[0-9_]*(?:i8|i16|i32|i64|i128|isize|u8|u16|u32|u64|u128|usize)?\b/],
56
+ # Macros (identifier followed by !)
57
+ [:macro, /\G\b[a-z_][a-zA-Z0-9_]*!/],
58
+ # Primitive types
59
+ [:type, /\G\b(?:bool|char|str|i8|i16|i32|i64|i128|isize|u8|u16|u32|u64|u128|usize|f32|f64)\b/],
60
+ # Keywords
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_]*/],
64
+ # Type names (start with uppercase)
65
+ [:constant, /\G\b[A-Z][a-zA-Z0-9_]*\b/],
66
+ # Regular identifiers
67
+ [:identifier, /\G\b[a-z_][a-zA-Z0-9_]*\b/],
68
+ # Operators
69
+ [:operator, %r{\G(?:&&|\|\||<<|>>|=>|->|::|\.\.=?|[+\-*/%&|^<>=!]=?|\?)}]
70
+ ].freeze
71
+
72
+ # Multiline comment patterns (pre-compiled)
73
+ BLOCK_COMMENT_END = %r{\*/}
74
+ BLOCK_COMMENT_START = %r{/\*}
75
+ BLOCK_COMMENT_START_ANCHOR = %r{\A/\*}
76
+
77
+ protected
78
+
79
+ def compiled_patterns
80
+ COMPILED_PATTERNS
81
+ end
82
+
83
+ # Handle /* ... */ block comments that span multiple lines
84
+ def handle_multiline_state(line, pos, state)
85
+ return [nil, nil, pos] unless state == :block_comment
86
+
87
+ end_match = line[pos..].match(BLOCK_COMMENT_END)
88
+ if end_match
89
+ end_pos = pos + end_match.begin(0) + 1
90
+ text = line[pos..end_pos]
91
+ token = Token.new(
92
+ type: :comment,
93
+ start_col: pos,
94
+ end_col: end_pos,
95
+ text:
96
+ )
97
+ [token, nil, end_pos + 1]
98
+ else
99
+ text = line[pos..]
100
+ token = if text.empty?
101
+ nil
102
+ else
103
+ Token.new(
104
+ type: :comment,
105
+ start_col: pos,
106
+ end_col: line.length - 1,
107
+ text:
108
+ )
109
+ end
110
+ [token, :block_comment, line.length]
111
+ end
112
+ end
113
+
114
+ def check_multiline_start(line, pos)
115
+ rest = line[pos..]
116
+
117
+ start_match = rest.match(BLOCK_COMMENT_START)
118
+ return [nil, nil, pos] unless start_match
119
+
120
+ start_pos = pos + start_match.begin(0)
121
+ after_start = line[(start_pos + 2)..]
122
+
123
+ if after_start&.include?("*/")
124
+ [nil, nil, pos]
125
+ else
126
+ text = line[start_pos..]
127
+ token = Token.new(
128
+ type: :comment,
129
+ start_col: start_pos,
130
+ end_col: line.length - 1,
131
+ text:
132
+ )
133
+ [:block_comment, token, line.length]
134
+ end
135
+ end
136
+
137
+ private
138
+
139
+ def match_token(line, pos)
140
+ if line[pos..].match?(BLOCK_COMMENT_START_ANCHOR)
141
+ rest = line[(pos + 2)..]
142
+ return nil unless rest&.include?("*/")
143
+ end
144
+
145
+ super
146
+ end
147
+ end
148
+ end
149
+ end
150
+ end