mirror of
https://github.com/gohugoio/hugo.git
synced 2024-11-07 20:30:36 -05:00
markup/highlight: Add hl_inline option
Closes #9442 Closes #9635 Closes #9638
This commit is contained in:
parent
580b214a4c
commit
d863dde6c6
4 changed files with 163 additions and 14 deletions
|
@ -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 {
|
||||
|
|
|
@ -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, ""))
|
||||
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, wrapper.End(true))
|
||||
return low, high, nil
|
||||
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)
|
||||
}
|
||||
|
|
85
markup/highlight/integration_test.go
Normal file
85
markup/highlight/integration_test.go
Normal 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\">"this highlight shortcode"</span><span class=\"p\">)</span></code>:End.",
|
||||
"Inline Unknown:<code class=\"code-inline language-foo\">(message "this highlight shortcode")</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\">"highlight me"</span><span class=\"o\">)</span>\n</code>",
|
||||
"HighlightCodeBlock: Wrapped:<code class=\"code-inline language-html\">(message "highlight me 2")</code>|Inner:<code class=\"code-inline language-html\">(message "highlight me 2")</code>",
|
||||
"<code class=\"code-inline language-foo\">(message "highlight me 3")\n</code>",
|
||||
)
|
||||
}
|
|
@ -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,
|
||||
|
|
Loading…
Reference in a new issue