endbasic_std/console/
format.rs1use super::Pager;
19use crate::console::Console;
20use std::io;
21
22fn refill(paragraph: &str, width: usize) -> Vec<String> {
27 if paragraph.is_empty() {
28 return vec!["".to_owned()];
29 }
30
31 let mut lines = vec![];
32
33 let mut line = String::new();
34 for word in paragraph.split_whitespace() {
35 if !line.is_empty() {
36 let spaces = if line.ends_with('.') {
40 let first = word.chars().next().expect("Words cannot be empty");
41 if first == first.to_ascii_uppercase() { 2 } else { 1 }
42 } else {
43 1
44 };
45
46 if (line.len() + word.len() + spaces) >= width {
47 lines.push(line);
48 line = String::new();
49 } else {
50 for _ in 0..spaces {
51 line.push(' ');
52 }
53 }
54 }
55 line.push_str(word);
56 }
57 if !line.is_empty() {
58 lines.push(line);
59 }
60
61 lines
62}
63
64fn refill_many<S: AsRef<str>, P: IntoIterator<Item = S>>(
66 paragraphs: P,
67 indent: &str,
68 width: usize,
69) -> Vec<String> {
70 let mut formatted = vec![];
71
72 let mut first = true;
73 for paragraph in paragraphs {
74 let paragraph = paragraph.as_ref();
75
76 if !first {
77 formatted.push(String::new());
78 }
79 first = false;
80
81 let mut extra_indent = String::new();
82 for ch in paragraph.chars() {
83 if ch.is_whitespace() {
86 extra_indent.push(' ');
87 } else {
88 break;
89 }
90 }
91
92 let lines = refill(paragraph, width - 4 - indent.len() - extra_indent.len());
93 for line in lines {
94 if line.is_empty() {
95 formatted.push(String::new());
96 } else {
97 formatted.push(format!("{}{}{}", indent, extra_indent, line));
98 }
99 }
100 }
101
102 formatted
103}
104
105pub fn refill_and_print<S: AsRef<str>, P: IntoIterator<Item = S>>(
110 console: &mut dyn Console,
111 paragraphs: P,
112 indent: &str,
113) -> io::Result<()> {
114 let size = console.size_chars()?;
117 for line in refill_many(paragraphs, indent, usize::from(size.x)) {
118 console.print(&line)?;
119 }
120 Ok(())
121}
122
123pub(crate) async fn refill_and_page<S: AsRef<str>, P: IntoIterator<Item = S>>(
128 pager: &mut Pager<'_>,
129 paragraphs: P,
130 indent: &str,
131) -> io::Result<()> {
132 for line in refill_many(paragraphs, indent, usize::from(pager.columns())) {
133 pager.print(&line).await?;
134 }
135 Ok(())
136}
137
138#[cfg(test)]
139mod tests {
140 use super::*;
141 use crate::console::CharsXY;
142 use crate::testutils::{CapturedOut, MockConsole};
143
144 #[test]
145 fn test_refill_empty() {
146 assert_eq!(&[""], refill("", 0).as_slice());
147 assert_eq!(&[""], refill("", 10).as_slice());
148 }
149
150 #[test]
151 fn test_refill_nothing_fits() {
152 assert_eq!(&["this", "is", "some", "text"], refill("this is some text", 0).as_slice());
153 assert_eq!(&["this", "is", "some", "text"], refill("this is some text", 1).as_slice());
154 }
155
156 #[test]
157 fn test_refill_some_lines() {
158 assert_eq!(
159 &["this is a piece", "of text with", "a-fictitious-very-long-word", "within it"],
160 refill("this is a piece of text with a-fictitious-very-long-word within it", 16)
161 .as_slice()
162 );
163 }
164
165 #[test]
166 fn test_refill_reformats_periods() {
167 assert_eq!(&["foo. bar. baz."], refill("foo. bar. baz.", 100).as_slice());
168 assert_eq!(&["foo. Bar. baz."], refill("foo. Bar. baz.", 100).as_slice());
169 assert_eq!(&["[some .. range]"], refill("[some .. range]", 100).as_slice());
170 }
171
172 #[test]
173 fn test_refill_and_print_empty() {
174 let mut console = MockConsole::default();
175 let paragraphs: &[&str] = &[];
176 refill_and_print(&mut console, paragraphs, " ").unwrap();
177 assert!(console.captured_out().is_empty());
178 }
179
180 #[test]
181 fn test_refill_and_print_one() {
182 let mut console = MockConsole::default();
183 let paragraphs = &["First paragraph"];
184 refill_and_print(&mut console, paragraphs, " ").unwrap();
185 assert_eq!(&[CapturedOut::Print(" First paragraph".to_owned())], console.captured_out());
186 }
187
188 #[test]
189 fn test_refill_and_print_multiple() {
190 let mut console = MockConsole::default();
191 let paragraphs = &["First paragraph", "Second", "Third. The. end."];
192 refill_and_print(&mut console, paragraphs, " ").unwrap();
193 assert_eq!(
194 &[
195 CapturedOut::Print(" First paragraph".to_owned()),
196 CapturedOut::Print("".to_owned()),
197 CapturedOut::Print(" Second".to_owned()),
198 CapturedOut::Print("".to_owned()),
199 CapturedOut::Print(" Third. The. end.".to_owned()),
200 ],
201 console.captured_out()
202 );
203 }
204
205 #[test]
206 fn test_refill_and_print_multiple_with_extra_indent() {
207 let mut console = MockConsole::default();
208 console.set_size_chars(CharsXY { x: 30, y: 30 });
209 let paragraphs = &[
210 "First paragraph that is somewhat long",
211 " The second paragraph contains an extra indent",
212 "Third. The. end.",
213 ];
214 refill_and_print(&mut console, paragraphs, " ").unwrap();
215 assert_eq!(
216 &[
217 CapturedOut::Print(" First paragraph that".to_owned()),
218 CapturedOut::Print(" is somewhat long".to_owned()),
219 CapturedOut::Print("".to_owned()),
220 CapturedOut::Print(" The second".to_owned()),
221 CapturedOut::Print(" paragraph contains".to_owned()),
222 CapturedOut::Print(" an extra indent".to_owned()),
223 CapturedOut::Print("".to_owned()),
224 CapturedOut::Print(" Third. The. end.".to_owned()),
225 ],
226 console.captured_out()
227 );
228 }
229}