package ncurses import ( "os" "bufio" "sync/atomic" ) func bitmatch(b byte, pattern byte, length int) bool { mask := ^(byte(1 << (8 - length)) - 1) return (b ^ pattern) & mask == 0 } func UTF8Listen(reader *bufio.Reader, channel chan []byte, active *atomic.Bool) { for active.Load() { out := make([]byte, 4) reader.Read(out[0:1]) if out[0] == 0x1B { // Escape sequences if reader.Buffered() != 0 { reader.Read(out[1:2]) if out[1] == 0x5B { // CSI out := []byte{0x1B, 0x5B} next := [1]byte{} for true { // TODO: safety reader.Read(next[0:1]) if (next[0] >= 0x20) && (next[0] <= 0x3F) { out = append(out, next[0]) } else if (next[0] >= 0x40) && (next[0] <= 0x7F) { out = append(out, next[0]) break } else { break } } channel<-out } } else { channel <- out[0:1] } } else if bitmatch(out[0], 0b00000000, 1) { channel <- out[0:1] } else if bitmatch(out[0], 0b11000000, 3) { if reader.Buffered() >= 1 { reader.Read(out[1:2]) channel <- out[0:2] } } else if bitmatch(out[0], 0b11100000, 4) { if reader.Buffered() >= 2 { reader.Read(out[1:3]) channel <- out[0:3] } } else if bitmatch(out[0], 0b11110000, 5) { if reader.Buffered() >= 3 { reader.Read(out[1:4]) channel <- out[0:4] } } } } // To cleanup, set active to false then close the channel func UTF8Listener(buffer int, file *os.File) (chan []byte, *atomic.Bool) { channel := make(chan []byte, buffer) reader := bufio.NewReader(file) active := new(atomic.Bool) active.Store(true) go UTF8Listen(reader, channel, active) return channel, active }