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 {