mirror of
https://github.com/helix-editor/helix.git
synced 2024-11-22 01:16:18 +04:00
track char_idx in DocFormatter
This commit is contained in:
parent
2023445a08
commit
e15626a00a
@ -37,52 +37,91 @@ pub enum GraphemeSource {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
impl GraphemeSource {
|
||||||
pub struct FormattedGrapheme<'a> {
|
/// Returns whether this grapheme is virtual inline text
|
||||||
pub grapheme: Grapheme<'a>,
|
pub fn is_virtual(self) -> bool {
|
||||||
pub source: GraphemeSource,
|
matches!(self, GraphemeSource::VirtualText { .. })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn doc_chars(self) -> usize {
|
||||||
|
match self {
|
||||||
|
GraphemeSource::Document { codepoints } => codepoints as usize,
|
||||||
|
GraphemeSource::VirtualText { .. } => 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> FormattedGrapheme<'a> {
|
#[derive(Debug, Clone)]
|
||||||
pub fn new(
|
pub struct FormattedGrapheme<'a> {
|
||||||
|
pub raw: Grapheme<'a>,
|
||||||
|
pub source: GraphemeSource,
|
||||||
|
pub visual_pos: Position,
|
||||||
|
/// Document line at the start of the grapheme
|
||||||
|
pub line_idx: usize,
|
||||||
|
/// Document char position at the start of the grapheme
|
||||||
|
pub char_idx: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FormattedGrapheme<'_> {
|
||||||
|
pub fn is_virtual(&self) -> bool {
|
||||||
|
self.source.is_virtual()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn doc_chars(&self) -> usize {
|
||||||
|
self.source.doc_chars()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_whitespace(&self) -> bool {
|
||||||
|
self.raw.is_whitespace()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn width(&self) -> usize {
|
||||||
|
self.raw.width()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn is_word_boundary(&self) -> bool {
|
||||||
|
self.raw.is_word_boundary()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct GraphemeWithSource<'a> {
|
||||||
|
grapheme: Grapheme<'a>,
|
||||||
|
source: GraphemeSource,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> GraphemeWithSource<'a> {
|
||||||
|
fn new(
|
||||||
g: GraphemeStr<'a>,
|
g: GraphemeStr<'a>,
|
||||||
visual_x: usize,
|
visual_x: usize,
|
||||||
tab_width: u16,
|
tab_width: u16,
|
||||||
source: GraphemeSource,
|
source: GraphemeSource,
|
||||||
) -> FormattedGrapheme<'a> {
|
) -> GraphemeWithSource<'a> {
|
||||||
FormattedGrapheme {
|
GraphemeWithSource {
|
||||||
grapheme: Grapheme::new(g, visual_x, tab_width),
|
grapheme: Grapheme::new(g, visual_x, tab_width),
|
||||||
source,
|
source,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Returns whether this grapheme is virtual inline text
|
fn placeholder() -> Self {
|
||||||
pub fn is_virtual(&self) -> bool {
|
GraphemeWithSource {
|
||||||
matches!(self.source, GraphemeSource::VirtualText { .. })
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn placeholder() -> Self {
|
|
||||||
FormattedGrapheme {
|
|
||||||
grapheme: Grapheme::Other { g: " ".into() },
|
grapheme: Grapheme::Other { g: " ".into() },
|
||||||
source: GraphemeSource::Document { codepoints: 0 },
|
source: GraphemeSource::Document { codepoints: 0 },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn doc_chars(&self) -> usize {
|
fn doc_chars(&self) -> usize {
|
||||||
match self.source {
|
self.source.doc_chars()
|
||||||
GraphemeSource::Document { codepoints } => codepoints as usize,
|
|
||||||
GraphemeSource::VirtualText { .. } => 0,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_whitespace(&self) -> bool {
|
fn is_whitespace(&self) -> bool {
|
||||||
self.grapheme.is_whitespace()
|
self.grapheme.is_whitespace()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn width(&self) -> usize {
|
fn width(&self) -> usize {
|
||||||
self.grapheme.width()
|
self.grapheme.width()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_word_boundary(&self) -> bool {
|
fn is_word_boundary(&self) -> bool {
|
||||||
self.grapheme.is_word_boundary()
|
self.grapheme.is_word_boundary()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -139,9 +178,9 @@ pub struct DocumentFormatter<'t> {
|
|||||||
indent_level: Option<usize>,
|
indent_level: Option<usize>,
|
||||||
/// In case a long word needs to be split a single grapheme might need to be wrapped
|
/// In case a long word needs to be split a single grapheme might need to be wrapped
|
||||||
/// while the rest of the word stays on the same line
|
/// while the rest of the word stays on the same line
|
||||||
peeked_grapheme: Option<(FormattedGrapheme<'t>, usize)>,
|
peeked_grapheme: Option<GraphemeWithSource<'t>>,
|
||||||
/// A first-in first-out (fifo) buffer for the Graphemes of any given word
|
/// A first-in first-out (fifo) buffer for the Graphemes of any given word
|
||||||
word_buf: Vec<FormattedGrapheme<'t>>,
|
word_buf: Vec<GraphemeWithSource<'t>>,
|
||||||
/// The index of the next grapheme that will be yielded from the `word_buf`
|
/// The index of the next grapheme that will be yielded from the `word_buf`
|
||||||
word_i: usize,
|
word_i: usize,
|
||||||
}
|
}
|
||||||
@ -157,32 +196,33 @@ pub fn new_at_prev_checkpoint(
|
|||||||
text_fmt: &'t TextFormat,
|
text_fmt: &'t TextFormat,
|
||||||
annotations: &'t TextAnnotations,
|
annotations: &'t TextAnnotations,
|
||||||
char_idx: usize,
|
char_idx: usize,
|
||||||
) -> (Self, usize) {
|
) -> Self {
|
||||||
// TODO divide long lines into blocks to avoid bad performance for long lines
|
// 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_line_idx = text.char_to_line(char_idx.min(text.len_chars()));
|
||||||
let block_char_idx = text.line_to_char(block_line_idx);
|
let block_char_idx = text.line_to_char(block_line_idx);
|
||||||
annotations.reset_pos(block_char_idx);
|
annotations.reset_pos(block_char_idx);
|
||||||
(
|
|
||||||
DocumentFormatter {
|
DocumentFormatter {
|
||||||
text_fmt,
|
text_fmt,
|
||||||
annotations,
|
annotations,
|
||||||
visual_pos: Position { row: 0, col: 0 },
|
visual_pos: Position { row: 0, col: 0 },
|
||||||
graphemes: RopeGraphemes::new(text.slice(block_char_idx..)),
|
graphemes: RopeGraphemes::new(text.slice(block_char_idx..)),
|
||||||
char_pos: block_char_idx,
|
char_pos: block_char_idx,
|
||||||
exhausted: false,
|
exhausted: false,
|
||||||
virtual_lines: 0,
|
virtual_lines: 0,
|
||||||
indent_level: None,
|
indent_level: None,
|
||||||
peeked_grapheme: None,
|
peeked_grapheme: None,
|
||||||
word_buf: Vec::with_capacity(64),
|
word_buf: Vec::with_capacity(64),
|
||||||
word_i: 0,
|
word_i: 0,
|
||||||
line_pos: block_line_idx,
|
line_pos: block_line_idx,
|
||||||
inline_anntoation_graphemes: None,
|
inline_anntoation_graphemes: None,
|
||||||
},
|
}
|
||||||
block_char_idx,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn next_inline_annotation_grapheme(&mut self) -> Option<(&'t str, Option<Highlight>)> {
|
fn next_inline_annotation_grapheme(
|
||||||
|
&mut self,
|
||||||
|
char_pos: usize,
|
||||||
|
) -> Option<(&'t str, Option<Highlight>)> {
|
||||||
loop {
|
loop {
|
||||||
if let Some(&mut (ref mut annotation, highlight)) =
|
if let Some(&mut (ref mut annotation, highlight)) =
|
||||||
self.inline_anntoation_graphemes.as_mut()
|
self.inline_anntoation_graphemes.as_mut()
|
||||||
@ -193,7 +233,7 @@ fn next_inline_annotation_grapheme(&mut self) -> Option<(&'t str, Option<Highlig
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Some((annotation, highlight)) =
|
if let Some((annotation, highlight)) =
|
||||||
self.annotations.next_inline_annotation_at(self.char_pos)
|
self.annotations.next_inline_annotation_at(char_pos)
|
||||||
{
|
{
|
||||||
self.inline_anntoation_graphemes = Some((
|
self.inline_anntoation_graphemes = Some((
|
||||||
UnicodeSegmentation::graphemes(&*annotation.text, true),
|
UnicodeSegmentation::graphemes(&*annotation.text, true),
|
||||||
@ -205,21 +245,20 @@ fn next_inline_annotation_grapheme(&mut self) -> Option<(&'t str, Option<Highlig
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn advance_grapheme(&mut self, col: usize) -> Option<FormattedGrapheme<'t>> {
|
fn advance_grapheme(&mut self, col: usize, char_pos: usize) -> Option<GraphemeWithSource<'t>> {
|
||||||
let (grapheme, source) =
|
let (grapheme, source) =
|
||||||
if let Some((grapheme, highlight)) = self.next_inline_annotation_grapheme() {
|
if let Some((grapheme, highlight)) = self.next_inline_annotation_grapheme(char_pos) {
|
||||||
(grapheme.into(), GraphemeSource::VirtualText { highlight })
|
(grapheme.into(), GraphemeSource::VirtualText { highlight })
|
||||||
} else if let Some(grapheme) = self.graphemes.next() {
|
} else if let Some(grapheme) = self.graphemes.next() {
|
||||||
self.virtual_lines += self.annotations.annotation_lines_at(self.char_pos);
|
self.virtual_lines += self.annotations.annotation_lines_at(self.char_pos);
|
||||||
let codepoints = grapheme.len_chars() as u32;
|
let codepoints = grapheme.len_chars() as u32;
|
||||||
|
|
||||||
let overlay = self.annotations.overlay_at(self.char_pos);
|
let overlay = self.annotations.overlay_at(char_pos);
|
||||||
let grapheme = match overlay {
|
let grapheme = match overlay {
|
||||||
Some((overlay, _)) => overlay.grapheme.as_str().into(),
|
Some((overlay, _)) => overlay.grapheme.as_str().into(),
|
||||||
None => Cow::from(grapheme).into(),
|
None => Cow::from(grapheme).into(),
|
||||||
};
|
};
|
||||||
|
|
||||||
self.char_pos += codepoints as usize;
|
|
||||||
(grapheme, GraphemeSource::Document { codepoints })
|
(grapheme, GraphemeSource::Document { codepoints })
|
||||||
} else {
|
} else {
|
||||||
if self.exhausted {
|
if self.exhausted {
|
||||||
@ -228,19 +267,19 @@ fn advance_grapheme(&mut self, col: usize) -> Option<FormattedGrapheme<'t>> {
|
|||||||
self.exhausted = true;
|
self.exhausted = true;
|
||||||
// EOF grapheme is required for rendering
|
// EOF grapheme is required for rendering
|
||||||
// and correct position computations
|
// and correct position computations
|
||||||
return Some(FormattedGrapheme {
|
return Some(GraphemeWithSource {
|
||||||
grapheme: Grapheme::Other { g: " ".into() },
|
grapheme: Grapheme::Other { g: " ".into() },
|
||||||
source: GraphemeSource::Document { codepoints: 0 },
|
source: GraphemeSource::Document { codepoints: 0 },
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
let grapheme = FormattedGrapheme::new(grapheme, col, self.text_fmt.tab_width, source);
|
let grapheme = GraphemeWithSource::new(grapheme, col, self.text_fmt.tab_width, source);
|
||||||
|
|
||||||
Some(grapheme)
|
Some(grapheme)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Move a word to the next visual line
|
/// Move a word to the next visual line
|
||||||
fn wrap_word(&mut self, virtual_lines_before_word: usize) -> usize {
|
fn wrap_word(&mut self) -> usize {
|
||||||
// softwrap this word to the next line
|
// softwrap this word to the next line
|
||||||
let indent_carry_over = if let Some(indent) = self.indent_level {
|
let indent_carry_over = if let Some(indent) = self.indent_level {
|
||||||
if indent as u16 <= self.text_fmt.max_indent_retain {
|
if indent as u16 <= self.text_fmt.max_indent_retain {
|
||||||
@ -255,14 +294,13 @@ fn wrap_word(&mut self, virtual_lines_before_word: usize) -> usize {
|
|||||||
};
|
};
|
||||||
|
|
||||||
self.visual_pos.col = indent_carry_over as usize;
|
self.visual_pos.col = indent_carry_over as usize;
|
||||||
self.virtual_lines -= virtual_lines_before_word;
|
self.visual_pos.row += 1 + take(&mut self.virtual_lines);
|
||||||
self.visual_pos.row += 1 + virtual_lines_before_word;
|
|
||||||
let mut i = 0;
|
let mut i = 0;
|
||||||
let mut word_width = 0;
|
let mut word_width = 0;
|
||||||
let wrap_indicator = UnicodeSegmentation::graphemes(&*self.text_fmt.wrap_indicator, true)
|
let wrap_indicator = UnicodeSegmentation::graphemes(&*self.text_fmt.wrap_indicator, true)
|
||||||
.map(|g| {
|
.map(|g| {
|
||||||
i += 1;
|
i += 1;
|
||||||
let grapheme = FormattedGrapheme::new(
|
let grapheme = GraphemeWithSource::new(
|
||||||
g.into(),
|
g.into(),
|
||||||
self.visual_pos.col + word_width,
|
self.visual_pos.col + word_width,
|
||||||
self.text_fmt.tab_width,
|
self.text_fmt.tab_width,
|
||||||
@ -288,8 +326,7 @@ fn wrap_word(&mut self, virtual_lines_before_word: usize) -> usize {
|
|||||||
fn advance_to_next_word(&mut self) {
|
fn advance_to_next_word(&mut self) {
|
||||||
self.word_buf.clear();
|
self.word_buf.clear();
|
||||||
let mut word_width = 0;
|
let mut word_width = 0;
|
||||||
let virtual_lines_before_word = self.virtual_lines;
|
let mut word_chars = 0;
|
||||||
let mut virtual_lines_before_grapheme = self.virtual_lines;
|
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
// softwrap word if necessary
|
// softwrap word if necessary
|
||||||
@ -301,27 +338,24 @@ fn advance_to_next_word(&mut self) {
|
|||||||
// However if the last grapheme is multiple columns wide it might extend beyond the EOL.
|
// However if the last grapheme is multiple columns wide it might extend beyond the EOL.
|
||||||
// The condition below ensures that this grapheme is not cutoff and instead wrapped to the next line
|
// The condition below ensures that this grapheme is not cutoff and instead wrapped to the next line
|
||||||
if word_width + self.visual_pos.col > self.text_fmt.viewport_width as usize {
|
if word_width + self.visual_pos.col > self.text_fmt.viewport_width as usize {
|
||||||
self.peeked_grapheme = self.word_buf.pop().map(|grapheme| {
|
self.peeked_grapheme = self.word_buf.pop();
|
||||||
(grapheme, self.virtual_lines - virtual_lines_before_grapheme)
|
|
||||||
});
|
|
||||||
self.virtual_lines = virtual_lines_before_grapheme;
|
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
word_width = self.wrap_word(virtual_lines_before_word);
|
word_width = self.wrap_word();
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual_lines_before_grapheme = self.virtual_lines;
|
let grapheme = if let Some(grapheme) = self.peeked_grapheme.take() {
|
||||||
|
|
||||||
let grapheme = if let Some((grapheme, virtual_lines)) = self.peeked_grapheme.take() {
|
|
||||||
self.virtual_lines += virtual_lines;
|
|
||||||
grapheme
|
grapheme
|
||||||
} else if let Some(grapheme) = self.advance_grapheme(self.visual_pos.col + word_width) {
|
} else if let Some(grapheme) =
|
||||||
|
self.advance_grapheme(self.visual_pos.col + word_width, self.char_pos + word_chars)
|
||||||
|
{
|
||||||
grapheme
|
grapheme
|
||||||
} else {
|
} else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
word_chars += grapheme.doc_chars();
|
||||||
|
|
||||||
// Track indentation
|
// Track indentation
|
||||||
if !grapheme.is_whitespace() && self.indent_level.is_none() {
|
if !grapheme.is_whitespace() && self.indent_level.is_none() {
|
||||||
@ -340,19 +374,19 @@ fn advance_to_next_word(&mut self) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// returns the document line pos of the **next** grapheme that will be yielded
|
/// returns the char index at the end of the last yielded grapheme
|
||||||
pub fn line_pos(&self) -> usize {
|
pub fn next_char_pos(&self) -> usize {
|
||||||
self.line_pos
|
self.char_pos
|
||||||
}
|
}
|
||||||
|
|
||||||
/// returns the visual pos of the **next** grapheme that will be yielded
|
/// returns the visual position at the end of the last yielded grapheme
|
||||||
pub fn visual_pos(&self) -> Position {
|
pub fn next_visual_pos(&self) -> Position {
|
||||||
self.visual_pos
|
self.visual_pos
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'t> Iterator for DocumentFormatter<'t> {
|
impl<'t> Iterator for DocumentFormatter<'t> {
|
||||||
type Item = (FormattedGrapheme<'t>, Position);
|
type Item = FormattedGrapheme<'t>;
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
let grapheme = if self.text_fmt.soft_wrap {
|
let grapheme = if self.text_fmt.soft_wrap {
|
||||||
@ -362,15 +396,18 @@ fn next(&mut self) -> Option<Self::Item> {
|
|||||||
}
|
}
|
||||||
let grapheme = replace(
|
let grapheme = replace(
|
||||||
self.word_buf.get_mut(self.word_i)?,
|
self.word_buf.get_mut(self.word_i)?,
|
||||||
FormattedGrapheme::placeholder(),
|
GraphemeWithSource::placeholder(),
|
||||||
);
|
);
|
||||||
self.word_i += 1;
|
self.word_i += 1;
|
||||||
grapheme
|
grapheme
|
||||||
} else {
|
} else {
|
||||||
self.advance_grapheme(self.visual_pos.col)?
|
self.advance_grapheme(self.visual_pos.col, self.char_pos)?
|
||||||
};
|
};
|
||||||
|
|
||||||
let pos = self.visual_pos;
|
let visual_pos = self.visual_pos;
|
||||||
|
let char_pos = self.char_pos;
|
||||||
|
self.char_pos += grapheme.doc_chars();
|
||||||
|
let line_idx = self.line_pos;
|
||||||
if grapheme.grapheme == Grapheme::Newline {
|
if grapheme.grapheme == Grapheme::Newline {
|
||||||
self.visual_pos.row += 1;
|
self.visual_pos.row += 1;
|
||||||
self.visual_pos.row += take(&mut self.virtual_lines);
|
self.visual_pos.row += take(&mut self.virtual_lines);
|
||||||
@ -379,6 +416,12 @@ fn next(&mut self) -> Option<Self::Item> {
|
|||||||
} else {
|
} else {
|
||||||
self.visual_pos.col += grapheme.width();
|
self.visual_pos.col += grapheme.width();
|
||||||
}
|
}
|
||||||
Some((grapheme, pos))
|
Some(FormattedGrapheme {
|
||||||
|
raw: grapheme.grapheme,
|
||||||
|
source: grapheme.source,
|
||||||
|
visual_pos,
|
||||||
|
line_idx,
|
||||||
|
char_idx: char_pos,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,18 +23,18 @@ fn collect_to_str(&mut self) -> String {
|
|||||||
let viewport_width = self.text_fmt.viewport_width;
|
let viewport_width = self.text_fmt.viewport_width;
|
||||||
let mut line = 0;
|
let mut line = 0;
|
||||||
|
|
||||||
for (grapheme, pos) in self {
|
for grapheme in self {
|
||||||
if pos.row != line {
|
if grapheme.visual_pos.row != line {
|
||||||
line += 1;
|
line += 1;
|
||||||
assert_eq!(pos.row, line);
|
assert_eq!(grapheme.visual_pos.row, line);
|
||||||
write!(res, "\n{}", ".".repeat(pos.col)).unwrap();
|
write!(res, "\n{}", ".".repeat(grapheme.visual_pos.col)).unwrap();
|
||||||
assert!(
|
assert!(
|
||||||
pos.col <= viewport_width as usize,
|
grapheme.visual_pos.col <= viewport_width as usize,
|
||||||
"softwrapped failed {}<={viewport_width}",
|
"softwrapped failed {}<={viewport_width}",
|
||||||
pos.col
|
grapheme.visual_pos.col
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
write!(res, "{}", grapheme.grapheme).unwrap();
|
write!(res, "{}", grapheme.raw).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
res
|
res
|
||||||
@ -48,7 +48,6 @@ fn softwrap_text(text: &str) -> String {
|
|||||||
&TextAnnotations::default(),
|
&TextAnnotations::default(),
|
||||||
0,
|
0,
|
||||||
)
|
)
|
||||||
.0
|
|
||||||
.collect_to_str()
|
.collect_to_str()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -106,7 +105,6 @@ fn overlay_text(text: &str, char_pos: usize, softwrap: bool, overlays: &[Overlay
|
|||||||
TextAnnotations::default().add_overlay(overlays, None),
|
TextAnnotations::default().add_overlay(overlays, None),
|
||||||
char_pos,
|
char_pos,
|
||||||
)
|
)
|
||||||
.0
|
|
||||||
.collect_to_str()
|
.collect_to_str()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -143,7 +141,6 @@ fn annotate_text(text: &str, softwrap: bool, annotations: &[InlineAnnotation]) -
|
|||||||
TextAnnotations::default().add_inline_annotations(annotations, None),
|
TextAnnotations::default().add_inline_annotations(annotations, None),
|
||||||
0,
|
0,
|
||||||
)
|
)
|
||||||
.0
|
|
||||||
.collect_to_str()
|
.collect_to_str()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -182,7 +179,6 @@ fn annotation_and_overlay() {
|
|||||||
.add_overlay(overlay.as_slice(), None),
|
.add_overlay(overlay.as_slice(), None),
|
||||||
0,
|
0,
|
||||||
)
|
)
|
||||||
.0
|
|
||||||
.collect_to_str(),
|
.collect_to_str(),
|
||||||
"fooo bar "
|
"fooo bar "
|
||||||
);
|
);
|
||||||
|
@ -157,16 +157,14 @@ pub fn visual_offset_from_block(
|
|||||||
annotations: &TextAnnotations,
|
annotations: &TextAnnotations,
|
||||||
) -> (Position, usize) {
|
) -> (Position, usize) {
|
||||||
let mut last_pos = Position::default();
|
let mut last_pos = Position::default();
|
||||||
let (formatter, block_start) =
|
let mut formatter =
|
||||||
DocumentFormatter::new_at_prev_checkpoint(text, text_fmt, annotations, anchor);
|
DocumentFormatter::new_at_prev_checkpoint(text, text_fmt, annotations, anchor);
|
||||||
let mut char_pos = block_start;
|
let block_start = formatter.next_char_pos();
|
||||||
|
|
||||||
for (grapheme, vpos) in formatter {
|
while let Some(grapheme) = formatter.next() {
|
||||||
last_pos = vpos;
|
last_pos = grapheme.visual_pos;
|
||||||
char_pos += grapheme.doc_chars();
|
if formatter.next_char_pos() > pos {
|
||||||
|
return (grapheme.visual_pos, block_start);
|
||||||
if char_pos > pos {
|
|
||||||
return (last_pos, block_start);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -189,22 +187,21 @@ pub fn visual_offset_from_anchor(
|
|||||||
annotations: &TextAnnotations,
|
annotations: &TextAnnotations,
|
||||||
max_rows: usize,
|
max_rows: usize,
|
||||||
) -> Result<(Position, usize), VisualOffsetError> {
|
) -> Result<(Position, usize), VisualOffsetError> {
|
||||||
let (formatter, block_start) =
|
let mut formatter =
|
||||||
DocumentFormatter::new_at_prev_checkpoint(text, text_fmt, annotations, anchor);
|
DocumentFormatter::new_at_prev_checkpoint(text, text_fmt, annotations, anchor);
|
||||||
let mut char_pos = block_start;
|
|
||||||
let mut anchor_line = None;
|
let mut anchor_line = None;
|
||||||
let mut found_pos = None;
|
let mut found_pos = None;
|
||||||
let mut last_pos = Position::default();
|
let mut last_pos = Position::default();
|
||||||
|
|
||||||
|
let block_start = formatter.next_char_pos();
|
||||||
if pos < block_start {
|
if pos < block_start {
|
||||||
return Err(VisualOffsetError::PosBeforeAnchorRow);
|
return Err(VisualOffsetError::PosBeforeAnchorRow);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (grapheme, vpos) in formatter {
|
while let Some(grapheme) = formatter.next() {
|
||||||
last_pos = vpos;
|
last_pos = grapheme.visual_pos;
|
||||||
char_pos += grapheme.doc_chars();
|
|
||||||
|
|
||||||
if char_pos > pos {
|
if formatter.next_char_pos() > pos {
|
||||||
if let Some(anchor_line) = anchor_line {
|
if let Some(anchor_line) = anchor_line {
|
||||||
last_pos.row -= anchor_line;
|
last_pos.row -= anchor_line;
|
||||||
return Ok((last_pos, block_start));
|
return Ok((last_pos, block_start));
|
||||||
@ -212,7 +209,7 @@ pub fn visual_offset_from_anchor(
|
|||||||
found_pos = Some(last_pos);
|
found_pos = Some(last_pos);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if char_pos > anchor && anchor_line.is_none() {
|
if formatter.next_char_pos() > anchor && anchor_line.is_none() {
|
||||||
if let Some(mut found_pos) = found_pos {
|
if let Some(mut found_pos) = found_pos {
|
||||||
return if found_pos.row == last_pos.row {
|
return if found_pos.row == last_pos.row {
|
||||||
found_pos.row = 0;
|
found_pos.row = 0;
|
||||||
@ -226,7 +223,7 @@ pub fn visual_offset_from_anchor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if let Some(anchor_line) = anchor_line {
|
if let Some(anchor_line) = anchor_line {
|
||||||
if vpos.row >= anchor_line + max_rows {
|
if grapheme.visual_pos.row >= anchor_line + max_rows {
|
||||||
return Err(VisualOffsetError::PosAfterMaxRow);
|
return Err(VisualOffsetError::PosAfterMaxRow);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -404,34 +401,33 @@ pub fn char_idx_at_visual_block_offset(
|
|||||||
text_fmt: &TextFormat,
|
text_fmt: &TextFormat,
|
||||||
annotations: &TextAnnotations,
|
annotations: &TextAnnotations,
|
||||||
) -> (usize, usize) {
|
) -> (usize, usize) {
|
||||||
let (formatter, mut char_idx) =
|
let mut formatter =
|
||||||
DocumentFormatter::new_at_prev_checkpoint(text, text_fmt, annotations, anchor);
|
DocumentFormatter::new_at_prev_checkpoint(text, text_fmt, annotations, anchor);
|
||||||
let mut last_char_idx = char_idx;
|
let mut last_char_idx = formatter.next_char_pos();
|
||||||
let mut last_char_idx_on_line = None;
|
let mut last_char_idx_on_line = None;
|
||||||
let mut last_row = 0;
|
let mut last_row = 0;
|
||||||
for (grapheme, grapheme_pos) in formatter {
|
for grapheme in &mut formatter {
|
||||||
match grapheme_pos.row.cmp(&row) {
|
match grapheme.visual_pos.row.cmp(&row) {
|
||||||
Ordering::Equal => {
|
Ordering::Equal => {
|
||||||
if grapheme_pos.col + grapheme.width() > column {
|
if grapheme.visual_pos.col + grapheme.width() > column {
|
||||||
if !grapheme.is_virtual() {
|
if !grapheme.is_virtual() {
|
||||||
return (char_idx, 0);
|
return (grapheme.char_idx, 0);
|
||||||
} else if let Some(char_idx) = last_char_idx_on_line {
|
} else if let Some(char_idx) = last_char_idx_on_line {
|
||||||
return (char_idx, 0);
|
return (char_idx, 0);
|
||||||
}
|
}
|
||||||
} else if !grapheme.is_virtual() {
|
} else if !grapheme.is_virtual() {
|
||||||
last_char_idx_on_line = Some(char_idx)
|
last_char_idx_on_line = Some(grapheme.char_idx)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ordering::Greater => return (last_char_idx, row - last_row),
|
Ordering::Greater => return (last_char_idx, row - last_row),
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
|
|
||||||
last_char_idx = char_idx;
|
last_char_idx = grapheme.char_idx;
|
||||||
last_row = grapheme_pos.row;
|
last_row = grapheme.visual_pos.row;
|
||||||
char_idx += grapheme.doc_chars();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
(char_idx, 0)
|
(formatter.next_char_pos(), 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -179,21 +179,18 @@ pub fn render_text<'t>(
|
|||||||
line_decorations: &mut [Box<dyn LineDecoration + '_>],
|
line_decorations: &mut [Box<dyn LineDecoration + '_>],
|
||||||
translated_positions: &mut [TranslatedPosition],
|
translated_positions: &mut [TranslatedPosition],
|
||||||
) {
|
) {
|
||||||
let (
|
let mut row_off = visual_offset_from_block(
|
||||||
Position {
|
|
||||||
row: mut row_off, ..
|
|
||||||
},
|
|
||||||
mut char_pos,
|
|
||||||
) = visual_offset_from_block(
|
|
||||||
text,
|
text,
|
||||||
offset.anchor,
|
offset.anchor,
|
||||||
offset.anchor,
|
offset.anchor,
|
||||||
text_fmt,
|
text_fmt,
|
||||||
text_annotations,
|
text_annotations,
|
||||||
);
|
)
|
||||||
|
.0
|
||||||
|
.row;
|
||||||
row_off += offset.vertical_offset;
|
row_off += offset.vertical_offset;
|
||||||
|
|
||||||
let (mut formatter, mut first_visible_char_idx) =
|
let mut formatter =
|
||||||
DocumentFormatter::new_at_prev_checkpoint(text, text_fmt, text_annotations, offset.anchor);
|
DocumentFormatter::new_at_prev_checkpoint(text, text_fmt, text_annotations, offset.anchor);
|
||||||
let mut syntax_styles = StyleIter {
|
let mut syntax_styles = StyleIter {
|
||||||
text_style: renderer.text_style,
|
text_style: renderer.text_style,
|
||||||
@ -226,19 +223,19 @@ pub fn render_text<'t>(
|
|||||||
let mut overlay_style_span = overlay_styles
|
let mut overlay_style_span = overlay_styles
|
||||||
.next()
|
.next()
|
||||||
.unwrap_or_else(|| (Style::default(), usize::MAX));
|
.unwrap_or_else(|| (Style::default(), usize::MAX));
|
||||||
|
let mut first_visible_char_idx = formatter.next_char_pos();
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
// formattter.line_pos returns to line index of the next grapheme
|
// formattter.line_pos returns to line index of the next grapheme
|
||||||
// so it must be called before formatter.next
|
// so it must be called before formatter.next
|
||||||
let doc_line = formatter.line_pos();
|
let Some(mut grapheme) = formatter.next() else {
|
||||||
let Some((grapheme, mut pos)) = formatter.next() else {
|
let mut last_pos = formatter.next_visual_pos();
|
||||||
let mut last_pos = formatter.visual_pos();
|
|
||||||
if last_pos.row >= row_off {
|
if last_pos.row >= row_off {
|
||||||
last_pos.col -= 1;
|
last_pos.col -= 1;
|
||||||
last_pos.row -= row_off;
|
last_pos.row -= row_off;
|
||||||
// check if any positions translated on the fly (like cursor) are at the EOF
|
// check if any positions translated on the fly (like cursor) are at the EOF
|
||||||
translate_positions(
|
translate_positions(
|
||||||
char_pos + 1,
|
text.len_chars() + 1,
|
||||||
first_visible_char_idx,
|
first_visible_char_idx,
|
||||||
translated_positions,
|
translated_positions,
|
||||||
text_fmt,
|
text_fmt,
|
||||||
@ -250,46 +247,56 @@ pub fn render_text<'t>(
|
|||||||
};
|
};
|
||||||
|
|
||||||
// skip any graphemes on visual lines before the block start
|
// skip any graphemes on visual lines before the block start
|
||||||
if pos.row < row_off {
|
// if pos.row < row_off {
|
||||||
if char_pos >= syntax_style_span.1 {
|
// if char_pos >= syntax_style_span.1 {
|
||||||
syntax_style_span = if let Some(syntax_style_span) = syntax_styles.next() {
|
// syntax_style_span = if let Some(syntax_style_span) = syntax_styles.next() {
|
||||||
syntax_style_span
|
// syntax_style_span
|
||||||
|
// } else {
|
||||||
|
// break;
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// if char_pos >= overlay_style_span.1 {
|
||||||
|
// overlay_style_span = if let Some(overlay_style_span) = overlay_styles.next() {
|
||||||
|
// overlay_style_span
|
||||||
|
if grapheme.visual_pos.row < row_off {
|
||||||
|
if grapheme.char_idx >= style_span.1 {
|
||||||
|
style_span = if let Some(style_span) = styles.next() {
|
||||||
|
style_span
|
||||||
} else {
|
} else {
|
||||||
break;
|
break;
|
||||||
}
|
};
|
||||||
}
|
overlay_span = if let Some(overlay_span) = overlays.next() {
|
||||||
if char_pos >= overlay_style_span.1 {
|
overlay_span
|
||||||
overlay_style_span = if let Some(overlay_style_span) = overlay_styles.next() {
|
|
||||||
overlay_style_span
|
|
||||||
} else {
|
} else {
|
||||||
break;
|
break;
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
char_pos += grapheme.doc_chars();
|
first_visible_char_idx = formatter.next_char_pos();
|
||||||
first_visible_char_idx = char_pos + 1;
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
pos.row -= row_off;
|
grapheme.visual_pos.row -= row_off;
|
||||||
|
|
||||||
// if the end of the viewport is reached stop rendering
|
// if the end of the viewport is reached stop rendering
|
||||||
if pos.row as u16 >= renderer.viewport.height {
|
if grapheme.visual_pos.row as u16 >= renderer.viewport.height + renderer.offset.row as u16 {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// apply decorations before rendering a new line
|
// apply decorations before rendering a new line
|
||||||
if pos.row as u16 != last_line_pos.visual_line {
|
if grapheme.visual_pos.row as u16 != last_line_pos.visual_line {
|
||||||
if pos.row > 0 {
|
if grapheme.visual_pos.row > 0 {
|
||||||
renderer.draw_indent_guides(last_line_indent_level, last_line_pos.visual_line);
|
// draw indent guides for the last line
|
||||||
|
renderer
|
||||||
|
.draw_indent_guides(last_line_indent_level, last_line_pos.visual_line as u16);
|
||||||
is_in_indent_area = true;
|
is_in_indent_area = true;
|
||||||
for line_decoration in &mut *line_decorations {
|
for line_decoration in &mut *line_decorations {
|
||||||
line_decoration.render_foreground(renderer, last_line_pos, char_pos);
|
line_decoration.render_foreground(renderer, last_line_pos, grapheme.char_idx);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
last_line_pos = LinePos {
|
last_line_pos = LinePos {
|
||||||
first_visual_line: doc_line != last_line_pos.doc_line,
|
first_visual_line: grapheme.line_idx != last_line_pos.doc_line,
|
||||||
doc_line,
|
doc_line: grapheme.line_idx,
|
||||||
visual_line: pos.row as u16,
|
visual_line: grapheme.visual_pos.row as u16,
|
||||||
start_char_idx: char_pos,
|
start_char_idx: grapheme.char_idx,
|
||||||
};
|
};
|
||||||
for line_decoration in &mut *line_decorations {
|
for line_decoration in &mut *line_decorations {
|
||||||
line_decoration.render_background(renderer, last_line_pos);
|
line_decoration.render_background(renderer, last_line_pos);
|
||||||
@ -297,26 +304,25 @@ pub fn render_text<'t>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// acquire the correct grapheme style
|
// acquire the correct grapheme style
|
||||||
while char_pos >= syntax_style_span.1 {
|
while grapheme.char_idx >= syntax_style_span.1 {
|
||||||
syntax_style_span = syntax_styles
|
syntax_style_span = syntax_styles
|
||||||
.next()
|
.next()
|
||||||
.unwrap_or((Style::default(), usize::MAX));
|
.unwrap_or((Style::default(), usize::MAX));
|
||||||
}
|
}
|
||||||
while char_pos >= overlay_style_span.1 {
|
while grapheme.char_idx >= overlay_style_span.1 {
|
||||||
overlay_style_span = overlay_styles
|
overlay_style_span = overlay_styles
|
||||||
.next()
|
.next()
|
||||||
.unwrap_or((Style::default(), usize::MAX));
|
.unwrap_or((Style::default(), usize::MAX));
|
||||||
}
|
}
|
||||||
char_pos += grapheme.doc_chars();
|
|
||||||
|
|
||||||
// check if any positions translated on the fly (like cursor) has been reached
|
// check if any positions translated on the fly (like cursor) has been reached
|
||||||
translate_positions(
|
translate_positions(
|
||||||
char_pos,
|
formatter.next_char_pos(),
|
||||||
first_visible_char_idx,
|
first_visible_char_idx,
|
||||||
translated_positions,
|
translated_positions,
|
||||||
text_fmt,
|
text_fmt,
|
||||||
renderer,
|
renderer,
|
||||||
pos,
|
grapheme.visual_pos,
|
||||||
);
|
);
|
||||||
|
|
||||||
let (syntax_style, overlay_style) =
|
let (syntax_style, overlay_style) =
|
||||||
@ -332,27 +338,37 @@ pub fn render_text<'t>(
|
|||||||
|
|
||||||
let is_virtual = grapheme.is_virtual();
|
let is_virtual = grapheme.is_virtual();
|
||||||
renderer.draw_grapheme(
|
renderer.draw_grapheme(
|
||||||
|
<<<<<<< HEAD
|
||||||
grapheme.grapheme,
|
grapheme.grapheme,
|
||||||
GraphemeStyle {
|
GraphemeStyle {
|
||||||
syntax_style,
|
syntax_style,
|
||||||
overlay_style,
|
overlay_style,
|
||||||
},
|
},
|
||||||
is_virtual,
|
is_virtual,
|
||||||
|
||||||| parent of 5e32edd8 (track char_idx in DocFormatter)
|
||||||
|
grapheme.grapheme,
|
||||||
|
grapheme_style,
|
||||||
|
virt,
|
||||||
|
=======
|
||||||
|
grapheme.raw,
|
||||||
|
grapheme_style,
|
||||||
|
virt,
|
||||||
|
>>>>>>> 5e32edd8 (track char_idx in DocFormatter)
|
||||||
&mut last_line_indent_level,
|
&mut last_line_indent_level,
|
||||||
&mut is_in_indent_area,
|
&mut is_in_indent_area,
|
||||||
pos,
|
grapheme.visual_pos,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderer.draw_indent_guides(last_line_indent_level, last_line_pos.visual_line);
|
renderer.draw_indent_guides(last_line_indent_level, last_line_pos.visual_line);
|
||||||
for line_decoration in &mut *line_decorations {
|
for line_decoration in &mut *line_decorations {
|
||||||
line_decoration.render_foreground(renderer, last_line_pos, char_pos);
|
line_decoration.render_foreground(renderer, last_line_pos, formatter.next_char_pos());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct TextRenderer<'a> {
|
pub struct TextRenderer<'a> {
|
||||||
pub surface: &'a mut Surface,
|
surface: &'a mut Surface,
|
||||||
pub text_style: Style,
|
pub text_style: Style,
|
||||||
pub whitespace_style: Style,
|
pub whitespace_style: Style,
|
||||||
pub indent_guide_char: String,
|
pub indent_guide_char: String,
|
||||||
|
Loading…
Reference in New Issue
Block a user