gioui.org@v0.6.1-0.20240506124620-7a9ce51988ce/app/ime.go (about) 1 // SPDX-License-Identifier: Unlicense OR MIT 2 package app 3 4 import ( 5 "unicode" 6 "unicode/utf16" 7 8 "gioui.org/io/input" 9 "gioui.org/io/key" 10 ) 11 12 type editorState struct { 13 input.EditorState 14 compose key.Range 15 } 16 17 func (e *editorState) Replace(r key.Range, text string) { 18 if r.Start > r.End { 19 r.Start, r.End = r.End, r.Start 20 } 21 runes := []rune(text) 22 newEnd := r.Start + len(runes) 23 adjust := func(pos int) int { 24 switch { 25 case newEnd < pos && pos <= r.End: 26 return newEnd 27 case r.End < pos: 28 diff := newEnd - r.End 29 return pos + diff 30 } 31 return pos 32 } 33 e.Selection.Start = adjust(e.Selection.Start) 34 e.Selection.End = adjust(e.Selection.End) 35 if e.compose.Start != -1 { 36 e.compose.Start = adjust(e.compose.Start) 37 e.compose.End = adjust(e.compose.End) 38 } 39 s := e.Snippet 40 if r.End < s.Start || r.Start > s.End { 41 // Discard snippet if it doesn't overlap with replacement. 42 s = key.Snippet{ 43 Range: key.Range{ 44 Start: r.Start, 45 End: r.Start, 46 }, 47 } 48 } 49 var newSnippet []rune 50 snippet := []rune(s.Text) 51 // Append first part of existing snippet. 52 if end := r.Start - s.Start; end > 0 { 53 newSnippet = append(newSnippet, snippet[:end]...) 54 } 55 // Append replacement. 56 newSnippet = append(newSnippet, runes...) 57 // Append last part of existing snippet. 58 if start := r.End; start < s.End { 59 newSnippet = append(newSnippet, snippet[start-s.Start:]...) 60 } 61 // Adjust snippet range to include replacement. 62 if r.Start < s.Start { 63 s.Start = r.Start 64 } 65 s.End = s.Start + len(newSnippet) 66 s.Text = string(newSnippet) 67 e.Snippet = s 68 } 69 70 // UTF16Index converts the given index in runes into an index in utf16 characters. 71 func (e *editorState) UTF16Index(runes int) int { 72 if runes == -1 { 73 return -1 74 } 75 if runes < e.Snippet.Start { 76 // Assume runes before sippet are one UTF-16 character each. 77 return runes 78 } 79 chars := e.Snippet.Start 80 runes -= e.Snippet.Start 81 for _, r := range e.Snippet.Text { 82 if runes == 0 { 83 break 84 } 85 runes-- 86 chars++ 87 if r1, _ := utf16.EncodeRune(r); r1 != unicode.ReplacementChar { 88 chars++ 89 } 90 } 91 // Assume runes after snippets are one UTF-16 character each. 92 return chars + runes 93 } 94 95 // RunesIndex converts the given index in utf16 characters to an index in runes. 96 func (e *editorState) RunesIndex(chars int) int { 97 if chars == -1 { 98 return -1 99 } 100 if chars < e.Snippet.Start { 101 // Assume runes before offset are one UTF-16 character each. 102 return chars 103 } 104 runes := e.Snippet.Start 105 chars -= e.Snippet.Start 106 for _, r := range e.Snippet.Text { 107 if chars == 0 { 108 break 109 } 110 chars-- 111 runes++ 112 if r1, _ := utf16.EncodeRune(r); r1 != unicode.ReplacementChar { 113 chars-- 114 } 115 } 116 // Assume runes after snippets are one UTF-16 character each. 117 return runes + chars 118 }