highlight

package
v0.0.0-...-51b5d32 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 25, 2026 License: MIT Imports: 10 Imported by: 0

README

highlight

Go Reference

A Go syntax highlighter for go-mx. It tokenizes Go source with the standard-library go/scanner (no third-party dependencies) and renders it two ways from the same tokens:

  • Highlighted HTML, built through mx/html components — every meaningful token becomes a <span class="hl-…">.
  • Generated Go source that uses the html package to build that same markup — a code generator, not an echo of the input.

Colors live in a separate Theme that emits CSS, so the same HTML works with any theme.

The package depends only on the root mx package and the html element helpers; it does not depend on shadcn, so it can be used on its own or dropped into any go-mx markup, including the shadcn UI.

For why it is built this way (two backends, go/scanner, byte-faithful round-trip, trade-offs), see DESIGN.md.

Tutorial: highlight Go on a web page

This walks from nothing to a styled HTML page you open in a browser. You need a Go module that can import go-mx.

Step 1: write the program

Create main.go. It highlights a snippet, puts the theme's CSS in the page <head>, and writes the whole page to stdout:

package main

import (
	"context"
	"fmt"
	"os"

	"github.com/ungerik/go-mx"
	"github.com/ungerik/go-mx/highlight"
	"github.com/ungerik/go-mx/html"
)

func main() {
	src := `package main

import "fmt"

func main() {
	fmt.Println("hello, highlight")
}
`
	page := html.HTML(
		html.Head(highlight.LightTheme.StyleElement("")),
		html.Body(highlight.Component(src)),
	)

	fmt.Println("<!DOCTYPE html>")
	if err := page.Render(context.Background(), mx.NewCheckedWriter(os.Stdout)); err != nil {
		panic(err)
	}
}

Three calls do the work: highlight.Component(src) builds the <pre><code> tree, highlight.LightTheme.StyleElement("") builds the <style> that colors it, and mx.NewCheckedWriter renders the tree to bytes (it is non-indenting, so the code layout inside <pre> survives).

Step 2: run it into a file
go run . > highlighted.html

highlighted.html is now a complete page: <!DOCTYPE html>, a <head> with pre.hl { … } plus one .hl-keyword { … } rule per token class, and a <body> whose <pre> holds spans like <span class="hl-keyword">func</span>.

Step 3: open it
open highlighted.html      # macOS; use xdg-open on Linux

You see the snippet with red keywords, purple function names, and a gray italic comment on a light background. Switch the look by swapping LightTheme for DarkTheme in both the StyleElement call and (if you want) nothing else: the markup is theme-independent, so only the <style> changes.

What you built

A self-contained highlighter page with no client-side JavaScript and no third-party dependencies. From here, Component/Inline/HTML cover the markup and GoSource emits the go-mx code that builds it; the Usage section below has each one.

Usage

import "github.com/ungerik/go-mx/highlight"

const src = `func main() { fmt.Println("hi") }`
Highlighted HTML
block := highlight.Component(src) // *mx.Element: <pre class="hl"><code>…</code></pre>
inline := highlight.Inline(src)   // *mx.Element: <code class="hl">… (no <pre>)
s, err := highlight.HTML(src)     // render directly to an HTML string

Component renders to:

<pre class="hl"><code><span class="hl-keyword">func</span> <span class="hl-function">main</span>() { fmt.<span class="hl-function">Println</span>(<span class="hl-string">&quot;hi&quot;</span>) }</code></pre>

Render with a non-indenting writer (the default mx.NewCheckedWriter); an indenting writer would inject whitespace inside <pre> and corrupt the layout.

Generated go-mx source
code := highlight.GoSource(src)

returns gofmt-formatted Go source that reproduces the markup above:

