Skip to content

Commit

Permalink
Put lines in a div & code lines in a span, Add WrapLongLines Option
Browse files Browse the repository at this point in the history
  • Loading branch information
CIAvash authored and alecthomas committed Nov 9, 2021
1 parent 7cefa29 commit 5ed5054
Show file tree
Hide file tree
Showing 4 changed files with 234 additions and 124 deletions.
73 changes: 59 additions & 14 deletions formatters/html/html.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,13 @@ func WithPreWrapper(wrapper PreWrapper) Option {
}
}

// WrapLongLines wraps long lines.
func WrapLongLines(b bool) Option {
return func(f *Formatter) {
f.wrapLongLines = b
}
}

// WithLineNumbers formats output with line numbers.
func WithLineNumbers(b bool) Option {
return func(f *Formatter) {
Expand Down Expand Up @@ -131,10 +138,18 @@ var (
}
defaultPreWrapper = preWrapper{
start: func(code bool, styleAttr string) string {
return fmt.Sprintf(`<pre tabindex="0"%s>`, styleAttr)
if code {
return fmt.Sprintf(`<pre tabindex="0"%s>`, styleAttr)
}

return fmt.Sprintf(`<div tabindex="0"%s>`, styleAttr)
},
end: func(code bool) string {
return "</pre>"
if code {
return `</pre>`
}

return `</div>`
},
}
)
Expand All @@ -147,6 +162,7 @@ type Formatter struct {
allClasses bool
preWrapper PreWrapper
tabWidth int
wrapLongLines bool
lineNumbers bool
lineNumbersInTable bool
linkableLineNumbers bool
Expand Down Expand Up @@ -197,10 +213,10 @@ func (f *Formatter) writeHTML(w io.Writer, style *chroma.Style, tokens []chroma.

if wrapInTable {
// List line numbers in its own <td>
fmt.Fprintf(w, "<div%s>\n", f.styleAttr(css, chroma.Background))
fmt.Fprintf(w, "<div%s>\n", f.styleAttr(css, chroma.PreWrapper))
fmt.Fprintf(w, "<table%s><tr>", f.styleAttr(css, chroma.LineTable))
fmt.Fprintf(w, "<td%s>\n", f.styleAttr(css, chroma.LineTableTD))
fmt.Fprintf(w, f.preWrapper.Start(false, f.styleAttr(css, chroma.Background)))
fmt.Fprintf(w, f.preWrapper.Start(false, f.styleAttr(css, chroma.PreWrapper)))
for index := range lines {
line := f.baseLineNumber + index
highlight, next := f.shouldHighlight(highlightIndex, line)
Expand All @@ -222,7 +238,7 @@ func (f *Formatter) writeHTML(w io.Writer, style *chroma.Style, tokens []chroma.
fmt.Fprintf(w, "<td%s>\n", f.styleAttr(css, chroma.LineTableTD, "width:100%"))
}

fmt.Fprintf(w, f.preWrapper.Start(true, f.styleAttr(css, chroma.Background)))
fmt.Fprintf(w, f.preWrapper.Start(true, f.styleAttr(css, chroma.PreWrapper)))

highlightIndex = 0
for index, tokens := range lines {
Expand All @@ -232,14 +248,28 @@ func (f *Formatter) writeHTML(w io.Writer, style *chroma.Style, tokens []chroma.
if next {
highlightIndex++
}

// Start of Line
fmt.Fprint(w, `<div`)
if highlight {
fmt.Fprintf(w, "<span%s>", f.styleAttr(css, chroma.LineHighlight))
// Line + LineHighlight
if f.Classes {
fmt.Fprintf(w, ` class="%s %s"`, f.class(chroma.Line), f.class(chroma.LineHighlight))
} else {
fmt.Fprintf(w, ` style="%s %s"`, css[chroma.Line], css[chroma.LineHighlight])
}
fmt.Fprint(w, `>`)
} else {
fmt.Fprintf(w, "%s>", f.styleAttr(css, chroma.Line))
}

// Line number
if f.lineNumbers && !wrapInTable {
fmt.Fprintf(w, "<span%s%s>%s</span>", f.styleAttr(css, chroma.LineNumbers), f.lineIDAttribute(line), f.lineTitleWithLinkIfNeeded(lineDigits, line))
}

fmt.Fprintf(w, `<span%s>`, f.styleAttr(css, chroma.CodeLine))

for _, token := range tokens {
html := html.EscapeString(token.String())
attr := f.styleAttr(css, token.Type)
Expand All @@ -248,9 +278,10 @@ func (f *Formatter) writeHTML(w io.Writer, style *chroma.Style, tokens []chroma.
}
fmt.Fprint(w, html)
}
if highlight {
fmt.Fprintf(w, "</span>")
}

fmt.Fprint(w, `</span>`) // End of CodeLine

fmt.Fprint(w, `</div>`) // End of Line
}

fmt.Fprintf(w, f.preWrapper.End(true))
Expand Down Expand Up @@ -351,7 +382,11 @@ func (f *Formatter) tabWidthStyle() string {
func (f *Formatter) WriteCSS(w io.Writer, style *chroma.Style) error {
css := f.styleToCSS(style)
// Special-case background as it is mapped to the outer ".chroma" class.
if _, err := fmt.Fprintf(w, "/* %s */ .%schroma { %s }\n", chroma.Background, f.prefix, css[chroma.Background]); err != nil {
if _, err := fmt.Fprintf(w, "/* %s */ .%sbg { %s }\n", chroma.Background, f.prefix, css[chroma.Background]); err != nil {
return err
}
// Special-case PreWrapper as it is the ".chroma" class.
if _, err := fmt.Fprintf(w, "/* %s */ .%schroma { %s }\n", chroma.PreWrapper, f.prefix, css[chroma.PreWrapper]); err != nil {
return err
}
// Special-case code column of table to expand width.
Expand All @@ -375,7 +410,8 @@ func (f *Formatter) WriteCSS(w io.Writer, style *chroma.Style) error {
sort.Ints(tts)
for _, ti := range tts {
tt := chroma.TokenType(ti)
if tt == chroma.Background {
switch tt {
case chroma.Background, chroma.PreWrapper:
continue
}
class := f.class(tt)
Expand Down Expand Up @@ -405,11 +441,20 @@ func (f *Formatter) styleToCSS(style *chroma.Style) map[chroma.TokenType]string
classes[t] = StyleEntryToCSS(entry)
}
classes[chroma.Background] += f.tabWidthStyle()
lineNumbersStyle := "margin-right: 0.4em; padding: 0 0.4em 0 0.4em;"
classes[chroma.PreWrapper] += classes[chroma.Background] + `;`
// Make PreWrapper a grid to show highlight style with full width.
if len(f.highlightRanges) > 0 {
classes[chroma.PreWrapper] += `display: grid;`
}
// Make PreWrapper wrap long lines.
if f.wrapLongLines {
classes[chroma.PreWrapper] += `white-space: pre-wrap; word-break: break-word;`
}
lineNumbersStyle := `white-space: pre; user-select: none; margin-right: 0.4em; padding: 0 0.4em 0 0.4em;`
// All rules begin with default rules followed by user provided rules
classes[chroma.Line] = `display: flex;` + classes[chroma.Line]
classes[chroma.LineNumbers] = lineNumbersStyle + classes[chroma.LineNumbers]
classes[chroma.LineNumbersTable] = lineNumbersStyle + classes[chroma.LineNumbersTable]
classes[chroma.LineHighlight] = "display: block; width: 100%;" + classes[chroma.LineHighlight]
classes[chroma.LineNumbersTable] = `font-family: monospace; font-size: inherit;` + lineNumbersStyle + classes[chroma.LineNumbersTable]
classes[chroma.LineTable] = "border-spacing: 0; padding: 0; margin: 0; border: 0; width: auto; overflow: auto; display: block;" + classes[chroma.LineTable]
classes[chroma.LineTableTD] = "vertical-align: top; padding: 0; margin: 0; border: 0;" + classes[chroma.LineTableTD]
return classes
Expand Down
60 changes: 55 additions & 5 deletions formatters/html/html_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,56 @@ func TestTableLineNumberNewlines(t *testing.T) {
</span>`)
}

func TestWrapLongLines(t *testing.T) {
f := New(WithClasses(false), WrapLongLines(true))
it, err := lexers.Get("go").Tokenise(nil, "package main\nfunc main()\n{\nprintln(\"hello world\")\n}\n")
assert.NoError(t, err)

var buf bytes.Buffer
err = f.Format(&buf, styles.Fallback, it)
assert.NoError(t, err)

assert.Regexp(t, `<pre.*style=".*white-space:pre-wrap;word-break:break-word;`, buf.String())
}

func TestHighlightLines(t *testing.T) {
f := New(WithClasses(true), HighlightLines([][2]int{{4, 5}}))
it, err := lexers.Get("go").Tokenise(nil, "package main\nfunc main()\n{\nprintln(\"hello world\")\n}\n")
assert.NoError(t, err)

var buf bytes.Buffer
err = f.Format(&buf, styles.Fallback, it)
assert.NoError(t, err)

assert.Contains(t, buf.String(), `<div class="line hl"><span class="cl">`)
}

func TestLineNumbers(t *testing.T) {
f := New(WithClasses(true), WithLineNumbers(true))
it, err := lexers.Get("bash").Tokenise(nil, "echo FOO")
assert.NoError(t, err)

var buf bytes.Buffer
err = f.Format(&buf, styles.Fallback, it)
assert.NoError(t, err)

assert.Contains(t, buf.String(), `<div class="line"><span class="ln">1</span><span class="cl"><span class="nb">echo</span> FOO</span></div>`)
}

func TestPreWrapper(t *testing.T) {
f := New(Standalone(true), WithClasses(true))
it, err := lexers.Get("bash").Tokenise(nil, "echo FOO")
assert.NoError(t, err)

var buf bytes.Buffer
err = f.Format(&buf, styles.Fallback, it)
assert.NoError(t, err)

assert.Regexp(t, "<body class=\"bg\">\n<pre.*class=\"chroma\"><div class=\"line\"><span class=\"cl\"><span class=\"nb\">echo</span> FOO</span></div></pre>\n</body>\n</html>", buf.String())
assert.Regexp(t, `\.bg { .+ }`, buf.String())
assert.Regexp(t, `\.chroma { .+ }`, buf.String())
}

func TestLinkeableLineNumbers(t *testing.T) {
f := New(WithClasses(true), WithLineNumbers(true), LinkableLineNumbers(true, "line"))
it, err := lexers.Get("go").Tokenise(nil, "package main\nfunc main()\n{\nprintln(\"hello world\")\n}\n")
Expand Down Expand Up @@ -202,17 +252,17 @@ func TestWithPreWrapper(t *testing.T) {

t.Run("Regular", func(t *testing.T) {
s := format(New(WithClasses(true)))
assert.Equal(t, s, `<pre tabindex="0" class="chroma"><span class="nb">echo</span> FOO</pre>`)
assert.Equal(t, s, `<pre tabindex="0" class="chroma"><div class="line"><span class="cl"><span class="nb">echo</span> FOO</span></div></pre>`)
})

t.Run("PreventSurroundingPre", func(t *testing.T) {
s := format(New(PreventSurroundingPre(true), WithClasses(true)))
assert.Equal(t, s, `<span class="nb">echo</span> FOO`)
assert.Equal(t, s, `<div class="line"><span class="cl"><span class="nb">echo</span> FOO</span></div>`)
})

t.Run("Wrapper", func(t *testing.T) {
s := format(New(WithPreWrapper(wrapper), WithClasses(true)))
assert.Equal(t, s, `<foo class="chroma" id="code-true"><span class="nb">echo</span> FOO</foo>`)
assert.Equal(t, s, `<foo class="chroma" id="code-true"><div class="line"><span class="cl"><span class="nb">echo</span> FOO</span></div></foo>`)
})

t.Run("Wrapper, LineNumbersInTable", func(t *testing.T) {
Expand All @@ -223,7 +273,7 @@ func TestWithPreWrapper(t *testing.T) {
<foo class="chroma" id="code-false"><span class="lnt">1
</span></foo></td>
<td class="lntd">
<foo class="chroma" id="code-true"><span class="nb">echo</span> FOO</foo></td></tr></table>
<foo class="chroma" id="code-true"><div class="line"><span class="cl"><span class="nb">echo</span> FOO</span></div></foo></td></tr></table>
</div>
`)
})
Expand All @@ -246,7 +296,7 @@ func TestReconfigureOptions(t *testing.T) {
err = f.Format(&buf, styles.Fallback, it)

assert.NoError(t, err)
assert.Equal(t, `<pre tabindex="0" class="chroma"><span class="nb">echo</span> FOO</pre>`, buf.String())
assert.Equal(t, `<pre tabindex="0" class="chroma"><div class="line"><span class="cl"><span class="nb">echo</span> FOO</span></div></pre>`, buf.String())
}

func TestWriteCssWithAllClasses(t *testing.T) {
Expand Down
Loading

0 comments on commit 5ed5054

Please sign in to comment.