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  }