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 }