mirror of https://github.com/chzyer/readline.git
Fix terminal resize race conditions
The runebuf, search and complete data structures all maintain their own cache of the terminal width and height. When the terminal is resized, a signal is sent to a go routine which calls a function that sets the new width and height to each data structure instance. However, this races with the main thread reading the sizes. Instead of introducing more locks, it makes sense that the terminal itself caches it's width and height and the other structures just get it as necessary. This removes all the racing. As part of this change, search, complete and runebuf constructor changes to no longer require the initial sizes. Also each structure needs a reference to the Terminal so they can get the width/height. As the io.Writer parameter is actually the terminal anyway, the simpliest option was just to change the type from the io.Writer to Terminal. I don't believe that anyone would be calling these functions directly so the signature changes should be ok. I also removed the no longer used OnWidthChange() and OnSizeChange() functions from these three structures.
This commit is contained in:
parent
86f47fbf9e
commit
c30c5a000a
53
complete.go
53
complete.go
|
@ -4,7 +4,6 @@ import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type AutoCompleter interface {
|
type AutoCompleter interface {
|
||||||
|
@ -25,10 +24,8 @@ func (t *TabCompleter) Do([]rune, int) ([][]rune, int) {
|
||||||
}
|
}
|
||||||
|
|
||||||
type opCompleter struct {
|
type opCompleter struct {
|
||||||
w io.Writer
|
w *Terminal
|
||||||
op *Operation
|
op *Operation
|
||||||
width int
|
|
||||||
height int
|
|
||||||
|
|
||||||
inCompleteMode bool
|
inCompleteMode bool
|
||||||
inSelectMode bool
|
inSelectMode bool
|
||||||
|
@ -42,12 +39,10 @@ type opCompleter struct {
|
||||||
candidateColWidth int // width of candidate columns
|
candidateColWidth int // width of candidate columns
|
||||||
}
|
}
|
||||||
|
|
||||||
func newOpCompleter(w io.Writer, op *Operation, width, height int) *opCompleter {
|
func newOpCompleter(w *Terminal, op *Operation) *opCompleter {
|
||||||
return &opCompleter{
|
return &opCompleter{
|
||||||
w: w,
|
w: w,
|
||||||
op: op,
|
op: op,
|
||||||
width: width,
|
|
||||||
height: height,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -73,7 +68,8 @@ func (o *opCompleter) nextCandidate(i int) {
|
||||||
// when tab pressed if cannot do complete for reason such as width unknown
|
// when tab pressed if cannot do complete for reason such as width unknown
|
||||||
// or no candidates available.
|
// or no candidates available.
|
||||||
func (o *opCompleter) OnComplete() (ringBell bool) {
|
func (o *opCompleter) OnComplete() (ringBell bool) {
|
||||||
if o.width == 0 || o.height < 3 {
|
tWidth, tHeight := o.w.GetWidthHeight()
|
||||||
|
if tWidth == 0 || tHeight < 3 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if o.IsInCompleteSelectMode() {
|
if o.IsInCompleteSelectMode() {
|
||||||
|
@ -246,15 +242,6 @@ func (o *opCompleter) getMatrixSize() int {
|
||||||
return line * colNum
|
return line * colNum
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *opCompleter) OnWidthChange(newWidth int) {
|
|
||||||
o.width = newWidth
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *opCompleter) OnSizeChange(newWidth, newHeight int) {
|
|
||||||
o.width = newWidth
|
|
||||||
o.height = newHeight
|
|
||||||
}
|
|
||||||
|
|
||||||
// setColumnInfo calculates column width and number of columns required
|
// setColumnInfo calculates column width and number of columns required
|
||||||
// to present the list of candidates on the terminal.
|
// to present the list of candidates on the terminal.
|
||||||
func (o *opCompleter) setColumnInfo() {
|
func (o *opCompleter) setColumnInfo() {
|
||||||
|
@ -270,8 +257,10 @@ func (o *opCompleter) setColumnInfo() {
|
||||||
}
|
}
|
||||||
colWidth++ // whitespace between cols
|
colWidth++ // whitespace between cols
|
||||||
|
|
||||||
|
tWidth, _ := o.w.GetWidthHeight()
|
||||||
|
|
||||||
// -1 to avoid end of line issues
|
// -1 to avoid end of line issues
|
||||||
width := o.width - 1
|
width := tWidth - 1
|
||||||
colNum := width / colWidth
|
colNum := width / colWidth
|
||||||
if colNum != 0 {
|
if colNum != 0 {
|
||||||
colWidth += (width - (colWidth * colNum)) / colNum
|
colWidth += (width - (colWidth * colNum)) / colNum
|
||||||
|
@ -283,8 +272,9 @@ func (o *opCompleter) setColumnInfo() {
|
||||||
|
|
||||||
// needPagerMode returns true if number of candidates would go off the page
|
// needPagerMode returns true if number of candidates would go off the page
|
||||||
func (o *opCompleter) needPagerMode() bool {
|
func (o *opCompleter) needPagerMode() bool {
|
||||||
|
tWidth, tHeight := o.w.GetWidthHeight()
|
||||||
buflineCnt := o.op.buf.LineCount() // lines taken by buffer content
|
buflineCnt := o.op.buf.LineCount() // lines taken by buffer content
|
||||||
linesAvail := o.height - buflineCnt // lines available without scrolling buffer off screen
|
linesAvail := tHeight - buflineCnt // lines available without scrolling buffer off screen
|
||||||
if o.candidateColNum > 0 {
|
if o.candidateColNum > 0 {
|
||||||
// Normal case where each candidate at least fits on a line
|
// Normal case where each candidate at least fits on a line
|
||||||
maxOrPage := linesAvail * o.candidateColNum // max candiates without needing to page
|
maxOrPage := linesAvail * o.candidateColNum // max candiates without needing to page
|
||||||
|
@ -299,9 +289,9 @@ func (o *opCompleter) needPagerMode() bool {
|
||||||
for _, c := range o.candidate {
|
for _, c := range o.candidate {
|
||||||
cWidth := sameWidth + runes.WidthAll(c)
|
cWidth := sameWidth + runes.WidthAll(c)
|
||||||
cLines := 1
|
cLines := 1
|
||||||
if o.width > 0 {
|
if tWidth > 0 {
|
||||||
cLines = cWidth / o.width
|
cLines = cWidth / tWidth
|
||||||
if cWidth % o.width > 0 {
|
if cWidth % tWidth > 0 {
|
||||||
cLines++
|
cLines++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -326,6 +316,7 @@ func (o *opCompleter) CompleteRefresh() {
|
||||||
buf.WriteString("\033[J")
|
buf.WriteString("\033[J")
|
||||||
|
|
||||||
same := o.op.buf.RuneSlice(-o.candidateOff)
|
same := o.op.buf.RuneSlice(-o.candidateOff)
|
||||||
|
tWidth, _ := o.w.GetWidthHeight()
|
||||||
|
|
||||||
colIdx := 0
|
colIdx := 0
|
||||||
lines := 0
|
lines := 0
|
||||||
|
@ -334,13 +325,13 @@ func (o *opCompleter) CompleteRefresh() {
|
||||||
inSelect := idx == o.candidateChoise && o.IsInCompleteSelectMode()
|
inSelect := idx == o.candidateChoise && o.IsInCompleteSelectMode()
|
||||||
cWidth := sameWidth + runes.WidthAll(c)
|
cWidth := sameWidth + runes.WidthAll(c)
|
||||||
cLines := 1
|
cLines := 1
|
||||||
if o.width > 0 {
|
if tWidth > 0 {
|
||||||
sWidth := 0
|
sWidth := 0
|
||||||
if isWindows && inSelect {
|
if isWindows && inSelect {
|
||||||
sWidth = 1 // adjust for hightlighting on Windows
|
sWidth = 1 // adjust for hightlighting on Windows
|
||||||
}
|
}
|
||||||
cLines = (cWidth + sWidth) / o.width
|
cLines = (cWidth + sWidth) / tWidth
|
||||||
if (cWidth + sWidth) % o.width > 0 {
|
if (cWidth + sWidth) % tWidth > 0 {
|
||||||
cLines++
|
cLines++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -408,6 +399,7 @@ func (o *opCompleter) pagerRefresh() (stayInMode bool) {
|
||||||
|
|
||||||
same := o.op.buf.RuneSlice(-o.candidateOff)
|
same := o.op.buf.RuneSlice(-o.candidateOff)
|
||||||
sameWidth := runes.WidthAll(same)
|
sameWidth := runes.WidthAll(same)
|
||||||
|
tWidth, tHeight := o.w.GetWidthHeight()
|
||||||
|
|
||||||
colIdx := 0
|
colIdx := 0
|
||||||
lines := 1
|
lines := 1
|
||||||
|
@ -415,13 +407,13 @@ func (o *opCompleter) pagerRefresh() (stayInMode bool) {
|
||||||
c := o.candidate[o.candidateChoise]
|
c := o.candidate[o.candidateChoise]
|
||||||
cWidth := sameWidth + runes.WidthAll(c)
|
cWidth := sameWidth + runes.WidthAll(c)
|
||||||
cLines := 1
|
cLines := 1
|
||||||
if o.width > 0 {
|
if tWidth > 0 {
|
||||||
cLines = cWidth / o.width
|
cLines = cWidth / tWidth
|
||||||
if cWidth % o.width > 0 {
|
if cWidth % tWidth > 0 {
|
||||||
cLines++
|
cLines++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if lines > 1 && lines + cLines > o.height {
|
if lines > 1 && lines + cLines > tHeight {
|
||||||
break // won't fit on page, stop early.
|
break // won't fit on page, stop early.
|
||||||
}
|
}
|
||||||
buf.WriteString(string(same))
|
buf.WriteString(string(same))
|
||||||
|
@ -460,7 +452,8 @@ func (o *opCompleter) pagerRefresh() (stayInMode bool) {
|
||||||
// we rewrite the prompt it does not over write the page content. The code to rewrite
|
// we rewrite the prompt it does not over write the page content. The code to rewrite
|
||||||
// the prompt assumes the cursor is at the index line, so we add enough blank lines.
|
// the prompt assumes the cursor is at the index line, so we add enough blank lines.
|
||||||
func (o *opCompleter) scrollOutOfPagerMode() {
|
func (o *opCompleter) scrollOutOfPagerMode() {
|
||||||
lineCnt := o.op.buf.IdxLine(o.width)
|
tWidth, _ := o.w.GetWidthHeight()
|
||||||
|
lineCnt := o.op.buf.IdxLine(tWidth)
|
||||||
if lineCnt > 0 {
|
if lineCnt > 0 {
|
||||||
buf := bufio.NewWriter(o.w)
|
buf := bufio.NewWriter(o.w)
|
||||||
buf.Write(bytes.Repeat([]byte("\n"), lineCnt))
|
buf.Write(bytes.Repeat([]byte("\n"), lineCnt))
|
||||||
|
|
24
operation.go
24
operation.go
|
@ -65,7 +65,8 @@ func (o *Operation) write(target io.Writer, b []byte) (int, error) {
|
||||||
n, err = target.Write(b)
|
n, err = target.Write(b)
|
||||||
// Adjust the prompt start position by b
|
// Adjust the prompt start position by b
|
||||||
rout := runes.ColorFilter([]rune(string(b[:])))
|
rout := runes.ColorFilter([]rune(string(b[:])))
|
||||||
sp := SplitByLine(rout, []rune{}, o.buf.ppos, o.buf.width, 1)
|
tWidth, _ := o.t.GetWidthHeight()
|
||||||
|
sp := SplitByLine(rout, []rune{}, o.buf.ppos, tWidth, 1)
|
||||||
if len(sp) > 1 {
|
if len(sp) > 1 {
|
||||||
o.buf.ppos = len(sp[len(sp)-1])
|
o.buf.ppos = len(sp[len(sp)-1])
|
||||||
} else {
|
} else {
|
||||||
|
@ -83,24 +84,18 @@ func (o *Operation) write(target io.Writer, b []byte) (int, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewOperation(t *Terminal, cfg *Config) *Operation {
|
func NewOperation(t *Terminal, cfg *Config) *Operation {
|
||||||
width, height := cfg.FuncGetSize()
|
|
||||||
op := &Operation{
|
op := &Operation{
|
||||||
t: t,
|
t: t,
|
||||||
buf: NewRuneBuffer(t, cfg.Prompt, cfg, width, height),
|
buf: NewRuneBuffer(t, cfg.Prompt, cfg),
|
||||||
outchan: make(chan []rune),
|
outchan: make(chan []rune),
|
||||||
errchan: make(chan error, 1),
|
errchan: make(chan error, 1),
|
||||||
}
|
}
|
||||||
op.w = op.buf.w
|
op.w = op.buf.w
|
||||||
op.SetConfig(cfg)
|
op.SetConfig(cfg)
|
||||||
op.opVim = newVimMode(op)
|
op.opVim = newVimMode(op)
|
||||||
op.opCompleter = newOpCompleter(op.buf.w, op, width, height)
|
op.opCompleter = newOpCompleter(op.buf.w, op)
|
||||||
op.opPassword = newOpPassword(op)
|
op.opPassword = newOpPassword(op)
|
||||||
op.cfg.FuncOnWidthChanged(func() {
|
op.cfg.FuncOnWidthChanged(t.OnSizeChange)
|
||||||
newWidth, newHeight := cfg.FuncGetSize()
|
|
||||||
op.opCompleter.OnSizeChange(newWidth, newHeight)
|
|
||||||
op.opSearch.OnSizeChange(newWidth, newHeight)
|
|
||||||
op.buf.OnSizeChange(newWidth, newHeight)
|
|
||||||
})
|
|
||||||
go op.ioloop()
|
go op.ioloop()
|
||||||
return op
|
return op
|
||||||
}
|
}
|
||||||
|
@ -431,7 +426,7 @@ func (o *Operation) Runes() ([]rune, error) {
|
||||||
// maybe existing text on the same line that ideally we don't
|
// maybe existing text on the same line that ideally we don't
|
||||||
// want to overwrite and cause prompt to jump left. Note that
|
// want to overwrite and cause prompt to jump left. Note that
|
||||||
// this is not perfect but works the majority of the time.
|
// this is not perfect but works the majority of the time.
|
||||||
o.buf.getAndSetOffset(o.t)
|
o.buf.getAndSetOffset()
|
||||||
o.buf.Print() // print prompt & buffer contents
|
o.buf.Print() // print prompt & buffer contents
|
||||||
o.t.KickRead()
|
o.t.KickRead()
|
||||||
|
|
||||||
|
@ -525,12 +520,11 @@ func (op *Operation) SetConfig(cfg *Config) (*Config, error) {
|
||||||
op.SetPrompt(cfg.Prompt)
|
op.SetPrompt(cfg.Prompt)
|
||||||
op.SetMaskRune(cfg.MaskRune)
|
op.SetMaskRune(cfg.MaskRune)
|
||||||
op.buf.SetConfig(cfg)
|
op.buf.SetConfig(cfg)
|
||||||
width, height := op.cfg.FuncGetSize()
|
|
||||||
|
|
||||||
if cfg.opHistory == nil {
|
if cfg.opHistory == nil {
|
||||||
op.SetHistoryPath(cfg.HistoryFile)
|
op.SetHistoryPath(cfg.HistoryFile)
|
||||||
cfg.opHistory = op.history
|
cfg.opHistory = op.history
|
||||||
cfg.opSearch = newOpSearch(op.buf.w, op.buf, op.history, cfg, width, height)
|
cfg.opSearch = newOpSearch(op.buf.w, op.buf, op.history, cfg)
|
||||||
}
|
}
|
||||||
op.history = cfg.opHistory
|
op.history = cfg.opHistory
|
||||||
|
|
||||||
|
@ -538,8 +532,8 @@ func (op *Operation) SetConfig(cfg *Config) (*Config, error) {
|
||||||
// so if we use it next time, we need to reopen it by `InitHistory()`
|
// so if we use it next time, we need to reopen it by `InitHistory()`
|
||||||
op.history.Init()
|
op.history.Init()
|
||||||
|
|
||||||
if op.cfg.AutoComplete != nil {
|
if op.cfg.AutoComplete != nil && op.opCompleter == nil {
|
||||||
op.opCompleter = newOpCompleter(op.buf.w, op, width, height)
|
op.opCompleter = newOpCompleter(op.buf.w, op)
|
||||||
}
|
}
|
||||||
|
|
||||||
op.opSearch = cfg.opSearch
|
op.opSearch = cfg.opSearch
|
||||||
|
|
50
runebuf.go
50
runebuf.go
|
@ -18,14 +18,11 @@ type RuneBuffer struct {
|
||||||
buf []rune
|
buf []rune
|
||||||
idx int
|
idx int
|
||||||
prompt []rune
|
prompt []rune
|
||||||
w io.Writer
|
w *Terminal
|
||||||
|
|
||||||
interactive bool
|
interactive bool
|
||||||
cfg *Config
|
cfg *Config
|
||||||
|
|
||||||
width int
|
|
||||||
height int
|
|
||||||
|
|
||||||
bck *runeBufferBck
|
bck *runeBufferBck
|
||||||
|
|
||||||
offset string // is offset useful? scrolling means row varies
|
offset string // is offset useful? scrolling means row varies
|
||||||
|
@ -40,19 +37,6 @@ func (r *RuneBuffer) pushKill(text []rune) {
|
||||||
r.lastKill = append([]rune{}, text...)
|
r.lastKill = append([]rune{}, text...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RuneBuffer) OnWidthChange(newWidth int) {
|
|
||||||
r.Lock()
|
|
||||||
r.width = newWidth
|
|
||||||
r.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *RuneBuffer) OnSizeChange(newWidth, newHeight int) {
|
|
||||||
r.Lock()
|
|
||||||
r.width = newWidth
|
|
||||||
r.height = newHeight
|
|
||||||
r.Unlock()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *RuneBuffer) Backup() {
|
func (r *RuneBuffer) Backup() {
|
||||||
r.Lock()
|
r.Lock()
|
||||||
r.bck = &runeBufferBck{r.buf, r.idx}
|
r.bck = &runeBufferBck{r.buf, r.idx}
|
||||||
|
@ -69,13 +53,11 @@ func (r *RuneBuffer) Restore() {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewRuneBuffer(w io.Writer, prompt string, cfg *Config, width int, height int) *RuneBuffer {
|
func NewRuneBuffer(w *Terminal, prompt string, cfg *Config) *RuneBuffer {
|
||||||
rb := &RuneBuffer{
|
rb := &RuneBuffer{
|
||||||
w: w,
|
w: w,
|
||||||
interactive: cfg.useInteractive(),
|
interactive: cfg.useInteractive(),
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
width: width,
|
|
||||||
height: height,
|
|
||||||
}
|
}
|
||||||
rb.SetPrompt(prompt)
|
rb.SetPrompt(prompt)
|
||||||
return rb
|
return rb
|
||||||
|
@ -102,9 +84,8 @@ func (r *RuneBuffer) CurrentWidth(x int) int {
|
||||||
|
|
||||||
func (r *RuneBuffer) PromptLen() int {
|
func (r *RuneBuffer) PromptLen() int {
|
||||||
r.Lock()
|
r.Lock()
|
||||||
width := r.promptLen()
|
defer r.Unlock()
|
||||||
r.Unlock()
|
return r.promptLen()
|
||||||
return width
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RuneBuffer) promptLen() int {
|
func (r *RuneBuffer) promptLen() int {
|
||||||
|
@ -448,12 +429,13 @@ func (r *RuneBuffer) isInLineEdge() bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RuneBuffer) getSplitByLine(rs []rune, nextWidth int) [][]rune {
|
func (r *RuneBuffer) getSplitByLine(rs []rune, nextWidth int) [][]rune {
|
||||||
|
tWidth, _ := r.w.GetWidthHeight()
|
||||||
if r.cfg.EnableMask {
|
if r.cfg.EnableMask {
|
||||||
w := runes.Width(r.cfg.MaskRune)
|
w := runes.Width(r.cfg.MaskRune)
|
||||||
masked := []rune(strings.Repeat(string(r.cfg.MaskRune), len(rs)))
|
masked := []rune(strings.Repeat(string(r.cfg.MaskRune), len(rs)))
|
||||||
return SplitByLine(runes.ColorFilter(r.prompt), masked, r.ppos, r.width, w)
|
return SplitByLine(runes.ColorFilter(r.prompt), masked, r.ppos, tWidth, w)
|
||||||
} else {
|
} else {
|
||||||
return SplitByLine(runes.ColorFilter(r.prompt), rs, r.ppos, r.width, nextWidth)
|
return SplitByLine(runes.ColorFilter(r.prompt), rs, r.ppos, tWidth, nextWidth)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -476,7 +458,8 @@ func (r *RuneBuffer) idxLine(width int) int {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RuneBuffer) CursorLineCount() int {
|
func (r *RuneBuffer) CursorLineCount() int {
|
||||||
return r.LineCount() - r.IdxLine(r.width)
|
tWidth, _ := r.w.GetWidthHeight()
|
||||||
|
return r.LineCount() - r.IdxLine(tWidth)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RuneBuffer) Refresh(f func()) {
|
func (r *RuneBuffer) Refresh(f func()) {
|
||||||
|
@ -506,7 +489,7 @@ func (r *RuneBuffer) refresh(f func()) {
|
||||||
// will write the offset back to us via stdin and there may already be
|
// will write the offset back to us via stdin and there may already be
|
||||||
// other data in the stdin buffer ahead of it.
|
// other data in the stdin buffer ahead of it.
|
||||||
// This function is called at the start of readline each time.
|
// This function is called at the start of readline each time.
|
||||||
func (r *RuneBuffer) getAndSetOffset(t *Terminal) {
|
func (r *RuneBuffer) getAndSetOffset() {
|
||||||
if !r.interactive {
|
if !r.interactive {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -517,7 +500,7 @@ func (r *RuneBuffer) getAndSetOffset(t *Terminal) {
|
||||||
// at the beginning of the next line.
|
// at the beginning of the next line.
|
||||||
r.w.Write([]byte(" \b"))
|
r.w.Write([]byte(" \b"))
|
||||||
}
|
}
|
||||||
t.GetOffset(r.SetOffset)
|
r.w.GetOffset(r.SetOffset)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RuneBuffer) SetOffset(offset string) {
|
func (r *RuneBuffer) SetOffset(offset string) {
|
||||||
|
@ -528,8 +511,9 @@ func (r *RuneBuffer) SetOffset(offset string) {
|
||||||
|
|
||||||
func (r *RuneBuffer) setOffset(offset string) {
|
func (r *RuneBuffer) setOffset(offset string) {
|
||||||
r.offset = offset
|
r.offset = offset
|
||||||
if _, c, ok := (&escapeKeyPair{attr:offset}).Get2(); ok && c > 0 && c < r.width {
|
tWidth, _ := r.w.GetWidthHeight()
|
||||||
r.ppos = c - 1 // c should be 1..width
|
if _, c, ok := (&escapeKeyPair{attr:offset}).Get2(); ok && c > 0 && c < tWidth {
|
||||||
|
r.ppos = c - 1 // c should be 1..tWidth
|
||||||
} else {
|
} else {
|
||||||
r.ppos = 0
|
r.ppos = 0
|
||||||
}
|
}
|
||||||
|
@ -703,7 +687,8 @@ 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)
|
||||||
|
|
||||||
if r.width == 0 {
|
tWidth, _ := r.w.GetWidthHeight()
|
||||||
|
if tWidth == 0 {
|
||||||
buf.WriteString(strings.Repeat("\r\b", len(r.buf)+r.promptLen()))
|
buf.WriteString(strings.Repeat("\r\b", len(r.buf)+r.promptLen()))
|
||||||
buf.Write([]byte("\033[J"))
|
buf.Write([]byte("\033[J"))
|
||||||
} else {
|
} else {
|
||||||
|
@ -724,7 +709,8 @@ func (r *RuneBuffer) Clean() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RuneBuffer) clean() {
|
func (r *RuneBuffer) clean() {
|
||||||
r.cleanWithIdxLine(r.idxLine(r.width))
|
tWidth, _ := r.w.GetWidthHeight()
|
||||||
|
r.cleanWithIdxLine(r.idxLine(tWidth))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RuneBuffer) cleanWithIdxLine(idxLine int) {
|
func (r *RuneBuffer) cleanWithIdxLine(idxLine int) {
|
||||||
|
|
23
search.go
23
search.go
|
@ -4,7 +4,6 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"container/list"
|
"container/list"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -22,36 +21,24 @@ type opSearch struct {
|
||||||
state int
|
state int
|
||||||
dir int
|
dir int
|
||||||
source *list.Element
|
source *list.Element
|
||||||
w io.Writer
|
w *Terminal
|
||||||
buf *RuneBuffer
|
buf *RuneBuffer
|
||||||
data []rune
|
data []rune
|
||||||
history *opHistory
|
history *opHistory
|
||||||
cfg *Config
|
cfg *Config
|
||||||
markStart int
|
markStart int
|
||||||
markEnd int
|
markEnd int
|
||||||
width int
|
|
||||||
height int
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func newOpSearch(w io.Writer, buf *RuneBuffer, history *opHistory, cfg *Config, width int, height int) *opSearch {
|
func newOpSearch(w *Terminal, buf *RuneBuffer, history *opHistory, cfg *Config) *opSearch {
|
||||||
return &opSearch{
|
return &opSearch{
|
||||||
w: w,
|
w: w,
|
||||||
buf: buf,
|
buf: buf,
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
history: history,
|
history: history,
|
||||||
width: width,
|
|
||||||
height: height,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *opSearch) OnWidthChange(newWidth int) {
|
|
||||||
o.width = newWidth
|
|
||||||
}
|
|
||||||
func (o *opSearch) OnSizeChange(newWidth, newHeight int) {
|
|
||||||
o.width = newWidth
|
|
||||||
o.height = newHeight
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *opSearch) IsSearchMode() bool {
|
func (o *opSearch) IsSearchMode() bool {
|
||||||
return o.inMode
|
return o.inMode
|
||||||
}
|
}
|
||||||
|
@ -103,7 +90,8 @@ func (o *opSearch) SearchChar(r rune) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *opSearch) SearchMode(dir int) bool {
|
func (o *opSearch) SearchMode(dir int) bool {
|
||||||
if o.width == 0 {
|
tWidth, _ := o.w.GetWidthHeight()
|
||||||
|
if tWidth == 0 {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
alreadyInMode := o.inMode
|
alreadyInMode := o.inMode
|
||||||
|
@ -131,6 +119,7 @@ func (o *opSearch) ExitSearchMode(revert bool) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *opSearch) SearchRefresh(x int) {
|
func (o *opSearch) SearchRefresh(x int) {
|
||||||
|
tWidth, _ := o.w.GetWidthHeight()
|
||||||
if x == -2 {
|
if x == -2 {
|
||||||
o.state = S_STATE_FAILING
|
o.state = S_STATE_FAILING
|
||||||
} else if x >= 0 {
|
} else if x >= 0 {
|
||||||
|
@ -141,7 +130,7 @@ func (o *opSearch) SearchRefresh(x int) {
|
||||||
}
|
}
|
||||||
x = o.buf.CurrentWidth(x)
|
x = o.buf.CurrentWidth(x)
|
||||||
x += o.buf.PromptLen()
|
x += o.buf.PromptLen()
|
||||||
x = x % o.width
|
x = x % tWidth
|
||||||
|
|
||||||
if o.markStart > 0 {
|
if o.markStart > 0 {
|
||||||
o.buf.SetStyle(o.markStart, o.markEnd, "4")
|
o.buf.SetStyle(o.markStart, o.markEnd, "4")
|
||||||
|
|
18
terminal.go
18
terminal.go
|
@ -19,6 +19,8 @@ type Terminal struct {
|
||||||
wg sync.WaitGroup
|
wg sync.WaitGroup
|
||||||
sleeping int32
|
sleeping int32
|
||||||
|
|
||||||
|
width int // terminal width
|
||||||
|
height int // terminal height
|
||||||
sizeChan chan string
|
sizeChan chan string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,6 +35,8 @@ func NewTerminal(cfg *Config) (*Terminal, error) {
|
||||||
stopChan: make(chan struct{}, 1),
|
stopChan: make(chan struct{}, 1),
|
||||||
sizeChan: make(chan string, 1),
|
sizeChan: make(chan string, 1),
|
||||||
}
|
}
|
||||||
|
// Get and cache the current terminal size.
|
||||||
|
t.OnSizeChange()
|
||||||
|
|
||||||
go t.ioloop()
|
go t.ioloop()
|
||||||
return t, nil
|
return t, nil
|
||||||
|
@ -244,3 +248,17 @@ func (t *Terminal) SetConfig(c *Config) error {
|
||||||
t.m.Unlock()
|
t.m.Unlock()
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OnSizeChange gets the current terminal size and caches it
|
||||||
|
func (t *Terminal) OnSizeChange() {
|
||||||
|
t.m.Lock()
|
||||||
|
defer t.m.Unlock()
|
||||||
|
t.width, t.height = t.cfg.FuncGetSize()
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetWidthHeight returns the cached width, height values from the terminal
|
||||||
|
func (t *Terminal) GetWidthHeight() (width, height int) {
|
||||||
|
t.m.Lock()
|
||||||
|
defer t.m.Unlock()
|
||||||
|
return t.width, t.height
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue