Skip to main content

cc_switch/interactive/
interactive.rs

1use crate::cli::display_utils::{
2    TextAlignment, format_token_for_display, get_terminal_width, pad_text_to_width,
3    text_display_width,
4};
5use crate::config::EnvironmentConfig;
6use crate::config::types::{ConfigStorage, Configuration};
7use anyhow::{Context, Result};
8use colored::*;
9use crossterm::{
10    event::{self, Event, KeyCode, KeyEvent, KeyEventKind},
11    execute, terminal,
12};
13use std::io::{self, Write};
14use std::process::Command;
15
16/// Calculate display width of a character
17/// Returns 2 for wide characters (CJK), 1 for others
18pub(crate) fn char_display_width(c: char) -> usize {
19    match c as u32 {
20        0x00..=0x7F => 1,
21        0x80..=0x2FF => 1,
22        0x2190..=0x21FF => 2,
23        0x3000..=0x303F => 2,
24        0x3040..=0x309F => 2,
25        0x30A0..=0x30FF => 2,
26        0x4E00..=0x9FFF => 2,
27        0xAC00..=0xD7AF => 2,
28        0x3400..=0x4DBF => 2,
29        0xFF01..=0xFF60 => 2,
30        _ => 1,
31    }
32}
33
34/// Truncate text to fit within available width, considering character display width
35pub(crate) fn truncate_text_to_width(text: &str, available_width: usize) -> (String, usize) {
36    let mut current_width = 0;
37    let truncated: String = text
38        .chars()
39        .take_while(|&c| {
40            let char_width = char_display_width(c);
41            if current_width + char_width <= available_width {
42                current_width += char_width;
43                true
44            } else {
45                false
46            }
47        })
48        .collect();
49    let truncated_width = text_display_width(&truncated);
50    (truncated, truncated_width)
51}
52
53/// Clean up terminal state by leaving alternate screen and disabling raw mode
54pub(crate) fn cleanup_terminal(stdout: &mut io::Stdout) {
55    let _ = execute!(stdout, terminal::LeaveAlternateScreen);
56    let _ = terminal::disable_raw_mode();
57}
58
59/// Border drawing utilities for terminal compatibility
60pub(crate) struct BorderDrawing {
61    /// Check if terminal supports Unicode box drawing characters
62    pub unicode_supported: bool,
63}
64
65impl BorderDrawing {
66    /// Create new border drawing utility
67    pub(crate) fn new() -> Self {
68        let unicode_supported = Self::detect_unicode_support();
69        Self { unicode_supported }
70    }
71
72    /// Detect if terminal supports Unicode characters
73    fn detect_unicode_support() -> bool {
74        // Check environment variables that indicate Unicode support
75        if let Ok(term) = std::env::var("TERM") {
76            // Modern terminals that support Unicode
77            if term.contains("xterm") || term.contains("screen") || term == "tmux-256color" {
78                return true;
79            }
80        }
81
82        // Check locale settings
83        if let Ok(lang) = std::env::var("LANG")
84            && (lang.contains("UTF-8") || lang.contains("utf8"))
85        {
86            return true;
87        }
88
89        // Conservative fallback - assume Unicode is supported for better UX
90        // If issues arise, ASCII fallback will be manually triggered
91        true
92    }
93
94    /// Draw top border with title
95    pub(crate) fn draw_top_border(&self, title: &str, width: usize) -> String {
96        if self.unicode_supported {
97            let title_padded = format!(" {title} ");
98            let title_len = text_display_width(&title_padded);
99
100            if title_len >= width.saturating_sub(2) {
101                // Title too long, use simple border
102                format!("╔{}╗", "═".repeat(width.saturating_sub(2)))
103            } else {
104                let inner_width = width.saturating_sub(2); // Total width minus borders
105                let padding_total = inner_width.saturating_sub(title_len);
106                let padding_left = padding_total / 2;
107                let padding_right = padding_total - padding_left;
108                format!(
109                    "╔{}{}{}╗",
110                    "═".repeat(padding_left),
111                    title_padded,
112                    "═".repeat(padding_right)
113                )
114            }
115        } else {
116            // ASCII fallback
117            let title_padded = format!(" {title} ");
118            let title_len = title_padded.len();
119
120            if title_len >= width.saturating_sub(2) {
121                format!("+{}+", "-".repeat(width.saturating_sub(2)))
122            } else {
123                let inner_width = width.saturating_sub(2);
124                let padding_total = inner_width.saturating_sub(title_len);
125                let padding_left = padding_total / 2;
126                let padding_right = padding_total - padding_left;
127                format!(
128                    "+{}{}{}+",
129                    "-".repeat(padding_left),
130                    title_padded,
131                    "-".repeat(padding_right)
132                )
133            }
134        }
135    }
136
137    /// Draw middle border line with text
138    pub(crate) fn draw_middle_line(&self, text: &str, width: usize) -> String {
139        let text_len = text_display_width(text);
140        // Account for borders: "║ " (1+1) + " ║" (1+1) = 4 characters
141        let available_width = width.saturating_sub(4);
142
143        let (left_border, right_border) = if self.unicode_supported {
144            ("║", "║")
145        } else {
146            ("|", "|")
147        };
148
149        if text_len > available_width {
150            // Truncate text to fit within available width, considering display width
151            let (truncated, truncated_width) = truncate_text_to_width(text, available_width);
152            let padding_spaces = available_width.saturating_sub(truncated_width);
153            format!(
154                "{left_border} {}{} {right_border}",
155                truncated,
156                " ".repeat(padding_spaces)
157            )
158        } else {
159            let padded_text = pad_text_to_width(text, available_width, TextAlignment::Left, ' ');
160            format!("{left_border} {padded_text} {right_border}")
161        }
162    }
163
164    /// Draw bottom border
165    pub(crate) fn draw_bottom_border(&self, width: usize) -> String {
166        if self.unicode_supported {
167            format!("╚{}╝", "═".repeat(width - 2))
168        } else {
169            format!("+{}+", "-".repeat(width - 2))
170        }
171    }
172}
173
174/// Handle interactive current command
175///
176/// Provides interactive menu for:
177/// 1. Execute claude --dangerously-skip-permissions
178/// 2. Switch configuration (lists available aliases)
179/// 3. Exit
180///
181/// # Errors
182/// Returns error if file operations fail or user input fails
183pub fn handle_current_command() -> Result<()> {
184    let storage = ConfigStorage::load()?;
185
186    println!("\n{}", "Current Configuration:".green().bold());
187    println!("Environment variable mode: configurations are set per-command execution");
188    println!("Select a configuration from the menu below to launch Claude");
189    println!("Select 'cc' to launch Claude with default settings");
190
191    // Try to enable interactive menu with keyboard navigation
192    let raw_mode_enabled = terminal::enable_raw_mode().is_ok();
193
194    if raw_mode_enabled {
195        let mut stdout = io::stdout();
196        if execute!(
197            stdout,
198            terminal::EnterAlternateScreen,
199            terminal::Clear(terminal::ClearType::All)
200        )
201        .is_ok()
202        {
203            // Full interactive mode with arrow keys for main menu
204            let result = handle_main_menu_interactive(&mut stdout, &storage);
205
206            // Always restore terminal
207            let _ = execute!(stdout, terminal::LeaveAlternateScreen);
208            let _ = terminal::disable_raw_mode();
209
210            return result;
211        } else {
212            // Fallback to simple mode
213            let _ = terminal::disable_raw_mode();
214        }
215    }
216
217    // Fallback to simple numbered menu
218    handle_main_menu_simple(&storage)
219}
220
221/// Handle main menu with keyboard navigation
222fn handle_main_menu_interactive(stdout: &mut io::Stdout, storage: &ConfigStorage) -> Result<()> {
223    let menu_items = [
224        "Execute claude --dangerously-skip-permissions",
225        "Switch configuration",
226        "Exit",
227    ];
228    let mut selected_index = 0;
229
230    loop {
231        // Clear screen and redraw
232        execute!(stdout, terminal::Clear(terminal::ClearType::All))?;
233        execute!(stdout, crossterm::cursor::MoveTo(0, 0))?;
234
235        // Header - use BorderDrawing for compatibility
236        let border = BorderDrawing::new();
237        const MAIN_MENU_WIDTH: usize = 68;
238
239        println!(
240            "\r{}",
241            border.draw_top_border("Main Menu", MAIN_MENU_WIDTH).green()
242        );
243        println!(
244            "\r{}",
245            border
246                .draw_middle_line(
247                    "↑↓/jk导航,1-9快选,E-编辑,R-官方,Q-退出,Enter确认,Esc取消",
248                    MAIN_MENU_WIDTH
249                )
250                .green()
251        );
252        println!("\r{}", border.draw_bottom_border(MAIN_MENU_WIDTH).green());
253        println!();
254
255        // Draw menu items
256        for (index, item) in menu_items.iter().enumerate() {
257            if index == selected_index {
258                println!("\r> {} {}", "●".blue().bold(), item.blue().bold());
259            } else {
260                println!("\r  {} {}", "○".dimmed(), item.dimmed());
261            }
262        }
263
264        // Ensure output is flushed
265        stdout.flush()?;
266
267        // Handle input with error recovery
268        let event = match event::read() {
269            Ok(event) => event,
270            Err(e) => {
271                // Clean up terminal state on input error
272                cleanup_terminal(stdout);
273                return Err(e.into());
274            }
275        };
276
277        match event {
278            Event::Key(KeyEvent {
279                code,
280                kind: KeyEventKind::Press,
281                ..
282            }) => {
283                match code {
284                    KeyCode::Up => {
285                        selected_index = selected_index.saturating_sub(1);
286                    }
287                    KeyCode::Down if selected_index < menu_items.len() - 1 => {
288                        selected_index += 1;
289                    }
290                    KeyCode::Down => {}
291                    KeyCode::Enter => {
292                        // Execute terminal cleanup here
293                        cleanup_terminal(stdout);
294
295                        return handle_main_menu_action(selected_index, storage);
296                    }
297                    KeyCode::Esc => {
298                        // Clean up terminal before exit
299                        cleanup_terminal(stdout);
300
301                        println!("\nExiting...");
302                        return Ok(());
303                    }
304                    _ => {}
305                }
306            }
307            Event::Key(_) => {} // Ignore key release events
308            _ => {}
309        }
310    }
311}
312
313/// Handle main menu simple fallback
314fn handle_main_menu_simple(storage: &ConfigStorage) -> Result<()> {
315    loop {
316        println!("\n{}", "Available Actions:".blue().bold());
317        println!("1. Execute claude --dangerously-skip-permissions");
318        println!("2. Switch configuration");
319        println!("3. Exit");
320
321        print!("\nPlease select an option (1-3): ");
322        io::stdout().flush().context("Failed to flush stdout")?;
323
324        let mut input = String::new();
325        io::stdin()
326            .read_line(&mut input)
327            .context("Failed to read input")?;
328
329        let choice = input.trim();
330
331        match choice {
332            "1" => return handle_main_menu_action(0, storage),
333            "2" => return handle_main_menu_action(1, storage),
334            "3" => return handle_main_menu_action(2, storage),
335            _ => {
336                println!("Invalid option. Please select 1-3.");
337            }
338        }
339    }
340}
341
342/// Handle main menu action based on selected index
343fn handle_main_menu_action(selected_index: usize, storage: &ConfigStorage) -> Result<()> {
344    match selected_index {
345        0 => {
346            println!("\nExecuting: claude --dangerously-skip-permissions");
347            execute_claude_command(true)?;
348        }
349        1 => {
350            // Use the interactive selection instead of simple menu
351            handle_interactive_selection(storage)?;
352        }
353        2 => {
354            println!("Exiting...");
355        }
356        _ => {
357            println!("Invalid selection");
358        }
359    }
360    Ok(())
361}
362
363/// Handle interactive configuration selection with real-time preview
364///
365/// # Arguments
366/// * `storage` - Reference to configuration storage
367///
368/// # Errors
369/// Returns error if terminal operations fail or user selection fails
370pub fn handle_interactive_selection(storage: &ConfigStorage) -> Result<()> {
371    if storage.configurations.is_empty() {
372        println!("No configurations available. Use 'add' command to create configurations first.");
373        return Ok(());
374    }
375
376    let mut configs: Vec<Configuration> = storage.configurations.values().cloned().collect();
377    configs.sort_by(|a, b| a.alias_name.cmp(&b.alias_name));
378
379    let mut selected_index = 0;
380
381    // Try to enable raw mode, fallback to simple menu if it fails
382    let raw_mode_enabled = terminal::enable_raw_mode().is_ok();
383
384    if raw_mode_enabled {
385        let mut stdout = io::stdout();
386        if execute!(
387            stdout,
388            terminal::EnterAlternateScreen,
389            terminal::Clear(terminal::ClearType::All)
390        )
391        .is_ok()
392        {
393            // Full interactive mode with arrow keys
394            let storage_mode = storage.default_storage_mode.clone().unwrap_or_default();
395            let result = handle_full_interactive_menu(
396                &mut stdout,
397                &mut configs,
398                &mut selected_index,
399                storage,
400                storage_mode,
401            );
402
403            // Always restore terminal
404            let _ = execute!(stdout, terminal::LeaveAlternateScreen);
405            let _ = terminal::disable_raw_mode();
406
407            return result;
408        } else {
409            // Fallback to simple mode
410            let _ = terminal::disable_raw_mode();
411        }
412    }
413
414    // Fallback to simple numbered menu
415    handle_simple_interactive_menu(&configs.iter().collect::<Vec<_>>(), storage)
416}
417
418/// Handle full interactive menu with arrow key navigation and pagination
419fn handle_full_interactive_menu(
420    stdout: &mut io::Stdout,
421    configs: &mut Vec<Configuration>,
422    selected_index: &mut usize,
423    storage: &ConfigStorage,
424    storage_mode: crate::config::types::StorageMode,
425) -> Result<()> {
426    // Handle empty configuration list
427    if configs.is_empty() {
428        println!("\r{}", "No configurations available".yellow());
429        println!(
430            "\r{}",
431            "Use 'cc-switch add <alias> <token> <url>' to add configurations first.".dimmed()
432        );
433        println!("\r{}", "Press any key to continue...".dimmed());
434        let _ = event::read(); // Wait for user input
435        return Ok(());
436    }
437
438    const PAGE_SIZE: usize = 9; // Maximum 9 configs per page
439
440    // Calculate pagination info
441    let total_pages = if configs.len() <= PAGE_SIZE {
442        1
443    } else {
444        configs.len().div_ceil(PAGE_SIZE)
445    };
446    let mut current_page = 0;
447
448    loop {
449        // Calculate current page config range
450        let start_idx = current_page * PAGE_SIZE;
451        let end_idx = std::cmp::min(start_idx + PAGE_SIZE, configs.len());
452        let page_configs = &configs[start_idx..end_idx];
453
454        // Clear screen and redraw
455        execute!(stdout, terminal::Clear(terminal::ClearType::All))?;
456        execute!(stdout, crossterm::cursor::MoveTo(0, 0))?;
457
458        // Header with pagination info - use BorderDrawing for compatibility
459        let border = BorderDrawing::new();
460        // Width needs to accommodate: ║ (1) + space (1) + text (76) + space (1) + ║ (1) = 80
461        // Text width includes arrows (↑↓) and Chinese characters counted as 2 columns each
462        const CONFIG_MENU_WIDTH: usize = 80;
463
464        println!(
465            "\r{}",
466            border
467                .draw_top_border("Select Configuration", CONFIG_MENU_WIDTH)
468                .green()
469        );
470        if total_pages > 1 {
471            println!(
472                "\r{}",
473                border
474                    .draw_middle_line(
475                        &format!("第 {} 页,共 {} 页", current_page + 1, total_pages),
476                        CONFIG_MENU_WIDTH
477                    )
478                    .green()
479            );
480            println!(
481                "\r{}",
482                border
483                    .draw_middle_line(
484                        "↑↓/jk导航,1-9快选,E-编辑,N/P翻页,R-官方,Q-退出,Enter确认",
485                        CONFIG_MENU_WIDTH
486                    )
487                    .green()
488            );
489        } else {
490            println!(
491                "\r{}",
492                border
493                    .draw_middle_line(
494                        "↑↓/jk导航,1-9快选,E-编辑,R-官方,Q-退出,Enter确认,Esc取消",
495                        CONFIG_MENU_WIDTH
496                    )
497                    .green()
498            );
499        }
500        println!("\r{}", border.draw_bottom_border(CONFIG_MENU_WIDTH).green());
501        println!();
502
503        // Add official option (always visible, always red)
504        let official_index = 0;
505        if *selected_index == official_index {
506            println!(
507                "\r> {} {} {}",
508                "●".red().bold(),
509                "[R]".red().bold(),
510                "official".red().bold()
511            );
512            println!("\r    Use official Claude API (no custom configuration)");
513            println!();
514        } else {
515            println!("\r  {} {} {}", "○".red(), "[R]".red(), "official".red());
516        }
517
518        // Draw current page configs with proper numbering
519        for (page_index, config) in page_configs.iter().enumerate() {
520            let actual_config_index = start_idx + page_index;
521            let display_number = page_index + 1; // Numbers 1-9 for current page
522            let actual_index = actual_config_index + 1; // +1 because official is at index 0
523            let number_label = format!("[{display_number}]");
524
525            if *selected_index == actual_index {
526                println!(
527                    "\r> {} {} {}",
528                    "●".blue().bold(),
529                    number_label.blue().bold(),
530                    config.alias_name.blue().bold()
531                );
532
533                // Show details with improved formatting and alignment
534                let details = format_config_details(config, "\r    ", false);
535                for detail_line in details {
536                    println!("{detail_line}");
537                }
538                println!();
539            } else {
540                println!(
541                    "\r  {} {} {}",
542                    "○".dimmed(),
543                    number_label.dimmed(),
544                    config.alias_name.dimmed()
545                );
546            }
547        }
548
549        // Add exit option (always visible)
550        let exit_index = configs.len() + 1;
551        if *selected_index == exit_index {
552            println!(
553                "\r> {} {} {}",
554                "●".yellow().bold(),
555                "[Q]".yellow().bold(),
556                "Exit".yellow().bold()
557            );
558            println!("\r    Exit without making changes");
559            println!();
560        } else {
561            println!(
562                "\r  {} {} {}",
563                "○".dimmed(),
564                "[Q]".dimmed(),
565                "Exit".dimmed()
566            );
567        }
568
569        // Show pagination help if needed
570        if total_pages > 1 {
571            println!(
572                "\r{}",
573                format!(
574                    "Page Navigation: [N]ext, [P]revious (第 {} 页,共 {} 页)",
575                    current_page + 1,
576                    total_pages
577                )
578                .dimmed()
579            );
580        }
581
582        // Ensure output is flushed
583        stdout.flush()?;
584
585        // Handle input with error recovery
586        let event = match event::read() {
587            Ok(event) => event,
588            Err(e) => {
589                // Clean up terminal state on input error
590                cleanup_terminal(stdout);
591                return Err(e.into());
592            }
593        };
594
595        match event {
596            Event::Key(KeyEvent {
597                code,
598                kind: KeyEventKind::Press,
599                ..
600            }) => match code {
601                KeyCode::Up | KeyCode::Char('k') | KeyCode::Char('K') => {
602                    *selected_index = selected_index.saturating_sub(1);
603                }
604                KeyCode::Down | KeyCode::Char('j') | KeyCode::Char('J')
605                    if *selected_index < configs.len() + 1 =>
606                {
607                    *selected_index += 1;
608                }
609                KeyCode::Down | KeyCode::Char('j') | KeyCode::Char('J') => {}
610                KeyCode::PageDown | KeyCode::Char('n') | KeyCode::Char('N')
611                    if total_pages > 1 && current_page < total_pages - 1 =>
612                {
613                    current_page += 1;
614                    let new_page_start_idx = current_page * PAGE_SIZE;
615                    *selected_index = new_page_start_idx + 1;
616                }
617                KeyCode::PageDown | KeyCode::Char('n') | KeyCode::Char('N') => {}
618                KeyCode::PageUp | KeyCode::Char('p') | KeyCode::Char('P')
619                    if total_pages > 1 && current_page > 0 =>
620                {
621                    current_page -= 1;
622                    let new_page_start_idx = current_page * PAGE_SIZE;
623                    *selected_index = new_page_start_idx + 1;
624                }
625                KeyCode::PageUp | KeyCode::Char('p') | KeyCode::Char('P') => {}
626                KeyCode::Enter => {
627                    // Clean up terminal before processing selection
628                    cleanup_terminal(stdout);
629
630                    return handle_selection_action(
631                        &configs.iter().collect::<Vec<_>>(),
632                        *selected_index,
633                        storage,
634                        storage_mode,
635                    );
636                }
637                KeyCode::Esc => {
638                    // Clean up terminal before exit
639                    cleanup_terminal(stdout);
640
641                    println!("\nSelection cancelled");
642                    return Ok(());
643                }
644                KeyCode::Char(c) if c.is_ascii_digit() => {
645                    let digit = c.to_digit(10).unwrap() as usize;
646                    // Map digit to current page config
647                    if digit >= 1 && digit <= page_configs.len() {
648                        let actual_config_index = start_idx + (digit - 1);
649                        let selection_index = actual_config_index + 1; // +1 because official is at index 0
650
651                        // Clean up terminal before processing selection
652                        cleanup_terminal(stdout);
653
654                        return handle_selection_action(
655                            &configs.iter().collect::<Vec<_>>(),
656                            selection_index,
657                            storage,
658                            storage_mode,
659                        );
660                    }
661                    // Invalid digit - ignore silently
662                }
663                KeyCode::Char('r') | KeyCode::Char('R') => {
664                    // Clean up terminal before processing selection
665                    cleanup_terminal(stdout);
666
667                    return handle_selection_action(
668                        &configs.iter().collect::<Vec<_>>(),
669                        0,
670                        storage,
671                        storage_mode,
672                    );
673                }
674                KeyCode::Char('e') | KeyCode::Char('E')
675                    if *selected_index > 0 && *selected_index <= configs.len() =>
676                {
677                    cleanup_terminal(stdout);
678                    let config_index = *selected_index - 1;
679                    let edit_result = handle_config_edit(&configs[config_index]);
680                    if execute!(
681                        stdout,
682                        terminal::EnterAlternateScreen,
683                        terminal::Clear(terminal::ClearType::All)
684                    )
685                    .is_ok()
686                        && terminal::enable_raw_mode().is_ok()
687                    {
688                        match edit_result {
689                            Ok(_) => {
690                                if let Ok(reloaded_storage) = ConfigStorage::load() {
691                                    *configs =
692                                        reloaded_storage.configurations.values().cloned().collect();
693                                    configs.sort_by(|a, b| a.alias_name.cmp(&b.alias_name));
694                                    if *selected_index > configs.len() + 1 {
695                                        *selected_index = configs.len() + 1;
696                                    }
697                                }
698                                continue;
699                            }
700                            Err(e) => {
701                                if e.downcast_ref::<EditModeError>()
702                                    == Some(&EditModeError::ReturnToMenu)
703                                {
704                                    continue;
705                                }
706                                cleanup_terminal(stdout);
707                                return Err(e);
708                            }
709                        }
710                    }
711                }
712                KeyCode::Char('e') | KeyCode::Char('E') => {}
713                KeyCode::Char('q') | KeyCode::Char('Q') => {
714                    // Clean up terminal before processing selection
715                    cleanup_terminal(stdout);
716
717                    return handle_selection_action(
718                        &configs.iter().collect::<Vec<_>>(),
719                        configs.len() + 1,
720                        storage,
721                        storage_mode,
722                    );
723                }
724                _ => {}
725            },
726            Event::Key(_) => {} // Ignore key release events
727            _ => {}
728        }
729    }
730}
731
732/// Handle simple interactive menu (fallback)
733fn handle_simple_interactive_menu(
734    configs: &[&Configuration],
735    storage: &ConfigStorage,
736) -> Result<()> {
737    const PAGE_SIZE: usize = 9; // Same page size as full interactive menu
738
739    // If configs fit in one page, show the simple original menu
740    if configs.len() <= PAGE_SIZE {
741        return handle_simple_single_page_menu(configs, storage);
742    }
743
744    // Multi-page simple menu
745    let total_pages = configs.len().div_ceil(PAGE_SIZE);
746    let mut current_page = 0;
747
748    loop {
749        // Calculate current page config range
750        let start_idx = current_page * PAGE_SIZE;
751        let end_idx = std::cmp::min(start_idx + PAGE_SIZE, configs.len());
752        let page_configs = &configs[start_idx..end_idx];
753
754        println!("\n{}", "Available Configurations:".blue().bold());
755        if total_pages > 1 {
756            println!("第 {} 页,共 {} 页", current_page + 1, total_pages);
757            println!("使用 'n' 下一页, 'p' 上一页, 'r' 官方配置, 'q' 退出");
758        }
759        println!();
760
761        // Add official option (always available)
762        println!("{} {}", "[r]".red().bold(), "official".red());
763        println!("   Use official Claude API (no custom configuration)");
764        println!();
765
766        // Show current page configs with improved formatting
767        for (page_index, config) in page_configs.iter().enumerate() {
768            let display_number = page_index + 1;
769
770            println!(
771                "{}. {}",
772                format!("[{display_number}]").green().bold(),
773                config.alias_name.green()
774            );
775
776            // Show config details with consistent formatting
777            let details = format_config_details(config, "   ", true);
778            for detail_line in details {
779                println!("{detail_line}");
780            }
781            println!();
782        }
783
784        // Exit option
785        println!("{} {}", "[q]".yellow().bold(), "Exit".yellow());
786
787        if total_pages > 1 {
788            println!(
789                "\n页面导航: [n]下页, [p]上页 | 配置选择: [1-{}] | [e]编辑 | [r]官方 | [q]退出",
790                page_configs.len()
791            );
792        }
793
794        print!("\n请输入选择: ");
795        io::stdout().flush()?;
796
797        let mut input = String::new();
798        io::stdin().read_line(&mut input)?;
799        let choice = input.trim().to_lowercase();
800
801        match choice.as_str() {
802            "r" => {
803                // Official option
804                println!("Using official Claude configuration");
805
806                // Update settings.json to remove Anthropic configuration
807                let mut settings = crate::config::types::ClaudeSettings::load(
808                    storage.get_claude_settings_dir().map(|s| s.as_str()),
809                )?;
810                settings.remove_anthropic_env();
811                settings.save(storage.get_claude_settings_dir().map(|s| s.as_str()))?;
812
813                return launch_claude_with_env(EnvironmentConfig::empty(), None, None, false);
814            }
815            "e" => {
816                // Edit functionality for simple menu
817                // In simple menu, we don't have a selected config, so we can't edit
818                println!("编辑功能在交互式菜单中可用");
819            }
820            "q" => {
821                println!("Exiting...");
822                return Ok(());
823            }
824            "n" if total_pages > 1 && current_page < total_pages - 1 => {
825                current_page += 1;
826                continue;
827            }
828            "p" if total_pages > 1 && current_page > 0 => {
829                current_page -= 1;
830                continue;
831            }
832            digit_str => {
833                if let Ok(digit) = digit_str.parse::<usize>()
834                    && digit >= 1
835                    && digit <= page_configs.len()
836                {
837                    let actual_config_index = start_idx + (digit - 1);
838                    let selection_index = actual_config_index + 1; // +1 because official is at index 0
839                    let storage_mode = storage.default_storage_mode.clone().unwrap_or_default();
840                    return handle_selection_action(
841                        configs,
842                        selection_index,
843                        storage,
844                        storage_mode,
845                    );
846                }
847                println!("无效选择,请重新输入");
848            }
849        }
850    }
851}
852
853/// Handle simple single page menu (original behavior for ≤9 configs)
854fn handle_simple_single_page_menu(
855    configs: &[&Configuration],
856    storage: &ConfigStorage,
857) -> Result<()> {
858    println!("\n{}", "Available Configurations:".blue().bold());
859
860    // Add official option (first)
861    println!("1. {}", "official".red());
862    println!("   Use official Claude API (no custom configuration)");
863    println!();
864
865    for (index, config) in configs.iter().enumerate() {
866        println!(
867            "{}. {}",
868            index + 2, // +2 because official is at position 1
869            config.alias_name.green()
870        );
871
872        // Show config details with consistent formatting
873        let details = format_config_details(config, "   ", true);
874        for detail_line in details {
875            println!("{detail_line}");
876        }
877        println!();
878    }
879
880    println!("{}. {}", configs.len() + 2, "Exit".yellow());
881
882    print!("\nSelect configuration (1-{}): ", configs.len() + 2);
883    io::stdout().flush()?;
884
885    let mut input = String::new();
886    io::stdin().read_line(&mut input)?;
887
888    match input.trim().parse::<usize>() {
889        Ok(1) => {
890            // Official option
891            println!("Using official Claude configuration");
892
893            // Update settings.json to remove Anthropic configuration
894            let mut settings = crate::config::types::ClaudeSettings::load(
895                storage.get_claude_settings_dir().map(|s| s.as_str()),
896            )?;
897            settings.remove_anthropic_env();
898            settings.save(storage.get_claude_settings_dir().map(|s| s.as_str()))?;
899
900            launch_claude_with_env(EnvironmentConfig::empty(), None, None, false)
901        }
902        Ok(num) if num >= 2 && num <= configs.len() + 1 => {
903            let storage_mode = storage.default_storage_mode.clone().unwrap_or_default();
904            handle_selection_action(configs, num - 1, storage, storage_mode) // -1 to account for official option at index 0
905        }
906        Ok(num) if num == configs.len() + 2 => {
907            println!("Exiting...");
908            Ok(())
909        }
910        _ => {
911            println!("Invalid selection");
912            Ok(())
913        }
914    }
915}
916
917/// Handle the actual selection and configuration switch
918fn handle_selection_action(
919    configs: &[&Configuration],
920    selected_index: usize,
921    storage: &ConfigStorage,
922    storage_mode: crate::config::types::StorageMode,
923) -> Result<()> {
924    if selected_index == 0 {
925        // Official option (reset to default)
926        println!("\nUsing official Claude configuration");
927
928        // Update settings.json to remove Anthropic configuration
929        let mut settings = crate::config::types::ClaudeSettings::load(
930            storage.get_claude_settings_dir().map(|s| s.as_str()),
931        )?;
932        settings.remove_anthropic_env();
933        settings.save(storage.get_claude_settings_dir().map(|s| s.as_str()))?;
934
935        launch_claude_with_env(EnvironmentConfig::empty(), None, None, false)
936    } else if selected_index <= configs.len() {
937        // Switch to selected configuration
938        let config_index = selected_index - 1; // -1 because official is at index 0
939        let selected_config = configs[config_index].clone();
940        let env_config = EnvironmentConfig::from_config(&selected_config);
941
942        println!(
943            "\nSwitched to configuration '{}'",
944            selected_config.alias_name.green().bold()
945        );
946
947        // Show selected configuration details with consistent formatting
948        let details = format_config_details(&selected_config, "", false);
949        for detail_line in details {
950            println!("{detail_line}");
951        }
952
953        // Update settings.json with the configuration
954        let mut settings = crate::config::types::ClaudeSettings::load(
955            storage.get_claude_settings_dir().map(|s| s.as_str()),
956        )?;
957        settings.switch_to_config_with_mode(
958            &selected_config,
959            storage_mode,
960            storage.get_claude_settings_dir().map(|s| s.as_str()),
961        )?;
962
963        launch_claude_with_env(env_config, None, None, false)
964    } else {
965        // Exit
966        println!("\nExiting...");
967        Ok(())
968    }
969}
970
971/// Launch Claude CLI with environment variables and exec to replace current process
972pub fn launch_claude_with_env(
973    env_config: EnvironmentConfig,
974    prompt: Option<&str>,
975    resume: Option<&str>,
976    continue_session: bool,
977) -> Result<()> {
978    println!("\nLaunching Claude CLI...");
979
980    // Set environment variables for current process
981    for (key, value) in env_config.as_env_tuples() {
982        unsafe {
983            std::env::set_var(&key, &value);
984        }
985    }
986
987    // On Unix systems, use exec to replace current process
988    #[cfg(unix)]
989    {
990        use std::os::unix::process::CommandExt;
991        let mut command = Command::new("claude");
992        command.arg("--dangerously-skip-permissions");
993        if let Some(session_id) = resume {
994            command.args(["--resume", session_id]);
995        }
996        if continue_session {
997            command.arg("--continue");
998        }
999        if let Some(p) = prompt {
1000            command.arg(p);
1001        }
1002        let error = command.exec();
1003        // exec never returns on success, so if we get here, it failed
1004        anyhow::bail!("Failed to exec claude: {}", error);
1005    }
1006
1007    // On non-Unix systems, fallback to spawn and wait
1008    #[cfg(not(unix))]
1009    {
1010        use std::process::Stdio;
1011        let mut command = Command::new("claude");
1012        command.arg("--dangerously-skip-permissions");
1013        if let Some(session_id) = resume {
1014            command.args(["--resume", session_id]);
1015        }
1016        if continue_session {
1017            command.arg("--continue");
1018        }
1019        if let Some(p) = prompt {
1020            command.arg(p);
1021        }
1022        command
1023            .stdin(Stdio::inherit())
1024            .stdout(Stdio::inherit())
1025            .stderr(Stdio::inherit());
1026
1027        let mut child = command.spawn().context(
1028            "Failed to launch Claude CLI. Make sure 'claude' command is available in PATH",
1029        )?;
1030
1031        let status = child.wait()?;
1032
1033        if !status.success() {
1034            anyhow::bail!("Claude CLI exited with error status: {}", status);
1035        }
1036    }
1037}
1038
1039/// Execute claude command with or without --dangerously-skip-permissions using exec
1040///
1041/// # Arguments
1042/// * `skip_permissions` - Whether to add --dangerously-skip-permissions flag
1043fn execute_claude_command(skip_permissions: bool) -> Result<()> {
1044    println!("Launching Claude CLI...");
1045
1046    // On Unix systems, use exec to replace current process
1047    #[cfg(unix)]
1048    {
1049        use std::os::unix::process::CommandExt;
1050        let mut command = Command::new("claude");
1051        if skip_permissions {
1052            command.arg("--dangerously-skip-permissions");
1053        }
1054
1055        let error = command.exec();
1056        // exec never returns on success, so if we get here, it failed
1057        anyhow::bail!("Failed to exec claude: {}", error);
1058    }
1059
1060    // On non-Unix systems, fallback to spawn and wait
1061    #[cfg(not(unix))]
1062    {
1063        use std::process::Stdio;
1064        let mut command = Command::new("claude");
1065        if skip_permissions {
1066            command.arg("--dangerously-skip-permissions");
1067        }
1068
1069        command
1070            .stdin(Stdio::inherit())
1071            .stdout(Stdio::inherit())
1072            .stderr(Stdio::inherit());
1073
1074        let mut child = command.spawn().context(
1075            "Failed to launch Claude CLI. Make sure 'claude' command is available in PATH",
1076        )?;
1077
1078        let status = child
1079            .wait()
1080            .context("Failed to wait for Claude CLI process")?;
1081
1082        if !status.success() {
1083            anyhow::bail!("Claude CLI exited with error status: {}", status);
1084        }
1085    }
1086}
1087
1088/// Read input from stdin with a prompt
1089///
1090/// # Arguments
1091/// * `prompt` - The prompt to display to the user
1092///
1093/// # Returns
1094/// The user's input as a String
1095pub fn read_input(prompt: &str) -> Result<String> {
1096    print!("{prompt}");
1097    io::stdout().flush().context("Failed to flush stdout")?;
1098    let mut input = String::new();
1099    io::stdin()
1100        .read_line(&mut input)
1101        .context("Failed to read input")?;
1102    Ok(input.trim().to_string())
1103}
1104
1105/// Read sensitive input (token) with a prompt (without echoing)
1106///
1107/// # Arguments
1108/// * `prompt` - The prompt to display to the user
1109///
1110/// # Returns
1111/// The user's input as a String
1112pub fn read_sensitive_input(prompt: &str) -> Result<String> {
1113    print!("{prompt}");
1114    io::stdout().flush().context("Failed to flush stdout")?;
1115    let mut input = String::new();
1116    io::stdin()
1117        .read_line(&mut input)
1118        .context("Failed to read input")?;
1119    Ok(input.trim().to_string())
1120}
1121
1122/// Format configuration details with consistent indentation and alignment
1123///
1124/// This function provides unified formatting for configuration display across
1125/// all interactive menus, ensuring consistent visual presentation.
1126///
1127/// # Arguments
1128/// * `config` - The configuration to format
1129/// * `indent` - Base indentation string (e.g., "    " or "   ")
1130/// * `compact` - Whether to use compact formatting (single line where possible)
1131///
1132/// # Returns  
1133/// Vector of formatted lines for configuration display
1134fn format_config_details(config: &Configuration, indent: &str, _compact: bool) -> Vec<String> {
1135    let mut lines = Vec::new();
1136
1137    // Calculate optimal field width for alignment
1138    let terminal_width = get_terminal_width();
1139    let _available_width = terminal_width.saturating_sub(text_display_width(indent) + 8);
1140
1141    // Field labels with consistent width for alignment
1142    let token_label = "Token:";
1143    let url_label = "URL:";
1144    let model_label = "Model:";
1145    let small_model_label = "Small Fast Model:";
1146    let max_thinking_tokens_label = "Max Thinking Tokens:";
1147    let api_timeout_ms_label = "API Timeout (ms):";
1148    let disable_nonessential_traffic_label = "Disable Nonessential Traffic:";
1149    let default_sonnet_model_label = "Default Sonnet Model:";
1150    let default_opus_model_label = "Default Opus Model:";
1151    let default_haiku_model_label = "Default Haiku Model:";
1152    let subagent_model_label = "Subagent Model:";
1153    let disable_nonstreaming_fallback_label = "Disable Nonstreaming Fallback:";
1154    let effort_level_label = "Effort Level:";
1155
1156    // Find the widest label for alignment
1157    let max_label_width = [
1158        token_label,
1159        url_label,
1160        model_label,
1161        small_model_label,
1162        max_thinking_tokens_label,
1163        api_timeout_ms_label,
1164        disable_nonessential_traffic_label,
1165        default_sonnet_model_label,
1166        default_opus_model_label,
1167        default_haiku_model_label,
1168        subagent_model_label,
1169        disable_nonstreaming_fallback_label,
1170        effort_level_label,
1171    ]
1172    .iter()
1173    .map(|label| text_display_width(label))
1174    .max()
1175    .unwrap_or(0);
1176
1177    // Format token with proper alignment
1178    let token_line = format!(
1179        "{}{} {}",
1180        indent,
1181        pad_text_to_width(token_label, max_label_width, TextAlignment::Left, ' '),
1182        format_token_for_display(&config.token).dimmed()
1183    );
1184    lines.push(token_line);
1185
1186    // Format URL with proper alignment
1187    let url_line = format!(
1188        "{}{} {}",
1189        indent,
1190        pad_text_to_width(url_label, max_label_width, TextAlignment::Left, ' '),
1191        config.url.cyan()
1192    );
1193    lines.push(url_line);
1194
1195    // Format model information if available
1196    if let Some(model) = &config.model {
1197        let model_line = format!(
1198            "{}{} {}",
1199            indent,
1200            pad_text_to_width(model_label, max_label_width, TextAlignment::Left, ' '),
1201            model.yellow()
1202        );
1203        lines.push(model_line);
1204    }
1205
1206    // Format small fast model if available
1207    if let Some(small_fast_model) = &config.small_fast_model {
1208        let small_model_line = format!(
1209            "{}{} {}",
1210            indent,
1211            pad_text_to_width(small_model_label, max_label_width, TextAlignment::Left, ' '),
1212            small_fast_model.yellow()
1213        );
1214        lines.push(small_model_line);
1215    }
1216
1217    // Format max thinking tokens if available
1218    if let Some(max_thinking_tokens) = config.max_thinking_tokens {
1219        let tokens_line = format!(
1220            "{}{} {}",
1221            indent,
1222            pad_text_to_width(
1223                max_thinking_tokens_label,
1224                max_label_width,
1225                TextAlignment::Left,
1226                ' '
1227            ),
1228            format!("{}", max_thinking_tokens).yellow()
1229        );
1230        lines.push(tokens_line);
1231    }
1232
1233    // Format API timeout if available
1234    if let Some(api_timeout_ms) = config.api_timeout_ms {
1235        let timeout_line = format!(
1236            "{}{} {}",
1237            indent,
1238            pad_text_to_width(
1239                api_timeout_ms_label,
1240                max_label_width,
1241                TextAlignment::Left,
1242                ' '
1243            ),
1244            format!("{}", api_timeout_ms).yellow()
1245        );
1246        lines.push(timeout_line);
1247    }
1248
1249    // Format disable nonessential traffic flag if available
1250    if let Some(disable_flag) = config.claude_code_disable_nonessential_traffic {
1251        let flag_line = format!(
1252            "{}{} {}",
1253            indent,
1254            pad_text_to_width(
1255                disable_nonessential_traffic_label,
1256                max_label_width,
1257                TextAlignment::Left,
1258                ' '
1259            ),
1260            format!("{}", disable_flag).yellow()
1261        );
1262        lines.push(flag_line);
1263    }
1264
1265    // Format default Sonnet model if available
1266    if let Some(sonnet_model) = &config.anthropic_default_sonnet_model {
1267        let sonnet_line = format!(
1268            "{}{} {}",
1269            indent,
1270            pad_text_to_width(
1271                default_sonnet_model_label,
1272                max_label_width,
1273                TextAlignment::Left,
1274                ' '
1275            ),
1276            sonnet_model.yellow()
1277        );
1278        lines.push(sonnet_line);
1279    }
1280
1281    // Format default Opus model if available
1282    if let Some(opus_model) = &config.anthropic_default_opus_model {
1283        let opus_line = format!(
1284            "{}{} {}",
1285            indent,
1286            pad_text_to_width(
1287                default_opus_model_label,
1288                max_label_width,
1289                TextAlignment::Left,
1290                ' '
1291            ),
1292            opus_model.yellow()
1293        );
1294        lines.push(opus_line);
1295    }
1296
1297    // Format default Haiku model if available
1298    if let Some(haiku_model) = &config.anthropic_default_haiku_model {
1299        let haiku_line = format!(
1300            "{}{} {}",
1301            indent,
1302            pad_text_to_width(
1303                default_haiku_model_label,
1304                max_label_width,
1305                TextAlignment::Left,
1306                ' '
1307            ),
1308            haiku_model.yellow()
1309        );
1310        lines.push(haiku_line);
1311    }
1312
1313    // Format subagent model if available
1314    if let Some(subagent_model) = &config.claude_code_subagent_model {
1315        let subagent_line = format!(
1316            "{}{} {}",
1317            indent,
1318            pad_text_to_width(
1319                subagent_model_label,
1320                max_label_width,
1321                TextAlignment::Left,
1322                ' '
1323            ),
1324            subagent_model.yellow()
1325        );
1326        lines.push(subagent_line);
1327    }
1328
1329    // Format disable non-streaming fallback if available
1330    if let Some(disable_flag) = config.claude_code_disable_nonstreaming_fallback {
1331        let flag_line = format!(
1332            "{}{} {}",
1333            indent,
1334            pad_text_to_width(
1335                disable_nonstreaming_fallback_label,
1336                max_label_width,
1337                TextAlignment::Left,
1338                ' '
1339            ),
1340            format!("{}", disable_flag).yellow()
1341        );
1342        lines.push(flag_line);
1343    }
1344
1345    // Format effort level if available
1346    if let Some(effort_level) = &config.claude_code_effort_level {
1347        let effort_line = format!(
1348            "{}{} {}",
1349            indent,
1350            pad_text_to_width(
1351                effort_level_label,
1352                max_label_width,
1353                TextAlignment::Left,
1354                ' '
1355            ),
1356            effort_level.yellow()
1357        );
1358        lines.push(effort_line);
1359    }
1360
1361    lines
1362}
1363
1364#[cfg(test)]
1365mod border_drawing_tests {
1366    use super::*;
1367
1368    #[test]
1369    fn test_border_drawing_unicode_support() {
1370        let _border = BorderDrawing::new();
1371        // Should create without panic - testing that BorderDrawing can be instantiated
1372    }
1373
1374    #[test]
1375    fn test_border_drawing_top_border() {
1376        let border = BorderDrawing {
1377            unicode_supported: true,
1378        };
1379        let result = border.draw_top_border("Test", 20);
1380        assert!(!result.is_empty());
1381        assert!(result.contains("Test"));
1382    }
1383
1384    #[test]
1385    fn test_border_drawing_ascii_fallback() {
1386        let border = BorderDrawing {
1387            unicode_supported: false,
1388        };
1389        let result = border.draw_top_border("Test", 20);
1390        assert!(!result.is_empty());
1391        assert!(result.contains("Test"));
1392        assert!(result.contains("+"));
1393        assert!(result.contains("-"));
1394    }
1395
1396    #[test]
1397    fn test_border_drawing_middle_line() {
1398        let border = BorderDrawing {
1399            unicode_supported: true,
1400        };
1401        let result = border.draw_middle_line("Test message", 30);
1402        assert!(!result.is_empty());
1403        assert!(result.contains("Test message"));
1404    }
1405
1406    #[test]
1407    fn test_border_drawing_bottom_border() {
1408        let border = BorderDrawing {
1409            unicode_supported: true,
1410        };
1411        let result = border.draw_bottom_border(20);
1412        assert!(!result.is_empty());
1413    }
1414
1415    #[test]
1416    fn test_border_drawing_width_consistency() {
1417        let border = BorderDrawing {
1418            unicode_supported: true,
1419        };
1420        let width = 30;
1421        let top = border.draw_top_border("Title", width);
1422        let middle = border.draw_middle_line("Content", width);
1423        let bottom = border.draw_bottom_border(width);
1424
1425        // All borders should have the same character length (approximately)
1426        assert!(top.chars().count() >= width - 2);
1427        assert!(middle.chars().count() >= width - 2);
1428        assert!(bottom.chars().count() >= width - 2);
1429    }
1430}
1431
1432#[cfg(test)]
1433mod pagination_tests {
1434
1435    /// Test pagination calculation logic
1436    #[test]
1437    fn test_pagination_calculation() {
1438        const PAGE_SIZE: usize = 9;
1439
1440        // Test single page scenarios
1441        assert_eq!(1_usize.div_ceil(PAGE_SIZE), 1); // 1 config -> 1 page
1442        assert_eq!(9_usize.div_ceil(PAGE_SIZE), 1); // 9 configs -> 1 page
1443
1444        // Test multi-page scenarios
1445        assert_eq!(10_usize.div_ceil(PAGE_SIZE), 2); // 10 configs -> 2 pages
1446        assert_eq!(18_usize.div_ceil(PAGE_SIZE), 2); // 18 configs -> 2 pages
1447        assert_eq!(19_usize.div_ceil(PAGE_SIZE), 3); // 19 configs -> 3 pages
1448        assert_eq!(27_usize.div_ceil(PAGE_SIZE), 3); // 27 configs -> 3 pages
1449        assert_eq!(28_usize.div_ceil(PAGE_SIZE), 4); // 28 configs -> 4 pages
1450    }
1451
1452    /// Test page range calculation
1453    #[test]
1454    fn test_page_range_calculation() {
1455        const PAGE_SIZE: usize = 9;
1456
1457        // Test first page
1458        let current_page = 0;
1459        let start_idx = current_page * PAGE_SIZE; // 0
1460        let end_idx = std::cmp::min(start_idx + PAGE_SIZE, 15); // min(9, 15) = 9
1461        assert_eq!(start_idx, 0);
1462        assert_eq!(end_idx, 9);
1463        assert_eq!(end_idx - start_idx, 9); // Full page
1464
1465        // Test second page
1466        let current_page = 1;
1467        let start_idx = current_page * PAGE_SIZE; // 9
1468        let end_idx = std::cmp::min(start_idx + PAGE_SIZE, 15); // min(18, 15) = 15
1469        assert_eq!(start_idx, 9);
1470        assert_eq!(end_idx, 15);
1471        assert_eq!(end_idx - start_idx, 6); // Partial page
1472
1473        // Test edge case: exactly PAGE_SIZE configs
1474        let current_page = 0;
1475        let start_idx = current_page * PAGE_SIZE; // 0
1476        let end_idx = std::cmp::min(start_idx + PAGE_SIZE, PAGE_SIZE); // min(9, 9) = 9
1477        assert_eq!(start_idx, 0);
1478        assert_eq!(end_idx, 9);
1479        assert_eq!(end_idx - start_idx, 9); // Full page
1480    }
1481
1482    /// Test digit key mapping to config indices
1483    #[test]
1484    fn test_digit_mapping_to_config_index() {
1485        const PAGE_SIZE: usize = 9;
1486
1487        // Test first page mapping (configs 0-8)
1488        let current_page = 0;
1489        let start_idx = current_page * PAGE_SIZE; // 0
1490
1491        // Digit 1 should map to config index 0
1492        let digit = 1;
1493        let actual_config_index = start_idx + (digit - 1); // 0 + (1-1) = 0
1494        assert_eq!(actual_config_index, 0);
1495
1496        // Digit 9 should map to config index 8
1497        let digit = 9;
1498        let actual_config_index = start_idx + (digit - 1); // 0 + (9-1) = 8
1499        assert_eq!(actual_config_index, 8);
1500
1501        // Test second page mapping (configs 9-17)
1502        let current_page = 1;
1503        let start_idx = current_page * PAGE_SIZE; // 9
1504
1505        // Digit 1 should map to config index 9
1506        let digit = 1;
1507        let actual_config_index = start_idx + (digit - 1); // 9 + (1-1) = 9
1508        assert_eq!(actual_config_index, 9);
1509
1510        // Digit 5 should map to config index 13
1511        let digit = 5;
1512        let actual_config_index = start_idx + (digit - 1); // 9 + (5-1) = 13
1513        assert_eq!(actual_config_index, 13);
1514    }
1515
1516    /// Test selection index conversion for handle_selection_action
1517    #[test]
1518    fn test_selection_index_conversion() {
1519        // Test mapping digit to selection index for handle_selection_action
1520        // Note: handle_selection_action expects indices where:
1521        // - 0 = official config
1522        // - 1 = first user config
1523        // - 2 = second user config, etc.
1524
1525        const PAGE_SIZE: usize = 9;
1526
1527        // First page, digit 1 -> config index 0 -> selection index 1
1528        let current_page = 0;
1529        let start_idx = current_page * PAGE_SIZE; // 0
1530        let digit = 1;
1531        let actual_config_index = start_idx + (digit - 1); // 0
1532        let selection_index = actual_config_index + 1; // +1 because official is at index 0
1533        assert_eq!(selection_index, 1);
1534
1535        // Second page, digit 1 -> config index 9 -> selection index 10
1536        let current_page = 1;
1537        let start_idx = current_page * PAGE_SIZE; // 9
1538        let digit = 1;
1539        let actual_config_index = start_idx + (digit - 1); // 9
1540        let selection_index = actual_config_index + 1; // +1 because official is at index 0
1541        assert_eq!(selection_index, 10);
1542    }
1543
1544    /// Test page navigation bounds checking
1545    #[test]
1546    fn test_page_navigation_bounds() {
1547        const PAGE_SIZE: usize = 9;
1548        let total_configs: usize = 25; // 3 pages total
1549        let total_pages = total_configs.div_ceil(PAGE_SIZE); // 3 pages
1550        assert_eq!(total_pages, 3);
1551
1552        // Test first page - can't go to previous
1553        let mut current_page = 0;
1554        if current_page > 0 {
1555            current_page -= 1;
1556        }
1557        assert_eq!(current_page, 0); // Should stay at 0
1558
1559        // Test last page - can't go to next
1560        let mut current_page = total_pages - 1; // 2 (last page)
1561        if current_page < total_pages - 1 {
1562            current_page += 1;
1563        }
1564        assert_eq!(current_page, 2); // Should stay at 2
1565
1566        // Test middle page navigation
1567        let mut current_page = 1;
1568
1569        // Can go to next page
1570        if current_page < total_pages - 1 {
1571            current_page += 1;
1572        }
1573        assert_eq!(current_page, 2);
1574
1575        // Can go to previous page
1576        if current_page > 0 {
1577            current_page = current_page.saturating_sub(1);
1578        }
1579        assert_eq!(current_page, 1);
1580    }
1581
1582    /// Test boundary conditions for digit key processing
1583    #[test]
1584    fn test_digit_key_boundary_conditions() {
1585        const PAGE_SIZE: usize = 9;
1586
1587        // Test digit 0 (should be ignored)
1588        let digit = 0;
1589        assert!(digit < 1, "Digit 0 should be less than 1 and ignored");
1590
1591        // Test digit beyond available configs (should be ignored)
1592        let configs_len = 5; // Only 5 configs available
1593        let page_configs_len = std::cmp::min(PAGE_SIZE, configs_len); // 5
1594        let digit = 9; // User presses 9
1595        assert!(
1596            digit > page_configs_len,
1597            "Digit 9 should be beyond available configs (5) and ignored"
1598        );
1599
1600        // Test valid digit range
1601        for digit in 1..=page_configs_len {
1602            assert!(
1603                digit >= 1 && digit <= page_configs_len,
1604                "Digit {} should be valid",
1605                digit
1606            );
1607        }
1608    }
1609
1610    /// Test empty configuration list handling
1611    #[test]
1612    fn test_empty_configs_handling() {
1613        let empty_configs: Vec<String> = Vec::new();
1614        assert!(
1615            empty_configs.is_empty(),
1616            "Empty config list should be properly detected"
1617        );
1618
1619        // Verify that empty check comes before pagination calculation
1620        let configs_len = empty_configs.len(); // 0
1621        assert_eq!(configs_len, 0, "Empty configs should have length 0");
1622
1623        // No pagination should be calculated for empty configs
1624        // (function should return early)
1625    }
1626
1627    /// Test page navigation boundary conditions
1628    #[test]
1629    fn test_page_navigation_boundaries() {
1630        const PAGE_SIZE: usize = 9;
1631        let total_configs: usize = 20; // 3 pages total
1632        let total_pages = total_configs.div_ceil(PAGE_SIZE); // 3 pages
1633
1634        // Test first page navigation (cannot go to previous page)
1635        let mut current_page = 0;
1636        let original_page = current_page;
1637
1638        // Simulate PageUp on first page (should not change)
1639        if current_page > 0 {
1640            current_page -= 1;
1641        }
1642        assert_eq!(
1643            current_page, original_page,
1644            "First page should not navigate to previous"
1645        );
1646
1647        // Test last page navigation (cannot go to next page)
1648        let mut current_page = total_pages - 1; // Last page (2)
1649        let original_page = current_page;
1650
1651        // Simulate PageDown on last page (should not change)
1652        if current_page < total_pages - 1 {
1653            current_page += 1;
1654        }
1655        assert_eq!(
1656            current_page, original_page,
1657            "Last page should not navigate to next"
1658        );
1659
1660        // Test valid navigation from middle page
1661        let mut current_page = 1; // Middle page
1662
1663        // Navigate to next page
1664        if current_page < total_pages - 1 {
1665            current_page += 1;
1666        }
1667        assert_eq!(current_page, 2, "Should navigate to next page");
1668
1669        // Navigate to previous page
1670        if current_page > 0 {
1671            current_page = current_page.saturating_sub(1);
1672        }
1673        assert_eq!(current_page, 1, "Should navigate to previous page");
1674    }
1675
1676    /// Test j key navigation (should move selection down like Down arrow)
1677    #[test]
1678    fn test_j_key_navigation() {
1679        let mut selected_index: usize = 0;
1680        let configs_len = 5; // 5 configs + 1 official + 1 exit = 7 total options
1681
1682        // Test j key moves selection down
1683        // j key should behave like Down arrow
1684        if selected_index < configs_len + 1 {
1685            selected_index += 1;
1686        }
1687        assert_eq!(selected_index, 1, "j key should move selection down by one");
1688
1689        // Test j key at bottom boundary (should not go beyond configs_len + 1)
1690        selected_index = configs_len + 1;
1691        let original_index = selected_index;
1692        if selected_index < configs_len + 1 {
1693            selected_index += 1;
1694        }
1695        assert_eq!(
1696            selected_index, original_index,
1697            "j key should not move beyond bottom boundary"
1698        );
1699    }
1700
1701    /// Test k key navigation (should move selection up like Up arrow)
1702    #[test]
1703    fn test_k_key_navigation() {
1704        let mut selected_index: usize = 5;
1705
1706        // Test k key moves selection up
1707        // k key should behave like Up arrow
1708        selected_index = selected_index.saturating_sub(1);
1709        assert_eq!(selected_index, 4, "k key should move selection up by one");
1710
1711        // Test k key at top boundary (should not go below 0)
1712        selected_index = 0;
1713        let original_index = selected_index;
1714        selected_index = selected_index.saturating_sub(1);
1715        assert_eq!(
1716            selected_index, original_index,
1717            "k key should not move beyond top boundary"
1718        );
1719    }
1720
1721    /// Test j/k key boundary conditions match arrow key behavior
1722    #[test]
1723    fn test_jk_key_boundary_conditions() {
1724        const CONFIGS_LEN: usize = 5;
1725
1726        // Test j key at bottom boundary (same as Down arrow)
1727        let mut selected_index: usize = CONFIGS_LEN + 1; // At exit option
1728        let original_index = selected_index;
1729        if selected_index < CONFIGS_LEN + 1 {
1730            selected_index += 1; // This is what j key does
1731        }
1732        assert_eq!(
1733            selected_index, original_index,
1734            "j key should respect bottom boundary like Down arrow"
1735        );
1736
1737        // Test k key at top boundary (same as Up arrow)
1738        let mut selected_index: usize = 0; // At official option
1739        let original_index = selected_index;
1740        selected_index = selected_index.saturating_sub(1); // This is what k key does
1741        assert_eq!(
1742            selected_index, original_index,
1743            "k key should respect top boundary like Up arrow"
1744        );
1745    }
1746}
1747
1748/// Error type for handling edit mode navigation
1749#[derive(Debug, PartialEq)]
1750pub(crate) enum EditModeError {
1751    ReturnToMenu,
1752}
1753
1754impl std::fmt::Display for EditModeError {
1755    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1756        match self {
1757            EditModeError::ReturnToMenu => write!(f, "return_to_menu"),
1758        }
1759    }
1760}
1761
1762impl std::error::Error for EditModeError {}
1763
1764/// Handle configuration editing with interactive field selection
1765fn handle_config_edit(config: &Configuration) -> Result<()> {
1766    println!("\n{}", "配置编辑模式".green().bold());
1767    println!("{}", "===================".green());
1768    println!("正在编辑配置: {}", config.alias_name.cyan().bold());
1769    println!();
1770
1771    // Create a mutable copy for editing
1772    let mut editing_config = config.clone();
1773    let original_alias = config.alias_name.clone();
1774
1775    loop {
1776        // Display current field values
1777        display_edit_menu(&editing_config);
1778
1779        // Get user input for field selection
1780        println!("\n{}", "提示: 可使用大小写字母".dimmed());
1781        print!("请选择要编辑的字段 (1-9, A-E), 或输入 S 保存, Q 返回上一级菜单: ");
1782        io::stdout().flush()?;
1783
1784        let mut input = String::new();
1785        io::stdin().read_line(&mut input)?;
1786        let input = input.trim();
1787
1788        // Note: Both lowercase and uppercase are accepted for commands
1789        match input {
1790            "1" => edit_field_alias(&mut editing_config)?,
1791            "2" => edit_field_token(&mut editing_config)?,
1792            "3" => edit_field_url(&mut editing_config)?,
1793            "4" => edit_field_model(&mut editing_config)?,
1794            "5" => edit_field_small_fast_model(&mut editing_config)?,
1795            "6" => edit_field_max_thinking_tokens(&mut editing_config)?,
1796            "7" => edit_field_api_timeout_ms(&mut editing_config)?,
1797            "8" => edit_field_claude_code_disable_nonessential_traffic(&mut editing_config)?,
1798            "9" => edit_field_anthropic_default_sonnet_model(&mut editing_config)?,
1799            "10" | "a" | "A" => edit_field_anthropic_default_opus_model(&mut editing_config)?,
1800            "11" | "b" | "B" => edit_field_anthropic_default_haiku_model(&mut editing_config)?,
1801            "12" | "c" | "C" => edit_field_claude_code_subagent_model(&mut editing_config)?,
1802            "13" | "d" | "D" => {
1803                edit_field_claude_code_disable_nonstreaming_fallback(&mut editing_config)?
1804            }
1805            "14" | "e" | "E" => edit_field_claude_code_effort_level(&mut editing_config)?,
1806            "s" | "S" => {
1807                // Save changes
1808                return save_configuration_changes(&original_alias, &editing_config);
1809            }
1810            "q" | "Q" => {
1811                println!("\n{}", "返回上一级菜单".blue());
1812                return Err(EditModeError::ReturnToMenu.into());
1813            }
1814            _ => {
1815                println!("{}", "无效选择,请重试".red());
1816            }
1817        }
1818    }
1819}
1820
1821/// Display the edit menu with current field values
1822fn display_edit_menu(config: &Configuration) {
1823    println!("\n{}", "当前配置值:".blue().bold());
1824    println!("{}", "─────────────────────────".blue());
1825
1826    println!("1. 别名 (alias_name): {}", config.alias_name.green());
1827
1828    println!(
1829        "2. 令牌 (ANTHROPIC_AUTH_TOKEN): {}",
1830        format_token_for_display(&config.token).green()
1831    );
1832
1833    println!("3. URL (ANTHROPIC_BASE_URL): {}", config.url.green());
1834
1835    println!(
1836        "4. 模型 (ANTHROPIC_MODEL): {}",
1837        config.model.as_deref().unwrap_or("[未设置]").green()
1838    );
1839
1840    println!(
1841        "5. 快速模型 (ANTHROPIC_SMALL_FAST_MODEL): {}",
1842        config
1843            .small_fast_model
1844            .as_deref()
1845            .unwrap_or("[未设置]")
1846            .green()
1847    );
1848
1849    println!(
1850        "6. 最大思考令牌数 (ANTHROPIC_MAX_THINKING_TOKENS): {}",
1851        config
1852            .max_thinking_tokens
1853            .map(|t| t.to_string())
1854            .unwrap_or("[未设置]".to_string())
1855            .green()
1856    );
1857
1858    println!(
1859        "7. API超时时间 (API_TIMEOUT_MS): {}",
1860        config
1861            .api_timeout_ms
1862            .map(|t| t.to_string())
1863            .unwrap_or("[未设置]".to_string())
1864            .green()
1865    );
1866
1867    println!(
1868        "8. 禁用非必要流量 (CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC): {}",
1869        config
1870            .claude_code_disable_nonessential_traffic
1871            .map(|t| t.to_string())
1872            .unwrap_or("[未设置]".to_string())
1873            .green()
1874    );
1875
1876    println!(
1877        "9. 默认 Sonnet 模型 (ANTHROPIC_DEFAULT_SONNET_MODEL): {}",
1878        config
1879            .anthropic_default_sonnet_model
1880            .as_deref()
1881            .unwrap_or("[未设置]")
1882            .green()
1883    );
1884
1885    println!(
1886        "A. 默认 Opus 模型 (ANTHROPIC_DEFAULT_OPUS_MODEL): {}",
1887        config
1888            .anthropic_default_opus_model
1889            .as_deref()
1890            .unwrap_or("[未设置]")
1891            .green()
1892    );
1893
1894    println!(
1895        "B. 默认 Haiku 模型 (ANTHROPIC_DEFAULT_HAIKU_MODEL): {}",
1896        config
1897            .anthropic_default_haiku_model
1898            .as_deref()
1899            .unwrap_or("[未设置]")
1900            .green()
1901    );
1902
1903    println!(
1904        "C. 子代理模型 (CLAUDE_CODE_SUBAGENT_MODEL): {}",
1905        config
1906            .claude_code_subagent_model
1907            .as_deref()
1908            .unwrap_or("[未设置]")
1909            .green()
1910    );
1911
1912    println!(
1913        "D. 禁用非流式回退 (CLAUDE_CODE_DISABLE_NONSTREAMING_FALLBACK): {}",
1914        config
1915            .claude_code_disable_nonstreaming_fallback
1916            .map(|t| t.to_string())
1917            .unwrap_or("[未设置]".to_string())
1918            .green()
1919    );
1920
1921    println!(
1922        "E. 努力级别 (CLAUDE_CODE_EFFORT_LEVEL): {}",
1923        config
1924            .claude_code_effort_level
1925            .as_deref()
1926            .unwrap_or("[未设置]")
1927            .green()
1928    );
1929
1930    println!("{}", "─────────────────────────".blue());
1931    println!(
1932        "S. {} | Q. {}",
1933        "保存更改".green().bold(),
1934        "返回上一级菜单".blue()
1935    );
1936}
1937
1938/// Helper function to edit a string field
1939pub(crate) fn edit_string_field(
1940    field_name: &str,
1941    current_value: &str,
1942    validator: impl Fn(&str) -> Result<()>,
1943) -> Result<Option<String>> {
1944    println!("\n编辑{field_name}:");
1945    println!("当前值: {}", current_value.cyan());
1946    print!("新值 (回车保持不变): ");
1947    io::stdout().flush()?;
1948
1949    let mut input = String::new();
1950    io::stdin().read_line(&mut input)?;
1951    let input = input.trim();
1952
1953    if !input.is_empty() {
1954        validator(input)?;
1955        println!("{field_name}已更新为: {}", input.green());
1956        Ok(Some(input.to_string()))
1957    } else {
1958        Ok(None)
1959    }
1960}
1961
1962/// Type alias for optional string field result
1963pub(crate) type OptionalStringResult = Result<Option<Option<String>>>;
1964
1965/// Helper function to edit an optional string field (can be cleared)
1966pub(crate) fn edit_optional_string_field(
1967    field_name: &str,
1968    current_value: Option<&str>,
1969) -> OptionalStringResult {
1970    println!("\n编辑{field_name}:");
1971    println!("当前值: {}", current_value.unwrap_or("[未设置]").cyan());
1972    print!("新值 (回车保持不变,输入空格清除): ");
1973    io::stdout().flush()?;
1974
1975    let mut input = String::new();
1976    io::stdin().read_line(&mut input)?;
1977    let input = input.trim();
1978
1979    if !input.is_empty() {
1980        if input == " " {
1981            println!("{}", format!("{field_name}已清除").green());
1982            Ok(Some(None))
1983        } else {
1984            println!("{field_name}已更新为: {}", input.green());
1985            Ok(Some(Some(input.to_string())))
1986        }
1987    } else {
1988        Ok(None)
1989    }
1990}
1991
1992/// Type alias for optional u32 field result
1993type OptionalU32Result = Result<Option<Option<u32>>>;
1994
1995/// Helper function to edit an optional u32 field (can be cleared)
1996fn edit_optional_u32_field(field_name: &str, current_value: Option<u32>) -> OptionalU32Result {
1997    println!("\n编辑{field_name}:");
1998    println!(
1999        "当前值: {}",
2000        current_value
2001            .map(|t| t.to_string())
2002            .unwrap_or("[未设置]".to_string())
2003            .cyan()
2004    );
2005    print!("新值 (回车保持不变,输入 0 清除): ");
2006    io::stdout().flush()?;
2007
2008    let mut input = String::new();
2009    io::stdin().read_line(&mut input)?;
2010    let input = input.trim();
2011
2012    if !input.is_empty() {
2013        if input == "0" {
2014            println!("{}", format!("{field_name}已清除").green());
2015            Ok(Some(None))
2016        } else if let Ok(value) = input.parse::<u32>() {
2017            println!("{field_name}已更新为: {}", value.to_string().green());
2018            Ok(Some(Some(value)))
2019        } else {
2020            println!("{}", "错误: 请输入有效的数字".red());
2021            Ok(None)
2022        }
2023    } else {
2024        Ok(None)
2025    }
2026}
2027
2028/// Edit alias field
2029fn edit_field_alias(config: &mut Configuration) -> Result<()> {
2030    let validator = |input: &str| -> Result<()> {
2031        if input.contains(char::is_whitespace) {
2032            anyhow::bail!("错误: 别名不能包含空白字符");
2033        }
2034        if input == "cc" {
2035            anyhow::bail!("错误: 'cc' 是保留名称");
2036        }
2037        if input == "official" {
2038            anyhow::bail!("错误: 'official' 是保留名称");
2039        }
2040        Ok(())
2041    };
2042
2043    match edit_string_field("别名", &config.alias_name, validator) {
2044        Ok(Some(new_value)) => config.alias_name = new_value,
2045        Ok(None) => {}
2046        Err(e) => println!("{}", e.to_string().red()),
2047    }
2048    Ok(())
2049}
2050
2051/// Edit token field
2052fn edit_field_token(config: &mut Configuration) -> Result<()> {
2053    let no_validator = |_: &str| -> Result<()> { Ok(()) };
2054    if let Some(new_value) = edit_string_field(
2055        "令牌",
2056        &format_token_for_display(&config.token),
2057        no_validator,
2058    )? {
2059        config.token = new_value;
2060        println!("{}", "令牌已更新".green());
2061    }
2062    Ok(())
2063}
2064
2065/// Edit URL field
2066fn edit_field_url(config: &mut Configuration) -> Result<()> {
2067    let no_validator = |_: &str| -> Result<()> { Ok(()) };
2068    if let Some(new_value) = edit_string_field("URL", &config.url, no_validator)? {
2069        config.url = new_value;
2070    }
2071    Ok(())
2072}
2073
2074/// Edit model field
2075fn edit_field_model(config: &mut Configuration) -> Result<()> {
2076    if let Some(result) = edit_optional_string_field("模型", config.model.as_deref())? {
2077        config.model = result;
2078    }
2079    Ok(())
2080}
2081
2082/// Edit small_fast_model field
2083fn edit_field_small_fast_model(config: &mut Configuration) -> Result<()> {
2084    if let Some(result) =
2085        edit_optional_string_field("快速模型", config.small_fast_model.as_deref())?
2086    {
2087        config.small_fast_model = result;
2088    }
2089    Ok(())
2090}
2091
2092/// Edit max_thinking_tokens field
2093fn edit_field_max_thinking_tokens(config: &mut Configuration) -> Result<()> {
2094    if let Some(result) = edit_optional_u32_field("最大思考令牌数", config.max_thinking_tokens)?
2095    {
2096        config.max_thinking_tokens = result;
2097    }
2098    Ok(())
2099}
2100
2101/// Edit api_timeout_ms field
2102fn edit_field_api_timeout_ms(config: &mut Configuration) -> Result<()> {
2103    if let Some(result) = edit_optional_u32_field("API超时时间 (毫秒)", config.api_timeout_ms)?
2104    {
2105        config.api_timeout_ms = result;
2106    }
2107    Ok(())
2108}
2109
2110/// Edit claude_code_disable_nonessential_traffic field
2111fn edit_field_claude_code_disable_nonessential_traffic(config: &mut Configuration) -> Result<()> {
2112    if let Some(result) = edit_optional_u32_field(
2113        "禁用非必要流量标志",
2114        config.claude_code_disable_nonessential_traffic,
2115    )? {
2116        config.claude_code_disable_nonessential_traffic = result;
2117    }
2118    Ok(())
2119}
2120
2121/// Edit anthropic_default_sonnet_model field
2122fn edit_field_anthropic_default_sonnet_model(config: &mut Configuration) -> Result<()> {
2123    if let Some(result) = edit_optional_string_field(
2124        "默认 Sonnet 模型",
2125        config.anthropic_default_sonnet_model.as_deref(),
2126    )? {
2127        config.anthropic_default_sonnet_model = result;
2128    }
2129    Ok(())
2130}
2131
2132/// Edit anthropic_default_opus_model field
2133fn edit_field_anthropic_default_opus_model(config: &mut Configuration) -> Result<()> {
2134    if let Some(result) = edit_optional_string_field(
2135        "默认 Opus 模型",
2136        config.anthropic_default_opus_model.as_deref(),
2137    )? {
2138        config.anthropic_default_opus_model = result;
2139    }
2140    Ok(())
2141}
2142
2143/// Edit anthropic_default_haiku_model field
2144fn edit_field_anthropic_default_haiku_model(config: &mut Configuration) -> Result<()> {
2145    if let Some(result) = edit_optional_string_field(
2146        "默认 Haiku 模型",
2147        config.anthropic_default_haiku_model.as_deref(),
2148    )? {
2149        config.anthropic_default_haiku_model = result;
2150    }
2151    Ok(())
2152}
2153
2154/// Edit claude_code_subagent_model field
2155fn edit_field_claude_code_subagent_model(config: &mut Configuration) -> Result<()> {
2156    if let Some(result) =
2157        edit_optional_string_field("子代理模型", config.claude_code_subagent_model.as_deref())?
2158    {
2159        config.claude_code_subagent_model = result;
2160    }
2161    Ok(())
2162}
2163
2164/// Edit claude_code_disable_nonstreaming_fallback field
2165fn edit_field_claude_code_disable_nonstreaming_fallback(config: &mut Configuration) -> Result<()> {
2166    if let Some(result) = edit_optional_u32_field(
2167        "禁用非流式回退标志",
2168        config.claude_code_disable_nonstreaming_fallback,
2169    )? {
2170        config.claude_code_disable_nonstreaming_fallback = result;
2171    }
2172    Ok(())
2173}
2174
2175/// Edit claude_code_effort_level field
2176fn edit_field_claude_code_effort_level(config: &mut Configuration) -> Result<()> {
2177    if let Some(result) =
2178        edit_optional_string_field("努力级别", config.claude_code_effort_level.as_deref())?
2179    {
2180        config.claude_code_effort_level = result;
2181    }
2182    Ok(())
2183}
2184
2185/// Save configuration changes to disk and handle alias conflicts
2186fn save_configuration_changes(original_alias: &str, new_config: &Configuration) -> Result<()> {
2187    // Load current storage
2188    let mut storage = ConfigStorage::load()?;
2189
2190    // Check for alias conflicts if alias changed
2191    if original_alias != new_config.alias_name
2192        && storage.get_configuration(&new_config.alias_name).is_some()
2193    {
2194        println!("\n{}", "别名冲突!".red().bold());
2195        println!("配置 '{}' 已存在", new_config.alias_name.yellow());
2196        print!("是否覆盖现有配置? (y/N): ");
2197        io::stdout().flush()?;
2198
2199        let mut input = String::new();
2200        io::stdin().read_line(&mut input)?;
2201        let input = input.trim().to_lowercase();
2202
2203        if input != "y" && input != "yes" {
2204            println!("{}", "编辑已取消".yellow());
2205            return Ok(());
2206        }
2207    }
2208
2209    // Update configuration using the method from config_storage.rs
2210    storage.update_configuration(original_alias, new_config.clone())?;
2211    storage.save()?;
2212
2213    println!("\n{}", "配置已成功保存!".green().bold());
2214
2215    Ok(())
2216}