Bracket and match offsets from the last draw belong to that text's stamp, so an input-method edit before the next draw no longer splits a character into font runs. A press past the last glyph's start picks the nearer of that start and the line end. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
87 lines
2.8 KiB
Rust
87 lines
2.8 KiB
Rust
//! Where drawn columns fall, as gpui lays them out.
|
|
|
|
use std::sync::Arc;
|
|
|
|
use gpui::{Hsla, LineLayout, Pixels, SharedString, TextRun, Window, font};
|
|
|
|
use super::layout::Shown;
|
|
|
|
/// A laid-out line: wide characters keep their own width.
|
|
pub(crate) struct Measured {
|
|
text: SharedString,
|
|
layout: Arc<LineLayout>,
|
|
advance: Pixels,
|
|
}
|
|
|
|
impl Measured {
|
|
/// `shown` in the code font `family` at `size`.
|
|
pub fn new(
|
|
shown: &Shown,
|
|
family: SharedString,
|
|
size: Pixels,
|
|
advance: Pixels,
|
|
window: &Window,
|
|
) -> Self {
|
|
let base = font(family);
|
|
let run = |len: usize, style: Option<&gpui::HighlightStyle>| {
|
|
let mut font = base.clone();
|
|
if let Some(style) = style {
|
|
font.weight = style.font_weight.unwrap_or(font.weight);
|
|
font.style = style.font_style.unwrap_or(font.style);
|
|
}
|
|
TextRun {
|
|
len,
|
|
font,
|
|
color: Hsla::default(),
|
|
background_color: None,
|
|
underline: None,
|
|
strikethrough: None,
|
|
}
|
|
};
|
|
let (text, mut runs, mut at) = (shown.text.clone(), Vec::new(), 0);
|
|
for (range, style) in shown.styles.iter().filter(|(range, _)| !range.is_empty()) {
|
|
if range.start > at {
|
|
runs.push(run(range.start - at, None));
|
|
}
|
|
runs.push(run(range.len(), Some(style)));
|
|
at = range.end;
|
|
}
|
|
if at < text.len() {
|
|
runs.push(run(text.len() - at, None));
|
|
}
|
|
let layout = window.text_system().layout_line(&text, size, &runs, None);
|
|
Self {
|
|
text,
|
|
layout,
|
|
advance,
|
|
}
|
|
}
|
|
|
|
/// Where `column` starts; past the end in whole advances.
|
|
pub fn x(&self, column: usize) -> Pixels {
|
|
match self.text.char_indices().nth(column) {
|
|
Some((at, _)) => self.layout.x_for_index(at),
|
|
None => {
|
|
let past = column - self.text.chars().count();
|
|
self.layout.width + self.advance * past as f32
|
|
}
|
|
}
|
|
}
|
|
|
|
/// The column whose start lies nearest `x`, line end included.
|
|
pub fn column(&self, x: Pixels) -> usize {
|
|
let starts = self.layout.runs.iter().flat_map(|run| {
|
|
run.glyphs
|
|
.iter()
|
|
.map(|glyph| (glyph.index, glyph.position.x))
|
|
});
|
|
let off = |edge: Pixels| f32::from((edge - x).abs());
|
|
let at = starts
|
|
.chain([(self.text.len(), self.layout.width)])
|
|
.min_by(|a, b| off(a.1).total_cmp(&off(b.1)))
|
|
.map(|(at, _)| at.min(self.text.len()))
|
|
.expect("the line end is always a stop");
|
|
self.text[..at].chars().count()
|
|
}
|
|
}
|