1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
//! Statusline rendering for MarkdownWidget.
use ratatui::{
layout::Rect,
style::{Color, Modifier, Style},
text::Span,
};
use unicode_width::UnicodeWidthStr;
use crate::primitives::statusline::{StatusLineStacked, SLANT_BL_TR, SLANT_TL_BR};
use crate::widgets::markdown_widget::widget::enums::MarkdownWidgetMode;
use crate::widgets::markdown_widget::widget::MarkdownWidget;
impl<'a> MarkdownWidget<'a> {
/// Render the statusline using StatusLineStacked (powerline style).
///
/// The statusline displays:
/// - Mode indicator (NORMAL/DRAG/FILTER) on the left with colored background
/// - Filename with git stats (no background on git icons)
/// - Scroll position (percentage/total lines) on the right
///
/// If an app theme is set, uses theme colors. Otherwise falls back to defaults.
///
/// # Arguments
///
/// * `area` - The area to render the statusline in
/// * `buf` - The buffer to render to
pub(crate) fn render_statusline(&self, area: Rect, buf: &mut ratatui::buffer::Buffer) {
// Get theme-based or default colors
let (mode_text, mode_color) = match self.mode {
MarkdownWidgetMode::Normal => {
let color = self
.app_theme
.as_ref()
.map(|t| t.info)
.unwrap_or(Color::Rgb(97, 175, 239)); // blue
(" NORMAL ".to_string(), color)
}
MarkdownWidgetMode::Drag => {
let color = self
.app_theme
.as_ref()
.map(|t| t.warning)
.unwrap_or(Color::Rgb(229, 192, 123)); // yellow/orange
(" DRAG ".to_string(), color)
}
MarkdownWidgetMode::Filter => {
let color = self
.app_theme
.as_ref()
.map(|t| t.success)
.unwrap_or(Color::Rgb(152, 195, 121)); // green
let filter_text = self.filter.as_deref().unwrap_or("");
let display_text = format!(" /{} ", filter_text);
(display_text, color)
}
};
// File segment background - use theme background_panel or default
let file_bg = self
.app_theme
.as_ref()
.map(|t| t.background_panel)
.unwrap_or(Color::Rgb(58, 58, 58));
// Mode text foreground - use theme background or default black
let mode_fg = self
.app_theme
.as_ref()
.map(|t| t.background)
.unwrap_or(Color::Black);
// File text color - use theme text or default white
let file_fg = self
.app_theme
.as_ref()
.map(|t| t.text)
.unwrap_or(Color::White);
// Get filename from source path
let filename = self
.source
.source_path()
.and_then(|p| p.file_name())
.and_then(|n| n.to_str());
// Position info - use source line count for accurate display
let source_line_count = self.source.line_count();
let display_total = if source_line_count > 0 {
source_line_count
} else {
self.scroll.total_lines
};
let current_line = self.scroll.current_line;
let percentage = if display_total == 0 {
0
} else {
(current_line * 100) / display_total.max(1)
};
let position_text = format!(" {}%/{} ", percentage, display_total);
// Position segment background - use theme text_muted or default
let position_bg = self
.app_theme
.as_ref()
.map(|t| t.text_muted)
.unwrap_or(Color::Rgb(171, 178, 191));
// Position text foreground - use theme background or default black
let position_fg = self
.app_theme
.as_ref()
.map(|t| t.background)
.unwrap_or(Color::Black);
// Calculate git stats start position
let git_stats_start_x = {
let mode_len = mode_text.len() as u16 + 1; // +1 for slant
let file_len = filename.map(|n| n.len() + 2).unwrap_or(0) as u16 + 1; // +2 for spaces, +1 for slant
area.x + mode_len + file_len
};
// Build the statusline
let mut statusline = StatusLineStacked::new()
// Mode segment (left)
.start(
Span::from(mode_text.clone()).style(
Style::new()
.fg(mode_fg)
.bg(mode_color)
.add_modifier(Modifier::BOLD),
),
Span::from(SLANT_TL_BR).style(Style::new().fg(mode_color).bg(file_bg)),
);
// Filename segment
if let Some(name) = filename {
let file_segment = format!(" {} ", name);
statusline = statusline.start(
Span::from(file_segment).style(Style::new().fg(file_fg).bg(file_bg)),
Span::from(SLANT_TL_BR).style(Style::new().fg(file_bg)),
);
}
// Position segment (right)
statusline = statusline.end(
Span::from(position_text).style(Style::new().fg(position_fg).bg(position_bg)),
Span::from(SLANT_BL_TR).style(Style::new().fg(position_bg)),
);
// Render the statusline base
ratatui::widgets::Widget::render(statusline, area, buf);
// Now render git stats with colored icons (no background)
// Icons from lvim: LineAdded (U+EADC), LineModified (U+EADE), LineRemoved (U+EADF)
// Get git stats from git_stats_state (which manages the caching/updates)
// or fall back to manually-set widget stats
let git_stats = self.git_stats_state.git_stats().or(self.git_stats);
if let Some(stats) = &git_stats {
// Use theme colors for git stats or fall back to defaults
let green = Style::new().fg(self
.app_theme
.as_ref()
.map(|t| t.success)
.unwrap_or(Color::Rgb(152, 195, 121)));
let yellow = Style::new().fg(self
.app_theme
.as_ref()
.map(|t| t.warning)
.unwrap_or(Color::Rgb(229, 192, 123)));
let red = Style::new().fg(self
.app_theme
.as_ref()
.map(|t| t.error)
.unwrap_or(Color::Rgb(224, 108, 117)));
let dim = Style::new().fg(self
.app_theme
.as_ref()
.map(|t| t.text_muted)
.unwrap_or(Color::Rgb(92, 99, 112)));
let mut x = git_stats_start_x;
// Add margin after filename
buf.set_string(x, area.y, " ", dim);
x += 2;
// Added: icon space number space
let add_icon = "\u{EADC}";
let add_num = format!("{}", stats.additions);
buf.set_string(x, area.y, add_icon, green);
x += add_icon.width() as u16;
buf.set_string(x, area.y, " ", green);
x += 1;
buf.set_string(x, area.y, &add_num, green);
x += add_num.width() as u16;
buf.set_string(x, area.y, " ", dim);
x += 1;
// Modified: icon space number space
let mod_icon = "\u{EADE}";
let mod_num = format!("{}", stats.modified);
buf.set_string(x, area.y, mod_icon, yellow);
x += mod_icon.width() as u16;
buf.set_string(x, area.y, " ", yellow);
x += 1;
buf.set_string(x, area.y, &mod_num, yellow);
x += mod_num.width() as u16;
buf.set_string(x, area.y, " ", dim);
x += 1;
// Removed: icon space number
let del_icon = "\u{EADF}";
let del_num = format!("{}", stats.deletions);
buf.set_string(x, area.y, del_icon, red);
x += del_icon.width() as u16;
buf.set_string(x, area.y, " ", red);
x += 1;
buf.set_string(x, area.y, &del_num, red);
}
}
}