From 866c063bf226a783350283d87c2e460c30771acf Mon Sep 17 00:00:00 2001
From: Arun Isaac
Date: Mon, 21 Jun 2021 15:41:39 +0530
Subject: ccwl: Link source links to entire s-exp.

* ccwl/skribilo.scm (sexp-position, position->line-number,
sexp-file-lines): New functions.
(source-ref): Refer to entire s-exp, not just a single line.
---
 ccwl/skribilo.scm | 68 +++++++++++++++++++++++++++++++++++++++++--------------
 1 file changed, 51 insertions(+), 17 deletions(-)

diff --git a/ccwl/skribilo.scm b/ccwl/skribilo.scm
index 815448c..7894ad4 100644
--- a/ccwl/skribilo.scm
+++ b/ccwl/skribilo.scm
@@ -66,29 +66,63 @@
 		  ,@(the-options opts #:ident #:class #:short #:long)))
        (body (the-body opts))))
 
-;; Source links
+;; S-exp source links
+(define (sexp-position str regexp)
+  "Return (START . END) where START is the start of the match to
+REGEXP in STR and END is the end of the sexp beginning at START. START
+and END are character positions indexed from 0. If multiple matches
+are found, error out."
+  (cond
+   ((string-match regexp str)
+    => (lambda (match-struct)
+         (let ((start (match:start match-struct)))
+           (if (string-match regexp (substring str (1+ start)))
+               (error "source-ref: regexp found on multiple lines" regexp)
+               (cons start
+                     (1- (- (string-length str)
+                            (string-length
+                             (call-with-input-string (substring str start)
+                               (lambda (port)
+                                 (read port)
+                                 (get-string-all port)))))))))))
+   (else
+    (error "source-sexp-ref: regexp not found" regexp))))
+
+(define (position->line-number str position)
+  "Return the line number in STR corresponding to POSITION."
+  (string-fold (lambda (c result)
+                 (if (char=? c #\newline)
+                     (1+ result)
+                     result))
+               1
+               (substring str 0 position)))
+
+(define (sexp-file-lines file regexp)
+    "Return (START . END) where START is the start of the match to
+REGEXP in STR and END is the end of the sexp beginning at START. START
+and END are line numbers indexed from 1."
+    (let ((str (call-with-input-file file get-string-all)))
+      (match (sexp-position str regexp)
+        ((start . end)
+         (cons (position->line-number str start)
+               (position->line-number str end))))))
+
 (define (source-ref file regexp text)
   (call-with-input-file file
     (lambda (port)
-      (ref #:url (call-with-input-file file
-                   (lambda (port)
-                     (match (port-transduce (compose (tenumerate 1)
-                                                     (tfilter-map
-                                                      (match-lambda
-                                                        ((line-number . line)
-                                                         (and (string-match regexp line)
-                                                              line-number)))))
-                                            rcons
-                                            get-line
-                                            port)
-                       ((line-number)
+      (ref #:url (match (sexp-file-lines file regexp)
+                   ((start-line . end-line)
+                    (if (= start-line end-line)
+                        (string-append %source-uri-base
+                                       file
+                                       "#L"
+                                       (number->string start-line))
                         (string-append %source-uri-base
                                        file
                                        "#L"
-                                       (number->string line-number)))
-                       ((line-numbers ...)
-                        (error "source-ref: regexp found on multiple lines"
-                               regexp line-numbers)))))
+                                       (number->string start-line)
+                                       "-L"
+                                       (number->string end-line)))))
            #:text text))))
 
 ;; HTML engine customizations
-- 
cgit v1.2.3