diff --git a/blorg/util.go b/blorg/util.go
index 05d74e2..ef588db 100644
--- a/blorg/util.go
+++ b/blorg/util.go
@@ -55,7 +55,7 @@ func getWriter() org.Writer {
return w
}
-func highlightCodeBlock(source, lang string, inline bool) string {
+func highlightCodeBlock(source, lang string, inline bool, params map[string]string) string {
var w strings.Builder
l := lexers.Get(lang)
if l == nil {
@@ -63,7 +63,14 @@ func highlightCodeBlock(source, lang string, inline bool) string {
}
l = chroma.Coalesce(l)
it, _ := l.Tokenise(nil, source)
- _ = html.New().Format(&w, styles.Get("github"), it)
+ options := []html.Option{}
+ if params[":hl_lines"] != "" {
+ ranges := org.ParseRanges(params[":hl_lines"])
+ if ranges != nil {
+ options = append(options, html.HighlightLines(ranges))
+ }
+ }
+ _ = html.New(options...).Format(&w, styles.Get("github"), it)
if inline {
return `
` + "\n" + w.String() + "\n" + `
`
}
diff --git a/main.go b/main.go
index 0b0a21a..62a22f3 100644
--- a/main.go
+++ b/main.go
@@ -119,7 +119,7 @@ func render(args []string) {
}
}
-func highlightCodeBlock(source, lang string, inline bool) string {
+func highlightCodeBlock(source, lang string, inline bool, params map[string]string) string {
var w strings.Builder
l := lexers.Get(lang)
if l == nil {
@@ -127,7 +127,14 @@ func highlightCodeBlock(source, lang string, inline bool) string {
}
l = chroma.Coalesce(l)
it, _ := l.Tokenise(nil, source)
- _ = html.New().Format(&w, styles.Get("friendly"), it)
+ options := []html.Option{}
+ if params[":hl_lines"] != "" {
+ ranges := org.ParseRanges(params[":hl_lines"])
+ if ranges != nil {
+ options = append(options, html.HighlightLines(ranges))
+ }
+ }
+ _ = html.New(options...).Format(&w, styles.Get("friendly"), it)
if inline {
return `` + "\n" + w.String() + "\n" + `
`
}
diff --git a/org/html_writer.go b/org/html_writer.go
index fc0780a..5f388d9 100644
--- a/org/html_writer.go
+++ b/org/html_writer.go
@@ -19,7 +19,7 @@ import (
// HTMLWriter exports an org document into a html document.
type HTMLWriter struct {
ExtendingWriter Writer
- HighlightCodeBlock func(source, lang string, inline bool) string
+ HighlightCodeBlock func(source, lang string, inline bool, params map[string]string) string
PrettyRelativeLinks bool
strings.Builder
@@ -66,7 +66,7 @@ func NewHTMLWriter() *HTMLWriter {
document: &Document{Configuration: defaultConfig},
log: defaultConfig.Log,
htmlEscape: true,
- HighlightCodeBlock: func(source, lang string, inline bool) string {
+ HighlightCodeBlock: func(source, lang string, inline bool, params map[string]string) string {
if inline {
return fmt.Sprintf("", html.EscapeString(source))
}
@@ -129,7 +129,7 @@ func (w *HTMLWriter) WriteBlock(b Block) {
if len(b.Parameters) >= 1 {
lang = strings.ToLower(b.Parameters[0])
}
- content = w.HighlightCodeBlock(content, lang, false)
+ content = w.HighlightCodeBlock(content, lang, false, params)
w.WriteString(fmt.Sprintf("\n%s\n
\n", lang, content))
case "EXAMPLE":
w.WriteString(`` + "\n" + html.EscapeString(content) + "\n
\n")
@@ -159,7 +159,7 @@ func (w *HTMLWriter) WriteInlineBlock(b InlineBlock) {
switch b.Name {
case "src":
lang := strings.ToLower(b.Parameters[0])
- content = w.HighlightCodeBlock(content, lang, true)
+ content = w.HighlightCodeBlock(content, lang, true, nil)
w.WriteString(fmt.Sprintf("\n%s\n
", lang, content))
case "export":
if strings.ToLower(b.Parameters[0]) == "html" {
diff --git a/org/testdata/hl-lines.html b/org/testdata/hl-lines.html
new file mode 100644
index 0000000..c458903
--- /dev/null
+++ b/org/testdata/hl-lines.html
@@ -0,0 +1,12 @@
+Lines in a source block can be highlighted with :hl_lines
.
+
+
+
+(+ 1 2)
+(+ 1 2)
+(+ 1 2)
+(+ 1 2)
+(+ 1 2)
+
+
+
diff --git a/org/testdata/hl-lines.org b/org/testdata/hl-lines.org
new file mode 100644
index 0000000..17851b8
--- /dev/null
+++ b/org/testdata/hl-lines.org
@@ -0,0 +1,9 @@
+Lines in a source block can be highlighted with =:hl_lines=.
+
+#+begin_src emacs-lisp :hl_lines 3-4
+(+ 1 2)
+(+ 1 2)
+(+ 1 2)
+(+ 1 2)
+(+ 1 2)
+#+end_src
diff --git a/org/testdata/hl-lines.pretty_org b/org/testdata/hl-lines.pretty_org
new file mode 100644
index 0000000..5d7ba70
--- /dev/null
+++ b/org/testdata/hl-lines.pretty_org
@@ -0,0 +1,9 @@
+Lines in a source block can be highlighted with =:hl_lines=.
+
+#+BEGIN_SRC emacs-lisp :hl_lines 3-4
+(+ 1 2)
+(+ 1 2)
+(+ 1 2)
+(+ 1 2)
+(+ 1 2)
+#+END_SRC
diff --git a/org/util.go b/org/util.go
index c25bf27..b83b6f4 100644
--- a/org/util.go
+++ b/org/util.go
@@ -1,5 +1,10 @@
package org
+import (
+ "strconv"
+ "strings"
+)
+
func isSecondBlankLine(d *Document, i int) bool {
if i-1 <= 0 {
return false
@@ -17,3 +22,49 @@ func isImageOrVideoLink(n Node) bool {
}
return false
}
+
+// Parse ranges like this:
+// "3-5" -> [[3, 5]]
+// "3 8-10" -> [[3, 3], [8, 10]]
+// "3 5 6" -> [[3, 3], [5, 5], [6, 6]]
+//
+// This is Hugo's hlLinesToRanges with "startLine" removed and errors
+// ignored.
+func ParseRanges(s string) [][2]int {
+ var ranges [][2]int
+ s = strings.TrimSpace(s)
+ if s == "" {
+ return ranges
+ }
+ fields := strings.Split(s, " ")
+ for _, field := range fields {
+ field = strings.TrimSpace(field)
+ if field == "" {
+ continue
+ }
+ numbers := strings.Split(field, "-")
+ var r [2]int
+ if len(numbers) > 1 {
+ first, err := strconv.Atoi(numbers[0])
+ if err != nil {
+ return ranges
+ }
+ second, err := strconv.Atoi(numbers[1])
+ if err != nil {
+ return ranges
+ }
+ r[0] = first
+ r[1] = second
+ } else {
+ first, err := strconv.Atoi(numbers[0])
+ if err != nil {
+ return ranges
+ }
+ r[0] = first
+ r[1] = first
+ }
+
+ ranges = append(ranges, r)
+ }
+ return ranges
+}
diff --git a/org/util_test.go b/org/util_test.go
new file mode 100644
index 0000000..3e1ad89
--- /dev/null
+++ b/org/util_test.go
@@ -0,0 +1,38 @@
+package org
+
+import (
+ "fmt"
+ "testing"
+)
+
+var parseRangesTests = map[string][][2]int{
+ "3-5": {{3, 5}},
+ "3 8-10": {{3, 3}, {8, 10}},
+ "3 5 6": {{3, 3}, {5, 5}, {6, 6}},
+ " 9-10 5-6 3 ": {{9, 10}, {5, 6}, {3, 3}},
+}
+
+func TestParseRanges(t *testing.T) {
+ for s, expected := range parseRangesTests {
+ t.Run(s, func(t *testing.T) {
+ actual := ParseRanges(s)
+ // If this fails it looks like:
+ // util_test.go:: 9-10 5-6 3 :
+ // --- Actual
+ // +++ Expected
+ // @@ -1 +1 @@
+ // -[[9 10] [5 9] [3 3]]
+ // +[[9 10] [5 6] [3 3]]
+ if len(actual) != len(expected) {
+ t.Errorf("%v:\n%v", s, diff(fmt.Sprintf("%v", actual), fmt.Sprintf("%v", expected)))
+ } else {
+ for i := range actual {
+ if actual[i] != expected[i] {
+ t.Errorf("%v:\n%v", s, diff(fmt.Sprintf("%v", actual), fmt.Sprintf("%v", expected)))
+ }
+ }
+ }
+ })
+ }
+
+}