1
0
mirror of https://github.com/alecthomas/chroma.git synced 2025-03-19 21:10:15 +02:00

Support for highlighting ranges of lines.

This commit is contained in:
Alec Thomas 2017-09-20 14:15:06 +10:00
parent 3f230ec717
commit a5637e60b2
5 changed files with 165 additions and 118 deletions

@ -40,8 +40,9 @@ var (
htmlInlineStyleFlag = kingpin.Flag("html-inline-styles", "Output HTML with inline styles (no classes).").Bool()
htmlTabWidthFlag = kingpin.Flag("html-tab-width", "Set the HTML tab width.").Default("8").Int()
htmlLinesFlag = kingpin.Flag("html-line-numbers", "Include line numbers in output.").Bool()
htmlHighlightStyleFlag = kingpin.Flag("html-highlight-style", "Style used for highlighting lines.").Default("bg:#yellow").String()
htmlHighlightFlag = kingpin.Flag("html-highlight", "Highlight these ranges (N:M).").Strings()
htmlLinesStyleFlag = kingpin.Flag("html-line-numbers-style", "Style for line numbers.").Default("#888").String()
htmlHighlightFlag = kingpin.Flag("html-highlight", "Highlight these lines.").PlaceHolder("N[:M][,...]").String()
htmlHighlightStyleFlag = kingpin.Flag("html-highlight-style", "Style used for highlighting lines.").Default("bg:#282828").String()
filesArgs = kingpin.Arg("files", "Files to highlight.").ExistingFiles()
)
@ -94,6 +95,16 @@ command, for Go.
if *htmlFlag {
*formatterFlag = "html"
}
// Retrieve user-specified style, clone it, and add some overrides.
style := styles.Get(*styleFlag).Clone()
if *htmlHighlightStyleFlag != "" {
style.Add(chroma.LineHighlight, *htmlHighlightStyleFlag)
}
if *htmlLinesStyleFlag != "" {
style.Add(chroma.LineNumbers, *htmlLinesStyleFlag)
}
if *formatterFlag == "html" {
options := []html.Option{html.TabWidth(*htmlTabWidthFlag)}
if *htmlPrefixFlag != "" {
@ -103,7 +114,7 @@ command, for Go.
// Dump styles.
if *htmlStylesFlag {
formatter := html.New(html.WithClasses())
formatter.WriteCSS(w, styles.Get(*styleFlag))
formatter.WriteCSS(w, style)
return
}
if !*htmlInlineStyleFlag {
@ -117,22 +128,25 @@ command, for Go.
}
if len(*htmlHighlightFlag) > 0 {
ranges := [][2]int{}
for _, span := range *htmlHighlightFlag {
for _, span := range strings.Split(*htmlHighlightFlag, ",") {
parts := strings.Split(span, ":")
if len(parts) != 2 {
kingpin.Fatalf("range should be N:M, not %q", span)
if len(parts) > 2 {
kingpin.Fatalf("range should be N[:M], not %q", span)
}
start, err := strconv.ParseInt(parts[0], 10, 64)
kingpin.FatalIfError(err, "min value of range should be integer not %q", parts[0])
end, err := strconv.ParseInt(parts[0], 10, 64)
kingpin.FatalIfError(err, "max value of range should be integer not %q", parts[0])
end := start
if len(parts) == 2 {
end, err = strconv.ParseInt(parts[1], 10, 64)
kingpin.FatalIfError(err, "max value of range should be integer not %q", parts[1])
}
ranges = append(ranges, [2]int{int(start), int(end)})
}
options = append(options, html.HighlightLines(*htmlHighlightStyleFlag, ranges))
options = append(options, html.HighlightLines(ranges))
}
formatters.Register("html", html.New(options...))
}
writer := getWriter(w)
writer := getWriter(w, style)
if len(*filesArgs) == 0 {
contents, err := ioutil.ReadAll(os.Stdin)
kingpin.FatalIfError(err, "")
@ -201,8 +215,7 @@ func selexer(path, contents string) (lexer chroma.Lexer) {
return lexers.Analyse(contents)
}
func getWriter(w io.Writer) func(*chroma.Token) {
style := styles.Get(*styleFlag)
func getWriter(w io.Writer, style *chroma.Style) func(*chroma.Token) {
formatter := formatters.Get(*formatterFlag)
// formatter := formatters.TTY8
writer, err := formatter.Format(w, style)

@ -32,12 +32,11 @@ func WithLineNumbers() Option {
}
}
// HighlightLines higlights the given line ranges.
// HighlightLines higlights the given line ranges with the Highlight style.
//
// A range is the beginning and ending of a range as 1-based line numbers, inclusive.
func HighlightLines(style string, ranges [][2]int) Option {
func HighlightLines(ranges [][2]int) Option {
return func(f *Formatter) {
f.highlightStyle = style
f.highlightRanges = ranges
sort.Sort(f.highlightRanges)
}
@ -59,7 +58,6 @@ type Formatter struct {
classes bool
tabWidth int
lineNumbers bool
highlightStyle string
highlightRanges highlightRanges
}
@ -104,7 +102,21 @@ func (f *Formatter) writeHTML(w io.Writer, style *chroma.Style, tokens []*chroma
fmt.Fprintf(w, "<pre%s>\n", f.styleAttr(css, chroma.Background))
lines := splitTokensIntoLines(tokens)
lineDigits := len(fmt.Sprintf("%d", len(lines)))
highlightIndex := 0
for line, tokens := range lines {
highlight := false
for highlightIndex < len(f.highlightRanges) && line+1 > f.highlightRanges[highlightIndex][1] {
highlightIndex++
}
if highlightIndex < len(f.highlightRanges) {
hrange := f.highlightRanges[highlightIndex]
if line+1 >= hrange[0] && line+1 <= hrange[1] {
highlight = true
}
}
if highlight {
fmt.Fprintf(w, "<span class=\"hl\">")
}
if f.lineNumbers {
fmt.Fprintf(w, "<span class=\"ln\">%*d</span>", lineDigits, line+1)
}
@ -117,6 +129,9 @@ 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, "</pre>\n")
@ -134,7 +149,7 @@ func (f *Formatter) class(tt chroma.TokenType) string {
return "chroma"
case chroma.LineNumbers:
return "ln"
case chroma.Highlight:
case chroma.LineHighlight:
return "hl"
}
if tt < 0 {
@ -173,11 +188,6 @@ func (f *Formatter) WriteCSS(w io.Writer, style *chroma.Style) error {
if _, err := fmt.Fprintf(w, "/* %s */ .chroma { %s }\n", chroma.Background, css[chroma.Background]); err != nil {
return err
}
// No line-numbers, add a default.
if _, ok := css[chroma.LineNumbers]; !ok {
css[chroma.LineNumbers] = "color: #888"
}
css[chroma.LineNumbers] += "; margin-right: 0.5em"
tts := []int{}
for tt := range css {
tts = append(tts, int(tt))
@ -199,11 +209,6 @@ func (f *Formatter) WriteCSS(w io.Writer, style *chroma.Style) error {
func (f *Formatter) styleToCSS(style *chroma.Style) map[chroma.TokenType]string {
bg := style.Get(chroma.Background)
classes := map[chroma.TokenType]string{}
// Insert highlight colour if needed.
if len(f.highlightRanges) > 0 {
highlight := chroma.ParseStyleEntry(bg, f.highlightStyle).Sub(bg)
classes[chroma.Highlight] = StyleEntryToCSS(highlight)
}
// Convert the style.
for t := range style.Entries {
e := style.Entries[t]
@ -213,6 +218,8 @@ func (f *Formatter) styleToCSS(style *chroma.Style) map[chroma.TokenType]string
classes[t] = StyleEntryToCSS(e)
}
classes[chroma.Background] += f.tabWidthStyle()
classes[chroma.LineNumbers] += "; margin-right: 0.5em"
classes[chroma.LineHighlight] += "; display: block; width: 100%"
return classes
}

@ -17,6 +17,13 @@ type StyleEntry struct {
Underline bool
}
// Clone this StyleEntry.
func (s *StyleEntry) Clone() *StyleEntry {
clone := &StyleEntry{}
*clone = *s
return clone
}
func (s *StyleEntry) String() string {
out := []string{}
if s.Bold {
@ -89,6 +96,18 @@ type Style struct {
Entries map[TokenType]*StyleEntry
}
// Clone this style. The clone can then be safely modified.
func (s *Style) Clone() *Style {
clone := &Style{
Name: s.Name,
Entries: map[TokenType]*StyleEntry{},
}
for tt, e := range s.Entries {
clone.Entries[tt] = e.Clone()
}
return clone
}
// Get a style entry. Will try sub-category or category if an exact match is not found, and
// finally return the entry mapped to `InheritStyle`.
func (s *Style) Get(ttype TokenType) *StyleEntry {

@ -4,102 +4,102 @@ package chroma
import "fmt"
const _TokenType_name = "EOFNoneOtherErrorEscapeHighlightLineNumbersBackgroundKeywordKeywordConstantKeywordDeclarationKeywordNamespaceKeywordPseudoKeywordReservedKeywordTypeNameNameAttributeNameBuiltinNameBuiltinPseudoNameClassNameConstantNameDecoratorNameEntityNameExceptionNameFunctionNameFunctionMagicNameKeywordNameLabelNameNamespaceNameOperatorNameOtherNamePseudoNamePropertyNameTagNameVariableNameVariableAnonymousNameVariableClassNameVariableGlobalNameVariableInstanceNameVariableMagicLiteralLiteralDateLiteralOtherLiteralStringLiteralStringAffixLiteralStringAtomLiteralStringBacktickLiteralStringBooleanLiteralStringCharLiteralStringDelimiterLiteralStringDocLiteralStringDoubleLiteralStringEscapeLiteralStringHeredocLiteralStringInterpolLiteralStringNameLiteralStringOtherLiteralStringRegexLiteralStringSingleLiteralStringSymbolLiteralNumberLiteralNumberBinLiteralNumberFloatLiteralNumberHexLiteralNumberIntegerLiteralNumberIntegerLongLiteralNumberOctOperatorOperatorWordPunctuationCommentCommentHashbangCommentMultilineCommentSingleCommentSpecialCommentPreprocCommentPreprocFileGenericGenericDeletedGenericEmphGenericErrorGenericHeadingGenericInsertedGenericOutputGenericPromptGenericStrongGenericSubheadingGenericTracebackGenericUnderlineTextTextWhitespaceTextSymbolTextPunctuation"
const _TokenType_name = "EOFNoneOtherErrorHighlightLineHighlightLineNumbersBackgroundKeywordKeywordConstantKeywordDeclarationKeywordNamespaceKeywordPseudoKeywordReservedKeywordTypeNameNameAttributeNameBuiltinNameBuiltinPseudoNameClassNameConstantNameDecoratorNameEntityNameExceptionNameFunctionNameFunctionMagicNameKeywordNameLabelNameNamespaceNameOperatorNameOtherNamePseudoNamePropertyNameTagNameVariableNameVariableAnonymousNameVariableClassNameVariableGlobalNameVariableInstanceNameVariableMagicLiteralLiteralDateLiteralOtherLiteralStringLiteralStringAffixLiteralStringAtomLiteralStringBacktickLiteralStringBooleanLiteralStringCharLiteralStringDelimiterLiteralStringDocLiteralStringDoubleLiteralStringEscapeLiteralStringHeredocLiteralStringInterpolLiteralStringNameLiteralStringOtherLiteralStringRegexLiteralStringSingleLiteralStringSymbolLiteralNumberLiteralNumberBinLiteralNumberFloatLiteralNumberHexLiteralNumberIntegerLiteralNumberIntegerLongLiteralNumberOctOperatorOperatorWordPunctuationCommentCommentHashbangCommentMultilineCommentSingleCommentSpecialCommentPreprocCommentPreprocFileGenericGenericDeletedGenericEmphGenericErrorGenericHeadingGenericInsertedGenericOutputGenericPromptGenericStrongGenericSubheadingGenericTracebackGenericUnderlineTextTextWhitespaceTextSymbolTextPunctuation"
var _TokenType_map = map[TokenType]string{
-8: _TokenType_name[0:3],
-7: _TokenType_name[3:7],
-6: _TokenType_name[7:12],
-5: _TokenType_name[12:17],
-4: _TokenType_name[17:23],
-3: _TokenType_name[23:32],
-2: _TokenType_name[32:43],
-1: _TokenType_name[43:53],
1000: _TokenType_name[53:60],
1001: _TokenType_name[60:75],
1002: _TokenType_name[75:93],
1003: _TokenType_name[93:109],
1004: _TokenType_name[109:122],
1005: _TokenType_name[122:137],
1006: _TokenType_name[137:148],
2000: _TokenType_name[148:152],
2001: _TokenType_name[152:165],
2002: _TokenType_name[165:176],
2003: _TokenType_name[176:193],
2004: _TokenType_name[193:202],
2005: _TokenType_name[202:214],
2006: _TokenType_name[214:227],
2007: _TokenType_name[227:237],
2008: _TokenType_name[237:250],
2009: _TokenType_name[250:262],
2010: _TokenType_name[262:279],
2011: _TokenType_name[279:290],
2012: _TokenType_name[290:299],
2013: _TokenType_name[299:312],
2014: _TokenType_name[312:324],
2015: _TokenType_name[324:333],
2016: _TokenType_name[333:343],
2017: _TokenType_name[343:355],
2018: _TokenType_name[355:362],
2019: _TokenType_name[362:374],
2020: _TokenType_name[374:395],
2021: _TokenType_name[395:412],
2022: _TokenType_name[412:430],
2023: _TokenType_name[430:450],
2024: _TokenType_name[450:467],
3000: _TokenType_name[467:474],
3001: _TokenType_name[474:485],
3002: _TokenType_name[485:497],
3100: _TokenType_name[497:510],
3101: _TokenType_name[510:528],
3102: _TokenType_name[528:545],
3103: _TokenType_name[545:566],
3104: _TokenType_name[566:586],
3105: _TokenType_name[586:603],
3106: _TokenType_name[603:625],
3107: _TokenType_name[625:641],
3108: _TokenType_name[641:660],
3109: _TokenType_name[660:679],
3110: _TokenType_name[679:699],
3111: _TokenType_name[699:720],
3112: _TokenType_name[720:737],
3113: _TokenType_name[737:755],
3114: _TokenType_name[755:773],
3115: _TokenType_name[773:792],
3116: _TokenType_name[792:811],
3200: _TokenType_name[811:824],
3201: _TokenType_name[824:840],
3202: _TokenType_name[840:858],
3203: _TokenType_name[858:874],
3204: _TokenType_name[874:894],
3205: _TokenType_name[894:918],
3206: _TokenType_name[918:934],
4000: _TokenType_name[934:942],
4001: _TokenType_name[942:954],
5000: _TokenType_name[954:965],
6000: _TokenType_name[965:972],
6001: _TokenType_name[972:987],
6002: _TokenType_name[987:1003],
6003: _TokenType_name[1003:1016],
6004: _TokenType_name[1016:1030],
6100: _TokenType_name[1030:1044],
6101: _TokenType_name[1044:1062],
7000: _TokenType_name[1062:1069],
7001: _TokenType_name[1069:1083],
7002: _TokenType_name[1083:1094],
7003: _TokenType_name[1094:1106],
7004: _TokenType_name[1106:1120],
7005: _TokenType_name[1120:1135],
7006: _TokenType_name[1135:1148],
7007: _TokenType_name[1148:1161],
7008: _TokenType_name[1161:1174],
7009: _TokenType_name[1174:1191],
7010: _TokenType_name[1191:1207],
7011: _TokenType_name[1207:1223],
8000: _TokenType_name[1223:1227],
8001: _TokenType_name[1227:1241],
8002: _TokenType_name[1241:1251],
8003: _TokenType_name[1251:1266],
-4: _TokenType_name[17:26],
-3: _TokenType_name[26:39],
-2: _TokenType_name[39:50],
-1: _TokenType_name[50:60],
1000: _TokenType_name[60:67],
1001: _TokenType_name[67:82],
1002: _TokenType_name[82:100],
1003: _TokenType_name[100:116],
1004: _TokenType_name[116:129],
1005: _TokenType_name[129:144],
1006: _TokenType_name[144:155],
2000: _TokenType_name[155:159],
2001: _TokenType_name[159:172],
2002: _TokenType_name[172:183],
2003: _TokenType_name[183:200],
2004: _TokenType_name[200:209],
2005: _TokenType_name[209:221],
2006: _TokenType_name[221:234],
2007: _TokenType_name[234:244],
2008: _TokenType_name[244:257],
2009: _TokenType_name[257:269],
2010: _TokenType_name[269:286],
2011: _TokenType_name[286:297],
2012: _TokenType_name[297:306],
2013: _TokenType_name[306:319],
2014: _TokenType_name[319:331],
2015: _TokenType_name[331:340],
2016: _TokenType_name[340:350],
2017: _TokenType_name[350:362],
2018: _TokenType_name[362:369],
2019: _TokenType_name[369:381],
2020: _TokenType_name[381:402],
2021: _TokenType_name[402:419],
2022: _TokenType_name[419:437],
2023: _TokenType_name[437:457],
2024: _TokenType_name[457:474],
3000: _TokenType_name[474:481],
3001: _TokenType_name[481:492],
3002: _TokenType_name[492:504],
3100: _TokenType_name[504:517],
3101: _TokenType_name[517:535],
3102: _TokenType_name[535:552],
3103: _TokenType_name[552:573],
3104: _TokenType_name[573:593],
3105: _TokenType_name[593:610],
3106: _TokenType_name[610:632],
3107: _TokenType_name[632:648],
3108: _TokenType_name[648:667],
3109: _TokenType_name[667:686],
3110: _TokenType_name[686:706],
3111: _TokenType_name[706:727],
3112: _TokenType_name[727:744],
3113: _TokenType_name[744:762],
3114: _TokenType_name[762:780],
3115: _TokenType_name[780:799],
3116: _TokenType_name[799:818],
3200: _TokenType_name[818:831],
3201: _TokenType_name[831:847],
3202: _TokenType_name[847:865],
3203: _TokenType_name[865:881],
3204: _TokenType_name[881:901],
3205: _TokenType_name[901:925],
3206: _TokenType_name[925:941],
4000: _TokenType_name[941:949],
4001: _TokenType_name[949:961],
5000: _TokenType_name[961:972],
6000: _TokenType_name[972:979],
6001: _TokenType_name[979:994],
6002: _TokenType_name[994:1010],
6003: _TokenType_name[1010:1023],
6004: _TokenType_name[1023:1037],
6100: _TokenType_name[1037:1051],
6101: _TokenType_name[1051:1069],
7000: _TokenType_name[1069:1076],
7001: _TokenType_name[1076:1090],
7002: _TokenType_name[1090:1101],
7003: _TokenType_name[1101:1113],
7004: _TokenType_name[1113:1127],
7005: _TokenType_name[1127:1142],
7006: _TokenType_name[1142:1155],
7007: _TokenType_name[1155:1168],
7008: _TokenType_name[1168:1181],
7009: _TokenType_name[1181:1198],
7010: _TokenType_name[1198:1214],
7011: _TokenType_name[1214:1230],
8000: _TokenType_name[1230:1234],
8001: _TokenType_name[1234:1248],
8002: _TokenType_name[1248:1258],
8003: _TokenType_name[1258:1273],
}
func (i TokenType) String() string {

@ -15,13 +15,21 @@ type TokenType int
// Meta token types.
const (
// Default background style.
Background TokenType = -1 - iota
// Line numbers in output.
LineNumbers
// Line higlight style.
LineHighlight
// Character highlight style.
Highlight
Escape
// Input that could not be tokenised.
Error
// Other is used by the Delegate lexer to indicate which tokens should be handled by the delegate.
Other
// No highlighting.
None
// Final token.
EOF
)