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