diff --git a/CHANGELOG.md b/CHANGELOG.md index 6a4b3eb7..581c7abd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ ### Bugs fixed +* [#656](https://github.com/clojure-emacs/clojure-mode/issues/656): Fix clojure-find-ns when ns form is preceded by other forms. + * [#593](https://github.com/clojure-emacs/clojure-mode/issues/593): Fix clojure-find-ns when ns form is preceded by whitespace or inside comment form. ## 5.16.2 (2023-08-23) diff --git a/clojure-mode.el b/clojure-mode.el index 43030a1d..151bd3be 100644 --- a/clojure-mode.el +++ b/clojure-mode.el @@ -2125,7 +2125,7 @@ content) are considered part of the preceding sexp." (make-obsolete-variable 'clojure-namespace-name-regex 'clojure-namespace-regexp "5.12.0") (defconst clojure-namespace-regexp - (rx line-start (zero-or-more whitespace) "(" (? "clojure.core/") (or "in-ns" "ns" "ns+") symbol-end)) + (rx "(" (? "clojure.core/") (or "in-ns" "ns" "ns+") symbol-end)) (defcustom clojure-cache-ns nil "Whether to cache the results of `clojure-find-ns'. @@ -2148,12 +2148,13 @@ DIRECTION is `forward' or `backward'." #'search-backward-regexp))) (while (and (not candidate) (funcall fn clojure-namespace-regexp nil t)) - (let ((end (match-end 0))) + (let ((start (match-beginning 0)) + (end (match-end 0))) (save-excursion - (save-match-data - (goto-char end) - (clojure-forward-logical-sexp) - (unless (or (clojure--in-string-p) (clojure--in-comment-p) (clojure-top-level-form-p "comment")) + (when (clojure--looking-at-top-level-form start) + (save-match-data + (goto-char end) + (clojure-forward-logical-sexp) (setq candidate (string-remove-prefix "'" (thing-at-point 'symbol)))))))) candidate)) @@ -2270,6 +2271,16 @@ This will skip over sexps that don't represent objects, so that ^hints and (backward-sexp 1)) (setq n (1- n)))))) +(defun clojure--looking-at-top-level-form (&optional point) + "Return truthy if form at POINT is a top level form." + (save-excursion + (when point (goto-char point)) + (and (looking-at-p "(") + (= (point) + (progn (forward-char) + (beginning-of-defun-raw) + (point)))))) + (defun clojure-top-level-form-p (first-form) "Return truthy if the first form matches FIRST-FORM." (condition-case nil diff --git a/test/clojure-mode-sexp-test.el b/test/clojure-mode-sexp-test.el index aaeb798d..11bf519f 100644 --- a/test/clojure-mode-sexp-test.el +++ b/test/clojure-mode-sexp-test.el @@ -41,7 +41,26 @@ (wrong))" (expect (let ((beginning-of-defun-function nil)) (clojure-top-level-form-p "comment")))))) - +(describe "clojure--looking-at-top-level-form" + (it "should return nil when point is inside a top level form" + (with-clojure-buffer-point + "(comment + |(ns foo))" + (expect (clojure--looking-at-top-level-form) :to-equal nil)) + (with-clojure-buffer-point + "\"|(ns foo)\"" + (expect (clojure--looking-at-top-level-form) :to-equal nil)) + (with-clojure-buffer-point + "^{:fake-ns |(ns foo)}" + (expect (clojure--looking-at-top-level-form) :to-equal nil))) + (it "should return true when point is looking at a top level form" + (with-clojure-buffer-point + "(comment + |(ns foo))" + (expect (clojure--looking-at-top-level-form (point-min)) :to-equal t)) + (with-clojure-buffer-point + "|(ns foo)" + (expect (clojure--looking-at-top-level-form) :to-equal t)))) (describe "clojure-beginning-of-defun-function" (it "should go to top level form" (with-clojure-buffer-point @@ -164,9 +183,9 @@ (expect (equal "baz-quux" (clojure-find-ns)))) (let ((data '(("\"\n(ns foo-bar)\"\n" "(in-ns 'baz-quux)" "baz-quux") - (";(ns foo-bar)\n" "(in-ns 'baz-quux)" "baz-quux") + (";(ns foo-bar)\n" "(in-ns 'baz-quux2)" "baz-quux2") ("(ns foo-bar)\n" "\"\n(in-ns 'baz-quux)\"" "foo-bar") - ("(ns foo-bar)\n" ";(in-ns 'baz-quux)" "foo-bar")))) + ("(ns foo-bar2)\n" ";(in-ns 'baz-quux)" "foo-bar2")))) (pcase-dolist (`(,form1 ,form2 ,expected) data) (with-clojure-buffer form1 (save-excursion (insert form2)) diff --git a/test/clojure-mode-util-test.el b/test/clojure-mode-util-test.el index 3f6e2de7..565773d4 100644 --- a/test/clojure-mode-util-test.el +++ b/test/clojure-mode-util-test.el @@ -142,6 +142,27 @@ (with-clojure-buffer "(ns foo) (ns-unmap *ns* 'map) (ns.misleading 1 2 3)" + (expect (clojure-find-ns) :to-equal "foo"))) + (it "should skip leading garbage" + (with-clojure-buffer " (ns foo)" + (expect (clojure-find-ns) :to-equal "foo")) + (with-clojure-buffer "1(ns foo)" + (expect (clojure-find-ns) :to-equal "foo")) + (with-clojure-buffer "1 (ns foo)" + (expect (clojure-find-ns) :to-equal "foo")) + (with-clojure-buffer "1 +(ns foo)" + (expect (clojure-find-ns) :to-equal "foo")) + (with-clojure-buffer "[1] +(ns foo)" + (expect (clojure-find-ns) :to-equal "foo")) + (with-clojure-buffer "[1] (ns foo)" + (expect (clojure-find-ns) :to-equal "foo")) + (with-clojure-buffer "[1](ns foo)" + (expect (clojure-find-ns) :to-equal "foo")) + (with-clojure-buffer "(ns)(ns foo)" + (expect (clojure-find-ns) :to-equal "foo")) + (with-clojure-buffer "(ns )(ns foo)" (expect (clojure-find-ns) :to-equal "foo")))) (describe "clojure-sort-ns"