So much better

This commit is contained in:
2026-01-26 02:24:04 +01:00
parent bde64e7dc5
commit 1b32a91b0d
16 changed files with 714 additions and 135 deletions

View File

@@ -60,6 +60,40 @@ pub struct Editor {
search: SearchState,
}
impl Editor {
pub fn start_selection(&mut self) {
self.text.start_selection();
}
pub fn cancel_selection(&mut self) {
self.text.cancel_selection();
}
pub fn is_selecting(&self) -> bool {
self.text.is_selecting()
}
pub fn copy(&mut self) {
self.text.copy();
}
pub fn cut(&mut self) -> bool {
self.text.cut()
}
pub fn paste(&mut self) -> bool {
self.text.paste()
}
pub fn select_all(&mut self) {
self.text.select_all();
}
pub fn selection_range(&self) -> Option<((usize, usize), (usize, usize))> {
self.text.selection_range()
}
}
impl Default for Editor {
fn default() -> Self {
Self::new()
@@ -300,6 +334,9 @@ impl Editor {
pub fn render(&self, frame: &mut Frame, area: Rect, highlighter: Highlighter) {
let (cursor_row, cursor_col) = self.text.cursor();
let cursor_style = Style::default().bg(Color::White).fg(Color::Black);
let selection_style = Style::default().bg(Color::Rgb(60, 80, 120));
let selection = self.text.selection_range();
let lines: Vec<Line> = self
.text
@@ -309,39 +346,30 @@ impl Editor {
.map(|(row, line)| {
let tokens = highlighter(row, line);
let mut spans: Vec<Span> = Vec::new();
let mut col = 0;
if row == cursor_row {
let mut col = 0;
for (style, text) in tokens {
let text_len = text.chars().count();
if cursor_col >= col && cursor_col < col + text_len {
let before = text.chars().take(cursor_col - col).collect::<String>();
let cursor_char =
text.chars().nth(cursor_col - col).unwrap_or(' ');
let after =
text.chars().skip(cursor_col - col + 1).collect::<String>();
for (base_style, text) in tokens {
for ch in text.chars() {
let is_cursor = row == cursor_row && col == cursor_col;
let is_selected = is_in_selection(row, col, selection);
if !before.is_empty() {
spans.push(Span::styled(before, style));
}
spans.push(Span::styled(cursor_char.to_string(), cursor_style));
if !after.is_empty() {
spans.push(Span::styled(after, style));
}
let style = if is_cursor {
cursor_style
} else if is_selected {
base_style.bg(selection_style.bg.unwrap())
} else {
spans.push(Span::styled(text, style));
}
col += text_len;
}
if cursor_col >= col {
spans.push(Span::styled(" ", cursor_style));
}
} else {
for (style, text) in tokens {
spans.push(Span::styled(text, style));
base_style
};
spans.push(Span::styled(ch.to_string(), style));
col += 1;
}
}
if row == cursor_row && cursor_col >= col {
spans.push(Span::styled(" ", cursor_style));
}
Line::from(spans)
})
.collect();
@@ -468,3 +496,23 @@ impl Editor {
fn is_word_char(c: char) -> bool {
c.is_alphanumeric() || matches!(c, '!' | '@' | '?' | '.' | ':' | '_' | '#')
}
fn is_in_selection(row: usize, col: usize, selection: Option<((usize, usize), (usize, usize))>) -> bool {
let Some(((start_row, start_col), (end_row, end_col))) = selection else {
return false;
};
if row < start_row || row > end_row {
return false;
}
if row == start_row && row == end_row {
col >= start_col && col < end_col
} else if row == start_row {
col >= start_col
} else if row == end_row {
col < end_col
} else {
true
}
}