perf(web): lex markdown code blocks without chroma coalescing and drop results past the deadline
2 files · +18 -8
M internal/adapters/web/highlight.go +3 -1
| @@ -166,7 +166,9 @@ func tokeniseInto(ctx context.Context, lx chroma.Lexer, src string, buf []chroma | |||
| 166 | 166 | } | |
| 167 | 167 | toks = append(toks, t) | |
| 168 | 168 | } | |
| 169 | - | if !tokensMatch(toks, src) { | |
| 169 | + | // The last tokens may have run past the deadline: the result is then | |
| 170 | + | // dropped, as promised. One iterator call itself cannot be preempted. | |
| 171 | + | if ctx.Err() != nil || !tokensMatch(toks, src) { | |
| 170 | 172 | return nil, false | |
| 171 | 173 | } | |
| 172 | 174 | return toks, true | |
| @@ -166,7 +166,9 @@ func tokeniseInto(ctx context.Context, lx chroma.Lexer, src string, buf []chroma | |||||
| 166 | } | 166 | } | ||
| 167 | toks = append(toks, t) | 167 | toks = append(toks, t) | ||
| 168 | } | 168 | } | ||
| 169 | - | if !tokensMatch(toks, src) { | 169 | + | // The last tokens may have run past the deadline: the result is then |
| 170 | + | // dropped, as promised. One iterator call itself cannot be preempted. | |||
| 171 | + | if ctx.Err() != nil || !tokensMatch(toks, src) { | |||
| 170 | return nil, false | 172 | return nil, false | ||
| 171 | } | 173 | } | ||
| 172 | return toks, true | 174 | return toks, true | ||
M internal/adapters/web/markdown_highlight.go +15 -7
| @@ -69,6 +69,9 @@ func (fencedRenderer) RegisterFuncs(r renderer.NodeRendererFuncRegisterer) { | |||
| 69 | 69 | line := n.Lines().At(i) | |
| 70 | 70 | remaining += len(line.Value(source)) | |
| 71 | 71 | } | |
| 72 | + | // The tokens are not coalesced (chroma's coalescer copies strings | |
| 73 | + | // quadratically): adjacent tokens of one class share a span here. | |
| 74 | + | open := "" | |
| 72 | 75 | for _, tk := range toks { | |
| 73 | 76 | v := tk.Value | |
| 74 | 77 | if len(v) > remaining { | |
| @@ -78,14 +81,19 @@ func (fencedRenderer) RegisterFuncs(r renderer.NodeRendererFuncRegisterer) { | |||
| 78 | 81 | if v == "" { | |
| 79 | 82 | continue | |
| 80 | 83 | } | |
| 81 | - | c := tokenClass(tk.Type) | |
| 82 | - | if c != "" { | |
| 83 | - | _, _ = w.WriteString(`<span class="` + c + `">`) | |
| 84 | + | if c := tokenClass(tk.Type); c != open { | |
| 85 | + | if open != "" { | |
| 86 | + | _, _ = w.WriteString(`</span>`) | |
| 87 | + | } | |
| 88 | + | if c != "" { | |
| 89 | + | _, _ = w.WriteString(`<span class="` + c + `">`) | |
| 90 | + | } | |
| 91 | + | open = c | |
| 84 | 92 | } | |
| 85 | 93 | writeMarked(w, v) | |
| 86 | - | if c != "" { | |
| 87 | - | _, _ = w.WriteString(`</span>`) | |
| 88 | - | } | |
| 94 | + | } | |
| 95 | + | if open != "" { | |
| 96 | + | _, _ = w.WriteString(`</span>`) | |
| 89 | 97 | } | |
| 90 | 98 | _, _ = w.WriteString("</code></pre>\n") | |
| 91 | 99 | return ast.WalkSkipChildren, nil | |
| @@ -152,6 +160,6 @@ func markdownTokens(n *ast.FencedCodeBlock, source []byte) ([]chroma.Token, stri | |||
| 152 | 160 | } | |
| 153 | 161 | ctx, cancel := context.WithDeadline(context.Background(), budget.deadline) | |
| 154 | 162 | defer cancel() | |
| 155 | - | toks, ok := tokenise(ctx, chroma.Coalesce(lx), code.String()) | |
| 163 | + | toks, ok := tokenise(ctx, lx, code.String()) | |
| 156 | 164 | return toks, lang, ok | |
| 157 | 165 | } | |
| @@ -69,6 +69,9 @@ func (fencedRenderer) RegisterFuncs(r renderer.NodeRendererFuncRegisterer) { | |||||
| 69 | line := n.Lines().At(i) | 69 | line := n.Lines().At(i) | ||
| 70 | remaining += len(line.Value(source)) | 70 | remaining += len(line.Value(source)) | ||
| 71 | } | 71 | } | ||
| 72 | + | // The tokens are not coalesced (chroma's coalescer copies strings | |||
| 73 | + | // quadratically): adjacent tokens of one class share a span here. | |||
| 74 | + | open := "" | |||
| 72 | for _, tk := range toks { | 75 | for _, tk := range toks { | ||
| 73 | v := tk.Value | 76 | v := tk.Value | ||
| 74 | if len(v) > remaining { | 77 | if len(v) > remaining { | ||
| @@ -78,14 +81,19 @@ func (fencedRenderer) RegisterFuncs(r renderer.NodeRendererFuncRegisterer) { | |||||
| 78 | if v == "" { | 81 | if v == "" { | ||
| 79 | continue | 82 | continue | ||
| 80 | } | 83 | } | ||
| 81 | - | c := tokenClass(tk.Type) | 84 | + | if c := tokenClass(tk.Type); c != open { |
| 82 | - | if c != "" { | 85 | + | if open != "" { |
| 83 | - | _, _ = w.WriteString(`<span class="` + c + `">`) | 86 | + | _, _ = w.WriteString(`</span>`) |
| 87 | + | } | |||
| 88 | + | if c != "" { | |||
| 89 | + | _, _ = w.WriteString(`<span class="` + c + `">`) | |||
| 90 | + | } | |||
| 91 | + | open = c | |||
| 84 | } | 92 | } | ||
| 85 | writeMarked(w, v) | 93 | writeMarked(w, v) | ||
| 86 | - | if c != "" { | 94 | + | } |
| 87 | - | _, _ = w.WriteString(`</span>`) | 95 | + | if open != "" { |
| 88 | - | } | 96 | + | _, _ = w.WriteString(`</span>`) |
| 89 | } | 97 | } | ||
| 90 | _, _ = w.WriteString("</code></pre>\n") | 98 | _, _ = w.WriteString("</code></pre>\n") | ||
| 91 | return ast.WalkSkipChildren, nil | 99 | return ast.WalkSkipChildren, nil | ||
| @@ -152,6 +160,6 @@ func markdownTokens(n *ast.FencedCodeBlock, source []byte) ([]chroma.Token, stri | |||||
| 152 | } | 160 | } | ||
| 153 | ctx, cancel := context.WithDeadline(context.Background(), budget.deadline) | 161 | ctx, cancel := context.WithDeadline(context.Background(), budget.deadline) | ||
| 154 | defer cancel() | 162 | defer cancel() | ||
| 155 | - | toks, ok := tokenise(ctx, chroma.Coalesce(lx), code.String()) | 163 | + | toks, ok := tokenise(ctx, lx, code.String()) |
| 156 | return toks, lang, ok | 164 | return toks, lang, ok | ||
| 157 | } | 165 | } | ||