From 8c73bd15441ca06c505d096e37facb01458bcde0 Mon Sep 17 00:00:00 2001 From: Kurtis Rader Date: Mon, 15 Mar 2021 20:52:51 -0700 Subject: [PATCH] Introduce $edit:command:-duration The `edit:command:-duration` variable is the elapsed seconds of the most recently run interactive command. It is currently marked experimental by virtue of a `-` prefix since it might be removed in favor of the command execution callback solution. Related #1029 --- 0.16.0-release-notes.md | 3 +++ pkg/edit/command_api.go | 20 ++++++++++++++++++-- pkg/eval/compile_effect.go | 3 --- pkg/shell/interact.go | 14 +++++++++++--- pkg/shell/script.go | 2 +- pkg/shell/shell.go | 8 ++++++-- 6 files changed, 39 insertions(+), 11 deletions(-) diff --git a/0.16.0-release-notes.md b/0.16.0-release-notes.md index d420f7a991..f4f71e31f4 100644 --- a/0.16.0-release-notes.md +++ b/0.16.0-release-notes.md @@ -28,3 +28,6 @@ New features in the interactive editor: bindings. - A new `edit:clear` builtin to clear the screen has been added. + +- A new, experimental, `edit:command:-duration` variable that is the number of + seconds to run the most recent command. diff --git a/pkg/edit/command_api.go b/pkg/edit/command_api.go index 26eaa06512..f1cb06cd54 100644 --- a/pkg/edit/command_api.go +++ b/pkg/edit/command_api.go @@ -3,11 +3,20 @@ package edit import ( "src.elv.sh/pkg/cli/mode" "src.elv.sh/pkg/eval" + "src.elv.sh/pkg/eval/vars" ) -//elv:fn command:start +//elvdoc:fn command:start // -// Starts the command mode. +// Start command mode. + +//elvdoc:var command:-duration +// +// Duration, in seconds, of the most recent interactive command. This can be useful in your prompt +// (left or right) to provide feedback on how long a command took to run. The initial value of this +// variable is the time to evaluate your *~/.elvish/rc.elv* script before issuing the first prompt. + +var CommandDuration float64 func initCommandAPI(ed *Editor, ev *eval.Evaler, nb eval.NsBuilder) { bindingVar := newBindingVar(emptyBindingsMap) @@ -15,6 +24,13 @@ func initCommandAPI(ed *Editor, ev *eval.Evaler, nb eval.NsBuilder) { nb.AddNs("command", eval.NsBuilder{ "binding": bindingVar, + // This is marked as an experimental variable because it might be preferable to provide + // this info via a callback. See https://github.com/elves/elvish/issues/1029. + // + // TODO: Change this to a read-only var, possibly by introducing a vars.FromPtrReadonly + // function, to guard against attempts to modify the value from Elvish code. Assuming, + // of course, we continue to expose it as a variable rather than via a callback. + "-duration": vars.FromPtr(&CommandDuration), }.AddGoFns(":", map[string]interface{}{ "start": func() { w := mode.NewStub(mode.StubSpec{ diff --git a/pkg/eval/compile_effect.go b/pkg/eval/compile_effect.go index 70ea1722f8..88ba2da7b9 100644 --- a/pkg/eval/compile_effect.go +++ b/pkg/eval/compile_effect.go @@ -171,7 +171,6 @@ func (cp *compiler) formOp(n *parse.Form) effectOp { lvalues := cp.parseIndexingLValue(a.Left) tempLValues = append(tempLValues, lvalues.lvalues...) } - logger.Println("temporary assignment of", len(n.Assignments), "pairs") } redirOps := cp.redirOps(n.Redirs) @@ -291,7 +290,6 @@ func (op *formOp) exec(fm *Frame) (errRet Exception) { } val := v.Get() saveVals = append(saveVals, val) - logger.Printf("saved %s = %s", v, val) } // Do assignment. for _, subop := range op.tempAssignOps { @@ -315,7 +313,6 @@ func (op *formOp) exec(fm *Frame) (errRet Exception) { if err != nil { errRet = fm.errorp(op, err) } - logger.Printf("restored %s = %s", v, val) } }() } diff --git a/pkg/shell/interact.go b/pkg/shell/interact.go index d95c65cf4f..35d550fb5c 100644 --- a/pkg/shell/interact.go +++ b/pkg/shell/interact.go @@ -5,6 +5,7 @@ import ( "io" "os" "path/filepath" + "strings" "syscall" "time" @@ -79,7 +80,6 @@ func Interact(fds [3]*os.File, cfg *InteractConfig) { cmdNum++ line, err := ed.ReadCode() - if err == io.EOF { break } else if err != nil { @@ -101,8 +101,15 @@ func Interact(fds [3]*os.File, cfg *InteractConfig) { // No error; reset cooldown. cooldown = time.Second - err = evalInTTY(ev, fds, + // We only bother evaluating the statement(s) if the line is not entirely whitespace. This + // isn't especially important but keeps things like the `edit:command:-duration` variable + // from being updated when we didn't actually evaluate any statements. + if strings.TrimSpace(line) == "" { + continue + } + duration, err := evalInTTY(ev, fds, parse.Source{Name: fmt.Sprintf("[tty %v]", cmdNum), Code: line}) + edit.CommandDuration = duration term.Sanitize(fds[0], fds[2]) if err != nil { diag.ShowError(fds[2], err) @@ -122,7 +129,8 @@ func sourceRC(fds [3]*os.File, ev *eval.Evaler, rcPath string) error { } return err } - err = evalInTTY(ev, fds, parse.Source{Name: absPath, Code: code, IsFile: true}) + duration, err := evalInTTY(ev, fds, parse.Source{Name: absPath, Code: code, IsFile: true}) + edit.CommandDuration = duration if err != nil { return err } diff --git a/pkg/shell/script.go b/pkg/shell/script.go index daae5cebc0..47430c5159 100644 --- a/pkg/shell/script.go +++ b/pkg/shell/script.go @@ -67,7 +67,7 @@ func Script(fds [3]*os.File, args []string, cfg *ScriptConfig) int { return 2 } } else { - err := evalInTTY(ev, fds, src) + _, err := evalInTTY(ev, fds, src) if err != nil { diag.ShowError(fds[2], err) return 2 diff --git a/pkg/shell/shell.go b/pkg/shell/shell.go index dd57d99cb8..36219dbe2c 100644 --- a/pkg/shell/shell.go +++ b/pkg/shell/shell.go @@ -5,6 +5,7 @@ import ( "os" "os/signal" "strconv" + "time" "src.elv.sh/pkg/cli/term" "src.elv.sh/pkg/env" @@ -62,11 +63,14 @@ func setupShell(fds [3]*os.File, p Paths, spawn bool) (*eval.Evaler, func()) { } } -func evalInTTY(ev *eval.Evaler, fds [3]*os.File, src parse.Source) error { +func evalInTTY(ev *eval.Evaler, fds [3]*os.File, src parse.Source) (float64, error) { + start := time.Now() ports, cleanup := eval.PortsFromFiles(fds, ev.ValuePrefix()) defer cleanup() - return ev.Eval(src, eval.EvalCfg{ + err := ev.Eval(src, eval.EvalCfg{ Ports: ports, Interrupt: eval.ListenInterrupts, PutInFg: true}) + end := time.Now() + return end.Sub(start).Seconds(), err } func incSHLVL() func() {