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  }