From 27fdf0b28553d0c2f87f331be53b269637c70b5e Mon Sep 17 00:00:00 2001 From: chzyer <0@0xdf.com> Date: Wed, 31 Aug 2016 23:51:28 +0800 Subject: [PATCH] Fix: a backup plan when can't get size of terminal (#71) * fix (0,0) size * fix --- complete.go | 16 +++++++++------ operation.go | 20 ++++++++++++++---- runebuf.go | 36 +++++++++++++++++++++++++++------ search.go | 6 +++++- terminal.go | 34 ++++++++++++++++++++++++++++++- utils.go | 57 +++++++++++++++++++++++++++++++++++++++++++--------- 6 files changed, 141 insertions(+), 28 deletions(-) diff --git a/complete.go b/complete.go index ef019f2..75f7f68 100644 --- a/complete.go +++ b/complete.go @@ -58,10 +58,13 @@ func (o *opCompleter) nextCandidate(i int) { } } -func (o *opCompleter) OnComplete() { +func (o *opCompleter) OnComplete() bool { + if o.width == 0 { + return false + } if o.IsInCompleteSelectMode() { o.doSelect() - return + return true } buf := o.op.buf @@ -70,7 +73,7 @@ func (o *opCompleter) OnComplete() { if o.IsInCompleteMode() && o.candidateSource != nil && runes.Equal(rs, o.candidateSource) { o.EnterCompleteSelectMode() o.doSelect() - return + return true } o.ExitCompleteSelectMode() @@ -78,7 +81,7 @@ func (o *opCompleter) OnComplete() { newLines, offset := o.op.cfg.AutoComplete.Do(rs, buf.idx) if len(newLines) == 0 { o.ExitCompleteMode(false) - return + return true } // only Aggregate candidates in non-complete mode @@ -86,18 +89,19 @@ func (o *opCompleter) OnComplete() { if len(newLines) == 1 { buf.WriteRunes(newLines[0]) o.ExitCompleteMode(false) - return + return true } same, size := runes.Aggregate(newLines) if size > 0 { buf.WriteRunes(same) o.ExitCompleteMode(false) - return + return true } } o.EnterCompleteMode(offset, newLines) + return true } func (o *opCompleter) IsInCompleteSelectMode() bool { diff --git a/operation.go b/operation.go index e83485a..8377cce 100644 --- a/operation.go +++ b/operation.go @@ -153,15 +153,26 @@ func (o *Operation) ioloop() { o.t.Bell() break } - o.OnComplete() - keepInCompleteMode = true + if o.OnComplete() { + keepInCompleteMode = true + } else { + o.t.Bell() + break + } + case CharBckSearch: - o.SearchMode(S_DIR_BCK) + if !o.SearchMode(S_DIR_BCK) { + o.t.Bell() + break + } keepInSearchMode = true case CharCtrlU: o.buf.KillFront() case CharFwdSearch: - o.SearchMode(S_DIR_FWD) + if !o.SearchMode(S_DIR_FWD) { + o.t.Bell() + break + } keepInSearchMode = true case CharKill: o.buf.Kill() @@ -345,6 +356,7 @@ func (o *Operation) Runes() ([]rune, error) { if o.cfg.Listener != nil { o.cfg.Listener.OnChange(nil, 0, 0) } + o.buf.Refresh(nil) // print prompt o.t.KickRead() select { diff --git a/runebuf.go b/runebuf.go index 3555b04..e402aed 100644 --- a/runebuf.go +++ b/runebuf.go @@ -5,6 +5,7 @@ import ( "bytes" "io" "strings" + "sync" ) type runeBufferBck struct { @@ -25,6 +26,10 @@ type RuneBuffer struct { width int bck *runeBufferBck + + offset string + + sync.Mutex } func (r *RuneBuffer) OnWidthChange(newWidth int) { @@ -370,6 +375,9 @@ func (r *RuneBuffer) getSplitByLine(rs []rune) []string { } func (r *RuneBuffer) IdxLine(width int) int { + if width == 0 { + return 0 + } sp := r.getSplitByLine(r.buf[:r.idx]) return len(sp) - 1 } @@ -379,12 +387,16 @@ func (r *RuneBuffer) CursorLineCount() int { } func (r *RuneBuffer) Refresh(f func()) { + r.Lock() + defer r.Unlock() + if !r.interactive { if f != nil { f() } return } + r.Clean() if f != nil { f() @@ -392,6 +404,12 @@ func (r *RuneBuffer) Refresh(f func()) { r.print() } +func (r *RuneBuffer) SetOffset(offset string) { + r.Lock() + r.offset = offset + r.Unlock() +} + func (r *RuneBuffer) print() { r.w.Write(r.output()) r.hadClean = false @@ -473,15 +491,21 @@ func (r *RuneBuffer) SetPrompt(prompt string) { func (r *RuneBuffer) cleanOutput(w io.Writer, idxLine int) { buf := bufio.NewWriter(w) - buf.Write([]byte("\033[J")) // just like ^k :) - if idxLine == 0 { - io.WriteString(buf, "\033[2K\r") + if r.width == 0 { + buf.WriteString(strings.Repeat("\r\b", len(r.buf)+r.PromptLen())) + buf.Write([]byte("\033[J")) } else { - for i := 0; i < idxLine; i++ { - io.WriteString(buf, "\033[2K\r\033[A") + buf.Write([]byte("\033[J")) // just like ^k :) + if idxLine == 0 { + buf.WriteString("\033[2K") + buf.WriteString("\r") + } else { + for i := 0; i < idxLine; i++ { + io.WriteString(buf, "\033[2K\r\033[A") + } + io.WriteString(buf, "\033[2K\r") } - io.WriteString(buf, "\033[2K\r") } buf.Flush() return diff --git a/search.go b/search.go index 2f20eb2..52e8ff0 100644 --- a/search.go +++ b/search.go @@ -96,7 +96,10 @@ func (o *opSearch) SearchChar(r rune) { o.search(true) } -func (o *opSearch) SearchMode(dir int) { +func (o *opSearch) SearchMode(dir int) bool { + if o.width == 0 { + return false + } alreadyInMode := o.inMode o.inMode = true o.dir = dir @@ -106,6 +109,7 @@ func (o *opSearch) SearchMode(dir int) { } else { o.SearchRefresh(-1) } + return true } func (o *opSearch) ExitSearchMode(revert bool) { diff --git a/terminal.go b/terminal.go index 0f1d393..36e1bf1 100644 --- a/terminal.go +++ b/terminal.go @@ -17,6 +17,8 @@ type Terminal struct { wg sync.WaitGroup isReading int32 sleeping int32 + + sizeChan chan string } func NewTerminal(cfg *Config) (*Terminal, error) { @@ -28,6 +30,7 @@ func NewTerminal(cfg *Config) (*Terminal, error) { kickChan: make(chan struct{}, 1), outchan: make(chan rune), stopChan: make(chan struct{}, 1), + sizeChan: make(chan string, 1), } go t.ioloop() @@ -60,6 +63,18 @@ func (t *Terminal) Write(b []byte) (int, error) { return t.cfg.Stdout.Write(b) } +type termSize struct { + left int + top int +} + +func (t *Terminal) GetOffset(f func(offset string)) { + go func() { + f(<-t.sizeChan) + }() + t.Write([]byte("\033[6n")) +} + func (t *Terminal) Print(s string) { fmt.Fprintf(t.cfg.Stdout, "%s", s) } @@ -132,7 +147,24 @@ func (t *Terminal) ioloop() { r = escapeKey(r, buf) } else if isEscapeEx { isEscapeEx = false - r = escapeExKey(r, buf) + if key := readEscKey(r, buf); key != nil { + r = escapeExKey(key) + // offset + if key.typ == 'R' { + if _, _, ok := key.Get2(); ok { + select { + case t.sizeChan <- key.attr: + default: + } + } + expectNextChar = true + continue + } + } + if r == 0 { + expectNextChar = true + continue + } } expectNextChar = true diff --git a/utils.go b/utils.go index c0031a8..6084a7d 100644 --- a/utils.go +++ b/utils.go @@ -4,8 +4,10 @@ import ( "bufio" "bytes" "strconv" + "strings" "sync" "time" + "unicode" ) var ( @@ -54,8 +56,9 @@ func IsPrintable(key rune) bool { } // translate Esc[X -func escapeExKey(r rune, reader *bufio.Reader) rune { - switch r { +func escapeExKey(key *escapeKeyPair) rune { + var r rune + switch key.typ { case 'D': r = CharBackward case 'C': @@ -68,19 +71,53 @@ func escapeExKey(r rune, reader *bufio.Reader) rune { r = CharLineStart case 'F': r = CharLineEnd - default: - if r == '3' && reader != nil { - d, _, _ := reader.ReadRune() - if d == '~' { - r = CharDelete - } else { - reader.UnreadRune() - } + 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 {