Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. // Copyright 2015 The Gogs Authors. All rights reserved.
  2. // Copyright 2020 The Gitea Authors. All rights reserved.
  3. // Use of this source code is governed by a MIT-style
  4. // license that can be found in the LICENSE file.
  5. package highlight
  6. import (
  7. "bufio"
  8. "bytes"
  9. gohtml "html"
  10. "path/filepath"
  11. "strings"
  12. "sync"
  13. "code.gitea.io/gitea/modules/analyze"
  14. "code.gitea.io/gitea/modules/log"
  15. "code.gitea.io/gitea/modules/setting"
  16. "github.com/alecthomas/chroma/formatters/html"
  17. "github.com/alecthomas/chroma/lexers"
  18. "github.com/alecthomas/chroma/styles"
  19. )
  20. // don't index files larger than this many bytes for performance purposes
  21. const sizeLimit = 1000000
  22. var (
  23. // For custom user mapping
  24. highlightMapping = map[string]string{}
  25. once sync.Once
  26. )
  27. // NewContext loads custom highlight map from local config
  28. func NewContext() {
  29. once.Do(func() {
  30. keys := setting.Cfg.Section("highlight.mapping").Keys()
  31. for i := range keys {
  32. highlightMapping[keys[i].Name()] = keys[i].Value()
  33. }
  34. })
  35. }
  36. // Code returns a HTML version of code string with chroma syntax highlighting classes
  37. func Code(fileName, code string) string {
  38. NewContext()
  39. // diff view newline will be passed as empty, change to literal \n so it can be copied
  40. // preserve literal newline in blame view
  41. if code == "" || code == "\n" {
  42. return "\n"
  43. }
  44. if len(code) > sizeLimit {
  45. return code
  46. }
  47. formatter := html.New(html.WithClasses(true),
  48. html.WithLineNumbers(false),
  49. html.PreventSurroundingPre(true),
  50. )
  51. if formatter == nil {
  52. log.Error("Couldn't create chroma formatter")
  53. return code
  54. }
  55. htmlbuf := bytes.Buffer{}
  56. htmlw := bufio.NewWriter(&htmlbuf)
  57. if val, ok := highlightMapping[filepath.Ext(fileName)]; ok {
  58. //change file name to one with mapped extension so we look that up instead
  59. fileName = "mapped." + val
  60. }
  61. lexer := lexers.Match(fileName)
  62. if lexer == nil {
  63. lexer = lexers.Fallback
  64. }
  65. iterator, err := lexer.Tokenise(nil, string(code))
  66. if err != nil {
  67. log.Error("Can't tokenize code: %v", err)
  68. return code
  69. }
  70. // style not used for live site but need to pass something
  71. err = formatter.Format(htmlw, styles.GitHub, iterator)
  72. if err != nil {
  73. log.Error("Can't format code: %v", err)
  74. return code
  75. }
  76. htmlw.Flush()
  77. // Chroma will add newlines for certain lexers in order to highlight them properly
  78. // Once highlighted, strip them here so they don't cause copy/paste trouble in HTML output
  79. return strings.TrimSuffix(htmlbuf.String(), "\n")
  80. }
  81. // File returns map with line lumbers and HTML version of code with chroma syntax highlighting classes
  82. func File(numLines int, fileName string, code []byte) map[int]string {
  83. NewContext()
  84. if len(code) > sizeLimit {
  85. return plainText(string(code), numLines)
  86. }
  87. formatter := html.New(html.WithClasses(true),
  88. html.WithLineNumbers(false),
  89. html.PreventSurroundingPre(true),
  90. )
  91. if formatter == nil {
  92. log.Error("Couldn't create chroma formatter")
  93. return plainText(string(code), numLines)
  94. }
  95. htmlbuf := bytes.Buffer{}
  96. htmlw := bufio.NewWriter(&htmlbuf)
  97. if val, ok := highlightMapping[filepath.Ext(fileName)]; ok {
  98. fileName = "test." + val
  99. }
  100. language := analyze.GetCodeLanguage(fileName, code)
  101. lexer := lexers.Get(language)
  102. if lexer == nil {
  103. lexer = lexers.Match(fileName)
  104. if lexer == nil {
  105. lexer = lexers.Fallback
  106. }
  107. }
  108. iterator, err := lexer.Tokenise(nil, string(code))
  109. if err != nil {
  110. log.Error("Can't tokenize code: %v", err)
  111. return plainText(string(code), numLines)
  112. }
  113. err = formatter.Format(htmlw, styles.GitHub, iterator)
  114. if err != nil {
  115. log.Error("Can't format code: %v", err)
  116. return plainText(string(code), numLines)
  117. }
  118. htmlw.Flush()
  119. m := make(map[int]string, numLines)
  120. for k, v := range strings.SplitN(htmlbuf.String(), "\n", numLines) {
  121. line := k + 1
  122. content := string(v)
  123. //need to keep lines that are only \n so copy/paste works properly in browser
  124. if content == "" {
  125. content = "\n"
  126. }
  127. m[line] = content
  128. }
  129. return m
  130. }
  131. // return unhiglighted map
  132. func plainText(code string, numLines int) map[int]string {
  133. m := make(map[int]string, numLines)
  134. for k, v := range strings.SplitN(string(code), "\n", numLines) {
  135. line := k + 1
  136. content := string(v)
  137. //need to keep lines that are only \n so copy/paste works properly in browser
  138. if content == "" {
  139. content = "\n"
  140. }
  141. m[line] = gohtml.EscapeString(content)
  142. }
  143. return m
  144. }