mui 0.1.0 → 0.2.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 (86) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop_todo.yml +158 -0
  3. data/CHANGELOG.md +349 -0
  4. data/exe/mui +1 -2
  5. data/lib/mui/autocmd.rb +66 -0
  6. data/lib/mui/buffer.rb +275 -0
  7. data/lib/mui/buffer_word_cache.rb +131 -0
  8. data/lib/mui/buffer_word_completer.rb +77 -0
  9. data/lib/mui/color_manager.rb +136 -0
  10. data/lib/mui/color_scheme.rb +63 -0
  11. data/lib/mui/command_completer.rb +21 -0
  12. data/lib/mui/command_context.rb +90 -0
  13. data/lib/mui/command_line.rb +137 -0
  14. data/lib/mui/command_registry.rb +25 -0
  15. data/lib/mui/completion_renderer.rb +84 -0
  16. data/lib/mui/completion_state.rb +58 -0
  17. data/lib/mui/config.rb +56 -0
  18. data/lib/mui/editor.rb +319 -0
  19. data/lib/mui/error.rb +29 -0
  20. data/lib/mui/file_completer.rb +51 -0
  21. data/lib/mui/floating_window.rb +161 -0
  22. data/lib/mui/handler_result.rb +101 -0
  23. data/lib/mui/highlight.rb +22 -0
  24. data/lib/mui/highlighters/base.rb +23 -0
  25. data/lib/mui/highlighters/search_highlighter.rb +26 -0
  26. data/lib/mui/highlighters/selection_highlighter.rb +48 -0
  27. data/lib/mui/highlighters/syntax_highlighter.rb +105 -0
  28. data/lib/mui/input.rb +17 -0
  29. data/lib/mui/insert_completion_renderer.rb +92 -0
  30. data/lib/mui/insert_completion_state.rb +77 -0
  31. data/lib/mui/job.rb +81 -0
  32. data/lib/mui/job_manager.rb +113 -0
  33. data/lib/mui/key_code.rb +30 -0
  34. data/lib/mui/key_handler/base.rb +100 -0
  35. data/lib/mui/key_handler/command_mode.rb +443 -0
  36. data/lib/mui/key_handler/insert_mode.rb +354 -0
  37. data/lib/mui/key_handler/motions/motion_handler.rb +56 -0
  38. data/lib/mui/key_handler/normal_mode.rb +579 -0
  39. data/lib/mui/key_handler/operators/base_operator.rb +134 -0
  40. data/lib/mui/key_handler/operators/change_operator.rb +179 -0
  41. data/lib/mui/key_handler/operators/delete_operator.rb +176 -0
  42. data/lib/mui/key_handler/operators/paste_operator.rb +113 -0
  43. data/lib/mui/key_handler/operators/yank_operator.rb +127 -0
  44. data/lib/mui/key_handler/search_mode.rb +188 -0
  45. data/lib/mui/key_handler/visual_line_mode.rb +20 -0
  46. data/lib/mui/key_handler/visual_mode.rb +397 -0
  47. data/lib/mui/key_handler/window_command.rb +112 -0
  48. data/lib/mui/key_handler.rb +16 -0
  49. data/lib/mui/layout/calculator.rb +15 -0
  50. data/lib/mui/layout/leaf_node.rb +33 -0
  51. data/lib/mui/layout/node.rb +29 -0
  52. data/lib/mui/layout/split_node.rb +132 -0
  53. data/lib/mui/line_renderer.rb +122 -0
  54. data/lib/mui/mode.rb +13 -0
  55. data/lib/mui/mode_manager.rb +185 -0
  56. data/lib/mui/motion.rb +139 -0
  57. data/lib/mui/plugin.rb +35 -0
  58. data/lib/mui/plugin_manager.rb +106 -0
  59. data/lib/mui/register.rb +110 -0
  60. data/lib/mui/screen.rb +85 -0
  61. data/lib/mui/search_completer.rb +50 -0
  62. data/lib/mui/search_input.rb +40 -0
  63. data/lib/mui/search_state.rb +88 -0
  64. data/lib/mui/selection.rb +55 -0
  65. data/lib/mui/status_line_renderer.rb +40 -0
  66. data/lib/mui/syntax/language_detector.rb +74 -0
  67. data/lib/mui/syntax/lexer_base.rb +106 -0
  68. data/lib/mui/syntax/lexers/c_lexer.rb +127 -0
  69. data/lib/mui/syntax/lexers/ruby_lexer.rb +114 -0
  70. data/lib/mui/syntax/token.rb +42 -0
  71. data/lib/mui/syntax/token_cache.rb +91 -0
  72. data/lib/mui/tab_bar_renderer.rb +87 -0
  73. data/lib/mui/tab_manager.rb +96 -0
  74. data/lib/mui/tab_page.rb +35 -0
  75. data/lib/mui/terminal_adapter/base.rb +92 -0
  76. data/lib/mui/terminal_adapter/curses.rb +162 -0
  77. data/lib/mui/terminal_adapter.rb +4 -0
  78. data/lib/mui/themes/default.rb +315 -0
  79. data/lib/mui/undo_manager.rb +83 -0
  80. data/lib/mui/undoable_action.rb +175 -0
  81. data/lib/mui/unicode_width.rb +100 -0
  82. data/lib/mui/version.rb +1 -1
  83. data/lib/mui/window.rb +158 -0
  84. data/lib/mui/window_manager.rb +249 -0
  85. data/lib/mui.rb +156 -2
  86. metadata +98 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f64c4815352bf9fcafa99092cea5126496eb9340f0e679b0408e70d59c248627
