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  }