Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
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
2 changes: 2 additions & 0 deletions book/src/generated/static-cmd.md
Original file line number Diff line number Diff line change
Expand Up @@ -313,3 +313,5 @@
| `goto_prev_tabstop` | Goto next snippet placeholder | |
| `rotate_selections_first` | Make the first selection your primary one | |
| `rotate_selections_last` | Make the last selection your primary one | |
| `fold` | Fold text objects | normal: `` Zf ``, `` zf ``, select: `` Zf ``, `` zf `` |
| `unfold` | Unfold text objects | normal: `` ZF ``, `` zF ``, select: `` ZF ``, `` zF `` |
2 changes: 2 additions & 0 deletions book/src/generated/typable-cmd.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,4 +88,6 @@
| `:yank-diagnostic` | Yank diagnostic(s) under primary cursor to register, or clipboard by default |
| `:read`, `:r` | Load a file into buffer |
| `:echo` | Prints the given arguments to the statusline. |
| `:fold` | Fold text. |
| `:unfold` | Unfold text. |
| `:noop` | Does nothing. |
4 changes: 4 additions & 0 deletions helix-core/src/command_line.rs
Original file line number Diff line number Diff line change
Expand Up @@ -932,6 +932,10 @@ impl<'a> Args<'a> {
self.positionals.join(sep)
}

pub fn contains(&self, arg: &str) -> bool {
self.positionals.contains(&Cow::Borrowed(arg))
}

/// Returns an iterator over all positional arguments.
pub fn iter(&self) -> slice::Iter<'_, Cow<'_, str>> {
self.positionals.iter()
Expand Down
50 changes: 47 additions & 3 deletions helix-core/src/doc_formatter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ use unicode_segmentation::{Graphemes, UnicodeSegmentation};

use helix_stdx::rope::{RopeGraphemes, RopeSliceExt};

use crate::graphemes::{Grapheme, GraphemeStr};
use crate::graphemes::{next_grapheme_boundary, Grapheme, GraphemeStr};
use crate::syntax::Highlight;
use crate::text_annotations::TextAnnotations;
use crate::{Position, RopeSlice};
Expand Down Expand Up @@ -172,6 +172,8 @@ impl Default for TextFormat {

#[derive(Debug)]
pub struct DocumentFormatter<'t> {
text: RopeSlice<'t>,

text_fmt: &'t TextFormat,
annotations: &'t TextAnnotations<'t>,

Expand Down Expand Up @@ -210,14 +212,23 @@ impl<'t> DocumentFormatter<'t> {
text: RopeSlice<'t>,
text_fmt: &'t TextFormat,
annotations: &'t TextAnnotations,
char_idx: usize,
mut char_idx: usize,
) -> Self {
// if `char_idx` is folded restore its value to the starting char of the block
if let Some(fold) = annotations
.folds
.superest_fold_containing(char_idx, |fold| fold.start.char..=fold.end.char)
{
char_idx = fold.start.char
}

// TODO divide long lines into blocks to avoid bad performance for long lines
let block_line_idx = text.char_to_line(char_idx.min(text.len_chars()));
let block_char_idx = text.line_to_char(block_line_idx);
annotations.reset_pos(block_char_idx);

DocumentFormatter {
text,
text_fmt,
annotations,
visual_pos: Position { row: 0, col: 0 },
Expand Down Expand Up @@ -259,7 +270,15 @@ impl<'t> DocumentFormatter<'t> {
}
}

fn advance_grapheme(&mut self, col: usize, char_pos: usize) -> Option<GraphemeWithSource<'t>> {
fn advance_grapheme(
&mut self,
col: usize,
mut char_pos: usize,
) -> Option<GraphemeWithSource<'t>> {
if let Some(folded_chars) = self.skip_folded_chars(char_pos) {
char_pos += folded_chars;
}

let (grapheme, source) =
if let Some((grapheme, highlight)) = self.next_inline_annotation_grapheme(char_pos) {
(grapheme.into(), GraphemeSource::VirtualText { highlight })
Expand Down Expand Up @@ -291,6 +310,31 @@ impl<'t> DocumentFormatter<'t> {
Some(grapheme)
}

fn skip_folded_chars(&mut self, char_pos: usize) -> Option<usize> {
let (folded_chars, folded_lines) = self
.annotations
.folds
.consume_next(char_pos, |fold| fold.start.char)
.map(|fold| {
(
next_grapheme_boundary(self.text, fold.end.char) - fold.start.char,
fold.end.line - fold.start.line + 1,
)
})?;

if char_pos + folded_chars < self.text.len_chars() {
self.graphemes = self.text.slice(char_pos + folded_chars..).graphemes();
} else {
self.graphemes = RopeSlice::from("").graphemes();
}
self.annotations.reset_pos(char_pos + folded_chars);

self.char_pos += folded_chars;
self.line_pos += folded_lines;

Some(folded_chars)
}

/// Move a word to the next visual line
fn wrap_word(&mut self) -> usize {
// softwrap this word to the next line
Expand Down
79 changes: 79 additions & 0 deletions helix-core/src/graphemes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use std::ptr::NonNull;
use std::{slice, str};

use crate::chars::{char_is_whitespace, char_is_word};
use crate::text_folding::FoldAnnotations;
use crate::LineEnding;

#[inline]
Expand Down Expand Up @@ -211,6 +212,84 @@ pub fn nth_next_grapheme_boundary(slice: RopeSlice, char_idx: usize, n: usize) -
chunk_char_idx + tmp
}

#[must_use]
// OPTIMIZE: consider inlining fold-checking into `nth_prev_grapheme_boundary`
// to avoid `prev_grapheme_boundary` loop calls.
pub fn nth_prev_folded_grapheme_boundary(
slice: RopeSlice,
annotations: &FoldAnnotations,
mut char_idx: usize,
n: usize,
) -> usize {
if n == 0 {
return char_idx;
}

annotations.reset_pos(char_idx, |fold| fold.start.char);

for _ in 0..n {
char_idx = prev_grapheme_boundary(slice, char_idx);
if let Some(fold) = annotations.consume_prev(char_idx, |fold| fold.end.char) {
char_idx = prev_grapheme_boundary(slice, fold.start.char)
}
}

char_idx
}

#[must_use]
// OPTIMIZE: consider inlining fold-checking into `nth_next_grapheme_boundary`
// to avoid `next_grapheme_boundary` loop calls.
pub fn nth_next_folded_grapheme_boundary(
slice: RopeSlice,
annotations: &FoldAnnotations,
mut char_idx: usize,
mut n: usize,
) -> usize {
if n == 0 {
return char_idx;
}

annotations.reset_pos(char_idx, |fold| fold.start.char);

// This function is used for `Range`, which utilizes a gap index.
// Consequently, the right index of `Range` may be positioned at the start fold point char.
// This code handles that specific case.
if let Some(fold) = annotations.consume_next(char_idx, |fold| fold.start.char) {
char_idx = next_grapheme_boundary(slice, fold.end.char);
n -= 1;
}

for _ in 0..n {
char_idx = next_grapheme_boundary(slice, char_idx);
if let Some(fold) = annotations.consume_next(char_idx, |fold| fold.start.char) {
char_idx = next_grapheme_boundary(slice, fold.end.char)
}
}

char_idx
}

#[must_use]
#[inline(always)]
pub fn prev_folded_grapheme_boundary(
slice: RopeSlice,
annotations: &FoldAnnotations,
char_idx: usize,
) -> usize {
nth_prev_folded_grapheme_boundary(slice, annotations, char_idx, 1)
}

#[must_use]
#[inline(always)]
pub fn next_folded_grapheme_boundary(
slice: RopeSlice,
annotations: &FoldAnnotations,
char_idx: usize,
) -> usize {
nth_next_folded_grapheme_boundary(slice, annotations, char_idx, 1)
}

/// Finds the next grapheme boundary after the given char position.
#[must_use]
#[inline(always)]
Expand Down
1 change: 1 addition & 0 deletions helix-core/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ pub mod surround;
pub mod syntax;
pub mod test;
pub mod text_annotations;
pub mod text_folding;
pub mod textobject;
mod transaction;
pub mod uri;
Expand Down
Loading