@@ -71,6 +71,27 @@ const INACTIVE_CURRENT_LINE_BG: Color = Color::AnsiValue(234); // Dimmed current |
| 71 | const INACTIVE_LINE_NUM_COLOR: Color = Color::AnsiValue(240); // Dimmed line numbers | 71 | const INACTIVE_LINE_NUM_COLOR: Color = Color::AnsiValue(240); // Dimmed line numbers |
| 72 | const INACTIVE_TEXT_COLOR: Color = Color::AnsiValue(245); // Dimmed text | 72 | const INACTIVE_TEXT_COLOR: Color = Color::AnsiValue(245); // Dimmed text |
| 73 | | 73 | |
| | 74 | +/// Extract the last component of a path for display |
| | 75 | +fn extract_dirname(path: &str) -> String { |
| | 76 | + // Handle home directory |
| | 77 | + if path == "/" { |
| | 78 | + return "/".to_string(); |
| | 79 | + } |
| | 80 | + |
| | 81 | + // Get the last path component |
| | 82 | + path.rsplit('/') |
| | 83 | + .find(|s| !s.is_empty()) |
| | 84 | + .map(|s| { |
| | 85 | + // If it starts with ~, keep it |
| | 86 | + if path.starts_with('~') || path == "/" { |
| | 87 | + s.to_string() |
| | 88 | + } else { |
| | 89 | + s.to_string() |
| | 90 | + } |
| | 91 | + }) |
| | 92 | + .unwrap_or_else(|| path.to_string()) |
| | 93 | +} |
| | 94 | + |
| 74 | /// Terminal screen renderer | 95 | /// Terminal screen renderer |
| 75 | pub struct Screen { | 96 | pub struct Screen { |
| 76 | stdout: Stdout, | 97 | stdout: Stdout, |
@@ -3728,24 +3749,108 @@ impl Screen { |
| 3728 | SetForegroundColor(Color::White), | 3749 | SetForegroundColor(Color::White), |
| 3729 | )?; | 3750 | )?; |
| 3730 | | 3751 | |
| 3731 | - // Terminal title bar | 3752 | + // Terminal title bar with tabs |
| 3732 | - let title = " Terminal "; | 3753 | + let session_count = terminal.session_count(); |
| 3733 | - let separator = "─".repeat((self.cols as usize).saturating_sub(title.len() + 2) / 2); | 3754 | + let active_idx = terminal.active_session_index(); |
| 3734 | - execute!( | 3755 | + |
| 3735 | - self.stdout, | 3756 | + if session_count <= 1 { |
| 3736 | - Print(&separator), | 3757 | + // Single session: show CWD or "Terminal" centered |
| 3737 | - SetAttribute(Attribute::Bold), | 3758 | + let name = terminal.active_cwd() |
| 3738 | - Print(title), | 3759 | + .map(|p| extract_dirname(p)) |
| 3739 | - SetAttribute(Attribute::Reset), | 3760 | + .unwrap_or_else(|| "Terminal".to_string()); |
| 3740 | - SetBackgroundColor(Color::AnsiValue(237)), | 3761 | + let title = format!(" {} ", name); |
| 3741 | - SetForegroundColor(Color::White), | 3762 | + let separator = "─".repeat((self.cols as usize).saturating_sub(title.len() + 2) / 2); |
| 3742 | - Print(&separator), | 3763 | + execute!( |
| 3743 | - )?; | 3764 | + self.stdout, |
| | 3765 | + Print(&separator), |
| | 3766 | + SetAttribute(Attribute::Bold), |
| | 3767 | + Print(&title), |
| | 3768 | + SetAttribute(Attribute::Reset), |
| | 3769 | + SetBackgroundColor(Color::AnsiValue(237)), |
| | 3770 | + SetForegroundColor(Color::White), |
| | 3771 | + Print(&separator), |
| | 3772 | + )?; |
| | 3773 | + |
| | 3774 | + // Pad to end of line |
| | 3775 | + let printed = separator.chars().count() * 2 + title.len(); |
| | 3776 | + if printed < self.cols as usize { |
| | 3777 | + execute!(self.stdout, Print(" ".repeat(self.cols as usize - printed)))?; |
| | 3778 | + } |
| | 3779 | + } else { |
| | 3780 | + // Multiple sessions: render tab bar |
| | 3781 | + let sessions = terminal.sessions(); |
| | 3782 | + let available_width = self.cols as usize; |
| | 3783 | + let tab_width = (available_width / session_count).max(8).min(25); |
| | 3784 | + |
| | 3785 | + let mut printed = 0; |
| | 3786 | + for (i, session) in sessions.iter().enumerate() { |
| | 3787 | + let is_active = i == active_idx; |
| | 3788 | + let name = session.cwd() |
| | 3789 | + .map(|p| extract_dirname(p)) |
| | 3790 | + .unwrap_or_else(|| format!("Term {}", i + 1)); |
| | 3791 | + |
| | 3792 | + // Format: "[n] name" with truncation |
| | 3793 | + let prefix = format!("{} ", i + 1); |
| | 3794 | + let max_name_len = tab_width.saturating_sub(prefix.len() + 1); |
| | 3795 | + let display_name = if name.len() > max_name_len { |
| | 3796 | + format!("{}…", &name[..max_name_len.saturating_sub(1)]) |
| | 3797 | + } else { |
| | 3798 | + name |
| | 3799 | + }; |
| | 3800 | + let tab_content = format!("{}{}", prefix, display_name); |
| | 3801 | + |
| | 3802 | + // Set colors based on active state |
| | 3803 | + if is_active { |
| | 3804 | + execute!( |
| | 3805 | + self.stdout, |
| | 3806 | + SetBackgroundColor(Color::AnsiValue(238)), |
| | 3807 | + SetForegroundColor(Color::White), |
| | 3808 | + SetAttribute(Attribute::Bold), |
| | 3809 | + )?; |
| | 3810 | + } else { |
| | 3811 | + execute!( |
| | 3812 | + self.stdout, |
| | 3813 | + SetBackgroundColor(Color::AnsiValue(235)), |
| | 3814 | + SetForegroundColor(Color::AnsiValue(245)), |
| | 3815 | + SetAttribute(Attribute::Reset), |
| | 3816 | + )?; |
| | 3817 | + } |
| | 3818 | + |
| | 3819 | + // Print tab with padding |
| | 3820 | + let padding = tab_width.saturating_sub(tab_content.len()); |
| | 3821 | + let left_pad = padding / 2; |
| | 3822 | + let right_pad = padding - left_pad; |
| | 3823 | + execute!( |
| | 3824 | + self.stdout, |
| | 3825 | + Print(" ".repeat(left_pad)), |
| | 3826 | + Print(&tab_content), |
| | 3827 | + Print(" ".repeat(right_pad)), |
| | 3828 | + )?; |
| | 3829 | + printed += tab_width; |
| 3744 | | 3830 | |
| 3745 | - // Pad to end of line | 3831 | + // Separator between tabs |
| 3746 | - let printed = separator.chars().count() * 2 + title.len(); | 3832 | + if i < session_count - 1 { |
| 3747 | - if printed < self.cols as usize { | 3833 | + execute!( |
| 3748 | - execute!(self.stdout, Print(" ".repeat(self.cols as usize - printed)))?; | 3834 | + self.stdout, |
| | 3835 | + SetBackgroundColor(Color::AnsiValue(237)), |
| | 3836 | + SetForegroundColor(Color::AnsiValue(240)), |
| | 3837 | + SetAttribute(Attribute::Reset), |
| | 3838 | + Print("│"), |
| | 3839 | + )?; |
| | 3840 | + printed += 1; |
| | 3841 | + } |
| | 3842 | + } |
| | 3843 | + |
| | 3844 | + // Fill remaining space |
| | 3845 | + if printed < available_width { |
| | 3846 | + execute!( |
| | 3847 | + self.stdout, |
| | 3848 | + SetBackgroundColor(Color::AnsiValue(237)), |
| | 3849 | + SetForegroundColor(Color::White), |
| | 3850 | + SetAttribute(Attribute::Reset), |
| | 3851 | + Print(" ".repeat(available_width - printed)), |
| | 3852 | + )?; |
| | 3853 | + } |
| 3749 | } | 3854 | } |
| 3750 | | 3855 | |
| 3751 | // Terminal content area - use batched rendering to reduce flicker | 3856 | // Terminal content area - use batched rendering to reduce flicker |