github.com/markusbkk/elvish@v0.0.0-20231204143114-91dc52438621/pkg/edit/navigation.go (about)

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