1
0
mirror of https://github.com/alecthomas/chroma.git synced 2025-01-26 03:20:10 +02:00

Add table styled line numbers (#54)

Fixes #52
This commit is contained in:
Bjørn Erik Pedersen 2017-10-13 01:49:20 +02:00 committed by Alec Thomas
parent 92586fdff2
commit 27733ac753
4 changed files with 97 additions and 26 deletions

BIN
cmd/chroma/chroma Executable file

Binary file not shown.

View File

@ -47,6 +47,7 @@ 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-lines", "Include line numbers in output.").Bool()
htmlLinesTableFlag = kingpin.Flag("html-lines-table", "Split line numbers and code in a HTML table").Bool()
htmlLinesStyleFlag = kingpin.Flag("html-lines-style", "Style for line numbers.").String()
htmlHighlightFlag = kingpin.Flag("html-highlight", "Highlight these lines.").PlaceHolder("N[:M][,...]").String()
htmlHighlightStyleFlag = kingpin.Flag("html-highlight-style", "Style used for highlighting lines.").String()
@ -140,6 +141,9 @@ command, for Go.
if *htmlLinesFlag {
options = append(options, html.WithLineNumbers())
}
if *htmlLinesTableFlag {
options = append(options, html.LineNumbersInTable())
}
if len(*htmlHighlightFlag) > 0 {
ranges := [][2]int{}
for _, span := range strings.Split(*htmlHighlightFlag, ",") {

View File

@ -32,6 +32,14 @@ func WithLineNumbers() Option {
}
}
// LineNumbersInTable will, when combined with WithLineNumbers, separate the line numbers
// and code in table td's, which make them copy-and-paste friendly.
func LineNumbersInTable() Option {
return func(f *Formatter) {
f.lineNumbersInTable = true
}
}
// 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.
@ -62,13 +70,14 @@ func New(options ...Option) *Formatter {
// Formatter that generates HTML.
type Formatter struct {
standalone bool
prefix string
classes bool
tabWidth int
lineNumbers bool
highlightRanges highlightRanges
baseLineNumber int
standalone bool
prefix string
classes bool
tabWidth int
lineNumbers bool
lineNumbersInTable bool
highlightRanges highlightRanges
baseLineNumber int
}
type highlightRanges [][2]int
@ -108,6 +117,7 @@ func (f *Formatter) restyle(style *chroma.Style) (*chroma.Style, error) {
text := chroma.StyleEntry{Colour: bg.Colour}
text.Colour = brightenOrDarken(text.Colour, 0.5)
builder.AddEntry(chroma.LineNumbers, text)
builder.AddEntry(chroma.LineNumbersTable, text)
}
return builder.Build()
}
@ -137,27 +147,51 @@ func (f *Formatter) writeHTML(w io.Writer, style *chroma.Style, tokens []*chroma
fmt.Fprintf(w, "<body%s>\n", f.styleAttr(css, chroma.Background))
}
fmt.Fprintf(w, "<pre%s>", f.styleAttr(css, chroma.Background))
wrapInTable := f.lineNumbers && f.lineNumbersInTable
lines := splitTokensIntoLines(tokens)
lineDigits := len(fmt.Sprintf("%d", len(lines)))
highlightIndex := 0
if wrapInTable {
// List line numbers in its own <td>
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, "<pre%s>", f.styleAttr(css, chroma.Background))
for index, _ := range lines {
line := f.baseLineNumber + index
highlight, next := f.shouldHighlight(highlightIndex, line)
if next {
highlightIndex++
}
if highlight {
fmt.Fprintf(w, "<span%s>", f.styleAttr(css, chroma.LineHighlight))
}
fmt.Fprintf(w, "<span%s>%*d</span>", f.styleAttr(css, chroma.LineNumbersTable), lineDigits, line)
if highlight {
fmt.Fprintf(w, "</span>")
}
}
fmt.Fprint(w, "</pre></td>\n")
fmt.Fprintf(w, "<td%s>\n", f.styleAttr(css, chroma.LineTableTD))
}
fmt.Fprintf(w, "<pre%s>", f.styleAttr(css, chroma.Background))
highlightIndex = 0
for index, tokens := range lines {
// 1-based line number.
line := f.baseLineNumber + index
highlight := false
for highlightIndex < len(f.highlightRanges) && line > f.highlightRanges[highlightIndex][1] {
highlight, next := f.shouldHighlight(highlightIndex, line)
if next {
highlightIndex++
}
if highlightIndex < len(f.highlightRanges) {
hrange := f.highlightRanges[highlightIndex]
if line >= hrange[0] && line <= hrange[1] {
highlight = true
}
}
if highlight {
fmt.Fprintf(w, "<span%s>", f.styleAttr(css, chroma.LineHighlight))
}
if f.lineNumbers {
if f.lineNumbers && !wrapInTable {
fmt.Fprintf(w, "<span%s>%*d</span>", f.styleAttr(css, chroma.LineNumbers), lineDigits, line)
}
@ -175,6 +209,11 @@ func (f *Formatter) writeHTML(w io.Writer, style *chroma.Style, tokens []*chroma
}
fmt.Fprint(w, "</pre>")
if wrapInTable {
fmt.Fprint(w, "</td></tr></table>\n")
}
if f.standalone {
fmt.Fprint(w, "\n</body>\n")
fmt.Fprint(w, "</html>\n")
@ -183,6 +222,21 @@ func (f *Formatter) writeHTML(w io.Writer, style *chroma.Style, tokens []*chroma
return nil
}
func (f *Formatter) shouldHighlight(highlightIndex, line int) (bool, bool) {
next := false
for highlightIndex < len(f.highlightRanges) && line > f.highlightRanges[highlightIndex][1] {
highlightIndex++
next = true
}
if highlightIndex < len(f.highlightRanges) {
hrange := f.highlightRanges[highlightIndex]
if line >= hrange[0] && line <= hrange[1] {
return true, next
}
}
return false, next
}
func (f *Formatter) class(t chroma.TokenType) string {
for t != 0 {
cls, ok := chroma.StandardTypes[t]
@ -261,8 +315,13 @@ func (f *Formatter) styleToCSS(style *chroma.Style) map[chroma.TokenType]string
classes[t] = StyleEntryToCSS(entry)
}
classes[chroma.Background] += f.tabWidthStyle()
classes[chroma.LineNumbers] += "; margin-right: 0.4em; padding: 0 0.4em 0 0.4em;"
lineNumbersStyle := "; margin-right: 0.4em; padding: 0 0.4em 0 0.4em;"
classes[chroma.LineNumbers] += lineNumbersStyle
classes[chroma.LineNumbersTable] += lineNumbersStyle + "; display: block"
classes[chroma.LineHighlight] += "; display: block; width: 100%"
classes[chroma.LineTable] += "; border-spacing: 0; padding: 0; margin: 0;"
classes[chroma.LineTableTD] += "; vertical-align: top; padding: 0; margin: 0;"
return classes
}

View File

@ -19,8 +19,14 @@ const (
Background TokenType = -1 - iota
// Line numbers in output.
LineNumbers
// Line numbers in output when in table.
LineNumbersTable
// Line higlight style.
LineHighlight
// Line numbers table wrapper style.
LineTable
// Line numbers table TD wrapper style.
LineTableTD
// Input that could not be tokenised.
Error
// Other is used by the Delegate lexer to indicate which tokens should be handled by the delegate.
@ -190,14 +196,16 @@ const (
var (
StandardTypes = map[TokenType]string{
Background: "chroma",
LineNumbers: "ln",
LineHighlight: "hl",
Text: "",
Whitespace: "w",
Error: "err",
Other: "x",
Background: "chroma",
LineNumbers: "ln",
LineNumbersTable: "lnt",
LineHighlight: "hl",
LineTable: "lntable",
LineTableTD: "lntd",
Text: "",
Whitespace: "w",
Error: "err",
Other: "x",
// I have no idea what this is used for...
// Escape: "esc",