1package readline 2 3import ( 4 "bufio" 5 "container/list" 6 "fmt" 7 "os" 8 "strings" 9 "sync" 10) 11 12type hisItem struct { 13 Source []rune 14 Version int64 15 Tmp []rune 16} 17 18func (h *hisItem) Clean() { 19 h.Source = nil 20 h.Tmp = nil 21} 22 23type opHistory struct { 24 cfg *Config 25 history *list.List 26 historyVer int64 27 current *list.Element 28 fd *os.File 29 fdLock sync.Mutex 30 enable bool 31} 32 33func newOpHistory(cfg *Config) (o *opHistory) { 34 o = &opHistory{ 35 cfg: cfg, 36 history: list.New(), 37 enable: true, 38 } 39 return o 40} 41 42func (o *opHistory) Reset() { 43 o.history = list.New() 44 o.current = nil 45} 46 47func (o *opHistory) IsHistoryClosed() bool { 48 o.fdLock.Lock() 49 defer o.fdLock.Unlock() 50 return o.fd.Fd() == ^(uintptr(0)) 51} 52 53func (o *opHistory) Init() { 54 if o.IsHistoryClosed() { 55 o.initHistory() 56 } 57} 58 59func (o *opHistory) initHistory() { 60 if o.cfg.HistoryFile != "" { 61 o.historyUpdatePath(o.cfg.HistoryFile) 62 } 63} 64 65// only called by newOpHistory 66func (o *opHistory) historyUpdatePath(path string) { 67 o.fdLock.Lock() 68 defer o.fdLock.Unlock() 69 f, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_RDWR, 0666) 70 if err != nil { 71 return 72 } 73 o.fd = f 74 r := bufio.NewReader(o.fd) 75 total := 0 76 for ; ; total++ { 77 line, err := r.ReadString('\n') 78 if err != nil { 79 break 80 } 81 // ignore the empty line 82 line = strings.TrimSpace(line) 83 if len(line) == 0 { 84 continue 85 } 86 o.Push([]rune(line)) 87 o.Compact() 88 } 89 if total > o.cfg.HistoryLimit { 90 o.rewriteLocked() 91 } 92 o.historyVer++ 93 o.Push(nil) 94 return 95} 96 97func (o *opHistory) Compact() { 98 for o.history.Len() > o.cfg.HistoryLimit && o.history.Len() > 0 { 99 o.history.Remove(o.history.Front()) 100 } 101} 102 103func (o *opHistory) Rewrite() { 104 o.fdLock.Lock() 105 defer o.fdLock.Unlock() 106 o.rewriteLocked() 107} 108 109func (o *opHistory) rewriteLocked() { 110 if o.cfg.HistoryFile == "" { 111 return 112 } 113 114 tmpFile := o.cfg.HistoryFile + ".tmp" 115 fd, err := os.OpenFile(tmpFile, os.O_CREATE|os.O_WRONLY|os.O_TRUNC|os.O_APPEND, 0666) 116 if err != nil { 117 return 118 } 119 120 buf := bufio.NewWriter(fd) 121 for elem := o.history.Front(); elem != nil; elem = elem.Next() { 122 buf.WriteString(string(elem.Value.(*hisItem).Source) + "\n") 123 } 124 buf.Flush() 125 126 // replace history file 127 if err = os.Rename(tmpFile, o.cfg.HistoryFile); err != nil { 128 fd.Close() 129 return 130 } 131 132 if o.fd != nil { 133 o.fd.Close() 134 } 135 // fd is write only, just satisfy what we need. 136 o.fd = fd 137} 138 139func (o *opHistory) Close() { 140 o.fdLock.Lock() 141 defer o.fdLock.Unlock() 142 if o.fd != nil { 143 o.fd.Close() 144 } 145} 146 147func (o *opHistory) FindBck(isNewSearch bool, rs []rune, start int) (int, *list.Element) { 148 for elem := o.current; elem != nil; elem = elem.Prev() { 149 item := o.showItem(elem.Value) 150 if isNewSearch { 151 start += len(rs) 152 } 153 if elem == o.current { 154 if len(item) >= start { 155 item = item[:start] 156 } 157 } 158 idx := runes.IndexAllBckEx(item, rs, o.cfg.HistorySearchFold) 159 if idx < 0 { 160 continue 161 } 162 return idx, elem 163 } 164 return -1, nil 165} 166 167func (o *opHistory) FindFwd(isNewSearch bool, rs []rune, start int) (int, *list.Element) { 168 for elem := o.current; elem != nil; elem = elem.Next() { 169 item := o.showItem(elem.Value) 170 if isNewSearch { 171 start -= len(rs) 172 if start < 0 { 173 start = 0 174 } 175 } 176 if elem == o.current { 177 if len(item)-1 >= start { 178 item = item[start:] 179 } else { 180 continue 181 } 182 } 183 idx := runes.IndexAllEx(item, rs, o.cfg.HistorySearchFold) 184 if idx < 0 { 185 continue 186 } 187 if elem == o.current { 188 idx += start 189 } 190 return idx, elem 191 } 192 return -1, nil 193} 194 195func (o *opHistory) showItem(obj interface{}) []rune { 196 item := obj.(*hisItem) 197 if item.Version == o.historyVer { 198 return item.Tmp 199 } 200 return item.Source 201} 202 203func (o *opHistory) Prev() []rune { 204 if o.current == nil { 205 return nil 206 } 207 current := o.current.Prev() 208 if current == nil { 209 return nil 210 } 211 o.current = current 212 return runes.Copy(o.showItem(current.Value)) 213} 214 215func (o *opHistory) Next() ([]rune, bool) { 216 if o.current == nil { 217 return nil, false 218 } 219 current := o.current.Next() 220 if current == nil { 221 return nil, false 222 } 223 224 o.current = current 225 return runes.Copy(o.showItem(current.Value)), true 226} 227 228// Disable the current history 229func (o *opHistory) Disable() { 230 o.enable = false 231} 232 233// Enable the current history 234func (o *opHistory) Enable() { 235 o.enable = true 236} 237 238func (o *opHistory) debug() { 239 Debug("-------") 240 for item := o.history.Front(); item != nil; item = item.Next() { 241 Debug(fmt.Sprintf("%+v", item.Value)) 242 } 243} 244 245// save history 246func (o *opHistory) New(current []rune) (err error) { 247 248 // history deactivated 249 if !o.enable { 250 return nil 251 } 252 253 current = runes.Copy(current) 254 255 // if just use last command without modify 256 // just clean lastest history 257 if back := o.history.Back(); back != nil { 258 prev := back.Prev() 259 if prev != nil { 260 if runes.Equal(current, prev.Value.(*hisItem).Source) { 261 o.current = o.history.Back() 262 o.current.Value.(*hisItem).Clean() 263 o.historyVer++ 264 return nil 265 } 266 } 267 } 268 269 if len(current) == 0 { 270 o.current = o.history.Back() 271 if o.current != nil { 272 o.current.Value.(*hisItem).Clean() 273 o.historyVer++ 274 return nil 275 } 276 } 277 278 if o.current != o.history.Back() { 279 // move history item to current command 280 currentItem := o.current.Value.(*hisItem) 281 // set current to last item 282 o.current = o.history.Back() 283 284 current = runes.Copy(currentItem.Tmp) 285 } 286 287 // err only can be a IO error, just report 288 err = o.Update(current, true) 289 290 // push a new one to commit current command 291 o.historyVer++ 292 o.Push(nil) 293 return 294} 295 296func (o *opHistory) Revert() { 297 o.historyVer++ 298 o.current = o.history.Back() 299} 300 301func (o *opHistory) Update(s []rune, commit bool) (err error) { 302 o.fdLock.Lock() 303 defer o.fdLock.Unlock() 304 s = runes.Copy(s) 305 if o.current == nil { 306 o.Push(s) 307 o.Compact() 308 return 309 } 310 r := o.current.Value.(*hisItem) 311 r.Version = o.historyVer 312 if commit { 313 r.Source = s 314 if o.fd != nil { 315 // just report the error 316 _, err = o.fd.Write([]byte(string(r.Source) + "\n")) 317 } 318 } else { 319 r.Tmp = append(r.Tmp[:0], s...) 320 } 321 o.current.Value = r 322 o.Compact() 323 return 324} 325 326func (o *opHistory) Push(s []rune) { 327 s = runes.Copy(s) 328 elem := o.history.PushBack(&hisItem{Source: s}) 329 o.current = elem 330} 331