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.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +13 -8
  3. data/CHANGELOG.md +99 -0
  4. data/README.md +309 -6
  5. data/docs/_config.yml +56 -0
  6. data/docs/configuration.md +301 -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 +149 -0
  13. data/lib/mui/command_completer.rb +11 -2
  14. data/lib/mui/command_history.rb +89 -0
  15. data/lib/mui/command_line.rb +32 -2
  16. data/lib/mui/command_registry.rb +21 -2
  17. data/lib/mui/config.rb +3 -1
  18. data/lib/mui/editor.rb +78 -2
  19. data/lib/mui/handler_result.rb +13 -7
  20. data/lib/mui/highlighters/search_highlighter.rb +2 -1
  21. data/lib/mui/highlighters/syntax_highlighter.rb +3 -1
  22. data/lib/mui/key_handler/base.rb +87 -0
  23. data/lib/mui/key_handler/command_mode.rb +68 -0
  24. data/lib/mui/key_handler/insert_mode.rb +10 -41
  25. data/lib/mui/key_handler/normal_mode.rb +24 -51
  26. data/lib/mui/key_handler/operators/paste_operator.rb +9 -3
  27. data/lib/mui/key_handler/search_mode.rb +10 -7
  28. data/lib/mui/key_handler/visual_mode.rb +15 -10
  29. data/lib/mui/key_notation_parser.rb +152 -0
  30. data/lib/mui/key_sequence.rb +67 -0
  31. data/lib/mui/key_sequence_buffer.rb +85 -0
  32. data/lib/mui/key_sequence_handler.rb +163 -0
  33. data/lib/mui/key_sequence_matcher.rb +79 -0
  34. data/lib/mui/line_renderer.rb +52 -1
  35. data/lib/mui/mode_manager.rb +3 -2
  36. data/lib/mui/screen.rb +24 -6
  37. data/lib/mui/search_state.rb +61 -28
  38. data/lib/mui/syntax/language_detector.rb +33 -1
  39. data/lib/mui/syntax/lexers/css_lexer.rb +121 -0
  40. data/lib/mui/syntax/lexers/go_lexer.rb +205 -0
  41. data/lib/mui/syntax/lexers/html_lexer.rb +118 -0
  42. data/lib/mui/syntax/lexers/javascript_lexer.rb +197 -0
  43. data/lib/mui/syntax/lexers/markdown_lexer.rb +210 -0
  44. data/lib/mui/syntax/lexers/rust_lexer.rb +148 -0
  45. data/lib/mui/syntax/lexers/typescript_lexer.rb +203 -0
  46. data/lib/mui/terminal_adapter/curses.rb +13 -11
  47. data/lib/mui/version.rb +1 -1
  48. data/lib/mui/window.rb +83 -40
  49. data/lib/mui/window_manager.rb +7 -0
  50. data/lib/mui/wrap_cache.rb +40 -0
  51. data/lib/mui/wrap_helper.rb +84 -0
  52. data/lib/mui.rb +15 -0
  53. metadata +26 -3
