refactor: organize host modules and document architecture with UML
Verify Tessera GUI / verify (push) Failing after 2m2s
Verify Tessera GUI / verify (push) Failing after 2m2s
This commit is contained in:
@@ -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,
|
||||
))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user