4
- data.tar.gz: 160afb5b1c9891413852c76357470864d3d1a9d3621497f87695313d480ca934
3
+ metadata.gz: 9515ecfb14d1a9ea5422a842fd22911c303f2465f35827b879117e3cce4d1551
4
+ data.tar.gz: e6daedc45ebb07f1e8b094f590ae9749bfae49e2ced12017cba16f1c1dc255c0
5
5
  SHA512:
6
- metadata.gz: cfd12998c552ac377bfb8be5c1b208cc73fc6d5897639e39be6cb29ce67d964dd6dbd71604727578ecd755a930c1a66feff4c800142c513a900b76ad7d8eb20c
7
- data.tar.gz: e09e98c9b835227152dc0ecfbf2d0207b5f5545bc300fbdd898fd0fdde79d5bb6906c113729e1a3dc7a1bbf12b467cd7a549cc5e19d04d948d3b94eb6f36885c
6
+ metadata.gz: 4f493037291b8801dd2fc8a6101fb714110c12653ebb432900eebaa8f135d91597c8c862d5cf2b380e332d689decbada6d06f406e7f8c8eae859819d63887845
7
+ data.tar.gz: de2b83ea5a999760a171bb037bc264035ef1b43878b806a2b0577099b5601e6e94bdbf6d711604fd9d5a7df01d381665ed734526e1d0f643f896b40e89954604
data/.rubocop_todo.yml ADDED
@@ -0,0 +1,158 @@
1
+ # This configuration was generated by
2
+ # `rubocop --auto-gen-config`
3
+ # on 2025-12-11 15:57:29 UTC using RuboCop version 1.81.7.
4
+ # The point is for the user to remove these configuration records
5
+ # one by one as the offenses are removed from the code base.
6
+ # Note that changes in the inspected code, or installation of new
7
+ # versions of RuboCop, may require this file to be generated again.
8
+
9
+ # Offense count: 1
10
+ # Configuration parameters: Severity.
11
+ Gemspec/RequiredRubyVersion:
12
+ Exclude:
13
+ - 'test/fixtures/mui-test-plugin/mui-test-plugin.gemspec'
14
+
15
+ # Offense count: 3
16
+ # Configuration parameters: IgnoreLiteralBranches, IgnoreConstantBranches, IgnoreDuplicateElseBranch.
17
+ Lint/DuplicateBranch:
18
+ Exclude:
19
+ - 'lib/mui/key_handler/command_mode.rb'
20
+ - 'lib/mui/plugin_manager.rb'
21
+ - 'lib/mui/register.rb'
22
+
23
+ # Offense count: 3
24
+ # Configuration parameters: AllowComments, AllowEmptyLambdas.
25
+ Lint/EmptyBlock:
26
+ Exclude:
27
+ - 'test/mui/test_autocmd.rb'
28
+ - 'test/mui/test_command_registry.rb'
29
+
30
+ # Offense count: 1
31
+ # Configuration parameters: AllowedParentClasses.
32
+ Lint/MissingSuper:
33
+ Exclude:
34
+ - 'test/test_helper.rb'
35
+
36
+ # Offense count: 2
37
+ # This cop supports safe autocorrection (--autocorrect).
38
+ # Configuration parameters: AllowUnusedKeywordArguments, IgnoreEmptyMethods, IgnoreNotImplementedMethods, NotImplementedExceptions.
39
+ # NotImplementedExceptions: NotImplementedError
40
+ Lint/UnusedMethodArgument:
41
+ Exclude:
42
+ - 'lib/mui/terminal_adapter/base.rb'
43
+
44
+ # Offense count: 1
45
+ Lint/UselessConstantScoping:
46
+ Exclude:
47
+ - 'lib/mui/buffer_word_cache.rb'
48
+
49
+ # Offense count: 32
50
+ # Configuration parameters: AllowedMethods, AllowedPatterns.
51
+ Metrics/CyclomaticComplexity:
52
+ Max: 49
53
+
54
+ # Offense count: 1
55
+ # Configuration parameters: CountComments, CountAsOne.
56
+ Metrics/ModuleLength:
57
+ Max: 284
58
+
59
+ # Offense count: 10
60
+ # Configuration parameters: CountKeywordArgs, MaxOptionalParameters.
61
+ Metrics/ParameterLists:
62
+ Max: 9
63
+
64
+ # Offense count: 12
65
+ # Configuration parameters: AllowedMethods, AllowedPatterns.
66
+ Metrics/PerceivedComplexity:
67
+ Max: 19
68
+
69
+ # Offense count: 3
70
+ Naming/AccessorMethodName:
71
+ Exclude:
72
+ - 'lib/mui/command_context.rb'
73
+ - 'lib/mui/search_input.rb'
74
+ - 'test/test_helper.rb'
75
+
76
+ # Offense count: 37
77
+ # Configuration parameters: EnforcedStyle, AllowedPatterns, ForbiddenIdentifiers, ForbiddenPatterns.
78
+ # SupportedStyles: snake_case, camelCase
79
+ # ForbiddenIdentifiers: __id__, __send__
80
+ Naming/MethodName:
81
+ Exclude:
82
+ - 'test/e2e/test_motion.rb'
83
+ - 'test/e2e/test_vim_operations.rb'
84
+ - 'test/e2e/test_visual_mode.rb'
85
+ - 'test/integration/test_editor_integration.rb'
86
+ - 'test/mui/editor/test_normal_mode.rb'
87
+ - 'test/mui/key_handler/test_normal_mode.rb'
88
+ - 'test/mui/key_handler/test_normal_mode_search.rb'
89
+ - 'test/mui/key_handler/test_visual_line_mode.rb'
90
+ - 'test/mui/key_handler/test_visual_mode.rb'
91
+ - 'test/mui/key_handler/test_window_command.rb'
92
+
93
+ # Offense count: 41
94
+ # Configuration parameters: MinNameLength, AllowNamesEndingInNumbers, AllowedNames, ForbiddenNames.
95
+ # AllowedNames: as, at, by, cc, db, id, if, in, io, ip, of, on, os, pp, to
96
+ Naming/MethodParameterName:
97
+ Exclude:
98
+ - 'lib/mui/buffer.rb'
99
+ - 'lib/mui/color_manager.rb'
100
+ - 'lib/mui/color_scheme.rb'
101
+ - 'lib/mui/layout/calculator.rb'
102
+ - 'lib/mui/line_renderer.rb'
103
+ - 'lib/mui/plugin.rb'
104
+ - 'lib/mui/screen.rb'
105
+ - 'lib/mui/terminal_adapter/base.rb'
106
+ - 'lib/mui/terminal_adapter/curses.rb'
107
+ - 'lib/mui/window.rb'
108
+ - 'test/e2e/test_helper.rb'
109
+ - 'test/test_helper.rb'
110
+
111
+ # Offense count: 6
112
+ # Configuration parameters: Mode, AllowedMethods, AllowedPatterns, AllowBangMethods, WaywardPredicates.
113
+ # AllowedMethods: call
114
+ # WaywardPredicates: nonzero?
115
+ Naming/PredicateMethod:
116
+ Exclude:
117
+ - 'lib/mui/key_handler/insert_mode.rb'
118
+ - 'lib/mui/undo_manager.rb'
119
+ - 'lib/mui/window_manager.rb'
120
+
121
+ # Offense count: 1
122
+ # Configuration parameters: NamePrefix, ForbiddenPrefixes, AllowedMethods, MethodDefinitionMacros, UseSorbetSigs.
123
+ # NamePrefix: is_, has_, have_, does_
124
+ # ForbiddenPrefixes: is_, has_, have_, does_
125
+ # AllowedMethods: is_a?
126
+ # MethodDefinitionMacros: define_method, define_singleton_method
127
+ Naming/PredicatePrefix:
128
+ Exclude:
129
+ - 'spec/**/*'
130
+ - 'lib/mui/search_state.rb'
131
+
132
+ # Offense count: 2
133
+ # Configuration parameters: EnforcedStyle, CheckMethodNames, CheckSymbols, AllowedIdentifiers, AllowedPatterns.
134
+ # SupportedStyles: snake_case, normalcase, non_integer
135
+ # AllowedIdentifiers: TLS1_1, TLS1_2, capture3, iso8601, rfc1123_date, rfc822, rfc2822, rfc3339, x86_64
136
+ Naming/VariableNumber:
137
+ Exclude:
138
+ - 'test/e2e/test_motion.rb'
139
+ - 'test/mui/test_register.rb'
140
+
141
+ # Offense count: 2
142
+ # This cop supports safe autocorrection (--autocorrect).
143
+ Style/ComparableClamp:
144
+ Exclude:
145
+ - 'lib/mui/screen.rb'
146
+
147
+ # Offense count: 25
148
+ # Configuration parameters: AllowedConstants.
149
+ Style/Documentation:
150
+ Enabled: false
151
+
152
+ # Offense count: 1
153
+ # This cop supports safe autocorrection (--autocorrect).
154
+ # Configuration parameters: ExactNameMatch, AllowPredicates, AllowDSLWriters, IgnoreClassMethods, AllowedMethods.
155
+ # AllowedMethods: to_ary, to_a, to_c, to_enum, to_h, to_hash, to_i, to_int, to_io, to_open, to_path, to_proc, to_r, to_regexp, to_str, to_s, to_sym
156
+ Style/TrivialAccessors:
157
+ Exclude:
158
+ - 'test/mui/test_autocmd.rb'
data/CHANGELOG.md CHANGED
@@ -1,5 +1,354 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.2.0] - 2025-12-12
4
+
5
+ ### Added
6
+ - Insert mode completion support for LSP:
7
+ - `InsertCompletionState` class for managing LSP completion items
8
+ - `InsertCompletionRenderer` for displaying completion popup below cursor
9
+ - `Editor#start_insert_completion(items, prefix:)` API for plugins
10
+ - `Editor#insert_completion_active?` to check completion state
11
+ - Key bindings in Insert mode:
12
+ - `↑` / `Ctrl+P`: Select previous completion
13
+ - `↓` / `Ctrl+N`: Select next completion
14
+ - `Tab`: Confirm and insert selected completion
15
+ - `Esc`: Cancel completion popup
16
+ - Popup automatically repositions above cursor if space is limited
17
+ - Auto-trigger completion after `.`, `@`, and `::` characters
18
+ - Uses LSP `textEdit` for precise text replacement
19
+ - Buffer word completion (`Ctrl+N` / `Ctrl+P` in Insert mode):
20
+ - Collects words from current buffer as completion candidates
21
+ - `BufferWordCache` class for high-performance word caching
22
+ - Cache built once when entering Insert mode, updated incrementally on changes
23
+ - Auto-triggers while typing (1+ character prefix)
24
+ - Dynamic filtering: completion list updates as you type more characters
25
+ - Excludes word at cursor position from candidates
26
+ - Triggered when no LSP completion is active
27
+ - `InsertCompletion` autocmd event for plugins to hook into completion triggers
28
+ - `KeyCode::CTRL_N`, `KeyCode::CTRL_P`, and `KeyCode::CTRL_SPACE` constants
29
+ - `KeyHandler::Base#editor` helper method for accessing editor instance
30
+ - Insert mode plugin keymap support (allows plugins to define Insert mode key bindings)
31
+ - LSP configuration stub (`Mui.lsp` / `Mui::LspConfigStub`):
32
+ - Allows `.muirc` to call `Mui.lsp { ... }` before mui-lsp gem is loaded
33
+ - Stub stores configuration (preset servers via `use`, custom servers via `server`)
34
+ - When mui-lsp gem loads, configurations are automatically migrated to real `ConfigDsl`
35
+ - `Mui.lsp_server_configs` returns stored server configurations
36
+ - Floating window (popup) support:
37
+ - `FloatingWindow` class for displaying temporary content like hover info
38
+ - `editor.show_floating(content, max_width:, max_height:)` to show popup at cursor
39
+ - `editor.hide_floating` to close the popup
40
+ - Automatic positioning with screen bounds adjustment
41
+ - Unicode box-drawing border characters
42
+ - Scroll support for long content
43
+ - Auto-close on any key press (Escape only closes without processing)
44
+ - Configurable via `:floating_window` color scheme element
45
+ - Dynamic custom highlighter management for buffers:
46
+ - `Buffer#add_custom_highlighter(key, highlighter)` to add named highlighter
47
+ - `Buffer#remove_custom_highlighter(key)` to remove highlighter by key
48
+ - `Buffer#custom_highlighter?(key)` to check if highlighter exists
49
+ - `Window#refresh_highlighters` to rebuild line renderer after highlighter changes
50
+ - Enables plugins to dynamically add/remove highlighting (e.g., LSP diagnostics)
51
+ - Interactive command execution support for plugins:
52
+ - `TerminalAdapter#suspend` / `#resume` methods for temporarily exiting curses mode
53
+ - `Editor#suspend_ui` block helper for safe UI suspension
54
+ - `CommandContext#run_interactive_command(cmd)` to run external interactive commands (e.g., fzf)
55
+ - `CommandContext#command_exists?(cmd)` to check if external command is available
56
+ - Enables plugins to integrate with interactive CLI tools like fzf, less, etc.
57
+ - Custom highlighter support for buffers:
58
+ - `Buffer#custom_highlighters(color_scheme)` method for buffer-specific highlighting
59
+ - Window automatically applies custom highlighters from buffer
60
+ - Enables plugins to provide syntax highlighting for special buffers (e.g., diff output)
61
+ - Diff highlighting color definitions:
62
+ - Added `diff_add`, `diff_delete`, `diff_hunk`, `diff_header` to ColorScheme elements
63
+ - All themes now include diff highlighting colors
64
+ - Incremental search (incsearch):
65
+ - Search results highlight in real-time as you type
66
+ - Cursor moves to first match while typing
67
+ - Escape cancels search and restores original cursor position
68
+ - Backspace updates search results incrementally
69
+ - Works with both forward (`/`) and backward (`?`) search
70
+ - Word completion popup shows matching words from buffer as you type
71
+ - Tab/Shift+Tab to cycle through completion candidates
72
+ - Completions are extracted from buffer content (identifiers and words)
73
+ - Word search commands (`*` and `#`):
74
+ - Normal mode: `*` searches forward for word under cursor
75
+ - Normal mode: `#` searches backward for word under cursor
76
+ - Uses word boundaries (`\b`) for whole-word matching (Vim behavior)
77
+ - Visual mode: `*`/`#` searches for selected text
78
+ - Works in both Visual mode (`v`) and Visual Line mode (`V`)
79
+ - Special regex characters are escaped for literal matching
80
+ - Search state is updated so `n`/`N` continues to find matches
81
+ - Visual mode indent commands (Vim-compatible):
82
+ - `>` to add indent to selected lines (shiftwidth spaces or tab)
83
+ - `<` to remove indent from selected lines
84
+ - Works in both Visual mode (`v`) and Visual Line mode (`V`)
85
+ - Empty lines are skipped (Vim behavior)
86
+ - Multiple lines indented as single undo group
87
+ - Cursor moves to first selected line after operation
88
+ - Configuration options: `shiftwidth` (default: 2), `expandtab` (default: true), `tabstop` (default: 8)
89
+ - `reselect_after_indent` option (default: false): Keep selection after indent for continuous adjustment
90
+ - Reselect last visual selection (`gv` command):
91
+ - `gv` in Normal mode restores the previous visual selection
92
+ - Works with both Visual mode and Visual Line mode
93
+ - Cursor moves to end of selection
94
+ - Useful for repeating operations on the same selection (e.g., multiple indents)
95
+ - Asynchronous job execution system (JobManager):
96
+ - `JobManager` class for managing background tasks with Thread + Queue
97
+ - `Job` class with status tracking (pending, running, completed, failed, cancelled)
98
+ - Thread-safe implementation with Mutex protection
99
+ - `context.run_async { ... }` to run Ruby blocks asynchronously
100
+ - `context.run_shell_command("cmd")` to run external shell commands via Open3
101
+ - `context.jobs_running?` to check if any jobs are active
102
+ - `context.cancel_job(id)` to cancel a running job
103
+ - Callback support via `on_complete:` parameter
104
+ - Autocmd events: JobStarted, JobCompleted, JobFailed, JobCancelled
105
+ - Non-blocking event loop with polling for job results
106
+ - Scratch buffer support:
107
+ - `editor.open_scratch_buffer(name, content)` to display read-only results
108
+ - `context.open_scratch_buffer(name, content)` for plugins
109
+ - Opens in horizontal split window
110
+ - Ideal for displaying test results, linter output, etc.
111
+ - Readonly buffer protection:
112
+ - `buffer.readonly` flag to mark buffers as read-only
113
+ - Edit commands (`i`, `a`, `o`, `O`, `x`, `d`, `c`, `p`, `P`) blocked with error message
114
+ - `:w` and `:wq` commands blocked on readonly buffers
115
+ - Navigation and yank operations still allowed
116
+ - Command-line completion (real-time popup):
117
+ - Auto-completion popup appears as you type in command mode
118
+ - Command name completion for all Ex commands (`:w`, `:q`, `:tabnew`, etc.)
119
+ - File path completion for file commands (`:e`, `:w`, `:sp`, `:vs`, `:tabnew`)
120
+ - Tab to cycle forward through candidates and apply selection
121
+ - Shift+Tab to cycle backward through candidates
122
+ - Popup displays up to 10 candidates with scroll support
123
+ - Completion styles configurable in all 8 themes (`completion_popup`, `completion_popup_selected`)
124
+ - Syntax highlighting for Ruby and C:
125
+ - Token-based lexer architecture with extensible base class
126
+ - Ruby lexer with support for: keywords, strings, comments, numbers, symbols, constants, instance variables (`@foo`, `@@bar`), global variables (`$stdout`), method calls (`.to_i`, `.each`)
127
+ - C lexer with support for: keywords, strings, char literals, comments, numbers, preprocessor directives
128
+ - Multiline comment support: Ruby `=begin`/`=end`, C `/* */`
129
+ - Line-based token caching for performance
130
+ - Language auto-detection from file extension (`.rb`, `.rake`, `.gemspec`, `.c`, `.h`, `.y`)
131
+ - Syntax colors defined for all 8 built-in themes
132
+ - Configurable via `set :syntax, true/false` in `.muirc`
133
+ - Tab page functionality (Vim-compatible):
134
+ - Tab > Window hierarchy: each tab contains independent window layout
135
+ - `:tabnew` / `:tabe` / `:tabedit` to create new tab
136
+ - `:tabclose` / `:tabc` to close current tab
137
+ - `:tabnext` / `:tabn` to go to next tab
138
+ - `:tabprev` / `:tabp` to go to previous tab
139
+ - `:tabfirst` / `:tabf` to go to first tab
140
+ - `:tablast` / `:tabl` to go to last tab
141
+ - `:Ntabn` / `:tabnext N` to go to specific tab (1-indexed)
142
+ - `:tabmove N` / `:tabm N` to move current tab to position N
143
+ - `gt` to go to next tab (wraps around)
144
+ - `gT` to go to previous tab (wraps around)
145
+ - Tab bar at top of screen with separator line
146
+ - Tab bar colors configurable via color scheme (`tab_bar`, `tab_bar_active`)
147
+ - `:q` closes window → tab → editor (Vim-like behavior)
148
+ - Window split functionality:
149
+ - `:sp` / `:split` to split window horizontally (top/bottom)
150
+ - `:vs` / `:vsplit` to split window vertically (left/right)
151
+ - `:sp <filename>` / `:vs <filename>` to split and open file
152
+ - `Ctrl-w h/j/k/l` to navigate between split windows
153
+ - `Ctrl-w w` to cycle to next window
154
+ - `Ctrl-w c` / `:close` to close current window
155
+ - `Ctrl-w o` / `:only` to close all windows except current
156
+ - `:q` / `:wq` / `:q!` in split window closes only that window (Vim-compatible)
157
+ - `:e <filename>` opens file in current window only (doesn't affect other split windows)
158
+ - Separator lines between windows with configurable colors
159
+ - Command line area with configurable background color (no terminal transparency)
160
+ - Each window has independent cursor position, scroll state, and buffer
161
+ - Japanese and multibyte character input support:
162
+ - Full UTF-8 input handling in Insert mode via IME
163
+ - Proper display width calculation for CJK characters (East Asian Width)
164
+ - Cursor position correctly accounts for double-width characters
165
+ - UTF-8 byte sequence assembly in terminal adapter
166
+ - Plugin system:
167
+ - `Mui.use "gem_name"` to declare plugin gems
168
+ - Lazy gem installation via `bundler/inline` on editor startup
169
+ - Class-based plugins: `class MyPlugin < Mui::Plugin`
170
+ - DSL-based plugins: `Mui.define_plugin(:name) { ... }`
171
+ - Plugin API: `command`, `keymap`, `autocmd`
172
+ - Plugin dependencies with topological sort for load order
173
+ - CommandContext for plugin access to editor internals
174
+ - Autocmd event system:
175
+ - 14 events: BufEnter, BufLeave, BufWrite, BufWritePre, BufWritePost, ModeChanged, CursorMoved, TextChanged, InsertEnter, InsertLeave, JobStarted, JobCompleted, JobFailed, JobCancelled
176
+ - Pattern matching for file paths (e.g., `"*.rb"`)
177
+ - `Mui.autocmd :BufEnter, pattern: "*.rb" do |ctx| ... end`
178
+ - Custom commands:
179
+ - `Mui.command :name do |ctx| ... end`
180
+ - Execute via `:name` in command mode
181
+ - Custom keymaps:
182
+ - `Mui.keymap :normal, "key" do |ctx| ... end`
183
+ - Per-mode key bindings
184
+ - Configuration file support:
185
+ - `~/.muirc` for global settings
186
+ - `.lmuirc` for local (per-project) settings
187
+ - Local settings override global settings
188
+ - Ruby DSL format: `Mui.set :colorscheme, "theme_name"`
189
+ - Color scheme system:
190
+ - 256-color terminal support
191
+ - Built-in themes: `mui` (default), `solarized_dark`, `solarized_light`, `monokai`, `nord`, `gruvbox_dark`, `dracula`, `tokyo_night`
192
+ - Customizable UI elements: normal text, status line, search highlight, visual selection, line numbers, messages
193
+ - Search functionality:
194
+ - `/` to search forward (supports regular expressions)
195
+ - `?` to search backward (supports regular expressions)
196
+ - `n` to jump to next match (respects search direction)
197
+ - `N` to jump to previous match (reverses search direction)
198
+ - Wrap-around search (continues from beginning/end of file)
199
+ - Search match highlighting across visible lines
200
+ - Undo/Redo functionality:
201
+ - `u` to undo last change
202
+ - `Ctrl-r` to redo undone change
203
+ - Vim-compatible undo granularity: Insert mode session as single undo unit
204
+ - Support for all editing operations (insert, delete, change, visual mode operations)
205
+ - Maximum 1000 undo history entries
206
+ - Vim-compatible named registers:
207
+ - Named registers (`"a` - `"z`): 26 user-specified registers
208
+ - Unnamed register (`""`): Default register
209
+ - Yank register (`"0`): Stores last yank, not affected by delete
210
+ - Delete history registers (`"1` - `"9`): Stores delete history, shifts on each delete
211
+ - Black hole register (`"_`): Discards content without saving
212
+ - Usage: `"ayy` (yank to register a), `"ap` (paste from register a)
213
+ - Delete/change operators now save to registers (Vim-compatible behavior):
214
+ - `dd`, `dw`, etc. save deleted text to unnamed register and delete history
215
+ - `cc`, `cw`, etc. save changed text to unnamed register and delete history
216
+ - Visual mode `d`/`c` also save to registers
217
+ - Yank operator (`y`):
218
+ - `yy` to yank current line
219
+ - `y` + motion: `yw`, `ye`, `yb`, `y0`, `y$`, `ygg`, `yG`
220
+ - `yf{char}`, `yt{char}`, `yF{char}`, `yT{char}` to yank to/till character
221
+ - `y` in Visual mode to yank selection (character-wise and line-wise)
222
+ - `yw` behaves like `ye` (yanks to end of word) matching Vim behavior
223
+ - Paste commands:
224
+ - `p` to paste after cursor (character-wise) or below current line (line-wise)
225
+ - `P` to paste before cursor (character-wise) or above current line (line-wise)
226
+ - Multi-line character-wise paste support
227
+ - Register system for yank/paste operations
228
+ - Default register for storing yanked text
229
+ - Named registers support
230
+ - Linewise flag to distinguish line-wise vs character-wise yanks
231
+ - TerminalAdapter abstraction layer for custom terminal implementations
232
+ - `Mui::TerminalAdapter::Base` abstract base class defining the terminal interface
233
+ - `Mui::TerminalAdapter::Curses` default implementation using Curses library
234
+ - Users can create custom adapters by subclassing `Base` and passing to `Editor.new(adapter:)`
235
+ - Example: `Mui::Editor.new(file_path, adapter: MyCustomAdapter.new)`
236
+ - Initial release of Mui, a Vim-like text editor written in Ruby
237
+ - Vim-like modal editing with five modes:
238
+ - Normal mode: Navigation and text manipulation
239
+ - Insert mode: Text input
240
+ - Command mode: Ex commands
241
+ - Visual mode (`v`): Character-wise selection
242
+ - Visual Line mode (`V`): Line-wise selection
243
+ - Basic cursor movement with `h`, `j`, `k`, `l` and arrow keys in Normal mode
244
+ - Arrow key cursor movement in Insert mode
245
+ - Motion commands:
246
+ - Word movements: `w` (word forward), `b` (word backward), `e` (word end)
247
+ - Line movements: `0` (line start), `^` (first non-blank), `$` (line end)
248
+ - File movements: `gg` (file start), `G` (file end)
249
+ - Character search: `f{char}`, `F{char}`, `t{char}`, `T{char}`
250
+ - Text editing operations:
251
+ - `i` to insert before cursor
252
+ - `a` to append after cursor
253
+ - `o` to open new line below
254
+ - `O` to open new line above
255
+ - `x` to delete character at cursor
256
+ - Backspace to delete and join lines
257
+ - Delete operator (`d`):
258
+ - `dd` to delete current line
259
+ - `d` + motion: `dw`, `de`, `db`, `d0`, `d$`, `dgg`, `dG`
260
+ - `df{char}`, `dt{char}`, `dF{char}`, `dT{char}` to delete to/till character
261
+ - `d` in Visual mode to delete selection (character-wise and line-wise)
262
+ - Change operator (`c`):
263
+ - `cc` to change current line (clear and enter Insert mode)
264
+ - `c` + motion: `cw`, `ce`, `cb`, `c0`, `c$`, `cgg`, `cG`
265
+ - `cf{char}`, `ct{char}`, `cF{char}`, `cT{char}` to change to/till character
266
+ - `c` in Visual mode to change selection (character-wise and line-wise)
267
+ - `cw` behaves like `ce` (changes to end of word, preserving space) matching Vim behavior
268
+ - Ex commands:
269
+ - `:w` to save file
270
+ - `:w <filename>` to save as
271
+ - `:q` to quit (with unsaved changes protection)
272
+ - `:q!` to force quit
273
+ - `:wq` to save and quit
274
+ - `:e` to reload current file
275
+ - `:e <filename>` to open file (Vim-compatible: non-existent files open as new buffer)
276
+ - `:{number}` to jump to specific line (e.g., `:10` jumps to line 10)
277
+ - Line numbers are 1-indexed (`:1` = first line)
278
+ - Out-of-range values are clamped (`:0` → first line, `:999` on 100-line file → last line)
279
+ - Cursor moves to beginning of line (column 0)
280
+ - Curses-based terminal UI with:
281
+ - Buffer management
282
+ - Window with scrolling support
283
+ - Status line display
284
+ - Command line input
285
+ - Visual mode features:
286
+ - Selection highlighting with reverse video
287
+ - Toggle between Visual and Visual Line mode with `v`/`V`
288
+ - All motion commands supported (h, j, k, l, w, b, e, 0, ^, $, gg, G, f, F, t, T)
289
+ - Exit to Normal mode with `Esc`
290
+ - Comprehensive test suite for `Mui::Input` and `Mui::Editor` classes
291
+ - Unit tests for Buffer, CommandLine, Input, Screen, Window, Selection, and Editor modes
292
+ - Integration tests for component interactions
293
+ - E2E tests with ScriptRunner DSL for Vim operation scenarios including Visual mode
294
+ - Test infrastructure with Curses mock and `MuiTestHelper` module
295
+
296
+ ### Changed
297
+ - Reorganized test directory structure to follow standard gem conventions
298
+ - Unit tests moved to `test/mui/`
299
+ - Editor mode tests in `test/mui/editor/`
300
+ - Integration tests in `test/integration/`
301
+ - E2E tests in `test/e2e/`
302
+ - Refactored test files to use nested classes per method for better readability
303
+ - Improved syntax highlighting performance:
304
+ - Use `\G` anchor for position-specific regex matching to avoid substring allocation
305
+ - Pre-compile all regex patterns as class-level constants for one-time compilation at class load
306
+ - Optimize event sorting in line renderer using array tuples instead of hash arrays
307
+ - Add style resolution cache to avoid repeated hash merges
308
+ - Reduce hash lookups in token cache by inlining cache validation
309
+ - Improved buffer change detection performance:
310
+ - Added `Buffer#change_count` for O(1) change detection
311
+ - Replaced `buffer.lines.hash` (O(n)) with counter comparison in `TextChanged` event trigger
312
+ - Improved buffer word completion performance:
313
+ - Use `String#scan` with regex for fast word extraction instead of character-by-character iteration
314
+ - Direct ASCII code comparison for word character detection instead of regex match per character
315
+
316
+ ### Fixed
317
+ - Command-line completion Tab behavior:
318
+ - First Tab now confirms current selection without cycling to next candidate
319
+ - Second Tab and subsequent presses cycle through candidates
320
+ - Previously, first Tab would skip the initially displayed candidate
321
+ - Command-line completion cursor position:
322
+ - Cursor now moves to end of buffer after applying completion
323
+ - Previously, cursor remained at old position after completion
324
+ - Plugin keymap handler now correctly uses active window's buffer instead of initial buffer
325
+ - Fixes issue where buffer-specific keymaps didn't work in split windows
326
+ - Plugin keymap handler now properly returns handler result
327
+ - Allows buffer-specific keymaps to conditionally pass through to built-in handlers
328
+ - Plugin keymap now correctly handles Enter key (Curses::KEY_ENTER)
329
+ - Previously special key codes like Curses::KEY_ENTER (343) were not converted to keymap string
330
+ - Now Enter key variants (CR, LF, KEY_ENTER) are all mapped to `"\r"` for plugin keymaps
331
+ - Auto-indent on newline in Insert mode
332
+ - Pressing Enter preserves indentation from the current line
333
+ - Leading whitespace (spaces and tabs) is automatically inserted on the new line
334
+ - Whitespace-only lines are cleared when pressing Escape (Vim behavior)
335
+ - Register is now shared globally across tabs and split windows
336
+ - Yank in one tab/window can be pasted in another
337
+ - Previously each ModeManager had its own Register instance
338
+ - Visual mode operations now correctly use the active window's buffer
339
+ - Fixed yank/delete/change/indent operations using stale buffer reference after tab or window switch
340
+ - Operations now properly target the current active window's buffer
341
+ - Command mode cursor movement with arrow keys
342
+ - Left/Right arrow keys now move cursor within command line
343
+ - Previously arrow keys were interpreted as characters (e.g., Left inserted 'a')
344
+ - Backspace now deletes character before cursor position
345
+ - Character input now inserts at cursor position
346
+ - Empty command (`:` then Enter) no longer crashes
347
+ - Previously caused error when trying to look up empty string as plugin command
348
+ - Buffer word completion now works correctly after opening files with `:e` or `:tabe`
349
+ - `InsertMode` now uses active window's buffer for `BufferWordCache` instead of initial buffer
350
+ - Previously, completion would use stale buffer reference after switching files
351
+
3
352
  ## [0.1.0] - 2025-11-30
4
353
 
5
354
  - Initial release
data/exe/mui CHANGED
@@ -3,5 +3,4 @@
3
3
 
4
4
  require "mui"
5
5
 
6
- puts "Mui v#{Mui::VERSION} - A Vim-like TUI editor written in Ruby"
7
- puts "Coming soon..."
6
+ Mui::Editor.new(ARGV[0]).run
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Mui
4
+ # Event-driven hook system for plugins
5
+ class Autocmd
6
+ EVENTS = %i[
7
+ BufEnter
8
+ BufLeave
9
+ BufWrite
10
+ BufWritePre
11
+ BufWritePost
12
+ ModeChanged
13
+ CursorMoved
14
+ TextChanged
15
+ InsertEnter
16
+ InsertLeave
17
+ InsertCompletion
18
+ JobStarted
19
+ JobCompleted
20
+ JobFailed
21
+ JobCancelled
22
+ ].freeze
23
+
24
+ def initialize
25
+ @handlers = {}
26
+ EVENTS.each { |e| @handlers[e] = [] }
27
+ end
28
+
29
+ def register(event, pattern: nil, &block)
30
+ event = event.to_sym
31
+ raise ArgumentError, "Unknown event: #{event}" unless EVENTS.include?(event)
32
+
33
+ @handlers[event] << { pattern:, handler: block }
34
+ end
35
+
36
+ def trigger(event, context = nil, **kwargs)
37
+ event = event.to_sym
38
+ return unless @handlers[event]
39
+
40
+ @handlers[event].each do |entry|
41
+ # Skip pattern matching for non-buffer events (like Job events)
42
+ next if context && entry[:pattern] && !match_pattern?(context, entry[:pattern])
43
+
44
+ # Pass context or kwargs depending on what's available
45
+ entry[:handler].call(context || kwargs)
46
+ end
47
+ end
48
+
49
+ private
50
+
51
+ def match_pattern?(context, pattern)
52
+ return true unless pattern
53
+
54
+ file_path = context.buffer.file_path
55
+
56
+ case pattern
57
+ when Regexp
58
+ file_path&.match?(pattern)
59
+ when String
60
+ File.fnmatch(pattern, file_path || "")
61
+ else
62
+ false
63
+ end
64
+ end
65
+ end
66
+ end