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", 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") @@ -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, `
echo FOO
`) + assert.Equal(t, s, `
echo FOO
`) }) t.Run("PreventSurroundingPre", func(t *testing.T) { s := format(New(PreventSurroundingPre(true), WithClasses(true))) - assert.Equal(t, s, `echo FOO`) + assert.Equal(t, s, `
echo FOO
`) }) t.Run("Wrapper", func(t *testing.T) { s := format(New(WithPreWrapper(wrapper), WithClasses(true))) - assert.Equal(t, s, `echo FOO`) + assert.Equal(t, s, `
echo FOO
`) }) t.Run("Wrapper, LineNumbersInTable", func(t *testing.T) { @@ -223,7 +273,7 @@ func TestWithPreWrapper(t *testing.T) { 1 -echo FOO +
echo FOO
`) }) @@ -246,7 +296,7 @@ func TestReconfigureOptions(t *testing.T) { err = f.Format(&buf, styles.Fallback, it) assert.NoError(t, err) - assert.Equal(t, `
echo FOO
`, buf.String()) + assert.Equal(t, `
echo FOO
`, buf.String()) } func TestWriteCssWithAllClasses(t *testing.T) { diff --git a/tokentype_string.go b/tokentype_string.go index 6fe386749..9c302f9c9 100644 --- a/tokentype_string.go +++ b/tokentype_string.go @@ -9,14 +9,17 @@ func _() { // Re-run the stringer command to generate them again. var x [1]struct{} _ = x[Background - -1] - _ = x[LineNumbers - -2] - _ = x[LineNumbersTable - -3] - _ = x[LineHighlight - -4] - _ = x[LineTable - -5] - _ = x[LineTableTD - -6] - _ = x[Error - -7] - _ = x[Other - -8] - _ = x[None - -9] + _ = x[PreWrapper - -2] + _ = x[Line - -3] + _ = x[LineNumbers - -4] + _ = x[LineNumbersTable - -5] + _ = x[LineHighlight - -6] + _ = x[LineTable - -7] + _ = x[LineTableTD - -8] + _ = x[CodeLine - -9] + _ = x[Error - -10] + _ = x[Other - -11] + _ = x[None - -12] _ = x[EOFType-0] _ = x[Keyword-1000] _ = x[KeywordConstant-1001] @@ -105,104 +108,107 @@ func _() { _ = x[TextPunctuation-8003] } -const _TokenType_name = "NoneOtherErrorLineTableTDLineTableLineHighlightLineNumbersTableLineNumbersBackgroundEOFTypeKeywordKeywordConstantKeywordDeclarationKeywordNamespaceKeywordPseudoKeywordReservedKeywordTypeNameNameAttributeNameBuiltinNameBuiltinPseudoNameClassNameConstantNameDecoratorNameEntityNameExceptionNameFunctionNameFunctionMagicNameKeywordNameLabelNameNamespaceNameOperatorNameOtherNamePseudoNamePropertyNameTagNameVariableNameVariableAnonymousNameVariableClassNameVariableGlobalNameVariableInstanceNameVariableMagicLiteralLiteralDateLiteralOtherLiteralStringLiteralStringAffixLiteralStringAtomLiteralStringBacktickLiteralStringBooleanLiteralStringCharLiteralStringDelimiterLiteralStringDocLiteralStringDoubleLiteralStringEscapeLiteralStringHeredocLiteralStringInterpolLiteralStringNameLiteralStringOtherLiteralStringRegexLiteralStringSingleLiteralStringSymbolLiteralNumberLiteralNumberBinLiteralNumberFloatLiteralNumberHexLiteralNumberIntegerLiteralNumberIntegerLongLiteralNumberOctOperatorOperatorWordPunctuationCommentCommentHashbangCommentMultilineCommentSingleCommentSpecialCommentPreprocCommentPreprocFileGenericGenericDeletedGenericEmphGenericErrorGenericHeadingGenericInsertedGenericOutputGenericPromptGenericStrongGenericSubheadingGenericTracebackGenericUnderlineTextTextWhitespaceTextSymbolTextPunctuation" +const _TokenType_name = "NoneOtherErrorCodeLineLineTableTDLineTableLineHighlightLineNumbersTableLineNumbersLinePreWrapperBackgroundEOFTypeKeywordKeywordConstantKeywordDeclarationKeywordNamespaceKeywordPseudoKeywordReservedKeywordTypeNameNameAttributeNameBuiltinNameBuiltinPseudoNameClassNameConstantNameDecoratorNameEntityNameExceptionNameFunctionNameFunctionMagicNameKeywordNameLabelNameNamespaceNameOperatorNameOtherNamePseudoNamePropertyNameTagNameVariableNameVariableAnonymousNameVariableClassNameVariableGlobalNameVariableInstanceNameVariableMagicLiteralLiteralDateLiteralOtherLiteralStringLiteralStringAffixLiteralStringAtomLiteralStringBacktickLiteralStringBooleanLiteralStringCharLiteralStringDelimiterLiteralStringDocLiteralStringDoubleLiteralStringEscapeLiteralStringHeredocLiteralStringInterpolLiteralStringNameLiteralStringOtherLiteralStringRegexLiteralStringSingleLiteralStringSymbolLiteralNumberLiteralNumberBinLiteralNumberFloatLiteralNumberHexLiteralNumberIntegerLiteralNumberIntegerLongLiteralNumberOctOperatorOperatorWordPunctuationCommentCommentHashbangCommentMultilineCommentSingleCommentSpecialCommentPreprocCommentPreprocFileGenericGenericDeletedGenericEmphGenericErrorGenericHeadingGenericInsertedGenericOutputGenericPromptGenericStrongGenericSubheadingGenericTracebackGenericUnderlineTextTextWhitespaceTextSymbolTextPunctuation" var _TokenType_map = map[TokenType]string{ - -9: _TokenType_name[0:4], - -8: _TokenType_name[4:9], - -7: _TokenType_name[9:14], - -6: _TokenType_name[14:25], - -5: _TokenType_name[25:34], - -4: _TokenType_name[34:47], - -3: _TokenType_name[47:63], - -2: _TokenType_name[63:74], - -1: _TokenType_name[74:84], - 0: _TokenType_name[84:91], - 1000: _TokenType_name[91:98], - 1001: _TokenType_name[98:113], - 1002: _TokenType_name[113:131], - 1003: _TokenType_name[131:147], - 1004: _TokenType_name[147:160], - 1005: _TokenType_name[160:175], - 1006: _TokenType_name[175:186], - 2000: _TokenType_name[186:190], - 2001: _TokenType_name[190:203], - 2002: _TokenType_name[203:214], - 2003: _TokenType_name[214:231], - 2004: _TokenType_name[231:240], - 2005: _TokenType_name[240:252], - 2006: _TokenType_name[252:265], - 2007: _TokenType_name[265:275], - 2008: _TokenType_name[275:288], - 2009: _TokenType_name[288:300], - 2010: _TokenType_name[300:317], - 2011: _TokenType_name[317:328], - 2012: _TokenType_name[328:337], - 2013: _TokenType_name[337:350], - 2014: _TokenType_name[350:362], - 2015: _TokenType_name[362:371], - 2016: _TokenType_name[371:381], - 2017: _TokenType_name[381:393], - 2018: _TokenType_name[393:400], - 2019: _TokenType_name[400:412], - 2020: _TokenType_name[412:433], - 2021: _TokenType_name[433:450], - 2022: _TokenType_name[450:468], - 2023: _TokenType_name[468:488], - 2024: _TokenType_name[488:505], - 3000: _TokenType_name[505:512], - 3001: _TokenType_name[512:523], - 3002: _TokenType_name[523:535], - 3100: _TokenType_name[535:548], - 3101: _TokenType_name[548:566], - 3102: _TokenType_name[566:583], - 3103: _TokenType_name[583:604], - 3104: _TokenType_name[604:624], - 3105: _TokenType_name[624:641], - 3106: _TokenType_name[641:663], - 3107: _TokenType_name[663:679], - 3108: _TokenType_name[679:698], - 3109: _TokenType_name[698:717], - 3110: _TokenType_name[717:737], - 3111: _TokenType_name[737:758], - 3112: _TokenType_name[758:775], - 3113: _TokenType_name[775:793], - 3114: _TokenType_name[793:811], - 3115: _TokenType_name[811:830], - 3116: _TokenType_name[830:849], - 3200: _TokenType_name[849:862], - 3201: _TokenType_name[862:878], - 3202: _TokenType_name[878:896], - 3203: _TokenType_name[896:912], - 3204: _TokenType_name[912:932], - 3205: _TokenType_name[932:956], - 3206: _TokenType_name[956:972], - 4000: _TokenType_name[972:980], - 4001: _TokenType_name[980:992], - 5000: _TokenType_name[992:1003], - 6000: _TokenType_name[1003:1010], - 6001: _TokenType_name[1010:1025], - 6002: _TokenType_name[1025:1041], - 6003: _TokenType_name[1041:1054], - 6004: _TokenType_name[1054:1068], - 6100: _TokenType_name[1068:1082], - 6101: _TokenType_name[1082:1100], - 7000: _TokenType_name[1100:1107], - 7001: _TokenType_name[1107:1121], - 7002: _TokenType_name[1121:1132], - 7003: _TokenType_name[1132:1144], - 7004: _TokenType_name[1144:1158], - 7005: _TokenType_name[1158:1173], - 7006: _TokenType_name[1173:1186], - 7007: _TokenType_name[1186:1199], - 7008: _TokenType_name[1199:1212], - 7009: _TokenType_name[1212:1229], - 7010: _TokenType_name[1229:1245], - 7011: _TokenType_name[1245:1261], - 8000: _TokenType_name[1261:1265], - 8001: _TokenType_name[1265:1279], - 8002: _TokenType_name[1279:1289], - 8003: _TokenType_name[1289:1304], + -12: _TokenType_name[0:4], + -11: _TokenType_name[4:9], + -10: _TokenType_name[9:14], + -9: _TokenType_name[14:22], + -8: _TokenType_name[22:33], + -7: _TokenType_name[33:42], + -6: _TokenType_name[42:55], + -5: _TokenType_name[55:71], + -4: _TokenType_name[71:82], + -3: _TokenType_name[82:86], + -2: _TokenType_name[86:96], + -1: _TokenType_name[96:106], + 0: _TokenType_name[106:113], + 1000: _TokenType_name[113:120], + 1001: _TokenType_name[120:135], + 1002: _TokenType_name[135:153], + 1003: _TokenType_name[153:169], + 1004: _TokenType_name[169:182], + 1005: _TokenType_name[182:197], + 1006: _TokenType_name[197:208], + 2000: _TokenType_name[208:212], + 2001: _TokenType_name[212:225], + 2002: _TokenType_name[225:236], + 2003: _TokenType_name[236:253], + 2004: _TokenType_name[253:262], + 2005: _TokenType_name[262:274], + 2006: _TokenType_name[274:287], + 2007: _TokenType_name[287:297], + 2008: _TokenType_name[297:310], + 2009: _TokenType_name[310:322], + 2010: _TokenType_name[322:339], + 2011: _TokenType_name[339:350], + 2012: _TokenType_name[350:359], + 2013: _TokenType_name[359:372], + 2014: _TokenType_name[372:384], + 2015: _TokenType_name[384:393], + 2016: _TokenType_name[393:403], + 2017: _TokenType_name[403:415], + 2018: _TokenType_name[415:422], + 2019: _TokenType_name[422:434], + 2020: _TokenType_name[434:455], + 2021: _TokenType_name[455:472], + 2022: _TokenType_name[472:490], + 2023: _TokenType_name[490:510], + 2024: _TokenType_name[510:527], + 3000: _TokenType_name[527:534], + 3001: _TokenType_name[534:545], + 3002: _TokenType_name[545:557], + 3100: _TokenType_name[557:570], + 3101: _TokenType_name[570:588], + 3102: _TokenType_name[588:605], + 3103: _TokenType_name[605:626], + 3104: _TokenType_name[626:646], + 3105: _TokenType_name[646:663], + 3106: _TokenType_name[663:685], + 3107: _TokenType_name[685:701], + 3108: _TokenType_name[701:720], + 3109: _TokenType_name[720:739], + 3110: _TokenType_name[739:759], + 3111: _TokenType_name[759:780], + 3112: _TokenType_name[780:797], + 3113: _TokenType_name[797:815], + 3114: _TokenType_name[815:833], + 3115: _TokenType_name[833:852], + 3116: _TokenType_name[852:871], + 3200: _TokenType_name[871:884], + 3201: _TokenType_name[884:900], + 3202: _TokenType_name[900:918], + 3203: _TokenType_name[918:934], + 3204: _TokenType_name[934:954], + 3205: _TokenType_name[954:978], + 3206: _TokenType_name[978:994], + 4000: _TokenType_name[994:1002], + 4001: _TokenType_name[1002:1014], + 5000: _TokenType_name[1014:1025], + 6000: _TokenType_name[1025:1032], + 6001: _TokenType_name[1032:1047], + 6002: _TokenType_name[1047:1063], + 6003: _TokenType_name[1063:1076], + 6004: _TokenType_name[1076:1090], + 6100: _TokenType_name[1090:1104], + 6101: _TokenType_name[1104:1122], + 7000: _TokenType_name[1122:1129], + 7001: _TokenType_name[1129:1143], + 7002: _TokenType_name[1143:1154], + 7003: _TokenType_name[1154:1166], + 7004: _TokenType_name[1166:1180], + 7005: _TokenType_name[1180:1195], + 7006: _TokenType_name[1195:1208], + 7007: _TokenType_name[1208:1221], + 7008: _TokenType_name[1221:1234], + 7009: _TokenType_name[1234:1251], + 7010: _TokenType_name[1251:1267], + 7011: _TokenType_name[1267:1283], + 8000: _TokenType_name[1283:1287], + 8001: _TokenType_name[1287:1301], + 8002: _TokenType_name[1301:1311], + 8003: _TokenType_name[1311:1326], } func (i TokenType) String() string { diff --git a/types.go b/types.go index ede945c52..3f15be84b 100644 --- a/types.go +++ b/types.go @@ -38,6 +38,10 @@ func (t *TokenType) UnmarshalJSON(data []byte) error { const ( // Default background style. Background TokenType = -1 - iota + // PreWrapper style. + PreWrapper + // Line style. + Line // Line numbers in output. LineNumbers // Line numbers in output when in table. @@ -48,6 +52,8 @@ const ( LineTable // Line numbers table TD wrapper style. LineTableTD + // Code line wrapper style. + CodeLine // Input that could not be tokenised. Error // Other is used by the Delegate lexer to indicate which tokens should be handled by the delegate. @@ -219,12 +225,15 @@ const ( var ( StandardTypes = map[TokenType]string{ - Background: "chroma", + Background: "bg", + PreWrapper: "chroma", + Line: "line", LineNumbers: "ln", LineNumbersTable: "lnt", LineHighlight: "hl", LineTable: "lntable", LineTableTD: "lntd", + CodeLine: "cl", Text: "", Whitespace: "w", Error: "err",