readline/search.go

171 lines
3.2 KiB
Go
Raw Normal View History

2015-09-22 18:01:15 +03:00
package readline
import (
"bytes"
"container/list"
"fmt"
"io"
)
const (
S_STATE_FOUND = iota
S_STATE_FAILING
)
const (
S_DIR_BCK = iota
S_DIR_FWD
)
type opSearch struct {
2015-09-23 09:34:58 +03:00
inMode bool
state int
dir int
source *list.Element
w io.Writer
buf *RuneBuffer
data []rune
history *opHistory
cfg *Config
2015-09-23 09:34:58 +03:00
markStart int
markEnd int
2016-03-13 13:32:48 +03:00
width int
A Pager to list completion candidates if they don't fit on one page. Main Features: - If there are too many candidates returned by the completer, completemode and completeselectmode did not work properly. Similar to the bash completion pager, list candidates and offer "--More--" on the end of each page. User can select " ", "y" or "Y" to keep listing or "q", "Q", "n", "N" to stop listing. When paging completes, we also exit out of completion mode. - Added aggregate completion when entering completeselectmode where the candiddates dwindle down sharing a larger common prefix. This makes typing a little faster than having to select. More bash-like behaviour. Other Fixes: - Fix various crashes where candidates are too wide for the width of the screen and causes division by zero. - Fix crash with wide (Asian characters) in completion. - Streamline redrawing as CompleteRefresh was called too often. - Fix crashes around ctrl-a and ctrl-b in select mode when candidates don't fit on a line - Fix prev/next candidates in select mode when candidates don't fit on a line - Fix crash when ctrl-k was pressed in select mode. This caused us to exitselectmode which cleaned up all the data but left us in complete mode such that if CompleteRefresh was callled directly, the data was not initialized. - Fix complete and select mode redraw issues when candidates did not fit on one line. - Fix cursor position issues after CompleteRefresh especially if the prompt and buffer also went over 1 line. - Fix redraw issue where exiting completion mode using certain key presses leaves candidates on the screen. Fixes for Windows: - Use window size for visible height/width instead of buffer size - Adjust for Window's EOL behaviour. Notes: - Added Height info to different structures as the decision to page or not required height information. - Added OnSizeChange(). Didn't know if I could get rid of the OnWidthChange()? Would be nice to remove the Width stuff and just have Size (width + height info).
2022-06-28 08:49:19 +03:00
height int
2015-09-22 18:01:15 +03:00
}
A Pager to list completion candidates if they don't fit on one page. Main Features: - If there are too many candidates returned by the completer, completemode and completeselectmode did not work properly. Similar to the bash completion pager, list candidates and offer "--More--" on the end of each page. User can select " ", "y" or "Y" to keep listing or "q", "Q", "n", "N" to stop listing. When paging completes, we also exit out of completion mode. - Added aggregate completion when entering completeselectmode where the candiddates dwindle down sharing a larger common prefix. This makes typing a little faster than having to select. More bash-like behaviour. Other Fixes: - Fix various crashes where candidates are too wide for the width of the screen and causes division by zero. - Fix crash with wide (Asian characters) in completion. - Streamline redrawing as CompleteRefresh was called too often. - Fix crashes around ctrl-a and ctrl-b in select mode when candidates don't fit on a line - Fix prev/next candidates in select mode when candidates don't fit on a line - Fix crash when ctrl-k was pressed in select mode. This caused us to exitselectmode which cleaned up all the data but left us in complete mode such that if CompleteRefresh was callled directly, the data was not initialized. - Fix complete and select mode redraw issues when candidates did not fit on one line. - Fix cursor position issues after CompleteRefresh especially if the prompt and buffer also went over 1 line. - Fix redraw issue where exiting completion mode using certain key presses leaves candidates on the screen. Fixes for Windows: - Use window size for visible height/width instead of buffer size - Adjust for Window's EOL behaviour. Notes: - Added Height info to different structures as the decision to page or not required height information. - Added OnSizeChange(). Didn't know if I could get rid of the OnWidthChange()? Would be nice to remove the Width stuff and just have Size (width + height info).
2022-06-28 08:49:19 +03:00
func newOpSearch(w io.Writer, buf *RuneBuffer, history *opHistory, cfg *Config, width int, height int) *opSearch {
2015-09-22 18:01:15 +03:00
return &opSearch{
w: w,
buf: buf,
2016-02-21 05:14:19 +03:00
cfg: cfg,
2015-09-22 18:01:15 +03:00
history: history,
2016-03-13 13:32:48 +03:00
width: width,
A Pager to list completion candidates if they don't fit on one page. Main Features: - If there are too many candidates returned by the completer, completemode and completeselectmode did not work properly. Similar to the bash completion pager, list candidates and offer "--More--" on the end of each page. User can select " ", "y" or "Y" to keep listing or "q", "Q", "n", "N" to stop listing. When paging completes, we also exit out of completion mode. - Added aggregate completion when entering completeselectmode where the candiddates dwindle down sharing a larger common prefix. This makes typing a little faster than having to select. More bash-like behaviour. Other Fixes: - Fix various crashes where candidates are too wide for the width of the screen and causes division by zero. - Fix crash with wide (Asian characters) in completion. - Streamline redrawing as CompleteRefresh was called too often. - Fix crashes around ctrl-a and ctrl-b in select mode when candidates don't fit on a line - Fix prev/next candidates in select mode when candidates don't fit on a line - Fix crash when ctrl-k was pressed in select mode. This caused us to exitselectmode which cleaned up all the data but left us in complete mode such that if CompleteRefresh was callled directly, the data was not initialized. - Fix complete and select mode redraw issues when candidates did not fit on one line. - Fix cursor position issues after CompleteRefresh especially if the prompt and buffer also went over 1 line. - Fix redraw issue where exiting completion mode using certain key presses leaves candidates on the screen. Fixes for Windows: - Use window size for visible height/width instead of buffer size - Adjust for Window's EOL behaviour. Notes: - Added Height info to different structures as the decision to page or not required height information. - Added OnSizeChange(). Didn't know if I could get rid of the OnWidthChange()? Would be nice to remove the Width stuff and just have Size (width + height info).
2022-06-28 08:49:19 +03:00
height: height,
2015-09-22 18:01:15 +03:00
}
}
2016-03-13 13:32:48 +03:00
func (o *opSearch) OnWidthChange(newWidth int) {
o.width = newWidth
}
A Pager to list completion candidates if they don't fit on one page. Main Features: - If there are too many candidates returned by the completer, completemode and completeselectmode did not work properly. Similar to the bash completion pager, list candidates and offer "--More--" on the end of each page. User can select " ", "y" or "Y" to keep listing or "q", "Q", "n", "N" to stop listing. When paging completes, we also exit out of completion mode. - Added aggregate completion when entering completeselectmode where the candiddates dwindle down sharing a larger common prefix. This makes typing a little faster than having to select. More bash-like behaviour. Other Fixes: - Fix various crashes where candidates are too wide for the width of the screen and causes division by zero. - Fix crash with wide (Asian characters) in completion. - Streamline redrawing as CompleteRefresh was called too often. - Fix crashes around ctrl-a and ctrl-b in select mode when candidates don't fit on a line - Fix prev/next candidates in select mode when candidates don't fit on a line - Fix crash when ctrl-k was pressed in select mode. This caused us to exitselectmode which cleaned up all the data but left us in complete mode such that if CompleteRefresh was callled directly, the data was not initialized. - Fix complete and select mode redraw issues when candidates did not fit on one line. - Fix cursor position issues after CompleteRefresh especially if the prompt and buffer also went over 1 line. - Fix redraw issue where exiting completion mode using certain key presses leaves candidates on the screen. Fixes for Windows: - Use window size for visible height/width instead of buffer size - Adjust for Window's EOL behaviour. Notes: - Added Height info to different structures as the decision to page or not required height information. - Added OnSizeChange(). Didn't know if I could get rid of the OnWidthChange()? Would be nice to remove the Width stuff and just have Size (width + height info).
2022-06-28 08:49:19 +03:00
func (o *opSearch) OnSizeChange(newWidth, newHeight int) {
o.width = newWidth
o.height = newHeight
}
2016-03-13 13:32:48 +03:00
2015-09-22 18:01:15 +03:00
func (o *opSearch) IsSearchMode() bool {
return o.inMode
}
func (o *opSearch) SearchBackspace() {
if len(o.data) > 0 {
o.data = o.data[:len(o.data)-1]
2015-09-23 06:10:36 +03:00
o.search(true)
2015-09-22 18:01:15 +03:00
}
}
2015-09-23 06:10:36 +03:00
func (o *opSearch) findHistoryBy(isNewSearch bool) (int, *list.Element) {
2015-09-22 18:01:15 +03:00
if o.dir == S_DIR_BCK {
return o.history.FindBck(isNewSearch, o.data, o.buf.idx)
2015-09-22 18:01:15 +03:00
}
return o.history.FindFwd(isNewSearch, o.data, o.buf.idx)
2015-09-22 18:01:15 +03:00
}
2015-09-23 06:10:36 +03:00
func (o *opSearch) search(isChange bool) bool {
if len(o.data) == 0 {
o.state = S_STATE_FOUND
o.SearchRefresh(-1)
return true
}
idx, elem := o.findHistoryBy(isChange)
2015-09-22 18:01:15 +03:00
if elem == nil {
o.SearchRefresh(-2)
return false
}
o.history.current = elem
2015-09-23 06:10:36 +03:00
item := o.history.showItem(o.history.current.Value)
start, end := 0, 0
if o.dir == S_DIR_BCK {
start, end = idx, idx+len(o.data)
} else {
start, end = idx, idx+len(o.data)
idx += len(o.data)
}
o.buf.SetWithIdx(idx, item)
2015-09-23 09:34:58 +03:00
o.markStart, o.markEnd = start, end
2015-09-22 18:01:15 +03:00
o.SearchRefresh(idx)
return true
}
func (o *opSearch) SearchChar(r rune) {
o.data = append(o.data, r)
2015-09-23 06:10:36 +03:00
o.search(true)
2015-09-22 18:01:15 +03:00
}
func (o *opSearch) SearchMode(dir int) bool {
if o.width == 0 {
return false
}
2015-09-23 06:10:36 +03:00
alreadyInMode := o.inMode
2015-09-22 18:01:15 +03:00
o.inMode = true
o.dir = dir
o.source = o.history.current
2015-09-23 06:10:36 +03:00
if alreadyInMode {
o.search(false)
} else {
o.SearchRefresh(-1)
}
return true
2015-09-22 18:01:15 +03:00
}
func (o *opSearch) ExitSearchMode(revert bool) {
if revert {
o.history.current = o.source
o.buf.Set(o.history.showItem(o.history.current.Value))
}
2015-09-23 09:34:58 +03:00
o.markStart, o.markEnd = 0, 0
2015-09-23 06:28:50 +03:00
o.state = S_STATE_FOUND
2015-09-22 18:01:15 +03:00
o.inMode = false
o.source = nil
o.data = nil
}
func (o *opSearch) SearchRefresh(x int) {
if x == -2 {
o.state = S_STATE_FAILING
} else if x >= 0 {
o.state = S_STATE_FOUND
}
if x < 0 {
x = o.buf.idx
}
x = o.buf.CurrentWidth(x)
2015-09-23 09:52:45 +03:00
x += o.buf.PromptLen()
2016-03-13 13:32:48 +03:00
x = x % o.width
2015-09-22 18:01:15 +03:00
2015-09-23 09:34:58 +03:00
if o.markStart > 0 {
2015-09-29 12:49:58 +03:00
o.buf.SetStyle(o.markStart, o.markEnd, "4")
2015-09-23 09:34:58 +03:00
}
2015-09-22 18:01:15 +03:00
lineCnt := o.buf.CursorLineCount()
buf := bytes.NewBuffer(nil)
buf.Write(bytes.Repeat([]byte("\n"), lineCnt))
buf.WriteString("\033[J")
if o.state == S_STATE_FAILING {
buf.WriteString("failing ")
}
if o.dir == S_DIR_BCK {
buf.WriteString("bck")
} else if o.dir == S_DIR_FWD {
buf.WriteString("fwd")
}
buf.WriteString("-i-search: ")
buf.WriteString(string(o.data)) // keyword
buf.WriteString("\033[4m \033[0m") // _
fmt.Fprintf(buf, "\r\033[%dA", lineCnt) // move prev
if x > 0 {
fmt.Fprintf(buf, "\033[%dC", x) // move forward
}
o.w.Write(buf.Bytes())
}