forked from helix-editor/helix
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Handle single-line comment prefixes in :reflow.
This uses the single-line comment prefixes from the language config, so it should be able to handle different languages in a robust way. The logic is fairly simple, and doesn't handle block comments, for example. Fixes helix-editor#3332, helix-editor#3622
- Loading branch information
Rose Hogenson
committed
Sep 21, 2024
1 parent
9f93de5
commit cfb6730
Showing
2 changed files
with
172 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,171 @@ | ||
use smartstring::{LazyCompact, SmartString}; | ||
use std::fmt::Write; | ||
use textwrap::{Options, WordSplitter::NoHyphenation}; | ||
|
||
/// Given a slice of text, return the text re-wrapped to fit it | ||
/// within the given width. | ||
pub fn reflow_hard_wrap(text: &str, text_width: usize) -> SmartString<LazyCompact> { | ||
fn reflow_textwrap(text: &str, text_width: usize) -> String { | ||
let options = Options::new(text_width).word_splitter(NoHyphenation); | ||
textwrap::refill(text, options).into() | ||
} | ||
|
||
fn tab_len(str: &str) -> usize { | ||
let mut n = 0; | ||
for c in str.chars() { | ||
if c == '\t' { | ||
n += 8 - n % 8; | ||
} else { | ||
n += 1; | ||
} | ||
} | ||
n | ||
} | ||
|
||
fn trim_comment_prefix<'a, 'b>( | ||
s: &'a str, | ||
comment_prefixes: &'b [String], | ||
) -> Option<(&'b str, &'a str)> { | ||
for pfx in comment_prefixes { | ||
match s.strip_prefix(pfx) { | ||
Some(s) => return Some((pfx, s.trim_start())), | ||
None => continue, | ||
} | ||
} | ||
return None; | ||
} | ||
|
||
/// Given a slice of text, return the text re-wrapped to fit it | ||
/// within the given width. | ||
pub fn reflow_hard_wrap( | ||
text: &str, | ||
text_width: usize, | ||
comment_prefixes: &[String], | ||
) -> SmartString<LazyCompact> { | ||
// Strip indent and line comment prefix from each line. We can do a | ||
// better job than textwrap since we know the comment prefix. | ||
let first_line = match text.lines().next() { | ||
Some(x) => x, | ||
// text is empty for some reason. | ||
None => return "".into(), | ||
}; | ||
let indent_end = first_line | ||
.find(|c: char| !c.is_whitespace()) | ||
.unwrap_or(first_line.len()); | ||
let indent = &first_line[..indent_end]; | ||
let (comment_prefix, _) = match trim_comment_prefix(&first_line[indent_end..], comment_prefixes) | ||
{ | ||
Some(x) => x, | ||
// not commented | ||
None => return reflow_textwrap(text, text_width).into(), | ||
}; | ||
|
||
let mut stripped_text = String::new(); | ||
let mut sep = ""; | ||
// I am intentionally using .split("\n") instead of .lines() here, | ||
// so that if the text has \r\n line endings we don't destroy them. | ||
for line in text.split("\n") { | ||
let line = line.trim_start(); | ||
let line = match trim_comment_prefix(line, comment_prefixes) { | ||
Some((_, x)) => x, | ||
None => line, | ||
}; | ||
write!(stripped_text, "{sep}{line}").unwrap(); | ||
sep = "\n"; | ||
} | ||
// Defer to textwrap for the actual wrapping, then copy back the | ||
// indent and comment prefix from the first line onto all lines. | ||
let wrapped_text = reflow_textwrap( | ||
&stripped_text, | ||
text_width | ||
.saturating_sub(tab_len(indent)) | ||
// unfortunately this doesn't correctly count multibyte | ||
// characters in the comment prefix | ||
.saturating_sub(comment_prefix.len()), | ||
); | ||
let mut out = String::new(); | ||
let mut sep = ""; | ||
for line in wrapped_text.split("\n") { | ||
out.push_str(sep); | ||
if line.trim_start() != "" { | ||
write!(out, "{indent}{comment_prefix} {line}").unwrap(); | ||
} | ||
sep = "\n"; | ||
} | ||
return out.into(); | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use super::*; | ||
|
||
#[test] | ||
fn reflow_line() { | ||
assert_eq!( | ||
reflow_hard_wrap("This is a long line bla bla bla", 4, &[]), | ||
"This | ||
is a | ||
long | ||
line | ||
bla | ||
bla | ||
bla" | ||
); | ||
} | ||
|
||
#[test] | ||
fn reflow_single_line_comment() { | ||
assert_eq!( | ||
reflow_hard_wrap( | ||
"// As you can see, the code works really well because of the way that it is", | ||
10, | ||
&vec!["//".into()] | ||
), | ||
"// As you | ||
// can see, | ||
// the code | ||
// works | ||
// really | ||
// well | ||
// because | ||
// of the | ||
// way that | ||
// it is" | ||
); | ||
} | ||
|
||
#[test] | ||
fn reflow_tabbed() { | ||
// https://github.com/helix-editor/helix/issues/3622 | ||
assert_eq!( | ||
reflow_hard_wrap( | ||
"\t// Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod | ||
\t// tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim | ||
\t// veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea | ||
\t// commodo consequat. Duis aute irure dolor in reprehenderit in voluptate | ||
\t// velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint | ||
\t// occaecat cupidatat non proident, sunt in culpa qui officia deserunt | ||
\t// mollit anim id est laborum. | ||
", | ||
100, | ||
&vec!["//".into()] | ||
), | ||
"\t// Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt | ||
\t// ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation | ||
\t// ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in | ||
\t// reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur | ||
\t// sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id | ||
\t// est laborum. | ||
" | ||
); | ||
} | ||
|
||
#[test] | ||
fn reflow_multiple_comment_prefixes() { | ||
assert_eq!( | ||
reflow_hard_wrap( | ||
"// how many different\n# ways to type comments\n-- do you need?", | ||
20, | ||
&vec!["//".into(), "#".into(), "--".into()] | ||
), | ||
"// how many different\n// ways to type\n// comments do you\n// need?", | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters