mui 0.2.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 +13 -8
- data/CHANGELOG.md +99 -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/lib/mui/command_completer.rb +11 -2
- data/lib/mui/command_history.rb +89 -0
- data/lib/mui/command_line.rb +32 -2
- data/lib/mui/command_registry.rb +21 -2
- data/lib/mui/config.rb +3 -1
- data/lib/mui/editor.rb +78 -2
- data/lib/mui/handler_result.rb +13 -7
- data/lib/mui/highlighters/search_highlighter.rb +2 -1
- data/lib/mui/highlighters/syntax_highlighter.rb +3 -1
- data/lib/mui/key_handler/base.rb +87 -0
- data/lib/mui/key_handler/command_mode.rb +68 -0
- data/lib/mui/key_handler/insert_mode.rb +10 -41
- data/lib/mui/key_handler/normal_mode.rb +24 -51
- data/lib/mui/key_handler/operators/paste_operator.rb +9 -3
- data/lib/mui/key_handler/search_mode.rb +10 -7
- data/lib/mui/key_handler/visual_mode.rb +15 -10
- 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/line_renderer.rb +52 -1
- data/lib/mui/mode_manager.rb +3 -2
- data/lib/mui/screen.rb +24 -6
- data/lib/mui/search_state.rb +61 -28
- data/lib/mui/syntax/language_detector.rb +33 -1
- 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/rust_lexer.rb +148 -0
- data/lib/mui/syntax/lexers/typescript_lexer.rb +203 -0
- data/lib/mui/terminal_adapter/curses.rb +13 -11
- data/lib/mui/version.rb +1 -1
- data/lib/mui/window.rb +83 -40
- data/lib/mui/window_manager.rb +7 -0
- data/lib/mui/wrap_cache.rb +40 -0
- data/lib/mui/wrap_helper.rb +84 -0
- data/lib/mui.rb +15 -0
- metadata +26 -3
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Mui
|
|
4
|
+
module Syntax
|
|
5
|
+
module Lexers
|
|
6
|
+
# Lexer for Go source code
|
|
7
|
+
class GoLexer < LexerBase
|
|
8
|
+
# Go keywords
|
|
9
|
+
KEYWORDS = %w[
|
|
10
|
+
break case chan const continue default defer else fallthrough
|
|
11
|
+
for func go goto if import interface map package range return
|
|
12
|
+
select struct switch type var
|
|
13
|
+
].freeze
|
|
14
|
+
|
|
15
|
+
# Go built-in types
|
|
16
|
+
TYPES = %w[
|
|
17
|
+
bool byte complex64 complex128 error float32 float64
|
|
18
|
+
int int8 int16 int32 int64 rune string
|
|
19
|
+
uint uint8 uint16 uint32 uint64 uintptr
|
|
20
|
+
any comparable
|
|
21
|
+
].freeze
|
|
22
|
+
|
|
23
|
+
# Go constants
|
|
24
|
+
CONSTANTS = %w[true false nil iota].freeze
|
|
25
|
+
|
|
26
|
+
# Pre-compiled patterns with \G anchor for position-specific matching
|
|
27
|
+
COMPILED_PATTERNS = [
|
|
28
|
+
# Single line comment
|
|
29
|
+
[:comment, %r{\G//.*}],
|
|
30
|
+
# Single-line block comment /* ... */ on one line
|
|
31
|
+
[:comment, %r{\G/\*.*?\*/}],
|
|
32
|
+
# Raw string literal (backtick)
|
|
33
|
+
[:string, /\G`[^`]*`/],
|
|
34
|
+
# Double quoted string (with escape handling)
|
|
35
|
+
[:string, /\G"(?:[^"\\]|\\.)*"/],
|
|
36
|
+
# Character literal (rune)
|
|
37
|
+
[:char, /\G'(?:[^'\\]|\\.)*'/],
|
|
38
|
+
# Float numbers (must be before integer)
|
|
39
|
+
[:number, /\G\b\d+\.\d+(?:e[+-]?\d+)?\b/i],
|
|
40
|
+
# Hexadecimal
|
|
41
|
+
[:number, /\G\b0x[0-9a-fA-F]+\b/i],
|
|
42
|
+
# Octal
|
|
43
|
+
[:number, /\G\b0o[0-7]+\b/i],
|
|
44
|
+
# Binary
|
|
45
|
+
[:number, /\G\b0b[01]+\b/i],
|
|
46
|
+
# Integer
|
|
47
|
+
[:number, /\G\b\d+\b/],
|
|
48
|
+
# Constants (true, false, nil, iota)
|
|
49
|
+
[:constant, /\G\b(?:true|false|nil|iota)\b/],
|
|
50
|
+
# Types
|
|
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
|
+
# Keywords
|
|
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
|
+
# Exported identifiers (start with uppercase)
|
|
55
|
+
[:constant, /\G\b[A-Z][a-zA-Z0-9_]*\b/],
|
|
56
|
+
# Regular identifiers
|
|
57
|
+
[:identifier, /\G\b[a-z_][a-zA-Z0-9_]*\b/],
|
|
58
|
+
# Operators
|
|
59
|
+
[:operator, %r{\G(?:&&|\|\||<-|<<=?|>>=?|&\^=?|[+\-*/%&|^<>=!]=?|:=|\+\+|--)}]
|
|
60
|
+
].freeze
|
|
61
|
+
|
|
62
|
+
# Multiline comment patterns (pre-compiled)
|
|
63
|
+
BLOCK_COMMENT_END = %r{\*/}
|
|
64
|
+
BLOCK_COMMENT_START = %r{/\*}
|
|
65
|
+
BLOCK_COMMENT_START_ANCHOR = %r{\A/\*}
|
|
66
|
+
|
|
67
|
+
# Raw string patterns (pre-compiled)
|
|
68
|
+
RAW_STRING_START = /\A`/
|
|
69
|
+
RAW_STRING_END = /`/
|
|
70
|
+
|
|
71
|
+
protected
|
|
72
|
+
|
|
73
|
+
def compiled_patterns
|
|
74
|
+
COMPILED_PATTERNS
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Handle /* ... */ block comments and raw strings that span multiple lines
|
|
78
|
+
def handle_multiline_state(line, pos, state)
|
|
79
|
+
case state
|
|
80
|
+
when :block_comment
|
|
81
|
+
handle_block_comment(line, pos)
|
|
82
|
+
when :raw_string
|
|
83
|
+
handle_raw_string(line, pos)
|
|
84
|
+
else
|
|
85
|
+
[nil, nil, pos]
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def check_multiline_start(line, pos)
|
|
90
|
+
rest = line[pos..]
|
|
91
|
+
|
|
92
|
+
# Check for raw string start
|
|
93
|
+
if rest.match?(RAW_STRING_START)
|
|
94
|
+
after_start = line[(pos + 1)..]
|
|
95
|
+
unless after_start&.include?("`")
|
|
96
|
+
# No closing on this line, enter raw string state
|
|
97
|
+
text = line[pos..]
|
|
98
|
+
token = Token.new(
|
|
99
|
+
type: :string,
|
|
100
|
+
start_col: pos,
|
|
101
|
+
end_col: line.length - 1,
|
|
102
|
+
text:
|
|
103
|
+
)
|
|
104
|
+
return [:raw_string, token, line.length]
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Check for /* that doesn't have a matching */ on this line
|
|
109
|
+
start_match = rest.match(BLOCK_COMMENT_START)
|
|
110
|
+
return [nil, nil, pos] unless start_match
|
|
111
|
+
|
|
112
|
+
start_pos = pos + start_match.begin(0)
|
|
113
|
+
after_start = line[(start_pos + 2)..]
|
|
114
|
+
|
|
115
|
+
if after_start&.include?("*/")
|
|
116
|
+
[nil, nil, pos]
|
|
117
|
+
else
|
|
118
|
+
text = line[start_pos..]
|
|
119
|
+
token = Token.new(
|
|
120
|
+
type: :comment,
|
|
121
|
+
start_col: start_pos,
|
|
122
|
+
end_col: line.length - 1,
|
|
123
|
+
text:
|
|
124
|
+
)
|
|
125
|
+
[:block_comment, token, line.length]
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
private
|
|
130
|
+
|
|
131
|
+
def handle_block_comment(line, pos)
|
|
132
|
+
end_match = line[pos..].match(BLOCK_COMMENT_END)
|
|
133
|
+
if end_match
|
|
134
|
+
end_pos = pos + end_match.begin(0) + 1
|
|
135
|
+
text = line[pos..end_pos]
|
|
136
|
+
token = Token.new(
|
|
137
|
+
type: :comment,
|
|
138
|
+
start_col: pos,
|
|
139
|
+
end_col: end_pos,
|
|
140
|
+
text:
|
|
141
|
+
)
|
|
142
|
+
[token, nil, end_pos + 1]
|
|
143
|
+
else
|
|
144
|
+
text = line[pos..]
|
|
145
|
+
token = if text.empty?
|
|
146
|
+
nil
|
|
147
|
+
else
|
|
148
|
+
Token.new(
|
|
149
|
+
type: :comment,
|
|
150
|
+
start_col: pos,
|
|
151
|
+
end_col: line.length - 1,
|
|
152
|
+
text:
|
|
153
|
+
)
|
|
154
|
+
end
|
|
155
|
+
[token, :block_comment, line.length]
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def handle_raw_string(line, pos)
|
|
160
|
+
end_match = line[pos..].match(RAW_STRING_END)
|
|
161
|
+
if end_match
|
|
162
|
+
end_pos = pos + end_match.begin(0)
|
|
163
|
+
text = line[pos..end_pos]
|
|
164
|
+
token = Token.new(
|
|
165
|
+
type: :string,
|
|
166
|
+
start_col: pos,
|
|
167
|
+
end_col: end_pos,
|
|
168
|
+
text:
|
|
169
|
+
)
|
|
170
|
+
[token, nil, end_pos + 1]
|
|
171
|
+
else
|
|
172
|
+
text = line[pos..]
|
|
173
|
+
token = if text.empty?
|
|
174
|
+
nil
|
|
175
|
+
else
|
|
176
|
+
Token.new(
|
|
177
|
+
type: :string,
|
|
178
|
+
start_col: pos,
|
|
179
|
+
end_col: line.length - 1,
|
|
180
|
+
text:
|
|
181
|
+
)
|
|
182
|
+
end
|
|
183
|
+
[token, :raw_string, line.length]
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def match_token(line, pos)
|
|
188
|
+
# Check for start of raw string
|
|
189
|
+
if line[pos..].match?(RAW_STRING_START)
|
|
190
|
+
rest = line[(pos + 1)..]
|
|
191
|
+
return nil unless rest&.include?("`")
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
# Check for start of multiline comment
|
|
195
|
+
if line[pos..].match?(BLOCK_COMMENT_START_ANCHOR)
|
|
196
|
+
rest = line[(pos + 2)..]
|
|
197
|
+
return nil unless rest&.include?("*/")
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
super
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
end
|
|
@@ -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,197 @@
|
|
|
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
|
+
protected
|
|
65
|
+
|
|
66
|
+
def compiled_patterns
|
|
67
|
+
COMPILED_PATTERNS
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Handle multiline constructs
|
|
71
|
+
def handle_multiline_state(line, pos, state)
|
|
72
|
+
case state
|
|
73
|
+
when :block_comment
|
|
74
|
+
handle_block_comment(line, pos)
|
|
75
|
+
when :template_literal
|
|
76
|
+
handle_template_literal(line, pos)
|
|
77
|
+
else
|
|
78
|
+
[nil, nil, pos]
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def check_multiline_start(line, pos)
|
|
83
|
+
rest = line[pos..]
|
|
84
|
+
|
|
85
|
+
# Check for template literal start
|
|
86
|
+
if rest.match?(TEMPLATE_LITERAL_START)
|
|
87
|
+
after_start = line[(pos + 1)..]
|
|
88
|
+
unless after_start&.include?("`")
|
|
89
|
+
text = line[pos..]
|
|
90
|
+
token = Token.new(
|
|
91
|
+
type: :string,
|
|
92
|
+
start_col: pos,
|
|
93
|
+
end_col: line.length - 1,
|
|
94
|
+
text:
|
|
95
|
+
)
|
|
96
|
+
return [:template_literal, token, line.length]
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# Check for /* that doesn't have a matching */ on this line
|
|
101
|
+
start_match = rest.match(BLOCK_COMMENT_START)
|
|
102
|
+
return [nil, nil, pos] unless start_match
|
|
103
|
+
|
|
104
|
+
start_pos = pos + start_match.begin(0)
|
|
105
|
+
after_start = line[(start_pos + 2)..]
|
|
106
|
+
|
|
107
|
+
if after_start&.include?("*/")
|
|
108
|
+
[nil, nil, pos]
|
|
109
|
+
else
|
|
110
|
+
text = line[start_pos..]
|
|
111
|
+
token = Token.new(
|
|
112
|
+
type: :comment,
|
|
113
|
+
start_col: start_pos,
|
|
114
|
+
end_col: line.length - 1,
|
|
115
|
+
text:
|
|
116
|
+
)
|
|
117
|
+
[:block_comment, token, line.length]
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
private
|
|
122
|
+
|
|
123
|
+
def handle_block_comment(line, pos)
|
|
124
|
+
end_match = line[pos..].match(BLOCK_COMMENT_END)
|
|
125
|
+
if end_match
|
|
126
|
+
end_pos = pos + end_match.begin(0) + 1
|
|
127
|
+
text = line[pos..end_pos]
|
|
128
|
+
token = Token.new(
|
|
129
|
+
type: :comment,
|
|
130
|
+
start_col: pos,
|
|
131
|
+
end_col: end_pos,
|
|
132
|
+
text:
|
|
133
|
+
)
|
|
134
|
+
[token, nil, end_pos + 1]
|
|
135
|
+
else
|
|
136
|
+
text = line[pos..]
|
|
137
|
+
token = if text.empty?
|
|
138
|
+
nil
|
|
139
|
+
else
|
|
140
|
+
Token.new(
|
|
141
|
+
type: :comment,
|
|
142
|
+
start_col: pos,
|
|
143
|
+
end_col: line.length - 1,
|
|
144
|
+
text:
|
|
145
|
+
)
|
|
146
|
+
end
|
|
147
|
+
[token, :block_comment, line.length]
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def handle_template_literal(line, pos)
|
|
152
|
+
end_match = line[pos..].match(TEMPLATE_LITERAL_END)
|
|
153
|
+
if end_match
|
|
154
|
+
end_pos = pos + end_match.begin(0)
|
|
155
|
+
text = line[pos..end_pos]
|
|
156
|
+
token = Token.new(
|
|
157
|
+
type: :string,
|
|
158
|
+
start_col: pos,
|
|
159
|
+
end_col: end_pos,
|
|
160
|
+
text:
|
|
161
|
+
)
|
|
162
|
+
[token, nil, end_pos + 1]
|
|
163
|
+
else
|
|
164
|
+
text = line[pos..]
|
|
165
|
+
token = if text.empty?
|
|
166
|
+
nil
|
|
167
|
+
else
|
|
168
|
+
Token.new(
|
|
169
|
+
type: :string,
|
|
170
|
+
start_col: pos,
|
|
171
|
+
end_col: line.length - 1,
|
|
172
|
+
text:
|
|
173
|
+
)
|
|
174
|
+
end
|
|
175
|
+
[token, :template_literal, line.length]
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def match_token(line, pos)
|
|
180
|
+
# Check for start of template literal
|
|
181
|
+
if line[pos..].match?(TEMPLATE_LITERAL_START)
|
|
182
|
+
rest = line[(pos + 1)..]
|
|
183
|
+
return nil unless rest&.include?("`")
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
# Check for start of multiline comment
|
|
187
|
+
if line[pos..].match?(BLOCK_COMMENT_START_ANCHOR)
|
|
188
|
+
rest = line[(pos + 2)..]
|
|
189
|
+
return nil unless rest&.include?("*/")
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
super
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
end
|