diff --git a/formatters/html/html.go b/formatters/html/html.go index 5121529eb..74db7ff33 100644 --- a/formatters/html/html.go +++ b/formatters/html/html.go @@ -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) { @@ -131,10 +138,18 @@ var ( } defaultPreWrapper = preWrapper{ start: func(code bool, styleAttr string) string { - return fmt.Sprintf(`
`, styleAttr) + if code { + return fmt.Sprintf(``, styleAttr) + } + + return fmt.Sprintf(``, styleAttr) }, end: func(code bool) string { - return "" + if code { + return `` + } + + return `` }, } ) @@ -147,6 +162,7 @@ type Formatter struct { allClasses bool preWrapper PreWrapper tabWidth int + wrapLongLines bool lineNumbers bool lineNumbersInTable bool linkableLineNumbers bool @@ -197,10 +213,10 @@ func (f *Formatter) writeHTML(w io.Writer, style *chroma.Style, tokens []chroma. if wrapInTable { // List line numbers in its own- fmt.Fprintf(w, " \n", f.styleAttr(css, chroma.Background)) + fmt.Fprintf(w, "\n", f.styleAttr(css, chroma.PreWrapper)) fmt.Fprintf(w, "
", f.styleAttr(css, chroma.LineTable)) fmt.Fprintf(w, " \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) @@ -222,7 +238,7 @@ func (f *Formatter) writeHTML(w io.Writer, style *chroma.Style, tokens []chroma. fmt.Fprintf(w, " \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 { @@ -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, ` ", 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, "%s", f.styleAttr(css, chroma.LineNumbers), f.lineIDAttribute(line), f.lineTitleWithLinkIfNeeded(lineDigits, line)) } + fmt.Fprintf(w, ``, f.styleAttr(css, chroma.CodeLine)) + for _, token := range tokens { html := html.EscapeString(token.String()) attr := f.styleAttr(css, token.Type) @@ -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, "") - } + + fmt.Fprint(w, ``) // End of CodeLine + + fmt.Fprint(w, ``) // End of Line } fmt.Fprintf(w, f.preWrapper.End(true)) @@ -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. @@ -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) @@ -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 diff --git a/formatters/html/html_test.go b/formatters/html/html_test.go index 70c08e6e5..39575ba3b 100644 --- a/formatters/html/html_test.go +++ b/formatters/html/html_test.go @@ -108,6 +108,56 @@ func TestTableLineNumberNewlines(t *testing.T) { `) } +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, ``) +} + +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(), ` 1echo FOO`) +} + +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, "\n echo FOO\n\n