Skip to content

Commit

Permalink
feat: Improved special text editing support (#622)
Browse files Browse the repository at this point in the history
* feat: Improved text editing support

* some improvements

* Clean up and simplify

* cleanup and fixes

* cleanup

* use chars len instead of bytes len in EditorHistory

* fix: Move cursor accordingly

* chore: Clean up

* chore: Clean up

* chore: Add test

* chore: Update tests

* chore: Update tests

* chore: Update tests

* chore: Update tests

* chore: Update tests

* chore: Update tests

* chore: Update tests

* chore: Update tests

* chore: Update tests

* chore: Update tests

* chore: Update tests

* chore: Update tests

* chore: Update tests

* chore: Update tests

* chore: Update tests

* chore: Clean up

* chore: Clean up

* chore: Clean up

* chore: Clean up
  • Loading branch information
marc2332 authored Jun 1, 2024
1 parent 128b962 commit 18d3e16
Show file tree
Hide file tree
Showing 10 changed files with 293 additions and 48 deletions.
2 changes: 1 addition & 1 deletion crates/components/src/input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ pub fn Input(
let (background, cursor_char) = if focus.is_focused() {
(
theme.hover_background,
editable.editor().read().cursor_pos().to_string(),
editable.editor().read().visible_cursor_pos().to_string(),
)
} else {
(theme.background, "none".to_string())
Expand Down
1 change: 1 addition & 0 deletions crates/engine/src/skia.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ pub use skia_safe::{
path::ArcSize,
rrect::Corner,
runtime_effect::Uniform,
surfaces::raster_n32_premul,
svg,
textlayout::{
paragraph::GlyphClusterInfo, Decoration, FontCollection, FontFeature, LineMetrics,
Expand Down
10 changes: 5 additions & 5 deletions crates/hooks/src/editor_history.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,10 @@ impl EditorHistory {
let idx_end = match last_change {
HistoryChange::Remove { idx, text } => {
rope.insert(*idx, text);
idx + text.len()
idx + text.chars().count()
}
HistoryChange::InsertChar { idx, .. } => {
rope.remove(*idx..*idx + 1);
HistoryChange::InsertChar { idx, char: ch } => {
rope.remove(*idx..*idx + ch.len_utf8());
*idx
}
HistoryChange::InsertText { idx, text } => {
Expand All @@ -85,7 +85,7 @@ impl EditorHistory {
if let Some(next_change) = next_change {
let idx_end = match next_change {
HistoryChange::Remove { idx, text } => {
rope.remove(*idx..idx + text.len());
rope.remove(*idx..idx + text.chars().count());
*idx
}
HistoryChange::InsertChar { idx, char: ch } => {
Expand All @@ -94,7 +94,7 @@ impl EditorHistory {
}
HistoryChange::InsertText { idx, text, .. } => {
rope.insert(*idx, text);
idx + text.len()
idx + text.chars().count()
}
};
self.current_change += 1;
Expand Down
41 changes: 26 additions & 15 deletions crates/hooks/src/rope_editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,14 @@ impl TextEditor for RopeEditor {
self.rope.line_to_char(line_idx)
}

fn utf16_cu_to_char(&self, utf16_cu_idx: usize) -> usize {
self.rope.utf16_cu_to_char(utf16_cu_idx)
}

fn char_to_utf16_cu(&self, idx: usize) -> usize {
self.rope.char_to_utf16_cu(idx)
}

fn line(&self, line_idx: usize) -> Option<Line<'_>> {
let line = self.rope.get_line(line_idx);

Expand Down Expand Up @@ -145,43 +153,46 @@ impl TextEditor for RopeEditor {
return Some((0, len));
}

match selected_from_row.cmp(&selected_to_row) {
let highlights = match selected_from_row.cmp(&selected_to_row) {
// Selection direction is from bottom -> top
Ordering::Greater => {
if selected_from_row == editor_id {
// Starting line
return Some((0, selected_from_col_idx));
Some((0, selected_from_col_idx))
} else if selected_to_row == editor_id {
// Ending line
let len = self.line(selected_to_row).unwrap().len_chars();
return Some((selected_to_col_idx, len));
Some((selected_to_col_idx, len))
} else {
None
}
}
// Selection direction is from top -> bottom
Ordering::Less => {
if selected_from_row == editor_id {
// Starting line
let len = self.line(selected_from_row).unwrap().len_chars();
return Some((selected_from_col_idx, len));
Some((selected_from_col_idx, len))
} else if selected_to_row == editor_id {
// Ending line
return Some((0, selected_to_col_idx));
Some((0, selected_to_col_idx))
} else {
None
}
}
Ordering::Equal => {
Ordering::Equal if selected_from_row == editor_id => {
// Starting and endline line are the same
if selected_from_row == editor_id {
return Some((
selected_from - editor_row_idx,
selected_to - editor_row_idx,
));
}
Some((selected_from - editor_row_idx, selected_to - editor_row_idx))
}
}
_ => None,
};

None
highlights.map(|(from, to)| (self.char_to_utf16_cu(from), self.char_to_utf16_cu(to)))
} else {
Some((selected_from, selected_to))
Some((
self.char_to_utf16_cu(selected_from),
self.char_to_utf16_cu(selected_to),
))
}
}

Expand Down
40 changes: 28 additions & 12 deletions crates/hooks/src/text_editor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,11 @@ impl Line<'_> {
self.text.chars().filter(|c| c != &'\r').count()
}

/// Get the length of the line
pub fn utf16_len_chars(&self) -> usize {
self.text.encode_utf16().count()
}

/// Get the text of the line
fn as_str(&self) -> &str {
&self.text
Expand Down Expand Up @@ -110,6 +115,10 @@ pub trait TextEditor {
/// Get the first char from the given line
fn line_to_char(&self, line_idx: usize) -> usize;

fn utf16_cu_to_char(&self, utf16_cu_idx: usize) -> usize;

fn char_to_utf16_cu(&self, idx: usize) -> usize;

/// Get a line from the text
fn line(&self, line_idx: usize) -> Option<Line<'_>>;

Expand All @@ -132,6 +141,11 @@ pub trait TextEditor {
self.cursor().col()
}

/// Get the visible cursor position
fn visible_cursor_col(&self) -> usize {
self.char_to_utf16_cu(self.cursor_col())
}

/// Move the cursor 1 line down
fn cursor_down(&mut self) {
let new_row = self.cursor_row() + 1;
Expand Down Expand Up @@ -162,6 +176,12 @@ pub trait TextEditor {
line_begining + self.cursor_col()
}

/// Get the cursor position
fn visible_cursor_pos(&self) -> usize {
let line_begining = self.char_to_utf16_cu(self.line_to_char(self.cursor_row()));
line_begining + self.char_to_utf16_cu(self.cursor_col())
}

/// Set the cursor position
fn set_cursor_pos(&mut self, pos: usize) {
let row = self.char_to_line(pos);
Expand Down Expand Up @@ -450,21 +470,17 @@ pub trait TextEditor {

_ => {
if let Ok(ch) = character.parse::<char>() {
// https://github.com/marc2332/freya/issues/461
if !ch.is_ascii_control() && ch.len_utf8() <= 2 {
// Inserts a character
let char_idx =
self.line_to_char(self.cursor_row()) + self.cursor_col();
self.insert(character, char_idx);
self.cursor_right();

event.insert(TextEvent::TEXT_CHANGED);
}
} else if character.is_ascii() {
// Inserts a character
let char_idx = self.line_to_char(self.cursor_row()) + self.cursor_col();
self.insert_char(ch, char_idx);
self.cursor_right();

event.insert(TextEvent::TEXT_CHANGED);
} else {
// Inserts a text
let char_idx = self.line_to_char(self.cursor_row()) + self.cursor_col();
self.insert(character, char_idx);
self.set_cursor_pos(char_idx + character.len());
self.set_cursor_pos(char_idx + character.chars().count());

event.insert(TextEvent::TEXT_CHANGED);
}
Expand Down
30 changes: 23 additions & 7 deletions crates/hooks/src/use_editable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -191,23 +191,34 @@ pub fn use_editable(initializer: impl Fn() -> EditableConfig, mode: EditableMode

let new_cursor_row = match mode {
EditableMode::MultipleLinesSingleEditor => {
text_editor.char_to_line(position)
text_editor.char_to_line(text_editor.utf16_cu_to_char(position))
}
EditableMode::SingleLineMultipleEditors => id,
};

let new_cursor_col = match mode {
EditableMode::MultipleLinesSingleEditor => {
position - text_editor.line_to_char(new_cursor_row)
EditableMode::MultipleLinesSingleEditor => text_editor
.utf16_cu_to_char(
position
- text_editor.char_to_utf16_cu(
text_editor.line_to_char(new_cursor_row),
),
),
EditableMode::SingleLineMultipleEditors => {
text_editor.utf16_cu_to_char(position)
}
EditableMode::SingleLineMultipleEditors => position,
};

let new_current_line = text_editor.line(new_cursor_row).unwrap();

// Use the line length as new column if the clicked column surpases the length
let new_cursor = if new_cursor_col >= new_current_line.len_chars() {
(new_current_line.len_chars(), new_cursor_row)
let new_cursor = if new_cursor_col >= new_current_line.utf16_len_chars()
{
(
text_editor
.utf16_cu_to_char(new_current_line.utf16_len_chars()),
new_cursor_row,
)
} else {
(new_cursor_col, new_cursor_row)
};
Expand All @@ -224,7 +235,12 @@ pub fn use_editable(initializer: impl Fn() -> EditableConfig, mode: EditableMode
}
// Update the text selections calculated by the layout
CursorLayoutResponse::TextSelection { from, to, id } => {
editor.write().highlight_text(from, to, id);
let mut text_editor = editor.write();
let (from, to) = (
text_editor.utf16_cu_to_char(from),
text_editor.utf16_cu_to_char(to),
);
text_editor.highlight_text(from, to, id);
cursor_reference.set_cursor_selections(None);
}
}
Expand Down
Loading

0 comments on commit 18d3e16

Please sign in to comment.