diff --git a/exec.go b/exec.go index 7e0f115..e543d62 100644 --- a/exec.go +++ b/exec.go @@ -16,6 +16,7 @@ const ( defaultHeight = 24 ) +//nolint:wrapcheck func executeCommand(ctx context.Context, args []string) ([]byte, error) { width, height, err := term.GetSize(os.Stdout.Fd()) if err != nil { diff --git a/main.go b/main.go index ed1c1fa..b0f7b12 100644 --- a/main.go +++ b/main.go @@ -3,7 +3,6 @@ package main import ( "bytes" "fmt" - "image/color" "io" "os" "strings" @@ -52,7 +51,6 @@ sequin -- some command to execute in, err = executeCommand(cmd.Context(), args) } if err != nil { - //nolint:wrapcheck return err } return process(w, in) @@ -62,56 +60,24 @@ sequin -- some command to execute return root } -//nolint:mnd func process(w *colorprofile.Writer, in []byte) error { - hasDarkBG := lipgloss.HasDarkBackground(os.Stdin, os.Stdout) - lightDark := func(light, dark string) color.Color { - return lipgloss.LightDark(hasDarkBG)(lipgloss.Color(light), lipgloss.Color(dark)) + var t theme + switch strings.ToLower(os.Getenv("SEQUIN_THEME")) { + case "ansi", "base16", "carlos", "secret_carlos", "matchy": + t = base16Theme(false) + default: + hasDarkBG := lipgloss.HasDarkBackground(os.Stdin, os.Stdout) + t = charmTheme(hasDarkBG) } - rawModeStyle := lipgloss.NewStyle() - rawKindStyle := lipgloss.NewStyle(). - Width(4). - Align(lipgloss.Right). - Bold(true). - MarginRight(1) - seqStyle := lipgloss.NewStyle(). - Foreground(lightDark("#917F8B", "#978692")) - separator := lipgloss.NewStyle(). - Foreground(lipgloss.Color("#978692")). - SetString(": ") - textStyle := lipgloss.NewStyle(). - Foreground(lightDark("#D9D9D9", "#D9D9D9")) - errStyle := lipgloss.NewStyle(). - Foreground(lightDark("#EC6A88", "#ff5f87")) - explanationStyle := lipgloss.NewStyle(). - Foreground(lightDark("#3C343A", "#D4CAD1")) - - kindColors := map[string]color.Color{ - "CSI": lightDark("#936EE5", "#8D58FF"), - "DCS": lightDark("#86C867", "#CEE88A"), - "OSC": lightDark("#43C7E0", "#1CD4F7"), - "APC": lightDark("#F58855", "#FF8383"), - "ESC": lipgloss.Color("#E46FDD"), - "Ctrl": lightDark("#4DBA94", "#4BD2A3"), - "Text": lightDark("#978692", "#6C6068"), - } - - kindStyle := func(kind string) lipgloss.Style { - if raw { - return rawModeStyle.Foreground(kindColors[kind]) - } - return rawKindStyle.Foreground(kindColors[kind]) - } - - textKindStyle := kindStyle("Text").SetString("Text") + t.IsRaw = raw seqPrint := func(kind string, seq []byte) { s := fmt.Sprintf("%q", seq) s = strings.TrimPrefix(s, `"`) s = strings.TrimSuffix(s, `"`) if raw { - _, _ = fmt.Fprint(w, kindStyle(kind).Render(s)) + _, _ = fmt.Fprint(w, t.kindStyle(kind).Render(s)) return } @@ -140,14 +106,14 @@ func process(w *colorprofile.Writer, in []byte) error { _, _ = fmt.Fprintf( w, "%s%s%s", - kindStyle(kind).Render(kind), - seqStyle.Render(s), - separator, + t.kindStyle(kind), + t.sequence.Render(s), + t.separator, ) switch kind { case "Ctrl": - _, _ = fmt.Fprintln(w, explanationStyle.Render(ctrlCodes[seq[0]])) + _, _ = fmt.Fprintln(w, t.explanation.Render(ctrlCodes[seq[0]])) case "": _, _ = fmt.Fprintf(w, "Unknown %q\n", seq) } @@ -158,9 +124,9 @@ func process(w *colorprofile.Writer, in []byte) error { return } if raw { - _, _ = fmt.Fprint(w, kindStyle("Text").Render(buf.String())) + _, _ = fmt.Fprint(w, t.kindStyle("Text").Render(buf.String())) } else { - _, _ = fmt.Fprintf(w, "%s%s\n", textKindStyle, textStyle.Render(buf.String())) + _, _ = fmt.Fprintf(w, "%s%s\n", t.kindStyle("text"), t.text.Render(buf.String())) } buf.Reset() @@ -173,15 +139,15 @@ func process(w *colorprofile.Writer, in []byte) error { handler, ok := reg[int(p.Cmd())] if !ok { - _, _ = fmt.Fprintln(w, errStyle.Render(errUnhandled.Error())) + _, _ = fmt.Fprintln(w, t.error.Render(errUnhandled.Error())) return } out, err := handler(p) if err != nil { - _, _ = fmt.Fprintln(w, errStyle.Render(err.Error())) + _, _ = fmt.Fprintln(w, t.error.Render(err.Error())) return } - _, _ = fmt.Fprintln(w, explanationStyle.Render(out)) + _, _ = fmt.Fprintln(w, t.explanation.Render(out)) } var state byte @@ -226,10 +192,10 @@ func process(w *colorprofile.Writer, in []byte) error { _, _ = fmt.Fprintf( w, "%s%s%s%s\n", - kindStyle("Ctrl").SetString("Ctrl"), - seqStyle.Render("ESC"), - separator, - explanationStyle.Render("Escape"), + t.kindStyle("Ctrl"), + t.sequence.Render("ESC"), + t.separator, + t.explanation.Render("Escape"), ) break } @@ -244,7 +210,7 @@ func process(w *colorprofile.Writer, in []byte) error { case width > 0: // Text - buf.WriteString(explanationStyle.Render(string(seq))) + buf.WriteString(t.explanation.Render(string(seq))) default: flushPrint() diff --git a/theme.go b/theme.go new file mode 100644 index 0000000..f78ffe9 --- /dev/null +++ b/theme.go @@ -0,0 +1,120 @@ +package main + +import ( + "image/color" + "strings" + + "github.com/charmbracelet/lipgloss/v2" +) + +type theme struct { + IsRaw bool + + raw lipgloss.Style + kind lipgloss.Style + sequence lipgloss.Style + separator lipgloss.Style + text lipgloss.Style + error lipgloss.Style + explanation lipgloss.Style + + kindColors struct { + apc, csi, ctrl, dcs, esc, osc, text color.Color + } +} + +func (t theme) kindStyle(kind string) lipgloss.Style { + kind = strings.ToLower(kind) + base := t.kind + if t.IsRaw { + base = t.raw + } + + s := map[string]lipgloss.Style{ + "apc": base.Foreground(t.kindColors.apc), + "csi": base.Foreground(t.kindColors.csi), + "ctrl": base.Foreground(t.kindColors.ctrl), + "dcs": base.Foreground(t.kindColors.dcs), + "esc": base.Foreground(t.kindColors.esc), + "osc": base.Foreground(t.kindColors.osc), + "text": base.Foreground(t.kindColors.text), + }[kind] + + if t.IsRaw { + return s + } + + switch kind { + case "csi": + return s.SetString("CSI") + case "dcs": + return s.SetString("DCS") + case "osc": + return s.SetString("OSC") + case "apc": + return s.SetString("APC") + case "esc": + return s.SetString("ESC") + case "ctrl": + return s.SetString("Ctrl") + case "text": + return s.SetString("Text") + default: + return s + } +} + +//nolint:mnd +func charmTheme(hasDarkBG bool) (t theme) { + lightDark := func(light, dark string) color.Color { + return lipgloss.LightDark(hasDarkBG)(lipgloss.Color(light), lipgloss.Color(dark)) + } + + t.raw = lipgloss.NewStyle() + t.kind = lipgloss.NewStyle(). + Width(4). + Align(lipgloss.Right). + Bold(true). + MarginRight(1) + t.sequence = lipgloss.NewStyle(). + Foreground(lightDark("#917F8B", "#978692")) + t.separator = lipgloss.NewStyle(). + Foreground(lipgloss.Color("#978692")). + SetString(": ") + t.text = lipgloss.NewStyle(). + Foreground(lightDark("#D9D9D9", "#D9D9D9")) + t.error = lipgloss.NewStyle(). + Foreground(lightDark("#EC6A88", "#ff5f87")) + t.explanation = lipgloss.NewStyle(). + Foreground(lightDark("#3C343A", "#D4CAD1")) + + t.kindColors.apc = lightDark("#F58855", "#FF8383") + t.kindColors.csi = lightDark("#936EE5", "#8D58FF") + t.kindColors.ctrl = lightDark("#4DBA94", "#4BD2A3") + t.kindColors.dcs = lightDark("#86C867", "#CEE88A") + t.kindColors.esc = lipgloss.Color("#E46FDD") + t.kindColors.osc = lightDark("#43C7E0", "#1CD4F7") + t.kindColors.text = lightDark("#978692", "#6C6068") + + return t +} + +func base16Theme(_ bool) theme { + t := charmTheme(false) + + t.sequence = t.sequence.Foreground(lipgloss.BrightBlack) + t.separator = t.separator.Foreground(lipgloss.BrightBlack) + t.text = t.text.Foreground(lipgloss.BrightBlack) + t.error = t.error.Foreground(lipgloss.BrightRed) + t.explanation = t.explanation.Foreground(lipgloss.White) + + t.kindColors.apc = lipgloss.Red + t.kindColors.csi = lipgloss.Blue + t.kindColors.ctrl = lipgloss.Green + t.kindColors.dcs = lipgloss.Yellow + t.kindColors.esc = lipgloss.Magenta + t.kindColors.osc = lipgloss.Cyan + t.kindColors.text = lipgloss.BrightBlack + + return t +}