@@ -46,13 +46,88 @@ local function load_provider(provider_name)
4646 return providers [provider_name ]
4747end
4848
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+
49104--- Gets the effective terminal provider, guaranteed to return a valid provider
50105--- Falls back to native provider if configured provider is unavailable
51106--- @return TerminalProvider provider The terminal provider module (never nil )
52107local function get_provider ()
53108 local logger = require (" claudecode.logger" )
54109
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
56131 -- Try snacks first, then fallback to native silently
57132 local snacks_provider = load_provider (" snacks" )
58133 if snacks_provider and snacks_provider .is_available () then
@@ -69,8 +144,13 @@ local function get_provider()
69144 elseif config .provider == " native" then
70145 -- noop, will use native provider as default below
71146 logger .debug (" terminal" , " Using native terminal provider" )
72- else
147+ elseif type ( config . provider ) == " string " then
73148 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+ )
74154 end
75155
76156 local native_provider = load_provider (" native" )
188268-- @param user_term_config table (optional) Configuration options for the terminal.
189269-- @field user_term_config.split_side string 'left' or 'right' (default: 'right').
190270-- @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 ').
192272-- @field user_term_config.show_native_term_exit_tip boolean Show tip for exiting native terminal (default: true).
193273-- @field user_term_config.snacks_win_opts table Opts to pass to `Snacks.terminal.open()` (default: {}).
194274-- @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)
227307 config [k ] = v
228308 elseif k == " split_width_percentage" and type (v ) == " number" and v > 0 and v < 1 then
229309 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
231311 config [k ] = v
232312 elseif k == " show_native_term_exit_tip" and type (v ) == " boolean" then
233313 config [k ] = v
@@ -314,11 +394,11 @@ end
314394--- Gets the managed terminal instance for testing purposes.
315395-- NOTE: This function is intended for use in tests to inspect internal state.
316396-- 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.
318398function 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 ()
322402 end
323403 return nil
324404end
0 commit comments