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