Skip to content

Commit 1176256

Browse files
authored
feat: improve CLI error messages (coder#6778)
1 parent b38d1ed commit 1176256

File tree

1 file changed

+40
-72
lines changed

1 file changed

+40
-72
lines changed

cli/root.go

+40-72
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package cli
22

33
import (
4-
"bufio"
54
"context"
65
"errors"
76
"flag"
@@ -14,14 +13,11 @@ import (
1413
"os"
1514
"os/signal"
1615
"path/filepath"
17-
"regexp"
1816
"runtime"
1917
"strings"
2018
"syscall"
2119
"time"
22-
"unicode/utf8"
2320

24-
"golang.org/x/crypto/ssh/terminal"
2521
"golang.org/x/exp/slices"
2622
"golang.org/x/xerrors"
2723

@@ -822,89 +818,61 @@ func isConnectionError(err error) bool {
822818
}
823819

824820
type prettyErrorFormatter struct {
825-
level int
826-
w io.Writer
827-
}
828-
829-
func (prettyErrorFormatter) prefixLines(spaces int, s string) string {
830-
twidth, _, err := terminal.GetSize(0)
831-
if err != nil {
832-
twidth = 80
833-
}
834-
835-
s = lipgloss.NewStyle().Width(twidth - spaces).Render(s)
836-
837-
var b strings.Builder
838-
scanner := bufio.NewScanner(strings.NewReader(s))
839-
for i := 0; scanner.Scan(); i++ {
840-
// The first line is already padded.
841-
if i == 0 {
842-
_, _ = fmt.Fprintf(&b, "%s\n", scanner.Text())
843-
continue
844-
}
845-
_, _ = fmt.Fprintf(&b, "%s%s\n", strings.Repeat(" ", spaces), scanner.Text())
846-
}
847-
return strings.TrimSuffix(strings.TrimSuffix(b.String(), "\n"), " ")
821+
w io.Writer
848822
}
849823

850824
func (p *prettyErrorFormatter) format(err error) {
851-
underErr := errors.Unwrap(err)
852-
853-
arrowStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("#515151"))
825+
errTail := errors.Unwrap(err)
854826

855827
//nolint:errorlint
856-
if _, ok := err.(*clibase.RunCommandError); ok && p.level == 0 && underErr != nil {
857-
// We can do a better job now.
858-
p.format(underErr)
828+
if _, ok := err.(*clibase.RunCommandError); ok && errTail != nil {
829+
// Avoid extra nesting.
830+
p.format(errTail)
859831
return
860832
}
861833

862-
var (
863-
padding string
864-
arrowWidth int
865-
)
866-
if p.level > 0 {
867-
const arrow = "┗━ "
868-
arrowWidth = utf8.RuneCount([]byte(arrow))
869-
padding = strings.Repeat(" ", arrowWidth*p.level)
870-
_, _ = fmt.Fprintf(p.w, "%v%v", padding, arrowStyle.Render(arrow))
871-
}
872-
873-
if underErr != nil {
874-
header := strings.TrimSuffix(err.Error(), ": "+underErr.Error())
875-
_, _ = fmt.Fprintf(p.w, "%s\n", p.prefixLines(len(padding)+arrowWidth, header))
876-
p.level++
877-
p.format(underErr)
878-
return
834+
var headErr string
835+
if errTail != nil {
836+
headErr = strings.TrimSuffix(err.Error(), ": "+errTail.Error())
837+
} else {
838+
headErr = err.Error()
879839
}
880840

881-
{
882-
style := lipgloss.NewStyle().Foreground(lipgloss.Color("#D16644")).Background(lipgloss.Color("#000000")).Bold(false)
883-
// This is the last error in a tree.
884-
p.wrappedPrintf(
885-
"%s\n",
886-
p.prefixLines(
887-
len(padding)+arrowWidth,
888-
fmt.Sprintf(
889-
"%s%s%s",
890-
lipgloss.NewStyle().Inherit(style).Underline(true).Render("ERROR"),
891-
lipgloss.NewStyle().Inherit(style).Foreground(arrowStyle.GetForeground()).Render(" ► "),
892-
style.Render(err.Error()),
893-
),
894-
),
895-
)
841+
var msg string
842+
var sdkError *codersdk.Error
843+
if errors.As(err, &sdkError) {
844+
// We don't want to repeat the same error message twice, so we
845+
// only show the SDK error on the top of the stack.
846+
msg = sdkError.Message
847+
if sdkError.Helper != "" {
848+
msg = msg + "\n" + sdkError.Helper
849+
}
850+
// The SDK error is usually good enough, and we don't want to overwhelm
851+
// the user with output.
852+
errTail = nil
853+
} else {
854+
msg = headErr
896855
}
897-
}
898856

899-
func (p *prettyErrorFormatter) wrappedPrintf(format string, a ...interface{}) {
900-
s := lipgloss.NewStyle().Width(ttyWidth()).Render(
901-
fmt.Sprintf(format, a...),
857+
headStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("#D16644"))
858+
p.printf(
859+
headStyle,
860+
"%s",
861+
msg,
902862
)
903863

904-
// Not sure why, but lipgloss is adding extra spaces we need to remove.
905-
excessSpaceRe := regexp.MustCompile(`[[:blank:]]*\n[[:blank:]]*$`)
906-
s = excessSpaceRe.ReplaceAllString(s, "\n")
864+
tailStyle := headStyle.Copy().Foreground(lipgloss.Color("#969696"))
865+
866+
if errTail != nil {
867+
p.printf(headStyle, ": ")
868+
// Grey out the less important, deep errors.
869+
p.printf(tailStyle, "%s", errTail.Error())
870+
}
871+
p.printf(tailStyle, "\n")
872+
}
907873

874+
func (p *prettyErrorFormatter) printf(style lipgloss.Style, format string, a ...interface{}) {
875+
s := style.Render(fmt.Sprintf(format, a...))
908876
_, _ = p.w.Write(
909877
[]byte(
910878
s,

0 commit comments

Comments
 (0)