mirror of
https://github.com/gohugoio/hugo.git
synced 2024-11-21 20:46:30 -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”.
|
// A space separated list of line numbers, e.g. “3-8 10-20”.
|
||||||
Hl_Lines string
|
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.
|
// A parsed and ready to use list of line ranges.
|
||||||
HL_lines_parsed [][2]int `json:"-"`
|
HL_lines_parsed [][2]int `json:"-"`
|
||||||
|
|
||||||
|
@ -93,6 +96,7 @@ func (cfg Config) ToHTMLOptions() []html.Option {
|
||||||
html.LineNumbersInTable(cfg.LineNumbersInTable),
|
html.LineNumbersInTable(cfg.LineNumbersInTable),
|
||||||
html.WithClasses(!cfg.NoClasses),
|
html.WithClasses(!cfg.NoClasses),
|
||||||
html.LinkableLineNumbers(cfg.AnchorLineNos, lineAnchors),
|
html.LinkableLineNumbers(cfg.AnchorLineNos, lineAnchors),
|
||||||
|
html.InlineCode(cfg.Hl_inline),
|
||||||
}
|
}
|
||||||
|
|
||||||
if cfg.Hl_Lines != "" || cfg.HL_lines_parsed != nil {
|
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
|
var b strings.Builder
|
||||||
|
|
||||||
attributes := ctx.(hooks.AttributesOptionsSliceProvider).AttributesSlice()
|
attributes := ctx.(hooks.AttributesOptionsSliceProvider).AttributesSlice()
|
||||||
|
|
||||||
options := ctx.Options()
|
options := ctx.Options()
|
||||||
|
|
||||||
if err := applyOptionsFromMap(options, &cfg); err != nil {
|
if err := applyOptionsFromMap(options, &cfg); err != nil {
|
||||||
|
@ -108,8 +109,13 @@ func (h chromaHighlighter) HighlightCodeBlock(ctx hooks.CodeblockContext, opts a
|
||||||
return HightlightResult{}, err
|
return HightlightResult{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
highlighted := b.String()
|
||||||
|
if high == 0 {
|
||||||
|
high = len(highlighted)
|
||||||
|
}
|
||||||
|
|
||||||
return HightlightResult{
|
return HightlightResult{
|
||||||
highlighted: template.HTML(b.String()),
|
highlighted: template.HTML(highlighted),
|
||||||
innerLow: low,
|
innerLow: low,
|
||||||
innerHigh: high,
|
innerHigh: high,
|
||||||
}, nil
|
}, 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 {
|
func (h chromaHighlighter) RenderCodeblock(w hugio.FlexiWriter, ctx hooks.CodeblockContext) error {
|
||||||
cfg := h.cfg
|
cfg := h.cfg
|
||||||
|
|
||||||
attributes := ctx.(hooks.AttributesOptionsSliceProvider).AttributesSlice()
|
attributes := ctx.(hooks.AttributesOptionsSliceProvider).AttributesSlice()
|
||||||
|
|
||||||
if err := applyOptionsFromMap(ctx.Options(), &cfg); err != nil {
|
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) {
|
func highlight(fw hugio.FlexiWriter, code, lang string, attributes []attributes.Attribute, cfg Config) (int, int, error) {
|
||||||
var low, high int
|
|
||||||
|
|
||||||
var lexer chroma.Lexer
|
var lexer chroma.Lexer
|
||||||
if lang != "" {
|
if lang != "" {
|
||||||
lexer = lexers.Get(lang)
|
lexer = lexers.Get(lang)
|
||||||
|
@ -176,11 +181,15 @@ func highlight(fw hugio.FlexiWriter, code, lang string, attributes []attributes.
|
||||||
w := &byteCountFlexiWriter{delegate: fw}
|
w := &byteCountFlexiWriter{delegate: fw}
|
||||||
|
|
||||||
if lexer == nil {
|
if lexer == nil {
|
||||||
wrapper := getPreWrapper(lang, w)
|
if cfg.Hl_inline {
|
||||||
fmt.Fprint(w, wrapper.Start(true, ""))
|
fmt.Fprint(w, fmt.Sprintf("<code%s>%s</code>", inlineCodeAttrs(lang), gohtml.EscapeString(code)))
|
||||||
fmt.Fprint(w, gohtml.EscapeString(code))
|
} else {
|
||||||
fmt.Fprint(w, wrapper.End(true))
|
preWrapper := getPreWrapper(lang, w)
|
||||||
return low, high, nil
|
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)
|
style := styles.Get(cfg.Style)
|
||||||
|
@ -194,20 +203,51 @@ func highlight(fw hugio.FlexiWriter, code, lang string, attributes []attributes.
|
||||||
return 0, 0, err
|
return 0, 0, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !cfg.Hl_inline {
|
||||||
|
writeDivStart(w, attributes)
|
||||||
|
}
|
||||||
|
|
||||||
options := cfg.ToHTMLOptions()
|
options := cfg.ToHTMLOptions()
|
||||||
preWrapper := getPreWrapper(lang, w)
|
var wrapper html.PreWrapper
|
||||||
options = append(options, html.WithPreWrapper(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...)
|
formatter := html.New(options...)
|
||||||
|
|
||||||
writeDivStart(w, attributes)
|
|
||||||
|
|
||||||
if err := formatter.Format(w, style, iterator); err != nil {
|
if err := formatter.Format(w, style, iterator); err != nil {
|
||||||
return 0, 0, err
|
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 {
|
func getPreWrapper(language string, writeCounter *byteCountFlexiWriter) *preWrapper {
|
||||||
|
@ -232,6 +272,12 @@ func (p *preWrapper) Start(code bool, styleAttr string) string {
|
||||||
return w.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) {
|
func WritePreStart(w io.Writer, language, styleAttr string) {
|
||||||
fmt.Fprintf(w, `<pre tabindex="0"%s>`, styleAttr)
|
fmt.Fprintf(w, `<pre tabindex="0"%s>`, styleAttr)
|
||||||
fmt.Fprint(w, "<code")
|
fmt.Fprint(w, "<code")
|
||||||
|
@ -249,6 +295,19 @@ func (p *preWrapper) End(code bool) string {
|
||||||
return preEnd
|
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) {
|
func WritePreEnd(w io.Writer) {
|
||||||
fmt.Fprint(w, preEnd)
|
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,
|
"anchorLineNos": true,
|
||||||
"guessSyntax": true,
|
"guessSyntax": true,
|
||||||
"hl_Lines": true,
|
"hl_Lines": true,
|
||||||
|
"hl_inline": true,
|
||||||
"lineAnchors": true,
|
"lineAnchors": true,
|
||||||
"lineNos": true,
|
"lineNos": true,
|
||||||
"lineNoStart": true,
|
"lineNoStart": true,
|
||||||
|
|
Loading…
Reference in a new issue