1use rmut_core::config::Config;
6use rmut_core::format;
7use rmut_core::message::MessageView;
8use rmut_session::Session;
9
10use crate::pager::{PagerStyle, RowCache};
11
12pub fn pager_wrap(config: &Config, width: usize) -> usize {
15 match config.pager.wrap {
16 Some(n) if n > 0 => (n as usize).min(width),
17 Some(n) if n < 0 => width.saturating_sub(n.unsigned_abs() as usize).max(20),
18 _ => width,
19 }
20}
21
22pub struct PagerView<'a> {
24 pub view: &'a MessageView,
25 pub rows: &'a RowCache,
27 pub scroll: usize,
28 pub full_headers: bool,
29 pub hide_quoted: bool,
30}
31
32pub fn index_status(session: &Session, index_offset: usize, width: usize, rows: usize) -> String {
33 let fmt = session
34 .config
35 .ui
36 .status_format
37 .as_deref()
38 .unwrap_or(format::DEFAULT_STATUS_FORMAT);
39 format::render_status(fmt, width, &|spec| {
40 index_status_field(session, index_offset, spec, rows)
41 })
42}
43
44pub fn index_title(session: &Session, index_offset: usize, rows: usize) -> String {
47 let fmt = session
48 .config
49 .ui
50 .title_format
51 .as_deref()
52 .unwrap_or("rmut: %f");
53 format::render_status(fmt, 200, &|spec| {
54 index_status_field(session, index_offset, spec, rows)
55 })
56 .trim_end()
57 .to_string()
58}
59
60fn index_status_field(session: &Session, index_offset: usize, spec: char, rows: usize) -> String {
63 match spec {
64 'f' => session.title.clone(),
65 'm' => session.msgs.len().to_string(),
66 'M' => {
68 if session.visible.len() != session.msgs.len() {
69 session.visible.len().to_string()
70 } else {
71 String::new()
72 }
73 }
74 'n' => session.new_count().to_string(),
75 'u' => session
76 .msgs
77 .iter()
78 .filter(|m| !m.env.file.flags.seen)
79 .count()
80 .to_string(),
81 'd' => session.deleted_count().to_string(),
82 'F' => session
83 .msgs
84 .iter()
85 .filter(|m| m.env.file.flags.flagged)
86 .count()
87 .to_string(),
88 't' => session
89 .msgs
90 .iter()
91 .filter(|m| m.env.tagged)
92 .count()
93 .to_string(),
94 's' => format!(
95 "{}{}",
96 session.sort.name(),
97 if session.sort_rev { "-rev" } else { "" }
98 ),
99 'V' => session
100 .limit
101 .as_ref()
102 .map(|(s, _)| s.clone())
103 .unwrap_or_default(),
104 'r' => {
105 let chars: Option<Vec<char>> = session
108 .config
109 .ui
110 .status_chars
111 .as_deref()
112 .map(|s| s.chars().collect());
113 let pick = |i: usize, default: &str| -> String {
114 chars
115 .as_ref()
116 .and_then(|c| c.get(i))
117 .map(|c| c.to_string())
118 .unwrap_or_else(|| default.to_string())
119 };
120 if session.read_only {
121 pick(2, "%")
122 } else if session.pending_count() > 0 {
123 pick(1, "*")
124 } else {
125 pick(0, "")
126 }
127 }
128 'v' => env!("CARGO_PKG_VERSION").to_string(),
129 'P' => {
131 let len = session.visible.len();
132 if len <= rows {
133 "all".into()
134 } else if index_offset == 0 {
135 "top".into()
136 } else if index_offset + rows >= len {
137 "bot".into()
138 } else {
139 format!("{}%", (index_offset + rows) * 100 / len)
140 }
141 }
142 '%' => "%".to_string(),
143 other => format!("%{other}"),
144 }
145}
146
147pub const DEFAULT_PAGER_FORMAT: &str = "---Message %C/%m: %s -- %P";
149
150pub fn pager_status(
154 session: &Session,
155 pager: &PagerView,
156 content_height: usize,
157 width: usize,
158) -> String {
159 let total = pager
160 .rows
161 .rows(
162 pager.view,
163 pager_wrap(&session.config, width),
164 pager.full_headers,
165 &PagerStyle::of(&session.config, &session.quote_re),
166 pager.hide_quoted,
167 )
168 .len()
169 .max(1);
170 let shown = (pager.scroll + content_height).min(total);
171 let subject = pager
172 .view
173 .brief
174 .iter()
175 .find(|(n, _)| n == "Subject" || n == "Content-Type")
176 .map(|(_, v)| v.as_str())
177 .unwrap_or("");
178 let msg = session.visible.get(session.sel).map(|&i| &session.msgs[i]);
179 let fmt = session
180 .config
181 .pager
182 .format
183 .as_deref()
184 .unwrap_or(DEFAULT_PAGER_FORMAT);
185 format::render_status(fmt, width, &|spec| match spec {
186 'C' => (session.sel + 1).to_string(),
187 'm' => session.visible.len().to_string(),
188 's' => subject.to_string(),
189 'n' => msg.map(|m| m.env.from.clone()).unwrap_or_default(),
190 'Z' => msg
191 .map(|m| {
192 let f = &m.env.file;
193 format!(
194 "{}{} ",
195 f.flags.status_char(f.is_new),
196 if f.flags.flagged { '!' } else { ' ' }
197 )
198 })
199 .unwrap_or_default(),
200 'P' => match (pager.scroll, shown >= total) {
203 (0, true) => "all".into(),
204 (_, true) => "end".into(),
205 _ => format!("{}%", shown * 100 / total),
206 },
207 'f' => session.title.clone(),
208 '%' => "%".to_string(),
209 other => format!("%{other}"),
210 })
211}