| package readline |
| |
| import ( |
| "bufio" |
| "bytes" |
| "container/list" |
| "fmt" |
| "os" |
| "strconv" |
| "strings" |
| "sync" |
| "time" |
| "unicode" |
| ) |
| |
| var ( |
| isWindows = false |
| ) |
| |
| const ( |
| CharLineStart = 1 |
| CharBackward = 2 |
| CharInterrupt = 3 |
| CharDelete = 4 |
| CharLineEnd = 5 |
| CharForward = 6 |
| CharBell = 7 |
| CharCtrlH = 8 |
| CharTab = 9 |
| CharCtrlJ = 10 |
| CharKill = 11 |
| CharCtrlL = 12 |
| CharEnter = 13 |
| CharNext = 14 |
| CharPrev = 16 |
| CharBckSearch = 18 |
| CharFwdSearch = 19 |
| CharTranspose = 20 |
| CharCtrlU = 21 |
| CharCtrlW = 23 |
| CharCtrlY = 25 |
| CharCtrlZ = 26 |
| CharEsc = 27 |
| CharEscapeEx = 91 |
| CharBackspace = 127 |
| ) |
| |
| const ( |
| MetaBackward rune = -iota - 1 |
| MetaForward |
| MetaDelete |
| MetaBackspace |
| MetaTranspose |
| ) |
| |
| // WaitForResume need to call before current process got suspend. |
| // It will run a ticker until a long duration is occurs, |
| // which means this process is resumed. |
| func WaitForResume() chan struct{} { |
| ch := make(chan struct{}) |
| var wg sync.WaitGroup |
| wg.Add(1) |
| go func() { |
| ticker := time.NewTicker(10 * time.Millisecond) |
| t := time.Now() |
| wg.Done() |
| for { |
| now := <-ticker.C |
| if now.Sub(t) > 100*time.Millisecond { |
| break |
| } |
| t = now |
| } |
| ticker.Stop() |
| ch <- struct{}{} |
| }() |
| wg.Wait() |
| return ch |
| } |
| |
| func Restore(fd int, state *State) error { |
| err := restoreTerm(fd, state) |
| if err != nil { |
| // errno 0 means everything is ok :) |
| if err.Error() == "errno 0" { |
| return nil |
| } else { |
| return err |
| } |
| } |
| return nil |
| } |
| |
| func IsPrintable(key rune) bool { |
| isInSurrogateArea := key >= 0xd800 && key <= 0xdbff |
| return key >= 32 && !isInSurrogateArea |
| } |
| |
| // translate Esc[X |
| func escapeExKey(key *escapeKeyPair) rune { |
| var r rune |
| switch key.typ { |
| case 'D': |
| r = CharBackward |
| case 'C': |
| r = CharForward |
| case 'A': |
| r = CharPrev |
| case 'B': |
| r = CharNext |
| case 'H': |
| r = CharLineStart |
| case 'F': |
| r = CharLineEnd |
| case '~': |
| if key.attr == "3" { |
| r = CharDelete |
| } |
| default: |
| } |
| return r |
| } |
| |
| type escapeKeyPair struct { |
| attr string |
| typ rune |
| } |
| |
| func (e *escapeKeyPair) Get2() (int, int, bool) { |
| sp := strings.Split(e.attr, ";") |
| if len(sp) < 2 { |
| return -1, -1, false |
| } |
| s1, err := strconv.Atoi(sp[0]) |
| if err != nil { |
| return -1, -1, false |
| } |
| s2, err := strconv.Atoi(sp[1]) |
| if err != nil { |
| return -1, -1, false |
| } |
| return s1, s2, true |
| } |
| |
| func readEscKey(r rune, reader *bufio.Reader) *escapeKeyPair { |
| p := escapeKeyPair{} |
| buf := bytes.NewBuffer(nil) |
| for { |
| if r == ';' { |
| } else if unicode.IsNumber(r) { |
| } else { |
| p.typ = r |
| break |
| } |
| buf.WriteRune(r) |
| r, _, _ = reader.ReadRune() |
| } |
| p.attr = buf.String() |
| return &p |
| } |
| |
| // translate EscX to Meta+X |
| func escapeKey(r rune, reader *bufio.Reader) rune { |
| switch r { |
| case 'b': |
| r = MetaBackward |
| case 'f': |
| r = MetaForward |
| case 'd': |
| r = MetaDelete |
| case CharTranspose: |
| r = MetaTranspose |
| case CharBackspace: |
| r = MetaBackspace |
| case 'O': |
| d, _, _ := reader.ReadRune() |
| switch d { |
| case 'H': |
| r = CharLineStart |
| case 'F': |
| r = CharLineEnd |
| default: |
| reader.UnreadRune() |
| } |
| case CharEsc: |
| |
| } |
| return r |
| } |
| |
| func SplitByLine(start, screenWidth int, rs []rune) []string { |
| var ret []string |
| buf := bytes.NewBuffer(nil) |
| currentWidth := start |
| for _, r := range rs { |
| w := runes.Width(r) |
| currentWidth += w |
| buf.WriteRune(r) |
| if currentWidth >= screenWidth { |
| ret = append(ret, buf.String()) |
| buf.Reset() |
| currentWidth = 0 |
| } |
| } |
| ret = append(ret, buf.String()) |
| return ret |
| } |
| |
| // calculate how many lines for N character |
| func LineCount(screenWidth, w int) int { |
| r := w / screenWidth |
| if w%screenWidth != 0 { |
| r++ |
| } |
| return r |
| } |
| |
| func IsWordBreak(i rune) bool { |
| switch { |
| case i >= 'a' && i <= 'z': |
| case i >= 'A' && i <= 'Z': |
| case i >= '0' && i <= '9': |
| default: |
| return true |
| } |
| return false |
| } |
| |
| func GetInt(s []string, def int) int { |
| if len(s) == 0 { |
| return def |
| } |
| c, err := strconv.Atoi(s[0]) |
| if err != nil { |
| return def |
| } |
| return c |
| } |
| |
| type RawMode struct { |
| state *State |
| } |
| |
| func (r *RawMode) Enter() (err error) { |
| r.state, err = MakeRaw(GetStdin()) |
| return err |
| } |
| |
| func (r *RawMode) Exit() error { |
| if r.state == nil { |
| return nil |
| } |
| return Restore(GetStdin(), r.state) |
| } |
| |
| // ----------------------------------------------------------------------------- |
| |
| func sleep(n int) { |
| Debug(n) |
| time.Sleep(2000 * time.Millisecond) |
| } |
| |
| // print a linked list to Debug() |
| func debugList(l *list.List) { |
| idx := 0 |
| for e := l.Front(); e != nil; e = e.Next() { |
| Debug(idx, fmt.Sprintf("%+v", e.Value)) |
| idx++ |
| } |
| } |
| |
| // append log info to another file |
| func Debug(o ...interface{}) { |
| f, _ := os.OpenFile("debug.tmp", os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666) |
| fmt.Fprintln(f, o...) |
| f.Close() |
| } |