mirror of https://github.com/helix-editor/helix
Separate reflow from DocumentFormatter
It was making things much more complicated by needing to handle two different cases in the same code path. By separating reflow out, it can be more efficient and easier to debug.pull/11738/head
parent
df264ffbb4
commit
8566d5ca1a
|
@ -24,7 +24,7 @@ use helix_stdx::rope::{RopeGraphemes, RopeSliceExt};
|
|||
use crate::graphemes::{Grapheme, GraphemeStr};
|
||||
use crate::syntax::Highlight;
|
||||
use crate::text_annotations::TextAnnotations;
|
||||
use crate::{movement, Change, LineEnding, Position, Rope, RopeSlice, Tendril};
|
||||
use crate::{movement, Change, LineEnding, Position, RopeSlice, Tendril};
|
||||
|
||||
/// TODO make Highlight a u32 to reduce the size of this enum to a single word.
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
|
@ -434,57 +434,6 @@ impl<'t> DocumentFormatter<'t> {
|
|||
pub fn next_visual_pos(&self) -> Position {
|
||||
self.visual_pos
|
||||
}
|
||||
|
||||
fn find_indent<'a>(&self, line: usize, doc: RopeSlice<'a>) -> RopeSlice<'a> {
|
||||
let line_start = doc.line_to_char(line);
|
||||
let mut indent_end = movement::skip_while(doc, line_start, |ch| matches!(ch, ' ' | '\t'))
|
||||
.unwrap_or(line_start);
|
||||
let slice = doc.slice(indent_end..);
|
||||
if let Some(token) = self
|
||||
.text_fmt
|
||||
.continue_comments
|
||||
.iter()
|
||||
.filter(|token| slice.starts_with(token))
|
||||
.max_by_key(|x| x.len())
|
||||
{
|
||||
indent_end += token.chars().count();
|
||||
}
|
||||
let indent_end = movement::skip_while(doc, indent_end, |ch| matches!(ch, ' ' | '\t'))
|
||||
.unwrap_or(indent_end);
|
||||
return doc.slice(line_start..indent_end);
|
||||
}
|
||||
|
||||
/// consumes the iterator and hard-wraps the input where soft wraps would
|
||||
/// have been applied. It probably only makes sense to call this method if
|
||||
/// soft_wrap is true.
|
||||
pub fn reflow(&mut self, doc: &Rope, line_ending: LineEnding) -> Vec<Change> {
|
||||
let slice = doc.slice(..);
|
||||
let mut current_line = self.visual_pos.row;
|
||||
let mut changes = Vec::new();
|
||||
while let Some(grapheme) = self.next() {
|
||||
if !grapheme.is_whitespace() && grapheme.visual_pos.row != current_line {
|
||||
let indent = Tendril::from(format!(
|
||||
"{}{}",
|
||||
line_ending.as_str(),
|
||||
self.find_indent(doc.char_to_line(grapheme.char_idx - 1), slice)
|
||||
));
|
||||
let mut whitespace_start = grapheme.char_idx;
|
||||
let mut whitespace_end = grapheme.char_idx;
|
||||
while whitespace_start > 0 && slice.char(whitespace_start - 1) == ' ' {
|
||||
whitespace_start -= 1;
|
||||
}
|
||||
while whitespace_end < slice.chars().len() && slice.char(whitespace_end) == ' ' {
|
||||
whitespace_end += 1;
|
||||
}
|
||||
changes.push((whitespace_start, whitespace_end, Some(indent)));
|
||||
current_line = grapheme.visual_pos.row;
|
||||
}
|
||||
if grapheme.raw == Grapheme::Newline {
|
||||
current_line += 1;
|
||||
}
|
||||
}
|
||||
changes
|
||||
}
|
||||
}
|
||||
|
||||
impl<'t> Iterator for DocumentFormatter<'t> {
|
||||
|
@ -535,3 +484,91 @@ impl<'t> Iterator for DocumentFormatter<'t> {
|
|||
Some(grapheme)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ReflowOpts<'a> {
|
||||
pub width: usize,
|
||||
pub line_ending: LineEnding,
|
||||
pub comment_tokens: &'a [String],
|
||||
}
|
||||
|
||||
impl ReflowOpts<'_> {
|
||||
fn find_indent<'a>(&self, line: usize, doc: RopeSlice<'a>) -> RopeSlice<'a> {
|
||||
let line_start = doc.line_to_char(line);
|
||||
let mut indent_end = movement::skip_while(doc, line_start, |ch| matches!(ch, ' ' | '\t'))
|
||||
.unwrap_or(line_start);
|
||||
let slice = doc.slice(indent_end..);
|
||||
if let Some(token) = self
|
||||
.comment_tokens
|
||||
.iter()
|
||||
.filter(|token| slice.starts_with(token))
|
||||
.max_by_key(|x| x.len())
|
||||
{
|
||||
indent_end += token.chars().count();
|
||||
}
|
||||
let indent_end = movement::skip_while(doc, indent_end, |ch| matches!(ch, ' ' | '\t'))
|
||||
.unwrap_or(indent_end);
|
||||
return doc.slice(line_start..indent_end);
|
||||
}
|
||||
}
|
||||
|
||||
/// reflow wraps long lines in text to be less than opts.width.
|
||||
pub fn reflow(text: RopeSlice, char_pos: usize, opts: &ReflowOpts) -> Vec<Change> {
|
||||
// A constant so that reflow behaves consistently across
|
||||
// different configurations.
|
||||
const TAB_WIDTH: u16 = 8;
|
||||
|
||||
let line_idx = text.char_to_line(char_pos.min(text.len_chars()));
|
||||
let mut char_pos = text.line_to_char(line_idx);
|
||||
|
||||
let mut col = 0;
|
||||
let mut word_width = 0;
|
||||
let mut last_word_boundary = None;
|
||||
let mut changes = Vec::new();
|
||||
for grapheme in text.graphemes() {
|
||||
let grapheme_chars = grapheme.len_chars();
|
||||
let mut grapheme = Grapheme::new(GraphemeStr::from(Cow::from(grapheme)), col, TAB_WIDTH);
|
||||
if col + grapheme.width() > opts.width && !grapheme.is_whitespace() {
|
||||
if let Some(n) = last_word_boundary {
|
||||
let indent = opts.find_indent(text.char_to_line(n - 1), text);
|
||||
let mut whitespace_start = n;
|
||||
let mut whitespace_end = n;
|
||||
while whitespace_start > 0 && text.char(whitespace_start - 1) == ' ' {
|
||||
whitespace_start -= 1;
|
||||
}
|
||||
while whitespace_end < text.chars().len() && text.char(whitespace_end) == ' ' {
|
||||
whitespace_end += 1;
|
||||
}
|
||||
changes.push((
|
||||
whitespace_start,
|
||||
whitespace_end,
|
||||
Some(Tendril::from(format!(
|
||||
"{}{}",
|
||||
opts.line_ending.as_str(),
|
||||
&indent
|
||||
))),
|
||||
));
|
||||
|
||||
col = 0;
|
||||
for g in indent.graphemes() {
|
||||
let g = Grapheme::new(GraphemeStr::from(Cow::from(g)), col, TAB_WIDTH);
|
||||
col += g.width();
|
||||
}
|
||||
col += word_width;
|
||||
last_word_boundary = None;
|
||||
grapheme.change_position(col, TAB_WIDTH);
|
||||
}
|
||||
}
|
||||
col += grapheme.width();
|
||||
word_width += grapheme.width();
|
||||
if grapheme == Grapheme::Newline {
|
||||
col = 0;
|
||||
word_width = 0;
|
||||
last_word_boundary = None;
|
||||
} else if grapheme.is_whitespace() {
|
||||
last_word_boundary = Some(char_pos);
|
||||
word_width = 0;
|
||||
}
|
||||
char_pos += grapheme_chars;
|
||||
}
|
||||
changes
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ use crate::job::Job;
|
|||
use super::*;
|
||||
|
||||
use helix_core::command_line::{Args, Flag, Signature, Token, TokenKind};
|
||||
use helix_core::doc_formatter::DocumentFormatter;
|
||||
use helix_core::doc_formatter::ReflowOpts;
|
||||
use helix_core::fuzzy::fuzzy_match;
|
||||
use helix_core::indent::MAX_INDENT;
|
||||
use helix_core::line_ending;
|
||||
|
@ -2161,33 +2161,22 @@ fn reflow(cx: &mut compositor::Context, args: Args, event: PromptEvent) -> anyho
|
|||
|
||||
let rope = doc.text();
|
||||
let slice = rope.slice(..);
|
||||
let format = TextFormat {
|
||||
soft_wrap: true,
|
||||
tab_width: 8,
|
||||
max_wrap: None,
|
||||
max_indent_retain: u16::try_from(text_width).unwrap_or(u16::MAX),
|
||||
wrap_indicator: Box::from(""),
|
||||
wrap_indicator_highlight: None,
|
||||
viewport_width: u16::try_from(text_width).unwrap_or(u16::MAX),
|
||||
soft_wrap_at_text_width: true,
|
||||
continue_comments: Vec::from(
|
||||
doc.language_config()
|
||||
.and_then(|config| config.comment_tokens.as_deref())
|
||||
.unwrap_or(&[]),
|
||||
),
|
||||
is_word_boundary: |g| g.is_whitespace(),
|
||||
let opts = ReflowOpts {
|
||||
width: text_width,
|
||||
line_ending: doc.line_ending,
|
||||
comment_tokens: doc
|
||||
.language_config()
|
||||
.and_then(|config| config.comment_tokens.as_deref())
|
||||
.unwrap_or(&[]),
|
||||
};
|
||||
let annotations = TextAnnotations::default();
|
||||
|
||||
let mut changes = Vec::new();
|
||||
for selection in doc.selection(view.id) {
|
||||
let mut formatter = DocumentFormatter::new_at_prev_checkpoint(
|
||||
changes.append(&mut helix_core::doc_formatter::reflow(
|
||||
slice.slice(..selection.to()),
|
||||
&format,
|
||||
&annotations,
|
||||
selection.from(),
|
||||
);
|
||||
changes.append(&mut formatter.reflow(rope, doc.line_ending));
|
||||
&opts,
|
||||
));
|
||||
}
|
||||
let transaction = Transaction::change(rope, changes.into_iter());
|
||||
|
||||
|
|
|
@ -840,9 +840,11 @@ bla]#",
|
|||
"// #[|This is a really long comment that we want to break onto multiple lines.]#",
|
||||
":lang rust<ret>:reflow 13<ret>",
|
||||
"// #[|This is a
|
||||
// really long
|
||||
// comment that
|
||||
// we want to
|
||||
// really
|
||||
// long
|
||||
// comment
|
||||
// that we
|
||||
// want to
|
||||
// break onto
|
||||
// multiple
|
||||
// lines.]#",
|
||||
|
@ -859,9 +861,8 @@ bla]#",
|
|||
\t// mollit anim id est laborum.
|
||||
|]#",
|
||||
":lang go<ret>:reflow 50<ret>",
|
||||
"#[\t// Lorem ipsum dolor sit amet,
|
||||
\t// consectetur adipiscing elit, sed do
|
||||
\t// eiusmod
|
||||
"#[\t// Lorem ipsum dolor sit amet, consectetur
|
||||
\t// adipiscing elit, sed do eiusmod
|
||||
\t// tempor incididunt ut labore et dolore
|
||||
\t// magna aliqua. Ut enim ad minim
|
||||
\t// veniam, quis nostrud exercitation
|
||||
|
@ -886,8 +887,8 @@ bla]#",
|
|||
// that each need wrapping
|
||||
|
||||
/// currently we wrap each line
|
||||
/// completely separately in order to
|
||||
/// preserve existing newlines.]#"
|
||||
/// completely separately in order
|
||||
/// to preserve existing newlines.]#"
|
||||
))
|
||||
.await?;
|
||||
|
||||
|
|
Loading…
Reference in New Issue