github.com/lmorg/murex@v0.0.0-20240217211045-e081c89cd4ef/utils/readline/history.go (about) 1 package readline 2 3 import ( 4 "errors" 5 "strconv" 6 "strings" 7 ) 8 9 // History is an interface to allow you to write your own history logging 10 // tools. eg sqlite backend instead of a file system. 11 // By default readline will just use the dummyLineHistory interface which only 12 // logs the history to memory ([]string to be precise). 13 type History interface { 14 // Append takes the line and returns an updated number of lines or an error 15 Write(string) (int, error) 16 17 // GetLine takes the historic line number and returns the line or an error 18 GetLine(int) (string, error) 19 20 // Len returns the number of history lines 21 Len() int 22 23 // Dump returns everything in readline. The return is an interface{} because 24 // not all LineHistory implementations will want to structure the history in 25 // the same way. And since Dump() is not actually used by the readline API 26 // internally, this methods return can be structured in whichever way is most 27 // convenient for your own applications (or even just create an empty 28 //function which returns `nil` if you don't require Dump() either) 29 Dump() interface{} 30 } 31 32 // ExampleHistory is an example of a LineHistory interface: 33 type ExampleHistory struct { 34 items []string 35 } 36 37 // Write to history 38 func (h *ExampleHistory) Write(s string) (int, error) { 39 h.items = append(h.items, s) 40 return len(h.items), nil 41 } 42 43 // GetLine returns a line from history 44 func (h *ExampleHistory) GetLine(i int) (string, error) { 45 switch { 46 case i < 0: 47 return "", errors.New("requested history item out of bounds: < 0") 48 case i > h.Len()-1: 49 return "", errors.New("requested history item out of bounds: > Len()") 50 default: 51 return h.items[i], nil 52 } 53 } 54 55 // Len returns the number of lines in history 56 func (h *ExampleHistory) Len() int { 57 return len(h.items) 58 } 59 60 // Dump returns the entire history 61 func (h *ExampleHistory) Dump() interface{} { 62 return h.items 63 } 64 65 // NullHistory is a null History interface for when you don't want line 66 // entries remembered eg password input. 67 type NullHistory struct{} 68 69 // Write to history 70 func (h *NullHistory) Write(s string) (int, error) { 71 return 0, nil 72 } 73 74 // GetLine returns a line from history 75 func (h *NullHistory) GetLine(i int) (string, error) { 76 return "", nil 77 } 78 79 // Len returns the number of lines in history 80 func (h *NullHistory) Len() int { 81 return 0 82 } 83 84 // Dump returns the entire history 85 func (h *NullHistory) Dump() interface{} { 86 return []string{} 87 } 88 89 // Browse historic lines 90 func (rl *Instance) walkHistory(i int) { 91 if rl.previewRef == previewRefLine { 92 return // don't walk history if [f9] preview open 93 } 94 95 line := rl.line.String() 96 line = strings.TrimSpace(line) 97 rl._walkHistory(i, line) 98 } 99 100 func (rl *Instance) _walkHistory(i int, oldLine string) { 101 var ( 102 newLine string 103 err error 104 ) 105 106 switch rl.histPos + i { 107 case -1, rl.History.Len() + 1: 108 return 109 110 case rl.History.Len(): 111 rl.clearPrompt() 112 rl.histPos += i 113 if len(rl.viUndoHistory) > 0 && rl.viUndoHistory[len(rl.viUndoHistory)-1].String() != "" { 114 rl.line = rl.lineBuf.Duplicate() 115 } 116 117 default: 118 newLine, err = rl.History.GetLine(rl.histPos + i) 119 if err != nil { 120 rl.resetHelpers() 121 print("\r\n" + err.Error() + "\r\n") 122 print(rl.prompt) 123 return 124 } 125 126 if rl.histPos-i == rl.History.Len() { 127 rl.lineBuf = rl.line.Duplicate() 128 } 129 130 rl.histPos += i 131 if oldLine == newLine { 132 rl._walkHistory(i, newLine) 133 return 134 } 135 136 if len(rl.viUndoHistory) > 0 { 137 last := rl.viUndoHistory[len(rl.viUndoHistory)-1] 138 if !strings.HasPrefix(newLine, strings.TrimSpace(last.String())) { 139 rl._walkHistory(i, oldLine) 140 return 141 } 142 } 143 144 rl.clearPrompt() 145 146 rl.line = new(UnicodeT) 147 rl.line.Set(rl, []rune(newLine)) 148 } 149 150 if i > 0 { 151 _, y := rl.lineWrapCellLen() 152 print(strings.Repeat("\r\n", y)) 153 rl.line.SetRunePos(rl.line.RuneLen()) 154 } else { 155 rl.line.SetCellPos(rl.termWidth - rl.promptLen - 1) 156 } 157 print(rl.echoStr()) 158 print(rl.updateHelpersStr()) 159 } 160 161 func (rl *Instance) autocompleteHistory() ([]string, map[string]string) { 162 if rl.AutocompleteHistory != nil { 163 rl.tcPrefix = rl.line.String() 164 return rl.AutocompleteHistory(rl.tcPrefix) 165 } 166 167 var ( 168 items []string 169 descs = make(map[string]string) 170 171 line string 172 num string 173 err error 174 ) 175 176 rl.tcPrefix = rl.line.String() 177 for i := rl.History.Len() - 1; i >= 0; i-- { 178 line, err = rl.History.GetLine(i) 179 if err != nil { 180 continue 181 } 182 183 if !strings.HasPrefix(line, rl.tcPrefix) { 184 continue 185 } 186 187 line = strings.Replace(line, "\n", ` `, -1)[rl.line.RuneLen():] 188 189 if descs[line] != "" { 190 continue 191 } 192 193 items = append(items, line) 194 num = strconv.Itoa(i) 195 196 descs[line] = num 197 } 198 199 return items, descs 200 }