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,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.nodelay = true
154
+ ::Curses.stdscr.timeout = 0
155
+
137
156
  key = ::Curses.getch
138
- return key.tap { ::Curses.stdscr.nodelay = false } unless key.is_a?(Integer)
139
- return key.tap { ::Curses.stdscr.nodelay = false } if key.negative? || key > 255
140
-
141
- result = if key >= 0x80
142
- read_utf8_char(key)
143
- else
144
- key
145
- end
146
- ::Curses.stdscr.nodelay = false
147
- result
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