fix: use ansi.Cut for Unicode grapheme handling in editor completions

Christian Rocha created

💘 Generated with Crush

Assisted-by: Kimi K2 0905 via Crush <crush@charm.land>

Change summary

internal/tui/components/chat/editor/editor.go | 18 +++++++++++++-----
1 file changed, 13 insertions(+), 5 deletions(-)

Detailed changes

internal/tui/components/chat/editor/editor.go 🔗

@@ -189,11 +189,17 @@ func (m *editorCmp) Update(msg tea.Msg) (util.Model, tea.Cmd) {
 		}
 		if item, ok := msg.Value.(FileCompletionItem); ok {
 			word := m.textarea.Word()
-			// If the selected item is a file, insert its path into the textarea
+			// If the selected item is a file, insert its path into the textarea.
 			value := m.textarea.Value()
-			value = value[:m.completionsStartIndex] + // Remove the current query
-				item.Path + // Insert the file path
-				value[m.completionsStartIndex+len(word):] // Append the rest of the value
+
+			// Convert byte indices to grapheme indices.
+			_, graphemeEnd := ansi.ByteToGraphemeRange(value, m.completionsStartIndex+len(word), m.completionsStartIndex+len(word))
+
+			// Rebuild with the file path inserted.
+			value = value[:m.completionsStartIndex] + // remove the current query
+				item.Path + // insert the file path
+				ansi.Cut(value, graphemeEnd, ansi.StringWidth(value)) // append the rest of the value
+
 			// XXX: This will always move the cursor to the end of the textarea.
 			m.textarea.SetValue(value)
 			m.textarea.MoveToEnd()
@@ -339,7 +345,9 @@ func (m *editorCmp) Update(msg tea.Msg) (util.Model, tea.Cmd) {
 				if strings.HasPrefix(word, "@") {
 					// XXX: wont' work if editing in the middle of the field.
 					m.completionsStartIndex = strings.LastIndex(m.textarea.Value(), word)
-					m.currentQuery = word[1:]
+					// Convert byte index to grapheme index for proper Unicode handling
+					_, graphemeEnd := ansi.ByteToGraphemeRange(word, 1, 1)
+					m.currentQuery = ansi.Cut(word, graphemeEnd, ansi.StringWidth(word))
 					x, y := m.completionsPosition()
 					x -= len(m.currentQuery)
 					m.isCompletionsOpen = true