github.com/kolbycrouch/elvish@v0.14.1-0.20210614162631-215b9ac1c423/pkg/edit/navigation.go (about)

     1  package edit
     2  
     3  import (
     4  	"strings"
     5  
     6  	"src.elv.sh/pkg/cli"
     7  	"src.elv.sh/pkg/cli/mode"
     8  	"src.elv.sh/pkg/cli/tk"
     9  	"src.elv.sh/pkg/eval"
    10  	"src.elv.sh/pkg/eval/vals"
    11  	"src.elv.sh/pkg/eval/vars"
    12  	"src.elv.sh/pkg/parse"
    13  )
    14  
    15  //elvdoc:var selected-file
    16  //
    17  // Name of the currently selected file in navigation mode. $nil if not in
    18  // navigation mode.
    19  
    20  //elvdoc:var navigation:binding
    21  //
    22  // Keybinding for the navigation mode.
    23  
    24  //elvdoc:fn navigation:start
    25  //
    26  // Start the navigation mode.
    27  
    28  //elvdoc:fn navigation:insert-selected
    29  //
    30  // Inserts the selected filename.
    31  
    32  func navInsertSelected(app cli.App) {
    33  	w, ok := getNavigation(app)
    34  	if !ok {
    35  		return
    36  	}
    37  	fname := w.SelectedName()
    38  	if fname == "" {
    39  		// User pressed Alt-Enter or Enter in an empty directory with nothing
    40  		// selected; don't do anything.
    41  		return
    42  	}
    43  
    44  	app.CodeArea().MutateState(func(s *tk.CodeAreaState) {
    45  		dot := s.Buffer.Dot
    46  		if dot != 0 && !strings.ContainsRune(" \n", rune(s.Buffer.Content[dot-1])) {
    47  			// The dot is not at the beginning of a buffer, and the previous
    48  			// character is not a space or newline. Insert a space.
    49  			s.Buffer.InsertAtDot(" ")
    50  		}
    51  		// Insert the selected filename.
    52  		s.Buffer.InsertAtDot(parse.Quote(fname))
    53  	})
    54  }
    55  
    56  //elvdoc:fn navigation:insert-selected-and-quit
    57  //
    58  // Inserts the selected filename and closes the navigation addon.
    59  
    60  func navInsertSelectedAndQuit(app cli.App) {
    61  	navInsertSelected(app)
    62  	closeMode(app)
    63  }
    64  
    65  //elvdoc:fn navigation:trigger-filter
    66  //
    67  // Toggles the filtering status of the navigation addon.
    68  
    69  //elvdoc:fn navigation:trigger-shown-hidden
    70  //
    71  // Toggles whether the navigation addon should be showing hidden files.
    72  
    73  //elvdoc:var navigation:width-ratio
    74  //
    75  // A list of 3 integers, used for specifying the width ratio of the 3 columns in
    76  // navigation mode.
    77  
    78  func convertNavWidthRatio(v interface{}) [3]int {
    79  	var (
    80  		numbers []int
    81  		hasErr  bool
    82  	)
    83  	vals.Iterate(v, func(elem interface{}) bool {
    84  		var i int
    85  		err := vals.ScanToGo(elem, &i)
    86  		if err != nil {
    87  			hasErr = true
    88  			return false
    89  		}
    90  		numbers = append(numbers, i)
    91  		return true
    92  	})
    93  	if hasErr || len(numbers) != 3 {
    94  		// TODO: Handle the error.
    95  		return [3]int{1, 3, 4}
    96  	}
    97  	var ret [3]int
    98  	copy(ret[:], numbers)
    99  	return ret
   100  }
   101  
   102  func initNavigation(ed *Editor, ev *eval.Evaler, nb eval.NsBuilder) {
   103  	bindingVar := newBindingVar(emptyBindingsMap)
   104  	bindings := newMapBindings(ed, ev, bindingVar)
   105  	widthRatioVar := newListVar(vals.MakeList(1.0, 3.0, 4.0))
   106  
   107  	selectedFileVar := vars.FromGet(func() interface{} {
   108  		if w, ok := getNavigation(ed.app); ok {
   109  			return w.SelectedName()
   110  		}
   111  		return nil
   112  	})
   113  
   114  	app := ed.app
   115  	nb.Add("selected-file", selectedFileVar)
   116  	nb.AddNs("navigation",
   117  		eval.NsBuilder{
   118  			"binding":     bindingVar,
   119  			"width-ratio": widthRatioVar,
   120  		}.AddGoFns("<edit:navigation>", map[string]interface{}{
   121  			"start": func() {
   122  				w := mode.NewNavigation(app, mode.NavigationSpec{
   123  					Bindings: bindings,
   124  					WidthRatio: func() [3]int {
   125  						return convertNavWidthRatio(widthRatioVar.Get())
   126  					},
   127  					Filter: filterSpec,
   128  				})
   129  				startMode(app, w, nil)
   130  			},
   131  			"left":  actOnNavigation(app, mode.Navigation.Ascend),
   132  			"right": actOnNavigation(app, mode.Navigation.Descend),
   133  			"up": actOnNavigation(app,
   134  				func(w mode.Navigation) { w.Select(tk.Prev) }),
   135  			"down": actOnNavigation(app,
   136  				func(w mode.Navigation) { w.Select(tk.Next) }),
   137  			"page-up": actOnNavigation(app,
   138  				func(w mode.Navigation) { w.Select(tk.PrevPage) }),
   139  			"page-down": actOnNavigation(app,
   140  				func(w mode.Navigation) { w.Select(tk.NextPage) }),
   141  
   142  			"file-preview-up": actOnNavigation(app,
   143  				func(w mode.Navigation) { w.ScrollPreview(-1) }),
   144  			"file-preview-down": actOnNavigation(app,
   145  				func(w mode.Navigation) { w.ScrollPreview(1) }),
   146  
   147  			"insert-selected":          func() { navInsertSelected(app) },
   148  			"insert-selected-and-quit": func() { navInsertSelectedAndQuit(app) },
   149  
   150  			"trigger-filter": actOnNavigation(app,
   151  				func(w mode.Navigation) { w.MutateFiltering(neg) }),
   152  			"trigger-shown-hidden": actOnNavigation(app,
   153  				func(w mode.Navigation) { w.MutateShowHidden(neg) }),
   154  		}).Ns())
   155  }
   156  
   157  func neg(b bool) bool { return !b }
   158  
   159  func getNavigation(app cli.App) (mode.Navigation, bool) {
   160  	w, ok := app.CopyState().Addon.(mode.Navigation)
   161  	return w, ok
   162  }
   163  
   164  func actOnNavigation(app cli.App, f func(mode.Navigation)) func() {
   165  	return func() {
   166  		if w, ok := getNavigation(app); ok {
   167  			f(w)
   168  		}
   169  	}
   170  }