html.Pre(html.Class("hl"),
	html.Code(
		html.Span(html.Class("hl-keyword"), "func"),
		" ",
		html.Span(html.Class("hl-function"), "main"),
		"() { fmt.",
		html.Span(html.Class("hl-function"), "Println"),
		"(",
		html.Span(html.Class("hl-string"), "\"hi\""),
		") }",
	),
)
CSS
css := highlight.LightTheme.CSS("")          // stylesheet string
style := highlight.DarkTheme.StyleElement("") // *mx.Element: <style>…</style>

Built-in themes: LightTheme and DarkTheme (GitHub-like palettes). Pass "" to match the default hl- class prefix.

Customizing

h := &highlight.Highlighter{
	Prefix:      "syn-",                                      // class prefix; block class becomes "syn"
	Highlighted: map[highlight.TokenClass]bool{               // which classes get a <span>
		highlight.ClassKeyword:  true,
		highlight.ClassString:   true,
		highlight.ClassOperator: true, // off by default
	},
}
out := h.Component(src)

By default the highlighted classes are keyword, type, function, builtin, constant, string, number and comment; operators, punctuation and plain identifiers render as text to keep the markup small.

Token classes

keyword, type, function, builtin, constant, string, number, comment, operator, punctuation, ident. Each maps to a Style in a Theme and to a CSS class <prefix><class> (e.g. hl-keyword).

Demo

go run ./cmd/highlight-demo
# browse to http://localhost:8080  (add ?theme=dark to switch)

The demo highlights a sample file, shows the generated GoSource output (itself highlighted) next to it, and injects the theme CSS into the page head.

Design

DESIGN.md explains the why: tokenize-once with two backends, the go/scanner choice, the byte-faithful round-trip that preserves <pre> layout, why classification is lexical, and the trade-offs behind each call.

Documentation

Overview

Package highlight turns Go source code into syntax-highlighted output built from go-mx components.

It is independent of the shadcn and other higher-level packages: it depends only on the root mx package and the html element helpers, so it can be used on its own or composed into any go-mx markup, including the shadcn UI.

Highlighting works in two steps, the way an editor does:

  1. TokenizeGo splits source into a flat list of [Token]s, each tagged with a semantic TokenClass (keyword, string, comment, ...). It uses the standard library go/scanner, so it has no third-party dependencies and is lenient about syntactically invalid input. The concatenation of all token texts reproduces the input byte-for-byte.

  2. A Highlighter turns those tokens into output. Two backends share the same tokens and configuration. Highlighter.Component and Highlighter.HTML emit the highlighted markup, built through mx/html components: every highlighted token becomes a <span class="hl-CLASS"> and the rest render as plain escaped text. Highlighter.GoSource instead emits Go source code that, using the html package, builds that same markup; it is a generator, not an echo of the input, so feeding it "func main() {}" returns a tree of html.Pre(...) calls.

Colors live in a separate Theme that emits a CSS stylesheet, so the same HTML works with any theme. Use LightTheme or DarkTheme, or build your own.

import "github.com/ungerik/go-mx/highlight"

block := highlight.Component(src)             // *mx.Element: <pre><code>…</code></pre>
code  := highlight.GoSource(src)              // string: html.Pre(...) Go source
style := highlight.LightTheme.StyleElement("") // <style>…</style>

Render the markup with a non-indenting writer (the default mx.NewCheckedWriter); an indenting writer would inject whitespace between the spans and corrupt the code layout inside <pre>.

Index

Constants

View Source
const DefaultPrefix = "hl-"

DefaultPrefix is prepended to every token CSS class name. Trimmed of its trailing "-" it also names the <pre> block ("hl").

Variables

View Source
var DarkTheme = Theme{
	Name:       "dark",
	Background: "#0d1117",
	Foreground: "#c9d1d9",
	Styles: map[TokenClass]Style{
		ClassKeyword:     {Color: "#ff7b72"},
		ClassType:        {Color: "#d2a8ff"},
		ClassFunction:    {Color: "#d2a8ff"},
		ClassBuiltin:     {Color: "#ffa657"},
		ClassConstant:    {Color: "#79c0ff"},
		ClassString:      {Color: "#a5d6ff"},
		ClassNumber:      {Color: "#79c0ff"},
		ClassComment:     {Color: "#8b949e", Italic: true},
		ClassOperator:    {Color: "#ff7b72"},
		ClassPunctuation: {Color: "#c9d1d9"},
		ClassIdent:       {Color: "#c9d1d9"},
	},
}

