// Package highlight_diff provides syntaxhighlight.Printer and syntaxhighlight.Annotator implementations
// for diff format. It implements intra-block character-level inner diff highlighting.
//
// It uses GitHub Flavored Markdown .css class names "gi", "gd", "gu", "gh" for outer blocks,
// "x" for inner emphasis blocks.
package highlight_diff
import (
"bufio"
"bytes"
"io"
"text/template"
"github.com/sergi/go-diff/diffmatchpatch"
"github.com/sourcegraph/annotate"
"github.com/sourcegraph/syntaxhighlight"
)
var gfmDiff = HTMLConfig{
"",
"gi",
"gd",
"gu",
"gh",
}
func Print(s *Scanner, w io.Writer) error {
var p syntaxhighlight.Printer = HTMLPrinter(gfmDiff)
for s.Scan() {
tok, kind := s.Token()
err := p.Print(w, kind, string(tok))
if err != nil {
return err
}
}
return s.Err()
}
type HTMLConfig []string
type HTMLPrinter HTMLConfig
func (p HTMLPrinter) Print(w io.Writer, kind syntaxhighlight.Kind, tokText string) error {
class := HTMLConfig(p)[kind]
if class != "" {
_, err := w.Write([]byte(``))
if err != nil {
return err
}
}
template.HTMLEscape(w, []byte(tokText))
if class != "" {
_, err := w.Write([]byte(``))
if err != nil {
return err
}
}
return nil
}
type Scanner struct {
br *bufio.Reader
line []byte
}
func NewScanner(src []byte) *Scanner {
r := bytes.NewReader(src)
return &Scanner{br: bufio.NewReader(r)}
}
func (s *Scanner) Scan() bool {
var err error
s.line, err = s.br.ReadBytes('\n')
return err == nil
}
func (s *Scanner) Token() ([]byte, syntaxhighlight.Kind) {
var kind syntaxhighlight.Kind
switch {
// The backslash is to detect "\ No newline at end of file" lines.
case len(s.line) == 0 || s.line[0] == ' ' || s.line[0] == '\\':
kind = 0
case s.line[0] == '+':
//kind = 1
kind = 0
case s.line[0] == '-':
//kind = 2
kind = 0
case s.line[0] == '@':
kind = 3
default:
kind = 4
}
return s.line, kind
}
func (s *Scanner) Err() error {
return nil
}
// ---
type HTMLAnnotator HTMLConfig
func (a HTMLAnnotator) Annotate(start int, kind syntaxhighlight.Kind, tokText string) (*annotate.Annotation, error) {
class := HTMLConfig(a)[kind]
if class != "" {
left := []byte(``)...)
return &annotate.Annotation{
Start: start, End: start + len(tokText),
Left: left, Right: []byte(""),
}, nil
}
return nil, nil
}
func Annotate(src []byte) (annotate.Annotations, error) {
var a syntaxhighlight.Annotator = HTMLAnnotator(gfmDiff)
s := NewScanner(src)
var anns annotate.Annotations
read := 0
for s.Scan() {
tok, kind := s.Token()
ann, err := a.Annotate(read, kind, string(tok))
if err != nil {
return nil, err
}
read += len(tok)
if ann != nil {
anns = append(anns, ann)
}
}
if err := s.Err(); err != nil {
return nil, err
}
return anns, nil
}
// ---
func HighlightedDiffFunc(leftContent, rightContent string, segments *[2][]*annotate.Annotation, offsets [2]int) {
dmp := diffmatchpatch.New()
diffs := dmp.DiffMain(leftContent, rightContent, true)
for side := range *segments {
offset := offsets[side]
for _, diff := range diffs {
if side == 0 && diff.Type == -1 {
(*segments)[side] = append((*segments)[side], &annotate.Annotation{Start: offset, End: offset + len(diff.Text), Left: []byte(``), Right: []byte(``), WantInner: 1})
offset += len(diff.Text)
}
if side == 1 && diff.Type == +1 {
(*segments)[side] = append((*segments)[side], &annotate.Annotation{Start: offset, End: offset + len(diff.Text), Left: []byte(``), Right: []byte(``), WantInner: 1})
offset += len(diff.Text)
}
if diff.Type == 0 {
offset += len(diff.Text)
}
}
}
}