mui 0.3.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 +8 -5
- data/CHANGELOG.md +63 -0
- data/docs/configuration.md +22 -9
- data/docs/syntax-highlighting.md +6 -0
- data/lib/mui/color_manager.rb +140 -6
- data/lib/mui/color_scheme.rb +1 -0
- data/lib/mui/editor.rb +12 -0
- data/lib/mui/floating_window.rb +53 -1
- data/lib/mui/highlighters/syntax_highlighter.rb +1 -0
- data/lib/mui/insert_completion_state.rb +15 -2
- data/lib/mui/key_notation_parser.rb +10 -3
- data/lib/mui/screen.rb +6 -0
- data/lib/mui/search_state.rb +25 -11
- data/lib/mui/syntax/lexers/c_lexer.rb +2 -0
- data/lib/mui/syntax/lexers/go_lexer.rb +2 -0
- data/lib/mui/syntax/lexers/javascript_lexer.rb +22 -0
- data/lib/mui/syntax/lexers/ruby_lexer.rb +3 -0
- data/lib/mui/syntax/lexers/rust_lexer.rb +2 -0
- data/lib/mui/syntax/lexers/typescript_lexer.rb +22 -0
- data/lib/mui/terminal_adapter/base.rb +21 -0
- data/lib/mui/terminal_adapter/curses.rb +24 -0
- data/lib/mui/themes/default.rb +263 -132
- data/lib/mui/version.rb +1 -1
- data/lib/mui/window.rb +23 -0
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: fc6a6725223b44bd937314729fc4008b726123dd1624adfdf3adc868b9d4d71d
|
|
4
|
+
data.tar.gz: 1c58af876fa5c06fb9033a9968dd1de012265f3d3b85bb50a46b17a9e24eeca5
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a7a0d46bdf37d31dd6a475b4c42665462e15ed2eee1cad432eed7dcec13776974cd95ed1853e2856fffae2005d367ae6a78294d30e2343ee25240995e943c5dc
|
|
7
|
+
data.tar.gz: 1f7b17692e0f2349eadbdd38b3687ca661f6d8f6196ba162579e28cc8b7f837d00d36d1ba1533a57d52e3e20f0f1510f51dc5333c2b494bc053c6533e62fd324
|
data/.rubocop_todo.yml
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# This configuration was generated by
|
|
2
2
|
# `rubocop --auto-gen-config`
|
|
3
|
-
# on 2025-12-
|
|
3
|
+
# on 2025-12-18 10:56:40 UTC using RuboCop version 1.81.7.
|
|
4
4
|
# The point is for the user to remove these configuration records
|
|
5
5
|
# one by one as the offenses are removed from the code base.
|
|
6
6
|
# Note that changes in the inspected code, or installation of new
|
|
@@ -48,7 +48,7 @@ Lint/UselessConstantScoping:
|
|
|
48
48
|
Exclude:
|
|
49
49
|
- 'lib/mui/buffer_word_cache.rb'
|
|
50
50
|
|
|
51
|
-
# Offense count:
|
|
51
|
+
# Offense count: 35
|
|
52
52
|
# Configuration parameters: AllowedMethods, AllowedPatterns.
|
|
53
53
|
Metrics/CyclomaticComplexity:
|
|
54
54
|
Max: 49
|
|
@@ -56,14 +56,14 @@ Metrics/CyclomaticComplexity:
|
|
|
56
56
|
# Offense count: 2
|
|
57
57
|
# Configuration parameters: CountComments, CountAsOne.
|
|
58
58
|
Metrics/ModuleLength:
|
|
59
|
-
Max:
|
|
59
|
+
Max: 328
|
|
60
60
|
|
|
61
61
|
# Offense count: 10
|
|
62
62
|
# Configuration parameters: CountKeywordArgs, MaxOptionalParameters.
|
|
63
63
|
Metrics/ParameterLists:
|
|
64
64
|
Max: 9
|
|
65
65
|
|
|
66
|
-
# Offense count:
|
|
66
|
+
# Offense count: 15
|
|
67
67
|
# Configuration parameters: AllowedMethods, AllowedPatterns.
|
|
68
68
|
Metrics/PerceivedComplexity:
|
|
69
69
|
Max: 19
|
|
@@ -123,7 +123,7 @@ Naming/PredicateMethod:
|
|
|
123
123
|
- 'lib/mui/undo_manager.rb'
|
|
124
124
|
- 'lib/mui/window_manager.rb'
|
|
125
125
|
|
|
126
|
-
# Offense count:
|
|
126
|
+
# Offense count: 4
|
|
127
127
|
# Configuration parameters: NamePrefix, ForbiddenPrefixes, AllowedMethods, MethodDefinitionMacros, UseSorbetSigs.
|
|
128
128
|
# NamePrefix: is_, has_, have_, does_
|
|
129
129
|
# ForbiddenPrefixes: is_, has_, have_, does_
|
|
@@ -133,6 +133,9 @@ Naming/PredicatePrefix:
|
|
|
133
133
|
Exclude:
|
|
134
134
|
- 'spec/**/*'
|
|
135
135
|
- 'lib/mui/search_state.rb'
|
|
136
|
+
- 'lib/mui/terminal_adapter/base.rb'
|
|
137
|
+
- 'lib/mui/terminal_adapter/curses.rb'
|
|
138
|
+
- 'test/test_helper.rb'
|
|
136
139
|
|
|
137
140
|
# Offense count: 2
|
|
138
141
|
# Configuration parameters: EnforcedStyle, CheckMethodNames, CheckSymbols, AllowedIdentifiers, AllowedPatterns.
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,68 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [0.4.0] 2025-12-19
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- `<S-Tab>` (Shift+Tab) notation support in keymap configuration:
|
|
7
|
+
- Added `s-tab` and `btab` to `SPECIAL_KEYS` mapping to `:shift_tab` symbol
|
|
8
|
+
- `normalize_input_key()` now handles `KEY_BTAB` (353) from Curses
|
|
9
|
+
- Enables tab navigation keymaps like:
|
|
10
|
+
```ruby
|
|
11
|
+
Mui.keymap :normal, "<Tab>", ->(ctx) { ctx.editor.tab_manager.next_tab }
|
|
12
|
+
Mui.keymap :normal, "<S-Tab>", ->(ctx) { ctx.editor.tab_manager.prev_tab }
|
|
13
|
+
```
|
|
14
|
+
- Function definition name highlighting for 6 languages:
|
|
15
|
+
- Ruby: `def hello` highlights `hello` (lookbehind pattern `(?<=def )`)
|
|
16
|
+
- Go: `func main()` highlights `main` (lookbehind pattern `(?<=func )`)
|
|
17
|
+
- Rust: `fn calculate()` highlights `calculate` (lookbehind pattern `(?<=fn )`)
|
|
18
|
+
- JavaScript: `function add()` highlights `add` (post-process after `function` keyword)
|
|
19
|
+
- TypeScript: `function fetchData()` highlights `fetchData` (post-process after `function` keyword)
|
|
20
|
+
- C: `int main()` highlights `main` (lookahead pattern for identifier before `(`)
|
|
21
|
+
- New token type `:function_definition` mapped to `syntax_function_definition` color scheme element
|
|
22
|
+
- All 8 themes updated with `syntax_function_definition` style (same color as `syntax_method_call`)
|
|
23
|
+
- Improved mui default theme with 256-color palette:
|
|
24
|
+
- Eye-friendly gray-based color scheme using 256-color palette
|
|
25
|
+
- 19 new `mui_*` colors in `EXTENDED_COLOR_MAP` (mui_bg, mui_fg, mui_comment, etc.)
|
|
26
|
+
- Syntax highlighting follows Vim standard highlight groups (Comment, Constant, Identifier, Statement, PreProc, Type, Special)
|
|
27
|
+
- 8-color fallbacks for all mui colors
|
|
28
|
+
- Improved all bundled themes to match their original color schemes:
|
|
29
|
+
- Solarized Dark/Light: Adjusted visual selection, operators, and diff colors
|
|
30
|
+
- Monokai: Fixed comment color to olive gray (#75715e), added `monokai_comment` color
|
|
31
|
+
- Nord: Adjusted status line and constant colors to match Nord palette
|
|
32
|
+
- Gruvbox: Adjusted identifier and keyword colors
|
|
33
|
+
- Dracula: Adjusted visual selection and symbol colors
|
|
34
|
+
- Tokyo Night: Adjusted operator and constant colors
|
|
35
|
+
- Added LSP diagnostics and floating window styles to all themes:
|
|
36
|
+
- `diagnostic_error`, `diagnostic_warning`, `diagnostic_info`, `diagnostic_hint`
|
|
37
|
+
- `floating_window`
|
|
38
|
+
- 256-color support stabilization:
|
|
39
|
+
- Environment capability detection (`Curses.has_colors?`, `Curses.colors`, `Curses.color_pairs`)
|
|
40
|
+
- Automatic 8-color fallback for terminals without 256-color support
|
|
41
|
+
- `FALLBACK_MAP` to map extended colors to nearest basic 8-color equivalents
|
|
42
|
+
- LRU cache for color pairs to prevent pair exhaustion
|
|
43
|
+
- Automatic eviction of oldest pairs when limit is reached
|
|
44
|
+
- Backward compatible: `ColorManager.new` without adapter assumes 256 colors
|
|
45
|
+
- `ColorManager#supports_256_colors` to check color capability
|
|
46
|
+
- `TerminalAdapter::Base#has_colors?`, `#colors`, `#color_pairs` interface methods
|
|
47
|
+
|
|
48
|
+
### Fixed
|
|
49
|
+
- Fixed text corruption after closing floating window or completion popup:
|
|
50
|
+
- Japanese/CJK multibyte characters were not displaying correctly after popup closed
|
|
51
|
+
- Added `touchwin` method to force complete screen redraw using `Curses.stdscr.redraw`
|
|
52
|
+
- `FloatingWindow` now tracks `last_bounds` and `needs_clear` flag
|
|
53
|
+
- `InsertCompletionState` now tracks `needs_clear` flag
|
|
54
|
+
- Editor calls `touchwin` when popup is closed to restore corrupted characters
|
|
55
|
+
|
|
56
|
+
### Changed
|
|
57
|
+
- Improved search performance for large files:
|
|
58
|
+
- Added row-based index (`row_index`) to search match cache for O(1) lookup
|
|
59
|
+
- `matches_for_row()` now uses hash lookup instead of linear search
|
|
60
|
+
- Before: O(M × visible_rows) per frame, After: O(visible_rows) per frame
|
|
61
|
+
- Improved navigation performance for large files (G, gg commands):
|
|
62
|
+
- Added smart scroll jump for cursor movements over 100 rows
|
|
63
|
+
- Directly calculates scroll position instead of iterating line by line
|
|
64
|
+
- Before: O(n) where n = cursor distance, After: O(1)
|
|
65
|
+
|
|
3
66
|
## [0.3.0] - 2025-12-15
|
|
4
67
|
|
|
5
68
|
### Added
|
data/docs/configuration.md
CHANGED
|
@@ -36,15 +36,27 @@ Configuration files are written in Ruby using Mui's DSL.
|
|
|
36
36
|
Mui.set :colorscheme, "tokyo_night"
|
|
37
37
|
```
|
|
38
38
|
|
|
39
|
-
Available themes:
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
39
|
+
Available themes (all themes support 256-color palette with 8-color fallback):
|
|
40
|
+
|
|
41
|
+
| Theme | Description |
|
|
42
|
+
|-------|-------------|
|
|
43
|
+
| `mui` | Default theme with eye-friendly gray-based colors |
|
|
44
|
+
| `solarized_dark` | Solarized dark theme |
|
|
45
|
+
| `solarized_light` | Solarized light theme |
|
|
46
|
+
| `monokai` | Monokai theme |
|
|
47
|
+
| `nord` | Nord theme |
|
|
48
|
+
| `gruvbox_dark` | Gruvbox dark theme |
|
|
49
|
+
| `dracula` | Dracula theme |
|
|
50
|
+
| `tokyo_night` | Tokyo Night theme |
|
|
51
|
+
|
|
52
|
+
### 256-Color Support
|
|
53
|
+
|
|
54
|
+
All bundled themes utilize 256-color palettes for rich syntax highlighting and UI elements. Mui automatically detects your terminal's color capabilities:
|
|
55
|
+
|
|
56
|
+
- **256-color terminals**: Full color palette with all theme colors
|
|
57
|
+
- **8-color terminals**: Automatic fallback to basic 8-color equivalents
|
|
58
|
+
|
|
59
|
+
Most modern terminals (iTerm2, gnome-terminal, Windows Terminal, etc.) support 256 colors. If colors appear incorrect, ensure your terminal's `TERM` environment variable is set correctly (e.g., `xterm-256color`).
|
|
48
60
|
|
|
49
61
|
### Indentation
|
|
50
62
|
|
|
@@ -125,6 +137,7 @@ end
|
|
|
125
137
|
| `<Leader>` | Leader key |
|
|
126
138
|
| `<Space>` | Space bar |
|
|
127
139
|
| `<Tab>` | Tab key |
|
|
140
|
+
| `<S-Tab>`, `<btab>` | Shift+Tab |
|
|
128
141
|
| `<CR>`, `<Enter>` | Enter key |
|
|
129
142
|
| `<Esc>` | Escape key |
|
|
130
143
|
| `<BS>` | Backspace |
|
data/docs/syntax-highlighting.md
CHANGED
|
@@ -144,6 +144,12 @@ Each theme defines colors for:
|
|
|
144
144
|
| `syntax_number` | Numeric literals |
|
|
145
145
|
| `syntax_type` | Types and classes |
|
|
146
146
|
| `syntax_function` | Function names |
|
|
147
|
+
| `syntax_function_definition` | Function definition names |
|
|
147
148
|
| `syntax_variable` | Variables |
|
|
148
149
|
| `syntax_constant` | Constants |
|
|
149
150
|
| `syntax_operator` | Operators |
|
|
151
|
+
| `diagnostic_error` | LSP diagnostic errors |
|
|
152
|
+
| `diagnostic_warning` | LSP diagnostic warnings |
|
|
153
|
+
| `diagnostic_info` | LSP diagnostic information |
|
|
154
|
+
| `diagnostic_hint` | LSP diagnostic hints |
|
|
155
|
+
| `floating_window` | Floating window background |
|
data/lib/mui/color_manager.rb
CHANGED
|
@@ -17,8 +17,27 @@ module Mui
|
|
|
17
17
|
# 256-color palette extended colors
|
|
18
18
|
# Use https://www.ditig.com/256-colors-cheat-sheet for reference
|
|
19
19
|
EXTENDED_COLOR_MAP = {
|
|
20
|
-
# mui theme
|
|
21
|
-
|
|
20
|
+
# mui theme - Eye-friendly gray-based theme
|
|
21
|
+
mui_bg: 236, # #303030 - Calm dark gray background
|
|
22
|
+
mui_fg: 253, # #dadada - Soft white (easy on the eyes)
|
|
23
|
+
mui_comment: 102, # #878787 - Subtle gray (for comments)
|
|
24
|
+
mui_constant: 110, # #87afd7 - Calm blue (constants/strings/numbers)
|
|
25
|
+
mui_identifier: 174, # #d78787 - Soft salmon pink
|
|
26
|
+
mui_statement: 186, # #d7d787 - Subtle yellow (keywords)
|
|
27
|
+
mui_preproc: 173, # #d7875f - Orange/brown (preprocessor)
|
|
28
|
+
mui_type: 109, # #87afaf - Calm cyan (types)
|
|
29
|
+
mui_special: 180, # #d7af87 - Soft beige (symbols)
|
|
30
|
+
mui_function: 216, # #ffaf87 - Peach/orange (functions)
|
|
31
|
+
# UI colors
|
|
32
|
+
mui_line_number: 243, # #767676 - Subtle gray
|
|
33
|
+
mui_status_bg: 238, # #444444 - Status bar background
|
|
34
|
+
mui_visual: 239, # #4e4e4e - Selection background
|
|
35
|
+
mui_search: 222, # #ffd787 - Search highlight (prominent yellow)
|
|
36
|
+
mui_tab_bg: 237, # #3a3a3a - Tab bar background
|
|
37
|
+
mui_tab_active: 110, # #87afd7 - Active tab
|
|
38
|
+
mui_error: 167, # #d75f5f - Error messages
|
|
39
|
+
mui_info: 109, # #87afaf - Info messages
|
|
40
|
+
darkgray: 235, # #262626 (~#2b2b2b) - Kept for backward compatibility
|
|
22
41
|
|
|
23
42
|
# solarized
|
|
24
43
|
solarized_base03: 234, # #1c1c1c (~#002b36)
|
|
@@ -41,6 +60,7 @@ module Mui
|
|
|
41
60
|
# monokai
|
|
42
61
|
monokai_bg: 235, # #262626 (~#272822)
|
|
43
62
|
monokai_fg: 231, # #ffffff (~#f8f8f2)
|
|
63
|
+
monokai_comment: 101, # #87875f (~#75715e) - Olive gray for comments
|
|
44
64
|
monokai_pink: 197, # #ff005f (~#f92672)
|
|
45
65
|
monokai_green: 148, # #afd700 (~#a6e22e)
|
|
46
66
|
monokai_orange: 208, # #ff8700 (~#fd971f)
|
|
@@ -104,18 +124,95 @@ module Mui
|
|
|
104
124
|
tokyo_yellow: 223 # #ffd7af (~#e0af68)
|
|
105
125
|
}.freeze
|
|
106
126
|
|
|
107
|
-
|
|
127
|
+
# Fallback map: 256-color to 8-color
|
|
128
|
+
FALLBACK_MAP = {
|
|
129
|
+
# mui theme
|
|
130
|
+
mui_bg: :black,
|
|
131
|
+
mui_fg: :white,
|
|
132
|
+
mui_comment: :white,
|
|
133
|
+
mui_constant: :cyan,
|
|
134
|
+
mui_identifier: :red,
|
|
135
|
+
mui_statement: :yellow,
|
|
136
|
+
mui_preproc: :yellow,
|
|
137
|
+
mui_type: :cyan,
|
|
138
|
+
mui_special: :yellow,
|
|
139
|
+
mui_function: :yellow,
|
|
140
|
+
mui_line_number: :white,
|
|
141
|
+
mui_status_bg: :blue,
|
|
142
|
+
mui_visual: :magenta,
|
|
143
|
+
mui_search: :yellow,
|
|
144
|
+
mui_tab_bg: :blue,
|
|
145
|
+
mui_tab_active: :cyan,
|
|
146
|
+
mui_error: :red,
|
|
147
|
+
mui_info: :cyan,
|
|
148
|
+
darkgray: :black,
|
|
149
|
+
# solarized
|
|
150
|
+
solarized_base03: :black, solarized_base02: :black,
|
|
151
|
+
solarized_base01: :white, solarized_base00: :white,
|
|
152
|
+
solarized_base0: :white, solarized_base1: :white,
|
|
153
|
+
solarized_base2: :white, solarized_base3: :white,
|
|
154
|
+
solarized_yellow: :yellow, solarized_orange: :red,
|
|
155
|
+
solarized_red: :red, solarized_magenta: :magenta,
|
|
156
|
+
solarized_violet: :blue, solarized_blue: :blue,
|
|
157
|
+
solarized_cyan: :cyan, solarized_green: :green,
|
|
158
|
+
# monokai
|
|
159
|
+
monokai_bg: :black, monokai_fg: :white, monokai_comment: :white,
|
|
160
|
+
monokai_pink: :magenta, monokai_green: :green,
|
|
161
|
+
monokai_orange: :yellow, monokai_purple: :magenta,
|
|
162
|
+
monokai_cyan: :cyan, monokai_yellow: :yellow,
|
|
163
|
+
# nord
|
|
164
|
+
nord_polar0: :black, nord_polar1: :black,
|
|
165
|
+
nord_polar2: :black, nord_polar3: :white,
|
|
166
|
+
nord_snow0: :white, nord_snow1: :white, nord_snow2: :white,
|
|
167
|
+
nord_frost0: :cyan, nord_frost1: :cyan,
|
|
168
|
+
nord_frost2: :blue, nord_frost3: :blue,
|
|
169
|
+
nord_aurora_red: :red, nord_aurora_orange: :yellow,
|
|
170
|
+
nord_aurora_yellow: :yellow, nord_aurora_green: :green,
|
|
171
|
+
nord_aurora_purple: :magenta,
|
|
172
|
+
# gruvbox
|
|
173
|
+
gruvbox_bg: :black, gruvbox_fg: :white,
|
|
174
|
+
gruvbox_red: :red, gruvbox_green: :green,
|
|
175
|
+
gruvbox_yellow: :yellow, gruvbox_blue: :blue,
|
|
176
|
+
gruvbox_purple: :magenta, gruvbox_aqua: :cyan,
|
|
177
|
+
gruvbox_orange: :yellow, gruvbox_gray: :white,
|
|
178
|
+
# dracula
|
|
179
|
+
dracula_bg: :black, dracula_fg: :white,
|
|
180
|
+
dracula_selection: :black, dracula_comment: :blue,
|
|
181
|
+
dracula_cyan: :cyan, dracula_green: :green,
|
|
182
|
+
dracula_orange: :yellow, dracula_pink: :magenta,
|
|
183
|
+
dracula_purple: :magenta, dracula_red: :red,
|
|
184
|
+
dracula_yellow: :yellow,
|
|
185
|
+
# tokyo night
|
|
186
|
+
tokyo_bg: :black, tokyo_fg: :white,
|
|
187
|
+
tokyo_comment: :blue, tokyo_cyan: :cyan,
|
|
188
|
+
tokyo_blue: :blue, tokyo_purple: :magenta,
|
|
189
|
+
tokyo_green: :green, tokyo_orange: :yellow,
|
|
190
|
+
tokyo_red: :red, tokyo_yellow: :yellow
|
|
191
|
+
}.freeze
|
|
108
192
|
|
|
109
|
-
|
|
193
|
+
attr_reader :pairs, :supports_256_colors
|
|
194
|
+
|
|
195
|
+
def initialize(adapter: nil)
|
|
110
196
|
@pair_index = 1
|
|
111
197
|
@pairs = {}
|
|
198
|
+
@pair_order = []
|
|
199
|
+
@adapter = adapter
|
|
200
|
+
configure_color_capability
|
|
112
201
|
end
|
|
113
202
|
|
|
114
203
|
def register_pair(fg, bg)
|
|
115
204
|
key = [fg, bg]
|
|
116
|
-
|
|
205
|
+
|
|
206
|
+
if @pairs[key]
|
|
207
|
+
touch_pair(key)
|
|
208
|
+
return @pairs[key]
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
# Check pair limit and evict oldest if needed
|
|
212
|
+
evict_oldest_pair if @max_pairs.positive? && @pair_index >= @max_pairs
|
|
117
213
|
|
|
118
214
|
@pairs[key] = @pair_index
|
|
215
|
+
@pair_order << key
|
|
119
216
|
@pair_index += 1
|
|
120
217
|
@pairs[key]
|
|
121
218
|
end
|
|
@@ -128,9 +225,46 @@ module Mui
|
|
|
128
225
|
return -1 if color.nil?
|
|
129
226
|
return color if color.is_a?(Integer)
|
|
130
227
|
|
|
131
|
-
|
|
228
|
+
resolved_color = resolve_with_fallback(color)
|
|
229
|
+
COLOR_MAP[resolved_color] || EXTENDED_COLOR_MAP[resolved_color] || -1
|
|
132
230
|
end
|
|
133
231
|
|
|
134
232
|
alias resolve color_code
|
|
233
|
+
|
|
234
|
+
private
|
|
235
|
+
|
|
236
|
+
def configure_color_capability
|
|
237
|
+
if @adapter.nil?
|
|
238
|
+
# Backward compatibility: assume 256 colors when adapter is not specified
|
|
239
|
+
@available_colors = 256
|
|
240
|
+
@max_pairs = 256
|
|
241
|
+
@supports_256_colors = true
|
|
242
|
+
elsif @adapter.has_colors?
|
|
243
|
+
@available_colors = @adapter.colors
|
|
244
|
+
@max_pairs = [@adapter.color_pairs, 256].min
|
|
245
|
+
@supports_256_colors = @available_colors >= 256
|
|
246
|
+
else
|
|
247
|
+
@available_colors = 0
|
|
248
|
+
@max_pairs = 0
|
|
249
|
+
@supports_256_colors = false
|
|
250
|
+
end
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def resolve_with_fallback(color)
|
|
254
|
+
return color if @supports_256_colors
|
|
255
|
+
return color if COLOR_MAP.key?(color)
|
|
256
|
+
|
|
257
|
+
FALLBACK_MAP[color] || :white
|
|
258
|
+
end
|
|
259
|
+
|
|
260
|
+
def touch_pair(key)
|
|
261
|
+
@pair_order.delete(key)
|
|
262
|
+
@pair_order << key
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
def evict_oldest_pair
|
|
266
|
+
oldest_key = @pair_order.shift
|
|
267
|
+
@pairs.delete(oldest_key) if oldest_key
|
|
268
|
+
end
|
|
135
269
|
end
|
|
136
270
|
end
|
data/lib/mui/color_scheme.rb
CHANGED
data/lib/mui/editor.rb
CHANGED
|
@@ -238,8 +238,20 @@ module Mui
|
|
|
238
238
|
end
|
|
239
239
|
|
|
240
240
|
def render
|
|
241
|
+
# Force complete redraw if floating window or completion popup was closed
|
|
242
|
+
# This is needed because multibyte characters (CJK) can be corrupted
|
|
243
|
+
# when partially overwritten by popups
|
|
244
|
+
if @floating_window.needs_clear? || @insert_completion_state.needs_clear?
|
|
245
|
+
@screen.touchwin
|
|
246
|
+
@insert_completion_state.clear_needs_clear
|
|
247
|
+
end
|
|
248
|
+
|
|
241
249
|
@screen.clear
|
|
242
250
|
|
|
251
|
+
# Clear the area where the floating window was previously displayed
|
|
252
|
+
# Must be done before window rendering to avoid overwriting text
|
|
253
|
+
@floating_window.clear_last_bounds(@screen) if @floating_window.needs_clear?
|
|
254
|
+
|
|
243
255
|
@tab_bar_renderer.render(@screen, 0)
|
|
244
256
|
|
|
245
257
|
window.ensure_cursor_visible
|
data/lib/mui/floating_window.rb
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
module Mui
|
|
4
4
|
# A floating window (popup) for displaying temporary content like hover info
|
|
5
5
|
class FloatingWindow
|
|
6
|
-
attr_reader :content, :row, :col, :width, :height
|
|
6
|
+
attr_reader :content, :row, :col, :width, :height, :last_bounds
|
|
7
7
|
attr_accessor :visible
|
|
8
8
|
|
|
9
9
|
def initialize(color_scheme)
|
|
@@ -15,6 +15,8 @@ module Mui
|
|
|
15
15
|
@height = 0
|
|
16
16
|
@visible = false
|
|
17
17
|
@scroll_offset = 0
|
|
18
|
+
@last_bounds = nil
|
|
19
|
+
@needs_clear = false
|
|
18
20
|
end
|
|
19
21
|
|
|
20
22
|
# Show the floating window with content at the specified position
|
|
@@ -27,14 +29,49 @@ module Mui
|
|
|
27
29
|
@scroll_offset = 0
|
|
28
30
|
calculate_dimensions
|
|
29
31
|
@visible = true
|
|
32
|
+
@needs_clear = false
|
|
30
33
|
end
|
|
31
34
|
|
|
32
35
|
# Hide the floating window
|
|
33
36
|
def hide
|
|
37
|
+
return unless @visible
|
|
38
|
+
|
|
39
|
+
# Record bounds for clearing on next render
|
|
40
|
+
@last_bounds = {
|
|
41
|
+
row: @row,
|
|
42
|
+
col: @col,
|
|
43
|
+
width: @width,
|
|
44
|
+
height: @height
|
|
45
|
+
}
|
|
46
|
+
@needs_clear = true
|
|
34
47
|
@visible = false
|
|
35
48
|
@content = []
|
|
36
49
|
end
|
|
37
50
|
|
|
51
|
+
# Check if the previous window area needs to be cleared
|
|
52
|
+
def needs_clear?
|
|
53
|
+
@needs_clear && @last_bounds
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Clear the area where the floating window was previously displayed
|
|
57
|
+
def clear_last_bounds(screen)
|
|
58
|
+
return unless needs_clear?
|
|
59
|
+
|
|
60
|
+
bounds = @last_bounds
|
|
61
|
+
adjusted_row, adjusted_col = adjust_position_for_bounds(screen, bounds)
|
|
62
|
+
|
|
63
|
+
bounds[:height].times do |i|
|
|
64
|
+
row = adjusted_row + i
|
|
65
|
+
next if row.negative? || row >= screen.height
|
|
66
|
+
|
|
67
|
+
spaces = " " * bounds[:width]
|
|
68
|
+
screen.put(row, adjusted_col, spaces)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
@needs_clear = false
|
|
72
|
+
@last_bounds = nil
|
|
73
|
+
end
|
|
74
|
+
|
|
38
75
|
# Scroll content up
|
|
39
76
|
def scroll_up
|
|
40
77
|
@scroll_offset = [@scroll_offset - 1, 0].max if @visible
|
|
@@ -105,6 +142,21 @@ module Mui
|
|
|
105
142
|
[row, col]
|
|
106
143
|
end
|
|
107
144
|
|
|
145
|
+
def adjust_position_for_bounds(screen, bounds)
|
|
146
|
+
row = bounds[:row]
|
|
147
|
+
col = bounds[:col]
|
|
148
|
+
|
|
149
|
+
# Adjust horizontal position
|
|
150
|
+
col = screen.width - bounds[:width] if col + bounds[:width] > screen.width
|
|
151
|
+
col = [col, 0].max
|
|
152
|
+
|
|
153
|
+
# Adjust vertical position
|
|
154
|
+
row = bounds[:row] - bounds[:height] if row + bounds[:height] > screen.height
|
|
155
|
+
row = [row, 0].max
|
|
156
|
+
|
|
157
|
+
[row, col]
|
|
158
|
+
end
|
|
159
|
+
|
|
108
160
|
def draw_border(screen, row, col)
|
|
109
161
|
style = @color_scheme[:floating_window] || @color_scheme[:completion_popup]
|
|
110
162
|
|
|
@@ -19,6 +19,7 @@ module Mui
|
|
|
19
19
|
instance_variable: :syntax_instance_variable,
|
|
20
20
|
global_variable: :syntax_global_variable,
|
|
21
21
|
method_call: :syntax_method_call,
|
|
22
|
+
function_definition: :syntax_function_definition,
|
|
22
23
|
type: :syntax_type,
|
|
23
24
|
macro: :syntax_keyword, # Rust macros (println!, vec!, etc.)
|
|
24
25
|
regex: :syntax_string # JavaScript/TypeScript regex literals
|
|
@@ -6,16 +6,29 @@ module Mui
|
|
|
6
6
|
attr_reader :items, :selected_index, :prefix, :original_items
|
|
7
7
|
|
|
8
8
|
def initialize
|
|
9
|
-
|
|
9
|
+
@needs_clear = false
|
|
10
|
+
reset(set_needs_clear: false)
|
|
10
11
|
end
|
|
11
12
|
|
|
12
|
-
def reset
|
|
13
|
+
def reset(set_needs_clear: true)
|
|
14
|
+
# Set needs_clear flag if we had items (popup was visible)
|
|
15
|
+
@needs_clear = true if set_needs_clear && !@items.empty?
|
|
13
16
|
@items = []
|
|
14
17
|
@original_items = []
|
|
15
18
|
@selected_index = 0
|
|
16
19
|
@prefix = ""
|
|
17
20
|
end
|
|
18
21
|
|
|
22
|
+
# Check if the previous popup area needs to be cleared
|
|
23
|
+
def needs_clear?
|
|
24
|
+
@needs_clear
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Clear the needs_clear flag after redraw
|
|
28
|
+
def clear_needs_clear
|
|
29
|
+
@needs_clear = false
|
|
30
|
+
end
|
|
31
|
+
|
|
19
32
|
def active?
|
|
20
33
|
!@items.empty?
|
|
21
34
|
end
|
|
@@ -10,6 +10,8 @@ module Mui
|
|
|
10
10
|
SPECIAL_KEYS = {
|
|
11
11
|
"space" => " ",
|
|
12
12
|
"tab" => "\t",
|
|
13
|
+
"s-tab" => :shift_tab,
|
|
14
|
+
"btab" => :shift_tab,
|
|
13
15
|
"cr" => "\r",
|
|
14
16
|
"enter" => "\r",
|
|
15
17
|
"return" => "\r",
|
|
@@ -92,15 +94,18 @@ module Mui
|
|
|
92
94
|
def parse_special(name)
|
|
93
95
|
return :leader if name.casecmp?("leader")
|
|
94
96
|
|
|
97
|
+
# Check SPECIAL_KEYS first (handles <S-Tab>, <btab>, etc.)
|
|
98
|
+
normalized_name = name.downcase
|
|
99
|
+
return SPECIAL_KEYS[normalized_name] if SPECIAL_KEYS.key?(normalized_name)
|
|
100
|
+
|
|
95
101
|
# Handle Ctrl key: <C-x>, <Ctrl-x>, <C-X>
|
|
96
102
|
return parse_ctrl_key(::Regexp.last_match(2)) if name =~ /\A(c|ctrl)-(.+)\z/i
|
|
97
103
|
|
|
98
104
|
# Handle Shift key: <S-x>, <Shift-x>
|
|
99
105
|
return parse_shift_key(::Regexp.last_match(2)) if name =~ /\A(s|shift)-(.+)\z/i
|
|
100
106
|
|
|
101
|
-
#
|
|
102
|
-
|
|
103
|
-
SPECIAL_KEYS[normalized_name] || name
|
|
107
|
+
# Unknown special key - return as-is
|
|
108
|
+
name
|
|
104
109
|
end
|
|
105
110
|
|
|
106
111
|
# Normalize an input key (from terminal) to internal representation
|
|
@@ -136,6 +141,8 @@ module Mui
|
|
|
136
141
|
"\e"
|
|
137
142
|
when KeyCode::TAB
|
|
138
143
|
"\t"
|
|
144
|
+
when 353 # Curses::KEY_BTAB (Shift+Tab)
|
|
145
|
+
:shift_tab
|
|
139
146
|
when KeyCode::BACKSPACE
|
|
140
147
|
"\x7f"
|
|
141
148
|
when 0..31
|
data/lib/mui/screen.rb
CHANGED
|
@@ -25,6 +25,12 @@ module Mui
|
|
|
25
25
|
@adapter.clear
|
|
26
26
|
end
|
|
27
27
|
|
|
28
|
+
# Force a complete redraw of the screen
|
|
29
|
+
# This is needed when multibyte characters may have been corrupted
|
|
30
|
+
def touchwin
|
|
31
|
+
@adapter.touchwin
|
|
32
|
+
end
|
|
33
|
+
|
|
28
34
|
def put(y, x, text)
|
|
29
35
|
return if y.negative?
|
|
30
36
|
return if y >= @height || x >= @width
|
data/lib/mui/search_state.rb
CHANGED
|
@@ -62,49 +62,62 @@ module Mui
|
|
|
62
62
|
end
|
|
63
63
|
|
|
64
64
|
# Get matches for a specific row in a specific buffer
|
|
65
|
+
# O(1) lookup using row_index
|
|
65
66
|
def matches_for_row(row, buffer: nil)
|
|
66
67
|
return [] if buffer.nil?
|
|
67
68
|
|
|
68
|
-
|
|
69
|
-
|
|
69
|
+
cache = get_or_calculate_cache(buffer)
|
|
70
|
+
cache[:row_index][row] || []
|
|
70
71
|
end
|
|
71
72
|
|
|
72
73
|
private
|
|
73
74
|
|
|
74
75
|
def get_or_calculate_matches(buffer)
|
|
76
|
+
get_or_calculate_cache(buffer)[:matches]
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def get_or_calculate_cache(buffer)
|
|
75
80
|
buffer_id = buffer.object_id
|
|
76
81
|
cached = @buffer_matches[buffer_id]
|
|
77
82
|
|
|
78
|
-
# Return cached
|
|
79
|
-
return cached
|
|
83
|
+
# Return cached data if valid (same pattern version and buffer hasn't changed)
|
|
84
|
+
return cached if cached && cached[:version] == @pattern_version && cached[:change_count] == buffer.change_count
|
|
80
85
|
|
|
81
86
|
# Calculate and cache matches for this buffer
|
|
82
|
-
matches = calculate_matches(buffer)
|
|
87
|
+
matches, row_index = calculate_matches(buffer)
|
|
83
88
|
@buffer_matches[buffer_id] = {
|
|
84
89
|
version: @pattern_version,
|
|
85
90
|
change_count: buffer.change_count,
|
|
86
|
-
matches
|
|
91
|
+
matches:,
|
|
92
|
+
row_index:
|
|
87
93
|
}
|
|
88
|
-
|
|
94
|
+
@buffer_matches[buffer_id]
|
|
89
95
|
end
|
|
90
96
|
|
|
91
97
|
def calculate_matches(buffer)
|
|
92
|
-
|
|
98
|
+
empty_result = [[], {}]
|
|
99
|
+
return empty_result if @pattern.nil? || @pattern.empty?
|
|
93
100
|
|
|
94
101
|
matches = []
|
|
102
|
+
row_index = {}
|
|
95
103
|
begin
|
|
96
104
|
regex = Regexp.new(@pattern)
|
|
97
105
|
buffer.line_count.times do |row|
|
|
98
106
|
line = buffer.line(row)
|
|
99
|
-
scan_line_matches(
|
|
107
|
+
row_matches = scan_line_matches(line, row, regex)
|
|
108
|
+
unless row_matches.empty?
|
|
109
|
+
matches.concat(row_matches)
|
|
110
|
+
row_index[row] = row_matches
|
|
111
|
+
end
|
|
100
112
|
end
|
|
101
113
|
rescue RegexpError
|
|
102
114
|
# Invalid regex pattern - no matches
|
|
103
115
|
end
|
|
104
|
-
matches
|
|
116
|
+
[matches, row_index]
|
|
105
117
|
end
|
|
106
118
|
|
|
107
|
-
def scan_line_matches(
|
|
119
|
+
def scan_line_matches(line, row, regex)
|
|
120
|
+
matches = []
|
|
108
121
|
offset = 0
|
|
109
122
|
while (match_data = line.match(regex, offset))
|
|
110
123
|
col = match_data.begin(0)
|
|
@@ -116,6 +129,7 @@ module Mui
|
|
|
116
129
|
offset += 1 if match_data[0].empty?
|
|
117
130
|
break if offset >= line.length
|
|
118
131
|
end
|
|
132
|
+
matches
|
|
119
133
|
end
|
|
120
134
|
end
|
|
121
135
|
end
|