DarkTheme is a dark, GitHub-like color scheme.

View Source
var Default = &Highlighter{}

Default is the zero-value Highlighter used by the package-level functions.

View Source
var DefaultHighlighted = map[TokenClass]bool{
	ClassKeyword:  true,
	ClassType:     true,
	ClassFunction: true,
	ClassBuiltin:  true,
	ClassConstant: true,
	ClassString:   true,
	ClassNumber:   true,
	ClassComment:  true,
}

DefaultHighlighted is the set of token classes wrapped in a <span> by a zero-value Highlighter. Operators, punctuation and plain identifiers render as text, which keeps the markup small and matches how editors like GitHub's highlight Go.

View Source
var LightTheme = Theme{
	Name:       "light",
	Background: "#f6f8fa",
	Foreground: "#24292e",
	Styles: map[TokenClass]Style{
		ClassKeyword:     {Color: "#d73a49"},
		ClassType:        {Color: "#6f42c1"},
		ClassFunction:    {Color: "#6f42c1"},
		ClassBuiltin:     {Color: "#e36209"},
		ClassConstant:    {Color: "#005cc5"},
		ClassString:      {Color: "#032f62"},
		ClassNumber:      {Color: "#005cc5"},
		ClassComment:     {Color: "#6a737d", Italic: true},
		ClassOperator:    {Color: "#d73a49"},
		ClassPunctuation: {Color: "#24292e"},
		ClassIdent:       {Color: "#24292e"},
	},
}

LightTheme is a light, GitHub-like color scheme.

Functions

func Component

func Component(src string) *mx.Element

Component highlights Go source with the Default highlighter. See Highlighter.Component.

func GoSource

func GoSource(src string) string

GoSource highlights Go source with the Default highlighter. See Highlighter.GoSource.

func HTML

func HTML(src string) (string, error)

HTML highlights Go source with the Default highlighter. See Highlighter.HTML.

func Inline

func Inline(src string) *mx.Element

Inline highlights Go source with the Default highlighter. See Highlighter.Inline.

Types

type Highlighter

type Highlighter struct {
	// Prefix is prepended to every token CSS class name. It conventionally
	// ends with "-". An empty Prefix means [DefaultPrefix].
	Prefix string
	// Highlighted selects which token classes are wrapped in a <span>; every
	// other class renders as plain text. A nil map means [DefaultHighlighted].
	Highlighted map[TokenClass]bool
}

Highlighter holds the rendering configuration shared by both output backends. The zero value is ready to use and is exposed as Default.

func (*Highlighter) BlockClass

func (h *Highlighter) BlockClass() string

BlockClass is the class name put on the <pre> block, derived from the prefix by trimming a trailing "-" (so DefaultPrefix "hl-" yields "hl").

func (*Highlighter) Component

func (h *Highlighter) Component(src string) *mx.Element

Component highlights Go source and returns it as a <pre class="hl"><code>…</code></pre> block element.

func (*Highlighter) Components

func (h *Highlighter) Components(tokens []Token) mx.Components

Components turns tokens into a sequence of mx components: a <span class="PREFIX+CLASS"> for every highlighted token and plain escaped text for everything else. Use it to place highlighted code inside a custom wrapper; Highlighter.Component and Highlighter.Inline wrap it for you.

func (*Highlighter) GoSource

func (h *Highlighter) GoSource(src string) string

GoSource highlights Go source and returns, instead of HTML, the Go source code that builds that highlighted markup with the html package. It is a generator: the returned code is not the input echoed back but a tree of html.Pre / html.Code / html.Span calls.

For the input

func main() {}

it returns roughly

html.Pre(html.Class("hl"),
	html.Code(
		html.Span(html.Class("hl-keyword"), "func"),
		" ",
		html.Span(html.Class("hl-function"), "main"),
		html.Span(html.Class("hl-punctuation"), "()"),
		" ",
		html.Span(html.Class("hl-punctuation"), "{}"),
	),
)

The result is gofmt-formatted. If the generated expression somehow fails to format, the unformatted but valid expression is returned instead.

func (*Highlighter) HTML

func (h *Highlighter) HTML(src string) (string, error)

HTML highlights Go source and renders it directly to an HTML string using a non-indenting mx.CheckedWriter, so the code layout inside <pre> is preserved exactly.

func (*Highlighter) Inline

func (h *Highlighter) Inline(src string) *mx.Element

Inline highlights Go source for inline use and returns a <code class="hl"> element without the <pre> wrapper.

type Style

type Style struct {
	Color  string // any CSS color, "" to inherit
	Bold   bool
	Italic bool
}

Style is the appearance of one TokenClass in a Theme.

type Theme

type Theme struct {
	Name       string // human-readable theme name
	Background string // <pre> background-color, "" to inherit
	Foreground string // <pre> base text color, "" to inherit
	Styles     map[TokenClass]Style
}

Theme maps token classes to colors and provides the CSS to render them. The HTML produced by a Highlighter is theme-independent, so the same markup can be styled by any theme that uses the same class prefix.

func (Theme) CSS

func (t Theme) CSS(prefix string) string

CSS renders the theme as a CSS stylesheet for the given class prefix (pass DefaultPrefix or "" to match the default Highlighter). It emits one rule for the <pre> block and one rule per styled token class, in deterministic order.

func (Theme) StyleElement

func (t Theme) StyleElement(prefix string) *mx.Element

StyleElement returns the theme's CSS wrapped in a <style> element, ready to place in a document <head>. It uses the given prefix; pass DefaultPrefix or "" to match the default Highlighter.

type Token

type Token struct {
	Class TokenClass
	Text  string
}

Token is a single classified slice of the source. The concatenation of the Text of every token returned by TokenizeGo equals the original source.

func TokenizeGo

func TokenizeGo(src string) []Token

TokenizeGo splits Go source into classified [Token]s. It never returns an error: invalid input is scanned as far as possible and any unrecognized bytes are emitted as plain text, so the result always reproduces src exactly when the token texts are concatenated.

type TokenClass

type TokenClass string

TokenClass is the semantic category of a token. It is also used, with the Highlighter prefix, as the CSS class name of a highlighted token's <span>.

const (
	// ClassPlain is the zero value: text that is not highlighted (whitespace
	// and, by default, identifiers, operators and punctuation).
	ClassPlain TokenClass = ""

	// ClassKeyword is a Go keyword such as if, for, func or package.
	ClassKeyword TokenClass = "keyword"
	// ClassType is a predeclared type such as int, string or error.
	ClassType TokenClass = "type"
	// ClassFunction is a called or declared function/method name.
	ClassFunction TokenClass = "function"
	// ClassBuiltin is a predeclared function such as make, len or append.
	ClassBuiltin TokenClass = "builtin"
	// ClassConstant is a predeclared value such as true, false, nil or iota.
	ClassConstant TokenClass = "constant"
	// ClassString is a string or rune literal.
	ClassString TokenClass = "string"
	// ClassNumber is an integer, float or imaginary literal.
	ClassNumber TokenClass = "number"
	// ClassComment is a line or block comment.
	ClassComment TokenClass = "comment"
	// ClassOperator is an operator such as +, :=, == or <-.
	ClassOperator TokenClass = "operator"
	// ClassPunctuation is a delimiter such as a parenthesis, brace, comma, dot or colon.
	ClassPunctuation TokenClass = "punctuation"
	// ClassIdent is any other identifier.
	ClassIdent TokenClass = "ident"
)

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL