From 9a2c37891b7b9f834632e429d1430a161fa3d591 Mon Sep 17 00:00:00 2001 From: "lincoln auster [they/them]" Date: Tue, 10 Aug 2021 15:52:28 -0600 Subject: [PATCH 01/21] init justification code --- src/commands/selection.rs | 47 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/src/commands/selection.rs b/src/commands/selection.rs index 93cfb049..813d724a 100644 --- a/src/commands/selection.rs +++ b/src/commands/selection.rs @@ -100,6 +100,39 @@ fn copy_to_clipboard(app: &mut Application) -> Result { Ok(()) } +fn justify(txt: impl AsRef, limit: usize) -> String { + let txt = txt.as_ref(); + let mut justified = String::with_capacity(txt.len()); + let mut pars = txt.split("\n\n").peekable(); + + let space_delims = ["", " ", "\n"]; + while let Some(par) = pars.next() { + let mut words = par.split_whitespace(); + let mut len = 0; + let mut first = true; + + while let Some(word) = words.next() { + len += word.len(); + + let over = len > limit; + let u_over = over as usize; + justified += space_delims[((!first as usize) * u_over) + !first as usize]; + + justified += word; + + len += 1; // account for the delim + len *= 1 - u_over; + first = false; + } + + if pars.peek().is_some() { + justified += "\n\n"; // add back the paragraph break. + } + } + + justified +} + #[cfg(test)] mod tests { use crate::commands; @@ -219,4 +252,18 @@ mod tests { String::from("amp\nitor\nbuffer") ) } + + // as simple as it gets: one character words for easy debugging. + #[test] + fn justify_simple() { + let txt = "\ +a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a"; + let jt = super::justify(txt, 80); + assert_eq!( + jt, + "\ +a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a +a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a" + ); + } } From a4c741ee83d6bc60a6f94c9eaf3939d18907cfe7 Mon Sep 17 00:00:00 2001 From: "lincoln auster [they/them]" Date: Tue, 10 Aug 2021 16:39:33 -0600 Subject: [PATCH 02/21] additional test and misc fixes for justify --- src/commands/selection.rs | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/src/commands/selection.rs b/src/commands/selection.rs index 813d724a..c608c727 100644 --- a/src/commands/selection.rs +++ b/src/commands/selection.rs @@ -114,14 +114,15 @@ fn justify(txt: impl AsRef, limit: usize) -> String { while let Some(word) = words.next() { len += word.len(); - let over = len > limit; + let over = len >= limit; let u_over = over as usize; - justified += space_delims[((!first as usize) * u_over) + !first as usize]; - + let idx = (!first as usize) * u_over + !first as usize; + + justified += space_delims[idx]; justified += word; - len += 1; // account for the delim - len *= 1 - u_over; + len += 1; + len = len * (1 - u_over) + word.len() * u_over; first = false; } @@ -266,4 +267,19 @@ a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a" ); } + + #[test] + fn justify_paragraph() { + let txt = "\ +these are words to be used as demos for the thing that this is. this is text \ +reflowing and justification over a few lines. this is just filler text in case \ +it wasn't obvious."; + let jt = super::justify(txt, 80); + assert_eq!( + jt, "\ +these are words to be used as demos for the thing that this is. this is text +reflowing and justification over a few lines. this is just filler text in case +it wasn't obvious." + ); + } } From ebe7df5babe1ddf69fca087439c7656b236ff784 Mon Sep 17 00:00:00 2001 From: "lincoln auster [they/them]" Date: Tue, 10 Aug 2021 17:00:19 -0600 Subject: [PATCH 03/21] additional test case, behavior correction im so sorry about the filler text really i swear --- src/commands/selection.rs | 39 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/src/commands/selection.rs b/src/commands/selection.rs index c608c727..b32b766a 100644 --- a/src/commands/selection.rs +++ b/src/commands/selection.rs @@ -114,15 +114,14 @@ fn justify(txt: impl AsRef, limit: usize) -> String { while let Some(word) = words.next() { len += word.len(); - let over = len >= limit; + let over = len > limit; let u_over = over as usize; let idx = (!first as usize) * u_over + !first as usize; justified += space_delims[idx]; justified += word; - len += 1; - len = len * (1 - u_over) + word.len() * u_over; + len = (len + 1) * (1 - u_over) + (word.len() + 1) * u_over; first = false; } @@ -282,4 +281,38 @@ reflowing and justification over a few lines. this is just filler text in case it wasn't obvious." ); } + + #[test] + fn justify_multiple_pars() { + let txt = "\ +Here's more filler text! So fun fact of the day, I was trying to just copy paste \ +some lorem ipsum to annoy my latin student friends, but honestly it broke the \ +M-q 'justify' function in emacs, which makes it a bit difficult to work with. \ +Overall, it's just not that great with code! + +Fun fact of the day number two, writing random paragraphs of text is honestly \ +taking way more effort than I anticipated, and I deeply apologize for the lack \ +of sanity and coherence here! + +Fun fact of the day number three is that I spent three hours getting this to not \ +branch. There is no way that that micro-optimization will actuall save three \ +hours worth of time, but I did it anyway because I'm actually just stupid!"; + let jt = super::justify(txt, 80); + + assert_eq!( + jt, "\ +Here's more filler text! So fun fact of the day, I was trying to just copy paste +some lorem ipsum to annoy my latin student friends, but honestly it broke the +M-q 'justify' function in emacs, which makes it a bit difficult to work with. +Overall, it's just not that great with code! + +Fun fact of the day number two, writing random paragraphs of text is honestly +taking way more effort than I anticipated, and I deeply apologize for the lack +of sanity and coherence here! + +Fun fact of the day number three is that I spent three hours getting this to not +branch. There is no way that that micro-optimization will actuall save three +hours worth of time, but I did it anyway because I'm actually just stupid!" + ); + } } From 684431000df17f1678ddb57145d5633ce5125fe7 Mon Sep 17 00:00:00 2001 From: "lincoln auster [they/them]" Date: Wed, 11 Aug 2021 11:27:27 -0600 Subject: [PATCH 04/21] rename `justify` -> `justify_str` in preperation for command --- src/commands/selection.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/commands/selection.rs b/src/commands/selection.rs index b32b766a..06f52b91 100644 --- a/src/commands/selection.rs +++ b/src/commands/selection.rs @@ -100,7 +100,7 @@ fn copy_to_clipboard(app: &mut Application) -> Result { Ok(()) } -fn justify(txt: impl AsRef, limit: usize) -> String { +fn justify_str(txt: impl AsRef, limit: usize) -> String { let txt = txt.as_ref(); let mut justified = String::with_capacity(txt.len()); let mut pars = txt.split("\n\n").peekable(); @@ -258,7 +258,7 @@ mod tests { fn justify_simple() { let txt = "\ a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a"; - let jt = super::justify(txt, 80); + let jt = super::justify_str(txt, 80); assert_eq!( jt, "\ @@ -273,7 +273,7 @@ a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a" these are words to be used as demos for the thing that this is. this is text \ reflowing and justification over a few lines. this is just filler text in case \ it wasn't obvious."; - let jt = super::justify(txt, 80); + let jt = super::justify_str(txt, 80); assert_eq!( jt, "\ these are words to be used as demos for the thing that this is. this is text @@ -297,7 +297,7 @@ of sanity and coherence here! Fun fact of the day number three is that I spent three hours getting this to not \ branch. There is no way that that micro-optimization will actuall save three \ hours worth of time, but I did it anyway because I'm actually just stupid!"; - let jt = super::justify(txt, 80); + let jt = super::justify_str(txt, 80); assert_eq!( jt, "\ From 5f87832cf430bfea8ffa0e77eb965ad3ecf87c94 Mon Sep 17 00:00:00 2001 From: "lincoln auster [they/them]" Date: Wed, 11 Aug 2021 11:57:55 -0600 Subject: [PATCH 05/21] extract sel_to_range --- src/commands/selection.rs | 60 +++++++++++++++++++++++---------------- 1 file changed, 35 insertions(+), 25 deletions(-) diff --git a/src/commands/selection.rs b/src/commands/selection.rs index 06f52b91..35bb7a38 100644 --- a/src/commands/selection.rs +++ b/src/commands/selection.rs @@ -6,31 +6,10 @@ use crate::commands::{self, Result}; use crate::util; pub fn delete(app: &mut Application) -> Result { - if let Some(buffer) = app.workspace.current_buffer() { - match app.mode { - Mode::Select(ref select_mode) => { - let cursor_position = *buffer.cursor.clone(); - let delete_range = Range::new(cursor_position, select_mode.anchor); - buffer.delete_range(delete_range.clone()); - buffer.cursor.move_to(delete_range.start()); - } - Mode::SelectLine(ref mode) => { - let delete_range = mode.to_range(&*buffer.cursor); - buffer.delete_range(delete_range.clone()); - buffer.cursor.move_to(delete_range.start()); - } - Mode::Search(ref mode) => { - let selection = mode.results - .as_ref() - .and_then(|r| r.selection()) - .ok_or("Can't delete in search mode without a selected result")?; - buffer.delete_range(selection.clone()); - } - _ => bail!("Can't delete selections outside of select mode"), - }; - } else { - bail!(BUFFER_MISSING); - } + let rng = sel_to_range(app)?; + let buf = app.workspace.current_buffer().unwrap(); + buf.delete_range(rng.clone()); + buf.cursor.move_to(rng.start()); Ok(()) } @@ -133,6 +112,37 @@ fn justify_str(txt: impl AsRef, limit: usize) -> String { justified } +pub fn sel_to_range(app: &mut Application) -> std::result::Result { + let buf = app.workspace.current_buffer().ok_or(BUFFER_MISSING)?; + + match app.mode { + Mode::Select(ref sel) => { + let cursor_position = *buf.cursor.clone(); + Ok(Range::new(cursor_position, sel.anchor)) + }, + Mode::SelectLine(ref sel) => { + Ok(util::inclusive_range( + &LineRange::new( + sel.anchor, + buf.cursor.line + ), + buf + )) + }, + Mode::Search(ref search) => { + Ok(search + .results + .as_ref() + .and_then(|r| r.selection()) + .ok_or("A selection is required.") + .unwrap() + .clone() + ) + } + _ => bail!("A selection is required."), + } +} + #[cfg(test)] mod tests { use crate::commands; From d4235702875a4509e63d6080529758a27bf250b2 Mon Sep 17 00:00:00 2001 From: "lincoln auster [they/them]" Date: Wed, 11 Aug 2021 12:18:18 -0600 Subject: [PATCH 06/21] use justify command and bind it to `z` in select and select_line --- src/commands/selection.rs | 20 +++++++++++++++++++- src/input/key_map/default.yml | 2 ++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/commands/selection.rs b/src/commands/selection.rs index 35bb7a38..5a07ce88 100644 --- a/src/commands/selection.rs +++ b/src/commands/selection.rs @@ -79,6 +79,24 @@ fn copy_to_clipboard(app: &mut Application) -> Result { Ok(()) } +pub fn justify(app: &mut Application) -> Result { + let rng = sel_to_range(app)?; + let buf = app.workspace.current_buffer().unwrap(); + + let txt = buf.read(&rng).unwrap(); + let tar = match app.preferences.borrow().line_length_guide() { + Some(n) => n, + None => bail!("Justification requires a line_length_guide."), + }; + + let jtxt = justify_str(txt, tar); + buf.delete_range(rng.clone()); + buf.cursor.move_to(rng.start()); + buf.insert(jtxt); + + Ok(()) +} + fn justify_str(txt: impl AsRef, limit: usize) -> String { let txt = txt.as_ref(); let mut justified = String::with_capacity(txt.len()); @@ -112,7 +130,7 @@ fn justify_str(txt: impl AsRef, limit: usize) -> String { justified } -pub fn sel_to_range(app: &mut Application) -> std::result::Result { +fn sel_to_range(app: &mut Application) -> std::result::Result { let buf = app.workspace.current_buffer().ok_or(BUFFER_MISSING)?; match app.mode { diff --git a/src/input/key_map/default.yml b/src/input/key_map/default.yml index 089854d0..a71d00c3 100644 --- a/src/input/key_map/default.yml +++ b/src/input/key_map/default.yml @@ -218,6 +218,7 @@ select: R: git::copy_remote_url m: view::scroll_down f: application::switch_to_second_stage_jump_mode + z: selection::justify "'": application::switch_to_jump_mode ",": view::scroll_up page_up: view::scroll_up @@ -263,6 +264,7 @@ select_line: R: git::copy_remote_url m: view::scroll_down f: application::switch_to_second_stage_jump_mode + z: selection::justify ",": view::scroll_up ">": buffer::indent_line "<": buffer::outdent_line From 6ab4430305f9df19413ca0238629706dff95e9b5 Mon Sep 17 00:00:00 2001 From: "lincoln auster [they/them]" Date: Sat, 14 Aug 2021 17:38:06 -0600 Subject: [PATCH 07/21] introduce prefix param --- src/commands/selection.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/commands/selection.rs b/src/commands/selection.rs index 5a07ce88..b20d0281 100644 --- a/src/commands/selection.rs +++ b/src/commands/selection.rs @@ -89,7 +89,7 @@ pub fn justify(app: &mut Application) -> Result { None => bail!("Justification requires a line_length_guide."), }; - let jtxt = justify_str(txt, tar); + let jtxt = justify_str(txt, "", tar); buf.delete_range(rng.clone()); buf.cursor.move_to(rng.start()); buf.insert(jtxt); @@ -97,7 +97,7 @@ pub fn justify(app: &mut Application) -> Result { Ok(()) } -fn justify_str(txt: impl AsRef, limit: usize) -> String { +fn justify_str(txt: impl AsRef, _prefix: &str, limit: usize) -> String { let txt = txt.as_ref(); let mut justified = String::with_capacity(txt.len()); let mut pars = txt.split("\n\n").peekable(); @@ -286,7 +286,7 @@ mod tests { fn justify_simple() { let txt = "\ a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a"; - let jt = super::justify_str(txt, 80); + let jt = super::justify_str(txt, "", 80); assert_eq!( jt, "\ @@ -301,7 +301,7 @@ a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a" these are words to be used as demos for the thing that this is. this is text \ reflowing and justification over a few lines. this is just filler text in case \ it wasn't obvious."; - let jt = super::justify_str(txt, 80); + let jt = super::justify_str(txt, "", 80); assert_eq!( jt, "\ these are words to be used as demos for the thing that this is. this is text @@ -325,7 +325,7 @@ of sanity and coherence here! Fun fact of the day number three is that I spent three hours getting this to not \ branch. There is no way that that micro-optimization will actuall save three \ hours worth of time, but I did it anyway because I'm actually just stupid!"; - let jt = super::justify_str(txt, 80); + let jt = super::justify_str(txt, "", 80); assert_eq!( jt, "\ From d142ff1b04ba89631fac4618aee20f4f153f6d85 Mon Sep 17 00:00:00 2001 From: "lincoln auster [they/them]" Date: Sat, 14 Aug 2021 18:00:17 -0600 Subject: [PATCH 08/21] basic prefixing --- src/commands/selection.rs | 37 +++++++++++++++++++++++++++++++++---- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/src/commands/selection.rs b/src/commands/selection.rs index b20d0281..35b6296f 100644 --- a/src/commands/selection.rs +++ b/src/commands/selection.rs @@ -97,27 +97,42 @@ pub fn justify(app: &mut Application) -> Result { Ok(()) } -fn justify_str(txt: impl AsRef, _prefix: &str, limit: usize) -> String { +fn justify_str(txt: impl AsRef, prefix: &str, mut limit: usize) -> String { let txt = txt.as_ref(); let mut justified = String::with_capacity(txt.len()); let mut pars = txt.split("\n\n").peekable(); - let space_delims = ["", " ", "\n"]; + let mut space_delims = ["".to_string(), " ".to_string(), "\n".to_string()]; + if prefix != "" { + space_delims[0] += prefix; + space_delims[0] += " "; + space_delims[2] += prefix; + space_delims[2] += " "; + limit -= prefix.len() + 1; + } + while let Some(par) = pars.next() { let mut words = par.split_whitespace(); let mut len = 0; let mut first = true; while let Some(word) = words.next() { + if word == prefix { + continue; + } + len += word.len(); let over = len > limit; let u_over = over as usize; let idx = (!first as usize) * u_over + !first as usize; - justified += space_delims[idx]; + justified += &space_delims[idx]; justified += word; - + + // if we're over, set the length to 0, otherwise increment it + // properly. This just does that mith multiplication by 0 instead of + // branching. len = (len + 1) * (1 - u_over) + (word.len() + 1) * u_over; first = false; } @@ -343,4 +358,18 @@ branch. There is no way that that micro-optimization will actuall save three hours worth of time, but I did it anyway because I'm actually just stupid!" ); } + + #[test] + fn justify_simple_prefix() { + let txt = "\ +a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a"; + let jt = super::justify_str(txt, "#", 80); + assert_eq!( + jt, + "\ +# a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a +# a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a" + ); + } + } From e0e9f79b18546429a7722b0566db1b598a2d8b80 Mon Sep 17 00:00:00 2001 From: "lincoln auster [they/them]" Date: Sat, 14 Aug 2021 18:07:30 -0600 Subject: [PATCH 09/21] extra test for prefixes make sure that extraneous (early and late) line breaks with extra unwieldy prefixes don't break things. --- src/commands/selection.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/commands/selection.rs b/src/commands/selection.rs index 35b6296f..91f5f8a8 100644 --- a/src/commands/selection.rs +++ b/src/commands/selection.rs @@ -372,4 +372,21 @@ a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a ); } + #[test] + fn justify_paragraph_prefix() { + let txt ="\ +// filler text meant +// to do stuff and things that end up with text nicely \ +wrappped around a comment delimiter such as the double slashes in c-style \ +languages."; + let jt = super::justify_str(txt, "//", 80); + assert_eq!( + jt, + "\ +// filler text meant to do stuff and things that end up with text nicely +// wrappped around a comment delimiter such as the double slashes in c-style +// languages.", + ); + } + } From a31ee71a7699bd024c443f233f1cc7c3b2fbe1e6 Mon Sep 17 00:00:00 2001 From: "lincoln auster [they/them]" Date: Sat, 14 Aug 2021 18:29:00 -0600 Subject: [PATCH 10/21] infer prefix --- src/commands/selection.rs | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/commands/selection.rs b/src/commands/selection.rs index 91f5f8a8..04d7a174 100644 --- a/src/commands/selection.rs +++ b/src/commands/selection.rs @@ -84,12 +84,14 @@ pub fn justify(app: &mut Application) -> Result { let buf = app.workspace.current_buffer().unwrap(); let txt = buf.read(&rng).unwrap(); + let prefix = infer_prefix(&txt)?; + let tar = match app.preferences.borrow().line_length_guide() { Some(n) => n, None => bail!("Justification requires a line_length_guide."), }; - let jtxt = justify_str(txt, "", tar); + let jtxt = justify_str(txt, &prefix, tar); buf.delete_range(rng.clone()); buf.cursor.move_to(rng.start()); buf.insert(jtxt); @@ -97,6 +99,17 @@ pub fn justify(app: &mut Application) -> Result { Ok(()) } +fn infer_prefix(txt: &str) -> std::result::Result { + match txt.split_whitespace().next() { + Some(n) => if n.chars().next().unwrap().is_alphanumeric() { + Ok("".to_string()) + } else { + Ok(n.to_string()) + }, + None => bail!("Selection is empty."), + } +} + fn justify_str(txt: impl AsRef, prefix: &str, mut limit: usize) -> String { let txt = txt.as_ref(); let mut justified = String::with_capacity(txt.len()); From a46f8dd8c0bce8e38874feadbb6bf5e90ec73755 Mon Sep 17 00:00:00 2001 From: "lincoln auster [they/them]" Date: Sun, 22 Aug 2021 17:18:16 -0600 Subject: [PATCH 11/21] thin down command logic to a custom type --- src/commands/selection.rs | 179 ++--------------------------- src/util/mod.rs | 1 + src/util/reflow.rs | 236 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 248 insertions(+), 168 deletions(-) create mode 100644 src/util/reflow.rs diff --git a/src/commands/selection.rs b/src/commands/selection.rs index 04d7a174..ca4b49fd 100644 --- a/src/commands/selection.rs +++ b/src/commands/selection.rs @@ -4,6 +4,7 @@ use super::application; use crate::errors::*; use crate::commands::{self, Result}; use crate::util; +use crate::util::reflow::Reflow; pub fn delete(app: &mut Application) -> Result { let rng = sel_to_range(app)?; @@ -81,81 +82,17 @@ fn copy_to_clipboard(app: &mut Application) -> Result { pub fn justify(app: &mut Application) -> Result { let rng = sel_to_range(app)?; - let buf = app.workspace.current_buffer().unwrap(); + let mut buf = app.workspace.current_buffer().unwrap(); - let txt = buf.read(&rng).unwrap(); - let prefix = infer_prefix(&txt)?; - let tar = match app.preferences.borrow().line_length_guide() { - Some(n) => n, - None => bail!("Justification requires a line_length_guide."), + Some(n) => n, + None => bail!("Justification requires a line_length_guide."), }; - - let jtxt = justify_str(txt, &prefix, tar); - buf.delete_range(rng.clone()); - buf.cursor.move_to(rng.start()); - buf.insert(jtxt); - - Ok(()) -} - -fn infer_prefix(txt: &str) -> std::result::Result { - match txt.split_whitespace().next() { - Some(n) => if n.chars().next().unwrap().is_alphanumeric() { - Ok("".to_string()) - } else { - Ok(n.to_string()) - }, - None => bail!("Selection is empty."), - } -} - -fn justify_str(txt: impl AsRef, prefix: &str, mut limit: usize) -> String { - let txt = txt.as_ref(); - let mut justified = String::with_capacity(txt.len()); - let mut pars = txt.split("\n\n").peekable(); - - let mut space_delims = ["".to_string(), " ".to_string(), "\n".to_string()]; - if prefix != "" { - space_delims[0] += prefix; - space_delims[0] += " "; - space_delims[2] += prefix; - space_delims[2] += " "; - limit -= prefix.len() + 1; - } - - while let Some(par) = pars.next() { - let mut words = par.split_whitespace(); - let mut len = 0; - let mut first = true; - - while let Some(word) = words.next() { - if word == prefix { - continue; - } - - len += word.len(); - - let over = len > limit; - let u_over = over as usize; - let idx = (!first as usize) * u_over + !first as usize; - - justified += &space_delims[idx]; - justified += word; - - // if we're over, set the length to 0, otherwise increment it - // properly. This just does that mith multiplication by 0 instead of - // branching. - len = (len + 1) * (1 - u_over) + (word.len() + 1) * u_over; - first = false; - } - if pars.peek().is_some() { - justified += "\n\n"; // add back the paragraph break. - } - } + let rfl = Reflow::new(&mut buf, rng, tar); + rfl.apply()?; - justified + Ok(()) } fn sel_to_range(app: &mut Application) -> std::result::Result { @@ -168,10 +105,10 @@ fn sel_to_range(app: &mut Application) -> std::result::Result { }, Mode::SelectLine(ref sel) => { Ok(util::inclusive_range( - &LineRange::new( - sel.anchor, - buf.cursor.line - ), + &LineRange::new( + sel.anchor, + buf.cursor.line + ), buf )) }, @@ -308,98 +245,4 @@ mod tests { String::from("amp\nitor\nbuffer") ) } - - // as simple as it gets: one character words for easy debugging. - #[test] - fn justify_simple() { - let txt = "\ -a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a"; - let jt = super::justify_str(txt, "", 80); - assert_eq!( - jt, - "\ -a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a -a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a" - ); - } - - #[test] - fn justify_paragraph() { - let txt = "\ -these are words to be used as demos for the thing that this is. this is text \ -reflowing and justification over a few lines. this is just filler text in case \ -it wasn't obvious."; - let jt = super::justify_str(txt, "", 80); - assert_eq!( - jt, "\ -these are words to be used as demos for the thing that this is. this is text -reflowing and justification over a few lines. this is just filler text in case -it wasn't obvious." - ); - } - - #[test] - fn justify_multiple_pars() { - let txt = "\ -Here's more filler text! So fun fact of the day, I was trying to just copy paste \ -some lorem ipsum to annoy my latin student friends, but honestly it broke the \ -M-q 'justify' function in emacs, which makes it a bit difficult to work with. \ -Overall, it's just not that great with code! - -Fun fact of the day number two, writing random paragraphs of text is honestly \ -taking way more effort than I anticipated, and I deeply apologize for the lack \ -of sanity and coherence here! - -Fun fact of the day number three is that I spent three hours getting this to not \ -branch. There is no way that that micro-optimization will actuall save three \ -hours worth of time, but I did it anyway because I'm actually just stupid!"; - let jt = super::justify_str(txt, "", 80); - - assert_eq!( - jt, "\ -Here's more filler text! So fun fact of the day, I was trying to just copy paste -some lorem ipsum to annoy my latin student friends, but honestly it broke the -M-q 'justify' function in emacs, which makes it a bit difficult to work with. -Overall, it's just not that great with code! - -Fun fact of the day number two, writing random paragraphs of text is honestly -taking way more effort than I anticipated, and I deeply apologize for the lack -of sanity and coherence here! - -Fun fact of the day number three is that I spent three hours getting this to not -branch. There is no way that that micro-optimization will actuall save three -hours worth of time, but I did it anyway because I'm actually just stupid!" - ); - } - - #[test] - fn justify_simple_prefix() { - let txt = "\ -a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a"; - let jt = super::justify_str(txt, "#", 80); - assert_eq!( - jt, - "\ -# a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a -# a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a" - ); - } - - #[test] - fn justify_paragraph_prefix() { - let txt ="\ -// filler text meant -// to do stuff and things that end up with text nicely \ -wrappped around a comment delimiter such as the double slashes in c-style \ -languages."; - let jt = super::justify_str(txt, "//", 80); - assert_eq!( - jt, - "\ -// filler text meant to do stuff and things that end up with text nicely -// wrappped around a comment delimiter such as the double slashes in c-style -// languages.", - ); - } - } diff --git a/src/util/mod.rs b/src/util/mod.rs index 55767bf4..e26c72bc 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -2,6 +2,7 @@ pub use self::selectable_vec::SelectableVec; pub mod movement_lexer; mod selectable_vec; +pub mod reflow; pub mod token; use crate::errors::*; diff --git a/src/util/reflow.rs b/src/util/reflow.rs new file mode 100644 index 00000000..11a7c606 --- /dev/null +++ b/src/util/reflow.rs @@ -0,0 +1,236 @@ +use super::*; + +/// Encapsulate reflow logic for buffer manipulation. +pub struct Reflow<'a> { + buf: &'a mut Buffer, + rng: Range, + txt: String, + lmt: usize, +} + +impl<'a> Reflow<'a> { + /// Create a reflow instance, where buffer and range determine the target, + /// and the limit is the maximum length of a line, regardless of prefixes. + pub fn new(buf: &'a mut Buffer, rng: Range, lmt: usize) -> Self { + let txt = buf.read(&rng).unwrap(); + Self { buf, rng, txt, lmt } + } + + pub fn apply(mut self) -> std::result::Result<(), Error> { + let prefix = self.infer_prefix()?; + let jtxt = self.justify_str(&prefix); + self.buf.delete_range(self.rng.clone()); + self.buf.cursor.move_to(self.rng.start()); + self.buf.insert(jtxt); + + Ok(()) + } + + fn infer_prefix(&self) -> std::result::Result { + match self.txt.split_whitespace().next() { + Some(n) => if n.chars().next().unwrap().is_alphanumeric() { + Ok("".to_string()) + } else { + Ok(n.to_string()) + }, + None => bail!("Selection is empty."), + } + } + + + fn justify_str(&mut self, prefix: &str) -> String { + let txt = self.buf.read(&self.rng).unwrap(); + let mut limit = self.lmt; + let mut justified = String::with_capacity(txt.len()); + let mut pars = txt.split("\n\n").peekable(); + + let mut space_delims = ["".to_string(), " ".to_string(), "\n".to_string()]; + if prefix != "" { + space_delims[0] += prefix; + space_delims[0] += " "; + space_delims[2] += prefix; + space_delims[2] += " "; + limit -= prefix.len() + 1; + } + + while let Some(par) = pars.next() { + let mut words = par.split_whitespace(); + let mut len = 0; + let mut first = true; + + while let Some(word) = words.next() { + if word == prefix { + continue; + } + + len += word.len(); + + let over = len > limit; + let u_over = over as usize; + let idx = (!first as usize) * u_over + !first as usize; + + justified += &space_delims[idx]; + justified += word; + + // if we're over, set the length to 0, otherwise increment it + // properly. This just does that mith multiplication by 0 instead of + // branching. + len = (len + 1) * (1 - u_over) + (word.len() + 1) * u_over; + first = false; + } + + if pars.peek().is_some() { + justified += "\n\n"; // add back the paragraph break. + } + } + + justified + } +} + +#[cfg(test)] +mod tests { + use super::*; + + // as simple as it gets: one character words for easy debugging. + #[test] + fn justify_simple() { + let mut buf = Buffer::new(); + buf.insert("\ +a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a\n"); + + Reflow::new( + &mut buf, + Range::new( + scribe::buffer::Position { line: 0, offset: 0 }, + scribe::buffer::Position { line: 1, offset: 0 }, + ), + 80, + ).apply().unwrap(); + + assert_eq!( + buf.data(), + "\ +a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a +a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a" + ); + } + + #[test] + fn justify_paragraph() { + let mut buf = Buffer::new(); + buf.insert("\ +these are words to be used as demos for the thing that this is. this is text \ +reflowing and justification over a few lines. this is just filler text in case \ +it wasn't obvious.\n" + ); + + Reflow::new( + &mut buf, + Range::new( + scribe::buffer::Position { line: 0, offset: 0 }, + scribe::buffer::Position { line: 1, offset: 0 }, + ), + 80, + ).apply().unwrap(); + assert_eq!( + buf.data(), "\ +these are words to be used as demos for the thing that this is. this is text +reflowing and justification over a few lines. this is just filler text in case +it wasn't obvious." + ); + } + + #[test] + fn justify_multiple_pars() { + let mut buf = Buffer::new(); + buf.insert("\ +Here's more filler text! So fun fact of the day, I was trying to just copy paste \ +some lorem ipsum to annoy my latin student friends, but honestly it broke the \ +M-q 'justify' function in emacs, which makes it a bit difficult to work with. \ +Overall, it's just not that great with code! + +Fun fact of the day number two, writing random paragraphs of text is honestly \ +taking way more effort than I anticipated, and I deeply apologize for the lack \ +of sanity and coherence here! + +Fun fact of the day number three is that I spent three hours getting this to not \ +branch. There is no way that that micro-optimization will actually save three \ +hours worth of time, but I did it anyway for no good reason!\n" + ); + + Reflow::new( + &mut buf, + Range::new( + scribe::buffer::Position { line: 0, offset: 0 }, + scribe::buffer::Position { line: 5, offset: 0 }, + ), + 80, + ).apply().unwrap(); + + assert_eq!( + buf.data(), "\ +Here's more filler text! So fun fact of the day, I was trying to just copy paste +some lorem ipsum to annoy my latin student friends, but honestly it broke the +M-q 'justify' function in emacs, which makes it a bit difficult to work with. +Overall, it's just not that great with code! + +Fun fact of the day number two, writing random paragraphs of text is honestly +taking way more effort than I anticipated, and I deeply apologize for the lack +of sanity and coherence here! + +Fun fact of the day number three is that I spent three hours getting this to not +branch. There is no way that that micro-optimization will actually save three +hours worth of time, but I did it anyway for no good reason!" + ); + } + + #[test] + fn justify_simple_prefix() { + let mut buf = Buffer::new(); + buf.insert("\ +# a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a\n" + ); + Reflow::new( + &mut buf, + Range::new( + scribe::buffer::Position { line: 0, offset: 0 }, + scribe::buffer::Position { line: 1, offset: 0 }, + ), + 80, + ).apply().unwrap(); + + assert_eq!( + buf.data(), "\ +# a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a +# a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a" + ); + } + + #[test] + fn justify_paragraph_prefix() { + let mut buf = Buffer::new(); + buf.insert("\ +// filler text meant +// to do stuff and things that end up with text nicely \ +wrappped around a comment delimiter such as the double slashes in c-style \ +languages.\n" + ); + + Reflow::new( + &mut buf, + Range::new( + scribe::buffer::Position { line: 0, offset: 0 }, + scribe::buffer::Position { line: 2, offset: 0 }, + ), + 80, + ).apply().unwrap(); + + assert_eq!( + buf.data(), "\ +// filler text meant to do stuff and things that end up with text nicely +// wrappped around a comment delimiter such as the double slashes in c-style +// languages.", + ); + } +} From 677250b6582569e251a7f00440d91f041d831400 Mon Sep 17 00:00:00 2001 From: "lincoln auster [they/them]" Date: Sun, 22 Aug 2021 19:28:01 -0600 Subject: [PATCH 12/21] don't assume that the selection is vaild. Use explicit error handling when we get at the selection the Reflow type constructor. --- src/commands/selection.rs | 2 +- src/util/reflow.rs | 21 +++++++++++++-------- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/commands/selection.rs b/src/commands/selection.rs index ca4b49fd..f5847862 100644 --- a/src/commands/selection.rs +++ b/src/commands/selection.rs @@ -89,7 +89,7 @@ pub fn justify(app: &mut Application) -> Result { None => bail!("Justification requires a line_length_guide."), }; - let rfl = Reflow::new(&mut buf, rng, tar); + let rfl = Reflow::new(&mut buf, rng, tar)?; rfl.apply()?; Ok(()) diff --git a/src/util/reflow.rs b/src/util/reflow.rs index 11a7c606..41d56421 100644 --- a/src/util/reflow.rs +++ b/src/util/reflow.rs @@ -11,9 +11,14 @@ pub struct Reflow<'a> { impl<'a> Reflow<'a> { /// Create a reflow instance, where buffer and range determine the target, /// and the limit is the maximum length of a line, regardless of prefixes. - pub fn new(buf: &'a mut Buffer, rng: Range, lmt: usize) -> Self { - let txt = buf.read(&rng).unwrap(); - Self { buf, rng, txt, lmt } + pub fn new( + buf: &'a mut Buffer, rng: Range, lmt: usize + ) -> std::result::Result { + let txt = match buf.read(&rng) { + Some(t) => t, + None => bail!("Selection is invalid."), + }; + Ok(Self { buf, rng, txt, lmt }) } pub fn apply(mut self) -> std::result::Result<(), Error> { @@ -106,7 +111,7 @@ a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a a scribe::buffer::Position { line: 1, offset: 0 }, ), 80, - ).apply().unwrap(); + ).unwrap().apply().unwrap(); assert_eq!( buf.data(), @@ -132,7 +137,7 @@ it wasn't obvious.\n" scribe::buffer::Position { line: 1, offset: 0 }, ), 80, - ).apply().unwrap(); + ).unwrap().apply().unwrap(); assert_eq!( buf.data(), "\ these are words to be used as demos for the thing that this is. this is text @@ -166,7 +171,7 @@ hours worth of time, but I did it anyway for no good reason!\n" scribe::buffer::Position { line: 5, offset: 0 }, ), 80, - ).apply().unwrap(); + ).unwrap().apply().unwrap(); assert_eq!( buf.data(), "\ @@ -198,7 +203,7 @@ hours worth of time, but I did it anyway for no good reason!" scribe::buffer::Position { line: 1, offset: 0 }, ), 80, - ).apply().unwrap(); + ).unwrap().apply().unwrap(); assert_eq!( buf.data(), "\ @@ -224,7 +229,7 @@ languages.\n" scribe::buffer::Position { line: 2, offset: 0 }, ), 80, - ).apply().unwrap(); + ).unwrap().apply().unwrap(); assert_eq!( buf.data(), "\ From c6c0f85c8a85e2c3ae28cdf718666d81a124d701 Mon Sep 17 00:00:00 2001 From: "lincoln auster [they/them]" Date: Sun, 5 Sep 2021 13:52:29 -0600 Subject: [PATCH 13/21] don't use empty gitmodules --- .gitmodules | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 .gitmodules diff --git a/.gitmodules b/.gitmodules deleted file mode 100644 index e69de29b..00000000 From 9f22d4809e1ac82f79adbbd44b903936344d3038 Mon Sep 17 00:00:00 2001 From: lincoln auster Date: Sun, 5 Sep 2021 16:00:56 -0600 Subject: [PATCH 14/21] don't blindly unwrap Co-authored-by: Jordan MacDonald --- src/commands/selection.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/commands/selection.rs b/src/commands/selection.rs index f5847862..7ab71964 100644 --- a/src/commands/selection.rs +++ b/src/commands/selection.rs @@ -117,9 +117,8 @@ fn sel_to_range(app: &mut Application) -> std::result::Result { .results .as_ref() .and_then(|r| r.selection()) - .ok_or("A selection is required.") - .unwrap() - .clone() + .ok_or("A selection is required.")? + .map(|s| s.clone()) ) } _ => bail!("A selection is required."), From fdec763bbc8b5c5c8ecf9c02227bbe73cde58479 Mon Sep 17 00:00:00 2001 From: lincoln auster Date: Sun, 5 Sep 2021 16:01:35 -0600 Subject: [PATCH 15/21] use sel.to_range instead of manually wrapping the range Co-authored-by: Jordan MacDonald --- src/commands/selection.rs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/commands/selection.rs b/src/commands/selection.rs index 7ab71964..9d665cde 100644 --- a/src/commands/selection.rs +++ b/src/commands/selection.rs @@ -104,13 +104,7 @@ fn sel_to_range(app: &mut Application) -> std::result::Result { Ok(Range::new(cursor_position, sel.anchor)) }, Mode::SelectLine(ref sel) => { - Ok(util::inclusive_range( - &LineRange::new( - sel.anchor, - buf.cursor.line - ), - buf - )) + Ok(sel.to_range(&*buf.cursor)) }, Mode::Search(ref search) => { Ok(search From 171673908518d6bd19fedd7599fa15836e1d943f Mon Sep 17 00:00:00 2001 From: "lincoln auster [they/them]" Date: Sun, 5 Sep 2021 16:03:50 -0600 Subject: [PATCH 16/21] don't map --- src/commands/selection.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/commands/selection.rs b/src/commands/selection.rs index 9d665cde..d8bf1afc 100644 --- a/src/commands/selection.rs +++ b/src/commands/selection.rs @@ -112,8 +112,7 @@ fn sel_to_range(app: &mut Application) -> std::result::Result { .as_ref() .and_then(|r| r.selection()) .ok_or("A selection is required.")? - .map(|s| s.clone()) - ) + .clone()) } _ => bail!("A selection is required."), } From 6adf8c20f006ce5787ba90ca9627c59265b50da8 Mon Sep 17 00:00:00 2001 From: "lincoln auster [they/them]" Date: Sun, 5 Sep 2021 16:05:04 -0600 Subject: [PATCH 17/21] clear names --- src/commands/selection.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/commands/selection.rs b/src/commands/selection.rs index d8bf1afc..92e0da40 100644 --- a/src/commands/selection.rs +++ b/src/commands/selection.rs @@ -81,16 +81,15 @@ fn copy_to_clipboard(app: &mut Application) -> Result { } pub fn justify(app: &mut Application) -> Result { - let rng = sel_to_range(app)?; - let mut buf = app.workspace.current_buffer().unwrap(); + let range = sel_to_range(app)?; + let mut buffer = app.workspace.current_buffer().unwrap(); - let tar = match app.preferences.borrow().line_length_guide() { + let limit = match app.preferences.borrow().line_length_guide() { Some(n) => n, None => bail!("Justification requires a line_length_guide."), }; - let rfl = Reflow::new(&mut buf, rng, tar)?; - rfl.apply()?; + Reflow::new(&mut buf, range, limit)?.apply()?; Ok(()) } From cc80df34ff71e58f28f5411abffdf15e3c8d2288 Mon Sep 17 00:00:00 2001 From: "lincoln auster [they/them]" Date: Sun, 5 Sep 2021 16:08:35 -0600 Subject: [PATCH 18/21] fix name issue --- src/commands/selection.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/commands/selection.rs b/src/commands/selection.rs index 92e0da40..32cf8efc 100644 --- a/src/commands/selection.rs +++ b/src/commands/selection.rs @@ -89,7 +89,7 @@ pub fn justify(app: &mut Application) -> Result { None => bail!("Justification requires a line_length_guide."), }; - Reflow::new(&mut buf, range, limit)?.apply()?; + Reflow::new(&mut buffer, range, limit)?.apply()?; Ok(()) } From c565673877a34fa09d9e70faa91b48466e41f3d9 Mon Sep 17 00:00:00 2001 From: "lincoln auster [they/them]" Date: Sun, 5 Sep 2021 16:09:50 -0600 Subject: [PATCH 19/21] shorten error handling logic --- src/util/reflow.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/util/reflow.rs b/src/util/reflow.rs index 41d56421..b30cfbe6 100644 --- a/src/util/reflow.rs +++ b/src/util/reflow.rs @@ -14,10 +14,7 @@ impl<'a> Reflow<'a> { pub fn new( buf: &'a mut Buffer, rng: Range, lmt: usize ) -> std::result::Result { - let txt = match buf.read(&rng) { - Some(t) => t, - None => bail!("Selection is invalid."), - }; + let txt = buf.read(&rng).ok_or("Selection is invalid.")?; Ok(Self { buf, rng, txt, lmt }) } From 04fae7971e49a53850a395f987f79530100c5776 Mon Sep 17 00:00:00 2001 From: "lincoln auster [they/them]" Date: Sun, 5 Sep 2021 16:25:36 -0600 Subject: [PATCH 20/21] Revert "use sel.to_range instead of manually wrapping the range" This reverts commit fdec763bbc8b5c5c8ecf9c02227bbe73cde58479. --- src/commands/selection.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/commands/selection.rs b/src/commands/selection.rs index 32cf8efc..64a56a1c 100644 --- a/src/commands/selection.rs +++ b/src/commands/selection.rs @@ -103,7 +103,13 @@ fn sel_to_range(app: &mut Application) -> std::result::Result { Ok(Range::new(cursor_position, sel.anchor)) }, Mode::SelectLine(ref sel) => { - Ok(sel.to_range(&*buf.cursor)) + Ok(util::inclusive_range( + &LineRange::new( + sel.anchor, + buf.cursor.line + ), + buf + )) }, Mode::Search(ref search) => { Ok(search From 2adc1eed1da0f43006adb710b0467a364df02332 Mon Sep 17 00:00:00 2001 From: "lincoln auster [they/them]" Date: Thu, 23 Sep 2021 14:31:33 -0600 Subject: [PATCH 21/21] more verbose names lmt -> limit rng -> range txt -> text ..etc --- src/util/reflow.rs | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/util/reflow.rs b/src/util/reflow.rs index b30cfbe6..e9411d09 100644 --- a/src/util/reflow.rs +++ b/src/util/reflow.rs @@ -3,33 +3,33 @@ use super::*; /// Encapsulate reflow logic for buffer manipulation. pub struct Reflow<'a> { buf: &'a mut Buffer, - rng: Range, - txt: String, - lmt: usize, + range: Range, + text: String, + limit: usize, } impl<'a> Reflow<'a> { /// Create a reflow instance, where buffer and range determine the target, /// and the limit is the maximum length of a line, regardless of prefixes. pub fn new( - buf: &'a mut Buffer, rng: Range, lmt: usize + buf: &'a mut Buffer, range: Range, limit: usize ) -> std::result::Result { - let txt = buf.read(&rng).ok_or("Selection is invalid.")?; - Ok(Self { buf, rng, txt, lmt }) + let text = buf.read(&range).ok_or("Selection is invalid.")?; + Ok(Self { buf, range, text, limit }) } pub fn apply(mut self) -> std::result::Result<(), Error> { let prefix = self.infer_prefix()?; let jtxt = self.justify_str(&prefix); - self.buf.delete_range(self.rng.clone()); - self.buf.cursor.move_to(self.rng.start()); + self.buf.delete_range(self.range.clone()); + self.buf.cursor.move_to(self.range.start()); self.buf.insert(jtxt); Ok(()) } fn infer_prefix(&self) -> std::result::Result { - match self.txt.split_whitespace().next() { + match self.text.split_whitespace().next() { Some(n) => if n.chars().next().unwrap().is_alphanumeric() { Ok("".to_string()) } else { @@ -41,10 +41,10 @@ impl<'a> Reflow<'a> { fn justify_str(&mut self, prefix: &str) -> String { - let txt = self.buf.read(&self.rng).unwrap(); - let mut limit = self.lmt; - let mut justified = String::with_capacity(txt.len()); - let mut pars = txt.split("\n\n").peekable(); + let text = self.buf.read(&self.range).unwrap(); + let mut limit = self.limit; + let mut justified = String::with_capacity(text.len()); + let mut pars = text.split("\n\n").peekable(); let mut space_delims = ["".to_string(), " ".to_string(), "\n".to_string()]; if prefix != "" {