src.elv.sh@v0.21.0-dev.0.20240515223629-06979efb9a2a/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/modes"
     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  	"src.elv.sh/pkg/ui"
    14  )
    15  
    16  func navInsertSelected(app cli.App) {
    17  	w, ok := activeNavigation(app)
    18  	if !ok {
    19  		return
    20  	}
    21  	codeArea, ok := focusedCodeArea(app)
    22  	if !ok {
    23  		return
    24  	}
    25  	fname := w.SelectedName()
    26  	if fname == "" {
    27  		// User pressed Alt-Enter or Enter in an empty directory with nothing
    28  		// selected; don't do anything.
    29  		return
    30  	}
    31  
    32  	codeArea.MutateState(func(s *tk.CodeAreaState) {
    33  		dot := s.Buffer.Dot
    34  		if dot != 0 && !strings.ContainsRune(" \n", rune(s.Buffer.Content[dot-1])) {
    35  			// The dot is not at the beginning of a buffer, and the previous
    36  			// character is not a space or newline. Insert a space.
    37  			s.Buffer.InsertAtDot(" ")
    38  		}
    39  		// Insert the selected filename.
    40  		s.Buffer.InsertAtDot(parse.Quote(fname))
    41  	})
    42  }
    43  
    44  func navInsertSelectedAndQuit(app cli.App) {
    45  	navInsertSelected(app)
    46  	closeMode(app)
    47  }
    48  
    49  func convertNavWidthRatio(v any) [3]int {
    50  	var (
    51  		numbers []int
    52  		hasErr  bool
    53  	)
    54  	vals.Iterate(v, func(elem any) bool {
    55  		var i int
    56  		err := vals.ScanToGo(elem, &i)
    57  		if err != nil {
    58  			hasErr = true
    59  			return false
    60  		}
    61  		numbers = append(numbers, i)
    62  		return true
    63  	})
    64  	if hasErr || len(numbers) != 3 {
    65  		// TODO: Handle the error.
    66  		return [3]int{1, 3, 4}
    67  	}
    68  	var ret [3]int
    69  	copy(ret[:], numbers)
    70  	return ret
    71  }
    72  
    73  func initNavigation(ed *Editor, ev *eval.Evaler, nb eval.NsBuilder) {
    74  	bindingVar := newBindingVar(emptyBindingsMap)
    75  	bindings := newMapBindings(ed, ev, bindingVar)
    76  	widthRatioVar := newListVar(vals.MakeList(1.0, 3.0, 4.0))
    77  
    78  	selectedFileVar := vars.FromGet(func() any {
    79  		if w, ok := activeNavigation(ed.app); ok {
    80  			return w.SelectedName()
    81  		}
    82  		return nil
    83  	})
    84  
    85  	app := ed.app
    86  	// TODO: Rename to $edit:navigation:selected-file after deprecation
    87  	nb.AddVar("selected-file", selectedFileVar)
    88  	ns := eval.BuildNsNamed("edit:navigation").
    89  		AddVars(map[string]vars.Var{
    90  			"binding":     bindingVar,
    91  			"width-ratio": widthRatioVar,
    92  		}).
    93  		AddGoFns(map[string]any{
    94  			"start": func() {
    95  				w, err := modes.NewNavigation(app, modes.NavigationSpec{
    96  					Bindings: bindings,
    97  					Cursor:   modes.NewOSNavigationCursor(ev.Chdir),
    98  					WidthRatio: func() [3]int {
    99  						return convertNavWidthRatio(widthRatioVar.Get())
   100  					},
   101  					Filter: filterSpec,
   102  					CodeAreaRPrompt: func() ui.Text {
   103  						return bindingTips(ed.ns, "navigation:binding",
   104  							bindingTip("hidden", "navigation:trigger-shown-hidden"),
   105  							bindingTip("filter", "navigation:trigger-filter"))
   106  					},
   107  				})
   108  				if err != nil {
   109  					app.Notify(modes.ErrorText(err))
   110  				} else {
   111  					startMode(app, w, nil)
   112  				}
   113  			},
   114  			"left":  actOnNavigation(app, modes.Navigation.Ascend),
   115  			"right": actOnNavigation(app, modes.Navigation.Descend),
   116  			"up": actOnNavigation(app,
   117  				func(w modes.Navigation) { w.Select(tk.Prev) }),
   118  			"down": actOnNavigation(app,
   119  				func(w modes.Navigation) { w.Select(tk.Next) }),
   120  			"page-up": actOnNavigation(app,
   121  				func(w modes.Navigation) { w.Select(tk.PrevPage) }),
   122  			"page-down": actOnNavigation(app,
   123  				func(w modes.Navigation) { w.Select(tk.NextPage) }),
   124  
   125  			"file-preview-up": actOnNavigation(app,
   126  				func(w modes.Navigation) { w.ScrollPreview(-1) }),
   127  			"file-preview-down": actOnNavigation(app,
   128  				func(w modes.Navigation) { w.ScrollPreview(1) }),
   129  
   130  			"insert-selected":          func() { navInsertSelected(app) },
   131  			"insert-selected-and-quit": func() { navInsertSelectedAndQuit(app) },
   132  
   133  			"trigger-filter": actOnNavigation(app,
   134  				func(w modes.Navigation) { w.MutateFiltering(neg) }),
   135  			// TODO: Rename to trigger-show-hidden after deprecation
   136  			"trigger-shown-hidden": actOnNavigation(app,
   137  				func(w modes.Navigation) { w.MutateShowHidden(neg) }),
   138  		}).Ns()
   139  	nb.AddNs("navigation", ns)
   140  }
   141  
   142  func neg(b bool) bool { return !b }
   143  
   144  func activeNavigation(app cli.App) (modes.Navigation, bool) {
   145  	w, ok := app.ActiveWidget().(modes.Navigation)
   146  	return w, ok
   147  }
   148  
   149  func actOnNavigation(app cli.App, f func(modes.Navigation)) func() {
   150  	return func() {
   151  		if w, ok := activeNavigation(app); ok {
   152  			f(w)
   153  		}
   154  	}
   155  }