src.elv.sh@v0.21.0-dev.0.20240515223629-06979efb9a2a/pkg/cli/modes/instant.go (about) 1 package modes 2 3 import ( 4 "errors" 5 6 "src.elv.sh/pkg/cli" 7 "src.elv.sh/pkg/cli/term" 8 "src.elv.sh/pkg/cli/tk" 9 "src.elv.sh/pkg/ui" 10 ) 11 12 // Instant is a mode that executes code whenever it changes and shows the 13 // result. 14 type Instant interface { 15 tk.Widget 16 } 17 18 // InstantSpec specifies the configuration for the instant mode. 19 type InstantSpec struct { 20 // Key bindings. 21 Bindings tk.Bindings 22 // The function to execute code and returns the output. 23 Execute func(code string) ([]string, error) 24 } 25 26 type instant struct { 27 InstantSpec 28 attachedTo tk.CodeArea 29 textView tk.TextView 30 lastCode string 31 lastErr error 32 } 33 34 func (w *instant) Render(width, height int) *term.Buffer { 35 buf := w.render(width, height) 36 buf.TrimToLines(0, height) 37 return buf 38 } 39 40 func (w *instant) MaxHeight(width, height int) int { 41 return len(w.render(width, height).Lines) 42 } 43 44 func (w *instant) render(width, height int) *term.Buffer { 45 bb := term.NewBufferBuilder(width). 46 WriteStyled(modeLine(" INSTANT ", false)).SetDotHere() 47 if w.lastErr != nil { 48 bb.Newline().Write(w.lastErr.Error(), ui.FgRed) 49 } 50 buf := bb.Buffer() 51 if len(buf.Lines) < height { 52 bufTextView := w.textView.Render(width, height-len(buf.Lines)) 53 buf.Extend(bufTextView, false) 54 } 55 return buf 56 } 57 58 func (w *instant) Focus() bool { return false } 59 60 func (w *instant) Handle(event term.Event) bool { 61 handled := w.Bindings.Handle(w, event) 62 if !handled { 63 handled = w.attachedTo.Handle(event) 64 } 65 w.update(false) 66 return handled 67 } 68 69 func (w *instant) update(force bool) { 70 code := w.attachedTo.CopyState().Buffer.Content 71 if code == w.lastCode && !force { 72 return 73 } 74 w.lastCode = code 75 output, err := w.Execute(code) 76 w.lastErr = err 77 if err == nil { 78 w.textView.MutateState(func(s *tk.TextViewState) { 79 *s = tk.TextViewState{Lines: output, First: 0} 80 }) 81 } 82 } 83 84 var errExecutorIsRequired = errors.New("executor is required") 85 86 // NewInstant creates a new instant mode. 87 func NewInstant(app cli.App, cfg InstantSpec) (Instant, error) { 88 codeArea, err := FocusedCodeArea(app) 89 if err != nil { 90 return nil, err 91 } 92 if cfg.Execute == nil { 93 return nil, errExecutorIsRequired 94 } 95 if cfg.Bindings == nil { 96 cfg.Bindings = tk.DummyBindings{} 97 } 98 w := instant{ 99 InstantSpec: cfg, 100 attachedTo: codeArea, 101 textView: tk.NewTextView(tk.TextViewSpec{Scrollable: true}), 102 } 103 w.update(true) 104 return &w, nil 105 }