Skip to content

Commit 96e3e3f

Browse files
committed
Merge pull request #321 from Malabarba/fix-sexp
[Fix clojure-emacs/cider#1323] Sexp navigation near end of buffer
2 parents 27c2a6f + fe75732 commit 96e3e3f

File tree

3 files changed

+103
-28
lines changed

3 files changed

+103
-28
lines changed

clojure-mode.el

Lines changed: 39 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@
6666

6767
(require 'cl-lib)
6868
(require 'imenu)
69+
(require 'newcomment)
6970

7071
(declare-function lisp-fill-paragraph "lisp-mode" (&optional justify))
7172

@@ -682,9 +683,11 @@ Implementation function for `clojure--find-indent-spec'."
682683
(let* ((function (thing-at-point 'symbol))
683684
(method (or (when function ;; Is there a spec here?
684685
(clojure--get-indent-method function))
685-
(progn (up-list) ;; Otherwise look higher up.
686-
(clojure-backward-logical-sexp 1)
687-
(clojure--find-indent-spec-backtracking)))))
686+
;; `up-list' errors on unbalanced sexps.
687+
(ignore-errors
688+
(up-list) ;; Otherwise look higher up.
689+
(clojure-backward-logical-sexp 1)
690+
(clojure--find-indent-spec-backtracking)))))
688691
(when (numberp method)
689692
(setq method (list method)))
690693
(pcase method
@@ -733,6 +736,18 @@ LAST-SEXP is the start of the previous sexp."
733736
(skip-chars-forward "[:blank:]")
734737
(current-column)))
735738

