Repository navigation
Expand file tree
/
Copy pathparser_escape.go
More file actions
76 lines (71 loc) · 2.54 KB
/
Copy pathparser_escape.go
File metadata and controls
76 lines (71 loc) · 2.54 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
package main
import (
"fmt"
"strings"
)
// asciiPunct is the set of ASCII punctuation characters that a backslash may
// escape, following the CommonMark rule: a backslash before any ASCII
// punctuation is an escape; before anything else it is a literal backslash.
const asciiPunct = "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"
func isASCIIPunct(r rune) bool {
return r < 128 && strings.ContainsRune(asciiPunct, r)
}
// resolveEscapes rewrites backslash escapes in a single left-to-right pass.
//
// A backslash before an ASCII punctuation character is replaced by that
// character's numeric HTML entity (e.g. \* -> *). The entity is inert to
// every downstream regex (which match the literal character) and already
// renders as the intended character, so no later un-masking is needed — the
// same trick codeSpanHandler uses for spell's own trigger characters.
//
// Backticks are tracked so that escapes inside inline code are left literal
// (Markdown does not process escapes inside code spans). Because an escaped
// backtick is consumed as an escape before it can toggle code state, \`
// correctly yields a literal backtick instead of opening a code span.
func resolveEscapes(line string) string {
runes := []rune(line)
var b strings.Builder
b.Grow(len(line))
inCode := false
for i := 0; i < len(runes); i++ {
r := runes[i]
if r == '\\' && !inCode && i+1 < len(runes) {
next := runes[i+1]
if isASCIIPunct(next) {
fmt.Fprintf(&b, "&#%d;", next)
i++
continue
}
// Backslash before a non-punctuation rune stays literal.
}
if r == '`' {
inCode = !inCode
}
b.WriteRune(r)
}
return b.String()
}
// escapeHandler resolves backslash escapes before any other handler runs, so
// that escaped block markers (\#, \-, \1.) are not mistaken for structure and
// escaped inline markers (\*, \%, \[) are not interpreted. It is skipped
// inside verbatim code fences, where backslashes are literal.
//
// match reports true only when resolution would actually change the line;
// this both avoids needless work and guarantees termination, since after one
// pass no resolvable escape remains and the handler no longer matches.
func escapeHandler() lineHandler {
return lineHandler{
match: func(line string, _ bool) bool {
if inBlockType == BLOCKTYPE_CODE {
return false
}
if strings.IndexByte(line, '\\') < 0 {
return false
}
return resolveEscapes(line) != line
},
handle: func(ctx *parseContext, line string, insideBlock bool) (string, bool) {
return parseLine(ctx, resolveEscapes(line), insideBlock), true
},
}
}