diff options
-rw-r--r-- | modules/highlight/highlight.go | 36 | ||||
-rw-r--r-- | modules/highlight/highlight_test.go | 59 | ||||
-rw-r--r-- | modules/indexer/code/search.go | 5 | ||||
-rw-r--r-- | modules/util/util.go | 10 | ||||
-rw-r--r-- | routers/web/repo/blame.go | 13 | ||||
-rw-r--r-- | routers/web/repo/view.go | 3 | ||||
-rw-r--r-- | services/gitdiff/gitdiff.go | 3 | ||||
-rw-r--r-- | services/gitdiff/highlightdiff.go | 4 | ||||
-rw-r--r-- | templates/repo/blame.tmpl | 13 | ||||
-rw-r--r-- | templates/repo/file_info.tmpl | 28 | ||||
-rw-r--r-- | templates/repo/view_file.tmpl | 30 |
11 files changed, 132 insertions, 72 deletions
diff --git a/modules/highlight/highlight.go b/modules/highlight/highlight.go index 65ed74b019..3836c05880 100644 --- a/modules/highlight/highlight.go +++ b/modules/highlight/highlight.go @@ -18,6 +18,7 @@ import ( "code.gitea.io/gitea/modules/analyze" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" + "code.gitea.io/gitea/modules/util" "github.com/alecthomas/chroma/v2" "github.com/alecthomas/chroma/v2/formatters/html" @@ -56,18 +57,18 @@ func NewContext() { }) } -// Code returns a HTML version of code string with chroma syntax highlighting classes -func Code(fileName, language, code string) string { +// Code returns a HTML version of code string with chroma syntax highlighting classes and the matched lexer name +func Code(fileName, language, code string) (string, string) { NewContext() // diff view newline will be passed as empty, change to literal '\n' so it can be copied // preserve literal newline in blame view if code == "" || code == "\n" { - return "\n" + return "\n", "" } if len(code) > sizeLimit { - return code + return code, "" } var lexer chroma.Lexer @@ -103,7 +104,10 @@ func Code(fileName, language, code string) string { } cache.Add(fileName, lexer) } - return CodeFromLexer(lexer, code) + + lexerName := formatLexerName(lexer.Config().Name) + + return CodeFromLexer(lexer, code), lexerName } // CodeFromLexer returns a HTML version of code string with chroma syntax highlighting classes @@ -134,12 +138,12 @@ func CodeFromLexer(lexer chroma.Lexer, code string) string { return strings.TrimSuffix(htmlbuf.String(), "\n") } -// File returns a slice of chroma syntax highlighted HTML lines of code -func File(fileName, language string, code []byte) ([]string, error) { +// File returns a slice of chroma syntax highlighted HTML lines of code and the matched lexer name +func File(fileName, language string, code []byte) ([]string, string, error) { NewContext() if len(code) > sizeLimit { - return PlainText(code), nil + return PlainText(code), "", nil } formatter := html.New(html.WithClasses(true), @@ -172,9 +176,11 @@ func File(fileName, language string, code []byte) ([]string, error) { } } + lexerName := formatLexerName(lexer.Config().Name) + iterator, err := lexer.Tokenise(nil, string(code)) if err != nil { - return nil, fmt.Errorf("can't tokenize code: %w", err) + return nil, "", fmt.Errorf("can't tokenize code: %w", err) } tokensLines := chroma.SplitTokensIntoLines(iterator.Tokens()) @@ -185,13 +191,13 @@ func File(fileName, language string, code []byte) ([]string, error) { iterator = chroma.Literator(tokens...) err = formatter.Format(htmlBuf, styles.GitHub, iterator) if err != nil { - return nil, fmt.Errorf("can't format code: %w", err) + return nil, "", fmt.Errorf("can't format code: %w", err) } lines = append(lines, htmlBuf.String()) htmlBuf.Reset() } - return lines, nil + return lines, lexerName, nil } // PlainText returns non-highlighted HTML for code @@ -212,3 +218,11 @@ func PlainText(code []byte) []string { } return m } + +func formatLexerName(name string) string { + if name == "fallback" { + return "Plaintext" + } + + return util.ToTitleCaseNoLower(name) +} diff --git a/modules/highlight/highlight_test.go b/modules/highlight/highlight_test.go index 8f83f4a2f6..1899753746 100644 --- a/modules/highlight/highlight_test.go +++ b/modules/highlight/highlight_test.go @@ -17,34 +17,52 @@ func lines(s string) []string { func TestFile(t *testing.T) { tests := []struct { - name string - code string - want []string + name string + code string + want []string + lexerName string }{ { - name: "empty.py", - code: "", - want: lines(""), + name: "empty.py", + code: "", + want: lines(""), + lexerName: "Python", }, { - name: "tags.txt", - code: "<>", - want: lines("<>"), + name: "empty.js", + code: "", + want: lines(""), + lexerName: "JavaScript", }, { - name: "tags.py", - code: "<>", - want: lines(`<span class="o"><</span><span class="o">></span>`), + name: "empty.yaml", + code: "", + want: lines(""), + lexerName: "YAML", }, { - name: "eol-no.py", - code: "a=1", - want: lines(`<span class="n">a</span><span class="o">=</span><span class="mi">1</span>`), + name: "tags.txt", + code: "<>", + want: lines("<>"), + lexerName: "Plaintext", }, { - name: "eol-newline1.py", - code: "a=1\n", - want: lines(`<span class="n">a</span><span class="o">=</span><span class="mi">1</span>\n`), + name: "tags.py", + code: "<>", + want: lines(`<span class="o"><</span><span class="o">></span>`), + lexerName: "Python", + }, + { + name: "eol-no.py", + code: "a=1", + want: lines(`<span class="n">a</span><span class="o">=</span><span class="mi">1</span>`), + lexerName: "Python", + }, + { + name: "eol-newline1.py", + code: "a=1\n", + want: lines(`<span class="n">a</span><span class="o">=</span><span class="mi">1</span>\n`), + lexerName: "Python", }, { name: "eol-newline2.py", @@ -54,6 +72,7 @@ func TestFile(t *testing.T) { \n `, ), + lexerName: "Python", }, { name: "empty-line-with-space.py", @@ -73,17 +92,19 @@ c=2 \n <span class="n">c</span><span class="o">=</span><span class="mi">2</span>`, ), + lexerName: "Python", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - out, err := File(tt.name, "", []byte(tt.code)) + out, lexerName, err := File(tt.name, "", []byte(tt.code)) assert.NoError(t, err) expected := strings.Join(tt.want, "\n") actual := strings.Join(out, "\n") assert.Equal(t, strings.Count(actual, "<span"), strings.Count(actual, "</span>")) assert.EqualValues(t, expected, actual) + assert.Equal(t, tt.lexerName, lexerName) }) } } diff --git a/modules/indexer/code/search.go b/modules/indexer/code/search.go index bb7715bafc..df255fa875 100644 --- a/modules/indexer/code/search.go +++ b/modules/indexer/code/search.go @@ -94,6 +94,9 @@ func searchResult(result *SearchResult, startIndex, endIndex int) (*Result, erro lineNumbers[i] = startLineNum + i index += len(line) } + + highlighted, _ := highlight.Code(result.Filename, "", formattedLinesBuffer.String()) + return &Result{ RepoID: result.RepoID, Filename: result.Filename, @@ -102,7 +105,7 @@ func searchResult(result *SearchResult, startIndex, endIndex int) (*Result, erro Language: result.Language, Color: result.Color, LineNumbers: lineNumbers, - FormattedLines: highlight.Code(result.Filename, "", formattedLinesBuffer.String()), + FormattedLines: highlighted, }, nil } diff --git a/modules/util/util.go b/modules/util/util.go index be60fe4b4b..6df47ca568 100644 --- a/modules/util/util.go +++ b/modules/util/util.go @@ -186,13 +186,21 @@ func ToUpperASCII(s string) string { return string(b) } -var titleCaser = cases.Title(language.English) +var ( + titleCaser = cases.Title(language.English) + titleCaserNoLower = cases.Title(language.English, cases.NoLower) +) // ToTitleCase returns s with all english words capitalized func ToTitleCase(s string) string { return titleCaser.String(s) } +// ToTitleCaseNoLower returns s with all english words capitalized without lowercasing +func ToTitleCaseNoLower(s string) string { + return titleCaserNoLower.String(s) +} + var ( whitespaceOnly = regexp.MustCompile("(?m)^[ \t]+$") leadingWhitespace = regexp.MustCompile("(?m)(^[ \t]*)(?:[^ \t\n])") diff --git a/routers/web/repo/blame.go b/routers/web/repo/blame.go index 64a6f0ec53..52d5c3c6e6 100644 --- a/routers/web/repo/blame.go +++ b/routers/web/repo/blame.go @@ -100,6 +100,8 @@ func RefBlame(ctx *context.Context) { ctx.Data["FileName"] = blob.Name() ctx.Data["NumLines"], err = blob.GetBlobLineCount() + ctx.Data["NumLinesSet"] = true + if err != nil { ctx.NotFound("GetBlobLineCount", err) return @@ -237,6 +239,8 @@ func renderBlame(ctx *context.Context, blameParts []git.BlamePart, commitNames m rows := make([]*blameRow, 0) escapeStatus := &charset.EscapeStatus{} + var lexerName string + i := 0 commitCnt := 0 for _, part := range blameParts { @@ -278,7 +282,13 @@ func renderBlame(ctx *context.Context, blameParts []git.BlamePart, commitNames m line += "\n" } fileName := fmt.Sprintf("%v", ctx.Data["FileName"]) - line = highlight.Code(fileName, language, line) + line, lexerNameForLine := highlight.Code(fileName, language, line) + + // set lexer name to the first detected lexer. this is certainly suboptimal and + // we should instead highlight the whole file at once + if lexerName == "" { + lexerName = lexerNameForLine + } br.EscapeStatus, line = charset.EscapeControlHTML(line, ctx.Locale) br.Code = gotemplate.HTML(line) @@ -290,4 +300,5 @@ func renderBlame(ctx *context.Context, blameParts []git.BlamePart, commitNames m ctx.Data["EscapeStatus"] = escapeStatus ctx.Data["BlameRows"] = rows ctx.Data["CommitCnt"] = commitCnt + ctx.Data["LexerName"] = lexerName } diff --git a/routers/web/repo/view.go b/routers/web/repo/view.go index e7aca04819..7500dbb34b 100644 --- a/routers/web/repo/view.go +++ b/routers/web/repo/view.go @@ -568,7 +568,8 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry, treeLink, rawLink st language = "" } } - fileContent, err := highlight.File(blob.Name(), language, buf) + fileContent, lexerName, err := highlight.File(blob.Name(), language, buf) + ctx.Data["LexerName"] = lexerName if err != nil { log.Error("highlight.File failed, fallback to plain text: %v", err) fileContent = highlight.PlainText(buf) diff --git a/services/gitdiff/gitdiff.go b/services/gitdiff/gitdiff.go index 3c8c5c81a5..d24c0ac082 100644 --- a/services/gitdiff/gitdiff.go +++ b/services/gitdiff/gitdiff.go @@ -280,7 +280,8 @@ func DiffInlineWithUnicodeEscape(s template.HTML, locale translation.Locale) Dif // DiffInlineWithHighlightCode makes a DiffInline with code highlight and hidden unicode characters escaped func DiffInlineWithHighlightCode(fileName, language, code string, locale translation.Locale) DiffInline { - status, content := charset.EscapeControlHTML(highlight.Code(fileName, language, code), locale) + highlighted, _ := highlight.Code(fileName, language, code) + status, content := charset.EscapeControlHTML(highlighted, locale) return DiffInline{EscapeStatus: status, Content: template.HTML(content)} } diff --git a/services/gitdiff/highlightdiff.go b/services/gitdiff/highlightdiff.go index 4ceada4d7e..727827232d 100644 --- a/services/gitdiff/highlightdiff.go +++ b/services/gitdiff/highlightdiff.go @@ -91,8 +91,8 @@ func (hcd *highlightCodeDiff) diffWithHighlight(filename, language, codeA, codeB hcd.collectUsedRunes(codeA) hcd.collectUsedRunes(codeB) - highlightCodeA := highlight.Code(filename, language, codeA) - highlightCodeB := highlight.Code(filename, language, codeB) + highlightCodeA, _ := highlight.Code(filename, language, codeA) + highlightCodeB, _ := highlight.Code(filename, language, codeB) highlightCodeA = hcd.convertToPlaceholders(highlightCodeA) highlightCodeB = hcd.convertToPlaceholders(highlightCodeB) diff --git a/templates/repo/blame.tmpl b/templates/repo/blame.tmpl index b697573d24..e4a10ee57d 100644 --- a/templates/repo/blame.tmpl +++ b/templates/repo/blame.tmpl @@ -1,14 +1,9 @@ <div class="{{TabSizeClass .Editorconfig .FileName}} non-diff-file-content"> - <h4 class="file-header ui top attached header df ac sb"> - <div class="file-header-left df ac"> - <div class="file-info text grey normal mono"> - <div class="file-info-entry"> - {{.NumLines}} {{.locale.TrN .NumLines "repo.line" "repo.lines"}} - </div> - <div class="file-info-entry">{{FileSize .FileSize}}</div> - </div> + <h4 class="file-header ui top attached header df ac sb fw"> + <div class="file-header-left df ac py-3 pr-4"> + {{template "repo/file_info" .}} </div> - <div class="file-header-right file-actions df ac"> + <div class="file-header-right file-actions df ac fw"> <div class="ui buttons"> <a class="ui tiny button" href="{{$.RawFileLink}}">{{.locale.Tr "repo.file_raw"}}</a> {{if not .IsViewCommit}} diff --git a/templates/repo/file_info.tmpl b/templates/repo/file_info.tmpl new file mode 100644 index 0000000000..90a831fb8e --- /dev/null +++ b/templates/repo/file_info.tmpl @@ -0,0 +1,28 @@ +<div class="file-info text grey normal mono"> + {{if .FileIsSymlink}} + <div class="file-info-entry"> + {{.locale.Tr "repo.symbolic_link"}} + </div> + {{end}} + {{if .NumLinesSet}}{{/* Explicit attribute needed to show 0 line changes */}} + <div class="file-info-entry"> + {{.NumLines}} {{.locale.TrN .NumLines "repo.line" "repo.lines"}} + </div> + {{end}} + {{if .FileSize}} + <div class="file-info-entry"> + {{FileSize .FileSize}}{{if .IsLFSFile}} ({{.locale.Tr "repo.stored_lfs"}}){{end}} + </div> + {{end}} + {{if .LFSLock}} + <div class="file-info-entry ui tooltip" data-content="{{.LFSLockHint}}"> + {{svg "octicon-lock" 16 "mr-2"}} + <a href="{{.LFSLockOwnerHomeLink}}">{{.LFSLockOwner}}</a> + </div> + {{end}} + {{if .LexerName}} + <div class="file-info-entry"> + {{.LexerName}} + </div> + {{end}} +</div> diff --git a/templates/repo/view_file.tmpl b/templates/repo/view_file.tmpl index 60d2a812de..321600a997 100644 --- a/templates/repo/view_file.tmpl +++ b/templates/repo/view_file.tmpl @@ -6,38 +6,16 @@ </div> </div> {{end}} - <h4 class="file-header ui top attached header df ac sb"> - <div class="file-header-left df ac pr-4"> + <h4 class="file-header ui top attached header df ac sb fw"> + <div class="file-header-left df ac py-3 pr-4"> {{if .ReadmeInList}} {{svg "octicon-book" 16 "mr-3"}} <strong>{{.FileName}}</strong> {{else}} - <div class="file-info text grey normal mono"> - {{if .FileIsSymlink}} - <div class="file-info-entry"> - {{.locale.Tr "repo.symbolic_link"}} - </div> - {{end}} - {{if .NumLinesSet}} - <div class="file-info-entry"> - {{.NumLines}} {{.locale.TrN .NumLines "repo.line" "repo.lines"}} - </div> - {{end}} - {{if .FileSize}} - <div class="file-info-entry"> - {{FileSize .FileSize}}{{if .IsLFSFile}} ({{.locale.Tr "repo.stored_lfs"}}){{end}} - </div> - {{end}} - {{if .LFSLock}} - <div class="file-info-entry ui tooltip" data-content="{{.LFSLockHint}}"> - {{svg "octicon-lock" 16 "mr-2"}} - <a href="{{.LFSLockOwnerHomeLink}}">{{.LFSLockOwner}}</a> - </div> - {{end}} - </div> + {{template "repo/file_info" .}} {{end}} </div> - <div class="file-header-right file-actions df ac"> + <div class="file-header-right file-actions df ac fw"> {{if .HasSourceRenderedToggle}} <div class="ui compact icon buttons two-toggle-buttons"> <a href="{{$.Link}}?display=source" class="ui mini basic button tooltip {{if .IsDisplayingSource}}active{{end}}" data-content="{{.locale.Tr "repo.file_view_source"}}" data-position="bottom center">{{svg "octicon-code" 15}}</a> |