From f80593bb23fad75c9642bcd0096c4881cf75ad5d Mon Sep 17 00:00:00 2001 From: silverwind Date: Fri, 3 Apr 2026 10:19:55 +0200 Subject: [PATCH] Extract shared ControlCharPicture helper and improve badge styling Extract charset.ControlCharPicture() shared between highlight and charset escape paths. Add controlCharHTML helper to deduplicate HTML template. Style control char badges with body color on gray background matching the original styling. Co-Authored-By: Claude (Opus 4.6) --- modules/charset/escape_stream.go | 24 ++++++++++++++++++------ modules/highlight/highlight.go | 13 +++++++++---- web_src/css/modules/charescape.css | 5 ++++- 3 files changed, 31 insertions(+), 11 deletions(-) diff --git a/modules/charset/escape_stream.go b/modules/charset/escape_stream.go index 98a25e9e55..5aa15a6fa9 100644 --- a/modules/charset/escape_stream.go +++ b/modules/charset/escape_stream.go @@ -18,6 +18,19 @@ import ( // VScode defaultWordRegexp var defaultWordRegexp = regexp.MustCompile(`(-?\d*\.\d\w*)|([^\` + "`" + `\~\!\@\#\$\%\^\&\*\(\)\-\=\+\[\{\]\}\\\|\;\:\'\"\,\.\<\>\/\?\s\x00-\x1f]+)`) +// ControlCharPicture returns the Unicode Control Picture for ASCII control +// characters (0x00-0x1F → U+2400-U+241F, 0x7F → U+2421). For other runes it +// returns 0, false. +func ControlCharPicture(r rune) (rune, bool) { + if r >= 0 && r <= 0x1f { + return 0x2400 + r, true + } + if r == 0x7f { + return 0x2421, true + } + return 0, false +} + func NewEscapeStreamer(locale translation.Locale, next HTMLStreamer, allowed ...rune) HTMLStreamer { allowedM := make(map[rune]bool, len(allowed)) for _, v := range allowed { @@ -199,12 +212,11 @@ func (e *escapeStreamer) invisibleRune(r rune) error { e.escaped.Escaped = true e.escaped.HasInvisible = true - // Use Unicode Control Pictures for ASCII control chars - escaped := fmt.Sprintf("[U+%04X]", r) - if r >= 0 && r <= 0x1f { - escaped = string(0x2400 + r) - } else if r == 0x7f { - escaped = string(rune(0x2421)) + var escaped string + if pic, ok := ControlCharPicture(r); ok { + escaped = string(pic) + } else { + escaped = fmt.Sprintf("[U+%04X]", r) } if err := e.PassthroughHTMLStreamer.StartTag("span", html.Attribute{ diff --git a/modules/highlight/highlight.go b/modules/highlight/highlight.go index 35d041527a..ec56a28851 100644 --- a/modules/highlight/highlight.go +++ b/modules/highlight/highlight.go @@ -10,6 +10,7 @@ import ( "slices" "sync" + "code.gitea.io/gitea/modules/charset" "code.gitea.io/gitea/modules/log" "code.gitea.io/gitea/modules/setting" "code.gitea.io/gitea/modules/util" @@ -43,12 +44,12 @@ func globalVars() *globalVarsType { globalVarsPtr.githubStyles = styles.Get("github") globalVarsPtr.highlightMapping = setting.GetHighlightMapping() globalVarsPtr.escCtrlCharsMap = make([]template.HTML, 256) - // ASCII control characters 0x00-0x1F map to Unicode Control Pictures U+2400-U+241F for i := range 0x20 { - globalVarsPtr.escCtrlCharsMap[i] = template.HTML(`` + string(byte(i)) + ``) + pic, _ := charset.ControlCharPicture(rune(i)) + globalVarsPtr.escCtrlCharsMap[i] = controlCharHTML(pic, byte(i)) } - // DEL (0x7F) maps to U+2421 - globalVarsPtr.escCtrlCharsMap[0x7f] = template.HTML(`` + string(byte(0x7f)) + ``) + pic, _ := charset.ControlCharPicture(0x7f) + globalVarsPtr.escCtrlCharsMap[0x7f] = controlCharHTML(pic, 0x7f) globalVarsPtr.escCtrlCharsMap['\t'] = "" globalVarsPtr.escCtrlCharsMap['\n'] = "" globalVarsPtr.escCtrlCharsMap['\r'] = "" @@ -64,6 +65,10 @@ func globalVars() *globalVarsType { return globalVarsPtr } +func controlCharHTML(pic rune, char byte) template.HTML { + return template.HTML(`` + string(char) + ``) +} + func escapeByMap(code []byte, escapeMap []template.HTML) template.HTML { firstEscapePos := -1 for i, c := range code { diff --git a/web_src/css/modules/charescape.css b/web_src/css/modules/charescape.css index b72d7bdf45..6f3dc99028 100644 --- a/web_src/css/modules/charescape.css +++ b/web_src/css/modules/charescape.css @@ -18,7 +18,10 @@ Only show the real-char: .broken-code-point[data-escaped]::before { visibility: visible; content: attr(data-escaped); - color: var(--color-red); + border-radius: 2px; + padding: 0 1px; + color: var(--color-body); + background: var(--color-text-light-1); } .broken-code-point[data-escaped] .char {