code.gitea.io/gitea@v1.22.3/services/markup/processorhelper_codepreview.go (about) 1 // Copyright 2024 The Gitea Authors. All rights reserved. 2 // SPDX-License-Identifier: MIT 3 4 package markup 5 6 import ( 7 "bufio" 8 "context" 9 "fmt" 10 "html/template" 11 "strings" 12 13 "code.gitea.io/gitea/models/perm/access" 14 "code.gitea.io/gitea/models/repo" 15 "code.gitea.io/gitea/models/unit" 16 "code.gitea.io/gitea/modules/charset" 17 "code.gitea.io/gitea/modules/gitrepo" 18 "code.gitea.io/gitea/modules/indexer/code" 19 "code.gitea.io/gitea/modules/markup" 20 "code.gitea.io/gitea/modules/setting" 21 gitea_context "code.gitea.io/gitea/services/context" 22 "code.gitea.io/gitea/services/repository/files" 23 ) 24 25 func renderRepoFileCodePreview(ctx context.Context, opts markup.RenderCodePreviewOptions) (template.HTML, error) { 26 opts.LineStop = max(opts.LineStop, opts.LineStart) 27 lineCount := opts.LineStop - opts.LineStart + 1 28 if lineCount <= 0 || lineCount > 140 /* GitHub at most show 140 lines */ { 29 lineCount = 10 30 opts.LineStop = opts.LineStart + lineCount 31 } 32 33 dbRepo, err := repo.GetRepositoryByOwnerAndName(ctx, opts.OwnerName, opts.RepoName) 34 if err != nil { 35 return "", err 36 } 37 38 webCtx, ok := ctx.Value(gitea_context.WebContextKey).(*gitea_context.Context) 39 if !ok { 40 return "", fmt.Errorf("context is not a web context") 41 } 42 doer := webCtx.Doer 43 44 perms, err := access.GetUserRepoPermission(ctx, dbRepo, doer) 45 if err != nil { 46 return "", err 47 } 48 if !perms.CanRead(unit.TypeCode) { 49 return "", fmt.Errorf("no permission") 50 } 51 52 gitRepo, err := gitrepo.OpenRepository(ctx, dbRepo) 53 if err != nil { 54 return "", err 55 } 56 defer gitRepo.Close() 57 58 commit, err := gitRepo.GetCommit(opts.CommitID) 59 if err != nil { 60 return "", err 61 } 62 63 language, _ := files.TryGetContentLanguage(gitRepo, opts.CommitID, opts.FilePath) 64 blob, err := commit.GetBlobByPath(opts.FilePath) 65 if err != nil { 66 return "", err 67 } 68 69 if blob.Size() > setting.UI.MaxDisplayFileSize { 70 return "", fmt.Errorf("file is too large") 71 } 72 73 dataRc, err := blob.DataAsync() 74 if err != nil { 75 return "", err 76 } 77 defer dataRc.Close() 78 79 reader := bufio.NewReader(dataRc) 80 for i := 1; i < opts.LineStart; i++ { 81 if _, err = reader.ReadBytes('\n'); err != nil { 82 return "", err 83 } 84 } 85 86 lineNums := make([]int, 0, lineCount) 87 lineCodes := make([]string, 0, lineCount) 88 for i := opts.LineStart; i <= opts.LineStop; i++ { 89 line, err := reader.ReadString('\n') 90 if err != nil && line == "" { 91 break 92 } 93 94 lineNums = append(lineNums, i) 95 lineCodes = append(lineCodes, line) 96 } 97 realLineStop := max(opts.LineStart, opts.LineStart+len(lineNums)-1) 98 highlightLines := code.HighlightSearchResultCode(opts.FilePath, language, lineNums, strings.Join(lineCodes, "")) 99 100 escapeStatus := &charset.EscapeStatus{} 101 lineEscapeStatus := make([]*charset.EscapeStatus, len(highlightLines)) 102 for i, hl := range highlightLines { 103 lineEscapeStatus[i], hl.FormattedContent = charset.EscapeControlHTML(hl.FormattedContent, webCtx.Base.Locale, charset.RuneNBSP) 104 escapeStatus = escapeStatus.Or(lineEscapeStatus[i]) 105 } 106 107 return webCtx.RenderToHTML("base/markup_codepreview", map[string]any{ 108 "FullURL": opts.FullURL, 109 "FilePath": opts.FilePath, 110 "LineStart": opts.LineStart, 111 "LineStop": realLineStop, 112 "RepoLink": dbRepo.Link(), 113 "CommitID": opts.CommitID, 114 "HighlightLines": highlightLines, 115 "EscapeStatus": escapeStatus, 116 "LineEscapeStatus": lineEscapeStatus, 117 }) 118 }