Repository: espanso/espanso (language: rust)

Issue:
issue with cursor placement when using multi-byte characters
### Description

When usingthe cursor hints feature, the cursor placement is incorrect if the replace string contains non-ASCII characters (e.g., some emojis). The issue arises because the number of backward steps for cursor positioning is calculated based on the number of chars instead of grapheme clusters. This leads to misalignment between the visual cursor position and the intended location, especially when the replace string includes characters that consist of multiple code units.

_I've already fixed this bug and will create a pull request referencing this bug report_

### Steps to reproduce

Create a trigger with cursor hints that contains non-ASCII characters made up from more than one char, for example ⏱️ or 🕰
```
  - trigger: "test1"
    replace: "[🏢::$|$] [⏱️"
```
This will result in the following `[🏢:|:] [⏱️::]` (where the pipe represents the final cursor position)
This is because ⏱️ counts as two chars, and there fore the number of left steps needed is miscalculated.

### Expected behavior

Expected final cursor position should be `[🏢::|] [⏱️::]`

### Screenshots

_No response_

### Logs

_No response_

### Your environment

- Windows 11
- Espanso version: 2.2.1


### Self-service

- [X] I'd be willing to fix this bug myself.


Discussion / hints:

Fix patch:
diff --git a/Cargo.lock b/Cargo.lock
index bbabecfb..3e9ae7f2 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -786,6 +786,7 @@ dependencies = [
  "log",
  "markdown",
  "thiserror",
+ "unicode-segmentation",
 ]
 
 [[package]]
@@ -3168,9 +3169,9 @@ dependencies = [
 
 [[package]]
 name = "unicode-segmentation"
-version = "1.7.1"
+version = "1.12.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bb0d2e7be6ae3a5fa87eed5fb451aff96f2573d2694942e40543ae0bbe19c796"
+checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
 
 [[package]]
 name = "unicode-width"
diff --git a/espanso-engine/Cargo.toml b/espanso-engine/Cargo.toml
index b3231f2a..98663f3c 100644
--- a/espanso-engine/Cargo.toml
+++ b/espanso-engine/Cargo.toml
@@ -11,6 +11,7 @@ thiserror.workspace = true
 crossbeam.workspace = true
 markdown = "0.3.0"
 html2text = "0.12.0"
+unicode-segmentation = "1.9.0"
 
 [lints]
 workspace = true
diff --git a/espanso-engine/src/process/middleware/cursor_hint.rs b/espanso-engine/src/process/middleware/cursor_hint.rs
index cdb9bd0e..f109e01c 100644
--- a/espanso-engine/src/process/middleware/cursor_hint.rs
+++ b/espanso-engine/src/process/middleware/cursor_hint.rs
@@ -21,6 +21,7 @@ use super::super::Middleware;
 use crate::event::{
   effect::CursorHintCompensationEvent, internal::RenderedEvent, Event, EventType,
 };
+use unicode_segmentation::UnicodeSegmentation;
 
 pub struct CursorHintMiddleware {}
 
@@ -64,8 +65,8 @@ fn process_cursor_hint(body: String) -> (String, Option<usize>) {
   if let Some(index) = body.find("$|$") {
     // Convert the byte index to a char index
     let char_str = &body[0..index];
-    let char_index = char_str.chars().count();
-    let total_size = body.chars().count();
+    let char_index = char_str.graphemes(true).count();
+    let total_size = body.graphemes(true).count();
 
     // Remove the $|$ placeholder
     let body = body.replace("$|$", "");


Test patch: