Implement inline source blocks

https://orgmode.org/manual/Structure-of-Code-Blocks.html
This commit is contained in:
Niklas Fasching 2020-04-16 14:28:40 +02:00
parent 6ed46ba95d
commit 3018ace8d0
8 changed files with 83 additions and 21 deletions

View file

@ -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>`
} }

View file

@ -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) {

View file

@ -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) }

View file

@ -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...)

View file

@ -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>
&lt;h1&gt;hello&lt;/h1&gt;
</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

View file

@ -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

View file

@ -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

View file

@ -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: