github.com/kolbycrouch/elvish@v0.14.1-0.20210614162631-215b9ac1c423/pkg/cli/mode/instant.go (about)

     1  package mode
     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  	app      cli.App
    29  	textView tk.TextView
    30  	lastCode string
    31  	lastErr  error
    32  }
    33  
    34  func (w *instant) Render(width, height int) *term.Buffer {
    35  	bb := term.NewBufferBuilder(width).
    36  		WriteStyled(modeLine(" INSTANT ", false)).SetDotHere()
    37  	if w.lastErr != nil {
    38  		bb.Newline().Write(w.lastErr.Error(), ui.FgRed)
    39  	}
    40  	buf := bb.Buffer()
    41  	if len(buf.Lines) >= height {
    42  		buf.TrimToLines(0, height)
    43  		return buf
    44  	}
    45  	bufTextView := w.textView.Render(width, height-len(buf.Lines))
    46  	buf.Extend(bufTextView, false)
    47  	return buf
    48  }
    49  
    50  func (w *instant) Focus() bool { return false }
    51  
    52  func (w *instant) Handle(event term.Event) bool {
    53  	handled := w.Bindings.Handle(w, event)
    54  	if !handled {
    55  		codeArea := w.app.CodeArea()
    56  		handled = codeArea.Handle(event)
    57  	}
    58  	w.update(false)
    59  	return handled
    60  }
    61  
    62  func (w *instant) update(force bool) {
    63  	code := w.app.CodeArea().CopyState().Buffer.Content
    64  	if code == w.lastCode && !force {
    65  		return
    66  	}
    67  	w.lastCode = code
    68  	output, err := w.Execute(code)
    69  	w.lastErr = err
    70  	if err == nil {
    71  		w.textView.MutateState(func(s *tk.TextViewState) {
    72  			*s = tk.TextViewState{Lines: output, First: 0}
    73  		})
    74  	}
    75  }
    76  
    77  var errExecutorIsRequired = errors.New("executor is required")
    78  
    79  // NewInstant creates a new instant mode.
    80  func NewInstant(app cli.App, cfg InstantSpec) (Instant, error) {
    81  	if cfg.Execute == nil {
    82  		return nil, errExecutorIsRequired
    83  	}
    84  	if cfg.Bindings == nil {
    85  		cfg.Bindings = tk.DummyBindings{}
    86  	}
    87  	w := instant{
    88  		InstantSpec: cfg,
    89  		app:         app,
    90  		textView:    tk.NewTextView(tk.TextViewSpec{Scrollable: true}),
    91  	}
    92  	w.update(true)
    93  	return &w, nil
    94  }