mirror of
https://github.com/go-gitea/gitea.git
synced 2026-04-03 16:52:10 +02:00
Merge 5edaf14f385d7495fed289f7fb6e3b0cfe88f99e into 30c07c20e94551141cc1873ab14bdd4c104bba94
This commit is contained in:
commit
9876f3061a
@ -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,19 @@ func (e *escapeStreamer) invisibleRune(r rune) error {
|
||||
e.escaped.Escaped = true
|
||||
e.escaped.HasInvisible = true
|
||||
|
||||
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{
|
||||
Key: "class",
|
||||
Val: "escaped-code-point",
|
||||
}, html.Attribute{
|
||||
Key: "data-escaped",
|
||||
Val: fmt.Sprintf("[U+%04X]", r),
|
||||
Val: escaped,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -151,7 +151,7 @@ func TestEscapeControlReader(t *testing.T) {
|
||||
for _, test := range escapeControlTests {
|
||||
test.name += " (+Control)"
|
||||
test.text = addPrefix("\u001E", test.text)
|
||||
test.result = addPrefix(`<span class="escaped-code-point" data-escaped="[U+001E]"><span class="char">`+"\u001e"+`</span></span>`, test.result)
|
||||
test.result = addPrefix(`<span class="escaped-code-point" data-escaped="`+string(rune(0x241e))+`"><span class="char">`+"\u001e"+`</span></span>`, test.result)
|
||||
test.status.Escaped = true
|
||||
test.status.HasInvisible = true
|
||||
tests = append(tests, test)
|
||||
|
||||
@ -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,20 +44,12 @@ func globalVars() *globalVarsType {
|
||||
globalVarsPtr.githubStyles = styles.Get("github")
|
||||
globalVarsPtr.highlightMapping = setting.GetHighlightMapping()
|
||||
globalVarsPtr.escCtrlCharsMap = make([]template.HTML, 256)
|
||||
// ASCII Table 0x00 - 0x1F
|
||||
controlCharNames := []string{
|
||||
"NUL", "SOH", "STX", "ETX", "EOT", "ENQ", "ACK", "BEL",
|
||||
"BS", "HT", "LF", "VT", "FF", "CR", "SO", "SI",
|
||||
"DLE", "DC1", "DC2", "DC3", "DC4", "NAK", "SYN", "ETB",
|
||||
"CAN", "EM", "SUB", "ESC", "FS", "GS", "RS", "US",
|
||||
for i := range 0x20 {
|
||||
pic, _ := charset.ControlCharPicture(rune(i))
|
||||
globalVarsPtr.escCtrlCharsMap[i] = controlCharHTML(pic, byte(i))
|
||||
}
|
||||
// Uncomment this line if you'd debug the layout without creating a special file, then Space (0x20) will also be escaped.
|
||||
// Don't worry, even if you forget to comment it out and push it to git repo, the CI tests will catch it and fail.
|
||||
// controlCharNames = append(controlCharNames, "SP")
|
||||
for i, s := range controlCharNames {
|
||||
globalVarsPtr.escCtrlCharsMap[i] = template.HTML(`<span class="broken-code-point" data-escaped="` + s + `"><span class="char">` + string(byte(i)) + `</span></span>`)
|
||||
}
|
||||
globalVarsPtr.escCtrlCharsMap[0x7f] = template.HTML(`<span class="broken-code-point" data-escaped="DEL"><span class="char">` + string(byte(0x7f)) + `</span></span>`)
|
||||
pic, _ := charset.ControlCharPicture(0x7f)
|
||||
globalVarsPtr.escCtrlCharsMap[0x7f] = controlCharHTML(pic, 0x7f)
|
||||
globalVarsPtr.escCtrlCharsMap['\t'] = ""
|
||||
globalVarsPtr.escCtrlCharsMap['\n'] = ""
|
||||
globalVarsPtr.escCtrlCharsMap['\r'] = ""
|
||||
@ -72,6 +65,10 @@ func globalVars() *globalVarsType {
|
||||
return globalVarsPtr
|
||||
}
|
||||
|
||||
func controlCharHTML(pic rune, char byte) template.HTML {
|
||||
return template.HTML(`<span class="broken-code-point" data-escaped="` + string(pic) + `"><span class="char">` + string(char) + `</span></span>`)
|
||||
}
|
||||
|
||||
func escapeByMap(code []byte, escapeMap []template.HTML) template.HTML {
|
||||
firstEscapePos := -1
|
||||
for i, c := range code {
|
||||
@ -175,11 +172,7 @@ func RenderCodeByLexer(lexer chroma.Lexer, code string) template.HTML {
|
||||
return escapeFullString(code)
|
||||
}
|
||||
|
||||
// At the moment, we do not escape control chars here (unlike RenderFullFile which escapes control chars).
|
||||
// The reason is: it is a very rare case that a text file contains control chars.
|
||||
// This function is usually used by highlight diff and blame, not quite sure whether there will be side effects.
|
||||
// If there would be new user feedback about this, we can re-consider about various edge cases.
|
||||
return template.HTML(htmlBuf.String())
|
||||
return escapeControlChars(htmlBuf.Bytes())
|
||||
}
|
||||
|
||||
// RenderFullFile returns a slice of chroma syntax highlighted HTML lines of code and the matched lexer name
|
||||
@ -191,10 +184,9 @@ func RenderFullFile(fileName, language string, code []byte) ([]template.HTML, st
|
||||
lexerName := formatLexerName(lexer.Config().Name)
|
||||
rendered := RenderCodeByLexer(lexer, util.UnsafeBytesToString(code))
|
||||
unsafeLines := UnsafeSplitHighlightedLines(rendered)
|
||||
lines := make([]template.HTML, 0, len(unsafeLines))
|
||||
for _, lineBytes := range unsafeLines {
|
||||
line := escapeControlChars(lineBytes)
|
||||
lines = append(lines, line)
|
||||
lines := make([]template.HTML, len(unsafeLines))
|
||||
for idx, lineBytes := range unsafeLines {
|
||||
lines[idx] = template.HTML(lineBytes)
|
||||
}
|
||||
return lines, lexerName
|
||||
}
|
||||
|
||||
@ -206,12 +206,12 @@ func TestUnsafeSplitHighlightedLines(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestEscape(t *testing.T) {
|
||||
assert.Equal(t, template.HTML("\t\r\n<span class=\"broken-code-point\" data-escaped=\"NUL\"><span class=\"char\">\x00</span></span><span class=\"broken-code-point\" data-escaped=\"US\"><span class=\"char\">\x1f</span></span>&'\"<>"), escapeControlChars([]byte("\t\r\n\x00\x1f&'\"<>")))
|
||||
assert.Equal(t, template.HTML("<span class=\"broken-code-point\" data-escaped=\"NUL\"><span class=\"char\">\x00</span></span><span class=\"broken-code-point\" data-escaped=\"US\"><span class=\"char\">\x1f</span></span>&'"<>\t\r\n"), escapeFullString("\x00\x1f&'\"<>\t\r\n"))
|
||||
assert.Equal(t, template.HTML("\t\r\n<span class=\"broken-code-point\" data-escaped=\"\u2400\"><span class=\"char\">\x00</span></span><span class=\"broken-code-point\" data-escaped=\"\u241f\"><span class=\"char\">\x1f</span></span>&'\"<>"), escapeControlChars([]byte("\t\r\n\x00\x1f&'\"<>")))
|
||||
assert.Equal(t, template.HTML("<span class=\"broken-code-point\" data-escaped=\"\u2400\"><span class=\"char\">\x00</span></span><span class=\"broken-code-point\" data-escaped=\"\u241f\"><span class=\"char\">\x1f</span></span>&'"<>\t\r\n"), escapeFullString("\x00\x1f&'\"<>\t\r\n"))
|
||||
|
||||
out, _ := RenderFullFile("a.py", "", []byte("# \x7f<>"))
|
||||
assert.Equal(t, template.HTML(`<span class="c1"># <span class="broken-code-point" data-escaped="DEL"><span class="char">`+string(byte(0x7f))+`</span></span><></span>`), out[0])
|
||||
assert.Equal(t, template.HTML(`<span class="c1"># <span class="broken-code-point" data-escaped="`+string(rune(0x2421))+`"><span class="char">`+string(byte(0x7f))+`</span></span><></span>`), out[0])
|
||||
|
||||
out = renderPlainText([]byte("# \x7f<>"))
|
||||
assert.Equal(t, template.HTML(`# <span class="broken-code-point" data-escaped="DEL"><span class="char">`+string(byte(0x7f))+`</span></span><>`), out[0])
|
||||
assert.Equal(t, template.HTML(`# <span class="broken-code-point" data-escaped="`+string(rune(0x2421))+`"><span class="char">`+string(byte(0x7f))+`</span></span><>`), out[0])
|
||||
}
|
||||
|
||||
@ -17,6 +17,7 @@
|
||||
/* images */
|
||||
--checkbox-mask-checked: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="12" height="9" viewBox="0 0 12 9"><path fill-rule="evenodd" d="M11.78.22a.75.75 0 0 1 0 1.061L4.52 8.541a.75.75 0 0 1-1.062 0L.202 5.285a.75.75 0 0 1 1.061-1.061l2.725 2.723L10.718.22a.75.75 0 0 1 1.062 0"/></svg>');
|
||||
--checkbox-mask-indeterminate: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="10" height="2" viewBox="0 0 10 2"><path fill-rule="evenodd" d="M0 1a1 1 0 0 1 1-1h8a1 1 0 1 1 0 2H1a1 1 0 0 1-1-1" clip-rule="evenodd"/></svg>');
|
||||
--octicon-alert-fill: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path d="M6.457 1.047c.659-1.234 2.427-1.234 3.086 0l6.082 11.378A1.75 1.75 0 0 1 14.082 15H1.918a1.75 1.75 0 0 1-1.543-2.575ZM8 5a.75.75 0 0 0-.75.75v2.5a.75.75 0 0 0 1.5 0v-2.5A.75.75 0 0 0 8 5Zm1 6a1 1 0 1 0-2 0 1 1 0 0 0 2 0Z"/></svg>');
|
||||
--octicon-chevron-right: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path d="M6.22 3.22a.75.75 0 0 1 1.06 0l4.25 4.25a.75.75 0 0 1 0 1.06l-4.25 4.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042L9.94 8 6.22 4.28a.75.75 0 0 1 0-1.06Z"></path></svg>');
|
||||
--octicon-x: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path d="M3.72 3.72a.75.75 0 0 1 1.06 0L8 6.94l3.22-3.22a.749.749 0 0 1 1.275.326.75.75 0 0 1-.215.734L9.06 8l3.22 3.22a.749.749 0 0 1-.326 1.275.75.75 0 0 1-.734-.215L8 9.06l-3.22 3.22a.75.75 0 0 1-1.042-.018.75.75 0 0 1-.018-1.042L6.94 8 3.72 4.78a.75.75 0 0 1 0-1.06"/></svg>');
|
||||
--select-arrows: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path d="m4.074 9.427 3.396 3.396a.25.25 0 0 0 .354 0l3.396-3.396A.25.25 0 0 0 11.043 9H4.251a.25.25 0 0 0-.177.427m0-1.957L7.47 4.073a.25.25 0 0 1 .354 0L11.22 7.47a.25.25 0 0 1-.177.426H4.251a.25.25 0 0 1-.177-.426"/></svg>');
|
||||
@ -686,6 +687,7 @@ overflow-menu .ui.label {
|
||||
}
|
||||
|
||||
.lines-num,
|
||||
.lines-escape,
|
||||
.lines-code {
|
||||
font-size: 12px;
|
||||
font-family: var(--fonts-monospace);
|
||||
|
||||
@ -1,24 +1,33 @@
|
||||
/*
|
||||
Show the escaped and hide the real char:
|
||||
<span class="broken-code-point" data-escaped="DEL"><span class="char">{real-char}</span></span>
|
||||
<span class="broken-code-point" data-escaped="␡"><span class="char">{real-char}</span></span>
|
||||
Only show the real-char:
|
||||
<span class="broken-code-point">{real-char}</span>
|
||||
*/
|
||||
.broken-code-point:not([data-escaped]),
|
||||
.broken-code-point[data-escaped]::before {
|
||||
.broken-code-point:not([data-escaped]) {
|
||||
border-radius: 4px;
|
||||
padding: 0 2px;
|
||||
color: var(--color-body);
|
||||
background: var(--color-text-light-1);
|
||||
}
|
||||
|
||||
.broken-code-point[data-escaped] {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.broken-code-point[data-escaped]::before {
|
||||
visibility: visible;
|
||||
content: attr(data-escaped);
|
||||
border-radius: 2px;
|
||||
padding: 0 1px;
|
||||
color: var(--color-body);
|
||||
background: var(--color-text-light-1);
|
||||
}
|
||||
|
||||
.broken-code-point[data-escaped] .char {
|
||||
/* make it copyable by selecting the text (AI suggestion, no other solution) */
|
||||
/* keep the original character selectable/copyable while showing the escaped label via ::before */
|
||||
position: absolute;
|
||||
left: 0;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
@ -26,11 +35,11 @@ Only show the real-char:
|
||||
/*
|
||||
Show the escaped and hide the real-char:
|
||||
<span class="unicode-escaped">
|
||||
<span class="escaped-code-point" data-escaped="U+1F600"><span class="char">{real-char}</span></span>
|
||||
<span class="escaped-code-point" data-escaped="[U+1F600]"><span class="char">{real-char}</span></span>
|
||||
</span>
|
||||
Hide the escaped and show the real-char:
|
||||
<span>
|
||||
<span class="escaped-code-point" data-escaped="U+1F600"><span class="char">{real-char}</span></span>
|
||||
<span class="escaped-code-point" data-escaped="[U+1F600]"><span class="char">{real-char}</span></span>
|
||||
</span>
|
||||
*/
|
||||
.unicode-escaped .escaped-code-point[data-escaped]::before {
|
||||
|
||||
@ -15,11 +15,23 @@
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.lines-escape .toggle-escape-button {
|
||||
padding: 2px;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.lines-escape .toggle-escape-button::before {
|
||||
visibility: visible;
|
||||
content: "⚠️";
|
||||
font-family: var(--fonts-emoji);
|
||||
color: var(--color-red);
|
||||
content: "";
|
||||
display: inline-block;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
vertical-align: middle;
|
||||
background-color: var(--color-yellow);
|
||||
mask-image: var(--octicon-alert-fill);
|
||||
-webkit-mask-image: var(--octicon-alert-fill);
|
||||
mask-size: contain;
|
||||
-webkit-mask-size: contain;
|
||||
}
|
||||
|
||||
.repository .diff-file-box .code-diff td.lines-escape {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user