739+
(defun clojure--not-function-form-p ()
740+
"Non-nil if form at point doesn't represent a function call."
741+
(or (member (char-after) '(?\[ ?\{))
742+
(save-excursion ;; Catch #?@ (:cljs ...)
743+
(skip-chars-backward "\r\n[:blank:]")
744+
(when (eq (char-before) ?@)
745+
(forward-char -1))
746+
(and (eq (char-before) ?\?)
747+
(eq (char-before (1- (point))) ?\#)))
748+
;; Car of form is not a symbol.
749+
(not (looking-at ".\\(?:\\sw\\|\\s_\\)"))))
750+
736751
(defun clojure-indent-function (indent-point state)
737752
"When indenting a line within a function call, indent properly.
738753
@@ -760,12 +775,7 @@ This function also returns nil meaning don't specify the indentation."
760775
;; Goto to the open-paren.
761776
(goto-char (elt state 1))
762777
;; Maps, sets, vectors and reader conditionals.
763-
(if (or (member (char-after) '(?\[ ?\{))
764-
(and (eq (char-before) ?\?)
765-
(eq (char-before (1- (point))) ?\#))
766-
;; Car of form is not a symbol.
767-
(and (elt state 2)
768-
(not (looking-at ".\\sw\\|.\\s_"))))
778+
(if (clojure--not-function-form-p)
769779
(1+ (current-column))
770780
;; Function or macro call.
771781
(forward-char 1)
@@ -1103,23 +1113,23 @@ Returns a list pair, e.g. (\"defn\" \"abc\") or (\"deftest\" \"some-test\")."
11031113

11041114

11051115
;;; Sexp navigation
1106-
(defun clojure--looking-at-logical-sexp ()
1116+
(defun clojure--looking-at-non-logical-sexp ()
11071117
"Return non-nil if sexp after point represents code.
11081118
Sexps that don't represent code are ^metadata or #reader.macros."
1109-
(forward-sexp 1)
1110-
(forward-sexp -1)
1111-
(not (looking-at-p "\\^\\|#[?[:alpha:]]")))
1119+
(comment-normalize-vars)
1120+
(comment-forward (point-max))
1121+
(looking-at-p "\\^\\|#[?[:alpha:]]"))
11121122

11131123
(defun clojure-forward-logical-sexp (&optional n)
11141124
"Move forward N logical sexps.
11151125
This will skip over sexps that don't represent objects, so that ^hints and
11161126
#reader.macros are considered part of the following sexp."
11171127
(interactive "p")
1118-
(let ((forward-sexp-function nil))
1119-
(if (< n 0)
1120-
(clojure-backward-logical-sexp (- n))
1128+
(if (< n 0)
1129+
(clojure-backward-logical-sexp (- n))
1130+
(let ((forward-sexp-function nil))
11211131
(while (> n 0)
1122-
(while (not (clojure--looking-at-logical-sexp))
1132+
(while (clojure--looking-at-non-logical-sexp)
11231133
(forward-sexp 1))
11241134
;; The actual sexp
11251135
(forward-sexp 1)
@@ -1132,17 +1142,18 @@ This will skip over sexps that don't represent objects, so that ^hints and
11321142
(interactive "p")
11331143
(if (< n 0)
11341144
(clojure-forward-logical-sexp (- n))
1135-
(while (> n 0)
1136-
;; The actual sexp
1137-
(backward-sexp 1)
1138-
;; Non-logical sexps.
1139-
(while (and (not (bobp))
1140-
(ignore-errors
1141-
(save-excursion
1142-
(backward-sexp 1)
1143-
(not (clojure--looking-at-logical-sexp)))))
1144-
(backward-sexp 1))
1145-
(setq n (1- n)))))
1145+
(let ((forward-sexp-function nil))
1146+
(while (> n 0)
1147+
;; The actual sexp
1148+
(backward-sexp 1)
1149+
;; Non-logical sexps.
1150+
(while (and (not (bobp))
1151+
(ignore-errors
1152+
(save-excursion
1153+
(backward-sexp 1)
1154+
(clojure--looking-at-non-logical-sexp))))
1155+
(backward-sexp 1))
1156+
(setq n (1- n))))))
11461157

11471158
(defconst clojurescript-font-lock-keywords
11481159
(eval-when-compile

test/clojure-mode-indentation-test.el

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -262,6 +262,45 @@ values of customisable variables."
262262
(x [_]
263263
1))")
264264

265+
(def-full-indent-test reader-conditionals
266+
"#?@ (:clj []
267+
:cljs [])")
268+
269+
270+
;;; Misc
271+
272+
(defun non-func (form-a form-b)
273+
(with-temp-buffer
274+
(clojure-mode)
275+
(insert form-a)
276+
(save-excursion (insert form-b))
277+
(clojure--not-function-form-p)))
278+
279+
(ert-deftest non-function-form ()
280+
(dolist (form '(("#?@ " "(c d)")
281+
("#?@" "(c d)")
282+
("#? " "(c d)")
283+
("#?" "(c d)")
284+
("" "[asda]")
285+
("" "{a b}")
286+
("#" "{a b}")
287+
("" "(~)")))
288+
(should (apply #'non-func form)))
289+
(dolist (form '("(c d)"
290+
"(.c d)"
291+
"(:c d)"
292+
"(c/a d)"
293+
"(.c/a d)"
294+
"(:c/a d)"
295+
"(c/a)"
296+
"(:c/a)"
297+
"(.c/a)"))
298+
(should-not (non-func "" form))
299+
(should-not (non-func "^hint" form))
300+
(should-not (non-func "#macro" form))
301+
(should-not (non-func "^hint " form))
302+
(should-not (non-func "#macro " form))))
303+
265304
(provide 'clojure-mode-indentation-test)
266305

267306
;; Local Variables:

test/clojure-mode-sexp-test.el

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,31 @@
4040
(clojure-backward-logical-sexp 1)
4141
(should (looking-at-p "\\^String biverse"))))
4242

43+
(ert-deftest test-buffer-corners ()
44+
(with-temp-buffer
45+
(insert "^String reverse")
46+
(clojure-mode)
47+
;; Return nil and don't error
48+
(should-not (clojure-backward-logical-sexp 100))
49+
(should (looking-at-p "\\^String reverse"))
50+
(should-not (clojure-forward-logical-sexp 100))
51+
(should (looking-at-p "$")))
52+
(with-temp-buffer
53+
(clojure-mode)
54+
(insert "(+ 10")
55+
(should-error (clojure-backward-logical-sexp 100))
56+
(goto-char (point-min))
57+
(should-error (clojure-forward-logical-sexp 100))
58+
;; Just don't hang.
59+
(goto-char (point-max))
60+
(should-not (clojure-forward-logical-sexp 1))
61+
(erase-buffer)
62+
(insert "(+ 10")
63+
(newline)
64+
(erase-buffer)
65+
(insert "(+ 10")
66+
(newline-and-indent)))
67+
4368
(provide 'clojure-mode-sexp-test)
4469

4570
;;; clojure-mode-sexp-test.el ends here

0 commit comments

Comments
 (0)