github.com/markusbkk/elvish@v0.0.0-20231204143114-91dc52438621/pkg/edit/builtins.go (about) 1 package edit 2 3 import ( 4 "errors" 5 6 "github.com/markusbkk/elvish/pkg/cli" 7 "github.com/markusbkk/elvish/pkg/cli/modes" 8 "github.com/markusbkk/elvish/pkg/cli/term" 9 "github.com/markusbkk/elvish/pkg/cli/tk" 10 "github.com/markusbkk/elvish/pkg/eval" 11 "github.com/markusbkk/elvish/pkg/eval/errs" 12 "github.com/markusbkk/elvish/pkg/eval/vals" 13 "github.com/markusbkk/elvish/pkg/parse" 14 "github.com/markusbkk/elvish/pkg/parse/parseutil" 15 "github.com/markusbkk/elvish/pkg/ui" 16 ) 17 18 //elvdoc:fn binding-table 19 // 20 // Converts a normal map into a binding map. 21 22 //elvdoc:fn -dump-buf 23 // 24 // Dumps the current UI buffer as HTML. This command is used to generate 25 // "ttyshots" on the [website](https://elv.sh). 26 // 27 // Example: 28 // 29 // ```elvish 30 // set edit:global-binding[Ctrl-X] = { print (edit:-dump-buf) > ~/a.html } 31 // ``` 32 33 func dumpBuf(tty cli.TTY) string { 34 return bufToHTML(tty.Buffer()) 35 } 36 37 //elvdoc:fn close-mode 38 // 39 // Closes the current active mode. 40 41 func closeMode(app cli.App) { 42 app.PopAddon() 43 } 44 45 //elvdoc:fn end-of-history 46 // 47 // Adds a notification saying "End of history". 48 49 func endOfHistory(app cli.App) { 50 app.Notify(ui.T("End of history")) 51 } 52 53 //elvdoc:fn redraw 54 // 55 // ```elvish 56 // edit:redraw &full=$false 57 // ``` 58 // 59 // Triggers a redraw. 60 // 61 // The `&full` option controls whether to do a full redraw. By default, all 62 // redraws performed by the line editor are incremental redraws, updating only 63 // the part of the screen that has changed from the last redraw. A full redraw 64 // updates the entire command line. 65 66 type redrawOpts struct{ Full bool } 67 68 func (redrawOpts) SetDefaultOptions() {} 69 70 func redraw(app cli.App, opts redrawOpts) { 71 if opts.Full { 72 app.RedrawFull() 73 } else { 74 app.Redraw() 75 } 76 } 77 78 //elvdoc:fn clear 79 // 80 // ```elvish 81 // edit:clear 82 // ``` 83 // 84 // Clears the screen. 85 // 86 // This command should be used in place of the external `clear` command to clear 87 // the screen. 88 89 func clear(app cli.App, tty cli.TTY) { 90 tty.HideCursor() 91 tty.ClearScreen() 92 app.RedrawFull() 93 tty.ShowCursor() 94 } 95 96 //elvdoc:fn insert-raw 97 // 98 // Requests the next terminal input to be inserted uninterpreted. 99 100 func insertRaw(app cli.App, tty cli.TTY) { 101 codeArea, ok := focusedCodeArea(app) 102 if !ok { 103 return 104 } 105 tty.SetRawInput(1) 106 w := modes.NewStub(modes.StubSpec{ 107 Bindings: tk.FuncBindings(func(w tk.Widget, event term.Event) bool { 108 switch event := event.(type) { 109 case term.KeyEvent: 110 codeArea.MutateState(func(s *tk.CodeAreaState) { 111 s.Buffer.InsertAtDot(string(event.Rune)) 112 }) 113 app.PopAddon() 114 return true 115 default: 116 return false 117 } 118 }), 119 Name: " RAW ", 120 }) 121 app.PushAddon(w) 122 } 123 124 //elvdoc:fn key 125 // 126 // ```elvish 127 // edit:key $string 128 // ``` 129 // 130 // Parses a string into a key. 131 132 var errMustBeKeyOrString = errors.New("must be key or string") 133 134 func toKey(v interface{}) (ui.Key, error) { 135 switch v := v.(type) { 136 case ui.Key: 137 return v, nil 138 case string: 139 return ui.ParseKey(v) 140 default: 141 return ui.Key{}, errMustBeKeyOrString 142 } 143 } 144 145 //elvdoc:fn notify 146 // 147 // ```elvish 148 // edit:notify $message 149 // ``` 150 // 151 // Prints a notification message. The argument may be a string or a [styled 152 // text](builtin.html#styled). 153 // 154 // If called while the editor is active, this will print the message above the 155 // editor, and redraw the editor. 156 // 157 // If called while the editor is inactive, the message will be queued, and shown 158 // once the editor becomes active. 159 160 func notify(app cli.App, x interface{}) error { 161 // TODO: De-duplicate with the implementation of the styled builtin. 162 var t ui.Text 163 switch x := x.(type) { 164 case string: 165 t = ui.T(x) 166 case ui.Text: 167 t = x.Clone() 168 default: 169 return errs.BadValue{What: "argument to edit:notify", 170 Valid: "string, styled segment or styled text", Actual: vals.Kind(x)} 171 } 172 app.Notify(t) 173 return nil 174 } 175 176 //elvdoc:fn return-line 177 // 178 // Causes the Elvish REPL to end the current read iteration and evaluate the 179 // code it just read. If called from a key binding, takes effect after the key 180 // binding returns. 181 182 //elvdoc:fn return-eof 183 // 184 // Causes the Elvish REPL to terminate. If called from a key binding, takes 185 // effect after the key binding returns. 186 187 //elvdoc:fn smart-enter 188 // 189 // Inserts a literal newline if the current code is not syntactically complete 190 // Elvish code. Accepts the current line otherwise. 191 192 func smartEnter(app cli.App) { 193 codeArea, ok := focusedCodeArea(app) 194 if !ok { 195 return 196 } 197 commit := false 198 codeArea.MutateState(func(s *tk.CodeAreaState) { 199 buf := &s.Buffer 200 if isSyntaxComplete(buf.Content) { 201 commit = true 202 } else { 203 buf.InsertAtDot("\n") 204 } 205 }) 206 if commit { 207 app.CommitCode() 208 } 209 } 210 211 func isSyntaxComplete(code string) bool { 212 _, err := parse.Parse(parse.Source{Code: code}, parse.Config{}) 213 if err != nil { 214 for _, e := range err.(*parse.Error).Entries { 215 if e.Context.From == len(code) { 216 return false 217 } 218 } 219 } 220 return true 221 } 222 223 //elvdoc:fn wordify 224 // 225 // 226 // ```elvish 227 // edit:wordify $code 228 // ``` 229 // Breaks Elvish code into words. 230 231 func wordify(fm *eval.Frame, code string) error { 232 out := fm.ValueOutput() 233 for _, s := range parseutil.Wordify(code) { 234 err := out.Put(s) 235 if err != nil { 236 return err 237 } 238 } 239 return nil 240 } 241 242 func initTTYBuiltins(app cli.App, tty cli.TTY, nb eval.NsBuilder) { 243 nb.AddGoFns(map[string]interface{}{ 244 "-dump-buf": func() string { return dumpBuf(tty) }, 245 "insert-raw": func() { insertRaw(app, tty) }, 246 "clear": func() { clear(app, tty) }, 247 }) 248 } 249 250 func initMiscBuiltins(app cli.App, nb eval.NsBuilder) { 251 nb.AddGoFns(map[string]interface{}{ 252 "binding-table": makeBindingMap, 253 "close-mode": func() { closeMode(app) }, 254 "end-of-history": func() { endOfHistory(app) }, 255 "key": toKey, 256 "notify": func(x interface{}) error { return notify(app, x) }, 257 "redraw": func(opts redrawOpts) { redraw(app, opts) }, 258 "return-line": app.CommitCode, 259 "return-eof": app.CommitEOF, 260 "smart-enter": func() { smartEnter(app) }, 261 "wordify": wordify, 262 }) 263 } 264 265 // Like mode.FocusedCodeArea, but handles the error by writing a notification. 266 func focusedCodeArea(app cli.App) (tk.CodeArea, bool) { 267 codeArea, err := modes.FocusedCodeArea(app) 268 if err != nil { 269 app.Notify(modes.ErrorText(err)) 270 return nil, false 271 } 272 return codeArea, true 273 }