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