Skip to content

Commit bb1efdb

Browse files
committed
Revert "Delete modules/highlight/highlight.go"
This reverts commit 95f7abb.
1 parent 95f7abb commit bb1efdb

File tree

1 file changed

+225
-0
lines changed

1 file changed

+225
-0
lines changed

modules/highlight/highlight.go

Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
// Copyright 2015 The Gogs Authors. All rights reserved.
2+
// Copyright 2020 The Gitea Authors. All rights reserved.
3+
// SPDX-License-Identifier: MIT
4+
5+
package highlight
6+
7+
import (
8+
"bufio"
9+
"bytes"
10+
"fmt"
11+
gohtml "html"
12+
"html/template"
13+
"io"
14+
"path"
15+
"path/filepath"
16+
"strings"
17+
"sync"
18+
19+
"code.gitea.io/gitea/modules/analyze"
20+
"code.gitea.io/gitea/modules/log"
21+
"code.gitea.io/gitea/modules/setting"
22+
"code.gitea.io/gitea/modules/util"
23+
24+
"github.com/alecthomas/chroma/v2"
25+
"github.com/alecthomas/chroma/v2/formatters/html"
26+
"github.com/alecthomas/chroma/v2/lexers"
27+
"github.com/alecthomas/chroma/v2/styles"
28+
lru "github.com/hashicorp/golang-lru/v2"
29+
)
30+
31+
// don't index files larger than this many bytes for performance purposes
32+
const sizeLimit = 1024 * 1024
33+
34+
var (
35+
// For custom user mapping
36+
highlightMapping = map[string]string{}
37+
38+
once sync.Once
39+
40+
cache *lru.TwoQueueCache[string, any]
41+
42+
githubStyles = styles.Get("github")
43+
)
44+
45+
// NewContext loads custom highlight map from local config
46+
func NewContext() {
47+
once.Do(func() {
48+
highlightMapping = setting.GetHighlightMapping()
49+
50+
// The size 512 is simply a conservative rule of thumb
51+
c, err := lru.New2Q[string, any](512)
52+
if err != nil {
53+
panic(fmt.Sprintf("failed to initialize LRU cache for highlighter: %s", err))
54+
}
55+
cache = c
56+
})
57+
}
58+
59+
// Code returns a HTML version of code string with chroma syntax highlighting classes and the matched lexer name
60+
func Code(fileName, language, code string) (output template.HTML, lexerName string) {
61+
NewContext()
62+
63+
// diff view newline will be passed as empty, change to literal '\n' so it can be copied
64+
// preserve literal newline in blame view
65+
if code == "" || code == "\n" {
66+
return "\n", ""
67+
}
68+
69+
if len(code) > sizeLimit {
70+
return template.HTML(template.HTMLEscapeString(code)), ""
71+
}
72+
73+
var lexer chroma.Lexer
74+
75+
if len(language) > 0 {
76+
lexer = lexers.Get(language)
77+
78+
if lexer == nil {
79+
// Attempt stripping off the '?'
80+
if idx := strings.IndexByte(language, '?'); idx > 0 {
81+
lexer = lexers.Get(language[:idx])
82+
}
83+
}
84+
}
85+
86+
if lexer == nil {
87+
if val, ok := highlightMapping[path.Ext(fileName)]; ok {
88+
// use mapped value to find lexer
89+
lexer = lexers.Get(val)
90+
}
91+
}
92+
93+
if lexer == nil {
94+
if l, ok := cache.Get(fileName); ok {
95+
lexer = l.(chroma.Lexer)
96+
}
97+
}
98+
99+
if lexer == nil {
100+
lexer = lexers.Match(fileName)
101+
if lexer == nil {
102+
lexer = lexers.Fallback
103+
}
104+
cache.Add(fileName, lexer)
105+
}
106+
107+
return CodeFromLexer(lexer, code), formatLexerName(lexer.Config().Name)
108+
}
109+
110+
// CodeFromLexer returns a HTML version of code string with chroma syntax highlighting classes
111+
func CodeFromLexer(lexer chroma.Lexer, code string) template.HTML {
112+
formatter := html.New(html.WithClasses(true),
113+
html.WithLineNumbers(false),
114+
html.PreventSurroundingPre(true),
115+
)
116+
117+
htmlbuf := bytes.Buffer{}
118+
htmlw := bufio.NewWriter(&htmlbuf)
119+
120+
iterator, err := lexer.Tokenise(nil, code)
121+
if err != nil {
122+
log.Error("Can't tokenize code: %v", err)
123+
return template.HTML(template.HTMLEscapeString(code))
124+
}
125+
// style not used for live site but need to pass something
126+
err = formatter.Format(htmlw, githubStyles, iterator)
127+
if err != nil {
128+
log.Error("Can't format code: %v", err)
129+
return template.HTML(template.HTMLEscapeString(code))
130+
}
131+
132+
_ = htmlw.Flush()
133+
// Chroma will add newlines for certain lexers in order to highlight them properly
134+
// // Once highlighted, strip them here, so they don't cause copy/paste trouble in HTML output
135+
return template.HTML(strings.TrimSuffix(htmlbuf.String(), "\n"))
136+
}
137+
138+
// File returns a slice of chroma syntax highlighted HTML lines of code and the matched lexer name
139+
func File(fileName, language string, code []byte) ([]template.HTML, string, error) {
140+
NewContext()
141+
142+
if len(code) > sizeLimit {
143+
return PlainText(code), "", nil
144+
}
145+
146+
formatter := html.New(html.WithClasses(true),
147+
html.WithLineNumbers(false),
148+
html.PreventSurroundingPre(true),
149+
)
150+
151+
var lexer chroma.Lexer
152+
153+
// provided language overrides everything
154+
if language != "" {
155+
lexer = lexers.Get(language)
156+
}
157+
158+
if lexer == nil {
159+
if val, ok := highlightMapping[filepath.Ext(fileName)]; ok {
160+
lexer = lexers.Get(val)
161+
}
162+
}
163+
164+
if lexer == nil {
165+
guessLanguage := analyze.GetCodeLanguage(fileName, code)
166+
167+
lexer = lexers.Get(guessLanguage)
168+
if lexer == nil {
169+
lexer = lexers.Match(fileName)
170+
if lexer == nil {
171+
lexer = lexers.Fallback
172+
}
173+
}
174+
}
175+
176+
lexerName := formatLexerName(lexer.Config().Name)
177+
178+
iterator, err := lexer.Tokenise(nil, string(code))
179+
if err != nil {
180+
return nil, "", fmt.Errorf("can't tokenize code: %w", err)
181+
}
182+
183+
tokensLines := chroma.SplitTokensIntoLines(iterator.Tokens())
184+
htmlBuf := &bytes.Buffer{}
185+
186+
lines := make([]template.HTML, 0, len(tokensLines))
187+
for _, tokens := range tokensLines {
188+
iterator = chroma.Literator(tokens...)
189+
err = formatter.Format(htmlBuf, githubStyles, iterator)
190+
if err != nil {
191+
return nil, "", fmt.Errorf("can't format code: %w", err)
192+
}
193+
lines = append(lines, template.HTML(htmlBuf.String()))
194+
htmlBuf.Reset()
195+
}
196+
197+
return lines, lexerName, nil
198+
}
199+
200+
// PlainText returns non-highlighted HTML for code
201+
func PlainText(code []byte) []template.HTML {
202+
r := bufio.NewReader(bytes.NewReader(code))
203+
m := make([]template.HTML, 0, bytes.Count(code, []byte{'\n'})+1)
204+
for {
205+
content, err := r.ReadString('\n')
206+
if err != nil && err != io.EOF {
207+
log.Error("failed to read string from buffer: %v", err)
208+
break
209+
}
210+
if content == "" && err == io.EOF {
211+
break
212+
}
213+
s := template.HTML(gohtml.EscapeString(content))
214+
m = append(m, s)
215+
}
216+
return m
217+
}
218+
219+
func formatLexerName(name string) string {
220+
if name == "fallback" {
221+
return "Plaintext"
222+
}
223+
224+
return util.ToTitleCaseNoLower(name)
225+
}

0 commit comments

Comments
 (0)