Skip to content

Commit

Permalink
Support for highlighting ranges of lines.
Browse files Browse the repository at this point in the history
  • Loading branch information
alecthomas committed Sep 20, 2017
1 parent 3f230ec commit a5637e6
Show file tree
Hide file tree
Showing 5 changed files with 165 additions and 118 deletions.
37 changes: 25 additions & 12 deletions cmd/chroma/main.go
Expand Up @@ -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()
)
Expand Down Expand Up @@ -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 != "" {
Expand All @@ -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 {
Expand All @@ -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, "")
Expand Down Expand Up @@ -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)
Expand Down
37 changes: 22 additions & 15 deletions formatters/html/html.go
Expand Up @@ -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)
}
Expand All @@ -59,7 +58,6 @@ type Formatter struct {
classes bool
tabWidth int
lineNumbers bool
highlightStyle string
highlightRanges highlightRanges
}

Expand Down Expand Up @@ -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)
}
Expand All @@ -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")
Expand All @@ -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 {
Expand Down Expand Up @@ -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))
Expand All @@ -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]
Expand All @@ -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
}

Expand Down
19 changes: 19 additions & 0 deletions style.go
Expand Up @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down

0 comments on commit a5637e6

Please sign in to comment.