2015-12-17 06:13:12 +03:00
// Copyright 2015 The Gogs Authors. All rights reserved.
2020-07-01 00:34:03 +03:00
// Copyright 2020 The Gitea Authors. All rights reserved.
2015-12-17 06:13:12 +03:00
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.
2016-01-31 19:19:02 +03:00
package highlight
2015-12-17 06:13:12 +03:00
import (
2020-07-01 00:34:03 +03:00
"bufio"
"bytes"
"path/filepath"
2015-12-17 06:13:12 +03:00
"strings"
2020-07-01 00:34:03 +03:00
"sync"
2015-12-18 06:31:34 +03:00
2020-07-01 00:34:03 +03:00
"code.gitea.io/gitea/modules/log"
2016-11-10 19:24:48 +03:00
"code.gitea.io/gitea/modules/setting"
2020-07-08 01:30:21 +03:00
"github.com/alecthomas/chroma"
2020-07-01 00:34:03 +03:00
"github.com/alecthomas/chroma/formatters/html"
"github.com/alecthomas/chroma/lexers"
"github.com/alecthomas/chroma/styles"
2015-12-17 06:13:12 +03:00
)
2020-07-01 00:34:03 +03:00
// don't index files larger than this many bytes for performance purposes
const sizeLimit = 1000000
2015-12-17 06:13:12 +03:00
var (
2020-07-01 00:34:03 +03:00
// For custom user mapping
highlightMapping = map [ string ] string { }
once sync . Once
)
// NewContext loads custom highlight map from local config
func NewContext ( ) {
once . Do ( func ( ) {
keys := setting . Cfg . Section ( "highlight.mapping" ) . Keys ( )
for i := range keys {
highlightMapping [ keys [ i ] . Name ( ) ] = keys [ i ] . Value ( )
}
} )
}
// Code returns a HTML version of code string with chroma syntax highlighting classes
func Code ( fileName , code string ) string {
NewContext ( )
2015-12-17 06:13:12 +03:00
2020-07-01 00:34:03 +03:00
if len ( code ) > sizeLimit {
return code
}
formatter := html . New ( html . WithClasses ( true ) ,
html . WithLineNumbers ( false ) ,
html . PreventSurroundingPre ( true ) ,
)
if formatter == nil {
log . Error ( "Couldn't create chroma formatter" )
return code
2015-12-17 06:13:12 +03:00
}
2020-07-01 00:34:03 +03:00
htmlbuf := bytes . Buffer { }
htmlw := bufio . NewWriter ( & htmlbuf )
if val , ok := highlightMapping [ filepath . Ext ( fileName ) ] ; ok {
//change file name to one with mapped extension so we look that up instead
fileName = "mapped." + val
2015-12-17 06:13:12 +03:00
}
2015-12-18 06:31:34 +03:00
2020-07-01 00:34:03 +03:00
lexer := lexers . Match ( fileName )
if lexer == nil {
lexer = lexers . Fallback
2017-06-10 03:39:16 +03:00
}
2015-12-17 06:13:12 +03:00
2020-07-08 01:30:21 +03:00
iterator , err := lexer . Tokenise ( & chroma . TokeniseOptions { State : "root" , Nested : true } , string ( code ) )
2020-07-01 00:34:03 +03:00
if err != nil {
log . Error ( "Can't tokenize code: %v" , err )
return code
2015-12-18 06:31:34 +03:00
}
2020-07-01 00:34:03 +03:00
// style not used for live site but need to pass something
err = formatter . Format ( htmlw , styles . GitHub , iterator )
if err != nil {
log . Error ( "Can't format code: %v" , err )
return code
}
htmlw . Flush ( )
return htmlbuf . String ( )
2015-12-18 06:31:34 +03:00
}
2020-07-01 00:34:03 +03:00
// File returns map with line lumbers and HTML version of code with chroma syntax highlighting classes
func File ( numLines int , fileName string , code [ ] byte ) map [ int ] string {
NewContext ( )
if len ( code ) > sizeLimit {
return plainText ( string ( code ) , numLines )
}
formatter := html . New ( html . WithClasses ( true ) ,
html . WithLineNumbers ( false ) ,
html . PreventSurroundingPre ( true ) ,
)
if formatter == nil {
log . Error ( "Couldn't create chroma formatter" )
return plainText ( string ( code ) , numLines )
}
htmlbuf := bytes . Buffer { }
htmlw := bufio . NewWriter ( & htmlbuf )
if val , ok := highlightMapping [ filepath . Ext ( fileName ) ] ; ok {
fileName = "test." + val
}
lexer := lexers . Match ( fileName )
if lexer == nil {
lexer = lexers . Analyse ( string ( code ) )
if lexer == nil {
lexer = lexers . Fallback
}
2015-12-17 06:13:12 +03:00
}
2020-07-01 00:34:03 +03:00
iterator , err := lexer . Tokenise ( nil , string ( code ) )
if err != nil {
log . Error ( "Can't tokenize code: %v" , err )
return plainText ( string ( code ) , numLines )
2015-12-17 06:13:12 +03:00
}
2020-07-01 00:34:03 +03:00
err = formatter . Format ( htmlw , styles . GitHub , iterator )
if err != nil {
log . Error ( "Can't format code: %v" , err )
return plainText ( string ( code ) , numLines )
2015-12-17 06:13:12 +03:00
}
2020-07-01 00:34:03 +03:00
htmlw . Flush ( )
m := make ( map [ int ] string , numLines )
for k , v := range strings . SplitN ( htmlbuf . String ( ) , "\n" , numLines ) {
line := k + 1
m [ line ] = string ( v )
2015-12-18 06:31:34 +03:00
}
2020-07-01 00:34:03 +03:00
return m
}
2015-12-18 06:31:34 +03:00
2020-07-01 00:34:03 +03:00
// return unhiglighted map
func plainText ( code string , numLines int ) map [ int ] string {
m := make ( map [ int ] string , numLines )
for k , v := range strings . SplitN ( string ( code ) , "\n" , numLines ) {
line := k + 1
m [ line ] = string ( v )
}
return m
2015-12-17 06:13:12 +03:00
}