-
-
Notifications
You must be signed in to change notification settings - Fork 65
Expand file tree
/
Copy pathconfig.lua
More file actions
532 lines (454 loc) · 18.9 KB
/
config.lua
File metadata and controls
532 lines (454 loc) · 18.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
---@mod claude-code.config Configuration management for claude-code.nvim
---@brief [[
--- This module handles configuration management and validation for claude-code.nvim.
--- It provides the default configuration, validation, and merging of user config.
---@brief ]]
local M = {}
--- ClaudeCodeWindow class for window configuration
-- @table ClaudeCodeWindow
-- @field split_ratio number Percentage of screen for the terminal window (height for horizontal, width for vertical)
-- @field position string Position of the window: "botright", "topleft", "vertical", "float" etc.
-- @field enter_insert boolean Whether to enter insert mode when opening Claude Code
-- @field start_in_normal_mode boolean Whether to start in normal mode instead of insert mode when opening Claude Code
-- @field hide_numbers boolean Hide line numbers in the terminal window
-- @field hide_signcolumn boolean Hide the sign column in the terminal window
-- @field float table|nil Floating window configuration (only used when position is "float")
-- @field float.width number|string Width of floating window (number: columns, string: percentage like "80%")
-- @field float.height number|string Height of floating window (number: rows, string: percentage like "80%")
-- @field float.row number|string|nil Row position (number: absolute, string: "center" or percentage)
-- @field float.col number|string|nil Column position (number: absolute, string: "center" or percentage)
-- @field float.border string Border style: "none", "single", "double", "rounded", "solid", "shadow", or array
-- @field float.relative string Relative positioning: "editor" or "cursor"
--- ClaudeCodeRefresh class for file refresh configuration
-- @table ClaudeCodeRefresh
-- @field enable boolean Enable file change detection
-- @field updatetime number updatetime when Claude Code is active (milliseconds)
-- @field timer_interval number How often to check for file changes (milliseconds)
-- @field show_notifications boolean Show notification when files are reloaded
--- ClaudeCodeGit class for git integration configuration
-- @table ClaudeCodeGit
-- @field use_git_root boolean Set CWD to git root when opening Claude Code (if in git project)
-- @field multi_instance boolean Use multiple Claude instances (one per git root)
--- ClaudeCodeKeymapsToggle class for toggle keymap configuration
-- @table ClaudeCodeKeymapsToggle
-- @field normal string|boolean Normal mode keymap for toggling Claude Code, false to disable
-- @field terminal string|boolean Terminal mode keymap for toggling Claude Code, false to disable
--- ClaudeCodeKeymaps class for keymap configuration
-- @table ClaudeCodeKeymaps
-- @field toggle ClaudeCodeKeymapsToggle Keymaps for toggling Claude Code
-- @field window_navigation table|nil Window navigation keymaps
-- @field window_navigation.enabled boolean Enable window navigation keymaps
-- @field scrolling table|nil Scrolling keymaps
-- @field scrolling.enabled boolean Enable scrolling keymaps
--- ClaudeCodeCommandVariants class for command variant configuration
-- @table ClaudeCodeCommandVariants
-- Conversation management:
-- @field continue string|boolean Resume the most recent conversation
-- @field resume string|boolean Display an interactive conversation picker
-- Output options:
-- @field verbose string|boolean Enable verbose logging with full turn-by-turn output
-- Additional options can be added as needed
--- ClaudeCodeShell class for shell configuration
-- @table ClaudeCodeShell
-- @field separator string Command separator used in shell commands (e.g., '&&', ';', '|')
-- @field pushd_cmd string Command to push directory onto stack (e.g., 'pushd' for bash/zsh)
-- @field popd_cmd string Command to pop directory from stack (e.g., 'popd' for bash/zsh)
--- ClaudeCodeConfig class for main configuration
-- @table ClaudeCodeConfig
-- @field window ClaudeCodeWindow Terminal window settings
-- @field refresh ClaudeCodeRefresh File refresh settings
-- @field git ClaudeCodeGit Git integration settings
-- @field shell ClaudeCodeShell Shell-specific configuration
-- @field command string Command used to launch Claude Code
-- @field command_variants ClaudeCodeCommandVariants Command variants configuration
-- @field keymaps ClaudeCodeKeymaps Keymaps configuration
--- Default configuration options
--- @type ClaudeCodeConfig
M.default_config = {
-- Terminal window settings
window = {
split_ratio = 0.3, -- Percentage of screen for the terminal window (height or width)
height_ratio = 0.3, -- DEPRECATED: Use split_ratio instead
position = 'botright', -- Position of the window: "botright", "topleft", "vertical", "float", etc.
enter_insert = true, -- Whether to enter insert mode when opening Claude Code
start_in_normal_mode = false, -- Whether to start in normal mode instead of insert mode
hide_numbers = true, -- Hide line numbers in the terminal window
hide_signcolumn = true, -- Hide the sign column in the terminal window
-- Default floating window configuration
float = {
width = '80%', -- Width as percentage of editor
height = '80%', -- Height as percentage of editor
row = 'center', -- Center vertically
col = 'center', -- Center horizontally
relative = 'editor', -- Position relative to editor
border = 'rounded', -- Border style
},
},
-- File refresh settings
refresh = {
enable = true, -- Enable file change detection
updatetime = 100, -- updatetime to use when Claude Code is active (milliseconds)
timer_interval = 1000, -- How often to check for file changes (milliseconds)
show_notifications = true, -- Show notification when files are reloaded
},
-- Git integration settings
git = {
use_git_root = true, -- Set CWD to git root when opening Claude Code (if in git project)
multi_instance = true, -- Use multiple Claude instances (one per git root)
},
-- Shell-specific settings
shell = {
separator = '&&', -- Command separator used in shell commands
pushd_cmd = 'pushd', -- Command to push directory onto stack
popd_cmd = 'popd', -- Command to pop directory from stack
},
-- Command settings
command = 'claude', -- Command used to launch Claude Code
-- Command variants
command_variants = {
-- Conversation management
continue = '--continue', -- Resume the most recent conversation
resume = '--resume', -- Display an interactive conversation picker
-- Output options
verbose = '--verbose', -- Enable verbose logging with full turn-by-turn output
},
-- Keymaps
keymaps = {
toggle = {
normal = '<C-,>', -- Normal mode keymap for toggling Claude Code
terminal = '<C-,>', -- Terminal mode keymap for toggling Claude Code
variants = {
continue = '<leader>cC', -- Normal mode keymap for Claude Code with continue flag
verbose = '<leader>cV', -- Normal mode keymap for Claude Code with verbose flag
},
},
window_navigation = {
enabled = true, -- Enable window navigation keymaps
left = '<C-h>', -- Move to left window
down = '<C-j>', -- Move to down window
up = '<C-k>', -- Move to up window
right = '<C-l>', -- Move to right window
},
scrolling = {
enabled = true, -- Enable scrolling keymaps
page_down = '<C-f>', -- Scroll down one page
page_up = '<C-b>', -- Scroll up one page
},
},
}
--- Validate the configuration
--- Validate window configuration
--- @param window table Window configuration
--- @return boolean valid
--- @return string? error_message
local function validate_window_config(window)
if type(window) ~= 'table' then
return false, 'window config must be a table'
end
if type(window.split_ratio) ~= 'number' or window.split_ratio <= 0 or window.split_ratio > 1 then
return false, 'window.split_ratio must be a number between 0 and 1'
end
if type(window.position) ~= 'string' then
return false, 'window.position must be a string'
end
if type(window.enter_insert) ~= 'boolean' then
return false, 'window.enter_insert must be a boolean'
end
if type(window.start_in_normal_mode) ~= 'boolean' then
return false, 'window.start_in_normal_mode must be a boolean'
end
if type(window.hide_numbers) ~= 'boolean' then
return false, 'window.hide_numbers must be a boolean'
end
if type(window.hide_signcolumn) ~= 'boolean' then
return false, 'window.hide_signcolumn must be a boolean'
end
return true, nil
end
--- Validate floating window configuration
--- @param float table Float configuration
--- @return boolean valid
--- @return string? error_message
local function validate_float_config(float)
if type(float) ~= 'table' then
return false, 'window.float must be a table when position is "float"'
end
-- Validate width (can be number or percentage string)
if type(float.width) == 'string' then
if not float.width:match('^%d+%%$') then
return false, 'window.float.width must be a number or percentage (e.g., "80%")'
end
elseif type(float.width) ~= 'number' or float.width <= 0 then
return false, 'window.float.width must be a positive number or percentage string'
end
-- Validate height (can be number or percentage string)
if type(float.height) == 'string' then
if not float.height:match('^%d+%%$') then
return false, 'window.float.height must be a number or percentage (e.g., "80%")'
end
elseif type(float.height) ~= 'number' or float.height <= 0 then
return false, 'window.float.height must be a positive number or percentage string'
end
-- Validate relative (must be "editor" or "cursor")
if float.relative ~= 'editor' and float.relative ~= 'cursor' then
return false, 'window.float.relative must be "editor" or "cursor"'
end
-- Validate border (must be valid border style)
local valid_borders = { 'none', 'single', 'double', 'rounded', 'solid', 'shadow' }
local is_valid_border = false
for _, border in ipairs(valid_borders) do
if float.border == border then
is_valid_border = true
break
end
end
-- Also allow array borders
if not is_valid_border and type(float.border) ~= 'table' then
return false,
'window.float.border must be one of: none, single, double, rounded, solid, shadow, or an array'
end
-- Validate row and col if they exist
if float.row ~= nil then
if type(float.row) == 'string' and float.row ~= 'center' then
if not float.row:match('^%d+%%$') then
return false, 'window.float.row must be a number, "center", or percentage string'
end
elseif type(float.row) ~= 'number' and float.row ~= 'center' then
return false, 'window.float.row must be a number, "center", or percentage string'
end
end
if float.col ~= nil then
if type(float.col) == 'string' and float.col ~= 'center' then
if not float.col:match('^%d+%%$') then
return false, 'window.float.col must be a number, "center", or percentage string'
end
elseif type(float.col) ~= 'number' and float.col ~= 'center' then
return false, 'window.float.col must be a number, "center", or percentage string'
end
end
return true, nil
end
--- Validate refresh configuration
--- @param refresh table Refresh configuration
--- @return boolean valid
--- @return string? error_message
local function validate_refresh_config(refresh)
if type(refresh) ~= 'table' then
return false, 'refresh config must be a table'
end
if type(refresh.enable) ~= 'boolean' then
return false, 'refresh.enable must be a boolean'
end
if type(refresh.updatetime) ~= 'number' or refresh.updatetime <= 0 then
return false, 'refresh.updatetime must be a positive number'
end
if type(refresh.timer_interval) ~= 'number' or refresh.timer_interval <= 0 then
return false, 'refresh.timer_interval must be a positive number'
end
if type(refresh.show_notifications) ~= 'boolean' then
return false, 'refresh.show_notifications must be a boolean'
end
return true, nil
end
--- Validate git configuration
--- @param git table Git configuration
--- @return boolean valid
--- @return string? error_message
local function validate_git_config(git)
if type(git) ~= 'table' then
return false, 'git config must be a table'
end
if type(git.use_git_root) ~= 'boolean' then
return false, 'git.use_git_root must be a boolean'
end
if type(git.multi_instance) ~= 'boolean' then
return false, 'git.multi_instance must be a boolean'
end
return true, nil
end
--- Validate shell configuration
--- @param shell table Shell configuration
--- @return boolean valid
--- @return string? error_message
local function validate_shell_config(shell)
if type(shell) ~= 'table' then
return false, 'shell config must be a table'
end
if type(shell.separator) ~= 'string' then
return false, 'shell.separator must be a string'
end
if type(shell.pushd_cmd) ~= 'string' then
return false, 'shell.pushd_cmd must be a string'
end
if type(shell.popd_cmd) ~= 'string' then
return false, 'shell.popd_cmd must be a string'
end
return true, nil
end
--- Validate keymaps configuration
--- @param keymaps table Keymaps configuration
--- @return boolean valid
--- @return string? error_message
local function validate_keymaps_config(keymaps)
if type(keymaps) ~= 'table' then
return false, 'keymaps config must be a table'
end
if type(keymaps.toggle) ~= 'table' then
return false, 'keymaps.toggle must be a table'
end
if not (keymaps.toggle.normal == false or type(keymaps.toggle.normal) == 'string') then
return false, 'keymaps.toggle.normal must be a string or false'
end
if not (keymaps.toggle.terminal == false or type(keymaps.toggle.terminal) == 'string') then
return false, 'keymaps.toggle.terminal must be a string or false'
end
-- Validate variant keymaps if they exist
if keymaps.toggle.variants then
if type(keymaps.toggle.variants) ~= 'table' then
return false, 'keymaps.toggle.variants must be a table'
end
-- Check each variant keymap
for variant_name, keymap in pairs(keymaps.toggle.variants) do
if not (keymap == false or type(keymap) == 'string') then
return false, 'keymaps.toggle.variants.' .. variant_name .. ' must be a string or false'
end
end
end
if type(keymaps.window_navigation) ~= 'table' then
return false, 'keymaps.window_navigation must be a table'
end
if type(keymaps.window_navigation.enabled) ~= 'boolean' then
return false, 'keymaps.window_navigation.enabled boolean'
end
if type(keymaps.window_navigation.left) ~= 'string' then
return false, 'keymaps.window_navigation.left must be a string'
end
if type(keymaps.window_navigation.down) ~= 'string' then
return false, 'keymaps.window_navigation.down must be a string'
end
if type(keymaps.window_navigation.up) ~= 'string' then
return false, 'keymaps.window_navigation.up must be a string'
end
if type(keymaps.window_navigation.right) ~= 'string' then
return false, 'keymaps.window_navigation.right must be a string'
end
if type(keymaps.scrolling) ~= 'table' then
return false, 'keymaps.scrolling must be a table'
end
if type(keymaps.scrolling.enabled) ~= 'boolean' then
return false, 'keymaps.scrolling.enabled must be a boolean'
end
if type(keymaps.scrolling.page_down) ~= 'string' then
return false, 'keymaps.scrolling.page_down must be a string'
end
if type(keymaps.scrolling.page_up) ~= 'string' then
return false, 'keymaps.scrolling.page_up must be a string'
end
return true, nil
end
--- Validate command variants configuration
--- @param command_variants table Command variants configuration
--- @return boolean valid
--- @return string? error_message
local function validate_command_variants_config(command_variants)
if type(command_variants) ~= 'table' then
return false, 'command_variants config must be a table'
end
-- Check each command variant
for variant_name, variant_args in pairs(command_variants) do
if not (variant_args == false or type(variant_args) == 'string') then
return false, 'command_variants.' .. variant_name .. ' must be a string or false'
end
end
return true, nil
end
--- Validate configuration options
--- @param config ClaudeCodeConfig
--- @return boolean valid
--- @return string? error_message
local function validate_config(config)
-- Validate window settings
local valid, err = validate_window_config(config.window)
if not valid then
return false, err
end
-- Validate float configuration if position is "float"
if config.window.position == 'float' then
valid, err = validate_float_config(config.window.float)
if not valid then
return false, err
end
end
-- Validate refresh settings
valid, err = validate_refresh_config(config.refresh)
if not valid then
return false, err
end
-- Validate git settings
valid, err = validate_git_config(config.git)
if not valid then
return false, err
end
-- Validate shell settings
valid, err = validate_shell_config(config.shell)
if not valid then
return false, err
end
-- Validate command settings
if type(config.command) ~= 'string' then
return false, 'command must be a string'
end
-- Validate command variants settings
valid, err = validate_command_variants_config(config.command_variants)
if not valid then
return false, err
end
-- Validate keymaps settings
valid, err = validate_keymaps_config(config.keymaps)
if not valid then
return false, err
end
-- Cross-validate keymaps with command variants
if config.keymaps.toggle.variants then
for variant_name, keymap in pairs(config.keymaps.toggle.variants) do
-- Ensure variant exists in command_variants
if keymap ~= false and not config.command_variants[variant_name] then
return false,
'keymaps.toggle.variants.' .. variant_name .. ' has no corresponding command variant'
end
end
end
return true, nil
end
--- Parse user configuration and merge with defaults
--- @param user_config? table
--- @param silent? boolean Set to true to suppress error notifications (for tests)
--- @return ClaudeCodeConfig
function M.parse_config(user_config, silent)
-- Handle backward compatibility first
if user_config and user_config.window then
if user_config.window.height_ratio and not user_config.window.split_ratio then
-- Copy height_ratio to split_ratio for backward compatibility
user_config.window.split_ratio = user_config.window.height_ratio
end
end
local config = vim.tbl_deep_extend('force', {}, M.default_config, user_config or {})
-- If position is float and no float config provided, use default float config
if
config.window.position == 'float'
and not (user_config and user_config.window and user_config.window.float)
then
config.window.float = vim.deepcopy(M.default_config.window.float)
end
local valid, err = validate_config(config)
if not valid then
-- Only notify if not in silent mode
if not silent then
vim.notify('Claude Code: ' .. err, vim.log.levels.ERROR)
end
-- Fall back to default config in case of error
return vim.deepcopy(M.default_config)
end
return config
end
return M