github.com/markusbkk/elvish@v0.0.0-20231204143114-91dc52438621/pkg/edit/listing.go (about) 1 package edit 2 3 import ( 4 "os" 5 6 "github.com/markusbkk/elvish/pkg/cli" 7 "github.com/markusbkk/elvish/pkg/cli/histutil" 8 "github.com/markusbkk/elvish/pkg/cli/modes" 9 "github.com/markusbkk/elvish/pkg/cli/tk" 10 "github.com/markusbkk/elvish/pkg/edit/filter" 11 "github.com/markusbkk/elvish/pkg/eval" 12 "github.com/markusbkk/elvish/pkg/eval/vals" 13 "github.com/markusbkk/elvish/pkg/eval/vars" 14 "github.com/markusbkk/elvish/pkg/store/storedefs" 15 ) 16 17 func initListings(ed *Editor, ev *eval.Evaler, st storedefs.Store, histStore histutil.Store, nb eval.NsBuilder) { 18 bindingVar := newBindingVar(emptyBindingsMap) 19 app := ed.app 20 nb.AddNs("listing", 21 eval.BuildNsNamed("edit:listing"). 22 AddVar("binding", bindingVar). 23 AddGoFns(map[string]interface{}{ 24 "accept": func() { listingAccept(app) }, 25 "up": func() { listingUp(app) }, 26 "down": func() { listingDown(app) }, 27 "up-cycle": func() { listingUpCycle(app) }, 28 "down-cycle": func() { listingDownCycle(app) }, 29 "page-up": func() { listingPageUp(app) }, 30 "page-down": func() { listingPageDown(app) }, 31 "start-custom": func(fm *eval.Frame, opts customListingOpts, items interface{}) { 32 listingStartCustom(ed, fm, opts, items) 33 }, 34 })) 35 36 initHistlist(ed, ev, histStore, bindingVar, nb) 37 initLastcmd(ed, ev, histStore, bindingVar, nb) 38 initLocation(ed, ev, st, bindingVar, nb) 39 } 40 41 var filterSpec = modes.FilterSpec{ 42 Maker: func(f string) func(string) bool { 43 q, _ := filter.Compile(f) 44 if q == nil { 45 return func(string) bool { return true } 46 } 47 return q.Match 48 }, 49 Highlighter: filter.Highlight, 50 } 51 52 func initHistlist(ed *Editor, ev *eval.Evaler, histStore histutil.Store, commonBindingVar vars.PtrVar, nb eval.NsBuilder) { 53 bindingVar := newBindingVar(emptyBindingsMap) 54 bindings := newMapBindings(ed, ev, bindingVar, commonBindingVar) 55 dedup := newBoolVar(true) 56 nb.AddNs("histlist", 57 eval.BuildNsNamed("edit:histlist"). 58 AddVar("binding", bindingVar). 59 AddGoFns(map[string]interface{}{ 60 "start": func() { 61 w, err := modes.NewHistlist(ed.app, modes.HistlistSpec{ 62 Bindings: bindings, 63 AllCmds: histStore.AllCmds, 64 Dedup: func() bool { 65 return dedup.Get().(bool) 66 }, 67 Filter: filterSpec, 68 }) 69 startMode(ed.app, w, err) 70 }, 71 "toggle-dedup": func() { 72 dedup.Set(!dedup.Get().(bool)) 73 listingRefilter(ed.app) 74 ed.app.Redraw() 75 }, 76 })) 77 } 78 79 func initLastcmd(ed *Editor, ev *eval.Evaler, histStore histutil.Store, commonBindingVar vars.PtrVar, nb eval.NsBuilder) { 80 bindingVar := newBindingVar(emptyBindingsMap) 81 bindings := newMapBindings(ed, ev, bindingVar, commonBindingVar) 82 nb.AddNs("lastcmd", 83 eval.BuildNsNamed("edit:lastcmd"). 84 AddVar("binding", bindingVar). 85 AddGoFn("start", func() { 86 // TODO: Specify wordifier 87 w, err := modes.NewLastcmd(ed.app, modes.LastcmdSpec{ 88 Bindings: bindings, Store: histStore}) 89 startMode(ed.app, w, err) 90 })) 91 } 92 93 func initLocation(ed *Editor, ev *eval.Evaler, st storedefs.Store, commonBindingVar vars.PtrVar, nb eval.NsBuilder) { 94 bindingVar := newBindingVar(emptyBindingsMap) 95 pinnedVar := newListVar(vals.EmptyList) 96 hiddenVar := newListVar(vals.EmptyList) 97 workspacesVar := newMapVar(vals.EmptyMap) 98 99 bindings := newMapBindings(ed, ev, bindingVar, commonBindingVar) 100 workspaceIterator := modes.LocationWSIterator( 101 adaptToIterateStringPair(workspacesVar)) 102 103 nb.AddNs("location", 104 eval.BuildNsNamed("edit:location"). 105 AddVars(map[string]vars.Var{ 106 "binding": bindingVar, 107 "hidden": hiddenVar, 108 "pinned": pinnedVar, 109 "workspaces": workspacesVar, 110 }). 111 AddGoFn("start", func() { 112 w, err := modes.NewLocation(ed.app, modes.LocationSpec{ 113 Bindings: bindings, Store: dirStore{ev, st}, 114 IteratePinned: adaptToIterateString(pinnedVar), 115 IterateHidden: adaptToIterateString(hiddenVar), 116 IterateWorkspaces: workspaceIterator, 117 Filter: filterSpec, 118 }) 119 startMode(ed.app, w, err) 120 })) 121 ev.AddAfterChdir(func(string) { 122 wd, err := os.Getwd() 123 if err != nil { 124 // TODO(xiaq): Surface the error. 125 return 126 } 127 if st != nil { 128 st.AddDir(wd, 1) 129 kind, root := workspaceIterator.Parse(wd) 130 if kind != "" { 131 st.AddDir(kind+wd[len(root):], 1) 132 } 133 } 134 }) 135 } 136 137 //elvdoc:fn listing:accept 138 // 139 // Accepts the current selected listing item. 140 141 func listingAccept(app cli.App) { 142 if w, ok := activeComboBox(app); ok { 143 w.ListBox().Accept() 144 } 145 } 146 147 //elvdoc:fn listing:up 148 // 149 // Moves the cursor up in listing mode. 150 151 func listingUp(app cli.App) { listingSelect(app, tk.Prev) } 152 153 //elvdoc:fn listing:down 154 // 155 // Moves the cursor down in listing mode. 156 157 func listingDown(app cli.App) { listingSelect(app, tk.Next) } 158 159 //elvdoc:fn listing:up-cycle 160 // 161 // Moves the cursor up in listing mode, or to the last item if the first item is 162 // currently selected. 163 164 func listingUpCycle(app cli.App) { listingSelect(app, tk.PrevWrap) } 165 166 //elvdoc:fn listing:down-cycle 167 // 168 // Moves the cursor down in listing mode, or to the first item if the last item is 169 // currently selected. 170 171 func listingDownCycle(app cli.App) { listingSelect(app, tk.NextWrap) } 172 173 //elvdoc:fn listing:page-up 174 // 175 // Moves the cursor up one page. 176 177 func listingPageUp(app cli.App) { listingSelect(app, tk.PrevPage) } 178 179 //elvdoc:fn listing:page-down 180 // 181 // Moves the cursor down one page. 182 183 func listingPageDown(app cli.App) { listingSelect(app, tk.NextPage) } 184 185 //elvdoc:fn listing:left 186 // 187 // Moves the cursor left in listing mode. 188 189 func listingLeft(app cli.App) { listingSelect(app, tk.Left) } 190 191 //elvdoc:fn listing:right 192 // 193 // Moves the cursor right in listing mode. 194 195 func listingRight(app cli.App) { listingSelect(app, tk.Right) } 196 197 func listingSelect(app cli.App, f func(tk.ListBoxState) int) { 198 if w, ok := activeComboBox(app); ok { 199 w.ListBox().Select(f) 200 } 201 } 202 203 func listingRefilter(app cli.App) { 204 if w, ok := activeComboBox(app); ok { 205 w.Refilter() 206 } 207 } 208 209 //elvdoc:var location:hidden 210 // 211 // A list of directories to hide in the location addon. 212 213 //elvdoc:var location:pinned 214 // 215 // A list of directories to always show at the top of the list of the location 216 // addon. 217 218 //elvdoc:var location:workspaces 219 // 220 // A map mapping types of workspaces to their patterns. 221 222 func adaptToIterateString(variable vars.Var) func(func(string)) { 223 return func(f func(s string)) { 224 vals.Iterate(variable.Get(), func(v interface{}) bool { 225 f(vals.ToString(v)) 226 return true 227 }) 228 } 229 } 230 231 func adaptToIterateStringPair(variable vars.Var) func(func(string, string) bool) { 232 return func(f func(a, b string) bool) { 233 m := variable.Get().(vals.Map) 234 for it := m.Iterator(); it.HasElem(); it.Next() { 235 k, v := it.Elem() 236 ks, kok := k.(string) 237 vs, vok := v.(string) 238 if kok && vok { 239 next := f(ks, vs) 240 if !next { 241 break 242 } 243 } 244 } 245 } 246 } 247 248 // Wraps an Evaler to implement the cli.DirStore interface. 249 type dirStore struct { 250 ev *eval.Evaler 251 st storedefs.Store 252 } 253 254 func (d dirStore) Chdir(path string) error { 255 return d.ev.Chdir(path) 256 } 257 258 func (d dirStore) Dirs(blacklist map[string]struct{}) ([]storedefs.Dir, error) { 259 if d.st == nil { 260 // A "no daemon" build won't have have a storedefs.Store object. 261 // Fail gracefully rather than panic. 262 return []storedefs.Dir{}, nil 263 } 264 return d.st.Dirs(blacklist) 265 } 266 267 func (d dirStore) Getwd() (string, error) { 268 return os.Getwd() 269 } 270 271 func startMode(app cli.App, w tk.Widget, err error) { 272 if w != nil { 273 app.PushAddon(w) 274 app.Redraw() 275 } 276 if err != nil { 277 app.Notify(modes.ErrorText(err)) 278 } 279 } 280 281 func activeComboBox(app cli.App) (tk.ComboBox, bool) { 282 w, ok := app.ActiveWidget().(tk.ComboBox) 283 return w, ok 284 }