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