diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c38e02ed..c3721dd0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -7,3 +7,11 @@ Please submit a pull request for minor changes and submit issues for major chang ## Testing When providing a new feature or bug fix, please provide tests that demonstrate the issue along with your fix. + +## Themes + +New styles need to be implemented in `styles/.go`, and then `go generate +./...` will create the JSON files from it. + +You can look up all references of another theme (e.g. Dracula), and add your +theme accordingly. diff --git a/examples/stdin-stdout-custom-styles/main.go b/examples/stdin-stdout-custom-styles/main.go new file mode 100644 index 00000000..7633cf39 --- /dev/null +++ b/examples/stdin-stdout-custom-styles/main.go @@ -0,0 +1,46 @@ +package main + +// A simple example that renders input through a pipe. +// +// Usage: +// echo "# Hello, world!" | go run main.go +// +// cat README.md | go run main.go +// +// go run main.go < README.md + +import ( + "fmt" + "io" + "os" + + "github.com/charmbracelet/glamour" +) + +const defaultWidth = 80 + +func main() { + // Read from stdin. + in, err := io.ReadAll(os.Stdin) + if err != nil { + fmt.Fprintf(os.Stderr, "Error reading stdin: %s\n", err) + } + + // Create a new renderer. + r, err := glamour.NewTermRenderer( + glamour.WithEnvironmentConfig(), + glamour.WithWordWrap(defaultWidth), + ) + if err != nil { + fmt.Fprintf(os.Stderr, "Error creating renderer: %s\n", err) + } + + // Render markdown. + md, err := r.RenderBytes(in) + if err != nil { + fmt.Fprintf(os.Stderr, "Error rendering markdown: %s\n", err) + } + + // Write markdown to stdout. + fmt.Fprintf(os.Stdout, "%s\n", md) +} diff --git a/glamour.go b/glamour.go index a38d88b6..6a8a1c38 100644 --- a/glamour.go +++ b/glamour.go @@ -16,17 +16,7 @@ import ( "golang.org/x/term" "github.com/charmbracelet/glamour/ansi" -) - -// Default styles. -const ( - AsciiStyle = "ascii" - AutoStyle = "auto" - DarkStyle = "dark" - DraculaStyle = "dracula" - LightStyle = "light" - NoTTYStyle = "notty" - PinkStyle = "pink" + styles "github.com/charmbracelet/glamour/styles" ) const ( @@ -138,7 +128,7 @@ func WithStandardStyle(style string) TermRendererOption { // WithAutoStyle sets a TermRenderer's styles with either the standard dark // or light style, depending on the terminal's background color at run-time. func WithAutoStyle() TermRendererOption { - return WithStandardStyle(AutoStyle) + return WithStandardStyle(styles.AutoStyle) } // WithEnvironmentConfig sets a TermRenderer's styles based on the @@ -253,24 +243,24 @@ func (tr *TermRenderer) RenderBytes(in []byte) ([]byte, error) { func getEnvironmentStyle() string { glamourStyle := os.Getenv("GLAMOUR_STYLE") if len(glamourStyle) == 0 { - glamourStyle = AutoStyle + glamourStyle = styles.AutoStyle } return glamourStyle } func getDefaultStyle(style string) (*ansi.StyleConfig, error) { - if style == AutoStyle { + if style == styles.AutoStyle { if !term.IsTerminal(int(os.Stdout.Fd())) { - return &NoTTYStyleConfig, nil + return &styles.NoTTYStyleConfig, nil } if termenv.HasDarkBackground() { - return &DarkStyleConfig, nil + return &styles.DarkStyleConfig, nil } - return &LightStyleConfig, nil + return &styles.LightStyleConfig, nil } - styles, ok := DefaultStyles[style] + styles, ok := styles.DefaultStyles[style] if !ok { return nil, fmt.Errorf("%s: style not found", style) } diff --git a/glamour_test.go b/glamour_test.go index 38979f65..dc152a72 100644 --- a/glamour_test.go +++ b/glamour_test.go @@ -8,6 +8,7 @@ import ( "strings" "testing" + styles "github.com/charmbracelet/glamour/styles" "github.com/charmbracelet/x/exp/golden" ) @@ -15,7 +16,7 @@ const markdown = "testdata/readme.markdown.in" func TestTermRendererWriter(t *testing.T) { r, err := NewTermRenderer( - WithStandardStyle(DarkStyle), + WithStandardStyle(styles.DarkStyle), ) if err != nil { t.Fatal(err) @@ -116,7 +117,7 @@ func TestStyles(t *testing.T) { } _, err = NewTermRenderer( - WithStandardStyle(AutoStyle), + WithStandardStyle(styles.AutoStyle), ) if err != nil { t.Fatal(err) @@ -141,8 +142,8 @@ func TestCustomStyle(t *testing.T) { expected string }{ {name: "style exists", stylePath: "testdata/custom.style", err: nil, expected: "testdata/custom.style"}, - {name: "style doesn't exist", stylePath: "testdata/notfound.style", err: os.ErrNotExist, expected: AutoStyle}, - {name: "style is empty", stylePath: "", err: nil, expected: AutoStyle}, + {name: "style doesn't exist", stylePath: "testdata/notfound.style", err: os.ErrNotExist, expected: styles.AutoStyle}, + {name: "style is empty", stylePath: "", err: nil, expected: styles.AutoStyle}, } for _, tc := range tests { @@ -186,7 +187,7 @@ func TestRenderHelpers(t *testing.T) { func TestCapitalization(t *testing.T) { p := true - style := DarkStyleConfig + style := styles.DarkStyleConfig style.H1.Upper = &p style.H2.Title = &p style.H3.Lower = &p @@ -209,7 +210,7 @@ func TestCapitalization(t *testing.T) { func FuzzData(f *testing.F) { f.Fuzz(func(t *testing.T, data []byte) { func() int { - _, err := RenderBytes(data, DarkStyle) + _, err := RenderBytes(data, styles.DarkStyle) if err != nil { return 0 } diff --git a/internal/generate-style-json/main.go b/internal/generate-style-json/main.go index 31dd0dc5..ee2adb81 100644 --- a/internal/generate-style-json/main.go +++ b/internal/generate-style-json/main.go @@ -6,8 +6,8 @@ import ( "os" "path/filepath" - "github.com/charmbracelet/glamour" "github.com/charmbracelet/glamour/ansi" + styles "github.com/charmbracelet/glamour/styles" ) func writeStyleJSON(filename string, styleConfig *ansi.StyleConfig) error { @@ -22,7 +22,7 @@ func writeStyleJSON(filename string, styleConfig *ansi.StyleConfig) error { } func run() error { - for style, styleConfig := range glamour.DefaultStyles { + for style, styleConfig := range styles.DefaultStyles { if err := writeStyleJSON(filepath.Join("styles", style+".json"), styleConfig); err != nil { return err } diff --git a/dracula.go b/styles/dracula.go similarity index 98% rename from dracula.go rename to styles/dracula.go index cf0a7dfc..12eaf9d4 100644 --- a/dracula.go +++ b/styles/dracula.go @@ -1,7 +1,8 @@ -package glamour +package styles import "github.com/charmbracelet/glamour/ansi" +// DraculaStyleConfig is the dracula style. var DraculaStyleConfig = ansi.StyleConfig{ Document: ansi.StyleBlock{ StylePrimitive: ansi.StylePrimitive{ diff --git a/styles/gallery/README.md b/styles/gallery/README.md index e61d7b77..88c2c20a 100644 --- a/styles/gallery/README.md +++ b/styles/gallery/README.md @@ -2,18 +2,22 @@ ## Dark -![Glamour Dark Style](dark.png) +![Glamour Dark Style](./dark.png) ## Light -![Glamour Light Style](light.png) +![Glamour Light Style](./light.png) ## NoTTY Pronounced _naughty_. -![Glamour NoTTY Style](notty.png) +![Glamour NoTTY Style](./notty.png) ## Dracula -![Dracula Style](dracula.png) +![Dracula Style](./dracula.png) + +## Tokyo Night + +![Tokyo Night Style](./tokyo-night.png) diff --git a/styles/gallery/tokyo-night.png b/styles/gallery/tokyo-night.png new file mode 100644 index 00000000..7f63dea8 Binary files /dev/null and b/styles/gallery/tokyo-night.png differ diff --git a/styles/notty.json b/styles/notty.json index b49ec1de..be13b917 100644 --- a/styles/notty.json +++ b/styles/notty.json @@ -6,7 +6,7 @@ }, "block_quote": { "indent": 1, - "indent_token": "│ " + "indent_token": "| " }, "paragraph": {}, "list": { @@ -56,7 +56,7 @@ "block_prefix": ". " }, "task": { - "ticked": "[✓] ", + "ticked": "[x] ", "unticked": "[ ] " }, "link": {}, @@ -72,15 +72,11 @@ "code_block": { "margin": 2 }, - "table": { - "center_separator": "┼", - "column_separator": "│", - "row_separator": "─" - }, + "table": {}, "definition_list": {}, "definition_term": {}, "definition_description": { - "block_prefix": "\n🠶 " + "block_prefix": "\n* " }, "html_block": {}, "html_span": {} diff --git a/styles.go b/styles/styles.go similarity index 96% rename from styles.go rename to styles/styles.go index 1d735ebe..422c7cfe 100644 --- a/styles.go +++ b/styles/styles.go @@ -1,6 +1,6 @@ -package glamour +package styles -//go:generate go run ./internal/generate-style-json +//go:generate go run ../internal/generate-style-json import ( "github.com/charmbracelet/glamour/ansi" @@ -12,6 +12,18 @@ const ( defaultMargin = 2 ) +// Default styles. +const ( + AsciiStyle = "ascii" + AutoStyle = "auto" + DarkStyle = "dark" + DraculaStyle = "dracula" + TokyoNightStyle = "tokyo-night" + LightStyle = "light" + NoTTYStyle = "notty" + PinkStyle = "pink" +) + var ( // ASCIIStyleConfig uses only ASCII characters. ASCIIStyleConfig = ansi.StyleConfig{ @@ -647,12 +659,15 @@ var ( // DefaultStyles are the default styles. DefaultStyles = map[string]*ansi.StyleConfig{ - AsciiStyle: &ASCIIStyleConfig, - DarkStyle: &DarkStyleConfig, - DraculaStyle: &DraculaStyleConfig, - LightStyle: &LightStyleConfig, - NoTTYStyle: &NoTTYStyleConfig, - PinkStyle: &PinkStyleConfig, + AsciiStyle: &ASCIIStyleConfig, + DarkStyle: &DarkStyleConfig, + LightStyle: &LightStyleConfig, + NoTTYStyle: &NoTTYStyleConfig, + PinkStyle: &PinkStyleConfig, + + // Popular themes + DraculaStyle: &DraculaStyleConfig, + TokyoNightStyle: &TokyoNightStyleConfig, } ) diff --git a/styles/tokyo-night.go b/styles/tokyo-night.go new file mode 100644 index 00000000..5cb966e2 --- /dev/null +++ b/styles/tokyo-night.go @@ -0,0 +1,209 @@ +package styles + +import "github.com/charmbracelet/glamour/ansi" + +// TokyoNightStyle is the tokyo night style. +var TokyoNightStyleConfig = ansi.StyleConfig{ + Document: ansi.StyleBlock{ + StylePrimitive: ansi.StylePrimitive{ + BlockPrefix: "\n", + BlockSuffix: "\n", + Color: stringPtr("#a9b1d6"), + }, + Margin: uintPtr(defaultMargin), + }, + BlockQuote: ansi.StyleBlock{ + StylePrimitive: ansi.StylePrimitive{}, + Indent: uintPtr(1), + IndentToken: stringPtr("│ "), + }, + List: ansi.StyleList{ + StyleBlock: ansi.StyleBlock{ + StylePrimitive: ansi.StylePrimitive{ + Color: stringPtr("#a9b1d6"), + }, + }, + LevelIndent: defaultListIndent, + }, + Heading: ansi.StyleBlock{ + StylePrimitive: ansi.StylePrimitive{ + BlockSuffix: "\n", + Color: stringPtr("#bb9af7"), + Bold: boolPtr(true), + }, + }, + H1: ansi.StyleBlock{ + StylePrimitive: ansi.StylePrimitive{ + Prefix: "# ", + Bold: boolPtr(true), + }, + }, + H2: ansi.StyleBlock{ + StylePrimitive: ansi.StylePrimitive{ + Prefix: "## ", + }, + }, + H3: ansi.StyleBlock{ + StylePrimitive: ansi.StylePrimitive{ + Prefix: "### ", + }, + }, + H4: ansi.StyleBlock{ + StylePrimitive: ansi.StylePrimitive{ + Prefix: "#### ", + }, + }, + H5: ansi.StyleBlock{ + StylePrimitive: ansi.StylePrimitive{ + Prefix: "##### ", + }, + }, + H6: ansi.StyleBlock{ + StylePrimitive: ansi.StylePrimitive{ + Prefix: "###### ", + }, + }, + Strikethrough: ansi.StylePrimitive{ + CrossedOut: boolPtr(true), + }, + Emph: ansi.StylePrimitive{ + Italic: boolPtr(true), + }, + Strong: ansi.StylePrimitive{ + Bold: boolPtr(true), + }, + HorizontalRule: ansi.StylePrimitive{ + Color: stringPtr("#565f89"), + Format: "\n--------\n", + }, + Item: ansi.StylePrimitive{ + BlockPrefix: "• ", + }, + Enumeration: ansi.StylePrimitive{ + BlockPrefix: ". ", + Color: stringPtr("#7aa2f7"), + }, + Task: ansi.StyleTask{ + StylePrimitive: ansi.StylePrimitive{}, + Ticked: "[✓] ", + Unticked: "[ ] ", + }, + Link: ansi.StylePrimitive{ + Color: stringPtr("#7aa2f7"), + Underline: boolPtr(true), + }, + LinkText: ansi.StylePrimitive{ + Color: stringPtr("#2ac3de"), + }, + Image: ansi.StylePrimitive{ + Color: stringPtr("#7aa2f7"), + Underline: boolPtr(true), + }, + ImageText: ansi.StylePrimitive{ + Color: stringPtr("#2ac3de"), + Format: "Image: {{.text}} →", + }, + Code: ansi.StyleBlock{ + StylePrimitive: ansi.StylePrimitive{ + Color: stringPtr("#9ece6a"), + }, + }, + CodeBlock: ansi.StyleCodeBlock{ + StyleBlock: ansi.StyleBlock{ + StylePrimitive: ansi.StylePrimitive{ + Color: stringPtr("#ff9e64"), + }, + Margin: uintPtr(defaultMargin), + }, + Chroma: &ansi.Chroma{ + Text: ansi.StylePrimitive{ + Color: stringPtr("#a9b1d6"), + }, + Error: ansi.StylePrimitive{ + Color: stringPtr("#a9b1d6"), + BackgroundColor: stringPtr("#f7768e"), + }, + Comment: ansi.StylePrimitive{ + Color: stringPtr("#565f89"), + }, + CommentPreproc: ansi.StylePrimitive{ + Color: stringPtr("#2ac3de"), + }, + Keyword: ansi.StylePrimitive{ + Color: stringPtr("#2ac3de"), + }, + KeywordReserved: ansi.StylePrimitive{ + Color: stringPtr("#2ac3de"), + }, + KeywordNamespace: ansi.StylePrimitive{ + Color: stringPtr("#2ac3de"), + }, + KeywordType: ansi.StylePrimitive{ + Color: stringPtr("#7aa2f7"), + }, + Operator: ansi.StylePrimitive{ + Color: stringPtr("#2ac3de"), + }, + Punctuation: ansi.StylePrimitive{ + Color: stringPtr("#a9b1d6"), + }, + Name: ansi.StylePrimitive{ + Color: stringPtr("#7aa2f7"), + }, + NameConstant: ansi.StylePrimitive{ + Color: stringPtr("#bb9af7"), + }, + NameBuiltin: ansi.StylePrimitive{ + Color: stringPtr("#7aa2f7"), + }, + NameTag: ansi.StylePrimitive{ + Color: stringPtr("#2ac3de"), + }, + NameAttribute: ansi.StylePrimitive{ + Color: stringPtr("#9ece6a"), + }, + NameClass: ansi.StylePrimitive{ + Color: stringPtr("#7aa2f7"), + }, + NameDecorator: ansi.StylePrimitive{ + Color: stringPtr("#9ece6a"), + }, + NameFunction: ansi.StylePrimitive{ + Color: stringPtr("#9ece6a"), + }, + LiteralNumber: ansi.StylePrimitive{}, + LiteralString: ansi.StylePrimitive{ + Color: stringPtr("#e0af68"), + }, + LiteralStringEscape: ansi.StylePrimitive{ + Color: stringPtr("#2ac3de"), + }, + GenericDeleted: ansi.StylePrimitive{ + Color: stringPtr("#f7768e"), + }, + GenericEmph: ansi.StylePrimitive{ + Italic: boolPtr(true), + }, + GenericInserted: ansi.StylePrimitive{ + Color: stringPtr("#9ece6a"), + }, + GenericStrong: ansi.StylePrimitive{ + Bold: boolPtr(true), + }, + GenericSubheading: ansi.StylePrimitive{ + Color: stringPtr("#bb9af7"), + }, + Background: ansi.StylePrimitive{ + BackgroundColor: stringPtr("#1a1b26"), + }, + }, + }, + Table: ansi.StyleTable{ + StyleBlock: ansi.StyleBlock{ + StylePrimitive: ansi.StylePrimitive{}, + }, + }, + DefinitionDescription: ansi.StylePrimitive{ + BlockPrefix: "\n🠶 ", + }, +} diff --git a/styles/tokyo_night.json b/styles/tokyo_night.json index 82dd7755..8016b096 100644 --- a/styles/tokyo_night.json +++ b/styles/tokyo_night.json @@ -20,7 +20,8 @@ "bold": true }, "h1": { - "prefix": "# " + "prefix": "# ", + "bold": true }, "h2": { "prefix": "## " @@ -142,6 +143,7 @@ }, "name_other": {}, "literal": {}, + "literal_number": {}, "literal_date": {}, "literal_string": { "color": "#e0af68"