@@ -3,7 +3,10 @@ use std::path::PathBuf;
33use crate :: cli:: helpers:: {
44 first_defaulted_multiselect, generic_confirm, generic_input, generic_select, run_with_spinner,
55} ;
6- use idf_im_lib:: settings:: Settings ;
6+ use dialoguer:: theme:: ColorfulTheme ;
7+ use dialoguer:: MultiSelect ;
8+ use idf_im_lib:: idf_features:: FeatureInfo ;
9+ use idf_im_lib:: { idf_features:: RequirementsMetadata , settings:: Settings } ;
710use idf_im_lib:: system_dependencies;
811use log:: { debug, info} ;
912use rust_i18n:: t;
@@ -264,3 +267,192 @@ pub fn save_config_if_desired(config: &Settings) -> Result<(), String> {
264267 }
265268 Ok ( ( ) )
266269}
270+
271+ /// Select features from requirements metadata with interactive or non-interactive mode
272+ ///
273+ /// # Arguments
274+ /// * `metadata` - The requirements metadata containing available features
275+ /// * `non_interactive` - If true, returns all required features by default
276+ /// * `include_optional` - If true, allows selection of optional features (interactive mode only)
277+ ///
278+ /// # Returns
279+ /// * `Ok(Vec<FeatureInfo>)` - Selected features
280+ /// * `Err(String)` - Error message
281+ pub fn select_features (
282+ metadata : & RequirementsMetadata ,
283+ non_interactive : bool ,
284+ include_optional : bool ,
285+ ) -> Result < Vec < FeatureInfo > , String > {
286+ if non_interactive {
287+ // Non-interactive mode: return all required features
288+ println ! ( "Non-interactive mode: selecting all required features by default" ) ;
289+ let required = metadata
290+ . required_features ( )
291+ . into_iter ( )
292+ . cloned ( )
293+ . collect ( ) ;
294+ Ok ( required)
295+ } else {
296+ // Interactive mode: let user select features
297+ select_features_interactive ( metadata, include_optional)
298+ }
299+ }
300+
301+ /// Interactive feature selection with multi-select dialog
302+ fn select_features_interactive (
303+ metadata : & RequirementsMetadata ,
304+ include_optional : bool ,
305+ ) -> Result < Vec < FeatureInfo > , String > {
306+ let features_to_show: Vec < & FeatureInfo > = if include_optional {
307+ metadata. features . iter ( ) . collect ( )
308+ } else {
309+ metadata. required_features ( )
310+ } ;
311+
312+ if features_to_show. is_empty ( ) {
313+ return Err ( "No features available for selection" . to_string ( ) ) ;
314+ }
315+
316+ // Create display strings for each feature
317+ let items: Vec < String > = features_to_show
318+ . iter ( )
319+ . map ( |f| {
320+ format ! (
321+ "{} {} - {}" ,
322+ if f. optional { "[ ]" } else { "[*]" } ,
323+ f. name,
324+ f. description. as_deref( ) . unwrap_or( "No description" )
325+ )
326+ } )
327+ . collect ( ) ;
328+
329+ // Pre-select all required features
330+ let defaults: Vec < bool > = features_to_show
331+ . iter ( )
332+ . map ( |f| !f. optional )
333+ . collect ( ) ;
334+
335+ // Show multi-select dialog
336+ let selections = MultiSelect :: with_theme ( & ColorfulTheme :: default ( ) )
337+ . with_prompt ( "Select ESP-IDF features to install (Space to toggle, Enter to confirm)" )
338+ . items ( & items)
339+ . defaults ( & defaults)
340+ . interact ( )
341+ . map_err ( |e| format ! ( "Selection failed: {}" , e) ) ?;
342+
343+ if selections. is_empty ( ) {
344+ return Err ( "No features selected. At least one feature must be selected." . to_string ( ) ) ;
345+ }
346+
347+ // Return selected features
348+ let selected_features: Vec < FeatureInfo > = selections
349+ . into_iter ( )
350+ . map ( |idx| features_to_show[ idx] . clone ( ) )
351+ . collect ( ) ;
352+
353+ Ok ( selected_features)
354+ }
355+
356+ /// Select features and return their names only
357+ pub fn select_feature_names (
358+ metadata : & RequirementsMetadata ,
359+ non_interactive : bool ,
360+ include_optional : bool ,
361+ ) -> Result < Vec < String > , String > {
362+ let features = select_features ( metadata, non_interactive, include_optional) ?;
363+ Ok ( features. into_iter ( ) . map ( |f| f. name ) . collect ( ) )
364+ }
365+
366+ /// Select features and return their requirement paths
367+ pub fn select_requirement_paths (
368+ metadata : & RequirementsMetadata ,
369+ non_interactive : bool ,
370+ include_optional : bool ,
371+ ) -> Result < Vec < String > , String > {
372+ let features = select_features ( metadata, non_interactive, include_optional) ?;
373+ Ok ( features. into_iter ( ) . map ( |f| f. requirement_path ) . collect ( ) )
374+ }
375+
376+ /// Advanced selection: filter by specific criteria
377+ pub struct FeatureSelectionOptions {
378+ pub non_interactive : bool ,
379+ pub include_optional : bool ,
380+ pub show_only_optional : bool ,
381+ pub filter_by_name : Option < Vec < String > > ,
382+ }
383+
384+ impl Default for FeatureSelectionOptions {
385+ fn default ( ) -> Self {
386+ Self {
387+ non_interactive : false ,
388+ include_optional : true ,
389+ show_only_optional : false ,
390+ filter_by_name : None ,
391+ }
392+ }
393+ }
394+
395+ /// Advanced feature selection with filtering options
396+ pub fn select_features_advanced (
397+ metadata : & RequirementsMetadata ,
398+ options : FeatureSelectionOptions ,
399+ ) -> Result < Vec < FeatureInfo > , String > {
400+ // Apply filters
401+ let mut filtered_features: Vec < & FeatureInfo > = metadata. features . iter ( ) . collect ( ) ;
402+
403+ // Filter by optional/required
404+ if options. show_only_optional {
405+ filtered_features. retain ( |f| f. optional ) ;
406+ } else if !options. include_optional {
407+ filtered_features. retain ( |f| !f. optional ) ;
408+ }
409+
410+ // Filter by name if specified
411+ if let Some ( ref names) = options. filter_by_name {
412+ filtered_features. retain ( |f| names. contains ( & f. name ) ) ;
413+ }
414+
415+ if filtered_features. is_empty ( ) {
416+ return Err ( "No features match the specified criteria" . to_string ( ) ) ;
417+ }
418+
419+ if options. non_interactive {
420+ // Return all filtered features in non-interactive mode
421+ println ! (
422+ "Non-interactive mode: selecting {} filtered feature(s)" ,
423+ filtered_features. len( )
424+ ) ;
425+ Ok ( filtered_features. into_iter ( ) . cloned ( ) . collect ( ) )
426+ } else {
427+ // Interactive selection from filtered features
428+ let items: Vec < String > = filtered_features
429+ . iter ( )
430+ . map ( |f| {
431+ format ! (
432+ "{} {} - {}" ,
433+ if f. optional { "[ ]" } else { "[*]" } ,
434+ f. name,
435+ f. description. as_deref( ) . unwrap_or( "No description" )
436+ )
437+ } )
438+ . collect ( ) ;
439+
440+ let defaults: Vec < bool > = filtered_features. iter ( ) . map ( |f| !f. optional ) . collect ( ) ;
441+
442+ let selections = MultiSelect :: with_theme ( & ColorfulTheme :: default ( ) )
443+ . with_prompt ( "Select ESP-IDF features (Space to toggle, Enter to confirm)" )
444+ . items ( & items)
445+ . defaults ( & defaults)
446+ . interact ( )
447+ . map_err ( |e| format ! ( "Selection failed: {}" , e) ) ?;
448+
449+ if selections. is_empty ( ) {
450+ return Err ( "No features selected" . to_string ( ) ) ;
451+ }
452+
453+ Ok ( selections
454+ . into_iter ( )
455+ . map ( |idx| filtered_features[ idx] . clone ( ) )
456+ . collect ( ) )
457+ }
458+ }
0 commit comments