github.com/markusbkk/elvish@v0.0.0-20231204143114-91dc52438621/pkg/cli/term/read_rune.go (about)

     1  package term
     2  
     3  import (
     4  	"time"
     5  )
     6  
     7  type byteReaderWithTimeout interface {
     8  	// ReadByteWithTimeout reads a single byte with a timeout. A negative
     9  	// timeout means no timeout.
    10  	ReadByteWithTimeout(timeout time.Duration) (byte, error)
    11  }
    12  
    13  const badRune = '\ufffd'
    14  
    15  var utf8SeqTimeout = 10 * time.Millisecond
    16  
    17  // Reads a rune from the reader. The timeout applies to the first byte; a
    18  // negative value means no timeout.
    19  func readRune(rd byteReaderWithTimeout, timeout time.Duration) (rune, error) {
    20  	leader, err := rd.ReadByteWithTimeout(timeout)
    21  	if err != nil {
    22  		return badRune, err
    23  	}
    24  	var r rune
    25  	pending := 0
    26  	switch {
    27  	case leader>>7 == 0:
    28  		r = rune(leader)
    29  	case leader>>5 == 0x6:
    30  		r = rune(leader & 0x1f)
    31  		pending = 1
    32  	case leader>>4 == 0xe:
    33  		r = rune(leader & 0xf)
    34  		pending = 2
    35  	case leader>>3 == 0x1e:
    36  		r = rune(leader & 0x7)
    37  		pending = 3
    38  	}
    39  	for i := 0; i < pending; i++ {
    40  		b, err := rd.ReadByteWithTimeout(utf8SeqTimeout)
    41  		if err != nil {
    42  			return badRune, err
    43  		}
    44  		r = r<<6 + rune(b&0x3f)
    45  	}
    46  	return r, nil
    47  }