@@ -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
@@ -0,0 +1,148 @@
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
+ # Type names (start with uppercase)
63
+ [:constant, /\G\b[A-Z][a-zA-Z0-9_]*\b/],
64
+ # Regular identifiers
65
+ [:identifier, /\G\b[a-z_][a-zA-Z0-9_]*\b/],
66
+ # Operators
67
+ [:operator, %r{\G(?:&&|\|\||<<|>>|=>|->|::|\.\.=?|[+\-*/%&|^<>=!]=?|\?)}]
68
+ ].freeze
69
+
70
+ # Multiline comment patterns (pre-compiled)
71
+ BLOCK_COMMENT_END = %r{\*/}
72
+ BLOCK_COMMENT_START = %r{/\*}
73
+ BLOCK_COMMENT_START_ANCHOR = %r{\A/\*}
74
+
75
+ protected
76
+
77
+ def compiled_patterns
78
+ COMPILED_PATTERNS
79
+ end
80
+
81
+ # Handle /* ... */ block comments that span multiple lines
82
+ def handle_multiline_state(line, pos, state)
83
+ return [nil, nil, pos] unless state == :block_comment
84
+
85
+ end_match = line[pos..].match(BLOCK_COMMENT_END)
86
+ if end_match
87
+ end_pos = pos + end_match.begin(0) + 1
88
+ text = line[pos..end_pos]
89
+ token = Token.new(
90
+ type: :comment,
91
+ start_col: pos,
92
+ end_col: end_pos,
93
+ text:
94
+ )
95
+ [token, nil, end_pos + 1]
96
+ else
97
+ text = line[pos..]
98
+ token = if text.empty?
99
+ nil
100
+ else
101
+ Token.new(
102
+ type: :comment,
103
+ start_col: pos,
104
+ end_col: line.length - 1,
105
+ text:
106
+ )
107
+ end
108
+ [token, :block_comment, line.length]
109
+ end
110
+ end
111
+
112
+ def check_multiline_start(line, pos)
113
+ rest = line[pos..]
114
+
115
+ start_match = rest.match(BLOCK_COMMENT_START)
116
+ return [nil, nil, pos] unless start_match
117
+
118
+ start_pos = pos + start_match.begin(0)
119
+ after_start = line[(start_pos + 2)..]
120
+
121
+ if after_start&.include?("*/")
122
+ [nil, nil, pos]
123
+ else
124
+ text = line[start_pos..]
125
+ token = Token.new(
126
+ type: :comment,
127
+ start_col: start_pos,
128
+ end_col: line.length - 1,
129
+ text:
130
+ )
131
+ [:block_comment, token, line.length]
132
+ end
133
+ end
134
+
135
+ private
136
+
137
+ def match_token(line, pos)
138
+ if line[pos..].match?(BLOCK_COMMENT_START_ANCHOR)
139
+ rest = line[(pos + 2)..]
140
+ return nil unless rest&.include?("*/")
141
+ end
142
+
143
+ super
144
+ end
145
+ end
146
+ end
147
+ end
148
+ end
@@ -0,0 +1,203 @@
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
+ protected
71
+
72
+ def compiled_patterns
73
+ COMPILED_PATTERNS
74
+ end
75
+
76
+ # Handle multiline constructs
77
+ def handle_multiline_state(line, pos, state)
78
+ case state
79
+ when :block_comment
80
+ handle_block_comment(line, pos)
81
+ when :template_literal
82
+ handle_template_literal(line, pos)
83
+ else
84
+ [nil, nil, pos]
85
+ end
86
+ end
87
+
88
+ def check_multiline_start(line, pos)
89
+ rest = line[pos..]
90
+
91
+ # Check for template literal start
92
+ if rest.match?(TEMPLATE_LITERAL_START)
93
+ after_start = line[(pos + 1)..]
94
+ unless after_start&.include?("`")
95
+ text = line[pos..]
96
+ token = Token.new(
97
+ type: :string,
98
+ start_col: pos,
99
+ end_col: line.length - 1,
100
+ text:
101
+ )
102
+ return [:template_literal, token, line.length]
103
+ end
104
+ end
105
+
106
+ # Check for /* that doesn't have a matching */ on this line
107
+ start_match = rest.match(BLOCK_COMMENT_START)
108
+ return [nil, nil, pos] unless start_match
109
+
110
+ start_pos = pos + start_match.begin(0)
111
+ after_start = line[(start_pos + 2)..]
112
+
113
+ if after_start&.include?("*/")
114
+ [nil, nil, pos]
115
+ else
116
+ text = line[start_pos..]
117
+ token = Token.new(
118
+ type: :comment,
119
+ start_col: start_pos,
120
+ end_col: line.length - 1,
121
+ text:
122
+ )
123
+ [:block_comment, token, line.length]
124
+ end
125
+ end
126
+
127
+ private
128
+
129
+ def handle_block_comment(line, pos)
130
+ end_match = line[pos..].match(BLOCK_COMMENT_END)
131
+ if end_match
132
+ end_pos = pos + end_match.begin(0) + 1
133
+ text = line[pos..end_pos]
134
+ token = Token.new(
135
+ type: :comment,
136
+ start_col: pos,
137
+ end_col: end_pos,
138
+ text:
139
+ )
140
+ [token, nil, end_pos + 1]
141
+ else
142
+ text = line[pos..]
143
+ token = if text.empty?
144
+ nil
145
+ else
146
+ Token.new(
147
+ type: :comment,
148
+ start_col: pos,
149
+ end_col: line.length - 1,
150
+ text:
151
+ )
152
+ end
153
+ [token, :block_comment, line.length]
154
+ end
155
+ end
156
+
157
+ def handle_template_literal(line, pos)
158
+ end_match = line[pos..].match(TEMPLATE_LITERAL_END)
159
+ if end_match
160
+ end_pos = pos + end_match.begin(0)
161
+ text = line[pos..end_pos]
162
+ token = Token.new(
163
+ type: :string,
164
+ start_col: pos,
165
+ end_col: end_pos,
166
+ text:
167
+ )
168
+ [token, nil, end_pos + 1]
169
+ else
170
+ text = line[pos..]
171
+ token = if text.empty?
172
+ nil
173
+ else
174
+ Token.new(
175
+ type: :string,
176
+ start_col: pos,
177
+ end_col: line.length - 1,
178
+ text:
179
+ )
180
+ end
181
+ [token, :template_literal, line.length]
182
+ end
183
+ end
184
+
185
+ def match_token(line, pos)
186
+ # Check for start of template literal
187
+ if line[pos..].match?(TEMPLATE_LITERAL_START)
188
+ rest = line[(pos + 1)..]
189
+ return nil unless rest&.include?("`")
190
+ end
191
+
192
+ # Check for start of multiline comment
193
+ if line[pos..].match?(BLOCK_COMMENT_START_ANCHOR)
194
+ rest = line[(pos + 2)..]
195
+ return nil unless rest&.include?("*/")
196
+ end
197
+
198
+ super
199
+ end
200
+ end
201
+ end
202
+ end
203
+ 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)
@@ -133,18 +134,19 @@ module Mui
133
134
  public
134
135
 
135
136
  def getch_nonblock
136
- ::Curses.stdscr.nodelay = true
137
+ ::Curses.stdscr.timeout = 0
138
+
137
139
  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
140
+
141
+ return if key == -1 || key.nil?
142
+ return key unless key.is_a?(Integer)
143
+ return key if key.negative? || key > 255
144
+
145
+ if key >= 0x80
146
+ read_utf8_char(key)
147
+ else
148
+ key
149
+ end
148
150
  end
149
151
 
150
152
  def suspend
data/lib/mui/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Mui
4
- VERSION = "0.2.0"
4
+ VERSION = "0.3.0"
5
5
  end