Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use the new terminal to handle ^C #200

Merged
merged 4 commits into from
Aug 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 2 additions & 13 deletions extensions/extension.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ package extensions

import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
Expand All @@ -17,6 +16,7 @@ import (
"unicode/utf8"

"fortio.org/log"
"fortio.org/terminal"
"github.com/rivo/uniseg"
"grol.io/grol/eval"
"grol.io/grol/lexer"
Expand Down Expand Up @@ -390,7 +390,7 @@ func createMisc() {
}
durDur := time.Duration(durSec * 1e9)
log.Infof("Sleeping for %v", durDur)
return s.Error(SleepWithContext(s.Context, durDur))
return s.Error(terminal.SleepWithContext(s.Context, durDur))
},
})
MustCreate(object.Extension{
Expand Down Expand Up @@ -434,17 +434,6 @@ func createMisc() {

// --- implementation of the functions that aren't inlined in lambdas above.

func SleepWithContext(ctx context.Context, duration time.Duration) error {
select {
case <-time.After(duration):
// Completed the sleep duration
return nil
case <-ctx.Done():
// Context was canceled
return ctx.Err()
}
}

func pow(args []object.Object) object.Object {
// Arg len check already done through MinArgs and MaxArgs
// and so is type check through ArgTypes.
Expand Down
4 changes: 2 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ require (
fortio.org/log v1.16.0
fortio.org/sets v1.2.0
fortio.org/struct2env v0.4.1
fortio.org/terminal v0.7.3
fortio.org/terminal v0.8.0
fortio.org/testscript v0.3.1 // only for tests
fortio.org/version v1.0.4
github.com/rivo/uniseg v0.4.7
Expand All @@ -17,7 +17,7 @@ require (
fortio.org/term v0.23.0-fortio-6 // indirect
github.com/kortschak/goroutine v1.1.2 // indirect
golang.org/x/crypto/x509roots/fallback v0.0.0-20240806160748-b2d3a6a4b4d3 // indirect
golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa // indirect
golang.org/x/exp v0.0.0-20240823005443-9b4947da3948 // indirect
golang.org/x/sys v0.24.0 // indirect
golang.org/x/tools v0.24.0 // indirect
)
8 changes: 4 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ fortio.org/struct2env v0.4.1 h1:rJludAMO5eBvpWplWEQNqoVDFZr4RWMQX7RUapgZyc0=
fortio.org/struct2env v0.4.1/go.mod h1:lENUe70UwA1zDUCX+8AsO663QCFqYaprk5lnPhjD410=
fortio.org/term v0.23.0-fortio-6 h1:pKrUX0tKOxyEhkhLV50oJYucTVx94rzFrXc24lIuLvk=
fortio.org/term v0.23.0-fortio-6/go.mod h1:7buBfn81wEJUGWiVjFNiUE/vxWs5FdM9c7PyZpZRS30=
fortio.org/terminal v0.7.3 h1:tQV8YikqowjuIxk3Ayfglf/AQWs2ywlBWuggAzCY6Dc=
fortio.org/terminal v0.7.3/go.mod h1:hRs4ZM5C06GyfLRD6JHD96yjJGgm1mXQH75Ze8CMLjw=
fortio.org/terminal v0.8.0 h1:NSaa/JQZ4lIK08QEfBExwuzNY/bXfkzZzpfuPB7MlEA=
fortio.org/terminal v0.8.0/go.mod h1:hRs4ZM5C06GyfLRD6JHD96yjJGgm1mXQH75Ze8CMLjw=
fortio.org/testscript v0.3.1 h1:MmRO64AsmzaU1KlYMzAbotJIMKRGxD1XXssJnBRiMGQ=
fortio.org/testscript v0.3.1/go.mod h1:7OJ+U4avooRNqc7p/VHKJadYgj9fA6+N0SbGU8FVWGs=
fortio.org/version v1.0.4 h1:FWUMpJ+hVTNc4RhvvOJzb0xesrlRmG/a+D6bjbQ4+5U=
Expand All @@ -22,8 +22,8 @@ github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
golang.org/x/crypto/x509roots/fallback v0.0.0-20240806160748-b2d3a6a4b4d3 h1:oWb21rU9Q9XrRwXLB7jHc1rbp6EiiimZZv5MLxpu4T0=
golang.org/x/crypto/x509roots/fallback v0.0.0-20240806160748-b2d3a6a4b4d3/go.mod h1:kNa9WdvYnzFwC79zRpLRMJbdEFlhyM5RPFBBZp/wWH8=
golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa h1:ELnwvuAXPNtPk1TJRuGkI9fDTwym6AYBu0qzT8AcHdI=
golang.org/x/exp v0.0.0-20240808152545-0cdaa3abc0fa/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ=
golang.org/x/exp v0.0.0-20240823005443-9b4947da3948 h1:kx6Ds3MlpiUHKj7syVnbp57++8WpuKPcR5yjLBjvLEA=
golang.org/x/exp v0.0.0-20240823005443-9b4947da3948/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ=
golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/tools v0.24.0 h1:J1shsA93PJUEVaUSaay7UXAyE8aimq3GW0pjlolpa24=
Expand Down
3 changes: 2 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
package main

import (
"context"
"flag"
"fmt"
"io"
Expand Down Expand Up @@ -123,7 +124,7 @@ func Main() (retcode int) {
return log.FErrf("Error initializing extensions: %v", err)
}
if *commandFlag != "" {
res, errs, _ := repl.EvalStringWithOption(options, *commandFlag)
res, errs, _ := repl.EvalStringWithOption(context.Background(), options, *commandFlag)
// Only parsing errors are already logged, eval errors aren't, we (re)log everything:
numErrs := len(errs)
if numErrs > 0 {
Expand Down
30 changes: 20 additions & 10 deletions repl/repl.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ func EvalAll(s *eval.State, in io.Reader, out io.Writer, options Options) []stri
if options.PreInput != nil {
options.PreInput(s)
}
_, _, errs, _ := EvalOne(s, what, out, options) //nolint:dogsled // as mentioned we should refactor EvalOne.
_, _, errs, _ := EvalOne(context.Background(), s, what, out, options) //nolint:dogsled // as mentioned we should refactor EvalOne.
return errs
}

Expand All @@ -162,7 +162,7 @@ func EvalStringOptions() Options {
// occurred.
// Default options are EvalStringOptions()'s.
func EvalString(what string) (string, []string, string) {
return EvalStringWithOption(EvalStringOptions(), what)
return EvalStringWithOption(context.Background(), EvalStringOptions(), what)
}

// EvalStringWithOption can be used from playground etc for single eval.
Expand All @@ -172,7 +172,7 @@ func EvalString(what string) (string, []string, string) {
// Following options should be set (starting from EvalStringOptions()) to
// additionally control the behavior:
// AutoLoad, AutoSave, Compact.
func EvalStringWithOption(o Options, what string) (res string, errs []string, formatted string) {
func EvalStringWithOption(ctx context.Context, o Options, what string) (res string, errs []string, formatted string) {
s := eval.NewState()
if o.MaxDepth > 0 {
s.MaxDepth = o.MaxDepth
Expand All @@ -188,7 +188,7 @@ func EvalStringWithOption(o Options, what string) (res string, errs []string, fo
}
panicked := false
incomplete := false
incomplete, panicked, errs, formatted = EvalOne(s, what, out, o)
incomplete, panicked, errs, formatted = EvalOne(ctx, s, what, out, o)
if incomplete && len(errs) == 0 {
errs = append(errs, "Incomplete input")
}
Expand All @@ -209,12 +209,13 @@ func extractHistoryNumber(input string) (int, bool) {
return 0, false
}

func Interactive(options Options) int {
func Interactive(options Options) int { //nolint:funlen // we do have quite a few cases.
options.NilAndErr = true
s := eval.NewState()
s.MaxDepth = options.MaxDepth
s.MaxValueLen = options.MaxValueLen // 0 is unlimited so ok to copy as is.
term, err := terminal.Open()
term, err := terminal.Open(context.Background())
ctx := term.Context
if err != nil {
return log.FErrf("Error creating readline: %v", err)
}
Expand Down Expand Up @@ -248,10 +249,15 @@ func Interactive(options Options) int {
for {
rd, err := term.ReadLine()
if errors.Is(err, io.EOF) {
log.Infof("Exit requested") // Don't say EOF as ^C comes through as EOF as well.
log.Infof("EOF, exiting")
_ = AutoSave(s, options)
return 0
}
if errors.Is(err, terminal.ErrInterrupted) {
log.Debugf("Interrupted error")
ctx, _ = term.ResetInterrupts(context.Background()) //nolint:fatcontext // we only get a new one after the previous one is done.
continue
}
if err != nil {
return log.FErrf("Error reading line: %v", err)
}
Expand Down Expand Up @@ -280,10 +286,14 @@ func Interactive(options Options) int {
fmt.Fprintln(term.Out,
"Type 'history' to see history, '!n' to repeat history n, 'info' for language builtins, use <tab> for completion.")
continue
case l == "exit":
log.Infof("Exit requested")
_ = AutoSave(s, options)
return 0
}
// normal errors are already logged but not the panic recoveries
// Note this is the only case that can get contNeeded (EOL instead of EOF mode)
contNeeded, _, _, formatted := EvalOne(s, l, term.Out, options)
contNeeded, _, _, formatted := EvalOne(ctx, s, l, term.Out, options)
if contNeeded {
prev = l + "\n"
term.SetPrompt(CONTINUATION)
Expand Down Expand Up @@ -347,7 +357,7 @@ func (g *Grol) Run(out io.Writer) error {
// errs is the list of errors, formatted is the normalized input.
// If a panic occurs, panicked is true and errs contains the one panic message.
// TODO: this one size fits 3 different calls (file, interactive, bot) is getting spaghetti.
func EvalOne(s *eval.State, what string, out io.Writer, options Options) (
func EvalOne(ctx context.Context, s *eval.State, what string, out io.Writer, options Options) (
continuation, panicked bool,
errs []string,
formatted string,
Expand All @@ -369,7 +379,7 @@ func EvalOne(s *eval.State, what string, out io.Writer, options Options) (
}
}()
}
s.SetContext(context.Background(), options.MaxDuration)
s.SetContext(ctx, options.MaxDuration)
continuation, errs, formatted = evalOne(s, what, out, options)
return
}
Expand Down
3 changes: 2 additions & 1 deletion repl/repl_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package repl_test

import (
"context"
"testing"

"grol.io/grol/eval"
Expand Down Expand Up @@ -165,7 +166,7 @@ func TestPreInputHook(t *testing.T) {
}
inp := `testHook()`
expected := "42\n"
res, errs, _ := repl.EvalStringWithOption(opts, inp)
res, errs, _ := repl.EvalStringWithOption(context.Background(), opts, inp)
if res != expected || len(errs) > 0 {
t.Errorf("EvalString() got %v\n---\n%s\n---want---\n%s\n---", errs, res, expected)
}
Expand Down
3 changes: 2 additions & 1 deletion wasm/wasm_main.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ Web assembly main for grol, exposing grol (repl.EvalString for now) to JS
package main

import (
"context"
"runtime"
"runtime/debug"
"strings"
Expand Down Expand Up @@ -53,7 +54,7 @@ func jsEval(this js.Value, args []js.Value) interface{} {
// as not exactly responsive to PRs nor issues folks (everyone trying their best yet...).
opts.MaxDepth = WasmMaxDepth
opts.MaxDuration = WasmMaxDuration
res, errs, formatted := repl.EvalStringWithOption(opts, input)
res, errs, formatted := repl.EvalStringWithOption(context.Background(), opts, input)
result := make(map[string]any)
result["result"] = strings.TrimSuffix(res, "\n")
// transfer errors to []any (!)
Expand Down