diff --git a/changes/unreleased/repl-usage-error-lines.fixed.md b/changes/unreleased/repl-usage-error-lines.fixed.md new file mode 100644 index 0000000000..2cf0c3f474 --- /dev/null +++ b/changes/unreleased/repl-usage-error-lines.fixed.md @@ -0,0 +1 @@ +- **The terminal prompt prints every line of a usage error.** A repository or notebook command given wrong arguments — `%load --id x`, `%load --id=a --id=b`, `%load --tags=a a.ipynb` — printed only the usage line at the prompt, leaving out the line saying what was wrong (`--id is not an option of %load`), which the Jupyter kernel's `UsageError` traceback showed. The prompt now prints the usage and the problem, as the kernel does. diff --git a/internal/frontend/repl/loop.go b/internal/frontend/repl/loop.go index b29ac645b0..71d096e630 100644 --- a/internal/frontend/repl/loop.go +++ b/internal/frontend/repl/loop.go @@ -56,7 +56,7 @@ func Loop(r LineReader, out io.Writer, s *Session) error { printLines(w, metaOut) _ = w.Flush() if merr != nil { - printLines(w, []string{"error: " + merr.Error()}) + printLines(w, metaErrorLines(merr)) _ = w.Flush() } if quit { @@ -124,6 +124,16 @@ func bareExpression(src string) (string, bool) { return trimmed, true } +// metaErrorLines prints a meta command's error: every line of a usage error, +// the first under the error prefix as the kernel's value; other errors as before. +func metaErrorLines(err error) []string { + var usage *UsageError + if errors.As(err, &usage) && len(usage.Lines) > 0 { + return append([]string{errPrefix + usage.Error()}, usage.Lines[1:]...) + } + return []string{errPrefix + err.Error()} +} + func printLines(w io.Writer, lines []string) { for _, l := range lines { _, _ = io.WriteString(w, l+"\n") diff --git a/internal/frontend/repl/repository_test.go b/internal/frontend/repl/repository_test.go index 10846841a5..3a9c3fb534 100644 --- a/internal/frontend/repl/repository_test.go +++ b/internal/frontend/repl/repository_test.go @@ -61,6 +61,25 @@ func wantUsage(t *testing.T, err error, line string) { func joined(lines []string) string { return strings.Join(lines, "\n") } +// TestLoopPrintsEveryLineOfAUsageError drives repository usage errors through the +// terminal loop: the usage line under the error prefix, then what was wrong. +func TestLoopPrintsEveryLineOfAUsageError(t *testing.T) { + repoSession(t) + var out strings.Builder + if err := Loop(&scriptReader{lines: []string{"%load --id x", "%load --id=a --id=b", "%projects now"}}, &out, NewSession()); err != nil { + t.Fatalf("Loop error: %v", err) + } + got := strings.Split(strings.TrimSuffix(out.String(), "\n"), "\n") + want := []string{ + "error: " + usageLoadRepo, "--id is not an option of %load", + "error: " + usageLoadRepo, "--id was given twice", + "error: " + usageProjects, + } + if strings.Join(got, "\n") != strings.Join(want, "\n") { + t.Errorf("the loop printed:\n%s\nwant:\n%s", strings.Join(got, "\n"), strings.Join(want, "\n")) + } +} + func TestRepoShowsAndSetsTheBaseURL(t *testing.T) { s, api := repoSession(t) if got := joined(runMeta(t, s, "%repo")); got != "API base path: "+api.URL() {