Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix insertion of a space between multibyte characters #72

Merged
merged 2 commits into from
Aug 26, 2024

Conversation

ilotterytea
Copy link
Contributor

Added a fix to correctly insert a space in strings containing multibyte characters. Previously, the space was inserted based on the character position, which caused issues with multibyte strings by attempting to insert the space between bytes of a multibyte character, leading to panics (assertion failed: self.is_char_boundary(idx)).

For example, the word apple has 5 bytes and 5 characters, so inserting a space with the old code works correctly. However, the Cyrillic word яблоко has 12 bytes and 6 characters. With the old code, inserting a space could put it inside a multibyte character, causing panic.

@rparrett
Copy link
Owner

Thanks for the detailed report.

The code just below is using a different method for inserting at a character boundary. Is one method or the other obviously superior?

I'd like to add an insert_char_at function similar to the existing remove_char_at and reuse the same code in both places.

@rparrett
Copy link
Owner

rparrett commented Aug 26, 2024

Oh right, the code below has to deal with potentially multiple chars.

I will take a closer look later tonight.

@rparrett
Copy link
Owner

rparrett commented Aug 26, 2024

Would you mind testing a solution more along these lines? It seems to work fine on my end, testing by mashing at various cursor positions with a virtual Cyrillic keyboard.

diff --git a/src/lib.rs b/src/lib.rs
index f9d94dd..3a5c17e 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -441,15 +441,15 @@ fn keyboard(
 
             match input.logical_key {
                 Key::Space => {
-                    text_input.0.insert(pos, ' ');
+                    let byte_pos = byte_pos(&text_input.0, pos);
+                    text_input.0.insert(byte_pos, ' ');
                     cursor_pos.0 += 1;
 
                     cursor_timer.should_reset = true;
                 }
                 Key::Character(ref s) => {
-                    let before = text_input.0.chars().take(cursor_pos.0);
-                    let after = text_input.0.chars().skip(cursor_pos.0);
-                    text_input.0 = before.chain(s.chars()).chain(after).collect();
+                    let byte_pos = byte_pos(&text_input.0, pos);
+                    text_input.0.insert_str(byte_pos, s.as_str());
 
                     cursor_pos.0 += 1;
 
@@ -857,6 +857,14 @@ fn remove_char_at(input: &str, index: usize) -> String {
         .collect()
 }
 
+fn byte_pos(input: &str, char_pos: usize) -> usize {
+    let mut char_indices = input.char_indices();
+    char_indices
+        .nth(char_pos)
+        .map(|(pos, _)| pos)
+        .unwrap_or(input.len())
+}
+
 fn masked_value(value: &str, mask: Option<char>) -> String {
     mask.map_or_else(
         || value.to_string(),

@ilotterytea
Copy link
Contributor Author

Thank you for the suggestion! I tested the solution you provided, and it works perfectly. 👍

@rparrett rparrett merged commit b28c3b0 into rparrett:main Aug 26, 2024
1 check passed
@rparrett
Copy link
Owner

Great, thanks!

@rparrett
Copy link
Owner

Published in v0.9.2

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants