1+ use ratatui_kit:: {
2+ Component ,
3+ ComponentDrawer ,
4+ ComponentUpdater ,
5+ Hooks ,
6+ Props ,
7+ UseState ,
8+ } ;
9+
10+ use ratatui_kit:: ratatui:: {
11+ style:: { Color , Style , Stylize } ,
12+ text:: { Line , Span } ,
13+ widgets:: { Block , Borders , Paragraph , Widget } ,
14+ layout:: { Alignment , Rect } ,
15+ buffer:: Buffer ,
16+ } ;
17+
18+ #[ derive( Props ) ]
19+ pub struct StatusBarProps {
20+ pub project_path : Option < String > ,
21+ pub context_length : usize ,
22+ pub max_context_length : usize ,
23+ pub style : Style ,
24+ pub progress_style : Style ,
25+ pub border_style : Style ,
26+ }
27+
28+ impl Default for StatusBarProps {
29+ fn default ( ) -> Self {
30+ Self {
31+ project_path : None ,
32+ context_length : 0 ,
33+ max_context_length : 100 ,
34+ style : Style :: default ( ) ,
35+ progress_style : Style :: default ( ) . fg ( Color :: Green ) ,
36+ border_style : Style :: default ( ) ,
37+ }
38+ }
39+ }
40+
41+ pub struct StatusBar {
42+ project_path : Option < String > ,
43+ context_length : usize ,
44+ max_context_length : usize ,
45+ style : Style ,
46+ progress_style : Style ,
47+ border_style : Style ,
48+ }
49+
50+ impl Component for StatusBar {
51+ type Props < ' a > = StatusBarProps ;
52+
53+ fn new ( props : & Self :: Props < ' _ > ) -> Self {
54+ Self {
55+ project_path : props. project_path . clone ( ) ,
56+ context_length : props. context_length ,
57+ max_context_length : props. max_context_length ,
58+ style : props. style ,
59+ progress_style : props. progress_style ,
60+ border_style : props. border_style ,
61+ }
62+ }
63+
64+ fn update (
65+ & mut self ,
66+ props : & mut Self :: Props < ' _ > ,
67+ _hooks : Hooks ,
68+ _updater : & mut ComponentUpdater ,
69+ ) {
70+ // 同步props
71+ self . project_path = props. project_path . clone ( ) ;
72+ self . context_length = props. context_length ;
73+ self . max_context_length = props. max_context_length ;
74+ self . style = props. style ;
75+ self . progress_style = props. progress_style ;
76+ self . border_style = props. border_style ;
77+ }
78+
79+ fn draw ( & mut self , drawer : & mut ComponentDrawer < ' _ , ' _ > ) {
80+ let area = drawer. area ;
81+ let buf = drawer. buffer_mut ( ) ;
82+
83+ // 绘制顶部边框线
84+ self . render_border_top ( buf, area) ;
85+
86+ // 绘制内容区域(减去顶部边框占用的一行)
87+ let content_area = Rect {
88+ x : area. x ,
89+ y : area. y + 1 ,
90+ width : area. width ,
91+ height : area. height . saturating_sub ( 1 ) ,
92+ } ;
93+
94+ self . render_content ( buf, content_area) ;
95+ }
96+ }
97+
98+ impl StatusBar {
99+ /// 渲染顶部边框线
100+ fn render_border_top ( & self , buf : & mut Buffer , area : Rect ) {
101+ if area. height > 0 {
102+ for x in area. left ( ) ..area. right ( ) {
103+ buf. get_mut ( x, area. top ( ) )
104+ . set_char ( '─' )
105+ . set_style ( self . border_style ) ;
106+ }
107+ }
108+ }
109+
110+ /// 渲染内容区域
111+ fn render_content ( & self , buf : & mut Buffer , area : Rect ) {
112+ if area. height == 0 {
113+ return ;
114+ }
115+
116+ // 创建主段落容器
117+ let content_line = self . create_content_line ( area. width as usize ) ;
118+
119+ // 使用Paragraph渲染内容
120+ Paragraph :: new ( content_line)
121+ . alignment ( Alignment :: Left )
122+ . render ( area, buf) ;
123+ }
124+
125+ /// 创建内容行,包含左侧路径和右侧进度条
126+ fn create_content_line ( & self , total_width : usize ) -> Line {
127+ let mut spans = Vec :: new ( ) ;
128+
129+ // 计算左右两部分的最大宽度
130+ let min_left_width = 20 ; // 最小左侧宽度,确保路径能显示一些内容
131+ let min_right_width = 20 ; // 最小右侧宽度,确保进度条能显示
132+
133+ let left_max_width = std:: cmp:: max ( total_width. saturating_sub ( min_right_width) , min_left_width) ;
134+ let right_max_width = std:: cmp:: max ( total_width. saturating_sub ( left_max_width) , min_right_width) ;
135+
136+ // 添加左侧项目路径
137+ if let Some ( ref path) = self . project_path {
138+ let truncated_path = self . truncate_path_for_display ( path, left_max_width) ;
139+ spans. push ( Span :: styled ( truncated_path, self . style ) ) ;
140+ } else {
141+ spans. push ( Span :: styled ( "No project" , self . style . dim ( ) ) ) ;
142+ }
143+
144+ // 计算中间空白区域
145+ let current_path_width = self . project_path . as_ref ( ) . map ( |p| {
146+ let truncated = self . truncate_path_for_display ( p, left_max_width) ;
147+ truncated. len ( )
148+ } ) . unwrap_or ( 7 ) ; // "No project" 长度为9,这里用7是为了安全
149+
150+ // 为右侧进度条预留空间
151+ let progress_bar_width = std:: cmp:: min ( right_max_width, 20 ) ; // 最大20字符宽的进度条
152+ let required_space = progress_bar_width + 3 ; // 加上一些间隔
153+ let available_middle_space = total_width. saturating_sub ( current_path_width + progress_bar_width) ;
154+
155+ // 添加中间空白
156+ if available_middle_space > 0 {
157+ spans. push ( Span :: raw ( " " . repeat ( available_middle_space) ) ) ;
158+ }
159+
160+ // 添加右侧上下文长度进度条
161+ let progress_spans = self . create_progress_bar ( progress_bar_width) ;
162+ spans. extend ( progress_spans) ;
163+
164+ Line :: from ( spans)
165+ }
166+
167+ /// 将路径截断以适应显示宽度
168+ fn truncate_path_for_display ( & self , path : & str , max_width : usize ) -> String {
169+ if path. len ( ) <= max_width {
170+ return path. to_string ( ) ;
171+ }
172+
173+ if max_width <= 5 {
174+ return if max_width >= 3 {
175+ ".." . to_string ( )
176+ } else {
177+ "" . to_string ( )
178+ } ;
179+ }
180+
181+ let part_len = ( max_width - 3 ) / 2 ;
182+ let start_part = & path[ ..std:: cmp:: min ( part_len, path. len ( ) ) ] ;
183+ let end_start = std:: cmp:: max ( path. len ( ) - part_len, part_len) ;
184+ let end_part = & path[ end_start..] ;
185+
186+ format ! ( "{}...{}" , start_part, end_part)
187+ }
188+
189+ /// 创建上下文长度进度条
190+ fn create_progress_bar ( & self , max_width : usize ) -> Vec < Span > {
191+ let mut spans = Vec :: new ( ) ;
192+
193+ if self . max_context_length == 0 {
194+ // 如果最大长度为0,则显示简单的文本
195+ spans. push ( Span :: styled (
196+ format ! ( "Ctx: {}" , self . context_length) ,
197+ self . style ,
198+ ) ) ;
199+ return spans;
200+ }
201+
202+ let progress_ratio = self . context_length as f64 / self . max_context_length as f64 ;
203+ let filled_count = ( ( max_width as f64 * progress_ratio) . round ( ) as usize )
204+ . min ( max_width. saturating_sub ( 2 ) ) ; // 保留至少2个字符用于显示百分比
205+
206+ // 计算百分比
207+ let percentage = ( progress_ratio * 100.0 ) . round ( ) as usize ;
208+
209+ // 根据百分比选择颜色
210+ let bar_color = if percentage < 50 {
211+ Color :: Green
212+ } else if percentage < 80 {
213+ Color :: Yellow
214+ } else {
215+ Color :: Red
216+ } ;
217+
218+ // 构建进度条字符串
219+ let mut progress_str = String :: new ( ) ;
220+ for i in 0 ..max_width {
221+ if i < filled_count {
222+ progress_str. push ( '█' ) ;
223+ } else if i == filled_count && filled_count < max_width. saturating_sub ( 2 ) {
224+ // 在进度条末尾添加百分比,如果还有空间的话
225+ break ;
226+ } else {
227+ progress_str. push ( '░' ) ;
228+ }
229+ }
230+
231+ // 添加进度条
232+ spans. push ( Span :: styled (
233+ format ! ( "{}" , progress_str) ,
234+ self . progress_style . fg ( bar_color) ,
235+ ) ) ;
236+
237+ // 如果有足够的空间,添加百分比和数值信息
238+ if max_width > progress_str. len ( ) + 5 { // 需要额外空间显示百分比
239+ spans. push ( Span :: styled (
240+ format ! ( " {}% ({}/{})" ,
241+ percentage,
242+ self . context_length,
243+ self . max_context_length) ,
244+ self . style ,
245+ ) ) ;
246+ } else if max_width > progress_str. len ( ) + 2 { // 至少显示数值
247+ spans. push ( Span :: styled (
248+ format ! ( " {}/{}" , self . context_length, self . max_context_length) ,
249+ self . style ,
250+ ) ) ;
251+ }
252+
253+ spans
254+ }
255+ }
0 commit comments