mui 0.3.0 → 0.4.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f38d3d434535fc336c9b5394801e3fb49393f691b15878a1994758cfb73a6217
4
- data.tar.gz: 220128d17afb0f711f2642cd7e64acc14364ca5e65de77c7130828e5a809796c
3
+ metadata.gz: d3555a781cf03a72f45aece354917a6221b12915deb638589fb3678d21764e16
4
+ data.tar.gz: 64ffe746691655d9e479e263ec023953f1a6fae20a9984cffea2da323c845fd0
5
5
  SHA512:
6
- metadata.gz: 82468df468d286b1b672b4c47257565f348661cf38633fdabfdc021cf42639d3637f1b6a1b233bd7af65b47fa426b15da2cd52e270cbe7be987dd071e4d785db
7
- data.tar.gz: dbf19ebadd262acc004320ebcf55d25f404ce22ad7a77beb08f4363d5154dc2f9241834f531fa15b4b953d8768bc3c1ec8213441d3150ec803081b1be32590b0
6
+ metadata.gz: 4b3cd55455549c90e5b229d028e12b735f50c4cb1e5bd7e0fb7f84e3d5fc7f2a82097ac10e50cf0b52a304076460adab2e6b01671cfd32629f225888d4a8ff8c
7
+ data.tar.gz: ee99312173108b0efc849791a426ca247317999cf2b0b5ec3e99e3cbf120086b945f85c8fa46db25a59cd24e7b5784b1414465fd8b4a71bea37b4641fc88c64e
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-14 12:18:50 UTC using RuboCop version 1.81.7.
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: 33
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: 284
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: 14
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: 1
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,76 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.4.1] 2025-12-20
4
+
5
+ ### Fixed
6
+ - Fixed Ctrl+w window commands not working in new tabs
7
+ - Previously, Ctrl+w w/h/j/k/l/s/v/c/o commands would use the first tab's WindowManager even after switching tabs
8
+ - `@window_command` was memoized with the initial tab's WindowManager
9
+ - Now creates WindowCommand with current tab's WindowManager on each invocation
10
+
11
+ ## [0.4.0] 2025-12-19
12
+
13
+ ### Added
14
+ - `<S-Tab>` (Shift+Tab) notation support in keymap configuration:
15
+ - Added `s-tab` and `btab` to `SPECIAL_KEYS` mapping to `:shift_tab` symbol
16
+ - `normalize_input_key()` now handles `KEY_BTAB` (353) from Curses
17
+ - Enables tab navigation keymaps like:
18
+ ```ruby
19
+ Mui.keymap :normal, "<Tab>", ->(ctx) { ctx.editor.tab_manager.next_tab }
20
+ Mui.keymap :normal, "<S-Tab>", ->(ctx) { ctx.editor.tab_manager.prev_tab }
21
+ ```
22
+ - Function definition name highlighting for 6 languages:
23
+ - Ruby: `def hello` highlights `hello` (lookbehind pattern `(?<=def )`)
24
+ - Go: `func main()` highlights `main` (lookbehind pattern `(?<=func )`)
25
+ - Rust: `fn calculate()` highlights `calculate` (lookbehind pattern `(?<=fn )`)
26
+ - JavaScript: `function add()` highlights `add` (post-process after `function` keyword)
27
+ - TypeScript: `function fetchData()` highlights `fetchData` (post-process after `function` keyword)
28
+ - C: `int main()` highlights `main` (lookahead pattern for identifier before `(`)
29
+ - New token type `:function_definition` mapped to `syntax_function_definition` color scheme element
30
+ - All 8 themes updated with `syntax_function_definition` style (same color as `syntax_method_call`)
31
+ - Improved mui default theme with 256-color palette:
32
+ - Eye-friendly gray-based color scheme using 256-color palette
33
+ - 19 new `mui_*` colors in `EXTENDED_COLOR_MAP` (mui_bg, mui_fg, mui_comment, etc.)
34
+ - Syntax highlighting follows Vim standard highlight groups (Comment, Constant, Identifier, Statement, PreProc, Type, Special)
35
+ - 8-color fallbacks for all mui colors
36
+ - Improved all bundled themes to match their original color schemes:
37
+ - Solarized Dark/Light: Adjusted visual selection, operators, and diff colors
38
+ - Monokai: Fixed comment color to olive gray (#75715e), added `monokai_comment` color
39
+ - Nord: Adjusted status line and constant colors to match Nord palette
40
+ - Gruvbox: Adjusted identifier and keyword colors
41
+ - Dracula: Adjusted visual selection and symbol colors
42
+ - Tokyo Night: Adjusted operator and constant colors
43
+ - Added LSP diagnostics and floating window styles to all themes:
44
+ - `diagnostic_error`, `diagnostic_warning`, `diagnostic_info`, `diagnostic_hint`
45
+ - `floating_window`
46
+ - 256-color support stabilization:
47
+ - Environment capability detection (`Curses.has_colors?`, `Curses.colors`, `Curses.color_pairs`)
48
+ - Automatic 8-color fallback for terminals without 256-color support
49
+ - `FALLBACK_MAP` to map extended colors to nearest basic 8-color equivalents
50
+ - LRU cache for color pairs to prevent pair exhaustion
51
+ - Automatic eviction of oldest pairs when limit is reached
52
+ - Backward compatible: `ColorManager.new` without adapter assumes 256 colors
53
+ - `ColorManager#supports_256_colors` to check color capability
54
+ - `TerminalAdapter::Base#has_colors?`, `#colors`, `#color_pairs` interface methods
55
+
56
+ ### Fixed
57
+ - Fixed text corruption after closing floating window or completion popup:
58
+ - Japanese/CJK multibyte characters were not displaying correctly after popup closed
59
+ - Added `touchwin` method to force complete screen redraw using `Curses.stdscr.redraw`
60
+ - `FloatingWindow` now tracks `last_bounds` and `needs_clear` flag
61
+ - `InsertCompletionState` now tracks `needs_clear` flag
62
+ - Editor calls `touchwin` when popup is closed to restore corrupted characters
63
+
64
+ ### Changed
65
+ - Improved search performance for large files:
66
+ - Added row-based index (`row_index`) to search match cache for O(1) lookup
67
+ - `matches_for_row()` now uses hash lookup instead of linear search
68
+ - Before: O(M × visible_rows) per frame, After: O(visible_rows) per frame
69
+ - Improved navigation performance for large files (G, gg commands):
70
+ - Added smart scroll jump for cursor movements over 100 rows
71
+ - Directly calculates scroll position instead of iterating line by line
72
+ - Before: O(n) where n = cursor distance, After: O(1)
73
+
3
74
  ## [0.3.0] - 2025-12-15
4
75
 
5
76
  ### Added
@@ -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
- - `mui` (default)
41
- - `solarized_dark`
42
- - `solarized_light`
43
- - `monokai`
44
- - `nord`
45
- - `gruvbox_dark`
46
- - `dracula`
47
- - `tokyo_night`
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 |
@@ -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 |
@@ -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
- darkgray: 235, # #262626 (~#2b2b2b)
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
- attr_reader :pairs
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
- def initialize
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
- return @pairs[key] if @pairs[key]
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
- COLOR_MAP[color] || EXTENDED_COLOR_MAP[color] || -1
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
@@ -27,6 +27,7 @@ module Mui
27
27
  syntax_instance_variable
28
28
  syntax_global_variable
29
29
  syntax_method_call
30
+ syntax_function_definition
30
31
  syntax_type
31
32
  diff_add
32
33
  diff_delete
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
@@ -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
- reset
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
@@ -13,7 +13,6 @@ module Mui
13
13
  @search_state = search_state
14
14
  @pending_motion = nil
15
15
  @pending_register = nil
16
- @window_command = nil
17
16
  initialize_operators
18
17
  end
19
18
 
@@ -409,7 +408,7 @@ module Mui
409
408
  return result(message: "Window commands not available")
410
409
  end
411
410
 
412
- @window_command ||= WindowCommand.new(window_manager)
411
+ @window_command = WindowCommand.new(window_manager)
413
412
  @window_command.handle(key)
414
413
  clear_pending
415
414
  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
- # Handle other special keys
102
- normalized_name = name.downcase
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