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