Fix: a backup plan when can't get size of terminal (#71)

* fix (0,0) size

* fix
This commit is contained in:
chzyer 2016-08-31 23:51:28 +08:00 committed by GitHub
parent 8159bd380c
commit 27fdf0b285
6 changed files with 141 additions and 28 deletions

View File

@ -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() { if o.IsInCompleteSelectMode() {
o.doSelect() o.doSelect()
return return true
} }
buf := o.op.buf buf := o.op.buf
@ -70,7 +73,7 @@ func (o *opCompleter) OnComplete() {
if o.IsInCompleteMode() && o.candidateSource != nil && runes.Equal(rs, o.candidateSource) { if o.IsInCompleteMode() && o.candidateSource != nil && runes.Equal(rs, o.candidateSource) {
o.EnterCompleteSelectMode() o.EnterCompleteSelectMode()
o.doSelect() o.doSelect()
return return true
} }
o.ExitCompleteSelectMode() o.ExitCompleteSelectMode()
@ -78,7 +81,7 @@ func (o *opCompleter) OnComplete() {
newLines, offset := o.op.cfg.AutoComplete.Do(rs, buf.idx) newLines, offset := o.op.cfg.AutoComplete.Do(rs, buf.idx)
if len(newLines) == 0 { if len(newLines) == 0 {
o.ExitCompleteMode(false) o.ExitCompleteMode(false)
return return true
} }
// only Aggregate candidates in non-complete mode // only Aggregate candidates in non-complete mode
@ -86,18 +89,19 @@ func (o *opCompleter) OnComplete() {
if len(newLines) == 1 { if len(newLines) == 1 {
buf.WriteRunes(newLines[0]) buf.WriteRunes(newLines[0])
o.ExitCompleteMode(false) o.ExitCompleteMode(false)
return return true
} }
same, size := runes.Aggregate(newLines) same, size := runes.Aggregate(newLines)
if size > 0 { if size > 0 {
buf.WriteRunes(same) buf.WriteRunes(same)
o.ExitCompleteMode(false) o.ExitCompleteMode(false)
return return true
} }
} }
o.EnterCompleteMode(offset, newLines) o.EnterCompleteMode(offset, newLines)
return true
} }
func (o *opCompleter) IsInCompleteSelectMode() bool { func (o *opCompleter) IsInCompleteSelectMode() bool {

View File

@ -153,15 +153,26 @@ func (o *Operation) ioloop() {
o.t.Bell() o.t.Bell()
break break
} }
o.OnComplete() if o.OnComplete() {
keepInCompleteMode = true keepInCompleteMode = true
} else {
o.t.Bell()
break
}
case CharBckSearch: case CharBckSearch:
o.SearchMode(S_DIR_BCK) if !o.SearchMode(S_DIR_BCK) {
o.t.Bell()
break
}
keepInSearchMode = true keepInSearchMode = true
case CharCtrlU: case CharCtrlU:
o.buf.KillFront() o.buf.KillFront()
case CharFwdSearch: case CharFwdSearch:
o.SearchMode(S_DIR_FWD) if !o.SearchMode(S_DIR_FWD) {
o.t.Bell()
break
}
keepInSearchMode = true keepInSearchMode = true
case CharKill: case CharKill:
o.buf.Kill() o.buf.Kill()
@ -345,6 +356,7 @@ func (o *Operation) Runes() ([]rune, error) {
if o.cfg.Listener != nil { if o.cfg.Listener != nil {
o.cfg.Listener.OnChange(nil, 0, 0) o.cfg.Listener.OnChange(nil, 0, 0)
} }
o.buf.Refresh(nil) // print prompt o.buf.Refresh(nil) // print prompt
o.t.KickRead() o.t.KickRead()
select { select {

View File

@ -5,6 +5,7 @@ import (
"bytes" "bytes"
"io" "io"
"strings" "strings"
"sync"
) )
type runeBufferBck struct { type runeBufferBck struct {
@ -25,6 +26,10 @@ type RuneBuffer struct {
width int width int
bck *runeBufferBck bck *runeBufferBck
offset string
sync.Mutex
} }
func (r *RuneBuffer) OnWidthChange(newWidth int) { func (r *RuneBuffer) OnWidthChange(newWidth int) {
@ -370,6 +375,9 @@ func (r *RuneBuffer) getSplitByLine(rs []rune) []string {
} }
func (r *RuneBuffer) IdxLine(width int) int { func (r *RuneBuffer) IdxLine(width int) int {
if width == 0 {
return 0
}
sp := r.getSplitByLine(r.buf[:r.idx]) sp := r.getSplitByLine(r.buf[:r.idx])
return len(sp) - 1 return len(sp) - 1
} }
@ -379,12 +387,16 @@ func (r *RuneBuffer) CursorLineCount() int {
} }
func (r *RuneBuffer) Refresh(f func()) { func (r *RuneBuffer) Refresh(f func()) {
r.Lock()
defer r.Unlock()
if !r.interactive { if !r.interactive {
if f != nil { if f != nil {
f() f()
} }
return return
} }
r.Clean() r.Clean()
if f != nil { if f != nil {
f() f()
@ -392,6 +404,12 @@ func (r *RuneBuffer) Refresh(f func()) {
r.print() r.print()
} }
func (r *RuneBuffer) SetOffset(offset string) {
r.Lock()
r.offset = offset
r.Unlock()
}
func (r *RuneBuffer) print() { func (r *RuneBuffer) print() {
r.w.Write(r.output()) r.w.Write(r.output())
r.hadClean = false r.hadClean = false
@ -473,15 +491,21 @@ func (r *RuneBuffer) SetPrompt(prompt string) {
func (r *RuneBuffer) cleanOutput(w io.Writer, idxLine int) { func (r *RuneBuffer) cleanOutput(w io.Writer, idxLine int) {
buf := bufio.NewWriter(w) buf := bufio.NewWriter(w)
buf.Write([]byte("\033[J")) // just like ^k :)
if idxLine == 0 { if r.width == 0 {
io.WriteString(buf, "\033[2K\r") buf.WriteString(strings.Repeat("\r\b", len(r.buf)+r.PromptLen()))
buf.Write([]byte("\033[J"))
} else { } else {
for i := 0; i < idxLine; i++ { buf.Write([]byte("\033[J")) // just like ^k :)
io.WriteString(buf, "\033[2K\r\033[A") 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() buf.Flush()
return return

View File

@ -96,7 +96,10 @@ func (o *opSearch) SearchChar(r rune) {
o.search(true) 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 alreadyInMode := o.inMode
o.inMode = true o.inMode = true
o.dir = dir o.dir = dir
@ -106,6 +109,7 @@ func (o *opSearch) SearchMode(dir int) {
} else { } else {
o.SearchRefresh(-1) o.SearchRefresh(-1)
} }
return true
} }
func (o *opSearch) ExitSearchMode(revert bool) { func (o *opSearch) ExitSearchMode(revert bool) {

View File

@ -17,6 +17,8 @@ type Terminal struct {
wg sync.WaitGroup wg sync.WaitGroup
isReading int32 isReading int32
sleeping int32 sleeping int32
sizeChan chan string
} }
func NewTerminal(cfg *Config) (*Terminal, error) { func NewTerminal(cfg *Config) (*Terminal, error) {
@ -28,6 +30,7 @@ func NewTerminal(cfg *Config) (*Terminal, error) {
kickChan: make(chan struct{}, 1), kickChan: make(chan struct{}, 1),
outchan: make(chan rune), outchan: make(chan rune),
stopChan: make(chan struct{}, 1), stopChan: make(chan struct{}, 1),
sizeChan: make(chan string, 1),
} }
go t.ioloop() go t.ioloop()
@ -60,6 +63,18 @@ func (t *Terminal) Write(b []byte) (int, error) {
return t.cfg.Stdout.Write(b) 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) { func (t *Terminal) Print(s string) {
fmt.Fprintf(t.cfg.Stdout, "%s", s) fmt.Fprintf(t.cfg.Stdout, "%s", s)
} }
@ -132,7 +147,24 @@ func (t *Terminal) ioloop() {
r = escapeKey(r, buf) r = escapeKey(r, buf)
} else if isEscapeEx { } else if isEscapeEx {
isEscapeEx = false 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 expectNextChar = true

View File

@ -4,8 +4,10 @@ import (
"bufio" "bufio"
"bytes" "bytes"
"strconv" "strconv"
"strings"
"sync" "sync"
"time" "time"
"unicode"
) )
var ( var (
@ -54,8 +56,9 @@ func IsPrintable(key rune) bool {
} }
// translate Esc[X // translate Esc[X
func escapeExKey(r rune, reader *bufio.Reader) rune { func escapeExKey(key *escapeKeyPair) rune {
switch r { var r rune
switch key.typ {
case 'D': case 'D':
r = CharBackward r = CharBackward
case 'C': case 'C':
@ -68,19 +71,53 @@ func escapeExKey(r rune, reader *bufio.Reader) rune {
r = CharLineStart r = CharLineStart
case 'F': case 'F':
r = CharLineEnd r = CharLineEnd
default: case '~':
if r == '3' && reader != nil { if key.attr == "3" {
d, _, _ := reader.ReadRune() r = CharDelete
if d == '~' {
r = CharDelete
} else {
reader.UnreadRune()
}
} }
default:
} }
return r 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 // translate EscX to Meta+X
func escapeKey(r rune, reader *bufio.Reader) rune { func escapeKey(r rune, reader *bufio.Reader) rune {
switch r { switch r {