@@ -71,6 +71,27 @@ const INACTIVE_CURRENT_LINE_BG: Color = Color::AnsiValue(234); // Dimmed current |
| 71 | 71 | const INACTIVE_LINE_NUM_COLOR: Color = Color::AnsiValue(240); // Dimmed line numbers |
| 72 | 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 | 95 | /// Terminal screen renderer |
| 75 | 96 | pub struct Screen { |
| 76 | 97 | stdout: Stdout, |
@@ -3728,24 +3749,108 @@ impl Screen { |
| 3728 | 3749 | SetForegroundColor(Color::White), |
| 3729 | 3750 | )?; |
| 3730 | 3751 | |
| 3731 | | - // Terminal title bar |
| 3732 | | - let title = " Terminal "; |
| 3733 | | - let separator = "─".repeat((self.cols as usize).saturating_sub(title.len() + 2) / 2); |
| 3734 | | - execute!( |
| 3735 | | - self.stdout, |
| 3736 | | - Print(&separator), |
| 3737 | | - SetAttribute(Attribute::Bold), |
| 3738 | | - Print(title), |
| 3739 | | - SetAttribute(Attribute::Reset), |
| 3740 | | - SetBackgroundColor(Color::AnsiValue(237)), |
| 3741 | | - SetForegroundColor(Color::White), |
| 3742 | | - Print(&separator), |
| 3743 | | - )?; |
| 3752 | + // Terminal title bar with tabs |
| 3753 | + let session_count = terminal.session_count(); |
| 3754 | + let active_idx = terminal.active_session_index(); |
| 3755 | + |
| 3756 | + if session_count <= 1 { |
| 3757 | + // Single session: show CWD or "Terminal" centered |
| 3758 | + let name = terminal.active_cwd() |
| 3759 | + .map(|p| extract_dirname(p)) |
| 3760 | + .unwrap_or_else(|| "Terminal".to_string()); |
| 3761 | + let title = format!(" {} ", name); |
| 3762 | + let separator = "─".repeat((self.cols as usize).saturating_sub(title.len() + 2) / 2); |
| 3763 | + execute!( |
| 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 |
| 3746 | | - let printed = separator.chars().count() * 2 + title.len(); |
| 3747 | | - if printed < self.cols as usize { |
| 3748 | | - execute!(self.stdout, Print(" ".repeat(self.cols as usize - printed)))?; |
| 3831 | + // Separator between tabs |
| 3832 | + if i < session_count - 1 { |
| 3833 | + execute!( |
| 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 | 3856 | // Terminal content area - use batched rendering to reduce flicker |