Skip to content

Commit 5ed5054

Browse files
CIAvashalecthomas
authored andcommitted
Put lines in a div & code lines in a span, Add WrapLongLines Option
1 parent 7cefa29 commit 5ed5054

File tree

4 files changed

+234
-124
lines changed

4 files changed

+234
-124
lines changed

Diff for: formatters/html/html.go

+59-14
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,13 @@ func WithPreWrapper(wrapper PreWrapper) Option {
4646
}
4747
}
4848

49+
// WrapLongLines wraps long lines.
50+
func WrapLongLines(b bool) Option {
51+
return func(f *Formatter) {
52+
f.wrapLongLines = b
53+
}
54+
}
55+
4956
// WithLineNumbers formats output with line numbers.
5057
func WithLineNumbers(b bool) Option {
5158
return func(f *Formatter) {
@@ -131,10 +138,18 @@ var (
131138
}
132139
defaultPreWrapper = preWrapper{
133140
start: func(code bool, styleAttr string) string {
134-
return fmt.Sprintf(`<pre tabindex="0"%s>`, styleAttr)
141+
if code {
142+
return fmt.Sprintf(`<pre tabindex="0"%s>`, styleAttr)
143+
}
144+
145+
return fmt.Sprintf(`<div tabindex="0"%s>`, styleAttr)
135146
},
136147
end: func(code bool) string {
137-
return "</pre>"
148+
if code {
149+
return `</pre>`
150+
}
151+
152+
return `</div>`
138153
},
139154
}
140155
)
@@ -147,6 +162,7 @@ type Formatter struct {
147162
allClasses bool
148163
preWrapper PreWrapper
149164
tabWidth int
165+
wrapLongLines bool
150166
lineNumbers bool
151167
lineNumbersInTable bool
152168
linkableLineNumbers bool
@@ -197,10 +213,10 @@ func (f *Formatter) writeHTML(w io.Writer, style *chroma.Style, tokens []chroma.
197213

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

225-
fmt.Fprintf(w, f.preWrapper.Start(true, f.styleAttr(css, chroma.Background)))
241+
fmt.Fprintf(w, f.preWrapper.Start(true, f.styleAttr(css, chroma.PreWrapper)))
226242

227243
highlightIndex = 0
228244
for index, tokens := range lines {
@@ -232,14 +248,28 @@ func (f *Formatter) writeHTML(w io.Writer, style *chroma.Style, tokens []chroma.
232248
if next {
233249
highlightIndex++
234250
}
251+
252+
// Start of Line
253+
fmt.Fprint(w, `<div`)
235254
if highlight {
236-
fmt.Fprintf(w, "<span%s>", f.styleAttr(css, chroma.LineHighlight))
255+
// Line + LineHighlight
256+
if f.Classes {
257+
fmt.Fprintf(w, ` class="%s %s"`, f.class(chroma.Line), f.class(chroma.LineHighlight))
258+
} else {
259+
fmt.Fprintf(w, ` style="%s %s"`, css[chroma.Line], css[chroma.LineHighlight])
260+
}
261+
fmt.Fprint(w, `>`)
262+
} else {
263+
fmt.Fprintf(w, "%s>", f.styleAttr(css, chroma.Line))
237264
}
238265

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

271+
fmt.Fprintf(w, `<span%s>`, f.styleAttr(css, chroma.CodeLine))
272+
243273
for _, token := range tokens {
244274
html := html.EscapeString(token.String())
245275
attr := f.styleAttr(css, token.Type)
@@ -248,9 +278,10 @@ func (f *Formatter) writeHTML(w io.Writer, style *chroma.Style, tokens []chroma.
248278
}
249279
fmt.Fprint(w, html)
250280
}
251-
if highlight {
252-
fmt.Fprintf(w, "</span>")
253-
}
281+
282+
fmt.Fprint(w, `</span>`) // End of CodeLine
283+
284+
fmt.Fprint(w, `</div>`) // End of Line
254285
}
255286

256287
fmt.Fprintf(w, f.preWrapper.End(true))
@@ -351,7 +382,11 @@ func (f *Formatter) tabWidthStyle() string {
351382
func (f *Formatter) WriteCSS(w io.Writer, style *chroma.Style) error {
352383
css := f.styleToCSS(style)
353384
// Special-case background as it is mapped to the outer ".chroma" class.
354-
if _, err := fmt.Fprintf(w, "/* %s */ .%schroma { %s }\n", chroma.Background, f.prefix, css[chroma.Background]); err != nil {
385+
if _, err := fmt.Fprintf(w, "/* %s */ .%sbg { %s }\n", chroma.Background, f.prefix, css[chroma.Background]); err != nil {
386+
return err
387+
}
388+
// Special-case PreWrapper as it is the ".chroma" class.
389+
if _, err := fmt.Fprintf(w, "/* %s */ .%schroma { %s }\n", chroma.PreWrapper, f.prefix, css[chroma.PreWrapper]); err != nil {
355390
return err
356391
}
357392
// Special-case code column of table to expand width.
@@ -375,7 +410,8 @@ func (f *Formatter) WriteCSS(w io.Writer, style *chroma.Style) error {
375410
sort.Ints(tts)
376411
for _, ti := range tts {
377412
tt := chroma.TokenType(ti)
378-
if tt == chroma.Background {
413+
switch tt {
414+
case chroma.Background, chroma.PreWrapper:
379415
continue
380416
}
381417
class := f.class(tt)
@@ -405,11 +441,20 @@ func (f *Formatter) styleToCSS(style *chroma.Style) map[chroma.TokenType]string
405441
classes[t] = StyleEntryToCSS(entry)
406442
}
407443
classes[chroma.Background] += f.tabWidthStyle()
408-
lineNumbersStyle := "margin-right: 0.4em; padding: 0 0.4em 0 0.4em;"
444+
classes[chroma.PreWrapper] += classes[chroma.Background] + `;`
445+
// Make PreWrapper a grid to show highlight style with full width.
446+
if len(f.highlightRanges) > 0 {
447+
classes[chroma.PreWrapper] += `display: grid;`
448+
}
449+
// Make PreWrapper wrap long lines.
450+
if f.wrapLongLines {
451+
classes[chroma.PreWrapper] += `white-space: pre-wrap; word-break: break-word;`
452+
}
453+
lineNumbersStyle := `white-space: pre; user-select: none; margin-right: 0.4em; padding: 0 0.4em 0 0.4em;`
409454
// All rules begin with default rules followed by user provided rules
455+
classes[chroma.Line] = `display: flex;` + classes[chroma.Line]
410456
classes[chroma.LineNumbers] = lineNumbersStyle + classes[chroma.LineNumbers]
411-
classes[chroma.LineNumbersTable] = lineNumbersStyle + classes[chroma.LineNumbersTable]
412-
classes[chroma.LineHighlight] = "display: block; width: 100%;" + classes[chroma.LineHighlight]
457+
classes[chroma.LineNumbersTable] = `font-family: monospace; font-size: inherit;` + lineNumbersStyle + classes[chroma.LineNumbersTable]
413458
classes[chroma.LineTable] = "border-spacing: 0; padding: 0; margin: 0; border: 0; width: auto; overflow: auto; display: block;" + classes[chroma.LineTable]
414459
classes[chroma.LineTableTD] = "vertical-align: top; padding: 0; margin: 0; border: 0;" + classes[chroma.LineTableTD]
415460
return classes

Diff for: formatters/html/html_test.go

+55-5
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,56 @@ func TestTableLineNumberNewlines(t *testing.T) {
108108
</span>`)
109109
}
110110

111+
func TestWrapLongLines(t *testing.T) {
112+
f := New(WithClasses(false), WrapLongLines(true))
113+
it, err := lexers.Get("go").Tokenise(nil, "package main\nfunc main()\n{\nprintln(\"hello world\")\n}\n")
114+
assert.NoError(t, err)
115+
116+
var buf bytes.Buffer
117+
err = f.Format(&buf, styles.Fallback, it)
118+
assert.NoError(t, err)
119+
120+
assert.Regexp(t, `<pre.*style=".*white-space:pre-wrap;word-break:break-word;`, buf.String())
121+
}
122+
123+
func TestHighlightLines(t *testing.T) {
124+
f := New(WithClasses(true), HighlightLines([][2]int{{4, 5}}))
125+
it, err := lexers.Get("go").Tokenise(nil, "package main\nfunc main()\n{\nprintln(\"hello world\")\n}\n")
126+
assert.NoError(t, err)
127+
128+
var buf bytes.Buffer
129+
err = f.Format(&buf, styles.Fallback, it)
130+
assert.NoError(t, err)
131+
132+
assert.Contains(t, buf.String(), `<div class="line hl"><span class="cl">`)
133+
}
134+
135+
func TestLineNumbers(t *testing.T) {
136+
f := New(WithClasses(true), WithLineNumbers(true))
137+
it, err := lexers.Get("bash").Tokenise(nil, "echo FOO")
138+
assert.NoError(t, err)
139+
140+
var buf bytes.Buffer
141+
err = f.Format(&buf, styles.Fallback, it)
142+
assert.NoError(t, err)
143+
144+
assert.Contains(t, buf.String(), `<div class="line"><span class="ln">1</span><span class="cl"><span class="nb">echo</span> FOO</span></div>`)
145+
}
146+
147+
func TestPreWrapper(t *testing.T) {
148+
f := New(Standalone(true), WithClasses(true))
149+
it, err := lexers.Get("bash").Tokenise(nil, "echo FOO")
150+
assert.NoError(t, err)
151+
152+
var buf bytes.Buffer
153+
err = f.Format(&buf, styles.Fallback, it)
154+
assert.NoError(t, err)
155+
156+
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())
157+
assert.Regexp(t, `\.bg { .+ }`, buf.String())
158+
assert.Regexp(t, `\.chroma { .+ }`, buf.String())
159+
}
160+
111161
func TestLinkeableLineNumbers(t *testing.T) {
112162
f := New(WithClasses(true), WithLineNumbers(true), LinkableLineNumbers(true, "line"))
113163
it, err := lexers.Get("go").Tokenise(nil, "package main\nfunc main()\n{\nprintln(\"hello world\")\n}\n")
@@ -202,17 +252,17 @@ func TestWithPreWrapper(t *testing.T) {
202252

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

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

213263
t.Run("Wrapper", func(t *testing.T) {
214264
s := format(New(WithPreWrapper(wrapper), WithClasses(true)))
215-
assert.Equal(t, s, `<foo class="chroma" id="code-true"><span class="nb">echo</span> FOO</foo>`)
265+
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>`)
216266
})
217267

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

248298
assert.NoError(t, err)
249-
assert.Equal(t, `<pre tabindex="0" class="chroma"><span class="nb">echo</span> FOO</pre>`, buf.String())
299+
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())
250300
}
251301

252302
func TestWriteCssWithAllClasses(t *testing.T) {

0 commit comments

Comments
 (0)