refactor: organize host modules and document architecture with UML
Verify Tessera GUI / verify (push) Failing after 2m2s

This commit is contained in:
2026-10-25 21:25:43 +08:00
parent 48fcb96f42
commit 18376793b0
85 changed files with 4450 additions and 3653 deletions
+151
View File
@@ -0,0 +1,151 @@
//! Read-only, grapheme-aware source selection, independent of console editing.
use crate::{
geometry::Point,
text::{self, TextStyle},
};
use unicode_segmentation::UnicodeSegmentation;
#[derive(Debug, Default)]
pub(super) struct SourceSelection {
lines: Vec<String>,
anchor: (usize, usize),
end: (usize, usize),
pub dragging: bool,
}
impl SourceSelection {
pub fn set_lines(&mut self, lines: &[String]) {
if self.lines != lines {
self.lines = lines.to_vec();
self.anchor = (0, 0);
self.end = (0, 0);
self.dragging = false;
}
}
fn locate(p: Point, origin: Point, lines: &[String]) -> (usize, usize) {
let row = (((p.y - origin.y) / super::ROW).floor().max(0.0) as usize)
.min(lines.len().saturating_sub(1));
let Some(line) = lines.get(row) else {
return (0, 0);
};
let x = (p.x - origin.x).max(0.0);
let mut byte = line.len();
for (i, g) in line.grapheme_indices(true) {
let left = text::measure(&line[..i], TextStyle::code()).width;
let right = text::measure(&line[..i + g.len()], TextStyle::code()).width;
if x < (left + right) * 0.5 {
byte = i;
break;
}
}
(row, byte)
}
pub fn begin(&mut self, p: Point, origin: Point, lines: &[String]) {
self.anchor = Self::locate(p, origin, lines);
self.end = self.anchor;
self.dragging = true;
}
pub fn extend(&mut self, p: Point, origin: Point, lines: &[String]) {
self.end = Self::locate(p, origin, lines);
}
pub fn navigate(
&mut self,
key: &winit::keyboard::NamedKey,
shift: bool,
command: bool,
) -> Option<usize> {
use winit::keyboard::NamedKey::*;
let (mut row, mut byte) = self.end;
let line = self.lines.get(row)?;
match key {
ArrowLeft => {
if byte > 0 {
byte = line[..byte]
.grapheme_indices(true)
.next_back()
.map_or(0, |(i, _)| i);
} else if row > 0 {
row -= 1;
byte = self.lines[row].len();
}
}
ArrowRight => {
if byte < line.len() {
byte += line[byte..].graphemes(true).next().map_or(0, str::len);
} else if row + 1 < self.lines.len() {
row += 1;
byte = 0;
}
}
ArrowUp | ArrowDown => {
let col = line[..byte].graphemes(true).count();
row = if *key == ArrowUp {
row.saturating_sub(1)
} else {
(row + 1).min(self.lines.len() - 1)
};
byte = self.lines[row]
.graphemes(true)
.take(col)
.map(str::len)
.sum();
}
Home => {
if command {
row = 0;
}
byte = 0;
}
End => {
if command {
row = self.lines.len() - 1;
}
byte = self.lines[row].len();
}
_ => return None,
}
self.end = (row, byte);
if !shift {
self.anchor = self.end;
}
Some(row)
}
fn range(&self, all: bool) -> ((usize, usize), (usize, usize)) {
if all {
return (
(0, 0),
(
self.lines.len().saturating_sub(1),
self.lines.last().map_or(0, String::len),
),
);
}
(self.anchor.min(self.end), self.anchor.max(self.end))
}
pub fn text(&self, all: bool) -> String {
let (a, b) = self.range(all);
self.lines
.iter()
.enumerate()
.filter(|(i, _)| *i >= a.0 && *i <= b.0)
.map(|(i, line)| {
let start = if i == a.0 { a.1 } else { 0 };
let end = if i == b.0 { b.1 } else { line.len() };
line.get(start..end).unwrap_or("").to_owned()
})
.collect::<Vec<_>>()
.join("\n")
}
pub fn span(&self, row: usize, all: bool, lines: &[String]) -> Option<(f32, f32)> {
let (a, b) = self.range(all);
if row < a.0 || row > b.0 || a == b {
return None;
}
let line = lines.get(row)?;
let start = if row == a.0 { a.1 } else { 0 };
let end = if row == b.0 { b.1 } else { line.len() };
Some((
text::measure(line.get(..start)?, TextStyle::code()).width,
text::measure(line.get(..end)?, TextStyle::code()).width,
))
}
}