Commita09184e2Recorded18 Apr 2026Repositorysigil-lsp
sigil-lsp: skip comments and strings when locating the first (import ...)
Message
The previous (string-find text "(import ") would happily match an import that lived inside a ;;; docstring at the top of a file — socket.sgl has an example (import (sigil socket) (sigil io)) in its file-level comment block, so the quick-fix was rewriting the example instead of the real define-library clause.
Replaced the stdlib search with a code-aware scan that steps past ; line comments and "..." string literals before looking for the (import literal. matching-close already skipped them while paren-balancing inside the form; this extends the same handling to the lead-in search.
Manually verified on socket.sgl: the action's target is line 37 (the real (import (sigil string)) clause), not the example buried in the commentary.
Changed
src/sigil/lsp/code-actions.sgl | 31 +++++++++++++++++++++++++++++--
1 file changed, 29 insertions(+), 2 deletions(-)Diff
src/sigil/lsp/code-actions.sglmodified
@@ -99,7 +99,8 @@
99
`((range . ,(range (position 0 0) (position 0 0))) 100
(newText . ,(string-append "(import " mod ")\n")))))) 101
−102
;; Scan TEXT for the first `(import ` form. On success returns+102
;; Scan TEXT for the first `(import ` form at code level (skipping+103
;; `;` comments and "..." strings). On success returns 104
;; (list CLOSE-OFFSET INDENT-COL) where CLOSE-OFFSET is the offset 105
;; of the final ')' of the (import ...) form, and INDENT-COL is the 106
;; column the next module spec should align to. Returns #f if no@@ -107,12 +108,38 @@
108
(define (find-first-import-end text) 109
(let* ((marker "(import ") 110
(mlen (string-length marker))−110
(start (string-find text marker)))+111
(start (find-code-substring text marker))) 112
(and start 113
(let ((close (matching-close text start))) 114
(and close 115
(list close (column-at text (+ start mlen)))))))) 116
+117
;; Walk TEXT looking for NEEDLE at a position that isn't inside+118
;; a `;...\n` line comment or a "..." string literal. Returns the+119
;; offset of the first such match, or #f.+120
(define (find-code-substring text needle)+121
(let ((tlen (string-length text))+122
(nlen (string-length needle)))+123
(let loop ((i 0))+124
(cond+125
((> (+ i nlen) tlen) #f)+126
(else+127
(let ((c (string-ref text i)))+128
(cond+129
((char=? c #\;) (loop (skip-line text (+ i 1))))+130
((char=? c #\") (loop (skip-string text (+ i 1))))+131
((substring-matches? text i needle nlen) i)+132
(else (loop (+ i 1))))))))))+133
+134
(define (substring-matches? text i needle nlen)+135
(let loop ((k 0))+136
(cond+137
((>= k nlen) #t)+138
((not (char=? (string-ref text (+ i k))+139
(string-ref needle k)))+140
#f)+141
(else (loop (+ k 1))))))+142
143
;; Given an offset to a '(' in TEXT, return the offset of its 144
;; matching ')', or #f. Skips over nested parens, strings, and 145
;; line comments.