This commit is contained in:
wxiaoguang 2025-03-10 19:51:33 +08:00
parent c102492e5a
commit 0e12b7c17c
9 changed files with 224 additions and 3275 deletions

View File

@ -18,13 +18,9 @@ import (
) )
type materialIconRulesData struct { type materialIconRulesData struct {
IconDefinitions map[string]*struct {
IconPath string `json:"iconPath"`
} `json:"iconDefinitions"`
FileNames map[string]string `json:"fileNames"` FileNames map[string]string `json:"fileNames"`
FolderNames map[string]string `json:"folderNames"` FolderNames map[string]string `json:"folderNames"`
FileExtensions map[string]string `json:"fileExtensions"` FileExtensions map[string]string `json:"fileExtensions"`
LanguageIDs map[string]string `json:"languageIds"`
} }
type MaterialIconProvider struct { type MaterialIconProvider struct {
@ -36,6 +32,7 @@ type MaterialIconProvider struct {
var materialIconProvider MaterialIconProvider var materialIconProvider MaterialIconProvider
func DefaultMaterialIconProvider() *MaterialIconProvider { func DefaultMaterialIconProvider() *MaterialIconProvider {
materialIconProvider.once.Do(materialIconProvider.loadData)
return &materialIconProvider return &materialIconProvider
} }
@ -88,8 +85,6 @@ func (m *MaterialIconProvider) renderFileIconSVG(ctx reqctx.RequestContext, name
} }
func (m *MaterialIconProvider) FileIcon(ctx reqctx.RequestContext, entry *git.TreeEntry) template.HTML { func (m *MaterialIconProvider) FileIcon(ctx reqctx.RequestContext, entry *git.TreeEntry) template.HTML {
m.once.Do(m.loadData)
if m.rules == nil { if m.rules == nil {
return BasicThemeIcon(entry) return BasicThemeIcon(entry)
} }
@ -101,7 +96,7 @@ func (m *MaterialIconProvider) FileIcon(ctx reqctx.RequestContext, entry *git.Tr
return svg.RenderHTML("octicon-file-symlink-file") // TODO: find some better icons for them return svg.RenderHTML("octicon-file-symlink-file") // TODO: find some better icons for them
} }
name := m.findIconName(entry) name := m.findIconNameByGit(entry)
if name == "folder" { if name == "folder" {
// the material icon pack's "folder" icon doesn't look good, so use our built-in one // the material icon pack's "folder" icon doesn't look good, so use our built-in one
return svg.RenderHTML("material-folder-generic") return svg.RenderHTML("material-folder-generic")
@ -112,34 +107,23 @@ func (m *MaterialIconProvider) FileIcon(ctx reqctx.RequestContext, entry *git.Tr
return svg.RenderHTML("octicon-file") return svg.RenderHTML("octicon-file")
} }
func (m *MaterialIconProvider) findIconName(entry *git.TreeEntry) string { func (m *MaterialIconProvider) FindIconName(name string, isDir bool) string {
if entry.IsSubModule() {
return "folder-git"
}
iconsData := m.rules iconsData := m.rules
fileName := path.Base(entry.Name()) fileNameLower := strings.ToLower(path.Base(name))
if isDir {
if entry.IsDir() { if s, ok := iconsData.FolderNames[fileNameLower]; ok {
if s, ok := iconsData.FolderNames[fileName]; ok {
return s
}
if s, ok := iconsData.FolderNames[strings.ToLower(fileName)]; ok {
return s return s
} }
return "folder" return "folder"
} }
if s, ok := iconsData.FileNames[fileName]; ok { if s, ok := iconsData.FileNames[fileNameLower]; ok {
return s
}
if s, ok := iconsData.FileNames[strings.ToLower(fileName)]; ok {
return s return s
} }
for i := len(fileName) - 1; i >= 0; i-- { for i := len(fileNameLower) - 1; i >= 0; i-- {
if fileName[i] == '.' { if fileNameLower[i] == '.' {
ext := fileName[i+1:] ext := fileNameLower[i+1:]
if s, ok := iconsData.FileExtensions[ext]; ok { if s, ok := iconsData.FileExtensions[ext]; ok {
return s return s
} }
@ -148,3 +132,10 @@ func (m *MaterialIconProvider) findIconName(entry *git.TreeEntry) string {
return "file" return "file"
} }
func (m *MaterialIconProvider) findIconNameByGit(entry *git.TreeEntry) string {
if entry.IsSubModule() {
return "folder-git"
}
return m.FindIconName(entry.Name(), entry.IsDir())
}

View File

@ -0,0 +1,24 @@
// Copyright 2025 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package fileicon_test
import (
"testing"
"code.gitea.io/gitea/models/unittest"
"code.gitea.io/gitea/modules/fileicon"
"github.com/stretchr/testify/assert"
)
func TestMain(m *testing.M) {
unittest.MainTest(m, &unittest.TestOptions{FixtureFiles: []string{}})
}
func TestFindIconName(t *testing.T) {
unittest.PrepareTestEnv(t)
p := fileicon.DefaultMaterialIconProvider()
assert.Equal(t, "php", p.FindIconName("foo.php", false))
assert.Equal(t, "php", p.FindIconName("foo.PHP", false))
}

View File

@ -65,7 +65,7 @@ func (tes Entries) GetCommitsInfo(ctx context.Context, commit *Commit, treePath
log.Debug("missing commit for %s", entry.Name()) log.Debug("missing commit for %s", entry.Name())
} }
// If the entry if a submodule add a submodule file for this // If the entry is a submodule add a submodule file for this
if entry.IsSubModule() { if entry.IsSubModule() {
subModuleURL := "" subModuleURL := ""
var fullPath string var fullPath string
@ -85,8 +85,8 @@ func (tes Entries) GetCommitsInfo(ctx context.Context, commit *Commit, treePath
} }
// Retrieve the commit for the treePath itself (see above). We basically // Retrieve the commit for the treePath itself (see above). We basically
// get it for free during the tree traversal and it's used for listing // get it for free during the tree traversal, and it's used for listing
// pages to display information about newest commit for a given path. // pages to display information about the newest commit for a given path.
var treeCommit *Commit var treeCommit *Commit
var ok bool var ok bool
if treePath == "" { if treePath == "" {

File diff suppressed because it is too large Load Diff

View File

@ -900,7 +900,6 @@ func ExcerptBlob(ctx *context.Context) {
} }
section := &gitdiff.DiffSection{ section := &gitdiff.DiffSection{
FileName: filePath, FileName: filePath,
Name: filePath,
} }
if direction == "up" && (idxLeft-lastLeft) > chunkSize { if direction == "up" && (idxLeft-lastLeft) > chunkSize {
idxLeft -= chunkSize idxLeft -= chunkSize

View File

@ -203,7 +203,6 @@ func getLineContent(content string, locale translation.Locale) DiffInline {
type DiffSection struct { type DiffSection struct {
file *DiffFile file *DiffFile
FileName string FileName string
Name string
Lines []*DiffLine Lines []*DiffLine
} }
@ -279,7 +278,7 @@ func (diffSection *DiffSection) getLineContentForRender(lineIdx int, diffLine *D
if setting.Git.DisableDiffHighlight { if setting.Git.DisableDiffHighlight {
return template.HTML(html.EscapeString(diffLine.Content[1:])) return template.HTML(html.EscapeString(diffLine.Content[1:]))
} }
h, _ = highlight.Code(diffSection.Name, fileLanguage, diffLine.Content[1:]) h, _ = highlight.Code(diffSection.FileName, fileLanguage, diffLine.Content[1:])
return h return h
} }
@ -292,20 +291,31 @@ func (diffSection *DiffSection) getDiffLineForRender(diffLineType DiffLineType,
highlightedLeftLines, highlightedRightLines = diffSection.file.highlightedLeftLines, diffSection.file.highlightedRightLines highlightedLeftLines, highlightedRightLines = diffSection.file.highlightedLeftLines, diffSection.file.highlightedRightLines
} }
var lineHTML template.HTML
hcd := newHighlightCodeDiff() hcd := newHighlightCodeDiff()
var diff1, diff2, lineHTML template.HTML if diffLineType == DiffLinePlain {
if leftLine != nil { // left and right are the same, no need to do line-level diff
diff1 = diffSection.getLineContentForRender(leftLine.LeftIdx, leftLine, fileLanguage, highlightedLeftLines) if leftLine != nil {
lineHTML = util.Iif(diffLineType == DiffLinePlain, diff1, "") lineHTML = diffSection.getLineContentForRender(leftLine.LeftIdx, leftLine, fileLanguage, highlightedLeftLines)
} } else if rightLine != nil {
if rightLine != nil { lineHTML = diffSection.getLineContentForRender(rightLine.RightIdx, rightLine, fileLanguage, highlightedRightLines)
diff2 = diffSection.getLineContentForRender(rightLine.RightIdx, rightLine, fileLanguage, highlightedRightLines) }
lineHTML = util.Iif(diffLineType == DiffLinePlain, diff2, "") } else {
} var diff1, diff2 template.HTML
if diffLineType != DiffLinePlain { if leftLine != nil {
// it seems that Gitea doesn't need the line wrapper of Chroma, so do not add them back diff1 = diffSection.getLineContentForRender(leftLine.LeftIdx, leftLine, fileLanguage, highlightedLeftLines)
// if the line wrappers are still needed in the future, it can be added back by "diffLineWithHighlightWrapper(hcd.lineWrapperTags. ...)" }
lineHTML = hcd.diffLineWithHighlight(diffLineType, diff1, diff2) if rightLine != nil {
diff2 = diffSection.getLineContentForRender(rightLine.RightIdx, rightLine, fileLanguage, highlightedRightLines)
}
if diff1 != "" && diff2 != "" {
// if only some parts of a line are changed, highlight these changed parts as "deleted/added".
lineHTML = hcd.diffLineWithHighlight(diffLineType, diff1, diff2)
} else {
// if left is empty or right is empty (a line is fully deleted or added), then we do not need to diff anymore.
// the tmpl code already adds background colors for these cases.
lineHTML = util.Iif(diffLineType == DiffLineDel, diff1, diff2)
}
} }
return DiffInlineWithUnicodeEscape(lineHTML, locale) return DiffInlineWithUnicodeEscape(lineHTML, locale)
} }
@ -383,15 +393,22 @@ type DiffLimitedContent struct {
// GetTailSectionAndLimitedContent creates a fake DiffLineSection if the last section is not the end of the file // GetTailSectionAndLimitedContent creates a fake DiffLineSection if the last section is not the end of the file
func (diffFile *DiffFile) GetTailSectionAndLimitedContent(leftCommit, rightCommit *git.Commit) (_ *DiffSection, diffLimitedContent DiffLimitedContent) { func (diffFile *DiffFile) GetTailSectionAndLimitedContent(leftCommit, rightCommit *git.Commit) (_ *DiffSection, diffLimitedContent DiffLimitedContent) {
if len(diffFile.Sections) == 0 || leftCommit == nil || diffFile.Type != DiffFileChange || diffFile.IsBin || diffFile.IsLFSFile { var leftLineCount, rightLineCount int
diffLimitedContent = DiffLimitedContent{}
if diffFile.IsBin || diffFile.IsLFSFile {
return nil, diffLimitedContent
}
if (diffFile.Type == DiffFileDel || diffFile.Type == DiffFileChange) && leftCommit != nil {
leftLineCount, diffLimitedContent.LeftContent = getCommitFileLineCountAndLimitedContent(leftCommit, diffFile.OldName)
}
if (diffFile.Type == DiffFileAdd || diffFile.Type == DiffFileChange) && rightCommit != nil {
rightLineCount, diffLimitedContent.RightContent = getCommitFileLineCountAndLimitedContent(rightCommit, diffFile.OldName)
}
if len(diffFile.Sections) == 0 || diffFile.Type != DiffFileChange {
return nil, diffLimitedContent return nil, diffLimitedContent
} }
lastSection := diffFile.Sections[len(diffFile.Sections)-1] lastSection := diffFile.Sections[len(diffFile.Sections)-1]
lastLine := lastSection.Lines[len(lastSection.Lines)-1] lastLine := lastSection.Lines[len(lastSection.Lines)-1]
leftLineCount, leftContent := getCommitFileLineCountAndLimitedContent(leftCommit, diffFile.Name)
rightLineCount, rightContent := getCommitFileLineCountAndLimitedContent(rightCommit, diffFile.Name)
diffLimitedContent = DiffLimitedContent{LeftContent: leftContent, RightContent: rightContent}
if leftLineCount <= lastLine.LeftIdx || rightLineCount <= lastLine.RightIdx { if leftLineCount <= lastLine.LeftIdx || rightLineCount <= lastLine.RightIdx {
return nil, diffLimitedContent return nil, diffLimitedContent
} }

View File

@ -99,7 +99,7 @@ func (hcd *highlightCodeDiff) diffLineWithHighlightWrapper(lineWrapperTags []str
dmp := defaultDiffMatchPatch() dmp := defaultDiffMatchPatch()
diffs := dmp.DiffMain(convertedCodeA, convertedCodeB, true) diffs := dmp.DiffMain(convertedCodeA, convertedCodeB, true)
diffs = dmp.DiffCleanupEfficiency(diffs) diffs = dmp.DiffCleanupSemantic(diffs)
buf := bytes.NewBuffer(nil) buf := bytes.NewBuffer(nil)

View File

@ -47,7 +47,6 @@ func TestGetDiffPreview(t *testing.T) {
Sections: []*gitdiff.DiffSection{ Sections: []*gitdiff.DiffSection{
{ {
FileName: "README.md", FileName: "README.md",
Name: "",
Lines: []*gitdiff.DiffLine{ Lines: []*gitdiff.DiffLine{
{ {
LeftIdx: 0, LeftIdx: 0,

View File

@ -63,8 +63,18 @@ async function processMaterialFileIcons() {
} }
fs.writeFileSync(fileURLToPath(new URL(`../options/fileicon/material-icon-svgs.json`, import.meta.url)), JSON.stringify(svgSymbols, null, 2)); fs.writeFileSync(fileURLToPath(new URL(`../options/fileicon/material-icon-svgs.json`, import.meta.url)), JSON.stringify(svgSymbols, null, 2));
const iconRules = await readFile(fileURLToPath(new URL(`../node_modules/material-icon-theme/dist/material-icons.json`, import.meta.url))); const iconRulesJson = await readFile(fileURLToPath(new URL(`../node_modules/material-icon-theme/dist/material-icons.json`, import.meta.url)));
const iconRulesPretty = JSON.stringify(JSON.parse(iconRules), null, 2); const iconRules = JSON.parse(iconRulesJson);
// The rules are from VSCode material-icon-theme, we need to adjust them to our needs
// 1. We only use lowercase filenames to match (it should be good enough for most cases and more efficient)
// 2. We do not have a "Language ID" system: https://code.visualstudio.com/docs/languages/identifiers#_known-language-identifiers
// * So we just treat the "Language ID" as file extension, it is not always true, but it is good enough for most cases.
delete iconRules.iconDefinitions;
for (const [k, v] of Object.entries(iconRules.fileNames)) iconRules.fileNames[k.toLowerCase()] = v;
for (const [k, v] of Object.entries(iconRules.folderNames)) iconRules.folderNames[k.toLowerCase()] = v;
for (const [k, v] of Object.entries(iconRules.fileExtensions)) iconRules.fileExtensions[k.toLowerCase()] = v;
for (const [k, v] of Object.entries(iconRules.languageIds)) iconRules.fileExtensions[k.toLowerCase()] = v;
const iconRulesPretty = JSON.stringify(iconRules, null, 2);
fs.writeFileSync(fileURLToPath(new URL(`../options/fileicon/material-icon-rules.json`, import.meta.url)), iconRulesPretty); fs.writeFileSync(fileURLToPath(new URL(`../options/fileicon/material-icon-rules.json`, import.meta.url)), iconRulesPretty);
} }