github.com/mem/u-root@v2.0.1-0.20181004165302-9b18b4636a33+incompatible/cmds/elvish/edit/history/history.go (about) 1 package history 2 3 import ( 4 "fmt" 5 "os" 6 "strconv" 7 "strings" 8 "sync" 9 10 "github.com/u-root/u-root/cmds/elvish/edit/eddefs" 11 "github.com/u-root/u-root/cmds/elvish/edit/ui" 12 "github.com/u-root/u-root/cmds/elvish/eval" 13 "github.com/u-root/u-root/cmds/elvish/eval/vals" 14 "github.com/u-root/u-root/cmds/elvish/eval/vars" 15 "github.com/u-root/u-root/cmds/elvish/store" 16 "github.com/u-root/u-root/cmds/elvish/util" 17 ) 18 19 var logger = util.GetLogger("[edit/history] ") 20 21 // Command history mode. 22 23 type hist struct { 24 ed eddefs.Editor 25 mutex sync.RWMutex 26 fuser *Fuser 27 binding eddefs.BindingMap 28 29 // Non-persistent state. 30 walker *Walker 31 bufferLen int 32 } 33 34 func Init(ed eddefs.Editor, ns eval.Ns) { 35 hist := &hist{ed: ed, binding: eddefs.EmptyBindingMap} 36 fuser, err := NewFuser(store.NewCmdHistory()) 37 if err != nil { 38 fmt.Fprintln(os.Stderr, "Failed to initialize command history; disabled.") 39 } else { 40 hist.fuser = fuser 41 ed.AddAfterReadline(hist.appendHistory) 42 } 43 hl := &histlist{} 44 histlistBinding := eddefs.EmptyBindingMap 45 46 historyNs := eval.Ns{ 47 "binding": vars.FromPtr(&hist.binding), 48 "list": vars.NewRo(List{&hist.mutex}), 49 } 50 historyNs.AddBuiltinFns("edit:history:", map[string]interface{}{ 51 "start": hist.start, 52 "up": hist.up, 53 "down": hist.down, 54 "down-or-quit": hist.downOrQuit, 55 "default": hist.defaultFn, 56 57 "fast-forward": hist.fuser.FastForward, 58 }) 59 60 histlistNs := eval.Ns{ 61 "binding": vars.FromPtr(&histlistBinding), 62 } 63 histlistNs.AddBuiltinFns("edit:histlist:", map[string]interface{}{ 64 "start": func() { 65 hl.start(ed, hist.fuser, histlistBinding) 66 }, 67 "toggle-dedup": func() { hl.toggleDedup(ed) }, 68 "toggle-case-sensitivity": func() { hl.toggleCaseSensitivity(ed) }, 69 }) 70 71 ns.AddNs("history", historyNs) 72 ns.AddNs("histlist", histlistNs) 73 // TODO(xiaq): Rename and put in edit:history 74 ns.AddBuiltinFn("edit:", "command-history", hist.commandHistory) 75 } 76 77 func (h *hist) Teardown() { 78 h.walker = nil 79 h.bufferLen = 0 80 } 81 82 func (h *hist) Binding(k ui.Key) eval.Callable { 83 return h.binding.GetOrDefault(k) 84 } 85 86 func (h *hist) ModeLine() ui.Renderer { 87 return ui.NewModeLineRenderer( 88 fmt.Sprintf(" HISTORY #%d ", h.walker.CurrentSeq()), "") 89 } 90 91 func (h *hist) Replacement() (int, int, string) { 92 begin := len(h.walker.Prefix()) 93 return begin, h.bufferLen, h.walker.CurrentCmd()[begin:] 94 } 95 96 func (hist *hist) start() { 97 ed := hist.ed 98 if hist.fuser == nil { 99 ed.Notify("history offline") 100 return 101 } 102 103 buffer, dot := ed.Buffer() 104 prefix := buffer[:dot] 105 walker := hist.fuser.Walker(prefix) 106 _, _, err := walker.Prev() 107 108 if err == nil { 109 hist.walker = walker 110 hist.bufferLen = len(buffer) 111 ed.SetMode(hist) 112 } else { 113 ed.AddTip("no matching history item") 114 } 115 } 116 117 func (hist *hist) up() { 118 _, _, err := hist.walker.Prev() 119 if err != nil { 120 hist.ed.Notify("%s", err) 121 } 122 } 123 124 func (hist *hist) down() { 125 _, _, err := hist.walker.Next() 126 if err != nil { 127 hist.ed.Notify("%s", err) 128 } 129 } 130 131 func (hist *hist) downOrQuit() { 132 _, _, err := hist.walker.Next() 133 if err != nil { 134 hist.ed.SetModeInsert() 135 } 136 } 137 138 func (hist *hist) defaultFn() { 139 newBuffer := hist.walker.CurrentCmd() 140 hist.ed.SetBuffer(newBuffer, len(newBuffer)) 141 hist.ed.SetModeInsert() 142 hist.ed.SetAction(eddefs.ReprocessKey) 143 } 144 145 func (hist *hist) appendHistory(line string) { 146 // Do not add empty commands or commands with leading spaces to 147 // TODO: Make this customizable. 148 if line == "" || strings.HasPrefix(line, " ") { 149 return 150 } 151 152 if hist.fuser != nil { 153 hist.mutex.Lock() 154 go func() { 155 err := hist.fuser.AddCmd(line) 156 hist.mutex.Unlock() 157 if err != nil { 158 logger.Printf("Failed to AddCmd %q: %v", line, err) 159 } 160 }() 161 } 162 } 163 164 func (hist *hist) commandHistory(fm *eval.Frame, args ...int) { 165 var limit, start, end int 166 167 out := fm.OutputChan() 168 cmds, err := hist.fuser.AllCmds() 169 if err != nil { 170 return 171 } 172 173 if len(args) > 0 { 174 limit = args[0] 175 } 176 177 total := len(cmds) 178 switch { 179 case limit > 0: 180 start = 0 181 end = limit 182 if limit > total { 183 end = total 184 } 185 case limit < 0: 186 start = limit + total 187 if start < 0 { 188 start = 0 189 } 190 end = total 191 default: 192 start = 0 193 end = total 194 } 195 196 for i := start; i < end; i++ { 197 out <- vals.MakeMapFromKV( 198 "id", strconv.Itoa(i), 199 "cmd", cmds[i], 200 ) 201 } 202 }