diff options
author | zeripath <art27@cantab.net> | 2022-08-13 19:32:34 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-08-13 19:32:34 +0100 |
commit | 99efa02edf4e3750e19bc28f7b856791356d1583 (patch) | |
tree | f82d0917d02e6c3a6025b0f08c67f32b2f6b41d0 /routers/web/repo | |
parent | 11dc6df5be5ae1da8d570e440f97060d2284dd13 (diff) | |
download | gitea-99efa02edf4e3750e19bc28f7b856791356d1583.tar.gz gitea-99efa02edf4e3750e19bc28f7b856791356d1583.zip |
Switch Unicode Escaping to a VSCode-like system (#19990)
This PR rewrites the invisible unicode detection algorithm to more
closely match that of the Monaco editor on the system. It provides a
technique for detecting ambiguous characters and relaxes the detection
of combining marks.
Control characters are in addition detected as invisible in this
implementation whereas they are not on monaco but this is related to
font issues.
Close #19913
Signed-off-by: Andrew Thornton <art27@cantab.net>
Diffstat (limited to 'routers/web/repo')
-rw-r--r-- | routers/web/repo/blame.go | 6 | ||||
-rw-r--r-- | routers/web/repo/lfs.go | 2 | ||||
-rw-r--r-- | routers/web/repo/view.go | 59 | ||||
-rw-r--r-- | routers/web/repo/wiki.go | 35 |
4 files changed, 64 insertions, 38 deletions
diff --git a/routers/web/repo/blame.go b/routers/web/repo/blame.go index 06c43aec19..c53a53b471 100644 --- a/routers/web/repo/blame.go +++ b/routers/web/repo/blame.go @@ -40,7 +40,7 @@ type blameRow struct { CommitMessage string CommitSince gotemplate.HTML Code gotemplate.HTML - EscapeStatus charset.EscapeStatus + EscapeStatus *charset.EscapeStatus } // RefBlame render blame page @@ -235,7 +235,7 @@ func renderBlame(ctx *context.Context, blameParts []git.BlamePart, commitNames m } lines := make([]string, 0) rows := make([]*blameRow, 0) - escapeStatus := charset.EscapeStatus{} + escapeStatus := &charset.EscapeStatus{} i := 0 commitCnt := 0 @@ -280,7 +280,7 @@ func renderBlame(ctx *context.Context, blameParts []git.BlamePart, commitNames m fileName := fmt.Sprintf("%v", ctx.Data["FileName"]) line = highlight.Code(fileName, language, line) - br.EscapeStatus, line = charset.EscapeControlString(line) + br.EscapeStatus, line = charset.EscapeControlHTML(line, ctx.Locale) br.Code = gotemplate.HTML(line) rows = append(rows, br) escapeStatus = escapeStatus.Or(br.EscapeStatus) diff --git a/routers/web/repo/lfs.go b/routers/web/repo/lfs.go index 0e446f2de0..baec48bfea 100644 --- a/routers/web/repo/lfs.go +++ b/routers/web/repo/lfs.go @@ -309,7 +309,7 @@ func LFSFileGet(ctx *context.Context) { // Building code view blocks with line number on server side. escapedContent := &bytes.Buffer{} - ctx.Data["EscapeStatus"], _ = charset.EscapeControlReader(rd, escapedContent) + ctx.Data["EscapeStatus"], _ = charset.EscapeControlReader(rd, escapedContent, ctx.Locale) var output bytes.Buffer lines := strings.Split(escapedContent.String(), "\n") diff --git a/routers/web/repo/view.go b/routers/web/repo/view.go index 6a9c6b9bba..72ffda7e01 100644 --- a/routers/web/repo/view.go +++ b/routers/web/repo/view.go @@ -328,35 +328,31 @@ func renderReadmeFile(ctx *context.Context, readmeFile *namedBlob, readmeTreelin if markupType := markup.Type(readmeFile.name); markupType != "" { ctx.Data["IsMarkup"] = true ctx.Data["MarkupType"] = markupType - var result strings.Builder - err := markup.Render(&markup.RenderContext{ + + ctx.Data["EscapeStatus"], ctx.Data["FileContent"], err = markupRender(ctx, &markup.RenderContext{ Ctx: ctx, RelativePath: path.Join(ctx.Repo.TreePath, readmeFile.name), // ctx.Repo.TreePath is the directory not the Readme so we must append the Readme filename (and path). URLPrefix: readmeTreelink, Metas: ctx.Repo.Repository.ComposeDocumentMetas(), GitRepo: ctx.Repo.GitRepo, - }, rd, &result) + }, rd) if err != nil { - log.Error("Render failed: %v then fallback", err) + log.Error("Render failed for %s in %-v: %v Falling back to rendering source", readmeFile.name, ctx.Repo.Repository, err) buf := &bytes.Buffer{} - ctx.Data["EscapeStatus"], _ = charset.EscapeControlReader(rd, buf) + ctx.Data["EscapeStatus"], _ = charset.EscapeControlReader(rd, buf, ctx.Locale) ctx.Data["FileContent"] = strings.ReplaceAll( gotemplate.HTMLEscapeString(buf.String()), "\n", `<br>`, ) - } else { - ctx.Data["EscapeStatus"], ctx.Data["FileContent"] = charset.EscapeControlString(result.String()) } } else { ctx.Data["IsRenderedHTML"] = true buf := &bytes.Buffer{} - ctx.Data["EscapeStatus"], err = charset.EscapeControlReader(rd, buf) + ctx.Data["EscapeStatus"], err = charset.EscapeControlReader(rd, &charset.BreakWriter{Writer: buf}, ctx.Locale, charset.RuneNBSP) if err != nil { log.Error("Read failed: %v", err) } - ctx.Data["FileContent"] = strings.ReplaceAll( - gotemplate.HTMLEscapeString(buf.String()), "\n", `<br>`, - ) + ctx.Data["FileContent"] = buf.String() } } @@ -498,32 +494,30 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry, treeLink, rawLink st if markupType != "" && !shouldRenderSource { ctx.Data["IsMarkup"] = true ctx.Data["MarkupType"] = markupType - var result strings.Builder if !detected { markupType = "" } metas := ctx.Repo.Repository.ComposeDocumentMetas() metas["BranchNameSubURL"] = ctx.Repo.BranchNameSubURL() - err := markup.Render(&markup.RenderContext{ + ctx.Data["EscapeStatus"], ctx.Data["FileContent"], err = markupRender(ctx, &markup.RenderContext{ Ctx: ctx, Type: markupType, RelativePath: ctx.Repo.TreePath, URLPrefix: path.Dir(treeLink), Metas: metas, GitRepo: ctx.Repo.GitRepo, - }, rd, &result) + }, rd) if err != nil { ctx.ServerError("Render", err) return } // to prevent iframe load third-party url ctx.Resp.Header().Add("Content-Security-Policy", "frame-src 'self'") - ctx.Data["EscapeStatus"], ctx.Data["FileContent"] = charset.EscapeControlString(result.String()) } else if readmeExist && !shouldRenderSource { buf := &bytes.Buffer{} ctx.Data["IsRenderedHTML"] = true - ctx.Data["EscapeStatus"], _ = charset.EscapeControlReader(rd, buf) + ctx.Data["EscapeStatus"], _ = charset.EscapeControlReader(rd, buf, ctx.Locale) ctx.Data["FileContent"] = strings.ReplaceAll( gotemplate.HTMLEscapeString(buf.String()), "\n", `<br>`, @@ -570,12 +564,13 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry, treeLink, rawLink st log.Error("highlight.File failed, fallback to plain text: %v", err) fileContent = highlight.PlainText(buf) } - status, _ := charset.EscapeControlReader(bytes.NewReader(buf), io.Discard) - ctx.Data["EscapeStatus"] = status - statuses := make([]charset.EscapeStatus, len(fileContent)) + status := &charset.EscapeStatus{} + statuses := make([]*charset.EscapeStatus, len(fileContent)) for i, line := range fileContent { - statuses[i], fileContent[i] = charset.EscapeControlString(line) + statuses[i], fileContent[i] = charset.EscapeControlHTML(line, ctx.Locale) + status = status.Or(statuses[i]) } + ctx.Data["EscapeStatus"] = status ctx.Data["FileContent"] = fileContent ctx.Data["LineEscapeStatus"] = statuses } @@ -613,20 +608,17 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry, treeLink, rawLink st rd := io.MultiReader(bytes.NewReader(buf), dataRc) ctx.Data["IsMarkup"] = true ctx.Data["MarkupType"] = markupType - var result strings.Builder - err := markup.Render(&markup.RenderContext{ + ctx.Data["EscapeStatus"], ctx.Data["FileContent"], err = markupRender(ctx, &markup.RenderContext{ Ctx: ctx, RelativePath: ctx.Repo.TreePath, URLPrefix: path.Dir(treeLink), Metas: ctx.Repo.Repository.ComposeDocumentMetas(), GitRepo: ctx.Repo.GitRepo, - }, rd, &result) + }, rd) if err != nil { ctx.ServerError("Render", err) return } - - ctx.Data["EscapeStatus"], ctx.Data["FileContent"] = charset.EscapeControlString(result.String()) } } @@ -645,6 +637,23 @@ func renderFile(ctx *context.Context, entry *git.TreeEntry, treeLink, rawLink st } } +func markupRender(ctx *context.Context, renderCtx *markup.RenderContext, input io.Reader) (escaped *charset.EscapeStatus, output string, err error) { + markupRd, markupWr := io.Pipe() + defer markupWr.Close() + done := make(chan struct{}) + go func() { + sb := &strings.Builder{} + // We allow NBSP here this is rendered + escaped, _ = charset.EscapeControlReader(markupRd, sb, ctx.Locale, charset.RuneNBSP) + output = sb.String() + close(done) + }() + err = markup.Render(renderCtx, input, markupWr) + _ = markupWr.CloseWithError(err) + <-done + return escaped, output, err +} + func safeURL(address string) string { u, err := url.Parse(address) if err != nil { diff --git a/routers/web/repo/wiki.go b/routers/web/repo/wiki.go index e4134028aa..1af511f50a 100644 --- a/routers/web/repo/wiki.go +++ b/routers/web/repo/wiki.go @@ -239,9 +239,28 @@ func renderViewPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) { Metas: ctx.Repo.Repository.ComposeDocumentMetas(), IsWiki: true, } - - var buf strings.Builder - if err := markdown.Render(rctx, bytes.NewReader(data), &buf); err != nil { + buf := &strings.Builder{} + + renderFn := func(data []byte) (escaped *charset.EscapeStatus, output string, err error) { + markupRd, markupWr := io.Pipe() + defer markupWr.Close() + done := make(chan struct{}) + go func() { + // We allow NBSP here this is rendered + escaped, _ = charset.EscapeControlReader(markupRd, buf, ctx.Locale, charset.RuneNBSP) + output = buf.String() + buf.Reset() + close(done) + }() + + err = markdown.Render(rctx, bytes.NewReader(data), markupWr) + _ = markupWr.CloseWithError(err) + <-done + return escaped, output, err + } + + ctx.Data["EscapeStatus"], ctx.Data["content"], err = renderFn(data) + if err != nil { if wikiRepo != nil { wikiRepo.Close() } @@ -249,11 +268,10 @@ func renderViewPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) { return nil, nil } - ctx.Data["EscapeStatus"], ctx.Data["content"] = charset.EscapeControlString(buf.String()) - if !isSideBar { buf.Reset() - if err := markdown.Render(rctx, bytes.NewReader(sidebarContent), &buf); err != nil { + ctx.Data["sidebarEscapeStatus"], ctx.Data["sidebarContent"], err = renderFn(sidebarContent) + if err != nil { if wikiRepo != nil { wikiRepo.Close() } @@ -261,14 +279,14 @@ func renderViewPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) { return nil, nil } ctx.Data["sidebarPresent"] = sidebarContent != nil - ctx.Data["sidebarEscapeStatus"], ctx.Data["sidebarContent"] = charset.EscapeControlString(buf.String()) } else { ctx.Data["sidebarPresent"] = false } if !isFooter { buf.Reset() - if err := markdown.Render(rctx, bytes.NewReader(footerContent), &buf); err != nil { + ctx.Data["footerEscapeStatus"], ctx.Data["footerContent"], err = renderFn(footerContent) + if err != nil { if wikiRepo != nil { wikiRepo.Close() } @@ -276,7 +294,6 @@ func renderViewPage(ctx *context.Context) (*git.Repository, *git.TreeEntry) { return nil, nil } ctx.Data["footerPresent"] = footerContent != nil - ctx.Data["footerEscapeStatus"], ctx.Data["footerContent"] = charset.EscapeControlString(buf.String()) } else { ctx.Data["footerPresent"] = false } |