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 }