1package readline 2 3import ( 4 "bytes" 5 "container/list" 6 "fmt" 7 "io" 8) 9 10const ( 11 S_STATE_FOUND = iota 12 S_STATE_FAILING 13) 14 15const ( 16 S_DIR_BCK = iota 17 S_DIR_FWD 18) 19 20type opSearch struct { 21 inMode bool 22 state int 23 dir int 24 source *list.Element 25 w io.Writer 26 buf *RuneBuffer 27 data []rune 28 history *opHistory 29 cfg *Config 30 markStart int 31 markEnd int 32 width int 33} 34 35func newOpSearch(w io.Writer, buf *RuneBuffer, history *opHistory, cfg *Config, width int) *opSearch { 36 return &opSearch{ 37 w: w, 38 buf: buf, 39 cfg: cfg, 40 history: history, 41 width: width, 42 } 43} 44 45func (o *opSearch) OnWidthChange(newWidth int) { 46 o.width = newWidth 47} 48 49func (o *opSearch) IsSearchMode() bool { 50 return o.inMode 51} 52 53func (o *opSearch) SearchBackspace() { 54 if len(o.data) > 0 { 55 o.data = o.data[:len(o.data)-1] 56 o.search(true) 57 } 58} 59 60func (o *opSearch) findHistoryBy(isNewSearch bool) (int, *list.Element) { 61 if o.dir == S_DIR_BCK { 62 return o.history.FindBck(isNewSearch, o.data, o.buf.idx) 63 } 64 return o.history.FindFwd(isNewSearch, o.data, o.buf.idx) 65} 66 67func (o *opSearch) search(isChange bool) bool { 68 if len(o.data) == 0 { 69 o.state = S_STATE_FOUND 70 o.SearchRefresh(-1) 71 return true 72 } 73 idx, elem := o.findHistoryBy(isChange) 74 if elem == nil { 75 o.SearchRefresh(-2) 76 return false 77 } 78 o.history.current = elem 79 80 item := o.history.showItem(o.history.current.Value) 81 start, end := 0, 0 82 if o.dir == S_DIR_BCK { 83 start, end = idx, idx+len(o.data) 84 } else { 85 start, end = idx, idx+len(o.data) 86 idx += len(o.data) 87 } 88 o.buf.SetWithIdx(idx, item) 89 o.markStart, o.markEnd = start, end 90 o.SearchRefresh(idx) 91 return true 92} 93 94func (o *opSearch) SearchChar(r rune) { 95 o.data = append(o.data, r) 96 o.search(true) 97} 98 99func (o *opSearch) SearchMode(dir int) bool { 100 if o.width == 0 { 101 return false 102 } 103 alreadyInMode := o.inMode 104 o.inMode = true 105 o.dir = dir 106 o.source = o.history.current 107 if alreadyInMode { 108 o.search(false) 109 } else { 110 o.SearchRefresh(-1) 111 } 112 return true 113} 114 115func (o *opSearch) ExitSearchMode(revert bool) { 116 if revert { 117 o.history.current = o.source 118 o.buf.Set(o.history.showItem(o.history.current.Value)) 119 } 120 o.markStart, o.markEnd = 0, 0 121 o.state = S_STATE_FOUND 122 o.inMode = false 123 o.source = nil 124 o.data = nil 125} 126 127func (o *opSearch) SearchRefresh(x int) { 128 if x == -2 { 129 o.state = S_STATE_FAILING 130 } else if x >= 0 { 131 o.state = S_STATE_FOUND 132 } 133 if x < 0 { 134 x = o.buf.idx 135 } 136 x = o.buf.CurrentWidth(x) 137 x += o.buf.PromptLen() 138 x = x % o.width 139 140 if o.markStart > 0 { 141 o.buf.SetStyle(o.markStart, o.markEnd, "4") 142 } 143 144 lineCnt := o.buf.CursorLineCount() 145 buf := bytes.NewBuffer(nil) 146 buf.Write(bytes.Repeat([]byte("\n"), lineCnt)) 147 buf.WriteString("\033[J") 148 if o.state == S_STATE_FAILING { 149 buf.WriteString("failing ") 150 } 151 if o.dir == S_DIR_BCK { 152 buf.WriteString("bck") 153 } else if o.dir == S_DIR_FWD { 154 buf.WriteString("fwd") 155 } 156 buf.WriteString("-i-search: ") 157 buf.WriteString(string(o.data)) // keyword 158 buf.WriteString("\033[4m \033[0m") // _ 159 fmt.Fprintf(buf, "\r\033[%dA", lineCnt) // move prev 160 if x > 0 { 161 fmt.Fprintf(buf, "\033[%dC", x) // move forward 162 } 163 o.w.Write(buf.Bytes()) 164} 165