Skip to content

Commit 11ae052

Browse files
committed
styles: case-insensitive style registration and lookup
Normalize style names to lowercase during registration and lookup to provide case-insensitive access across the library, CLI, and web interface. Key changes: - Normalize keys to lowercase in styles.Register() and registry init(). - Convert input names to lowercase in styles.Get(). - Update cmd/chroma and cmd/chromad to use these normalized keys. - Add regression tests for library and CLI case-insensitivity. - Update CI workflow to include tests for the cmd/chroma module. - Update README.md to document case-insensitive style names. Closes #1222
1 parent dccba78 commit 11ae052

File tree

9 files changed

+96
-16
lines changed

9 files changed

+96
-16
lines changed

.github/workflows/ci.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,11 @@ jobs:
1111
steps:
1212
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6
1313
- uses: cashapp/activate-hermit@e49f5cb4dd64ff0b0b659d1d8df499595451155a # v1
14-
- run: go test ./...
14+
- run: |
15+
go test ./...
16+
pushd cmd/chroma
17+
go test ./...
18+
popd
1519
lint:
1620
name: Lint
1721
runs-on: ubuntu-latest

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -226,11 +226,11 @@ formatter outputs raw tokens. The latter is useful for debugging lexers.
226226
### Styles
227227

228228
Chroma styles are defined in XML. The style entries use the
229-
[same syntax](http://pygments.org/docs/styles/) as Pygments.
230-
231-
All Pygments styles have been converted to Chroma using the `_tools/style.py`
229+
[same syntax](http://pygments.org/docs/styles/) as Pygments. All Pygments styles have been converted to Chroma using the `_tools/style.py`
232230
script.
233231

232+
Style names are case-insensitive. For example, `monokai` and `Monokai` are treated as the same style.
233+
234234
When you work with one of [Chroma's styles](https://github.com/alecthomas/chroma/tree/master/styles),
235235
know that the `Background` token type provides the default style for tokens. It does so
236236
by defining a foreground color and background color.

cmd/chroma/go.mod

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,16 @@ toolchain go1.25.6
77
replace github.com/alecthomas/chroma/v2 => ../../
88

99
require (
10+
github.com/alecthomas/assert/v2 v2.11.0
1011
github.com/alecthomas/chroma/v2 v2.23.1
1112
github.com/alecthomas/kong v1.13.0
1213
github.com/mattn/go-colorable v0.1.14
1314
github.com/mattn/go-isatty v0.0.20
1415
)
1516

1617
require (
18+
github.com/alecthomas/repr v0.5.2 // indirect
1719
github.com/dlclark/regexp2 v1.11.5 // indirect
20+
github.com/hexops/gotextdiff v1.0.3 // indirect
1821
golang.org/x/sys v0.29.0 // indirect
1922
)

cmd/chroma/go.sum

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
11
github.com/alecthomas/assert/v2 v2.11.0 h1:2Q9r3ki8+JYXvGsDyBXwH3LcJ+WK5D0gc5E8vS6K3D0=
22
github.com/alecthomas/assert/v2 v2.11.0/go.mod h1:Bze95FyfUr7x34QZrjL+XP+0qgp/zg8yS+TtBj1WA3k=
3-
github.com/alecthomas/kong v1.12.1 h1:iq6aMJDcFYP9uFrLdsiZQ2ZMmcshduyGv4Pek0MQPW0=
4-
github.com/alecthomas/kong v1.12.1/go.mod h1:p2vqieVMeTAnaC83txKtXe8FLke2X07aruPWXyMPQrU=
53
github.com/alecthomas/kong v1.13.0 h1:5e/7XC3ugvhP1DQBmTS+WuHtCbcv44hsohMgcvVxSrA=
64
github.com/alecthomas/kong v1.13.0/go.mod h1:wrlbXem1CWqUV5Vbmss5ISYhsVPkBb1Yo7YKJghju2I=
7-
github.com/alecthomas/repr v0.5.1 h1:E3G4t2QbHTSNpPKBgMTln5KLkZHLOcU7r37J4pXBuIg=
8-
github.com/alecthomas/repr v0.5.1/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
5+
github.com/alecthomas/repr v0.5.2 h1:SU73FTI9D1P5UNtvseffFSGmdNci/O6RsqzeXJtP0Qs=
6+
github.com/alecthomas/repr v0.5.2/go.mod h1:Fr0507jx4eOXV7AlPV6AVZLYrLIuIeSOWtW57eE/O/4=
97
github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ=
108
github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
119
github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM=

cmd/chroma/main.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -267,8 +267,7 @@ func main() {
267267
}
268268

269269
func selectStyle() (*chroma.Style, error) {
270-
style, ok := styles.Registry[cli.Style]
271-
if ok {
270+
if style, ok := styles.Registry[strings.ToLower(cli.Style)]; ok {
272271
return style, nil
273272
}
274273
r, err := os.Open(cli.Style)
@@ -434,7 +433,7 @@ func dumpXMLLexerDefinitions(dir string) error {
434433
fmt.Fprintf(os.Stderr, "warning: %s already exists\n", filename)
435434
continue
436435
}
437-
err = os.WriteFile(filename, data, 0600)
436+
err = os.WriteFile(filename, data, 0o600)
438437
if err != nil {
439438
return err
440439
}

cmd/chroma/main_test.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package main
2+
3+
import (
4+
"testing"
5+
6+
"github.com/alecthomas/assert/v2"
7+
)
8+
9+
func TestSelectStyle(t *testing.T) {
10+
// Restore global state after test.
11+
original := cli.Style
12+
defer func() { cli.Style = original }()
13+
14+
names := []string{"monokai", "Monokai", "MONOKAI"}
15+
16+
for _, name := range names {
17+
t.Run(name, func(t *testing.T) {
18+
cli.Style = name
19+
style, err := selectStyle()
20+
21+
assert.NoError(t, err)
22+
assert.Equal(t, "monokai", style.Name)
23+
})
24+
}
25+
}
26+
27+
func TestSelectStyleUnknown(t *testing.T) {
28+
// Unknown styles fall back to file lookups and should error if not found.
29+
cli.Style = "non-existent"
30+
_, err := selectStyle()
31+
assert.Error(t, err)
32+
}

cmd/chromad/main.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -164,8 +164,8 @@ func newContext(r *http.Request) context {
164164
ctx.Languages = append(ctx.Languages, lexer.Config().Name)
165165
}
166166
sort.Strings(ctx.Languages)
167-
for _, style := range styles.Registry {
168-
ctx.Styles = append(ctx.Styles, style.Name)
167+
for name := range styles.Registry {
168+
ctx.Styles = append(ctx.Styles, name)
169169
}
170170
sort.Strings(ctx.Styles)
171171
return ctx

styles/api.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"embed"
55
"io/fs"
66
"sort"
7+
"strings"
78

89
"github.com/alecthomas/chroma/v2"
910
)
@@ -31,7 +32,7 @@ var Registry = func() map[string]*chroma.Style {
3132
if err != nil {
3233
panic(err)
3334
}
34-
registry[style.Name] = style
35+
registry[strings.ToLower(style.Name)] = style
3536
_ = r.Close()
3637
}
3738
return registry
@@ -42,7 +43,7 @@ var Fallback = Registry["swapoff"]
4243

4344
// Register a chroma.Style.
4445
func Register(style *chroma.Style) *chroma.Style {
45-
Registry[style.Name] = style
46+
Registry[strings.ToLower(style.Name)] = style
4647
return style
4748
}
4849

@@ -58,7 +59,7 @@ func Names() []string {
5859

5960
// Get named style, or Fallback.
6061
func Get(name string) *chroma.Style {
61-
if style, ok := Registry[name]; ok {
62+
if style, ok := Registry[strings.ToLower(name)]; ok {
6263
return style
6364
}
6465
return Fallback

styles/api_test.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package styles
2+
3+
import (
4+
"strings"
5+
"testing"
6+
7+
"github.com/alecthomas/assert/v2"
8+
"github.com/alecthomas/chroma/v2"
9+
)
10+
11+
func TestStyleRegistryCaseInsensitivity(t *testing.T) {
12+
// Verify that all keys in the Registry are lowercase.
13+
for name := range Registry {
14+
assert.Equal(t, strings.ToLower(name), name)
15+
}
16+
17+
// Verify that Get is case-insensitive.
18+
names := []string{"monokai", "Monokai", "MONOKAI"}
19+
20+
for _, name := range names {
21+
t.Run(name, func(t *testing.T) {
22+
style := Get(name)
23+
24+
assert.NotEqual(t, Fallback, style)
25+
assert.True(t, strings.EqualFold(style.Name, name))
26+
})
27+
}
28+
}
29+
30+
func TestGetUnknownStyleReturnsFallback(t *testing.T) {
31+
assert.Equal(t, Fallback, Get("non-existent-style"))
32+
}
33+
34+
func TestRegisterCaseInsensitivity(t *testing.T) {
35+
custom := chroma.MustNewStyle("CustomStyle", chroma.StyleEntries{
36+
chroma.Text: "#ffffff",
37+
})
38+
Register(custom)
39+
40+
assert.Equal(t, custom, Get("customstyle"))
41+
assert.Equal(t, custom, Get("CUSTOMSTYLE"))
42+
assert.Equal(t, custom, Get("CustomStyle"))
43+
}

0 commit comments

Comments
 (0)