Skip to content

Commit 81ac3a5

Browse files
pascalkutheAlexanderDickie
authored andcommitted
track char_idx in DocFormatter
1 parent 1040990 commit 81ac3a5

File tree

4 files changed

+206
-155
lines changed

4 files changed

+206
-155
lines changed

helix-core/src/doc_formatter.rs

Lines changed: 118 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -37,52 +37,91 @@ pub enum GraphemeSource {
3737
},
3838
}
3939

40+
impl GraphemeSource {
41+
/// Returns whether this grapheme is virtual inline text
42+
pub fn is_virtual(self) -> bool {
43+
matches!(self, GraphemeSource::VirtualText { .. })
44+
}
45+
46+
pub fn doc_chars(self) -> usize {
47+
match self {
48+
GraphemeSource::Document { codepoints } => codepoints as usize,
49+
GraphemeSource::VirtualText { .. } => 0,
50+
}
51+
}
52+
}
53+
4054
#[derive(Debug, Clone)]
4155
pub struct FormattedGrapheme<'a> {
42-
pub grapheme: Grapheme<'a>,
56+
pub raw: Grapheme<'a>,
4357
pub source: GraphemeSource,
58+
pub visual_pos: Position,
59+
/// Document line at the start of the grapheme
60+
pub line_idx: usize,
61+
/// Document char position at the start of the grapheme
62+
pub char_idx: usize,
4463
}
4564

46-
impl<'a> FormattedGrapheme<'a> {
47-
pub fn new(
65+
impl FormattedGrapheme<'_> {
66+
pub fn is_virtual(&self) -> bool {
67+
self.source.is_virtual()
68+
}
69+
70+
pub fn doc_chars(&self) -> usize {
71+
self.source.doc_chars()
72+
}
73+
74+
pub fn is_whitespace(&self) -> bool {
75+
self.raw.is_whitespace()
76+
}
77+
78+
pub fn width(&self) -> usize {
79+
self.raw.width()
80+
}
81+
82+
pub fn is_word_boundary(&self) -> bool {
83+
self.raw.is_word_boundary()
84+
}
85+
}
86+
87+
#[derive(Debug, Clone)]
88+
struct GraphemeWithSource<'a> {
89+
grapheme: Grapheme<'a>,
90+
source: GraphemeSource,
91+
}
92+
93+
impl<'a> GraphemeWithSource<'a> {
94+
fn new(
4895
g: GraphemeStr<'a>,
4996
visual_x: usize,
5097
tab_width: u16,
5198
source: GraphemeSource,
52-
) -> FormattedGrapheme<'a> {
53-
FormattedGrapheme {
99+
) -> GraphemeWithSource<'a> {
100+
GraphemeWithSource {
54101
grapheme: Grapheme::new(g, visual_x, tab_width),
55102
source,
56103
}
57104
}
58-
/// Returns whether this grapheme is virtual inline text
59-
pub fn is_virtual(&self) -> bool {
60-
matches!(self.source, GraphemeSource::VirtualText { .. })
61-
}
62-
63-
pub fn placeholder() -> Self {
64-
FormattedGrapheme {
105+
fn placeholder() -> Self {
106+
GraphemeWithSource {
65107
grapheme: Grapheme::Other { g: " ".into() },
66108
source: GraphemeSource::Document { codepoints: 0 },
67109
}
68110
}
69111

70-
pub fn doc_chars(&self) -> usize {
71-
match self.source {
72-
GraphemeSource::Document { codepoints } => codepoints as usize,
73-
GraphemeSource::VirtualText { .. } => 0,
74-
}
112+
fn doc_chars(&self) -> usize {
113+
self.source.doc_chars()
75114
}
76115

77-
pub fn is_whitespace(&self) -> bool {
116+
fn is_whitespace(&self) -> bool {
78117
self.grapheme.is_whitespace()
79118
}
80119

81-
pub fn width(&self) -> usize {
120+
fn width(&self) -> usize {
82121
self.grapheme.width()
83122
}
84123

85-
pub fn is_word_boundary(&self) -> bool {
124+
fn is_word_boundary(&self) -> bool {
86125
self.grapheme.is_word_boundary()
87126
}
88127
}
@@ -139,9 +178,9 @@ pub struct DocumentFormatter<'t> {
139178
indent_level: Option<usize>,
140179
/// In case a long word needs to be split a single grapheme might need to be wrapped
141180
/// while the rest of the word stays on the same line
142-
peeked_grapheme: Option<(FormattedGrapheme<'t>, usize)>,
181+
peeked_grapheme: Option<GraphemeWithSource<'t>>,
143182
/// A first-in first-out (fifo) buffer for the Graphemes of any given word
144-
word_buf: Vec<FormattedGrapheme<'t>>,
183+
word_buf: Vec<GraphemeWithSource<'t>>,
145184
/// The index of the next grapheme that will be yielded from the `word_buf`
146185
word_i: usize,
147186
}
@@ -157,32 +196,33 @@ impl<'t> DocumentFormatter<'t> {
157196
text_fmt: &'t TextFormat,
158197
annotations: &'t TextAnnotations,
159198
char_idx: usize,
160-
) -> (Self, usize) {
199+
) -> Self {
161200
// TODO divide long lines into blocks to avoid bad performance for long lines
162201
let block_line_idx = text.char_to_line(char_idx.min(text.len_chars()));
163202
let block_char_idx = text.line_to_char(block_line_idx);
164203
annotations.reset_pos(block_char_idx);
165-
(
166-
DocumentFormatter {
167-
text_fmt,
168-
annotations,
169-
visual_pos: Position { row: 0, col: 0 },
170-
graphemes: RopeGraphemes::new(text.slice(block_char_idx..)),
171-
char_pos: block_char_idx,
172-
exhausted: false,
173-
virtual_lines: 0,
174-
indent_level: None,
175-
peeked_grapheme: None,
176-
word_buf: Vec::with_capacity(64),
177-
word_i: 0,
178-
line_pos: block_line_idx,
179-
inline_anntoation_graphemes: None,
180-
},
181-
block_char_idx,
182-
)
204+
205+
DocumentFormatter {
206+
text_fmt,
207+
annotations,
208+
visual_pos: Position { row: 0, col: 0 },
209+
graphemes: RopeGraphemes::new(text.slice(block_char_idx..)),
210+
char_pos: block_char_idx,
211+
exhausted: false,
212+
virtual_lines: 0,
213+
indent_level: None,
214+
peeked_grapheme: None,
215+
word_buf: Vec::with_capacity(64),
216+
word_i: 0,
217+
line_pos: block_line_idx,
218+
inline_anntoation_graphemes: None,
219+
}
183220
}
184221

185-
fn next_inline_annotation_grapheme(&mut self) -> Option<(&'t str, Option<Highlight>)> {
222+
fn next_inline_annotation_grapheme(
223+
&mut self,
224+
char_pos: usize,
225+
) -> Option<(&'t str, Option<Highlight>)> {
186226
loop {
187227
if let Some(&mut (ref mut annotation, highlight)) =
188228
self.inline_anntoation_graphemes.as_mut()
@@ -193,7 +233,7 @@ impl<'t> DocumentFormatter<'t> {
193233
}
194234

195235
if let Some((annotation, highlight)) =
196-
self.annotations.next_inline_annotation_at(self.char_pos)
236+
self.annotations.next_inline_annotation_at(char_pos)
197237
{
198238
self.inline_anntoation_graphemes = Some((
199239
UnicodeSegmentation::graphemes(&*annotation.text, true),
@@ -205,21 +245,20 @@ impl<'t> DocumentFormatter<'t> {
205245
}
206246
}
207247

208-
fn advance_grapheme(&mut self, col: usize) -> Option<FormattedGrapheme<'t>> {
248+
fn advance_grapheme(&mut self, col: usize, char_pos: usize) -> Option<GraphemeWithSource<'t>> {
209249
let (grapheme, source) =
210-
if let Some((grapheme, highlight)) = self.next_inline_annotation_grapheme() {
250+
if let Some((grapheme, highlight)) = self.next_inline_annotation_grapheme(char_pos) {
211251
(grapheme.into(), GraphemeSource::VirtualText { highlight })
212252
} else if let Some(grapheme) = self.graphemes.next() {
213253
self.virtual_lines += self.annotations.annotation_lines_at(self.char_pos);
214254
let codepoints = grapheme.len_chars() as u32;
215255

216-
let overlay = self.annotations.overlay_at(self.char_pos);
256+
let overlay = self.annotations.overlay_at(char_pos);
217257
let grapheme = match overlay {
218258
Some((overlay, _)) => overlay.grapheme.as_str().into(),
219259
None => Cow::from(grapheme).into(),
220260
};
221261

222-
self.char_pos += codepoints as usize;
223262
(grapheme, GraphemeSource::Document { codepoints })
224263
} else {
225264
if self.exhausted {
@@ -228,19 +267,19 @@ impl<'t> DocumentFormatter<'t> {
228267
self.exhausted = true;
229268
// EOF grapheme is required for rendering
230269
// and correct position computations
231-
return Some(FormattedGrapheme {
270+
return Some(GraphemeWithSource {
232271
grapheme: Grapheme::Other { g: " ".into() },
233272
source: GraphemeSource::Document { codepoints: 0 },
234273
});
235274
};
236275

237-
let grapheme = FormattedGrapheme::new(grapheme, col, self.text_fmt.tab_width, source);
276+
let grapheme = GraphemeWithSource::new(grapheme, col, self.text_fmt.tab_width, source);
238277

239278
Some(grapheme)
240279
}
241280

242281
/// Move a word to the next visual line
243-
fn wrap_word(&mut self, virtual_lines_before_word: usize) -> usize {
282+
fn wrap_word(&mut self) -> usize {
244283
// softwrap this word to the next line
245284
let indent_carry_over = if let Some(indent) = self.indent_level {
246285
if indent as u16 <= self.text_fmt.max_indent_retain {
@@ -255,14 +294,13 @@ impl<'t> DocumentFormatter<'t> {
255294
};
256295

257296
self.visual_pos.col = indent_carry_over as usize;
258-
self.virtual_lines -= virtual_lines_before_word;
259-
self.visual_pos.row += 1 + virtual_lines_before_word;
297+
self.visual_pos.row += 1 + take(&mut self.virtual_lines);
260298
let mut i = 0;
261299
let mut word_width = 0;
262300
let wrap_indicator = UnicodeSegmentation::graphemes(&*self.text_fmt.wrap_indicator, true)
263301
.map(|g| {
264302
i += 1;
265-
let grapheme = FormattedGrapheme::new(
303+
let grapheme = GraphemeWithSource::new(
266304
g.into(),
267305
self.visual_pos.col + word_width,
268306
self.text_fmt.tab_width,
@@ -288,8 +326,7 @@ impl<'t> DocumentFormatter<'t> {
288326
fn advance_to_next_word(&mut self) {
289327
self.word_buf.clear();
290328
let mut word_width = 0;
291-
let virtual_lines_before_word = self.virtual_lines;
292-
let mut virtual_lines_before_grapheme = self.virtual_lines;
329+
let mut word_chars = 0;
293330

294331
loop {
295332
// softwrap word if necessary
@@ -301,27 +338,24 @@ impl<'t> DocumentFormatter<'t> {
301338
// However if the last grapheme is multiple columns wide it might extend beyond the EOL.
302339
// The condition below ensures that this grapheme is not cutoff and instead wrapped to the next line
303340
if word_width + self.visual_pos.col > self.text_fmt.viewport_width as usize {
304-
self.peeked_grapheme = self.word_buf.pop().map(|grapheme| {
305-
(grapheme, self.virtual_lines - virtual_lines_before_grapheme)
306-
});
307-
self.virtual_lines = virtual_lines_before_grapheme;
341+
self.peeked_grapheme = self.word_buf.pop();
308342
}
309343
return;
310344
}
311345

312-
word_width = self.wrap_word(virtual_lines_before_word);
346+
word_width = self.wrap_word();
313347
}
314348

315-
virtual_lines_before_grapheme = self.virtual_lines;
316-
317-
let grapheme = if let Some((grapheme, virtual_lines)) = self.peeked_grapheme.take() {
318-
self.virtual_lines += virtual_lines;
349+
let grapheme = if let Some(grapheme) = self.peeked_grapheme.take() {
319350
grapheme
320-
} else if let Some(grapheme) = self.advance_grapheme(self.visual_pos.col + word_width) {
351+
} else if let Some(grapheme) =
352+
self.advance_grapheme(self.visual_pos.col + word_width, self.char_pos + word_chars)
353+
{
321354
grapheme
322355
} else {
323356
return;
324357
};
358+
word_chars += grapheme.doc_chars();
325359

326360
// Track indentation
327361
if !grapheme.is_whitespace() && self.indent_level.is_none() {
@@ -340,19 +374,19 @@ impl<'t> DocumentFormatter<'t> {
340374
}
341375
}
342376

343-
/// returns the document line pos of the **next** grapheme that will be yielded
344-
pub fn line_pos(&self) -> usize {
345-
self.line_pos
377+
/// returns the char index at the end of the last yielded grapheme
378+
pub fn next_char_pos(&self) -> usize {
379+
self.char_pos
346380
}
347381

348-
/// returns the visual pos of the **next** grapheme that will be yielded
349-
pub fn visual_pos(&self) -> Position {
382+
/// returns the visual position at the end of the last yielded grapheme
383+
pub fn next_visual_pos(&self) -> Position {
350384
self.visual_pos
351385
}
352386
}
353387

354388
impl<'t> Iterator for DocumentFormatter<'t> {
355-
type Item = (FormattedGrapheme<'t>, Position);
389+
type Item = FormattedGrapheme<'t>;
356390

357391
fn next(&mut self) -> Option<Self::Item> {
358392
let grapheme = if self.text_fmt.soft_wrap {
@@ -362,15 +396,18 @@ impl<'t> Iterator for DocumentFormatter<'t> {
362396
}
363397
let grapheme = replace(
364398
self.word_buf.get_mut(self.word_i)?,
365-
FormattedGrapheme::placeholder(),
399+
GraphemeWithSource::placeholder(),
366400
);
367401
self.word_i += 1;
368402
grapheme
369403
} else {
370-
self.advance_grapheme(self.visual_pos.col)?
404+
self.advance_grapheme(self.visual_pos.col, self.char_pos)?
371405
};
372406

373-
let pos = self.visual_pos;
407+
let visual_pos = self.visual_pos;
408+
let char_pos = self.char_pos;
409+
self.char_pos += grapheme.doc_chars();
410+
let line_idx = self.line_pos;
374411
if grapheme.grapheme == Grapheme::Newline {
375412
self.visual_pos.row += 1;
376413
self.visual_pos.row += take(&mut self.virtual_lines);
@@ -379,6 +416,12 @@ impl<'t> Iterator for DocumentFormatter<'t> {
379416
} else {
380417
self.visual_pos.col += grapheme.width();
381418
}
382-
Some((grapheme, pos))
419+
Some(FormattedGrapheme {
420+
raw: grapheme.grapheme,
421+
source: grapheme.source,
422+
visual_pos,
423+
line_idx,
424+
char_idx: char_pos,
425+
})
383426
}
384427
}

0 commit comments

Comments
 (0)