neuro-cli 4.1.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.
- package/LICENSE +21 -0
- package/README.md +510 -0
- package/dist/advisor/advisor.d.ts +50 -0
- package/dist/advisor/advisor.js +178 -0
- package/dist/agents/base.d.ts +62 -0
- package/dist/agents/base.js +215 -0
- package/dist/agents/orchestrator.d.ts +46 -0
- package/dist/agents/orchestrator.js +192 -0
- package/dist/agents/team.d.ts +51 -0
- package/dist/agents/team.js +210 -0
- package/dist/api/models.d.ts +23 -0
- package/dist/api/models.js +514 -0
- package/dist/api/ollama.d.ts +153 -0
- package/dist/api/ollama.js +751 -0
- package/dist/api/openrouter.d.ts +55 -0
- package/dist/api/openrouter.js +223 -0
- package/dist/commands/commands.d.ts +43 -0
- package/dist/commands/commands.js +308 -0
- package/dist/config/config.d.ts +8 -0
- package/dist/config/config.js +311 -0
- package/dist/context/compaction.d.ts +54 -0
- package/dist/context/compaction.js +251 -0
- package/dist/context/custom-agents.d.ts +107 -0
- package/dist/context/custom-agents.js +397 -0
- package/dist/context/custom-tools.d.ts +120 -0
- package/dist/context/custom-tools.js +564 -0
- package/dist/context/git-checkpoint.d.ts +52 -0
- package/dist/context/git-checkpoint.js +240 -0
- package/dist/context/neuro-md.d.ts +48 -0
- package/dist/context/neuro-md.js +202 -0
- package/dist/context/neuroignore.d.ts +102 -0
- package/dist/context/neuroignore.js +441 -0
- package/dist/context/repo-map.d.ts +38 -0
- package/dist/context/repo-map.js +220 -0
- package/dist/context/skill-standard.d.ts +262 -0
- package/dist/context/skill-standard.js +1156 -0
- package/dist/context/skill-system.d.ts +75 -0
- package/dist/context/skill-system.js +578 -0
- package/dist/context/tree-sitter.d.ts +99 -0
- package/dist/context/tree-sitter.js +1956 -0
- package/dist/core/acp.d.ts +325 -0
- package/dist/core/acp.js +1498 -0
- package/dist/core/api-server.d.ts +143 -0
- package/dist/core/api-server.js +550 -0
- package/dist/core/approval.d.ts +81 -0
- package/dist/core/approval.js +432 -0
- package/dist/core/auto-compact.d.ts +127 -0
- package/dist/core/auto-compact.js +436 -0
- package/dist/core/auto-mode.d.ts +232 -0
- package/dist/core/auto-mode.js +831 -0
- package/dist/core/background-session.d.ts +166 -0
- package/dist/core/background-session.js +696 -0
- package/dist/core/cicd.d.ts +257 -0
- package/dist/core/cicd.js +1443 -0
- package/dist/core/cloud-sync.d.ts +156 -0
- package/dist/core/cloud-sync.js +582 -0
- package/dist/core/code-review.d.ts +132 -0
- package/dist/core/code-review.js +1191 -0
- package/dist/core/completion.d.ts +49 -0
- package/dist/core/completion.js +384 -0
- package/dist/core/context.d.ts +38 -0
- package/dist/core/context.js +144 -0
- package/dist/core/diff-preview.d.ts +35 -0
- package/dist/core/diff-preview.js +173 -0
- package/dist/core/doom-loop.d.ts +51 -0
- package/dist/core/doom-loop.js +179 -0
- package/dist/core/engine.d.ts +183 -0
- package/dist/core/engine.js +942 -0
- package/dist/core/extended-thinking.d.ts +103 -0
- package/dist/core/extended-thinking.js +269 -0
- package/dist/core/fallback.d.ts +54 -0
- package/dist/core/fallback.js +104 -0
- package/dist/core/git-worktree.d.ts +139 -0
- package/dist/core/git-worktree.js +614 -0
- package/dist/core/headless.d.ts +30 -0
- package/dist/core/headless.js +101 -0
- package/dist/core/i18n.d.ts +83 -0
- package/dist/core/i18n.js +739 -0
- package/dist/core/linting.d.ts +129 -0
- package/dist/core/linting.js +699 -0
- package/dist/core/model-router.d.ts +109 -0
- package/dist/core/model-router.js +581 -0
- package/dist/core/multi-model.d.ts +243 -0
- package/dist/core/multi-model.js +1099 -0
- package/dist/core/multi-session.d.ts +144 -0
- package/dist/core/multi-session.js +442 -0
- package/dist/core/multimodal.d.ts +125 -0
- package/dist/core/multimodal.js +286 -0
- package/dist/core/observability.d.ts +93 -0
- package/dist/core/observability.js +737 -0
- package/dist/core/os-sandbox.d.ts +122 -0
- package/dist/core/os-sandbox.js +1193 -0
- package/dist/core/outcome-grading.d.ts +228 -0
- package/dist/core/outcome-grading.js +1123 -0
- package/dist/core/output-styles.d.ts +57 -0
- package/dist/core/output-styles.js +382 -0
- package/dist/core/parallel-agents.d.ts +183 -0
- package/dist/core/parallel-agents.js +563 -0
- package/dist/core/plugin-bundle.d.ts +236 -0
- package/dist/core/plugin-bundle.js +887 -0
- package/dist/core/plugin-sdk.d.ts +139 -0
- package/dist/core/plugin-sdk.js +273 -0
- package/dist/core/prompt-cache.d.ts +163 -0
- package/dist/core/prompt-cache.js +599 -0
- package/dist/core/sandbox.d.ts +127 -0
- package/dist/core/sandbox.js +369 -0
- package/dist/core/scheduled-tasks.d.ts +151 -0
- package/dist/core/scheduled-tasks.js +502 -0
- package/dist/core/security-scanner.d.ts +160 -0
- package/dist/core/security-scanner.js +1494 -0
- package/dist/core/session.d.ts +83 -0
- package/dist/core/session.js +269 -0
- package/dist/core/shell-completion.d.ts +51 -0
- package/dist/core/shell-completion.js +674 -0
- package/dist/core/smart-monitor.d.ts +146 -0
- package/dist/core/smart-monitor.js +1199 -0
- package/dist/core/spec-driven.d.ts +233 -0
- package/dist/core/spec-driven.js +1485 -0
- package/dist/core/spending-warnings.d.ts +123 -0
- package/dist/core/spending-warnings.js +456 -0
- package/dist/core/sub-agent.d.ts +298 -0
- package/dist/core/sub-agent.js +1023 -0
- package/dist/core/telemetry.d.ts +157 -0
- package/dist/core/telemetry.js +412 -0
- package/dist/core/terminal-ux.d.ts +134 -0
- package/dist/core/terminal-ux.js +649 -0
- package/dist/core/testing.d.ts +146 -0
- package/dist/core/testing.js +1200 -0
- package/dist/core/types.d.ts +439 -0
- package/dist/core/types.js +6 -0
- package/dist/core/undo-redo.d.ts +112 -0
- package/dist/core/undo-redo.js +290 -0
- package/dist/core/updater.d.ts +159 -0
- package/dist/core/updater.js +608 -0
- package/dist/core/vim-mode.d.ts +151 -0
- package/dist/core/vim-mode.js +771 -0
- package/dist/core/voice.d.ts +137 -0
- package/dist/core/voice.js +538 -0
- package/dist/core/web-dashboard.d.ts +109 -0
- package/dist/core/web-dashboard.js +484 -0
- package/dist/hooks/hooks.d.ts +74 -0
- package/dist/hooks/hooks.js +160 -0
- package/dist/hooks/llm-evaluator.d.ts +165 -0
- package/dist/hooks/llm-evaluator.js +560 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +1186 -0
- package/dist/lsp/lsp-manager.d.ts +63 -0
- package/dist/lsp/lsp-manager.js +351 -0
- package/dist/mcp/client.d.ts +133 -0
- package/dist/mcp/client.js +684 -0
- package/dist/mcp/mcp-apps.d.ts +70 -0
- package/dist/mcp/mcp-apps.js +1007 -0
- package/dist/tools/bash.d.ts +5 -0
- package/dist/tools/bash.js +195 -0
- package/dist/tools/browser.d.ts +92 -0
- package/dist/tools/browser.js +1570 -0
- package/dist/tools/extended.d.ts +6 -0
- package/dist/tools/extended.js +191 -0
- package/dist/tools/file.d.ts +10 -0
- package/dist/tools/file.js +382 -0
- package/dist/tools/github.d.ts +389 -0
- package/dist/tools/github.js +759 -0
- package/dist/tools/index.d.ts +9 -0
- package/dist/tools/index.js +40 -0
- package/dist/tools/memory.d.ts +6 -0
- package/dist/tools/memory.js +197 -0
- package/dist/tools/registry.d.ts +29 -0
- package/dist/tools/registry.js +64 -0
- package/dist/tools/web.d.ts +6 -0
- package/dist/tools/web.js +150 -0
- package/dist/ui/renderer.d.ts +97 -0
- package/dist/ui/renderer.js +279 -0
- package/dist/ui/theme.d.ts +27 -0
- package/dist/ui/theme.js +106 -0
- package/package.json +83 -0
|
@@ -0,0 +1,771 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// NeuroCLI - Vim Keybindings Mode
|
|
3
|
+
// Vim-like input mode (normal/insert/visual) with hjkl
|
|
4
|
+
// navigation, dd, yy, p, command mode (:), mode indicator
|
|
5
|
+
// Integration with readline
|
|
6
|
+
// ============================================================
|
|
7
|
+
import chalk from 'chalk';
|
|
8
|
+
import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
|
|
9
|
+
import { join } from 'path';
|
|
10
|
+
import { homedir } from 'os';
|
|
11
|
+
// -----------------------------------------------------------
|
|
12
|
+
// Default config
|
|
13
|
+
// -----------------------------------------------------------
|
|
14
|
+
const VIM_CONFIG_PATH = join(homedir(), '.neuro', 'vim-config.json');
|
|
15
|
+
function defaultConfig() {
|
|
16
|
+
return {
|
|
17
|
+
enabled: false,
|
|
18
|
+
showModeIndicator: true,
|
|
19
|
+
keyMappings: {},
|
|
20
|
+
bellOnError: true,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
// -----------------------------------------------------------
|
|
24
|
+
// VimModeManager
|
|
25
|
+
// -----------------------------------------------------------
|
|
26
|
+
export class VimModeManager {
|
|
27
|
+
config;
|
|
28
|
+
mode = 'insert'; // Start in insert mode (user-friendly)
|
|
29
|
+
registers = new Map();
|
|
30
|
+
commandBuffer = '';
|
|
31
|
+
normalBuffer = '';
|
|
32
|
+
visualStart = 0;
|
|
33
|
+
cursorPosition = 0;
|
|
34
|
+
lineBuffer = '';
|
|
35
|
+
lastAction = '';
|
|
36
|
+
repeatCount = 0;
|
|
37
|
+
commandHistory = [];
|
|
38
|
+
commandHistoryIndex = -1;
|
|
39
|
+
rl = null;
|
|
40
|
+
keyHandlerAttached = false;
|
|
41
|
+
constructor(config) {
|
|
42
|
+
this.config = { ...defaultConfig(), ...config };
|
|
43
|
+
this.loadConfig();
|
|
44
|
+
if (this.config.enabled) {
|
|
45
|
+
this.mode = 'insert'; // Start in insert for better UX
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
// ----------------------------------------------------------
|
|
49
|
+
// Public API
|
|
50
|
+
// ----------------------------------------------------------
|
|
51
|
+
/**
|
|
52
|
+
* Check if vim mode is enabled
|
|
53
|
+
*/
|
|
54
|
+
isEnabled() {
|
|
55
|
+
return this.config.enabled;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Enable vim mode
|
|
59
|
+
*/
|
|
60
|
+
enable() {
|
|
61
|
+
this.config.enabled = true;
|
|
62
|
+
this.mode = 'insert';
|
|
63
|
+
this.saveConfig();
|
|
64
|
+
console.log(chalk.green('Vim mode enabled. Press Esc to enter normal mode.'));
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Disable vim mode
|
|
68
|
+
*/
|
|
69
|
+
disable() {
|
|
70
|
+
this.config.enabled = false;
|
|
71
|
+
this.mode = 'insert';
|
|
72
|
+
this.saveConfig();
|
|
73
|
+
console.log(chalk.gray('Vim mode disabled. Standard readline bindings active.'));
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Toggle vim mode
|
|
77
|
+
*/
|
|
78
|
+
toggle() {
|
|
79
|
+
if (this.config.enabled)
|
|
80
|
+
this.disable();
|
|
81
|
+
else
|
|
82
|
+
this.enable();
|
|
83
|
+
return this.config.enabled;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Get current mode
|
|
87
|
+
*/
|
|
88
|
+
getMode() {
|
|
89
|
+
return this.mode;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Set current mode
|
|
93
|
+
*/
|
|
94
|
+
setMode(mode) {
|
|
95
|
+
this.mode = mode;
|
|
96
|
+
this.commandBuffer = '';
|
|
97
|
+
this.normalBuffer = '';
|
|
98
|
+
this.repeatCount = 0;
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Get mode indicator string for the prompt
|
|
102
|
+
*/
|
|
103
|
+
getModeIndicator() {
|
|
104
|
+
if (!this.config.enabled || !this.config.showModeIndicator)
|
|
105
|
+
return '';
|
|
106
|
+
switch (this.mode) {
|
|
107
|
+
case 'normal':
|
|
108
|
+
return chalk.bgBlue.white.bold(' NORMAL ');
|
|
109
|
+
case 'insert':
|
|
110
|
+
return chalk.bgGreen.white.bold(' INSERT ');
|
|
111
|
+
case 'visual':
|
|
112
|
+
return chalk.bgMagenta.white.bold(' VISUAL ');
|
|
113
|
+
case 'command':
|
|
114
|
+
return chalk.bgYellow.black.bold(' CMD ');
|
|
115
|
+
default:
|
|
116
|
+
return '';
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Get the modified prompt with mode indicator
|
|
121
|
+
*/
|
|
122
|
+
getModifiedPrompt(basePrompt) {
|
|
123
|
+
if (!this.config.enabled)
|
|
124
|
+
return basePrompt;
|
|
125
|
+
const indicator = this.getModeIndicator();
|
|
126
|
+
return indicator ? `${indicator} ${basePrompt}` : basePrompt;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Attach to a readline interface for key handling
|
|
130
|
+
*/
|
|
131
|
+
attachToReadline(rl) {
|
|
132
|
+
if (!this.config.enabled)
|
|
133
|
+
return;
|
|
134
|
+
this.rl = rl;
|
|
135
|
+
if (!this.keyHandlerAttached && typeof process.stdin.setRawMode === 'function') {
|
|
136
|
+
// We handle vim keys at a higher level via processKey
|
|
137
|
+
this.keyHandlerAttached = true;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Detach from readline interface
|
|
142
|
+
*/
|
|
143
|
+
detachFromReadline() {
|
|
144
|
+
this.rl = null;
|
|
145
|
+
this.keyHandlerAttached = false;
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Process a key press and return the action to take
|
|
149
|
+
*/
|
|
150
|
+
processKey(key) {
|
|
151
|
+
if (!this.config.enabled)
|
|
152
|
+
return null;
|
|
153
|
+
// Handle escape first
|
|
154
|
+
if (key === '\x1b' || key === 'Escape') {
|
|
155
|
+
return this.handleEscape();
|
|
156
|
+
}
|
|
157
|
+
switch (this.mode) {
|
|
158
|
+
case 'normal':
|
|
159
|
+
return this.handleNormalKey(key);
|
|
160
|
+
case 'insert':
|
|
161
|
+
return this.handleInsertKey(key);
|
|
162
|
+
case 'visual':
|
|
163
|
+
return this.handleVisualKey(key);
|
|
164
|
+
case 'command':
|
|
165
|
+
return this.handleCommandKey(key);
|
|
166
|
+
default:
|
|
167
|
+
return null;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Get all yanked text from a register
|
|
172
|
+
*/
|
|
173
|
+
getRegister(name) {
|
|
174
|
+
const reg = this.registers.get(name);
|
|
175
|
+
return reg ? reg.content : '';
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Set register content
|
|
179
|
+
*/
|
|
180
|
+
setRegister(name, content, type = 'line') {
|
|
181
|
+
this.registers.set(name, { name, content, type });
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Get current line buffer
|
|
185
|
+
*/
|
|
186
|
+
getLineBuffer() {
|
|
187
|
+
return this.lineBuffer;
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Set current line buffer
|
|
191
|
+
*/
|
|
192
|
+
setLineBuffer(buffer) {
|
|
193
|
+
this.lineBuffer = buffer;
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* Get cursor position
|
|
197
|
+
*/
|
|
198
|
+
getCursorPosition() {
|
|
199
|
+
return this.cursorPosition;
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* Set cursor position
|
|
203
|
+
*/
|
|
204
|
+
setCursorPosition(pos) {
|
|
205
|
+
this.cursorPosition = Math.max(0, Math.min(pos, this.lineBuffer.length));
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* Execute a vim command (e.g. :w, :q, :set)
|
|
209
|
+
*/
|
|
210
|
+
executeCommand(cmd) {
|
|
211
|
+
const trimmed = cmd.trim();
|
|
212
|
+
// Built-in commands
|
|
213
|
+
const commands = [
|
|
214
|
+
{
|
|
215
|
+
name: 'set',
|
|
216
|
+
pattern: /^set\s+/,
|
|
217
|
+
handler: (args) => this.handleSetCommand(args),
|
|
218
|
+
description: 'Set vim options',
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
name: 'w',
|
|
222
|
+
pattern: /^w$/,
|
|
223
|
+
handler: () => console.log(chalk.gray('Session auto-saved.')),
|
|
224
|
+
description: 'Write/save (auto-saved in NeuroCLI)',
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
name: 'q',
|
|
228
|
+
pattern: /^q(!)?$/,
|
|
229
|
+
handler: (args) => {
|
|
230
|
+
if (args.includes('!')) {
|
|
231
|
+
process.exit(0);
|
|
232
|
+
}
|
|
233
|
+
else {
|
|
234
|
+
console.log(chalk.yellow('Use :q! to force quit.'));
|
|
235
|
+
}
|
|
236
|
+
},
|
|
237
|
+
description: 'Quit',
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
name: 'help',
|
|
241
|
+
pattern: /^help$/,
|
|
242
|
+
handler: () => this.printHelp(),
|
|
243
|
+
description: 'Show vim mode help',
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
name: 'registers',
|
|
247
|
+
pattern: /^registers$/,
|
|
248
|
+
handler: () => this.printRegisters(),
|
|
249
|
+
description: 'Show register contents',
|
|
250
|
+
},
|
|
251
|
+
{
|
|
252
|
+
name: 'mode',
|
|
253
|
+
pattern: /^mode$/,
|
|
254
|
+
handler: () => console.log(`Current mode: ${chalk.cyan(this.mode)}`),
|
|
255
|
+
description: 'Show current mode',
|
|
256
|
+
},
|
|
257
|
+
{
|
|
258
|
+
name: 'history',
|
|
259
|
+
pattern: /^history$/,
|
|
260
|
+
handler: () => this.printCommandHistory(),
|
|
261
|
+
description: 'Show command history',
|
|
262
|
+
},
|
|
263
|
+
];
|
|
264
|
+
for (const command of commands) {
|
|
265
|
+
if (command.pattern.test(trimmed)) {
|
|
266
|
+
const args = trimmed.replace(command.pattern, '');
|
|
267
|
+
command.handler(args, this);
|
|
268
|
+
this.commandHistory.push(trimmed);
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
console.log(chalk.yellow(`Unknown command: :${trimmed}. Type :help for vim mode help.`));
|
|
273
|
+
this.commandHistory.push(trimmed);
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Print vim mode help
|
|
277
|
+
*/
|
|
278
|
+
printHelp() {
|
|
279
|
+
console.log('');
|
|
280
|
+
console.log(chalk.bold('--- NeuroCLI Vim Mode Help ---'));
|
|
281
|
+
console.log('');
|
|
282
|
+
console.log(chalk.cyan(' Mode Switching:'));
|
|
283
|
+
console.log(' Esc - Switch to Normal mode');
|
|
284
|
+
console.log(' i - Enter Insert mode');
|
|
285
|
+
console.log(' a - Enter Insert mode (after cursor)');
|
|
286
|
+
console.log(' v - Enter Visual mode');
|
|
287
|
+
console.log(' : - Enter Command mode');
|
|
288
|
+
console.log('');
|
|
289
|
+
console.log(chalk.cyan(' Normal Mode Navigation:'));
|
|
290
|
+
console.log(' h - Move cursor left');
|
|
291
|
+
console.log(' j - Next history entry');
|
|
292
|
+
console.log(' k - Previous history entry');
|
|
293
|
+
console.log(' l - Move cursor right');
|
|
294
|
+
console.log(' 0 - Move to start of line');
|
|
295
|
+
console.log(' $ - Move to end of line');
|
|
296
|
+
console.log(' w - Move to next word');
|
|
297
|
+
console.log(' b - Move to previous word');
|
|
298
|
+
console.log('');
|
|
299
|
+
console.log(chalk.cyan(' Normal Mode Editing:'));
|
|
300
|
+
console.log(' dd - Delete entire line');
|
|
301
|
+
console.log(' dw - Delete word');
|
|
302
|
+
console.log(' x - Delete character under cursor');
|
|
303
|
+
console.log(' yy - Yank (copy) entire line');
|
|
304
|
+
console.log(' yw - Yank word');
|
|
305
|
+
console.log(' p - Paste after cursor');
|
|
306
|
+
console.log(' P - Paste before cursor');
|
|
307
|
+
console.log(' u - Undo');
|
|
308
|
+
console.log(' Ctrl+r - Redo');
|
|
309
|
+
console.log('');
|
|
310
|
+
console.log(chalk.cyan(' Command Mode:'));
|
|
311
|
+
console.log(' :help - Show this help');
|
|
312
|
+
console.log(' :w - Save session');
|
|
313
|
+
console.log(' :q! - Force quit');
|
|
314
|
+
console.log(' :set <opt> - Set option');
|
|
315
|
+
console.log(' :registers - Show registers');
|
|
316
|
+
console.log(' :mode - Show current mode');
|
|
317
|
+
console.log('');
|
|
318
|
+
console.log(chalk.bold('-------------------------------'));
|
|
319
|
+
console.log('');
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Print register contents
|
|
323
|
+
*/
|
|
324
|
+
printRegisters() {
|
|
325
|
+
console.log('');
|
|
326
|
+
console.log(chalk.bold('--- Vim Registers ---'));
|
|
327
|
+
for (const [name, reg] of this.registers) {
|
|
328
|
+
const preview = reg.content.length > 40 ? reg.content.slice(0, 40) + '...' : reg.content;
|
|
329
|
+
console.log(` "${name}: ${chalk.cyan(preview)} (${reg.type})`);
|
|
330
|
+
}
|
|
331
|
+
if (this.registers.size === 0) {
|
|
332
|
+
console.log(chalk.gray(' (no registers set)'));
|
|
333
|
+
}
|
|
334
|
+
console.log('');
|
|
335
|
+
}
|
|
336
|
+
/**
|
|
337
|
+
* Get config
|
|
338
|
+
*/
|
|
339
|
+
getConfig() {
|
|
340
|
+
return { ...this.config };
|
|
341
|
+
}
|
|
342
|
+
// ----------------------------------------------------------
|
|
343
|
+
// Private key handlers
|
|
344
|
+
// ----------------------------------------------------------
|
|
345
|
+
handleEscape() {
|
|
346
|
+
if (this.mode === 'insert' || this.mode === 'visual') {
|
|
347
|
+
this.mode = 'normal';
|
|
348
|
+
this.normalBuffer = '';
|
|
349
|
+
this.repeatCount = 0;
|
|
350
|
+
return { mode: 'normal', key: '\x1b', action: 'switch_mode', description: 'Switch to normal mode' };
|
|
351
|
+
}
|
|
352
|
+
if (this.mode === 'command') {
|
|
353
|
+
this.mode = 'normal';
|
|
354
|
+
this.commandBuffer = '';
|
|
355
|
+
return { mode: 'normal', key: '\x1b', action: 'switch_mode', description: 'Cancel command mode' };
|
|
356
|
+
}
|
|
357
|
+
return { mode: 'normal', key: '\x1b', action: 'no_op', description: 'Already in normal mode' };
|
|
358
|
+
}
|
|
359
|
+
handleNormalKey(key) {
|
|
360
|
+
// Accumulate repeat count
|
|
361
|
+
if (/^[1-9]$/.test(key) && this.normalBuffer === '') {
|
|
362
|
+
this.repeatCount = this.repeatCount * 10 + parseInt(key);
|
|
363
|
+
return { mode: 'normal', key, action: 'repeat_count', description: `Repeat count: ${this.repeatCount}` };
|
|
364
|
+
}
|
|
365
|
+
this.normalBuffer += key;
|
|
366
|
+
// Check for two-character commands
|
|
367
|
+
if (this.normalBuffer.length === 1) {
|
|
368
|
+
// Single-character commands
|
|
369
|
+
switch (key) {
|
|
370
|
+
case 'i':
|
|
371
|
+
this.mode = 'insert';
|
|
372
|
+
this.normalBuffer = '';
|
|
373
|
+
return { mode: 'insert', key, action: 'switch_mode', description: 'Enter insert mode' };
|
|
374
|
+
case 'a':
|
|
375
|
+
this.mode = 'insert';
|
|
376
|
+
this.cursorPosition++;
|
|
377
|
+
this.normalBuffer = '';
|
|
378
|
+
return { mode: 'insert', key, action: 'append', description: 'Append (insert after cursor)' };
|
|
379
|
+
case 'A':
|
|
380
|
+
this.mode = 'insert';
|
|
381
|
+
this.cursorPosition = this.lineBuffer.length;
|
|
382
|
+
this.normalBuffer = '';
|
|
383
|
+
return { mode: 'insert', key, action: 'append_end', description: 'Append at end of line' };
|
|
384
|
+
case 'I':
|
|
385
|
+
this.mode = 'insert';
|
|
386
|
+
this.cursorPosition = 0;
|
|
387
|
+
this.normalBuffer = '';
|
|
388
|
+
return { mode: 'insert', key, action: 'insert_start', description: 'Insert at start of line' };
|
|
389
|
+
case 'v':
|
|
390
|
+
this.mode = 'visual';
|
|
391
|
+
this.visualStart = this.cursorPosition;
|
|
392
|
+
this.normalBuffer = '';
|
|
393
|
+
return { mode: 'visual', key, action: 'switch_mode', description: 'Enter visual mode' };
|
|
394
|
+
case ':':
|
|
395
|
+
this.mode = 'command';
|
|
396
|
+
this.commandBuffer = '';
|
|
397
|
+
this.normalBuffer = '';
|
|
398
|
+
return { mode: 'command', key, action: 'switch_mode', description: 'Enter command mode' };
|
|
399
|
+
case 'h':
|
|
400
|
+
this.cursorPosition = Math.max(0, this.cursorPosition - 1);
|
|
401
|
+
this.normalBuffer = '';
|
|
402
|
+
return { mode: 'normal', key, action: 'cursor_left', description: 'Move cursor left' };
|
|
403
|
+
case 'l':
|
|
404
|
+
this.cursorPosition = Math.min(this.lineBuffer.length, this.cursorPosition + 1);
|
|
405
|
+
this.normalBuffer = '';
|
|
406
|
+
return { mode: 'normal', key, action: 'cursor_right', description: 'Move cursor right' };
|
|
407
|
+
case 'j':
|
|
408
|
+
this.normalBuffer = '';
|
|
409
|
+
return { mode: 'normal', key, action: 'history_next', description: 'Next history entry' };
|
|
410
|
+
case 'k':
|
|
411
|
+
this.normalBuffer = '';
|
|
412
|
+
return { mode: 'normal', key, action: 'history_prev', description: 'Previous history entry' };
|
|
413
|
+
case '0':
|
|
414
|
+
if (this.repeatCount === 0) {
|
|
415
|
+
this.cursorPosition = 0;
|
|
416
|
+
this.normalBuffer = '';
|
|
417
|
+
return { mode: 'normal', key, action: 'cursor_start', description: 'Move to start of line' };
|
|
418
|
+
}
|
|
419
|
+
break;
|
|
420
|
+
case '$':
|
|
421
|
+
this.cursorPosition = this.lineBuffer.length;
|
|
422
|
+
this.normalBuffer = '';
|
|
423
|
+
return { mode: 'normal', key, action: 'cursor_end', description: 'Move to end of line' };
|
|
424
|
+
case 'w':
|
|
425
|
+
this.moveWordForward();
|
|
426
|
+
this.normalBuffer = '';
|
|
427
|
+
return { mode: 'normal', key, action: 'word_forward', description: 'Move to next word' };
|
|
428
|
+
case 'b':
|
|
429
|
+
this.moveWordBackward();
|
|
430
|
+
this.normalBuffer = '';
|
|
431
|
+
return { mode: 'normal', key, action: 'word_backward', description: 'Move to previous word' };
|
|
432
|
+
case 'x':
|
|
433
|
+
this.deleteCharAtCursor();
|
|
434
|
+
this.normalBuffer = '';
|
|
435
|
+
return { mode: 'normal', key, action: 'delete_char', description: 'Delete character under cursor' };
|
|
436
|
+
case 'p':
|
|
437
|
+
this.pasteAfter();
|
|
438
|
+
this.normalBuffer = '';
|
|
439
|
+
return { mode: 'normal', key, action: 'paste_after', description: 'Paste after cursor' };
|
|
440
|
+
case 'P':
|
|
441
|
+
this.pasteBefore();
|
|
442
|
+
this.normalBuffer = '';
|
|
443
|
+
return { mode: 'normal', key, action: 'paste_before', description: 'Paste before cursor' };
|
|
444
|
+
case 'u':
|
|
445
|
+
this.normalBuffer = '';
|
|
446
|
+
return { mode: 'normal', key, action: 'undo', description: 'Undo' };
|
|
447
|
+
case 'r':
|
|
448
|
+
// Waiting for next key (replace char)
|
|
449
|
+
return { mode: 'normal', key, action: 'waiting', description: 'Waiting for replace target' };
|
|
450
|
+
case 'o':
|
|
451
|
+
this.mode = 'insert';
|
|
452
|
+
this.lineBuffer = '';
|
|
453
|
+
this.cursorPosition = 0;
|
|
454
|
+
this.normalBuffer = '';
|
|
455
|
+
return { mode: 'insert', key, action: 'open_line_below', description: 'Open new line below' };
|
|
456
|
+
case 'O':
|
|
457
|
+
this.mode = 'insert';
|
|
458
|
+
this.lineBuffer = '';
|
|
459
|
+
this.cursorPosition = 0;
|
|
460
|
+
this.normalBuffer = '';
|
|
461
|
+
return { mode: 'insert', key, action: 'open_line_above', description: 'Open new line above' };
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
// Two-character commands
|
|
465
|
+
if (this.normalBuffer.length === 2) {
|
|
466
|
+
const cmd = this.normalBuffer;
|
|
467
|
+
this.normalBuffer = '';
|
|
468
|
+
switch (cmd) {
|
|
469
|
+
case 'dd':
|
|
470
|
+
this.setRegister('', this.lineBuffer, 'line');
|
|
471
|
+
this.lineBuffer = '';
|
|
472
|
+
this.cursorPosition = 0;
|
|
473
|
+
this.repeatCount = 0;
|
|
474
|
+
return { mode: 'normal', key: cmd, action: 'delete_line', description: 'Delete entire line' };
|
|
475
|
+
case 'yy':
|
|
476
|
+
this.setRegister('', this.lineBuffer, 'line');
|
|
477
|
+
this.repeatCount = 0;
|
|
478
|
+
return { mode: 'normal', key: cmd, action: 'yank_line', description: 'Yank (copy) line' };
|
|
479
|
+
case 'dw':
|
|
480
|
+
this.deleteWordForward();
|
|
481
|
+
this.repeatCount = 0;
|
|
482
|
+
return { mode: 'normal', key: cmd, action: 'delete_word', description: 'Delete word' };
|
|
483
|
+
case 'yw':
|
|
484
|
+
this.yankWordForward();
|
|
485
|
+
this.repeatCount = 0;
|
|
486
|
+
return { mode: 'normal', key: cmd, action: 'yank_word', description: 'Yank word' };
|
|
487
|
+
case 'cc':
|
|
488
|
+
this.setRegister('', this.lineBuffer, 'line');
|
|
489
|
+
this.lineBuffer = '';
|
|
490
|
+
this.cursorPosition = 0;
|
|
491
|
+
this.mode = 'insert';
|
|
492
|
+
this.repeatCount = 0;
|
|
493
|
+
return { mode: 'insert', key: cmd, action: 'change_line', description: 'Change entire line' };
|
|
494
|
+
case 'cw':
|
|
495
|
+
this.changeWordForward();
|
|
496
|
+
this.mode = 'insert';
|
|
497
|
+
this.repeatCount = 0;
|
|
498
|
+
return { mode: 'insert', key: cmd, action: 'change_word', description: 'Change word' };
|
|
499
|
+
default:
|
|
500
|
+
// Handle replace char (r followed by char)
|
|
501
|
+
if (cmd[0] === 'r' && cmd.length === 2) {
|
|
502
|
+
this.replaceCharAtCursor(cmd[1]);
|
|
503
|
+
this.repeatCount = 0;
|
|
504
|
+
return { mode: 'normal', key: cmd, action: 'replace_char', description: `Replace char with '${cmd[1]}'` };
|
|
505
|
+
}
|
|
506
|
+
if (this.config.bellOnError) {
|
|
507
|
+
return { mode: 'normal', key: cmd, action: 'bell', description: `Unknown command: ${cmd}` };
|
|
508
|
+
}
|
|
509
|
+
return { mode: 'normal', key: cmd, action: 'no_op', description: `Unknown command: ${cmd}` };
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
return { mode: 'normal', key, action: 'waiting', description: 'Waiting for next key' };
|
|
513
|
+
}
|
|
514
|
+
handleInsertKey(key) {
|
|
515
|
+
// In insert mode, most keys are passed through to readline
|
|
516
|
+
// We only intercept special keys
|
|
517
|
+
if (key === '\x1b' || key === 'Escape') {
|
|
518
|
+
return this.handleEscape();
|
|
519
|
+
}
|
|
520
|
+
// Ctrl+C in insert mode goes to normal mode
|
|
521
|
+
if (key === '\x03') {
|
|
522
|
+
this.mode = 'normal';
|
|
523
|
+
return { mode: 'normal', key, action: 'switch_mode', description: 'Ctrl+C: switch to normal mode' };
|
|
524
|
+
}
|
|
525
|
+
// Pass through to readline
|
|
526
|
+
return { mode: 'insert', key, action: 'passthrough', description: 'Insert mode passthrough' };
|
|
527
|
+
}
|
|
528
|
+
handleVisualKey(key) {
|
|
529
|
+
switch (key) {
|
|
530
|
+
case '\x1b':
|
|
531
|
+
case 'Escape':
|
|
532
|
+
this.mode = 'normal';
|
|
533
|
+
return { mode: 'normal', key, action: 'switch_mode', description: 'Exit visual mode' };
|
|
534
|
+
case 'h':
|
|
535
|
+
this.cursorPosition = Math.max(0, this.cursorPosition - 1);
|
|
536
|
+
return { mode: 'visual', key, action: 'cursor_left', description: 'Move selection left' };
|
|
537
|
+
case 'l':
|
|
538
|
+
this.cursorPosition = Math.min(this.lineBuffer.length, this.cursorPosition + 1);
|
|
539
|
+
return { mode: 'visual', key, action: 'cursor_right', description: 'Move selection right' };
|
|
540
|
+
case 'y':
|
|
541
|
+
this.yankVisualSelection();
|
|
542
|
+
this.mode = 'normal';
|
|
543
|
+
return { mode: 'normal', key, action: 'yank_selection', description: 'Yank visual selection' };
|
|
544
|
+
case 'd':
|
|
545
|
+
case 'x':
|
|
546
|
+
this.deleteVisualSelection();
|
|
547
|
+
this.mode = 'normal';
|
|
548
|
+
return { mode: 'normal', key, action: 'delete_selection', description: 'Delete visual selection' };
|
|
549
|
+
default:
|
|
550
|
+
return { mode: 'visual', key, action: 'no_op', description: `Unknown visual key: ${key}` };
|
|
551
|
+
}
|
|
552
|
+
}
|
|
553
|
+
handleCommandKey(key) {
|
|
554
|
+
// Enter executes command
|
|
555
|
+
if (key === '\r' || key === '\n') {
|
|
556
|
+
const cmd = this.commandBuffer;
|
|
557
|
+
this.commandBuffer = '';
|
|
558
|
+
this.mode = 'normal';
|
|
559
|
+
this.executeCommand(cmd);
|
|
560
|
+
return { mode: 'normal', key, action: 'execute_command', description: `Execute: :${cmd}` };
|
|
561
|
+
}
|
|
562
|
+
// Backspace
|
|
563
|
+
if (key === '\x7f' || key === 'Backspace') {
|
|
564
|
+
this.commandBuffer = this.commandBuffer.slice(0, -1);
|
|
565
|
+
if (this.commandBuffer === '') {
|
|
566
|
+
this.mode = 'normal';
|
|
567
|
+
return { mode: 'normal', key, action: 'switch_mode', description: 'Cancel command' };
|
|
568
|
+
}
|
|
569
|
+
return { mode: 'command', key, action: 'backspace', description: 'Command backspace' };
|
|
570
|
+
}
|
|
571
|
+
// Escape cancels
|
|
572
|
+
if (key === '\x1b' || key === 'Escape') {
|
|
573
|
+
this.commandBuffer = '';
|
|
574
|
+
this.mode = 'normal';
|
|
575
|
+
return { mode: 'normal', key, action: 'switch_mode', description: 'Cancel command' };
|
|
576
|
+
}
|
|
577
|
+
// Up/Down for command history
|
|
578
|
+
if (key === '\x1b[A' || key === 'ArrowUp') {
|
|
579
|
+
if (this.commandHistory.length > 0) {
|
|
580
|
+
this.commandHistoryIndex = Math.min(this.commandHistoryIndex + 1, this.commandHistory.length - 1);
|
|
581
|
+
this.commandBuffer = this.commandHistory[this.commandHistory.length - 1 - this.commandHistoryIndex];
|
|
582
|
+
}
|
|
583
|
+
return { mode: 'command', key, action: 'history_prev', description: 'Previous command' };
|
|
584
|
+
}
|
|
585
|
+
if (key === '\x1b[B' || key === 'ArrowDown') {
|
|
586
|
+
this.commandHistoryIndex = Math.max(this.commandHistoryIndex - 1, -1);
|
|
587
|
+
if (this.commandHistoryIndex === -1) {
|
|
588
|
+
this.commandBuffer = '';
|
|
589
|
+
}
|
|
590
|
+
else {
|
|
591
|
+
this.commandBuffer = this.commandHistory[this.commandHistory.length - 1 - this.commandHistoryIndex];
|
|
592
|
+
}
|
|
593
|
+
return { mode: 'command', key, action: 'history_next', description: 'Next command' };
|
|
594
|
+
}
|
|
595
|
+
// Regular character
|
|
596
|
+
if (key.length === 1 && key.charCodeAt(0) >= 32) {
|
|
597
|
+
this.commandBuffer += key;
|
|
598
|
+
return { mode: 'command', key, action: 'type', description: `Command: :${this.commandBuffer}` };
|
|
599
|
+
}
|
|
600
|
+
return { mode: 'command', key, action: 'no_op', description: 'Ignored key in command mode' };
|
|
601
|
+
}
|
|
602
|
+
// ----------------------------------------------------------
|
|
603
|
+
// Private text manipulation helpers
|
|
604
|
+
// ----------------------------------------------------------
|
|
605
|
+
moveWordForward() {
|
|
606
|
+
const text = this.lineBuffer;
|
|
607
|
+
let pos = this.cursorPosition;
|
|
608
|
+
// Skip current word
|
|
609
|
+
while (pos < text.length && /\w/.test(text[pos]))
|
|
610
|
+
pos++;
|
|
611
|
+
// Skip whitespace
|
|
612
|
+
while (pos < text.length && /\s/.test(text[pos]))
|
|
613
|
+
pos++;
|
|
614
|
+
this.cursorPosition = pos;
|
|
615
|
+
}
|
|
616
|
+
moveWordBackward() {
|
|
617
|
+
const text = this.lineBuffer;
|
|
618
|
+
let pos = this.cursorPosition - 1;
|
|
619
|
+
// Skip whitespace
|
|
620
|
+
while (pos > 0 && /\s/.test(text[pos]))
|
|
621
|
+
pos--;
|
|
622
|
+
// Skip current word
|
|
623
|
+
while (pos > 0 && /\w/.test(text[pos - 1]))
|
|
624
|
+
pos--;
|
|
625
|
+
this.cursorPosition = Math.max(0, pos);
|
|
626
|
+
}
|
|
627
|
+
deleteCharAtCursor() {
|
|
628
|
+
if (this.cursorPosition < this.lineBuffer.length) {
|
|
629
|
+
const deleted = this.lineBuffer[this.cursorPosition];
|
|
630
|
+
this.lineBuffer = this.lineBuffer.slice(0, this.cursorPosition) + this.lineBuffer.slice(this.cursorPosition + 1);
|
|
631
|
+
this.setRegister('', deleted, 'char');
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
replaceCharAtCursor(char) {
|
|
635
|
+
if (this.cursorPosition < this.lineBuffer.length) {
|
|
636
|
+
this.lineBuffer = this.lineBuffer.slice(0, this.cursorPosition) + char + this.lineBuffer.slice(this.cursorPosition + 1);
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
deleteWordForward() {
|
|
640
|
+
const start = this.cursorPosition;
|
|
641
|
+
const text = this.lineBuffer;
|
|
642
|
+
let end = start;
|
|
643
|
+
while (end < text.length && /\w/.test(text[end]))
|
|
644
|
+
end++;
|
|
645
|
+
while (end < text.length && /\s/.test(text[end]))
|
|
646
|
+
end++;
|
|
647
|
+
const deleted = text.slice(start, end);
|
|
648
|
+
this.setRegister('', deleted, 'char');
|
|
649
|
+
this.lineBuffer = text.slice(0, start) + text.slice(end);
|
|
650
|
+
}
|
|
651
|
+
yankWordForward() {
|
|
652
|
+
const start = this.cursorPosition;
|
|
653
|
+
const text = this.lineBuffer;
|
|
654
|
+
let end = start;
|
|
655
|
+
while (end < text.length && /\w/.test(text[end]))
|
|
656
|
+
end++;
|
|
657
|
+
const yanked = text.slice(start, end);
|
|
658
|
+
this.setRegister('', yanked, 'char');
|
|
659
|
+
}
|
|
660
|
+
changeWordForward() {
|
|
661
|
+
const start = this.cursorPosition;
|
|
662
|
+
const text = this.lineBuffer;
|
|
663
|
+
let end = start;
|
|
664
|
+
while (end < text.length && /\w/.test(text[end]))
|
|
665
|
+
end++;
|
|
666
|
+
const deleted = text.slice(start, end);
|
|
667
|
+
this.setRegister('', deleted, 'char');
|
|
668
|
+
this.lineBuffer = text.slice(0, start) + text.slice(end);
|
|
669
|
+
this.cursorPosition = start;
|
|
670
|
+
}
|
|
671
|
+
pasteAfter() {
|
|
672
|
+
const reg = this.registers.get('');
|
|
673
|
+
if (!reg)
|
|
674
|
+
return;
|
|
675
|
+
if (reg.type === 'line') {
|
|
676
|
+
this.lineBuffer += reg.content;
|
|
677
|
+
this.cursorPosition = this.lineBuffer.length;
|
|
678
|
+
}
|
|
679
|
+
else {
|
|
680
|
+
const pos = this.cursorPosition + 1;
|
|
681
|
+
this.lineBuffer = this.lineBuffer.slice(0, pos) + reg.content + this.lineBuffer.slice(pos);
|
|
682
|
+
this.cursorPosition = pos + reg.content.length;
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
pasteBefore() {
|
|
686
|
+
const reg = this.registers.get('');
|
|
687
|
+
if (!reg)
|
|
688
|
+
return;
|
|
689
|
+
if (reg.type === 'line') {
|
|
690
|
+
this.lineBuffer = reg.content + this.lineBuffer;
|
|
691
|
+
this.cursorPosition = 0;
|
|
692
|
+
}
|
|
693
|
+
else {
|
|
694
|
+
this.lineBuffer = this.lineBuffer.slice(0, this.cursorPosition) + reg.content + this.lineBuffer.slice(this.cursorPosition);
|
|
695
|
+
this.cursorPosition += reg.content.length;
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
yankVisualSelection() {
|
|
699
|
+
const start = Math.min(this.visualStart, this.cursorPosition);
|
|
700
|
+
const end = Math.max(this.visualStart, this.cursorPosition);
|
|
701
|
+
const yanked = this.lineBuffer.slice(start, end);
|
|
702
|
+
this.setRegister('', yanked, 'char');
|
|
703
|
+
}
|
|
704
|
+
deleteVisualSelection() {
|
|
705
|
+
const start = Math.min(this.visualStart, this.cursorPosition);
|
|
706
|
+
const end = Math.max(this.visualStart, this.cursorPosition);
|
|
707
|
+
const deleted = this.lineBuffer.slice(start, end);
|
|
708
|
+
this.setRegister('', deleted, 'char');
|
|
709
|
+
this.lineBuffer = this.lineBuffer.slice(0, start) + this.lineBuffer.slice(end);
|
|
710
|
+
this.cursorPosition = start;
|
|
711
|
+
}
|
|
712
|
+
handleSetCommand(args) {
|
|
713
|
+
const parts = args.trim().split(/\s+/);
|
|
714
|
+
if (parts.length === 0)
|
|
715
|
+
return;
|
|
716
|
+
switch (parts[0]) {
|
|
717
|
+
case 'number':
|
|
718
|
+
case 'nu':
|
|
719
|
+
this.config.showModeIndicator = true;
|
|
720
|
+
console.log(chalk.gray('Mode indicator enabled.'));
|
|
721
|
+
break;
|
|
722
|
+
case 'nonumber':
|
|
723
|
+
case 'nonu':
|
|
724
|
+
this.config.showModeIndicator = false;
|
|
725
|
+
console.log(chalk.gray('Mode indicator disabled.'));
|
|
726
|
+
break;
|
|
727
|
+
case 'bell':
|
|
728
|
+
this.config.bellOnError = true;
|
|
729
|
+
console.log(chalk.gray('Bell on error enabled.'));
|
|
730
|
+
break;
|
|
731
|
+
case 'nobell':
|
|
732
|
+
this.config.bellOnError = false;
|
|
733
|
+
console.log(chalk.gray('Bell on error disabled.'));
|
|
734
|
+
break;
|
|
735
|
+
default:
|
|
736
|
+
console.log(chalk.yellow(`Unknown option: ${parts[0]}`));
|
|
737
|
+
}
|
|
738
|
+
this.saveConfig();
|
|
739
|
+
}
|
|
740
|
+
printCommandHistory() {
|
|
741
|
+
console.log('');
|
|
742
|
+
console.log(chalk.bold('--- Command History ---'));
|
|
743
|
+
for (let i = this.commandHistory.length - 1; i >= Math.max(0, this.commandHistory.length - 20); i--) {
|
|
744
|
+
console.log(` :${this.commandHistory[i]}`);
|
|
745
|
+
}
|
|
746
|
+
if (this.commandHistory.length === 0) {
|
|
747
|
+
console.log(chalk.gray(' (no commands in history)'));
|
|
748
|
+
}
|
|
749
|
+
console.log('');
|
|
750
|
+
}
|
|
751
|
+
saveConfig() {
|
|
752
|
+
try {
|
|
753
|
+
const dir = join(VIM_CONFIG_PATH, '..');
|
|
754
|
+
if (!existsSync(dir))
|
|
755
|
+
mkdirSync(dir, { recursive: true });
|
|
756
|
+
writeFileSync(VIM_CONFIG_PATH, JSON.stringify(this.config, null, 2), 'utf-8');
|
|
757
|
+
}
|
|
758
|
+
catch { /* Silently fail */ }
|
|
759
|
+
}
|
|
760
|
+
loadConfig() {
|
|
761
|
+
try {
|
|
762
|
+
if (existsSync(VIM_CONFIG_PATH)) {
|
|
763
|
+
const raw = readFileSync(VIM_CONFIG_PATH, 'utf-8');
|
|
764
|
+
const saved = JSON.parse(raw);
|
|
765
|
+
this.config = { ...this.config, ...saved };
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
catch { /* Silently fail */ }
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
//# sourceMappingURL=vim-mode.js.map
|