markup/highlight: Add hl_inline option

Closes #9442
Closes #9635
Closes #9638
This commit is contained in:
Bjørn Erik Pedersen 2022-06-15 13:51:29 +02:00
parent 580b214a4c
commit d863dde6c6
4 changed files with 163 additions and 14 deletions

View file

@ -72,6 +72,9 @@ type Config struct {
// A space separated list of line numbers, e.g. “3-8 10-20”.
Hl_Lines string
// If set, the markup will not be wrapped in any container.
Hl_inline bool
// A parsed and ready to use list of line ranges.
HL_lines_parsed [][2]int `json:"-"`
@ -93,6 +96,7 @@ func (cfg Config) ToHTMLOptions() []html.Option {
html.LineNumbersInTable(cfg.LineNumbersInTable),
html.WithClasses(!cfg.NoClasses),
html.LinkableLineNumbers(cfg.AnchorLineNos, lineAnchors),
html.InlineCode(cfg.Hl_inline),
}
if cfg.Hl_Lines != "" || cfg.HL_lines_parsed != nil {

View file

@ -88,6 +88,7 @@ func (h chromaHighlighter) HighlightCodeBlock(ctx hooks.CodeblockContext, opts a
var b strings.Builder
attributes := ctx.(hooks.AttributesOptionsSliceProvider).AttributesSlice()
options := ctx.Options()
if err := applyOptionsFromMap(options, &cfg); err != nil {
@ -108,8 +109,13 @@ func (h chromaHighlighter) HighlightCodeBlock(ctx hooks.CodeblockContext, opts a
return HightlightResult{}, err
}
highlighted := b.String()
if high == 0 {
high = len(highlighted)
}
return HightlightResult{
highlighted: template.HTML(b.String()),
highlighted: template.HTML(highlighted),
innerLow: low,
innerHigh: high,
}, nil
@ -117,6 +123,7 @@ func (h chromaHighlighter) HighlightCodeBlock(ctx hooks.CodeblockContext, opts a
func (h chromaHighlighter) RenderCodeblock(w hugio.FlexiWriter, ctx hooks.CodeblockContext) error {
cfg := h.cfg
attributes := ctx.(hooks.AttributesOptionsSliceProvider).AttributesSlice()
if err := applyOptionsFromMap(ctx.Options(), &cfg); err != nil {
@ -158,8 +165,6 @@ func (h HightlightResult) Inner() template.HTML {
}
func highlight(fw hugio.FlexiWriter, code, lang string, attributes []attributes.Attribute, cfg Config) (int, int, error) {
var low, high int
var lexer chroma.Lexer
if lang != "" {
lexer = lexers.Get(lang)
@ -176,11 +181,15 @@ func highlight(fw hugio.FlexiWriter, code, lang string, attributes []attributes.
w := &byteCountFlexiWriter{delegate: fw}
if lexer == nil {
wrapper := getPreWrapper(lang, w)
fmt.Fprint(w, wrapper.Start(true, ""))
fmt.Fprint(w, gohtml.EscapeString(code))
fmt.Fprint(w, wrapper.End(true))
return low, high, nil
if cfg.Hl_inline {
fmt.Fprint(w, fmt.Sprintf("<code%s>%s</code>", inlineCodeAttrs(lang), gohtml.EscapeString(code)))
} else {
preWrapper := getPreWrapper(lang, w)
fmt.Fprint(w, preWrapper.Start(true, ""))
fmt.Fprint(w, gohtml.EscapeString(code))
fmt.Fprint(w, preWrapper.End(true))
}
return 0, 0, nil
}
style := styles.Get(cfg.Style)
@ -194,20 +203,51 @@ func highlight(fw hugio.FlexiWriter, code, lang string, attributes []attributes.
return 0, 0, err
}
if !cfg.Hl_inline {
writeDivStart(w, attributes)
}
options := cfg.ToHTMLOptions()
preWrapper := getPreWrapper(lang, w)
options = append(options, html.WithPreWrapper(preWrapper))
var wrapper html.PreWrapper
if cfg.Hl_inline {
wrapper = startEnd{
start: func(code bool, styleAttr string) string {
if code {
return fmt.Sprintf(`<code%s>`, inlineCodeAttrs(lang))
}
return ``
},
end: func(code bool) string {
if code {
return `</code>`
}
return ``
},
}
} else {
wrapper = getPreWrapper(lang, w)
}
options = append(options, html.WithPreWrapper(wrapper))
formatter := html.New(options...)
writeDivStart(w, attributes)
if err := formatter.Format(w, style, iterator); err != nil {
return 0, 0, err
}
writeDivEnd(w)
return preWrapper.low, preWrapper.high, nil
if !cfg.Hl_inline {
writeDivEnd(w)
}
if p, ok := wrapper.(*preWrapper); ok {
return p.low, p.high, nil
}
return 0, 0, nil
}
func getPreWrapper(language string, writeCounter *byteCountFlexiWriter) *preWrapper {
@ -232,6 +272,12 @@ func (p *preWrapper) Start(code bool, styleAttr string) string {
return w.String()
}
func inlineCodeAttrs(lang string) string {
if lang == "" {
}
return fmt.Sprintf(` class="code-inline language-%s"`, lang)
}
func WritePreStart(w io.Writer, language, styleAttr string) {
fmt.Fprintf(w, `<pre tabindex="0"%s>`, styleAttr)
fmt.Fprint(w, "<code")
@ -249,6 +295,19 @@ func (p *preWrapper) End(code bool) string {
return preEnd
}
type startEnd struct {
start func(code bool, styleAttr string) string
end func(code bool) string
}
func (s startEnd) Start(code bool, styleAttr string) string {
return s.start(code, styleAttr)
}
func (s startEnd) End(code bool) string {
return s.end(code)
}
func WritePreEnd(w io.Writer) {
fmt.Fprint(w, preEnd)
}

View file

@ -0,0 +1,85 @@
// Copyright 2022 The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package highlight_test
import (
"testing"
"github.com/gohugoio/hugo/hugolib"
)
func TestHighlightInline(t *testing.T) {
t.Parallel()
files := `
-- config.toml --
[markup]
[markup.highlight]
codeFences = true
noClasses = false
-- content/p1.md --
---
title: "p1"
---
## Inline in Shortcode
Inline:{{< highlight emacs "hl_inline=true" >}}(message "this highlight shortcode"){{< /highlight >}}:End.
Inline Unknown:{{< highlight foo "hl_inline=true" >}}(message "this highlight shortcode"){{< /highlight >}}:End.
## Inline in code block
Not sure if this makes sense, but add a test for it:
§§§bash {hl_inline=true}
(message "highlight me")
§§§
## HighlightCodeBlock in hook
§§§html
(message "highlight me 2")
§§§
## Unknown lexer
§§§foo {hl_inline=true}
(message "highlight me 3")
§§§
-- layouts/_default/_markup/render-codeblock-html.html --
{{ $opts := dict "hl_inline" true }}
{{ $result := transform.HighlightCodeBlock . $opts }}
HighlightCodeBlock: Wrapped:{{ $result.Wrapped }}|Inner:{{ $result.Inner }}
-- layouts/_default/single.html --
{{ .Content }}
`
b := hugolib.NewIntegrationTestBuilder(
hugolib.IntegrationTestConfig{
T: t,
TxtarString: files,
NeedsOsFS: false,
},
).Build()
b.AssertFileContent("public/p1/index.html",
"Inline:<code class=\"code-inline language-emacs\"><span class=\"p\">(</span><span class=\"nf\">message</span> <span class=\"s\">&#34;this highlight shortcode&#34;</span><span class=\"p\">)</span></code>:End.",
"Inline Unknown:<code class=\"code-inline language-foo\">(message &#34;this highlight shortcode&#34;)</code>:End.",
"Not sure if this makes sense, but add a test for it:</p>\n<code class=\"code-inline language-bash\"><span class=\"o\">(</span>message <span class=\"s2\">&#34;highlight me&#34;</span><span class=\"o\">)</span>\n</code>",
"HighlightCodeBlock: Wrapped:<code class=\"code-inline language-html\">(message &#34;highlight me 2&#34;)</code>|Inner:<code class=\"code-inline language-html\">(message &#34;highlight me 2&#34;)</code>",
"<code class=\"code-inline language-foo\">(message &#34;highlight me 3&#34;)\n</code>",
)
}

View file

@ -30,6 +30,7 @@ var chromaHightlightProcessingAttributes = map[string]bool{
"anchorLineNos": true,
"guessSyntax": true,
"hl_Lines": true,
"hl_inline": true,
"lineAnchors": true,
"lineNos": true,
"lineNoStart": true,