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  }