Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle single-line comment prefixes in :reflow. #11738

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 0 additions & 24 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion helix-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ encoding_rs = "0.8"
chrono = { version = "0.4", default-features = false, features = ["alloc", "std"] }

etcetera = "0.8"
textwrap = "0.16.1"

nucleo.workspace = true
parking_lot = "0.12"
Expand Down
50 changes: 49 additions & 1 deletion helix-core/src/doc_formatter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ use unicode_segmentation::{Graphemes, UnicodeSegmentation};
use crate::graphemes::{Grapheme, GraphemeStr};
use crate::syntax::Highlight;
use crate::text_annotations::TextAnnotations;
use crate::{Position, RopeGraphemes, RopeSlice};
use crate::{movement, Change, LineEnding, Position, Rope, RopeGraphemes, RopeSlice, Tendril};
use helix_stdx::rope::RopeSliceExt;

/// TODO make Highlight a u32 to reduce the size of this enum to a single word.
#[derive(Debug, Clone, Copy)]
Expand Down Expand Up @@ -150,6 +151,7 @@ pub struct TextFormat {
pub wrap_indicator_highlight: Option<Highlight>,
pub viewport_width: u16,
pub soft_wrap_at_text_width: bool,
pub continue_comments: Vec<String>,
}

// test implementation is basically only used for testing or when softwrap is always disabled
Expand All @@ -164,6 +166,7 @@ impl Default for TextFormat {
viewport_width: 17,
wrap_indicator_highlight: None,
soft_wrap_at_text_width: false,
continue_comments: Vec::new(),
}
}
}
Expand Down Expand Up @@ -425,6 +428,51 @@ 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 last_char_start = self.char_pos;
let mut current_line = self.visual_pos.row;
let mut changes = Vec::new();
while let Some(grapheme) = self.next() {
if grapheme.visual_pos.row != current_line {
let indent = Tendril::from(format!(
"{}{}",
line_ending.as_str(),
self.find_indent(doc.char_to_line(last_char_start), slice)
));
changes.push((last_char_start, grapheme.char_idx, Some(indent)));
current_line = grapheme.visual_pos.row;
}
if grapheme.raw == Grapheme::Newline {
current_line += 1;
}
last_char_start = grapheme.char_idx;
}
changes
}
}

impl<'t> Iterator for DocumentFormatter<'t> {
Expand Down
1 change: 1 addition & 0 deletions helix-core/src/doc_formatter/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ impl TextFormat {
// use a prime number to allow lining up too often with repeat
viewport_width: 17,
soft_wrap_at_text_width: false,
continue_comments: Vec::new(),
}
}
}
Expand Down
1 change: 0 additions & 1 deletion helix-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ pub mod text_annotations;
pub mod textobject;
mod transaction;
pub mod uri;
pub mod wrap;

pub mod unicode {
pub use unicode_general_category as category;
Expand Down
9 changes: 0 additions & 9 deletions helix-core/src/wrap.rs

This file was deleted.

38 changes: 30 additions & 8 deletions helix-term/src/commands/typed.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use crate::job::Job;

use super::*;

use helix_core::doc_formatter::DocumentFormatter;
use helix_core::fuzzy::fuzzy_match;
use helix_core::indent::MAX_INDENT;
use helix_core::{line_ending, shellwords::Shellwords};
Expand Down Expand Up @@ -2118,14 +2119,35 @@ fn reflow(
.unwrap_or(cfg_text_width);

let rope = doc.text();

let selection = doc.selection(view.id);
let transaction = Transaction::change_by_selection(rope, selection, |range| {
let fragment = range.fragment(rope.slice(..));
let reflowed_text = helix_core::wrap::reflow_hard_wrap(&fragment, text_width);

(range.from(), range.to(), Some(reflowed_text))
});
let slice = rope.slice(..);
let format = TextFormat {
soft_wrap: true,
tab_width: 8,
max_wrap: u16::try_from(text_width).unwrap_or(u16::MAX),
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(&[]),
),
};
let annotations = TextAnnotations::default();

let mut changes = Vec::new();
for selection in doc.selection(view.id) {
let mut formatter = DocumentFormatter::new_at_prev_checkpoint(
slice.slice(..selection.to()),
&format,
&annotations,
selection.from(),
);
changes.append(&mut formatter.reflow(rope, doc.line_ending));
}
let transaction = Transaction::change(rope, changes.into_iter());

doc.apply(&transaction, view.id);
doc.append_changes_to_history(view);
Expand Down
71 changes: 71 additions & 0 deletions helix-term/tests/test/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -729,3 +729,74 @@ fn foo() {

Ok(())
}

#[tokio::test(flavor = "multi_thread")]
async fn test_reflow() -> anyhow::Result<()> {
test((
"#[|This is a long line bla bla bla]#",
":reflow 5<ret>",
"#[|This
is a
long
line
bla
bla
bla]#",
))
.await?;

test((
"// #[|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
// break onto
// multiple
// lines.]#",
))
.await?;

test((
"#[\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.
|]#",
":lang go<ret>:reflow 50<ret>",
"#[\t// Lorem ipsum dolor sit amet,
\t// consectetur adipiscing elit, sed do
\t// eiusmod
\t// tempor incididunt ut labore et dolore
\t// magna aliqua. Ut enim ad minim
\t// veniam, quis nostrud exercitation
\t// ullamco laboris nisi ut aliquip ex ea
\t// commodo consequat. Duis aute irure
\t// dolor in reprehenderit in voluptate
\t// velit esse cillum dolore eu fugiat
\t// nulla pariatur. Excepteur sint
\t// occaecat cupidatat non proident, sunt
\t// in culpa qui officia deserunt
\t// mollit anim id est laborum.
|]#",
))
.await?;

test((
" // #[|This document has multiple lines that each need wrapping
/// currently we wrap each line completely separately in order to preserve existing newlines.]#",
":lang rust<ret>:reflow 40<ret>",
" // #[|This document has multiple lines
// that each need wrapping
/// currently we wrap each line
/// completely separately in order to
/// preserve existing newlines.]#"
))
.await?;

Ok(())
}
1 change: 1 addition & 0 deletions helix-view/src/annotations/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ impl InlineDiagnosticsConfig {
wrap_indicator_highlight: None,
viewport_width: width,
soft_wrap_at_text_width: true,
continue_comments: Vec::new(),
}
}
}
Expand Down
1 change: 1 addition & 0 deletions helix-view/src/document.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2106,6 +2106,7 @@ impl Document {
.and_then(|theme| theme.find_scope_index("ui.virtual.wrap"))
.map(Highlight),
soft_wrap_at_text_width,
continue_comments: Vec::new(),
}
}

Expand Down