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.
- checksums.yaml +4 -4
- data/.rubocop_todo.yml +18 -10
- data/CHANGELOG.md +162 -0
- data/README.md +309 -6
- data/docs/_config.yml +56 -0
- data/docs/configuration.md +314 -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 +155 -0
- data/lib/mui/color_manager.rb +140 -6
- data/lib/mui/color_scheme.rb +1 -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 +90 -2
- data/lib/mui/floating_window.rb +53 -1
- data/lib/mui/handler_result.rb +13 -7
- data/lib/mui/highlighters/search_highlighter.rb +2 -1
- data/lib/mui/highlighters/syntax_highlighter.rb +4 -1
- data/lib/mui/insert_completion_state.rb +15 -2
- 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 +159 -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 +30 -6
- data/lib/mui/search_state.rb +74 -27
- data/lib/mui/syntax/language_detector.rb +33 -1
- data/lib/mui/syntax/lexers/c_lexer.rb +2 -0
- data/lib/mui/syntax/lexers/css_lexer.rb +121 -0
- data/lib/mui/syntax/lexers/go_lexer.rb +207 -0
- data/lib/mui/syntax/lexers/html_lexer.rb +118 -0
- data/lib/mui/syntax/lexers/javascript_lexer.rb +219 -0
- data/lib/mui/syntax/lexers/markdown_lexer.rb +210 -0
- data/lib/mui/syntax/lexers/ruby_lexer.rb +3 -0
- data/lib/mui/syntax/lexers/rust_lexer.rb +150 -0
- data/lib/mui/syntax/lexers/typescript_lexer.rb +225 -0
- data/lib/mui/terminal_adapter/base.rb +21 -0
- data/lib/mui/terminal_adapter/curses.rb +37 -11
- data/lib/mui/themes/default.rb +263 -132
- data/lib/mui/version.rb +1 -1
- data/lib/mui/window.rb +105 -39
- 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,225 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Mui
|
|
4
|
+
module Syntax
|
|
5
|
+
module Lexers
|
|
6
|
+
# Lexer for TypeScript source code
|
|
7
|
+
# Extends JavaScript with additional TypeScript-specific patterns
|
|
8
|
+
class TypeScriptLexer < LexerBase
|
|
9
|
+
# TypeScript keywords (JavaScript keywords + TypeScript-specific)
|
|
10
|
+
KEYWORDS = %w[
|
|
11
|
+
async await break case catch class const continue debugger default
|
|
12
|
+
delete do else export extends finally for function if import in
|
|
13
|
+
instanceof let new return static switch throw try typeof var void
|
|
14
|
+
while with yield
|
|
15
|
+
abstract as asserts declare enum implements interface keyof
|
|
16
|
+
module namespace never override private protected public readonly
|
|
17
|
+
type unknown
|
|
18
|
+
].freeze
|
|
19
|
+
|
|
20
|
+
# TypeScript constants
|
|
21
|
+
CONSTANTS = %w[true false null undefined NaN Infinity this super].freeze
|
|
22
|
+
|
|
23
|
+
# Pre-compiled patterns with \G anchor for position-specific matching
|
|
24
|
+
COMPILED_PATTERNS = [
|
|
25
|
+
# Single line comment
|
|
26
|
+
[:comment, %r{\G//.*}],
|
|
27
|
+
# Single-line block comment /* ... */ on one line
|
|
28
|
+
[:comment, %r{\G/\*.*?\*/}],
|
|
29
|
+
# Template literal (single line)
|
|
30
|
+
[:string, /\G`[^`]*`/],
|
|
31
|
+
# Double quoted string (with escape handling)
|
|
32
|
+
[:string, /\G"(?:[^"\\]|\\.)*"/],
|
|
33
|
+
# Single quoted string (with escape handling)
|
|
34
|
+
[:string, /\G'(?:[^'\\]|\\.)*'/],
|
|
35
|
+
# Regular expression literal
|
|
36
|
+
[:regex, %r{\G/(?:[^/\\]|\\.)+/[gimsuy]*}],
|
|
37
|
+
# Float numbers (must be before integer)
|
|
38
|
+
[:number, /\G\b\d+\.\d+(?:e[+-]?\d+)?\b/i],
|
|
39
|
+
# Hexadecimal
|
|
40
|
+
[:number, /\G\b0x[0-9a-fA-F]+n?\b/i],
|
|
41
|
+
# Octal
|
|
42
|
+
[:number, /\G\b0o[0-7]+n?\b/i],
|
|
43
|
+
# Binary
|
|
44
|
+
[:number, /\G\b0b[01]+n?\b/i],
|
|
45
|
+
# Integer (with optional BigInt suffix)
|
|
46
|
+
[:number, /\G\b\d+n?\b/],
|
|
47
|
+
# Constants (true, false, null, undefined, NaN, Infinity, this, super)
|
|
48
|
+
[:constant, /\G\b(?:true|false|null|undefined|NaN|Infinity|this|super)\b/],
|
|
49
|
+
# TypeScript + JavaScript keywords
|
|
50
|
+
[:keyword, /\G\b(?:abstract|as|asserts|async|await|break|case|catch|class|const|continue|debugger|declare|default|delete|do|else|enum|export|extends|finally|for|function|if|implements|import|in|instanceof|interface|keyof|let|module|namespace|never|new|override|private|protected|public|readonly|return|static|switch|throw|try|type|typeof|unknown|var|void|while|with|yield)\b/],
|
|
51
|
+
# Generic type parameters <T> and type annotations
|
|
52
|
+
[:type, /\G<[A-Z][a-zA-Z0-9_,\s]*>/],
|
|
53
|
+
# Class/constructor/type names (start with uppercase)
|
|
54
|
+
[:constant, /\G\b[A-Z][a-zA-Z0-9_]*\b/],
|
|
55
|
+
# Regular identifiers
|
|
56
|
+
[:identifier, /\G\b[a-zA-Z_$][a-zA-Z0-9_$]*\b/],
|
|
57
|
+
# Operators (=> must come before = patterns)
|
|
58
|
+
[:operator, %r{\G(?:\.{3}|=>|&&=?|\|\|=?|\?\?=?|===?|!==?|>>>?=?|<<=?|\+\+|--|\?\.?|[+\-*/%&|^<>=!]=?)}]
|
|
59
|
+
].freeze
|
|
60
|
+
|
|
61
|
+
# Multiline patterns (pre-compiled)
|
|
62
|
+
BLOCK_COMMENT_END = %r{\*/}
|
|
63
|
+
BLOCK_COMMENT_START = %r{/\*}
|
|
64
|
+
BLOCK_COMMENT_START_ANCHOR = %r{\A/\*}
|
|
65
|
+
|
|
66
|
+
# Template literal patterns
|
|
67
|
+
TEMPLATE_LITERAL_START = /\A`/
|
|
68
|
+
TEMPLATE_LITERAL_END = /`/
|
|
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
|
+
|
|
92
|
+
protected
|
|
93
|
+
|
|
94
|
+
def compiled_patterns
|
|
95
|
+
COMPILED_PATTERNS
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Handle multiline constructs
|
|
99
|
+
def handle_multiline_state(line, pos, state)
|
|
100
|
+
case state
|
|
101
|
+
when :block_comment
|
|
102
|
+
handle_block_comment(line, pos)
|
|
103
|
+
when :template_literal
|
|
104
|
+
handle_template_literal(line, pos)
|
|
105
|
+
else
|
|
106
|
+
[nil, nil, pos]
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def check_multiline_start(line, pos)
|
|
111
|
+
rest = line[pos..]
|
|
112
|
+
|
|
113
|
+
# Check for template literal start
|
|
114
|
+
if rest.match?(TEMPLATE_LITERAL_START)
|
|
115
|
+
after_start = line[(pos + 1)..]
|
|
116
|
+
unless after_start&.include?("`")
|
|
117
|
+
text = line[pos..]
|
|
118
|
+
token = Token.new(
|
|
119
|
+
type: :string,
|
|
120
|
+
start_col: pos,
|
|
121
|
+
end_col: line.length - 1,
|
|
122
|
+
text:
|
|
123
|
+
)
|
|
124
|
+
return [:template_literal, token, line.length]
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# Check for /* that doesn't have a matching */ on this line
|
|
129
|
+
start_match = rest.match(BLOCK_COMMENT_START)
|
|
130
|
+
return [nil, nil, pos] unless start_match
|
|
131
|
+
|
|
132
|
+
start_pos = pos + start_match.begin(0)
|
|
133
|
+
after_start = line[(start_pos + 2)..]
|
|
134
|
+
|
|
135
|
+
if after_start&.include?("*/")
|
|
136
|
+
[nil, nil, pos]
|
|
137
|
+
else
|
|
138
|
+
text = line[start_pos..]
|
|
139
|
+
token = Token.new(
|
|
140
|
+
type: :comment,
|
|
141
|
+
start_col: start_pos,
|
|
142
|
+
end_col: line.length - 1,
|
|
143
|
+
text:
|
|
144
|
+
)
|
|
145
|
+
[:block_comment, token, line.length]
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
private
|
|
150
|
+
|
|
151
|
+
def handle_block_comment(line, pos)
|
|
152
|
+
end_match = line[pos..].match(BLOCK_COMMENT_END)
|
|
153
|
+
if end_match
|
|
154
|
+
end_pos = pos + end_match.begin(0) + 1
|
|
155
|
+
text = line[pos..end_pos]
|
|
156
|
+
token = Token.new(
|
|
157
|
+
type: :comment,
|
|
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: :comment,
|
|
170
|
+
start_col: pos,
|
|
171
|
+
end_col: line.length - 1,
|
|
172
|
+
text:
|
|
173
|
+
)
|
|
174
|
+
end
|
|
175
|
+
[token, :block_comment, line.length]
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def handle_template_literal(line, pos)
|
|
180
|
+
end_match = line[pos..].match(TEMPLATE_LITERAL_END)
|
|
181
|
+
if end_match
|
|
182
|
+
end_pos = pos + end_match.begin(0)
|
|
183
|
+
text = line[pos..end_pos]
|
|
184
|
+
token = Token.new(
|
|
185
|
+
type: :string,
|
|
186
|
+
start_col: pos,
|
|
187
|
+
end_col: end_pos,
|
|
188
|
+
text:
|
|
189
|
+
)
|
|
190
|
+
[token, nil, end_pos + 1]
|
|
191
|
+
else
|
|
192
|
+
text = line[pos..]
|
|
193
|
+
token = if text.empty?
|
|
194
|
+
nil
|
|
195
|
+
else
|
|
196
|
+
Token.new(
|
|
197
|
+
type: :string,
|
|
198
|
+
start_col: pos,
|
|
199
|
+
end_col: line.length - 1,
|
|
200
|
+
text:
|
|
201
|
+
)
|
|
202
|
+
end
|
|
203
|
+
[token, :template_literal, line.length]
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
def match_token(line, pos)
|
|
208
|
+
# Check for start of template literal
|
|
209
|
+
if line[pos..].match?(TEMPLATE_LITERAL_START)
|
|
210
|
+
rest = line[(pos + 1)..]
|
|
211
|
+
return nil unless rest&.include?("`")
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
# Check for start of multiline comment
|
|
215
|
+
if line[pos..].match?(BLOCK_COMMENT_START_ANCHOR)
|
|
216
|
+
rest = line[(pos + 2)..]
|
|
217
|
+
return nil unless rest&.include?("*/")
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
super
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
end
|
|
@@ -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
|
|
@@ -8,6 +8,7 @@ module Mui
|
|
|
8
8
|
class Curses < Base
|
|
9
9
|
def init
|
|
10
10
|
::Curses.init_screen
|
|
11
|
+
::Curses.ESCDELAY = 10
|
|
11
12
|
::Curses.raw
|
|
12
13
|
::Curses.noecho
|
|
13
14
|
::Curses.curs_set(1)
|
|
@@ -16,8 +17,25 @@ module Mui
|
|
|
16
17
|
end
|
|
17
18
|
|
|
18
19
|
def init_colors
|
|
20
|
+
return unless ::Curses.has_colors?
|
|
21
|
+
|
|
19
22
|
::Curses.start_color
|
|
20
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
|
|
21
39
|
end
|
|
22
40
|
|
|
23
41
|
def init_color_pair(pair_index, fg, bg)
|
|
@@ -133,18 +151,19 @@ module Mui
|
|
|
133
151
|
public
|
|
134
152
|
|
|
135
153
|
def getch_nonblock
|
|
136
|
-
::Curses.stdscr.
|
|
154
|
+
::Curses.stdscr.timeout = 0
|
|
155
|
+
|
|
137
156
|
key = ::Curses.getch
|
|
138
|
-
|
|
139
|
-
return key
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
157
|
+
|
|
158
|
+
return if key == -1 || key.nil?
|
|
159
|
+
return key unless key.is_a?(Integer)
|
|
160
|
+
return key if key.negative? || key > 255
|
|
161
|
+
|
|
162
|
+
if key >= 0x80
|
|
163
|
+
read_utf8_char(key)
|
|
164
|
+
else
|
|
165
|
+
key
|
|
166
|
+
end
|
|
148
167
|
end
|
|
149
168
|
|
|
150
169
|
def suspend
|
|
@@ -157,6 +176,13 @@ module Mui
|
|
|
157
176
|
::Curses.refresh
|
|
158
177
|
::Curses.curs_set(1)
|
|
159
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
|
|
160
186
|
end
|
|
161
187
|
end
|
|
162
188
|
end
|