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  }