@@ -46,13 +46,88 @@ local function load_provider(provider_name)
46
46
return providers [provider_name ]
47
47
end
48
48
49
+ --- Validates and enhances a custom table provider with smart defaults
50
+ --- @param provider table The custom provider table to validate
51
+ --- @return TerminalProvider | nil provider The enhanced provider , or nil if invalid
52
+ --- @return string | nil error Error message if validation failed
53
+ local function validate_and_enhance_provider (provider )
54
+ if type (provider ) ~= " table" then
55
+ return nil , " Custom provider must be a table"
56
+ end
57
+
58
+ -- Required functions that must be implemented
59
+ local required_functions = {
60
+ " setup" ,
61
+ " open" ,
62
+ " close" ,
63
+ " simple_toggle" ,
64
+ " focus_toggle" ,
65
+ " get_active_bufnr" ,
66
+ " is_available" ,
67
+ }
68
+
69
+ -- Validate all required functions exist and are callable
70
+ for _ , func_name in ipairs (required_functions ) do
71
+ local func = provider [func_name ]
72
+ if not func then
73
+ return nil , " Custom provider missing required function: " .. func_name
74
+ end
75
+ -- Check if it's callable (function or table with __call metamethod)
76
+ local is_callable = type (func ) == " function"
77
+ or (type (func ) == " table" and getmetatable (func ) and getmetatable (func ).__call )
78
+ if not is_callable then
79
+ return nil , " Custom provider field '" .. func_name .. " ' must be callable, got: " .. type (func )
80
+ end
81
+ end
82
+
83
+ -- Create enhanced provider with defaults for optional functions
84
+ -- Note: Don't deep copy to preserve spy functions in tests
85
+ local enhanced_provider = provider
86
+
87
+ -- Add default toggle function if not provided (calls simple_toggle for backward compatibility)
88
+ if not enhanced_provider .toggle then
89
+ enhanced_provider .toggle = function (cmd_string , env_table , effective_config )
90
+ return enhanced_provider .simple_toggle (cmd_string , env_table , effective_config )
91
+ end
92
+ end
93
+
94
+ -- Add default test function if not provided
95
+ if not enhanced_provider ._get_terminal_for_test then
96
+ enhanced_provider ._get_terminal_for_test = function ()
97
+ return nil
98
+ end
99
+ end
100
+
101
+ return enhanced_provider , nil
102
+ end
103
+
49
104
--- Gets the effective terminal provider, guaranteed to return a valid provider
50
105
--- Falls back to native provider if configured provider is unavailable
51
106
--- @return TerminalProvider provider The terminal provider module (never nil )
52
107
local function get_provider ()
53
108
local logger = require (" claudecode.logger" )
54
109
55
- if config .provider == " auto" then
110
+ -- Handle custom table provider
111
+ if type (config .provider ) == " table" then
112
+ local enhanced_provider , error_msg = validate_and_enhance_provider (config .provider )
113
+ if enhanced_provider then
114
+ -- Check if custom provider is available
115
+ local is_available_ok , is_available = pcall (enhanced_provider .is_available )
116
+ if is_available_ok and is_available then
117
+ logger .debug (" terminal" , " Using custom table provider" )
118
+ return enhanced_provider
119
+ else
120
+ local availability_msg = is_available_ok and " provider reports not available" or " error checking availability"
121
+ logger .warn (
122
+ " terminal" ,
123
+ " Custom table provider configured but " .. availability_msg .. " . Falling back to 'native'."
124
+ )
125
+ end
126
+ else
127
+ logger .warn (" terminal" , " Invalid custom table provider: " .. error_msg .. " . Falling back to 'native'." )
128
+ end
129
+ -- Fall through to native provider
130
+ elseif config .provider == " auto" then
56
131
-- Try snacks first, then fallback to native silently
57
132
local snacks_provider = load_provider (" snacks" )
58
133
if snacks_provider and snacks_provider .is_available () then
@@ -69,8 +144,13 @@ local function get_provider()
69
144
elseif config .provider == " native" then
70
145
-- noop, will use native provider as default below
71
146
logger .debug (" terminal" , " Using native terminal provider" )
72
- else
147
+ elseif type ( config . provider ) == " string " then
73
148
logger .warn (" terminal" , " Invalid provider configured: " .. tostring (config .provider ) .. " . Defaulting to 'native'." )
149
+ else
150
+ logger .warn (
151
+ " terminal" ,
152
+ " Invalid provider type: " .. type (config .provider ) .. " . Must be string or table. Defaulting to 'native'."
153
+ )
74
154
end
75
155
76
156
local native_provider = load_provider (" native" )
188
268
-- @param user_term_config table (optional) Configuration options for the terminal.
189
269
-- @field user_term_config.split_side string 'left' or 'right' (default: 'right').
190
270
-- @field user_term_config.split_width_percentage number Percentage of screen width (0.0 to 1.0, default: 0.30).
191
- -- @field user_term_config.provider string ' snacks' or 'native' (default: 'snacks ').
271
+ -- @field user_term_config.provider string|table 'auto', ' snacks', 'native', or custom provider table (default: 'auto ').
192
272
-- @field user_term_config.show_native_term_exit_tip boolean Show tip for exiting native terminal (default: true).
193
273
-- @field user_term_config.snacks_win_opts table Opts to pass to `Snacks.terminal.open()` (default: {}).
194
274
-- @param p_terminal_cmd string|nil The command to run in the terminal (from main config).
@@ -227,7 +307,7 @@ function M.setup(user_term_config, p_terminal_cmd, p_env)
227
307
config [k ] = v
228
308
elseif k == " split_width_percentage" and type (v ) == " number" and v > 0 and v < 1 then
229
309
config [k ] = v
230
- elseif k == " provider" and (v == " snacks" or v == " native" ) then
310
+ elseif k == " provider" and (v == " snacks" or v == " native" or v == " auto " or type ( v ) == " table " ) then
231
311
config [k ] = v
232
312
elseif k == " show_native_term_exit_tip" and type (v ) == " boolean" then
233
313
config [k ] = v
@@ -314,11 +394,11 @@ end
314
394
--- Gets the managed terminal instance for testing purposes.
315
395
-- NOTE: This function is intended for use in tests to inspect internal state.
316
396
-- The underscore prefix indicates it's not part of the public API for regular use.
317
- -- @return snacks.terminal |nil The managed Snacks terminal instance, or nil.
397
+ -- @return table |nil The managed terminal instance, or nil.
318
398
function M ._get_managed_terminal_for_test ()
319
- local snacks_provider = load_provider ( " snacks " )
320
- if snacks_provider and snacks_provider ._get_terminal_for_test then
321
- return snacks_provider ._get_terminal_for_test ()
399
+ local provider = get_provider ( )
400
+ if provider and provider ._get_terminal_for_test then
401
+ return provider ._get_terminal_for_test ()
322
402
end
323
403
return nil
324
404
end
0 commit comments