use crate::{cell::Cell, Constraint, Element, Line, Paint, Size};
pub const DEFAULT_WRAP: usize = 80;
pub const SOFT_TAB: &str = " ";
#[derive(Debug)]
pub struct TextArea {
body: Paint<String>,
wrap: usize,
}
impl TextArea {
pub fn new(body: impl Into<Paint<String>>) -> Self {
Self {
body: body.into(),
wrap: DEFAULT_WRAP,
}
}
pub fn wrap(mut self, cols: usize) -> Self {
self.wrap = cols;
self
}
pub fn lines(&self) -> impl Iterator<Item = String> {
let mut lines: Vec<String> = Vec::new();
let mut fenced = false;
for line in self
.body
.content()
.lines()
.map(|l| l.replace('\t', SOFT_TAB))
{
if line.starts_with("```") {
fenced = !fenced;
}
if fenced || line.starts_with('\t') || line.starts_with(' ') {
lines.push(line.truncate(self.wrap, "…"));
continue;
}
let mut current = String::new();
for word in line.split_whitespace() {
if current.width() + word.width() > self.wrap {
lines.push(current.trim_end().to_owned());
current = word.to_owned();
} else {
current.push_str(word);
}
current.push(' ');
}
lines.push(current.trim_end().to_owned());
}
lines.into_iter()
}
pub fn boxed(self) -> Box<dyn Element> {
Box::new(self)
}
}
impl Element for TextArea {
fn size(&self, _parent: Constraint) -> Size {
let cols = self.lines().map(|l| l.width()).max().unwrap_or(0);
let rows = self.lines().count();
Size::new(cols, rows)
}
fn render(&self, _parent: Constraint) -> Vec<Line> {
self.lines()
.map(|l| Line::new(Paint::new(l).with_style(self.body.style)))
.collect()
}
}
pub fn textarea(content: impl Into<Paint<String>>) -> TextArea {
TextArea::new(content)
}
#[cfg(test)]
mod test {
use super::*;
use pretty_assertions::assert_eq;
#[test]
fn test_wrapping() {
let t = TextArea::new(
"Radicle enables users to run their own nodes, \
ensuring censorship-resistant code collaboration \
and fostering a resilient network without reliance \
on third-parties.",
)
.wrap(50);
let wrapped = t.lines().collect::<Vec<_>>();
assert_eq!(
wrapped,
vec![
"Radicle enables users to run their own nodes,".to_owned(),
"ensuring censorship-resistant code collaboration".to_owned(),
"and fostering a resilient network without reliance".to_owned(),
"on third-parties.".to_owned(),
]
);
}
#[test]
fn test_wrapping_paragraphs() {
let t = TextArea::new(
"Radicle enables users to run their own nodes, \
ensuring censorship-resistant code collaboration \
and fostering a resilient network without reliance \
on third-parties.\n\n\
All social artifacts are stored in git, and signed \
using public-key cryptography. Radicle verifies \
the authenticity and authorship of all data \
automatically.",
)
.wrap(50);
let wrapped = t.lines().collect::<Vec<_>>();
assert_eq!(
wrapped,
vec![
"Radicle enables users to run their own nodes,".to_owned(),
"ensuring censorship-resistant code collaboration".to_owned(),
"and fostering a resilient network without reliance".to_owned(),
"on third-parties.".to_owned(),
"".to_owned(),
"All social artifacts are stored in git, and signed".to_owned(),
"using public-key cryptography. Radicle verifies".to_owned(),
"the authenticity and authorship of all data".to_owned(),
"automatically.".to_owned(),
]
);
}
#[test]
fn test_wrapping_code_block() {
let t = TextArea::new(
"\
Here's an example:
$ git push rad://z3gqcJUoA1n9HaHKufZs5FCSGazv5/z6MksFqXN3Yhqk8pTJdUGLwATkRfQvwZXPqR2qMEhbS9wzpT
$ rad sync
Run the above and wait for your project to sync.\
",
)
.wrap(50);
let wrapped = t.lines().collect::<Vec<_>>();
assert_eq!(
wrapped,
vec![
"Here's an example:".to_owned(),
"".to_owned(),
" $ git push rad://z3gqcJUoA1n9HaHKufZs5FCSGazv5/…".to_owned(),
" $ rad sync".to_owned(),
"".to_owned(),
"Run the above and wait for your project to sync.".to_owned()
]
);
}
#[test]
fn test_wrapping_fenced_block() {
let t = TextArea::new(
"\
Here's an example:
```
$ git push rad://z3gqcJUoA1n9HaHKufZs5FCSGazv5/z6MksFqXN3Yhqk8pTJdUGLwATkRfQvwZXPqR2qMEhbS9wzpT
$ rad sync
```
Run the above and wait for your project to sync.\
",
)
.wrap(40);
let wrapped = t.lines().collect::<Vec<_>>();
assert_eq!(
wrapped,
vec![
"Here's an example:".to_owned(),
"```".to_owned(),
"$ git push rad://z3gqcJUoA1n9HaHKufZs5F…".to_owned(),
"$ rad sync".to_owned(),
"```".to_owned(),
"Run the above and wait for your project".to_owned(),
"to sync.".to_owned()
]
);
}
}