highlight_decl.go

v1.3.5
Doc Versions Source
1
package godoc
2
3
import (
4
	"go/scanner"
5
	"go/token"
6
	"html"
7
	"strings"
8
)
9
10
// HighlightDecl returns syntax-highlighted HTML for a Go declaration snippet.
11
// Unlike HighlightGo, this produces inline HTML without table/line-number wrapping.
12
func HighlightDecl(src string) string {
13
	return highlightDecl(src, nil)
14
}
15
16
// HighlightDeclLinked returns syntax-highlighted HTML with cross-references.
17
// Identifiers matching a key in symbols are rendered as links.
18
// The map value is the link target (e.g., "#TypeName" or a full URL).
19
func HighlightDeclLinked(src string, symbols map[string]string) string {
20
	return highlightDecl(src, symbols)
21
}
22
23
type declSpan struct {
24
	offset int
25
	end    int
26
	class  string
27
	ident  string // non-empty for linkable identifiers
28
}
29
30
func highlightDecl(src string, symbols map[string]string) string {
31
	prefix := "package p\n"
32
	full := []byte(prefix + src)
33
34
	fset := token.NewFileSet()
35
	file := fset.AddFile("", fset.Base(), len(full))
36
37
	var s scanner.Scanner
38
	s.Init(file, full, nil, scanner.ScanComments)
39
40
	var spans []declSpan
41
42
	for {
43
		pos, tok, lit := s.Scan()
44
		if tok == token.EOF {
45
			break
46
		}
47
48
		offset := int(pos) - file.Base()
49
		if offset < len(prefix) {
50
			continue
51
		}
52
53
		var class string
54
		var length int
55
		var ident string
56
57
		switch {
58
		case tok == token.COMMENT:
59
			class = "com"
60
			length = len(lit)
61
		case tok == token.STRING || tok == token.CHAR:
62
			class = "str"
63
			length = len(lit)
64
		case tok == token.INT || tok == token.FLOAT || tok == token.IMAG:
65
			class = "num"
66
			length = len(lit)
67
		case tok.IsKeyword():
68
			class = "kw"
69
			length = len(tok.String())
70
		case tok == token.IDENT:
71
			length = len(lit)
72
			if isBuiltin(lit) {
73
				class = "bi"
74
			} else if symbols != nil {
75
				if _, ok := symbols[lit]; ok {
76
					ident = lit
77
				}
78
			}
79
		}
80
81
		if class != "" || ident != "" {
82
			spans = append(spans, declSpan{
83
				offset: offset - len(prefix),
84
				end:    offset - len(prefix) + length,
85
				class:  class,
86
				ident:  ident,
87
			})
88
		}
89
	}
90
91
	var b strings.Builder
92
	pos := 0
93
	for _, sp := range spans {
94
		if sp.offset < 0 {
95
			continue
96
		}
97
		start := sp.offset
98
		end := sp.end
99
		if end > len(src) {
100
			end = len(src)
101
		}
102
		if start > pos {
103
			b.WriteString(html.EscapeString(src[pos:start]))
104
		}
105
106
		text := html.EscapeString(src[start:end])
107
108
		if sp.ident != "" && (start == 0 || src[start-1] != '.') {
109
			// Linkable identifier (skip qualified names like http.Client).
110
			href := symbols[sp.ident]
111
			b.WriteString(`<a href="`)
112
			b.WriteString(html.EscapeString(href))
113
			b.WriteString(`">`)
114
			b.WriteString(text)
115
			b.WriteString(`</a>`)
116
		} else {
117
			b.WriteString(`<span class="`)
118
			b.WriteString(sp.class)
119
			b.WriteString(`">`)
120
			b.WriteString(text)
121
			b.WriteString(`</span>`)
122
		}
123
124
		pos = end
125
	}
126
	if pos < len(src) {
127
		b.WriteString(html.EscapeString(src[pos:]))
128
	}
129
130
	return b.String()
131
}
132

Source Files