Implement inline source blocks
https://orgmode.org/manual/Structure-of-Code-Blocks.html
This commit is contained in:
parent
6ed46ba95d
commit
3018ace8d0
8 changed files with 83 additions and 21 deletions
5
main.go
5
main.go
|
@ -46,7 +46,7 @@ func main() {
|
||||||
fmt.Fprint(os.Stdout, out)
|
fmt.Fprint(os.Stdout, out)
|
||||||
}
|
}
|
||||||
|
|
||||||
func highlightCodeBlock(source, lang string) string {
|
func highlightCodeBlock(source, lang string, inline bool) string {
|
||||||
var w strings.Builder
|
var w strings.Builder
|
||||||
l := lexers.Get(lang)
|
l := lexers.Get(lang)
|
||||||
if l == nil {
|
if l == nil {
|
||||||
|
@ -55,5 +55,8 @@ func highlightCodeBlock(source, lang string) string {
|
||||||
l = chroma.Coalesce(l)
|
l = chroma.Coalesce(l)
|
||||||
it, _ := l.Tokenise(nil, source)
|
it, _ := l.Tokenise(nil, source)
|
||||||
_ = html.New().Format(&w, styles.Get("friendly"), it)
|
_ = html.New().Format(&w, styles.Get("friendly"), it)
|
||||||
|
if inline {
|
||||||
|
return `<div class="highlight-inline">` + "\n" + w.String() + "\n" + `</div>`
|
||||||
|
}
|
||||||
return `<div class="highlight">` + "\n" + w.String() + "\n" + `</div>`
|
return `<div class="highlight">` + "\n" + w.String() + "\n" + `</div>`
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@ import (
|
||||||
// HTMLWriter exports an org document into a html document.
|
// HTMLWriter exports an org document into a html document.
|
||||||
type HTMLWriter struct {
|
type HTMLWriter struct {
|
||||||
ExtendingWriter Writer
|
ExtendingWriter Writer
|
||||||
HighlightCodeBlock func(source, lang string) string
|
HighlightCodeBlock func(source, lang string, inline bool) string
|
||||||
|
|
||||||
strings.Builder
|
strings.Builder
|
||||||
document *Document
|
document *Document
|
||||||
|
@ -61,7 +61,10 @@ func NewHTMLWriter() *HTMLWriter {
|
||||||
document: &Document{Configuration: defaultConfig},
|
document: &Document{Configuration: defaultConfig},
|
||||||
log: defaultConfig.Log,
|
log: defaultConfig.Log,
|
||||||
htmlEscape: true,
|
htmlEscape: true,
|
||||||
HighlightCodeBlock: func(source, lang string) string {
|
HighlightCodeBlock: func(source, lang string, inline bool) string {
|
||||||
|
if inline {
|
||||||
|
return fmt.Sprintf("<div class=\"highlight-inline\">\n<pre>\n%s\n</pre>\n</div>", html.EscapeString(source))
|
||||||
|
}
|
||||||
return fmt.Sprintf("<div class=\"highlight\">\n<pre>\n%s\n</pre>\n</div>", html.EscapeString(source))
|
return fmt.Sprintf("<div class=\"highlight\">\n<pre>\n%s\n</pre>\n</div>", html.EscapeString(source))
|
||||||
},
|
},
|
||||||
footnotes: &footnotes{
|
footnotes: &footnotes{
|
||||||
|
@ -103,24 +106,14 @@ func (w *HTMLWriter) WriteComment(Comment) {}
|
||||||
func (w *HTMLWriter) WritePropertyDrawer(PropertyDrawer) {}
|
func (w *HTMLWriter) WritePropertyDrawer(PropertyDrawer) {}
|
||||||
|
|
||||||
func (w *HTMLWriter) WriteBlock(b Block) {
|
func (w *HTMLWriter) WriteBlock(b Block) {
|
||||||
content := ""
|
content := w.blockContent(b.Name, b.Children)
|
||||||
if isRawTextBlock(b.Name) {
|
|
||||||
builder, htmlEscape := w.Builder, w.htmlEscape
|
|
||||||
w.Builder, w.htmlEscape = strings.Builder{}, false
|
|
||||||
WriteNodes(w, b.Children...)
|
|
||||||
out := w.String()
|
|
||||||
w.Builder, w.htmlEscape = builder, htmlEscape
|
|
||||||
content = strings.TrimRightFunc(out, unicode.IsSpace)
|
|
||||||
} else {
|
|
||||||
content = w.WriteNodesAsString(b.Children...)
|
|
||||||
}
|
|
||||||
switch name := b.Name; {
|
switch name := b.Name; {
|
||||||
case name == "SRC":
|
case name == "SRC":
|
||||||
lang := "text"
|
lang := "text"
|
||||||
if len(b.Parameters) >= 1 {
|
if len(b.Parameters) >= 1 {
|
||||||
lang = strings.ToLower(b.Parameters[0])
|
lang = strings.ToLower(b.Parameters[0])
|
||||||
}
|
}
|
||||||
content = w.HighlightCodeBlock(content, lang)
|
content = w.HighlightCodeBlock(content, lang, false)
|
||||||
w.WriteString(fmt.Sprintf("<div class=\"src src-%s\">\n%s\n</div>\n", lang, content))
|
w.WriteString(fmt.Sprintf("<div class=\"src src-%s\">\n%s\n</div>\n", lang, content))
|
||||||
case name == "EXAMPLE":
|
case name == "EXAMPLE":
|
||||||
w.WriteString(`<pre class="example">` + "\n" + html.EscapeString(content) + "\n</pre>\n")
|
w.WriteString(`<pre class="example">` + "\n" + html.EscapeString(content) + "\n</pre>\n")
|
||||||
|
@ -139,6 +132,12 @@ func (w *HTMLWriter) WriteBlock(b Block) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *HTMLWriter) WriteInlineBlock(b InlineBlock) {
|
||||||
|
content := w.blockContent(b.Name, b.Children)
|
||||||
|
lang := strings.ToLower(b.Parameters[0])
|
||||||
|
w.WriteString(fmt.Sprintf("<div class=\"src src-inline src-%s\">\n%s\n</div>", lang, content))
|
||||||
|
}
|
||||||
|
|
||||||
func (w *HTMLWriter) WriteDrawer(d Drawer) {
|
func (w *HTMLWriter) WriteDrawer(d Drawer) {
|
||||||
WriteNodes(w, d.Children...)
|
WriteNodes(w, d.Children...)
|
||||||
}
|
}
|
||||||
|
@ -483,6 +482,19 @@ func (w *HTMLWriter) withHTMLAttributes(input string, kvs ...string) string {
|
||||||
return out.String()
|
return out.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *HTMLWriter) blockContent(name string, children []Node) string {
|
||||||
|
if isRawTextBlock(name) {
|
||||||
|
builder, htmlEscape := w.Builder, w.htmlEscape
|
||||||
|
w.Builder, w.htmlEscape = strings.Builder{}, false
|
||||||
|
WriteNodes(w, children...)
|
||||||
|
out := w.String()
|
||||||
|
w.Builder, w.htmlEscape = builder, htmlEscape
|
||||||
|
return strings.TrimRightFunc(out, unicode.IsSpace)
|
||||||
|
} else {
|
||||||
|
return w.WriteNodesAsString(children...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func setHTMLAttribute(attributes []h.Attribute, k, v string) []h.Attribute {
|
func setHTMLAttribute(attributes []h.Attribute, k, v string) []h.Attribute {
|
||||||
for i, a := range attributes {
|
for i, a := range attributes {
|
||||||
if strings.ToLower(a.Key) == strings.ToLower(k) {
|
if strings.ToLower(a.Key) == strings.ToLower(k) {
|
||||||
|
|
|
@ -30,6 +30,12 @@ type Emphasis struct {
|
||||||
Content []Node
|
Content []Node
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type InlineBlock struct {
|
||||||
|
Name string
|
||||||
|
Parameters []string
|
||||||
|
Children []Node
|
||||||
|
}
|
||||||
|
|
||||||
type LatexFragment struct {
|
type LatexFragment struct {
|
||||||
OpeningPair string
|
OpeningPair string
|
||||||
ClosingPair string
|
ClosingPair string
|
||||||
|
@ -58,6 +64,7 @@ var timestampRegexp = regexp.MustCompile(`^<(\d{4}-\d{2}-\d{2})( [A-Za-z]+)?( \d
|
||||||
var footnoteRegexp = regexp.MustCompile(`^\[fn:([\w-]*?)(:(.*?))?\]`)
|
var footnoteRegexp = regexp.MustCompile(`^\[fn:([\w-]*?)(:(.*?))?\]`)
|
||||||
var statisticsTokenRegexp = regexp.MustCompile(`^\[(\d+/\d+|\d+%)\]`)
|
var statisticsTokenRegexp = regexp.MustCompile(`^\[(\d+/\d+|\d+%)\]`)
|
||||||
var latexFragmentRegexp = regexp.MustCompile(`(?s)^\\begin{(\w+)}(.*)\\end{(\w+)}`)
|
var latexFragmentRegexp = regexp.MustCompile(`(?s)^\\begin{(\w+)}(.*)\\end{(\w+)}`)
|
||||||
|
var inlineBlockRegexp = regexp.MustCompile(`src_(\w+)(\[(.*)\])?{(.*)}`)
|
||||||
|
|
||||||
var timestampFormat = "2006-01-02 Mon 15:04"
|
var timestampFormat = "2006-01-02 Mon 15:04"
|
||||||
var datestampFormat = "2006-01-02 Mon"
|
var datestampFormat = "2006-01-02 Mon"
|
||||||
|
@ -77,7 +84,7 @@ func (d *Document) parseInline(input string) (nodes []Node) {
|
||||||
case '^':
|
case '^':
|
||||||
consumed, node = d.parseSubOrSuperScript(input, current)
|
consumed, node = d.parseSubOrSuperScript(input, current)
|
||||||
case '_':
|
case '_':
|
||||||
consumed, node = d.parseSubScriptOrEmphasis(input, current)
|
rewind, consumed, node = d.parseSubScriptOrEmphasisOrInlineBlock(input, current)
|
||||||
case '*', '/', '+':
|
case '*', '/', '+':
|
||||||
consumed, node = d.parseEmphasis(input, current, false)
|
consumed, node = d.parseEmphasis(input, current, false)
|
||||||
case '=', '~':
|
case '=', '~':
|
||||||
|
@ -94,8 +101,8 @@ func (d *Document) parseInline(input string) (nodes []Node) {
|
||||||
consumed, node = d.parseLineBreak(input, current)
|
consumed, node = d.parseLineBreak(input, current)
|
||||||
case ':':
|
case ':':
|
||||||
rewind, consumed, node = d.parseAutoLink(input, current)
|
rewind, consumed, node = d.parseAutoLink(input, current)
|
||||||
current -= rewind
|
|
||||||
}
|
}
|
||||||
|
current -= rewind
|
||||||
if consumed != 0 {
|
if consumed != 0 {
|
||||||
if current > previous {
|
if current > previous {
|
||||||
nodes = append(nodes, Text{input[previous:current], false})
|
nodes = append(nodes, Text{input[previous:current], false})
|
||||||
|
@ -144,6 +151,16 @@ func (d *Document) parseLineBreak(input string, start int) (int, Node) {
|
||||||
return i - start, LineBreak{i - start}
|
return i - start, LineBreak{i - start}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *Document) parseInlineBlock(input string, start int) (int, int, Node) {
|
||||||
|
if !(strings.HasSuffix(input[:start], "src") && (start-4 < 0 || unicode.IsSpace(rune(input[start-4])))) {
|
||||||
|
return 0, 0, nil
|
||||||
|
}
|
||||||
|
if m := inlineBlockRegexp.FindStringSubmatch(input[start-4:]); m != nil {
|
||||||
|
return 3, len(m[0]), InlineBlock{"src", strings.Fields(m[1] + " " + m[3]), d.parseRawInline(m[4])}
|
||||||
|
}
|
||||||
|
return 0, 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (d *Document) parseExplicitLineBreakOrLatexFragment(input string, start int) (int, Node) {
|
func (d *Document) parseExplicitLineBreakOrLatexFragment(input string, start int) (int, Node) {
|
||||||
switch {
|
switch {
|
||||||
case start+2 >= len(input):
|
case start+2 >= len(input):
|
||||||
|
@ -190,11 +207,14 @@ func (d *Document) parseSubOrSuperScript(input string, start int) (int, Node) {
|
||||||
return 0, nil
|
return 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Document) parseSubScriptOrEmphasis(input string, start int) (int, Node) {
|
func (d *Document) parseSubScriptOrEmphasisOrInlineBlock(input string, start int) (int, int, Node) {
|
||||||
if consumed, node := d.parseSubOrSuperScript(input, start); consumed != 0 {
|
if rewind, consumed, node := d.parseInlineBlock(input, start); consumed != 0 {
|
||||||
return consumed, node
|
return rewind, consumed, node
|
||||||
|
} else if consumed, node := d.parseSubOrSuperScript(input, start); consumed != 0 {
|
||||||
|
return 0, consumed, node
|
||||||
}
|
}
|
||||||
return d.parseEmphasis(input, start, false)
|
consumed, node := d.parseEmphasis(input, start, false)
|
||||||
|
return 0, consumed, node
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Document) parseOpeningBracket(input string, start int) (int, Node) {
|
func (d *Document) parseOpeningBracket(input string, start int) (int, Node) {
|
||||||
|
@ -355,6 +375,7 @@ func (n LineBreak) String() string { return orgWriter.WriteNodesAsString
|
||||||
func (n ExplicitLineBreak) String() string { return orgWriter.WriteNodesAsString(n) }
|
func (n ExplicitLineBreak) String() string { return orgWriter.WriteNodesAsString(n) }
|
||||||
func (n StatisticToken) String() string { return orgWriter.WriteNodesAsString(n) }
|
func (n StatisticToken) String() string { return orgWriter.WriteNodesAsString(n) }
|
||||||
func (n Emphasis) String() string { return orgWriter.WriteNodesAsString(n) }
|
func (n Emphasis) String() string { return orgWriter.WriteNodesAsString(n) }
|
||||||
|
func (n InlineBlock) String() string { return orgWriter.WriteNodesAsString(n) }
|
||||||
func (n LatexFragment) String() string { return orgWriter.WriteNodesAsString(n) }
|
func (n LatexFragment) String() string { return orgWriter.WriteNodesAsString(n) }
|
||||||
func (n FootnoteLink) String() string { return orgWriter.WriteNodesAsString(n) }
|
func (n FootnoteLink) String() string { return orgWriter.WriteNodesAsString(n) }
|
||||||
func (n RegularLink) String() string { return orgWriter.WriteNodesAsString(n) }
|
func (n RegularLink) String() string { return orgWriter.WriteNodesAsString(n) }
|
||||||
|
|
|
@ -97,6 +97,16 @@ func (w *OrgWriter) WriteBlock(b Block) {
|
||||||
w.WriteString("#+END_" + b.Name + "\n")
|
w.WriteString("#+END_" + b.Name + "\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *OrgWriter) WriteInlineBlock(b InlineBlock) {
|
||||||
|
w.WriteString(b.Name + "_" + b.Parameters[0])
|
||||||
|
if len(b.Parameters) > 1 {
|
||||||
|
w.WriteString("[" + strings.Join(b.Parameters[1:], " ") + "]")
|
||||||
|
}
|
||||||
|
w.WriteString("{")
|
||||||
|
WriteNodes(w, b.Children...)
|
||||||
|
w.WriteString("}")
|
||||||
|
}
|
||||||
|
|
||||||
func (w *OrgWriter) WriteDrawer(d Drawer) {
|
func (w *OrgWriter) WriteDrawer(d Drawer) {
|
||||||
w.WriteString(w.indent + ":" + d.Name + ":\n")
|
w.WriteString(w.indent + ":" + d.Name + ":\n")
|
||||||
WriteNodes(w, d.Children...)
|
WriteNodes(w, d.Children...)
|
||||||
|
|
11
org/testdata/inline.html
vendored
11
org/testdata/inline.html
vendored
|
@ -43,6 +43,17 @@ links with slashes do not become <em>emphasis</em>: <a href="https://somelinksho
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<p>
|
<p>
|
||||||
|
inline source blocks like <div class="src src-inline src-html">
|
||||||
|
<div class="highlight-inline">
|
||||||
|
<pre>
|
||||||
|
<h1>hello</h1>
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<p>
|
||||||
<code class="verbatim">multiline emphasis is
|
<code class="verbatim">multiline emphasis is
|
||||||
supported - and respects MaxEmphasisNewLines (default: 1)</code>
|
supported - and respects MaxEmphasisNewLines (default: 1)</code>
|
||||||
<em>so this
|
<em>so this
|
||||||
|
|
1
org/testdata/inline.org
vendored
1
org/testdata/inline.org
vendored
|
@ -8,6 +8,7 @@
|
||||||
- links with slashes do not become /emphasis/: [[https://somelinkshouldntrenderaccidentalemphasis.com]]/ /emphasis/
|
- links with slashes do not become /emphasis/: [[https://somelinkshouldntrenderaccidentalemphasis.com]]/ /emphasis/
|
||||||
- _underlined_ *bold* =verbatim= ~code~ +strikethrough+
|
- _underlined_ *bold* =verbatim= ~code~ +strikethrough+
|
||||||
- *bold string with an *asterisk inside*
|
- *bold string with an *asterisk inside*
|
||||||
|
- inline source blocks like src_html[:eval no]{<h1>hello</h1>}
|
||||||
- =multiline emphasis is
|
- =multiline emphasis is
|
||||||
supported - and respects MaxEmphasisNewLines (default: 1)=
|
supported - and respects MaxEmphasisNewLines (default: 1)=
|
||||||
/so this
|
/so this
|
||||||
|
|
1
org/testdata/inline.pretty_org
vendored
1
org/testdata/inline.pretty_org
vendored
|
@ -8,6 +8,7 @@
|
||||||
- links with slashes do not become /emphasis/: [[https://somelinkshouldntrenderaccidentalemphasis.com]]/ /emphasis/
|
- links with slashes do not become /emphasis/: [[https://somelinkshouldntrenderaccidentalemphasis.com]]/ /emphasis/
|
||||||
- _underlined_ *bold* =verbatim= ~code~ +strikethrough+
|
- _underlined_ *bold* =verbatim= ~code~ +strikethrough+
|
||||||
- *bold string with an *asterisk inside*
|
- *bold string with an *asterisk inside*
|
||||||
|
- inline source blocks like src_html[:eval no]{<h1>hello</h1>}
|
||||||
- =multiline emphasis is
|
- =multiline emphasis is
|
||||||
supported - and respects MaxEmphasisNewLines (default: 1)=
|
supported - and respects MaxEmphasisNewLines (default: 1)=
|
||||||
/so this
|
/so this
|
||||||
|
|
|
@ -18,6 +18,7 @@ type Writer interface {
|
||||||
WriteNodeWithName(NodeWithName)
|
WriteNodeWithName(NodeWithName)
|
||||||
WriteHeadline(Headline)
|
WriteHeadline(Headline)
|
||||||
WriteBlock(Block)
|
WriteBlock(Block)
|
||||||
|
WriteInlineBlock(InlineBlock)
|
||||||
WriteExample(Example)
|
WriteExample(Example)
|
||||||
WriteDrawer(Drawer)
|
WriteDrawer(Drawer)
|
||||||
WritePropertyDrawer(PropertyDrawer)
|
WritePropertyDrawer(PropertyDrawer)
|
||||||
|
@ -57,6 +58,8 @@ func WriteNodes(w Writer, nodes ...Node) {
|
||||||
w.WriteHeadline(n)
|
w.WriteHeadline(n)
|
||||||
case Block:
|
case Block:
|
||||||
w.WriteBlock(n)
|
w.WriteBlock(n)
|
||||||
|
case InlineBlock:
|
||||||
|
w.WriteInlineBlock(n)
|
||||||
case Example:
|
case Example:
|
||||||
w.WriteExample(n)
|
w.WriteExample(n)
|
||||||
case Drawer:
|
case Drawer:
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue