From 8f214c92fde68a97a147799daa84c074f5a38c32 Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Fri, 30 May 2014 22:47:33 +0300 Subject: [PATCH 001/379] Lay the groundwork of a test suite --- clojure-mode-test.el | 83 ++++++++++++++++++++++++++++++++++++++++++++ test-helper.el | 39 +++++++++++++++++++++ 2 files changed, 122 insertions(+) create mode 100644 clojure-mode-test.el create mode 100644 test-helper.el diff --git a/clojure-mode-test.el b/clojure-mode-test.el new file mode 100644 index 0000000..5eb5402 --- /dev/null +++ b/clojure-mode-test.el @@ -0,0 +1,83 @@ +;;; clojure-mode-test.el --- Clojure Mode: Unit test suite -*- lexical-binding: t; -*- + +;; Copyright (C) 2014 Bozhidar Batsov + +;; This file is not part of GNU Emacs. + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;;; Commentary: + +;; The unit test suite of Clojure Mode + +;;; Code: + +(require 'clojure-mode) +(require 'ert) + + +;;;; Utilities + +(defmacro clojure-test-with-temp-buffer (content &rest body) + "Evaluate BODY in a temporary buffer with CONTENTS." + (declare (debug t) + (indent 1)) + `(with-temp-buffer + (insert ,content) + (clojure-mode) + (font-lock-fontify-buffer) + (goto-char (point-min)) + ,@body)) + +(defun clojure-test-face-at (pos &optional content) + "Get the face at POS in CONTENT. + +If CONTENT is not given, return the face at POS in the current +buffer." + (if content + (clojure-test-with-temp-buffer content + (get-text-property pos 'face)) + (get-text-property pos 'face))) + +(defconst clojure-test-syntax-classes + [whitespace punctuation word symbol open-paren close-paren expression-prefix + string-quote paired-delim escape character-quote comment-start + comment-end inherit generic-comment generic-string] + "Readable symbols for syntax classes. + +Each symbol in this vector corresponding to the syntax code of +its index.") + +(defun clojure-test-syntax-at (pos) + "Get the syntax at POS. + +Get the syntax class symbol at POS, or nil if there is no syntax a +POS." + (let ((code (syntax-class (syntax-after pos)))) + (aref clojure-test-syntax-classes code))) + + +;;;; Font locking + +(ert-deftest clojure-mode-syntax-table/fontify-clojure-keyword () + :tags '(fontification syntax-table) + (should (eq (clojure-test-face-at 2 "{:something 20}") 'font-lock-constant-face))) + +(provide 'clojure-mode-test) + +;; Local Variables: +;; indent-tabs-mode: nil +;; End: + +;;; clojure-mode-test.el ends here diff --git a/test-helper.el b/test-helper.el new file mode 100644 index 0000000..558d4ed --- /dev/null +++ b/test-helper.el @@ -0,0 +1,39 @@ +;;; test-helper.el --- Clojure Mode: Non-interactive unit-test setup -*- lexical-binding: t; -*- + +;; Copyright (C) 2014 Bozhidar Batsov + +;; This file is not part of GNU Emacs. + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;;; Commentary: + +;; Non-interactive test suite setup for ERT Runner. + +;;; Code: + +(message "Running tests on Emacs %s" emacs-version) + +(let* ((current-file (if load-in-progress load-file-name (buffer-file-name))) + (source-directory (locate-dominating-file current-file "Cask")) + ;; Do not load outdated byte code for tests + (load-prefer-newer t)) + ;; Load the file under test + (load (expand-file-name "clojure-mode" source-directory))) + +;; Local Variables: +;; indent-tabs-mode: nil +;; End: + +;;; test-helper.el ends here From a1ecf40aa3cb32b62228358f7aa0a58044d682f6 Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Sat, 31 May 2014 01:13:20 +0300 Subject: [PATCH 002/379] Make it possible to check a range for a specific face --- clojure-mode-test.el | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/clojure-mode-test.el b/clojure-mode-test.el index 5eb5402..50639fb 100644 --- a/clojure-mode-test.el +++ b/clojure-mode-test.el @@ -40,15 +40,22 @@ (goto-char (point-min)) ,@body)) -(defun clojure-test-face-at (pos &optional content) - "Get the face at POS in CONTENT. +(defun clojure-get-face-at-range (start end) + (let ((start-face (get-text-property start 'face)) + (all-faces (cl-loop for i from start to end collect (get-text-property i 'face)))) + (if (cl-every (lambda (face) (eq face start-face)) all-faces) + start-face + 'various-faces))) -If CONTENT is not given, return the face at POS in the current +(defun clojure-test-face-at (start end &optional content) + "Get the face between START and END in CONTENT. + +If CONTENT is not given, return the face at the specified range in the current buffer." (if content (clojure-test-with-temp-buffer content - (get-text-property pos 'face)) - (get-text-property pos 'face))) + (clojure-get-face-at-range start end)) + (clojure-get-face-at-range start end))) (defconst clojure-test-syntax-classes [whitespace punctuation word symbol open-paren close-paren expression-prefix @@ -72,7 +79,7 @@ POS." (ert-deftest clojure-mode-syntax-table/fontify-clojure-keyword () :tags '(fontification syntax-table) - (should (eq (clojure-test-face-at 2 "{:something 20}") 'font-lock-constant-face))) + (should (eq (clojure-test-face-at 2 11 "{:something 20}") 'font-lock-constant-face))) (provide 'clojure-mode-test) From 38c4b4df1e5a3725a964601ea27f85a2fc5daaa6 Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Sat, 7 Jun 2014 13:34:43 +0300 Subject: [PATCH 003/379] Add a couple more font-lock tests --- clojure-mode-test.el | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/clojure-mode-test.el b/clojure-mode-test.el index 50639fb..21d5aba 100644 --- a/clojure-mode-test.el +++ b/clojure-mode-test.el @@ -81,6 +81,17 @@ POS." :tags '(fontification syntax-table) (should (eq (clojure-test-face-at 2 11 "{:something 20}") 'font-lock-constant-face))) +(ert-deftest clojure-mode-syntax-table/type () + :tags '(fontification syntax-table) + (should (eq (clojure-test-face-at 1 9 "SomeClass") 'font-lock-type-face))) + +(ert-deftest clojure-mode-syntax-table/namespaced-symbol () + :tags '(fontification syntax-table) + (clojure-test-with-temp-buffer "clo.core/something" + (should (eq (clojure-test-face-at 9 9) nil)) + (should (eq (clojure-test-face-at 1 8) 'font-lock-type-face)) + (should (eq (clojure-test-face-at 10 18) nil)))) + (provide 'clojure-mode-test) ;; Local Variables: From 9c08ad2cd041ee2ba5ae911d3e63ae82cf40472c Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Sun, 8 Jun 2014 15:16:42 +0300 Subject: [PATCH 004/379] Font-lock lambda params (%, %1, %2, etc) --- clojure-mode-test.el | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/clojure-mode-test.el b/clojure-mode-test.el index 21d5aba..afa0465 100644 --- a/clojure-mode-test.el +++ b/clojure-mode-test.el @@ -92,6 +92,13 @@ POS." (should (eq (clojure-test-face-at 1 8) 'font-lock-type-face)) (should (eq (clojure-test-face-at 10 18) nil)))) +(ert-deftest clojure-mode-syntax-table/lambda-params () + :tags '(fontification syntax-table) + (clojure-test-with-temp-buffer "#(+ % %2 %3)" + (should (eq (clojure-test-face-at 5 5) 'font-lock-variable-name-face)) + (should (eq (clojure-test-face-at 7 8) 'font-lock-variable-name-face)) + (should (eq (clojure-test-face-at 10 11) 'font-lock-variable-name-face)))) + (provide 'clojure-mode-test) ;; Local Variables: From d4dbbdfd94131646ab7dd662db688e3d7234b352 Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Mon, 9 Jun 2014 15:12:13 +0300 Subject: [PATCH 005/379] Fix font-locking bug for namespaced definitions --- clojure-mode-test.el | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/clojure-mode-test.el b/clojure-mode-test.el index afa0465..80edee0 100644 --- a/clojure-mode-test.el +++ b/clojure-mode-test.el @@ -92,6 +92,14 @@ POS." (should (eq (clojure-test-face-at 1 8) 'font-lock-type-face)) (should (eq (clojure-test-face-at 10 18) nil)))) +(ert-deftest clojure-mode-syntax-table/namespaced-def () + :tags '(fontification syntax-table) + (clojure-test-with-temp-buffer "(clo/defbar foo nil)" + (should (eq (clojure-test-face-at 2 4) 'font-lock-type-face)) + (should (eq (clojure-test-face-at 5 5) nil)) + (should (eq (clojure-test-face-at 6 11) 'font-lock-keyword-face)) + (should (eq (clojure-test-face-at 13 15) 'font-lock-function-name-face)))) + (ert-deftest clojure-mode-syntax-table/lambda-params () :tags '(fontification syntax-table) (clojure-test-with-temp-buffer "#(+ % %2 %3)" From 196d8d41d70903cb8363f13666d0dc54c980a022 Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Mon, 9 Jun 2014 15:13:40 +0300 Subject: [PATCH 006/379] Add missing require --- clojure-mode-test.el | 1 + 1 file changed, 1 insertion(+) diff --git a/clojure-mode-test.el b/clojure-mode-test.el index 80edee0..1088d5c 100644 --- a/clojure-mode-test.el +++ b/clojure-mode-test.el @@ -24,6 +24,7 @@ ;;; Code: (require 'clojure-mode) +(require 'cl-lib) (require 'ert) From 2f21638aed980d14c91e98eb2da4fee5b244c048 Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Mon, 9 Jun 2014 16:49:58 +0300 Subject: [PATCH 007/379] Use different font-locking for "variables", types and functions --- clojure-mode-test.el | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/clojure-mode-test.el b/clojure-mode-test.el index 1088d5c..c27b7a5 100644 --- a/clojure-mode-test.el +++ b/clojure-mode-test.el @@ -101,6 +101,24 @@ POS." (should (eq (clojure-test-face-at 6 11) 'font-lock-keyword-face)) (should (eq (clojure-test-face-at 13 15) 'font-lock-function-name-face)))) +(ert-deftest clojure-mode-syntax-table/variable-def () + :tags '(fontification syntax-table) + (clojure-test-with-temp-buffer "(def foo 10)" + (should (eq (clojure-test-face-at 2 4) 'font-lock-keyword-face)) + (should (eq (clojure-test-face-at 6 8) 'font-lock-variable-name-face)))) + +(ert-deftest clojure-mode-syntax-table/type-def () + :tags '(fontification syntax-table) + (clojure-test-with-temp-buffer "(deftype Foo)" + (should (eq (clojure-test-face-at 2 8) 'font-lock-keyword-face)) + (should (eq (clojure-test-face-at 10 12) 'font-lock-type-face)))) + +(ert-deftest clojure-mode-syntax-table/function-def () + :tags '(fontification syntax-table) + (clojure-test-with-temp-buffer "(defn foo [x] x)" + (should (eq (clojure-test-face-at 2 5) 'font-lock-keyword-face)) + (should (eq (clojure-test-face-at 7 9) 'font-lock-function-name-face)))) + (ert-deftest clojure-mode-syntax-table/lambda-params () :tags '(fontification syntax-table) (clojure-test-with-temp-buffer "#(+ % %2 %3)" From 5f268e139fa58ce8def60d263744a68688f72670 Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Wed, 11 Jun 2014 17:25:28 +0300 Subject: [PATCH 008/379] Font lock static method calls like SomeClass/someMethod --- clojure-mode-test.el | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/clojure-mode-test.el b/clojure-mode-test.el index c27b7a5..fd4615c 100644 --- a/clojure-mode-test.el +++ b/clojure-mode-test.el @@ -93,6 +93,13 @@ POS." (should (eq (clojure-test-face-at 1 8) 'font-lock-type-face)) (should (eq (clojure-test-face-at 10 18) nil)))) +(ert-deftest clojure-mode-syntax-table/static-method () + :tags '(fontification syntax-table) + (clojure-test-with-temp-buffer "Class/methodName" + (should (eq (clojure-test-face-at 6 6) nil)) + (should (eq (clojure-test-face-at 1 5) 'font-lock-type-face)) + (should (eq (clojure-test-face-at 7 16) 'font-lock-preprocessor-face)))) + (ert-deftest clojure-mode-syntax-table/namespaced-def () :tags '(fontification syntax-table) (clojure-test-with-temp-buffer "(clo/defbar foo nil)" From 4aa8c8d51606c62019c5c5e3cc72f332908a9f16 Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Wed, 11 Jun 2014 17:52:55 +0300 Subject: [PATCH 009/379] Font-lock SNAKE_CASE constants --- clojure-mode-test.el | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/clojure-mode-test.el b/clojure-mode-test.el index fd4615c..fe48713 100644 --- a/clojure-mode-test.el +++ b/clojure-mode-test.el @@ -100,6 +100,18 @@ POS." (should (eq (clojure-test-face-at 1 5) 'font-lock-type-face)) (should (eq (clojure-test-face-at 7 16) 'font-lock-preprocessor-face)))) +(ert-deftest clojure-mode-syntax-table/constant () + :tags '(fontification syntax-table) + (should (eq (clojure-test-face-at 1 5 "CONST") 'font-lock-constant-face)) + (should (eq (clojure-test-face-at 1 10 "CONST_NAME") 'font-lock-constant-face))) + +(ert-deftest clojure-mode-syntax-table/class-constant () + :tags '(fontification syntax-table) + (clojure-test-with-temp-buffer "Class/CONST_NAME" + (should (eq (clojure-test-face-at 6 6) nil)) + (should (eq (clojure-test-face-at 1 5) 'font-lock-type-face)) + (should (eq (clojure-test-face-at 7 16) 'font-lock-constant-face)))) + (ert-deftest clojure-mode-syntax-table/namespaced-def () :tags '(fontification syntax-table) (clojure-test-with-temp-buffer "(clo/defbar foo nil)" From 1227c5393d3ec2c43abdd031596a7c11c6dba956 Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Fri, 13 Jun 2014 15:51:33 +0300 Subject: [PATCH 010/379] Correct the boundaries of nil/true/false font-locking --- clojure-mode-test.el | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/clojure-mode-test.el b/clojure-mode-test.el index fe48713..f0aff76 100644 --- a/clojure-mode-test.el +++ b/clojure-mode-test.el @@ -145,6 +145,11 @@ POS." (should (eq (clojure-test-face-at 7 8) 'font-lock-variable-name-face)) (should (eq (clojure-test-face-at 10 11) 'font-lock-variable-name-face)))) +(ert-deftest clojure-mode-syntax-table/nil () + :tags '(fontification syntax-table) + (should (eq (clojure-test-face-at 4 6 "(= nil x)") 'font-lock-constant-face)) + (should-not (eq (clojure-test-face-at 3 5 "(fnil x)") 'font-lock-constant-face))) + (provide 'clojure-mode-test) ;; Local Variables: From d50c5db11f204dfd46a63ee5d0f9710bbd95a7c6 Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Fri, 13 Jun 2014 15:53:15 +0300 Subject: [PATCH 011/379] Add a couple of font-locking tests --- clojure-mode-test.el | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/clojure-mode-test.el b/clojure-mode-test.el index f0aff76..3db0ea6 100644 --- a/clojure-mode-test.el +++ b/clojure-mode-test.el @@ -150,6 +150,14 @@ POS." (should (eq (clojure-test-face-at 4 6 "(= nil x)") 'font-lock-constant-face)) (should-not (eq (clojure-test-face-at 3 5 "(fnil x)") 'font-lock-constant-face))) +(ert-deftest clojure-mode-syntax-table/true () + :tags '(fontification syntax-table) + (should (eq (clojure-test-face-at 4 7 "(= true x)") 'font-lock-constant-face))) + +(ert-deftest clojure-mode-syntax-table/false () + :tags '(fontification syntax-table) + (should (eq (clojure-test-face-at 4 8 "(= false x)") 'font-lock-constant-face))) + (provide 'clojure-mode-test) ;; Local Variables: From badb6f29cc14a5fb0b2a0cdd28d6b3dc6c0bfb1b Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Mon, 16 Jun 2014 09:39:25 +0300 Subject: [PATCH 012/379] Font-lock character literals --- clojure-mode-test.el | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/clojure-mode-test.el b/clojure-mode-test.el index 3db0ea6..e837af0 100644 --- a/clojure-mode-test.el +++ b/clojure-mode-test.el @@ -158,6 +158,13 @@ POS." :tags '(fontification syntax-table) (should (eq (clojure-test-face-at 4 8 "(= false x)") 'font-lock-constant-face))) +(ert-deftest clojure-mode-syntax-table/characters () + :tags '(fontification syntax-table) + (should (eq (clojure-test-face-at 1 2 "\\a") 'font-lock-string-face)) + (should (eq (clojure-test-face-at 1 8 "\\newline") 'font-lock-string-face)) + (should (eq (clojure-test-face-at 1 2 "\\1") 'font-lock-string-face)) + (should (eq (clojure-test-face-at 1 6 "\\u0032") 'font-lock-string-face))) + (provide 'clojure-mode-test) ;; Local Variables: From 1a9b88b7743220400309f84f70e5ec8019183289 Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Wed, 16 Jul 2014 18:48:00 +0300 Subject: [PATCH 013/379] Font-lock cljx features --- clojure-mode-test.el | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/clojure-mode-test.el b/clojure-mode-test.el index e837af0..4f0a38b 100644 --- a/clojure-mode-test.el +++ b/clojure-mode-test.el @@ -165,6 +165,11 @@ POS." (should (eq (clojure-test-face-at 1 2 "\\1") 'font-lock-string-face)) (should (eq (clojure-test-face-at 1 6 "\\u0032") 'font-lock-string-face))) +(ert-deftest clojure-mode-syntax-table/cljx () + :tags '(fontification syntax-table) + (should (eq (clojure-test-face-at 1 5 "#+clj x") 'font-lock-preprocessor-face)) + (should (eq (clojure-test-face-at 1 6 "#+cljs x") 'font-lock-preprocessor-face))) + (provide 'clojure-mode-test) ;; Local Variables: From fa63ec40b8bb091fa52b9536dcb8dfd2b741345f Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Mon, 21 Jul 2014 19:23:29 +0300 Subject: [PATCH 014/379] Introduce a few custom faces for things not covered by the standard font-lock faces --- clojure-mode-test.el | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/clojure-mode-test.el b/clojure-mode-test.el index 4f0a38b..fa7396a 100644 --- a/clojure-mode-test.el +++ b/clojure-mode-test.el @@ -80,7 +80,7 @@ POS." (ert-deftest clojure-mode-syntax-table/fontify-clojure-keyword () :tags '(fontification syntax-table) - (should (eq (clojure-test-face-at 2 11 "{:something 20}") 'font-lock-constant-face))) + (should (eq (clojure-test-face-at 2 11 "{:something 20}") 'clojure-keyword-face))) (ert-deftest clojure-mode-syntax-table/type () :tags '(fontification syntax-table) @@ -98,7 +98,7 @@ POS." (clojure-test-with-temp-buffer "Class/methodName" (should (eq (clojure-test-face-at 6 6) nil)) (should (eq (clojure-test-face-at 1 5) 'font-lock-type-face)) - (should (eq (clojure-test-face-at 7 16) 'font-lock-preprocessor-face)))) + (should (eq (clojure-test-face-at 7 16) 'clojure-interop-method-face)))) (ert-deftest clojure-mode-syntax-table/constant () :tags '(fontification syntax-table) @@ -160,10 +160,10 @@ POS." (ert-deftest clojure-mode-syntax-table/characters () :tags '(fontification syntax-table) - (should (eq (clojure-test-face-at 1 2 "\\a") 'font-lock-string-face)) - (should (eq (clojure-test-face-at 1 8 "\\newline") 'font-lock-string-face)) - (should (eq (clojure-test-face-at 1 2 "\\1") 'font-lock-string-face)) - (should (eq (clojure-test-face-at 1 6 "\\u0032") 'font-lock-string-face))) + (should (eq (clojure-test-face-at 1 2 "\\a") 'clojure-character-face)) + (should (eq (clojure-test-face-at 1 8 "\\newline") 'clojure-character-face)) + (should (eq (clojure-test-face-at 1 2 "\\1") 'clojure-character-face)) + (should (eq (clojure-test-face-at 1 6 "\\u0032") 'clojure-character-face))) (ert-deftest clojure-mode-syntax-table/cljx () :tags '(fontification syntax-table) From ff01ae5765df33b4ef20ef796e4a299cc91a36a5 Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Mon, 4 Aug 2014 16:40:50 +0300 Subject: [PATCH 015/379] Fix font-locking of namespaced constructor calls --- clojure-mode-test.el | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/clojure-mode-test.el b/clojure-mode-test.el index fa7396a..43e9471 100644 --- a/clojure-mode-test.el +++ b/clojure-mode-test.el @@ -86,6 +86,13 @@ POS." :tags '(fontification syntax-table) (should (eq (clojure-test-face-at 1 9 "SomeClass") 'font-lock-type-face))) +(ert-deftest clojure-mode-syntax-table/constructor () + :tags '(fontification syntax-table) + (should (eq (clojure-test-face-at 2 11 "(SomeClass.)") 'font-lock-type-face)) + (clojure-test-with-temp-buffer "(ns/SomeClass.)" + (should (eq (clojure-test-face-at 2 3) 'font-lock-type-face)) + (should (eq (clojure-test-face-at 5 14) 'font-lock-type-face)))) + (ert-deftest clojure-mode-syntax-table/namespaced-symbol () :tags '(fontification syntax-table) (clojure-test-with-temp-buffer "clo.core/something" From e3b81aa5fd9d6c8490644fcaf0ba5ad270ad159a Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Mon, 4 Aug 2014 16:41:23 +0300 Subject: [PATCH 016/379] Indentation fixes --- clojure-mode-test.el | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/clojure-mode-test.el b/clojure-mode-test.el index 43e9471..3ba3af8 100644 --- a/clojure-mode-test.el +++ b/clojure-mode-test.el @@ -103,9 +103,9 @@ POS." (ert-deftest clojure-mode-syntax-table/static-method () :tags '(fontification syntax-table) (clojure-test-with-temp-buffer "Class/methodName" - (should (eq (clojure-test-face-at 6 6) nil)) - (should (eq (clojure-test-face-at 1 5) 'font-lock-type-face)) - (should (eq (clojure-test-face-at 7 16) 'clojure-interop-method-face)))) + (should (eq (clojure-test-face-at 6 6) nil)) + (should (eq (clojure-test-face-at 1 5) 'font-lock-type-face)) + (should (eq (clojure-test-face-at 7 16) 'clojure-interop-method-face)))) (ert-deftest clojure-mode-syntax-table/constant () :tags '(fontification syntax-table) @@ -115,9 +115,9 @@ POS." (ert-deftest clojure-mode-syntax-table/class-constant () :tags '(fontification syntax-table) (clojure-test-with-temp-buffer "Class/CONST_NAME" - (should (eq (clojure-test-face-at 6 6) nil)) - (should (eq (clojure-test-face-at 1 5) 'font-lock-type-face)) - (should (eq (clojure-test-face-at 7 16) 'font-lock-constant-face)))) + (should (eq (clojure-test-face-at 6 6) nil)) + (should (eq (clojure-test-face-at 1 5) 'font-lock-type-face)) + (should (eq (clojure-test-face-at 7 16) 'font-lock-constant-face)))) (ert-deftest clojure-mode-syntax-table/namespaced-def () :tags '(fontification syntax-table) From 43d7203f073c21176a643207d88b83ac546cae3a Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Mon, 4 Aug 2014 18:13:15 +0300 Subject: [PATCH 017/379] Simplify font-locking of types used as type hints --- clojure-mode-test.el | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/clojure-mode-test.el b/clojure-mode-test.el index 3ba3af8..cca945e 100644 --- a/clojure-mode-test.el +++ b/clojure-mode-test.el @@ -86,6 +86,12 @@ POS." :tags '(fontification syntax-table) (should (eq (clojure-test-face-at 1 9 "SomeClass") 'font-lock-type-face))) +(ert-deftest clojure-mode-syntax-table/type-hint () + :tags '(fontification syntax-table) + (clojure-test-with-temp-buffer "#^SomeClass" + (should (eq (clojure-test-face-at 3 11) 'font-lock-type-face)) + (should (eq (clojure-test-face-at 1 2) nil)))) + (ert-deftest clojure-mode-syntax-table/constructor () :tags '(fontification syntax-table) (should (eq (clojure-test-face-at 2 11 "(SomeClass.)") 'font-lock-type-face)) From 699c3b2cab6acba6e163dd797d04b16b84c9ffd1 Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Mon, 4 Aug 2014 18:23:20 +0300 Subject: [PATCH 018/379] Refine keyword literals font-locking --- clojure-mode-test.el | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/clojure-mode-test.el b/clojure-mode-test.el index cca945e..2773fa4 100644 --- a/clojure-mode-test.el +++ b/clojure-mode-test.el @@ -171,6 +171,12 @@ POS." :tags '(fontification syntax-table) (should (eq (clojure-test-face-at 4 8 "(= false x)") 'font-lock-constant-face))) +(ert-deftest clojure-mode-syntax-table/keyword-meta () + :tags '(fontification syntax-table) + (clojure-test-with-temp-buffer "^:meta-data" + (should (eq (clojure-test-face-at 1 1) nil)) + (should (eq (clojure-test-face-at 2 11) 'clojure-keyword-face)))) + (ert-deftest clojure-mode-syntax-table/characters () :tags '(fontification syntax-table) (should (eq (clojure-test-face-at 1 2 "\\a") 'clojure-character-face)) From d71ff5266b81aa4286c34df505b860955795324e Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Tue, 12 Aug 2014 19:38:15 +0300 Subject: [PATCH 019/379] Improve namespace font-locking --- clojure-mode-test.el | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/clojure-mode-test.el b/clojure-mode-test.el index 2773fa4..04eadcc 100644 --- a/clojure-mode-test.el +++ b/clojure-mode-test.el @@ -99,6 +99,11 @@ POS." (should (eq (clojure-test-face-at 2 3) 'font-lock-type-face)) (should (eq (clojure-test-face-at 5 14) 'font-lock-type-face)))) +(ert-deftest clojure-mode-syntax-table/namespace () + :tags '(fontification syntax-table) + (should (eq (clojure-test-face-at 1 5 "one.p") 'font-lock-type-face)) + (should (eq (clojure-test-face-at 1 11 "one.p.top13") 'font-lock-type-face))) + (ert-deftest clojure-mode-syntax-table/namespaced-symbol () :tags '(fontification syntax-table) (clojure-test-with-temp-buffer "clo.core/something" From 001ee7dbba4f065d56ff89f48a35d2a9790e321f Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Wed, 13 Aug 2014 18:51:55 +0300 Subject: [PATCH 020/379] Fix the font-lock of interop calls like getX --- clojure-mode-test.el | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/clojure-mode-test.el b/clojure-mode-test.el index 04eadcc..80b636d 100644 --- a/clojure-mode-test.el +++ b/clojure-mode-test.el @@ -118,6 +118,13 @@ POS." (should (eq (clojure-test-face-at 1 5) 'font-lock-type-face)) (should (eq (clojure-test-face-at 7 16) 'clojure-interop-method-face)))) +(ert-deftest clojure-mode-syntax-table/interop-method () + :tags '(fontification syntax-table) + (should (eq (clojure-test-face-at 1 11 ".someMethod") 'clojure-interop-method-face)) + (should (eq (clojure-test-face-at 1 10 "someMethod") 'clojure-interop-method-face)) + (should (eq (clojure-test-face-at 1 11 "topHttpTest") 'clojure-interop-method-face)) + (should (eq (clojure-test-face-at 1 4 "getX") 'clojure-interop-method-face))) + (ert-deftest clojure-mode-syntax-table/constant () :tags '(fontification syntax-table) (should (eq (clojure-test-face-at 1 5 "CONST") 'font-lock-constant-face)) From 4f6441595b976595cfacdeded0a36a37e32473ce Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Thu, 21 Aug 2014 13:14:23 +0300 Subject: [PATCH 021/379] Font lock properly fully qualified type hints --- clojure-mode-test.el | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/clojure-mode-test.el b/clojure-mode-test.el index 80b636d..58c77cf 100644 --- a/clojure-mode-test.el +++ b/clojure-mode-test.el @@ -102,7 +102,8 @@ POS." (ert-deftest clojure-mode-syntax-table/namespace () :tags '(fontification syntax-table) (should (eq (clojure-test-face-at 1 5 "one.p") 'font-lock-type-face)) - (should (eq (clojure-test-face-at 1 11 "one.p.top13") 'font-lock-type-face))) + (should (eq (clojure-test-face-at 1 11 "one.p.top13") 'font-lock-type-face)) + (should (eq (clojure-test-face-at 2 12 "^one.p.top13") 'font-lock-type-face))) (ert-deftest clojure-mode-syntax-table/namespaced-symbol () :tags '(fontification syntax-table) From a8b250fe57fc935cfda5953b468ee98f2680537a Mon Sep 17 00:00:00 2001 From: Ryan Smith Date: Thu, 21 Aug 2014 14:57:28 -0700 Subject: [PATCH 022/379] @(dynamic)variable shouldn't break fontlock Currently `foo/some-var` will fontlock `foo`, but if this item is something that can be dereffed and you type `@foo/some-var`, `foo` is no longer font-locked. Similarly: `*dynamic-var*` is font locked, but `@` breaks the regex. In this change the `@` sign isn't font locked, but the var that follows it is. Conflicts: clojure-mode.el Add tests for dynamic-var and ns/refer fontlock --- clojure-mode-test.el | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/clojure-mode-test.el b/clojure-mode-test.el index 58c77cf..ba5cce4 100644 --- a/clojure-mode-test.el +++ b/clojure-mode-test.el @@ -202,6 +202,16 @@ POS." (should (eq (clojure-test-face-at 1 5 "#+clj x") 'font-lock-preprocessor-face)) (should (eq (clojure-test-face-at 1 6 "#+cljs x") 'font-lock-preprocessor-face))) +(ert-deftest clojure-mode-syntax-table/refer-ns () + :tags '(fontification syntax-table) + (should (eq (clojure-test-face-at 1 3 "foo/var") 'font-lock-type-face)) + (should (eq (clojure-test-face-at 2 4 "@foo/var") 'font-lock-type-face))) + +(ert-deftest clojure-mode-syntax-table/dynamic-var () + :tags '(fontification syntax-table) + (should (eq (clojure-test-face-at 1 10 "*some-var*") 'font-lock-variable-name-face)) + (should (eq (clojure-test-face-at 2 11 "@*some-var*") 'font-lock-variable-name-face))) + (provide 'clojure-mode-test) ;; Local Variables: From 49ee7f8fd779357b423cdbc563de773d5f07d5e8 Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Fri, 22 Aug 2014 17:55:37 +0300 Subject: [PATCH 023/379] Improve font-locking of punctuation characters --- clojure-mode-test.el | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/clojure-mode-test.el b/clojure-mode-test.el index ba5cce4..572c948 100644 --- a/clojure-mode-test.el +++ b/clojure-mode-test.el @@ -195,7 +195,9 @@ POS." (should (eq (clojure-test-face-at 1 2 "\\a") 'clojure-character-face)) (should (eq (clojure-test-face-at 1 8 "\\newline") 'clojure-character-face)) (should (eq (clojure-test-face-at 1 2 "\\1") 'clojure-character-face)) - (should (eq (clojure-test-face-at 1 6 "\\u0032") 'clojure-character-face))) + (should (eq (clojure-test-face-at 1 6 "\\u0032") 'clojure-character-face)) + (should (eq (clojure-test-face-at 1 2 "\\+") 'clojure-character-face)) + (should (eq (clojure-test-face-at 1 2 "\\.") 'clojure-character-face))) (ert-deftest clojure-mode-syntax-table/cljx () :tags '(fontification syntax-table) From 90d3a43eb7dc9513d4c39912a438e7a4ac088ae5 Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Tue, 2 Sep 2014 19:40:08 +0300 Subject: [PATCH 024/379] Fix font-locking of single-segment names in ns macro --- clojure-mode-test.el | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/clojure-mode-test.el b/clojure-mode-test.el index 572c948..d236d6d 100644 --- a/clojure-mode-test.el +++ b/clojure-mode-test.el @@ -214,6 +214,11 @@ POS." (should (eq (clojure-test-face-at 1 10 "*some-var*") 'font-lock-variable-name-face)) (should (eq (clojure-test-face-at 2 11 "@*some-var*") 'font-lock-variable-name-face))) +(ert-deftest clojure-mode-syntax-table/ns-macro () + :tags '(fontification syntax-table) + (should (eq (clojure-test-face-at 5 8 "(ns name)") 'font-lock-type-face)) + (should (eq (clojure-test-face-at 5 13 "(ns name.name)") 'font-lock-type-face))) + (provide 'clojure-mode-test) ;; Local Variables: From e569ab8865085b72dc6de38cb0ea2e38fb142911 Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Fri, 19 Sep 2014 15:45:10 +0300 Subject: [PATCH 025/379] Fix a ns font-locking bug Everything appearing after the word ns was treated as namespace (.e.g. [ns name]). Now we check if ns is preceded by a (. --- clojure-mode-test.el | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/clojure-mode-test.el b/clojure-mode-test.el index d236d6d..e08604a 100644 --- a/clojure-mode-test.el +++ b/clojure-mode-test.el @@ -217,7 +217,8 @@ POS." (ert-deftest clojure-mode-syntax-table/ns-macro () :tags '(fontification syntax-table) (should (eq (clojure-test-face-at 5 8 "(ns name)") 'font-lock-type-face)) - (should (eq (clojure-test-face-at 5 13 "(ns name.name)") 'font-lock-type-face))) + (should (eq (clojure-test-face-at 5 13 "(ns name.name)") 'font-lock-type-face)) + (should (eq (clojure-test-face-at 1 10 "[ns name]") nil))) (provide 'clojure-mode-test) From ef7cbf3b5dfe757bc6256992d29f29e0e8a9537a Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Sun, 21 Sep 2014 18:45:57 +0300 Subject: [PATCH 026/379] Font-lock properly fully-qualified static method invocations E.g. clojure.lang.Var/cloneThreadBindingFrame --- clojure-mode-test.el | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/clojure-mode-test.el b/clojure-mode-test.el index e08604a..22d9420 100644 --- a/clojure-mode-test.el +++ b/clojure-mode-test.el @@ -117,7 +117,11 @@ POS." (clojure-test-with-temp-buffer "Class/methodName" (should (eq (clojure-test-face-at 6 6) nil)) (should (eq (clojure-test-face-at 1 5) 'font-lock-type-face)) - (should (eq (clojure-test-face-at 7 16) 'clojure-interop-method-face)))) + (should (eq (clojure-test-face-at 7 16) 'clojure-interop-method-face))) + (clojure-test-with-temp-buffer "clojure.lang.Var/someMethod" + (should (eq (clojure-test-face-at 17 17) nil)) + (should (eq (clojure-test-face-at 1 16) 'font-lock-type-face)) + (should (eq (clojure-test-face-at 18 27) 'clojure-interop-method-face)))) (ert-deftest clojure-mode-syntax-table/interop-method () :tags '(fontification syntax-table) From 0267302ece24d803e54534440ea9c0ed5fa34991 Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Sun, 21 Sep 2014 18:47:47 +0300 Subject: [PATCH 027/379] Code style --- clojure-mode-test.el | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/clojure-mode-test.el b/clojure-mode-test.el index 22d9420..94842be 100644 --- a/clojure-mode-test.el +++ b/clojure-mode-test.el @@ -115,12 +115,12 @@ POS." (ert-deftest clojure-mode-syntax-table/static-method () :tags '(fontification syntax-table) (clojure-test-with-temp-buffer "Class/methodName" - (should (eq (clojure-test-face-at 6 6) nil)) (should (eq (clojure-test-face-at 1 5) 'font-lock-type-face)) + (should (eq (clojure-test-face-at 6 6) nil)) (should (eq (clojure-test-face-at 7 16) 'clojure-interop-method-face))) (clojure-test-with-temp-buffer "clojure.lang.Var/someMethod" - (should (eq (clojure-test-face-at 17 17) nil)) (should (eq (clojure-test-face-at 1 16) 'font-lock-type-face)) + (should (eq (clojure-test-face-at 17 17) nil)) (should (eq (clojure-test-face-at 18 27) 'clojure-interop-method-face)))) (ert-deftest clojure-mode-syntax-table/interop-method () From e03cb1f17ac8d46ab277b5d5bc8f209c4ebfa5f5 Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Sat, 1 Nov 2014 09:01:52 +0200 Subject: [PATCH 028/379] Fix the font-locking of classes in scenarios like URLDecoder/decode --- clojure-mode-test.el | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/clojure-mode-test.el b/clojure-mode-test.el index 94842be..41d3298 100644 --- a/clojure-mode-test.el +++ b/clojure-mode-test.el @@ -118,6 +118,10 @@ POS." (should (eq (clojure-test-face-at 1 5) 'font-lock-type-face)) (should (eq (clojure-test-face-at 6 6) nil)) (should (eq (clojure-test-face-at 7 16) 'clojure-interop-method-face))) + (clojure-test-with-temp-buffer "SomeClass/methodName" + (should (eq (clojure-test-face-at 1 9) 'font-lock-type-face)) + (should (eq (clojure-test-face-at 10 10) nil)) + (should (eq (clojure-test-face-at 11 20) 'clojure-interop-method-face))) (clojure-test-with-temp-buffer "clojure.lang.Var/someMethod" (should (eq (clojure-test-face-at 1 16) 'font-lock-type-face)) (should (eq (clojure-test-face-at 17 17) nil)) From a4dc62c36f4a46552377594f8044766ae1576fc5 Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Sat, 10 Jan 2015 12:10:15 +0200 Subject: [PATCH 029/379] Update the copyright years --- clojure-mode-test.el | 2 +- test-helper.el | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/clojure-mode-test.el b/clojure-mode-test.el index 41d3298..4cab810 100644 --- a/clojure-mode-test.el +++ b/clojure-mode-test.el @@ -1,6 +1,6 @@ ;;; clojure-mode-test.el --- Clojure Mode: Unit test suite -*- lexical-binding: t; -*- -;; Copyright (C) 2014 Bozhidar Batsov +;; Copyright (C) 2014-2015 Bozhidar Batsov ;; This file is not part of GNU Emacs. diff --git a/test-helper.el b/test-helper.el index 558d4ed..ee93188 100644 --- a/test-helper.el +++ b/test-helper.el @@ -1,6 +1,6 @@ ;;; test-helper.el --- Clojure Mode: Non-interactive unit-test setup -*- lexical-binding: t; -*- -;; Copyright (C) 2014 Bozhidar Batsov +;; Copyright (C) 2014-2015 Bozhidar Batsov ;; This file is not part of GNU Emacs. From 1b7a61db89617924002f705f0abaab38b3f18dab Mon Sep 17 00:00:00 2001 From: m00nlight Date: Thu, 5 Mar 2015 20:47:32 +0800 Subject: [PATCH 030/379] [Fix #274] Correct font-locking of punctuation character literals --- clojure-mode-test.el | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/clojure-mode-test.el b/clojure-mode-test.el index 41d3298..d7d8747 100644 --- a/clojure-mode-test.el +++ b/clojure-mode-test.el @@ -205,7 +205,9 @@ POS." (should (eq (clojure-test-face-at 1 2 "\\1") 'clojure-character-face)) (should (eq (clojure-test-face-at 1 6 "\\u0032") 'clojure-character-face)) (should (eq (clojure-test-face-at 1 2 "\\+") 'clojure-character-face)) - (should (eq (clojure-test-face-at 1 2 "\\.") 'clojure-character-face))) + (should (eq (clojure-test-face-at 1 2 "\\.") 'clojure-character-face)) + (should (eq (clojure-test-face-at 1 2 "\\,") 'clojure-character-face)) + (should (eq (clojure-test-face-at 1 2 "\\;") 'clojure-character-face))) (ert-deftest clojure-mode-syntax-table/cljx () :tags '(fontification syntax-table) From c1c4628eae4fdec73bd898dacc67789d13608c81 Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Sun, 15 Mar 2015 11:56:38 +0200 Subject: [PATCH 031/379] Fix font-locking of namespace-prefixed dynamic vars --- clojure-mode-test.el | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/clojure-mode-test.el b/clojure-mode-test.el index f5b7f62..5fe79bd 100644 --- a/clojure-mode-test.el +++ b/clojure-mode-test.el @@ -222,7 +222,8 @@ POS." (ert-deftest clojure-mode-syntax-table/dynamic-var () :tags '(fontification syntax-table) (should (eq (clojure-test-face-at 1 10 "*some-var*") 'font-lock-variable-name-face)) - (should (eq (clojure-test-face-at 2 11 "@*some-var*") 'font-lock-variable-name-face))) + (should (eq (clojure-test-face-at 2 11 "@*some-var*") 'font-lock-variable-name-face)) + (should (eq (clojure-test-face-at 9 13 "some.ns/*var*") 'font-lock-variable-name-face))) (ert-deftest clojure-mode-syntax-table/ns-macro () :tags '(fontification syntax-table) From 73f263549586d61bb12443556bec381cc95f2b0f Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Sun, 17 May 2015 11:32:42 +0300 Subject: [PATCH 032/379] [Fix #271] Add indentation tests --- clojure-mode-indentation-test.el | 116 +++++++++++++++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 clojure-mode-indentation-test.el diff --git a/clojure-mode-indentation-test.el b/clojure-mode-indentation-test.el new file mode 100644 index 0000000..32084cd --- /dev/null +++ b/clojure-mode-indentation-test.el @@ -0,0 +1,116 @@ +;;; clojure-mode-indentation-test.el --- Clojure Mode: indentation tests -*- lexical-binding: t; -*- + +;; Copyright (C) 2015 Bozhidar Batsov + +;; This file is not part of GNU Emacs. + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;;; Commentary: + +;; The unit test suite of Clojure Mode + +;;; Code: + +(require 'clojure-mode) +(require 'cl-lib) +(require 'ert) +(require 's) + +(defmacro check-indentation (description before after &optional var-bindings) + "Declare an ert test for indentation behaviour. +The test will check that the swift indentation command changes the buffer +from one state to another. It will also test that point is moved to an +expected position. + +DESCRIPTION is a symbol describing the test. + +BEFORE is the buffer string before indenting, where a pipe (|) represents +point. + +AFTER is the expected buffer string after indenting, where a pipe (|) +represents the expected position of point. + +VAR-BINDINGS is an optional let-bindings list. It can be used to set the +values of customisable variables." + (declare (indent 1)) + (let ((fname (intern (format "indentation/%s" description)))) + `(ert-deftest ,fname () + (let* ((after ,after) + (expected-cursor-pos (1+ (s-index-of "|" after))) + (expected-state (delete ?| after)) + ,@var-bindings) + (with-temp-buffer + (insert ,before) + (goto-char (point-min)) + (search-forward "|") + (delete-char -1) + (clojure-mode) + (indent-according-to-mode) + + (should (equal expected-state (buffer-string))) + (should (equal expected-cursor-pos (point)))))))) + +;; Provide font locking for easier test editing. + +(font-lock-add-keywords + 'emacs-lisp-mode + `((,(rx "(" (group "check-indentation") eow) + (1 font-lock-keyword-face)) + (,(rx "(" + (group "check-indentation") (+ space) + (group bow (+ (not space)) eow) + ) + (1 font-lock-keyword-face) + (2 font-lock-function-name-face)))) + + +;;; Tests + + +(check-indentation no-indentation-at-top-level + "|x" + "|x") + +(check-indentation cond-indentation + " +(cond +|x)" + " +(cond + |x)") + +(check-indentation threading-with-expression-on-first-line + " +(->> expr + |ala)" + " +(->> expr + |ala)") + +(check-indentation threading-with-expression-on-second-line + " +(->> +|expr)" + " +(->> + |expr)") + +(provide 'clojure-mode-indentation-test) + +;; Local Variables: +;; indent-tabs-mode: nil +;; End: + +;;; clojure-mode-indentation-test.el ends here From ac2ae956a69e19df2eab171fb959bba04bd7bf32 Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Sun, 17 May 2015 12:26:05 +0300 Subject: [PATCH 033/379] Rename clojure-mode-test.el to clojure-mode-font-lock-test.el --- clojure-mode-test.el => clojure-mode-font-lock-test.el | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) rename clojure-mode-test.el => clojure-mode-font-lock-test.el (98%) diff --git a/clojure-mode-test.el b/clojure-mode-font-lock-test.el similarity index 98% rename from clojure-mode-test.el rename to clojure-mode-font-lock-test.el index 5fe79bd..4c08fd8 100644 --- a/clojure-mode-test.el +++ b/clojure-mode-font-lock-test.el @@ -1,4 +1,4 @@ -;;; clojure-mode-test.el --- Clojure Mode: Unit test suite -*- lexical-binding: t; -*- +;;; clojure-mode-font-lock-test.el --- Clojure Mode: Font lock test suite -*- lexical-binding: t; -*- ;; Copyright (C) 2014-2015 Bozhidar Batsov @@ -231,10 +231,10 @@ POS." (should (eq (clojure-test-face-at 5 13 "(ns name.name)") 'font-lock-type-face)) (should (eq (clojure-test-face-at 1 10 "[ns name]") nil))) -(provide 'clojure-mode-test) +(provide 'clojure-mode-font-lock-test) ;; Local Variables: ;; indent-tabs-mode: nil ;; End: -;;; clojure-mode-test.el ends here +;;; clojure-mode-font-lock-test.el ends here From e98c459e563dee1fbfa8e4d30a0f542e20ea5d53 Mon Sep 17 00:00:00 2001 From: Achint Sandhu Date: Mon, 27 Apr 2015 09:48:05 -0400 Subject: [PATCH 034/379] Fix doctring indentation issue in #241 Added code to only apply the prefix if the current indent level is <= the default prefix spacing. --- clojure-mode-indentation-test.el | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/clojure-mode-indentation-test.el b/clojure-mode-indentation-test.el index 32084cd..81a9376 100644 --- a/clojure-mode-indentation-test.el +++ b/clojure-mode-indentation-test.el @@ -107,6 +107,32 @@ values of customisable variables." (->> |expr)") +(check-indentation doc-strings-without-indent-specified + " +(defn some-fn +|\"some doc string\"" + " +(defn some-fn + |\"some doc string\"") + +(check-indentation doc-strings-with-correct-indent-specified + " +(defn some-fn + |\"some doc string\"" + " +(defn some-fn + |\"some doc string\"") + +(check-indentation doc-strings-with-additional-indent-specified + " +(defn some-fn + |\"some doc string + - some note\"" + " +(defn some-fn + |\"some doc string + - some note\"") + (provide 'clojure-mode-indentation-test) ;; Local Variables: From 9e13a002ad0490d2edaf80c5f9539642c454648f Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Sat, 20 Jun 2015 09:30:33 +0300 Subject: [PATCH 035/379] Add tests and documentation for the indentation config changes --- clojure-mode-indentation-test.el | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/clojure-mode-indentation-test.el b/clojure-mode-indentation-test.el index 81a9376..5c547cf 100644 --- a/clojure-mode-indentation-test.el +++ b/clojure-mode-indentation-test.el @@ -133,6 +133,27 @@ values of customisable variables." |\"some doc string - some note\"") +;; we can specify different indentation for symbol with some ns prefix +(put-clojure-indent 'bala 0) +(put-clojure-indent 'ala/bala 1) + +(check-indentation symbol-without-ns + " +(bala +|one)" + " +(bala + |one)") + +(check-indentation symbol-with-ns + " +(ala/bala top +|one)" + " +(ala/bala top + |one)") + + (provide 'clojure-mode-indentation-test) ;; Local Variables: From 009c639c17087ba3e28b6923fcaf13743f6ef765 Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Tue, 30 Jun 2015 20:51:10 +0100 Subject: [PATCH 036/379] Implement commands for navigating over logical sexps --- clojure-mode-sexp-test.el | 45 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 clojure-mode-sexp-test.el diff --git a/clojure-mode-sexp-test.el b/clojure-mode-sexp-test.el new file mode 100644 index 0000000..3cfcc70 --- /dev/null +++ b/clojure-mode-sexp-test.el @@ -0,0 +1,45 @@ +;;; clojure-mode-sexp-test.el --- Clojure Mode: sexp tests -*- lexical-binding: t; -*- + +;; Copyright (C) 2015 Artur Malabarba + +;; This file is not part of GNU Emacs. + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;;; Code: + +(require 'clojure-mode) +(require 'ert) + +(ert-deftest test-sexp () + (with-temp-buffer + (insert "^String #macro ^dynamic reverse") + (clojure-mode) + (clojure-backward-logical-sexp 1) + (should (looking-at-p "\\^String \\#macro \\^dynamic reverse")) + (clojure-forward-logical-sexp 1) + (should (looking-back "\\^String \\#macro \\^dynamic reverse")) + (insert " ^String biverse inverse") + (clojure-backward-logical-sexp 1) + (should (looking-at-p "inverse")) + (clojure-backward-logical-sexp 2) + (should (looking-at-p "\\^String \\#macro \\^dynamic reverse")) + (clojure-forward-logical-sexp 2) + (should (looking-back "\\^String biverse")) + (clojure-backward-logical-sexp 1) + (should (looking-at-p "\\^String biverse")))) + +(provide 'clojure-mode-sexp-test) + +;;; clojure-mode-sexp-test.el ends here From a9fc8cb5bfe80d060cc46d0d97ef794bf3045f9f Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Wed, 22 Jul 2015 19:40:59 +0300 Subject: [PATCH 037/379] Fix font-locking tests --- clojure-mode-font-lock-test.el | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/clojure-mode-font-lock-test.el b/clojure-mode-font-lock-test.el index 4c08fd8..6bbd2ed 100644 --- a/clojure-mode-font-lock-test.el +++ b/clojure-mode-font-lock-test.el @@ -41,6 +41,17 @@ (goto-char (point-min)) ,@body)) +(defmacro clojurex-test-with-temp-buffer (content &rest body) + "Evaluate BODY in a temporary buffer with CONTENTS." + (declare (debug t) + (indent 1)) + `(with-temp-buffer + (insert ,content) + (clojurex-mode) + (font-lock-fontify-buffer) + (goto-char (point-min)) + ,@body)) + (defun clojure-get-face-at-range (start end) (let ((start-face (get-text-property start 'face)) (all-faces (cl-loop for i from start to end collect (get-text-property i 'face)))) @@ -58,6 +69,16 @@ buffer." (clojure-get-face-at-range start end)) (clojure-get-face-at-range start end))) +(defun clojurex-test-face-at (start end &optional content) + "Get the face between START and END in CONTENT. + +If CONTENT is not given, return the face at the specified range in the current +buffer." + (if content + (clojurex-test-with-temp-buffer content + (clojure-get-face-at-range start end)) + (clojure-get-face-at-range start end))) + (defconst clojure-test-syntax-classes [whitespace punctuation word symbol open-paren close-paren expression-prefix string-quote paired-delim escape character-quote comment-start @@ -209,10 +230,10 @@ POS." (should (eq (clojure-test-face-at 1 2 "\\,") 'clojure-character-face)) (should (eq (clojure-test-face-at 1 2 "\\;") 'clojure-character-face))) -(ert-deftest clojure-mode-syntax-table/cljx () +(ert-deftest clojurex-mode-syntax-table/cljx () :tags '(fontification syntax-table) - (should (eq (clojure-test-face-at 1 5 "#+clj x") 'font-lock-preprocessor-face)) - (should (eq (clojure-test-face-at 1 6 "#+cljs x") 'font-lock-preprocessor-face))) + (should (eq (clojurex-test-face-at 1 5 "#+clj x") 'font-lock-preprocessor-face)) + (should (eq (clojurex-test-face-at 1 6 "#+cljs x") 'font-lock-preprocessor-face))) (ert-deftest clojure-mode-syntax-table/refer-ns () :tags '(fontification syntax-table) From ccf307e760f5fc17d11e30c72f0b63c6311f99bf Mon Sep 17 00:00:00 2001 From: Lars Andersen Date: Wed, 29 Jul 2015 15:00:22 +0200 Subject: [PATCH 038/379] [Fix #310,#311] clojure-expected-ns with src/cljc When the source path is src/clj{,c,s,x} instead of just src/ clojure-expected ns would create namespaces like clj.my-project.my-ns whereas what's wanted is my-project.my-ns. Reading boot.clj or project.clj to find out the user's src dirs is out of scope for clojure-mode, so we use the simply heuristic that no namespace should start with clj, cljc or cljs because these are the idiomatic source directories in multi-source projects. When improving clojure-expected-ns I extracted out two utilities, clojure-project-dir and clojure-project-relative-path. These utilities already exist in clj-refactor so I opted to make them public rather than private, as they are generally useful. --- clojure-mode-util-test.el | 53 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 clojure-mode-util-test.el diff --git a/clojure-mode-util-test.el b/clojure-mode-util-test.el new file mode 100644 index 0000000..3736757 --- /dev/null +++ b/clojure-mode-util-test.el @@ -0,0 +1,53 @@ +;;; clojure-mode-util-test.el --- Clojure Mode: util test suite -*- lexical-binding: t; -*- + +;; Copyright (C) 2014-2015 Bozhidar Batsov + +;; This file is not part of GNU Emacs. + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;;; Commentary: + +;; The unit test suite of Clojure Mode + +;;; Code: +(require 'clojure-mode) +(require 'cl-lib) +(require 'ert) + +(let ((project-dir "/home/user/projects/my-project/") + (clj-file-path "/home/user/projects/my-project/src/clj/my_project/my_ns/my_file.clj") + (project-relative-clj-file-path "src/clj/my_project/my_ns/my_file.clj") + (clj-file-ns "my-project.my-ns.my-file")) + + (ert-deftest project-relative-path () + :tags '(utils) + (cl-letf (((symbol-function 'clojure-project-dir) (lambda () project-dir))) + (should (string= (clojure-project-relative-path clj-file-path) + project-relative-clj-file-path)))) + + (ert-deftest expected-ns () + :tags '(utils) + (cl-letf (((symbol-function 'clojure-project-relative-path) + (lambda (&optional current-buffer-file-name) + project-relative-clj-file-path))) + (should (string= (clojure-expected-ns clj-file-path) clj-file-ns))))) + +(provide 'clojure-mode-util-test) + +;; Local Variables: +;; indent-tabs-mode: nil +;; End: + +;;; clojure-mode-util-test.el ends here From c855d1eefd8e54ba25f3f52eeb9eab80b968196c Mon Sep 17 00:00:00 2001 From: Jinseop Kim Date: Thu, 30 Jul 2015 18:31:49 +0900 Subject: [PATCH 039/379] Fix for error in `clojure-expected-ns` When call `clojure-expected-ns` function without arguments, error occured. --- clojure-mode-util-test.el | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/clojure-mode-util-test.el b/clojure-mode-util-test.el index 3736757..5fd689b 100644 --- a/clojure-mode-util-test.el +++ b/clojure-mode-util-test.el @@ -42,7 +42,16 @@ (cl-letf (((symbol-function 'clojure-project-relative-path) (lambda (&optional current-buffer-file-name) project-relative-clj-file-path))) - (should (string= (clojure-expected-ns clj-file-path) clj-file-ns))))) + (should (string= (clojure-expected-ns clj-file-path) clj-file-ns)))) + + (ert-deftest expected-ns-without-argument () + :tags '(utils) + (cl-letf (((symbol-function 'clojure-project-relative-path) + (lambda (&optional current-buffer-file-name) + project-relative-clj-file-path))) + (should (string= (let ((buffer-file-name clj-file-path)) + (clojure-expected-ns)) + clj-file-ns))))) (provide 'clojure-mode-util-test) From eecf34ef10ef07a9f4d07f7ea4695aafd2bf5ee5 Mon Sep 17 00:00:00 2001 From: Bruno Bonacci Date: Mon, 3 Aug 2015 15:33:45 +0100 Subject: [PATCH 040/379] Handle properly def* symbols, containing special chars --- clojure-mode-font-lock-test.el | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/clojure-mode-font-lock-test.el b/clojure-mode-font-lock-test.el index 4c08fd8..ab42a53 100644 --- a/clojure-mode-font-lock-test.el +++ b/clojure-mode-font-lock-test.el @@ -172,6 +172,18 @@ POS." (should (eq (clojure-test-face-at 2 5) 'font-lock-keyword-face)) (should (eq (clojure-test-face-at 7 9) 'font-lock-function-name-face)))) +(ert-deftest clojure-mode-syntax-table/custom-def-with-special-chars1 () + :tags '(fontification syntax-table) + (clojure-test-with-temp-buffer "(defn* foo [x] x)" + (should (eq (clojure-test-face-at 2 6) 'font-lock-keyword-face)) + (should (eq (clojure-test-face-at 8 10) 'font-lock-function-name-face)))) + +(ert-deftest clojure-mode-syntax-table/custom-def-with-special-chars2 () + :tags '(fontification syntax-table) + (clojure-test-with-temp-buffer "(defsomething! foo [x] x)" + (should (eq (clojure-test-face-at 2 14) 'font-lock-keyword-face)) + (should (eq (clojure-test-face-at 16 18) 'font-lock-function-name-face)))) + (ert-deftest clojure-mode-syntax-table/lambda-params () :tags '(fontification syntax-table) (clojure-test-with-temp-buffer "#(+ % %2 %3)" From 34658f044bd878513defeace5e94b110ed0d0309 Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Thu, 10 Sep 2015 18:19:56 +0100 Subject: [PATCH 041/379] [Fix #304] Indentation of forms with metadata specs We were indenting like this (ns ^:doc app.core (:gen-class)) Now we indent like this (ns ^:doc app.core (:gen-class)) --- clojure-mode-indentation-test.el | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/clojure-mode-indentation-test.el b/clojure-mode-indentation-test.el index 5c547cf..60485cd 100644 --- a/clojure-mode-indentation-test.el +++ b/clojure-mode-indentation-test.el @@ -153,6 +153,14 @@ values of customisable variables." (ala/bala top |one)") +(check-indentation form-with-metadata + " +(ns ^:doc app.core +|(:gen-class))" +" +(ns ^:doc app.core + |(:gen-class))") + (provide 'clojure-mode-indentation-test) From db77aa7a3634ec7fa9dd1208d34ed2fdfa2ecf30 Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Thu, 10 Sep 2015 18:22:12 +0100 Subject: [PATCH 042/379] [Fix #308 Fix #287 Fix #45] Corner-case indentation of multi-line sexps We were indenting like this [[ 2] a x] Now we indent like this [[ 2] a x] --- clojure-mode-indentation-test.el | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/clojure-mode-indentation-test.el b/clojure-mode-indentation-test.el index 60485cd..de0176d 100644 --- a/clojure-mode-indentation-test.el +++ b/clojure-mode-indentation-test.el @@ -161,6 +161,16 @@ values of customisable variables." (ns ^:doc app.core |(:gen-class))") +(check-indentation multiline-sexps + " +[[ + 2] a +|x]" +" +[[ + 2] a + |x]") + (provide 'clojure-mode-indentation-test) From 29ce149a55f732dd667ed6c0278e82dc1a10b919 Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Thu, 10 Sep 2015 18:38:45 +0100 Subject: [PATCH 043/379] [Fix #292] Indentation in reader conditionals Before #?(:clj :foo :cljs :bar) Now #?(:clj :foo :cljs :bar) --- clojure-mode-indentation-test.el | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/clojure-mode-indentation-test.el b/clojure-mode-indentation-test.el index de0176d..867126c 100644 --- a/clojure-mode-indentation-test.el +++ b/clojure-mode-indentation-test.el @@ -171,6 +171,14 @@ values of customisable variables." 2] a |x]") +(check-indentation reader-conditionals + " +#?(:clj :foo +|:cljs :bar)" + " +#?(:clj :foo + |:cljs :bar)") + (provide 'clojure-mode-indentation-test) From fe0659d4c9f90815504eb6f103a7a11619ef9f3b Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Thu, 10 Sep 2015 19:13:51 +0100 Subject: [PATCH 044/379] [Fix #278] Understand namespace aliases in backtracking indent --- clojure-mode-indentation-test.el | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/clojure-mode-indentation-test.el b/clojure-mode-indentation-test.el index 867126c..93de734 100644 --- a/clojure-mode-indentation-test.el +++ b/clojure-mode-indentation-test.el @@ -179,6 +179,15 @@ values of customisable variables." #?(:clj :foo |:cljs :bar)") +(check-indentation backtracking-with-aliases + " +(clojure.core/letfn [(twice [x] +|(* x 2))] + :a)" + " +(clojure.core/letfn [(twice [x] + |(* x 2))] + :a)") (provide 'clojure-mode-indentation-test) From b708c7f15fb22b8b4547f8ffa8d2a84faa0422c9 Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Sat, 12 Sep 2015 19:55:09 +0100 Subject: [PATCH 045/379] Rewrite the indent logic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [Fix #282 Fix #296] Use custom-code to calculate normal-indent. Add tests for backtracking indent and several other scenarios. Support a new notation for indent specs. An indent spec can be: - `nil` (or absent), meaning “indent like a regular function”. - A list/vector meaning that this function/macro takes a number of “special” arguments which are indented by more spaces (in CIDER that's 4 spaces), and then all other arguments are indented like a body (in CIDER that's 2 spaces). - The first element is a number indicating how many "special" arguments this function/macro takes. - Each following element is an indent spec on its own, and it applies to the argument on the same position as this element. So, when the argument is a form, it specifies how to indent that argument internally (if it's not a form the spec is irrelevant). - If the function/macro has more aguments than the list has elements, the last element of the list applies to all remaining arguments. So, for instance, if I specify the `deftype` spec as `[2 nil nil fn]` (equivalent to `[2 nil nil [1]]`), it would be indented like this: ``` (deftype ImageSelection [data] Transferable (getTransferDataFlavors [this] (some-function)) SomethingElse (isDataFlavorSupported [this flavor] (= imageFlavor flavor))) ``` (I put `ImageSelection` and `[data]` on their own lines just to show the indentation). Another example, `reify` as `[1 nil fn]` ``` (reify Object (toString [this] (f) asodkaodkap)) ``` Or something more complicated, `letfn` as `[1 [fn] nil]` ``` (letfn [(twice [x] (* x 2)) (six-times [y] (* (twice y) 3))] (println "Twice 15 =" (twice 15)) (println "Six times 15 =" (six-times 15))) ``` --- clojure-mode-indentation-test.el | 85 +++++++++++++++++++++++++++++--- 1 file changed, 79 insertions(+), 6 deletions(-) diff --git a/clojure-mode-indentation-test.el b/clojure-mode-indentation-test.el index 93de734..01f4ebd 100644 --- a/clojure-mode-indentation-test.el +++ b/clojure-mode-indentation-test.el @@ -110,28 +110,28 @@ values of customisable variables." (check-indentation doc-strings-without-indent-specified " (defn some-fn -|\"some doc string\"" +|\"some doc string\")" " (defn some-fn - |\"some doc string\"") + |\"some doc string\")") (check-indentation doc-strings-with-correct-indent-specified " (defn some-fn - |\"some doc string\"" + |\"some doc string\")" " (defn some-fn - |\"some doc string\"") + |\"some doc string\")") (check-indentation doc-strings-with-additional-indent-specified " (defn some-fn |\"some doc string - - some note\"" + - some note\")" " (defn some-fn |\"some doc string - - some note\"") + - some note\")") ;; we can specify different indentation for symbol with some ns prefix (put-clojure-indent 'bala 0) @@ -189,6 +189,79 @@ values of customisable variables." |(* x 2))] :a)") +(check-indentation fixed-normal-indent + "(cond + (or 1 + 2) 3 +|:else 4)" + "(cond + (or 1 + 2) 3 + |:else 4)") + +(check-indentation fixed-normal-indent-2 + "(fact {:spec-type + :charnock-column-id} #{\"charnock\"} +|{:spec-type + :charnock-column-id} #{\"current_charnock\"})" + "(fact {:spec-type + :charnock-column-id} #{\"charnock\"} + |{:spec-type + :charnock-column-id} #{\"current_charnock\"})") + + +;;; Backtracking indent +(defmacro def-full-indent-test (name form) + "Verify that FORM corresponds to a properly indented sexp." + (declare (indent 1)) + `(ert-deftest ,(intern (format "test-backtracking-%s" name)) () + (with-temp-buffer + (clojure-mode) + (insert "\n" ,(replace-regexp-in-string "\n +" "\n " form)) + (indent-region (point-min) (point-max)) + (should (equal (buffer-string) + ,(concat "\n" form)))))) + +(def-full-indent-test closing-paren + "(ns ca + (:gen-class) + )") + +(def-full-indent-test non-symbol-at-start + "{\"1\" 2 + *3 4}") + +(def-full-indent-test non-symbol-at-start-2 + "(\"1\" 2 + *3 4)") + +(def-full-indent-test defrecord + "(defrecord TheNameOfTheRecord + [a pretty long argument list] + SomeType + (assoc [_ x] + (.assoc pretty x 10)))") + +(def-full-indent-test defrecord-2 + "(defrecord TheNameOfTheRecord [a pretty long argument list] + SomeType (assoc [_ x] + (.assoc pretty x 10)))") + +(def-full-indent-test letfn + "(letfn [(f [x] + (* x 2)) + (f [x] + (* x 2))] + (a b + c) (d) + e)") + +(def-full-indent-test reify + "(reify + Object + (x [_] + 1))") + (provide 'clojure-mode-indentation-test) ;; Local Variables: From e5b6783bacc8e96b3993829fdc469d0b2b2609a6 Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Tue, 15 Sep 2015 13:21:28 +0100 Subject: [PATCH 046/379] [Fix clojure-emacs/cider#1323] Sexp navigation near end of buffer --- clojure-mode-sexp-test.el | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/clojure-mode-sexp-test.el b/clojure-mode-sexp-test.el index 3cfcc70..22dcb8d 100644 --- a/clojure-mode-sexp-test.el +++ b/clojure-mode-sexp-test.el @@ -40,6 +40,31 @@ (clojure-backward-logical-sexp 1) (should (looking-at-p "\\^String biverse")))) +(ert-deftest test-buffer-corners () + (with-temp-buffer + (insert "^String reverse") + (clojure-mode) + ;; Return nil and don't error + (should-not (clojure-backward-logical-sexp 100)) + (should (looking-at-p "\\^String reverse")) + (should-not (clojure-forward-logical-sexp 100)) + (should (looking-at-p "$"))) + (with-temp-buffer + (clojure-mode) + (insert "(+ 10") + (should-error (clojure-backward-logical-sexp 100)) + (goto-char (point-min)) + (should-error (clojure-forward-logical-sexp 100)) + ;; Just don't hang. + (goto-char (point-max)) + (should-not (clojure-forward-logical-sexp 1)) + (erase-buffer) + (insert "(+ 10") + (newline) + (erase-buffer) + (insert "(+ 10") + (newline-and-indent))) + (provide 'clojure-mode-sexp-test) ;;; clojure-mode-sexp-test.el ends here From bf62b1bde01a34718693104e4d833cc6800626d1 Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Mon, 28 Sep 2015 13:39:43 +0100 Subject: [PATCH 047/379] [Fix #325] Fix indent for spliced reader conditionals --- clojure-mode-indentation-test.el | 39 ++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/clojure-mode-indentation-test.el b/clojure-mode-indentation-test.el index 01f4ebd..ba11482 100644 --- a/clojure-mode-indentation-test.el +++ b/clojure-mode-indentation-test.el @@ -262,6 +262,45 @@ values of customisable variables." (x [_] 1))") +(def-full-indent-test reader-conditionals + "#?@ (:clj [] + :cljs [])") + + +;;; Misc + +(defun non-func (form-a form-b) + (with-temp-buffer + (clojure-mode) + (insert form-a) + (save-excursion (insert form-b)) + (clojure--not-function-form-p))) + +(ert-deftest non-function-form () + (dolist (form '(("#?@ " "(c d)") + ("#?@" "(c d)") + ("#? " "(c d)") + ("#?" "(c d)") + ("" "[asda]") + ("" "{a b}") + ("#" "{a b}") + ("" "(~)"))) + (should (apply #'non-func form))) + (dolist (form '("(c d)" + "(.c d)" + "(:c d)" + "(c/a d)" + "(.c/a d)" + "(:c/a d)" + "(c/a)" + "(:c/a)" + "(.c/a)")) + (should-not (non-func "" form)) + (should-not (non-func "^hint" form)) + (should-not (non-func "#macro" form)) + (should-not (non-func "^hint " form)) + (should-not (non-func "#macro " form)))) + (provide 'clojure-mode-indentation-test) ;; Local Variables: From 1018c6e7690884069e3b49e22ca5fa7d587bb438 Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Tue, 29 Sep 2015 20:55:22 +0100 Subject: [PATCH 048/379] Font-lock namespaced keywords --- clojure-mode-font-lock-test.el | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/clojure-mode-font-lock-test.el b/clojure-mode-font-lock-test.el index 7763bdf..db3e1f4 100644 --- a/clojure-mode-font-lock-test.el +++ b/clojure-mode-font-lock-test.el @@ -101,7 +101,16 @@ POS." (ert-deftest clojure-mode-syntax-table/fontify-clojure-keyword () :tags '(fontification syntax-table) - (should (eq (clojure-test-face-at 2 11 "{:something 20}") 'clojure-keyword-face))) + (should (equal (clojure-test-face-at 2 11 "{:something 20}") '(clojure-keyword-face)))) + +(ert-deftest clojure-mode-syntax-table/fontify-namespaced-keyword () + :tags '(fontification syntax-table) + (should (equal (clojure-test-face-at 2 2 "{:alias/some 20}") '(clojure-keyword-face))) + (should (equal (clojure-test-face-at 3 7 "{:alias/some 20}") '(font-lock-type-face clojure-keyword-face))) + (should (equal (clojure-test-face-at 8 12 "{:alias/some 20}") '(clojure-keyword-face))) + (should (equal (clojure-test-face-at 2 2 "{:a.ias/some 20}") '(clojure-keyword-face))) + (should (equal (clojure-test-face-at 3 7 "{:a.ias/some 20}") '(font-lock-type-face clojure-keyword-face))) + (should (equal (clojure-test-face-at 8 12 "{:a.ias/some 20}") '(clojure-keyword-face)))) (ert-deftest clojure-mode-syntax-table/type () :tags '(fontification syntax-table) @@ -229,7 +238,7 @@ POS." :tags '(fontification syntax-table) (clojure-test-with-temp-buffer "^:meta-data" (should (eq (clojure-test-face-at 1 1) nil)) - (should (eq (clojure-test-face-at 2 11) 'clojure-keyword-face)))) + (should (equal (clojure-test-face-at 2 11) '(clojure-keyword-face))))) (ert-deftest clojure-mode-syntax-table/characters () :tags '(fontification syntax-table) From a7609cffa119613c4361736018f055780ef52082 Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Thu, 1 Oct 2015 23:02:51 +0100 Subject: [PATCH 049/379] [Fix #327] Indentation of a lonely close paren Indent like this (if (pred?) ) not like this (if (pred?) ) --- clojure-mode-indentation-test.el | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/clojure-mode-indentation-test.el b/clojure-mode-indentation-test.el index ba11482..0253540 100644 --- a/clojure-mode-indentation-test.el +++ b/clojure-mode-indentation-test.el @@ -211,16 +211,18 @@ values of customisable variables." ;;; Backtracking indent -(defmacro def-full-indent-test (name form) - "Verify that FORM corresponds to a properly indented sexp." +(defmacro def-full-indent-test (name &rest forms) + "Verify that all FORMs correspond to a properly indented sexps." (declare (indent 1)) `(ert-deftest ,(intern (format "test-backtracking-%s" name)) () - (with-temp-buffer - (clojure-mode) - (insert "\n" ,(replace-regexp-in-string "\n +" "\n " form)) - (indent-region (point-min) (point-max)) - (should (equal (buffer-string) - ,(concat "\n" form)))))) + (progn + ,@(dolist (form forms) + `(with-temp-buffer + (clojure-mode) + (insert "\n" ,(replace-regexp-in-string "\n +" "\n " form)) + (indent-region (point-min) (point-max)) + (should (equal (buffer-string) + ,(concat "\n" form)))))))) (def-full-indent-test closing-paren "(ns ca @@ -266,6 +268,17 @@ values of customisable variables." "#?@ (:clj [] :cljs [])") +(def-full-indent-test empty-close-paren + "(let [x] + )" + + "(ns ok + )" + + "(ns ^{:zen :dikar} + ok + )") + ;;; Misc From c1e84d0949645c722c944b3507061dae758b54a1 Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Thu, 1 Oct 2015 23:47:46 +0100 Subject: [PATCH 050/379] [Fix #327] Indentation when a symbol ends in ? or ' --- clojure-mode-indentation-test.el | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/clojure-mode-indentation-test.el b/clojure-mode-indentation-test.el index 0253540..aa5d6b4 100644 --- a/clojure-mode-indentation-test.el +++ b/clojure-mode-indentation-test.el @@ -279,6 +279,15 @@ values of customisable variables." ok )") +(def-full-indent-test symbols-ending-in-crap + "(msg? ExceptionInfo + 10)" + "(thrown-with-msg? ExceptionInfo + #\"Storage must be initialized before use\" + (f))" + "(msg' 1 + 10)") + ;;; Misc From 552edf753047e606a2b80d51175d95242cebcf8f Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Tue, 13 Oct 2015 16:45:21 +0100 Subject: [PATCH 051/379] [Fix #331] Indentation in buffers without a final newline --- clojure-mode-indentation-test.el | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/clojure-mode-indentation-test.el b/clojure-mode-indentation-test.el index aa5d6b4..fe61d26 100644 --- a/clojure-mode-indentation-test.el +++ b/clojure-mode-indentation-test.el @@ -28,6 +28,16 @@ (require 'ert) (require 's) +(ert-deftest dont-hang-on-eob () + (with-temp-buffer + (insert "(let [a b]") + (clojure-mode) + (goto-char (point-max)) + (should + (with-timeout (2) + (newline-and-indent) + t)))) + (defmacro check-indentation (description before after &optional var-bindings) "Declare an ert test for indentation behaviour. The test will check that the swift indentation command changes the buffer From c8b0551399729f3c8a053f75bc66011ffb1efc75 Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Mon, 19 Oct 2015 14:29:13 +0100 Subject: [PATCH 052/379] Add regression tests for function specs --- clojure-mode-indentation-test.el | 38 ++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/clojure-mode-indentation-test.el b/clojure-mode-indentation-test.el index fe61d26..3089483 100644 --- a/clojure-mode-indentation-test.el +++ b/clojure-mode-indentation-test.el @@ -298,6 +298,44 @@ values of customisable variables." "(msg' 1 10)") + +(defun indent-cond (indent-point state) + (goto-char (elt state 1)) + (let ((pos -1) + (base-col (current-column))) + (forward-char 1) + ;; `forward-sexp' will error if indent-point is after + ;; the last sexp in the current sexp. + (condition-case nil + (while (and (<= (point) indent-point) + (not (eobp))) + (clojure-forward-logical-sexp 1) + (cl-incf pos)) + ;; If indent-point is _after_ the last sexp in the + ;; current sexp, we detect that by catching the + ;; `scan-error'. In that case, we should return the + ;; indentation as if there were an extra sexp at point. + (scan-error (cl-incf pos))) + (+ base-col (if (evenp pos) 0 2)))) +(put-clojure-indent 'test-cond #'indent-cond) + +(defun indent-cond-0 (_indent-point _state) 0) +(put-clojure-indent 'test-cond-0 #'indent-cond-0) + +(def-full-indent-test function-spec + "(when me + (test-cond + x + 1 + 2 + 3))" + "(when me + (test-cond-0 +x +1 +2 +3))") + ;;; Misc From 9a1415cd7617e5a68ac3e193314be6c2cbb8b9fe Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Sun, 25 Oct 2015 21:35:41 +0000 Subject: [PATCH 053/379] [Fix #336] Fix empty indent tests --- clojure-mode-indentation-test.el | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/clojure-mode-indentation-test.el b/clojure-mode-indentation-test.el index 3089483..7e9c4e3 100644 --- a/clojure-mode-indentation-test.el +++ b/clojure-mode-indentation-test.el @@ -226,13 +226,14 @@ values of customisable variables." (declare (indent 1)) `(ert-deftest ,(intern (format "test-backtracking-%s" name)) () (progn - ,@(dolist (form forms) - `(with-temp-buffer - (clojure-mode) - (insert "\n" ,(replace-regexp-in-string "\n +" "\n " form)) - (indent-region (point-min) (point-max)) - (should (equal (buffer-string) - ,(concat "\n" form)))))))) + ,@(mapcar (lambda (form) + `(with-temp-buffer + (clojure-mode) + (insert "\n" ,(replace-regexp-in-string "\n +" "\n " form)) + (indent-region (point-min) (point-max)) + (should (equal (buffer-string) + ,(concat "\n" form))))) + forms)))) (def-full-indent-test closing-paren "(ns ca From 279d46432532a3709b44c42832bfb8658a1dc39a Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Sun, 25 Oct 2015 21:42:41 +0000 Subject: [PATCH 054/379] Add a regression test for #335 --- clojure-mode-indentation-test.el | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/clojure-mode-indentation-test.el b/clojure-mode-indentation-test.el index 7e9c4e3..acab19b 100644 --- a/clojure-mode-indentation-test.el +++ b/clojure-mode-indentation-test.el @@ -270,10 +270,18 @@ values of customisable variables." e)") (def-full-indent-test reify - "(reify - Object + "(reify Object (x [_] - 1))") + 1))" + "(reify + om/IRender + (render [this] + (let [indent-test :fail] + ...)) + om/IRender + (render [this] + (let [indent-test :fail] + ...)))") (def-full-indent-test reader-conditionals "#?@ (:clj [] From d81631423d51defdcf64b2f45a3e2211af3cad85 Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Sun, 25 Oct 2015 21:57:13 +0000 Subject: [PATCH 055/379] [Fix #338] Font-locking of stuff in backticks --- clojure-mode-font-lock-test.el | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/clojure-mode-font-lock-test.el b/clojure-mode-font-lock-test.el index db3e1f4..96b18ab 100644 --- a/clojure-mode-font-lock-test.el +++ b/clojure-mode-font-lock-test.el @@ -103,6 +103,16 @@ POS." :tags '(fontification syntax-table) (should (equal (clojure-test-face-at 2 11 "{:something 20}") '(clojure-keyword-face)))) +(ert-deftest clojure-mode-syntax-table/stuff-in-backticks () + :tags '(fontification syntax-table) + (should (equal (clojure-test-face-at 1 2 "\"`#'s/trim`\"") font-lock-string-face)) + (should (equal (clojure-test-face-at 3 10 "\"`#'s/trim`\"") '(font-lock-constant-face font-lock-string-face))) + (should (equal (clojure-test-face-at 11 12 "\"`#'s/trim`\"") font-lock-string-face)) + (should (equal (clojure-test-face-at 1 1 ";`#'s/trim`") font-lock-comment-delimiter-face)) + (should (equal (clojure-test-face-at 2 2 ";`#'s/trim`") font-lock-comment-face)) + (should (equal (clojure-test-face-at 3 10 ";`#'s/trim`") '(font-lock-constant-face font-lock-comment-face))) + (should (equal (clojure-test-face-at 11 11 ";`#'s/trim`") font-lock-comment-face))) + (ert-deftest clojure-mode-syntax-table/fontify-namespaced-keyword () :tags '(fontification syntax-table) (should (equal (clojure-test-face-at 2 2 "{:alias/some 20}") '(clojure-keyword-face))) From 51bf8995a60e25c29d4844890aeb4a1068ca7616 Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Sun, 25 Oct 2015 22:05:25 +0000 Subject: [PATCH 056/379] Fix void-function in tests --- clojure-mode-indentation-test.el | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/clojure-mode-indentation-test.el b/clojure-mode-indentation-test.el index acab19b..b800e65 100644 --- a/clojure-mode-indentation-test.el +++ b/clojure-mode-indentation-test.el @@ -325,7 +325,8 @@ values of customisable variables." ;; `scan-error'. In that case, we should return the ;; indentation as if there were an extra sexp at point. (scan-error (cl-incf pos))) - (+ base-col (if (evenp pos) 0 2)))) + (+ base-col (if (= (% pos 2) 0) + 0 2)))) (put-clojure-indent 'test-cond #'indent-cond) (defun indent-cond-0 (_indent-point _state) 0) From 1b9891178d5676e49d476011070b352ef10c1132 Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Sun, 25 Oct 2015 22:59:07 +0000 Subject: [PATCH 057/379] [Fix #315] Indentation of unfinished sexps A.K.A not everybody uses paredit. --- clojure-mode-indentation-test.el | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/clojure-mode-indentation-test.el b/clojure-mode-indentation-test.el index b800e65..23e153b 100644 --- a/clojure-mode-indentation-test.el +++ b/clojure-mode-indentation-test.el @@ -298,6 +298,10 @@ values of customisable variables." ok )") +(def-full-indent-test unfinished-sexps + "(letfn [(tw [x] + dd") + (def-full-indent-test symbols-ending-in-crap "(msg? ExceptionInfo 10)" From f9e7bdf1091e6fc012d25de912c29ca18731def5 Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Sun, 25 Oct 2015 23:45:29 +0000 Subject: [PATCH 058/379] Fix tests broken by the / font-lock --- clojure-mode-font-lock-test.el | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/clojure-mode-font-lock-test.el b/clojure-mode-font-lock-test.el index 96b18ab..d13d1c9 100644 --- a/clojure-mode-font-lock-test.el +++ b/clojure-mode-font-lock-test.el @@ -117,10 +117,12 @@ POS." :tags '(fontification syntax-table) (should (equal (clojure-test-face-at 2 2 "{:alias/some 20}") '(clojure-keyword-face))) (should (equal (clojure-test-face-at 3 7 "{:alias/some 20}") '(font-lock-type-face clojure-keyword-face))) - (should (equal (clojure-test-face-at 8 12 "{:alias/some 20}") '(clojure-keyword-face))) + (should (equal (clojure-test-face-at 8 8 "{:alias/some 20}") '(default clojure-keyword-face))) + (should (equal (clojure-test-face-at 9 12 "{:alias/some 20}") '(clojure-keyword-face))) (should (equal (clojure-test-face-at 2 2 "{:a.ias/some 20}") '(clojure-keyword-face))) (should (equal (clojure-test-face-at 3 7 "{:a.ias/some 20}") '(font-lock-type-face clojure-keyword-face))) - (should (equal (clojure-test-face-at 8 12 "{:a.ias/some 20}") '(clojure-keyword-face)))) + (should (equal (clojure-test-face-at 8 8 "{:a.ias/some 20}") '(default clojure-keyword-face))) + (should (equal (clojure-test-face-at 9 12 "{:a.ias/some 20}") '(clojure-keyword-face)))) (ert-deftest clojure-mode-syntax-table/type () :tags '(fontification syntax-table) @@ -148,7 +150,7 @@ POS." (ert-deftest clojure-mode-syntax-table/namespaced-symbol () :tags '(fontification syntax-table) (clojure-test-with-temp-buffer "clo.core/something" - (should (eq (clojure-test-face-at 9 9) nil)) + (should (eq (clojure-test-face-at 9 9) 'default)) (should (eq (clojure-test-face-at 1 8) 'font-lock-type-face)) (should (eq (clojure-test-face-at 10 18) nil)))) @@ -156,15 +158,15 @@ POS." :tags '(fontification syntax-table) (clojure-test-with-temp-buffer "Class/methodName" (should (eq (clojure-test-face-at 1 5) 'font-lock-type-face)) - (should (eq (clojure-test-face-at 6 6) nil)) + (should (eq (clojure-test-face-at 6 6) 'default)) (should (eq (clojure-test-face-at 7 16) 'clojure-interop-method-face))) (clojure-test-with-temp-buffer "SomeClass/methodName" (should (eq (clojure-test-face-at 1 9) 'font-lock-type-face)) - (should (eq (clojure-test-face-at 10 10) nil)) + (should (eq (clojure-test-face-at 10 10) 'default)) (should (eq (clojure-test-face-at 11 20) 'clojure-interop-method-face))) (clojure-test-with-temp-buffer "clojure.lang.Var/someMethod" (should (eq (clojure-test-face-at 1 16) 'font-lock-type-face)) - (should (eq (clojure-test-face-at 17 17) nil)) + (should (eq (clojure-test-face-at 17 17) 'default)) (should (eq (clojure-test-face-at 18 27) 'clojure-interop-method-face)))) (ert-deftest clojure-mode-syntax-table/interop-method () @@ -182,7 +184,7 @@ POS." (ert-deftest clojure-mode-syntax-table/class-constant () :tags '(fontification syntax-table) (clojure-test-with-temp-buffer "Class/CONST_NAME" - (should (eq (clojure-test-face-at 6 6) nil)) + (should (eq (clojure-test-face-at 6 6) 'default)) (should (eq (clojure-test-face-at 1 5) 'font-lock-type-face)) (should (eq (clojure-test-face-at 7 16) 'font-lock-constant-face)))) @@ -190,7 +192,7 @@ POS." :tags '(fontification syntax-table) (clojure-test-with-temp-buffer "(clo/defbar foo nil)" (should (eq (clojure-test-face-at 2 4) 'font-lock-type-face)) - (should (eq (clojure-test-face-at 5 5) nil)) + (should (eq (clojure-test-face-at 5 5) 'default)) (should (eq (clojure-test-face-at 6 11) 'font-lock-keyword-face)) (should (eq (clojure-test-face-at 13 15) 'font-lock-function-name-face)))) From 077e3491bcc77ec84f2e57b0dc007909351a8da7 Mon Sep 17 00:00:00 2001 From: Marian Schubert Date: Tue, 13 Oct 2015 16:28:25 +0200 Subject: [PATCH 059/379] Improve clojure-find-def matching (support hyphens) Custom def... macros might contain hyphens (e.g. defxxx-yyy). --- clojure-mode-font-lock-test.el | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/clojure-mode-font-lock-test.el b/clojure-mode-font-lock-test.el index db3e1f4..6d8ad39 100644 --- a/clojure-mode-font-lock-test.el +++ b/clojure-mode-font-lock-test.el @@ -214,6 +214,12 @@ POS." (should (eq (clojure-test-face-at 2 14) 'font-lock-keyword-face)) (should (eq (clojure-test-face-at 16 18) 'font-lock-function-name-face)))) +(ert-deftest clojure-mode-syntax-table/custom-def-with-special-chars3 () + :tags '(fontification syntax-table) + (clojure-test-with-temp-buffer "(def-something foo [x] x)" + (should (eq (clojure-test-face-at 2 14) 'font-lock-keyword-face)) + (should (eq (clojure-test-face-at 16 18) 'font-lock-function-name-face)))) + (ert-deftest clojure-mode-syntax-table/lambda-params () :tags '(fontification syntax-table) (clojure-test-with-temp-buffer "#(+ % %2 %3)" From 358f9b73a86acf650b643594c67c79a35cad30e8 Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Mon, 26 Oct 2015 23:56:05 +0200 Subject: [PATCH 060/379] Use cl-evenp in one of the tests --- clojure-mode-indentation-test.el | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/clojure-mode-indentation-test.el b/clojure-mode-indentation-test.el index 23e153b..620a715 100644 --- a/clojure-mode-indentation-test.el +++ b/clojure-mode-indentation-test.el @@ -329,8 +329,7 @@ values of customisable variables." ;; `scan-error'. In that case, we should return the ;; indentation as if there were an extra sexp at point. (scan-error (cl-incf pos))) - (+ base-col (if (= (% pos 2) 0) - 0 2)))) + (+ base-col (if (cl-evenp pos) 0 2)))) (put-clojure-indent 'test-cond #'indent-cond) (defun indent-cond-0 (_indent-point _state) 0) From a33c3e2dfddbf8d84d4b4a487971f5d0377b650f Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Sun, 8 Nov 2015 16:44:32 +0000 Subject: [PATCH 061/379] Don't treat the symbol default as def* macro. See https://github.com/clojure-emacs/clojure-mode/commit/b31e941366b318706023949a0ca579c08dbe46be#commitcomment-14257272 --- clojure-mode-indentation-test.el | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/clojure-mode-indentation-test.el b/clojure-mode-indentation-test.el index 620a715..db963a4 100644 --- a/clojure-mode-indentation-test.el +++ b/clojure-mode-indentation-test.el @@ -240,6 +240,11 @@ values of customisable variables." (:gen-class) )") +(def-full-indent-test default-is-not-a-define + "(default a + b + b)") + (def-full-indent-test non-symbol-at-start "{\"1\" 2 *3 4}") From bff5ddc1b530b60a58d2c8fa22e646fbfe3cf3da Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Sun, 8 Nov 2015 17:18:40 +0000 Subject: [PATCH 062/379] [Fix #344] Indentation for extend-type --- clojure-mode-indentation-test.el | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/clojure-mode-indentation-test.el b/clojure-mode-indentation-test.el index db963a4..da100fa 100644 --- a/clojure-mode-indentation-test.el +++ b/clojure-mode-indentation-test.el @@ -245,6 +245,22 @@ values of customisable variables." b b)") +(def-full-indent-test extend-type-allow-multiarity + "(extend-type Banana + Fruit + (subtotal + ([item] + (* 158 (:qty item))) + ([item a] + (* a (:qty item)))))" + "(extend-protocol Banana + Fruit + (subtotal + ([item] + (* 158 (:qty item))) + ([item a] + (* a (:qty item)))))") + (def-full-indent-test non-symbol-at-start "{\"1\" 2 *3 4}") From ebf33a3a060bfca7358ed33c196003dd97642243 Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Tue, 10 Nov 2015 11:41:40 +0000 Subject: [PATCH 063/379] Add a test for namespaced default* symbols --- clojure-mode-indentation-test.el | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/clojure-mode-indentation-test.el b/clojure-mode-indentation-test.el index da100fa..470b395 100644 --- a/clojure-mode-indentation-test.el +++ b/clojure-mode-indentation-test.el @@ -243,7 +243,10 @@ values of customisable variables." (def-full-indent-test default-is-not-a-define "(default a b - b)") + b)" + "(some.namespace/default a + b + b)") (def-full-indent-test extend-type-allow-multiarity "(extend-type Banana From b4e8a209f01e939165abb67173839c9ecbc381c2 Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Fri, 27 Nov 2015 13:09:39 +0000 Subject: [PATCH 064/379] [Fix #349] Indent and font-lock (let|when|while)-* forms --- clojure-mode-font-lock-test.el | 6 ++++++ clojure-mode-indentation-test.el | 4 ++++ 2 files changed, 10 insertions(+) diff --git a/clojure-mode-font-lock-test.el b/clojure-mode-font-lock-test.el index fa901e4..04fd265 100644 --- a/clojure-mode-font-lock-test.el +++ b/clojure-mode-font-lock-test.el @@ -124,6 +124,12 @@ POS." (should (equal (clojure-test-face-at 8 8 "{:a.ias/some 20}") '(default clojure-keyword-face))) (should (equal (clojure-test-face-at 9 12 "{:a.ias/some 20}") '(clojure-keyword-face)))) +(ert-deftest clojure-mode-syntax-table/fontify-let-when-while-type-forms () + :tags '(fontification syntax-table) + (should (equal (clojure-test-face-at 2 11 "(when-alist [x 1]\n ())") 'font-lock-keyword-face)) + (should (equal (clojure-test-face-at 2 11 "(while-alist [x 1]\n ())") 'font-lock-keyword-face)) + (should (equal (clojure-test-face-at 2 11 "(let-alist [x 1]\n ())") 'various-faces))) + (ert-deftest clojure-mode-syntax-table/type () :tags '(fontification syntax-table) (should (eq (clojure-test-face-at 1 9 "SomeClass") 'font-lock-type-face))) diff --git a/clojure-mode-indentation-test.el b/clojure-mode-indentation-test.el index 470b395..9a8325b 100644 --- a/clojure-mode-indentation-test.el +++ b/clojure-mode-indentation-test.el @@ -335,6 +335,10 @@ values of customisable variables." "(msg' 1 10)") +(def-full-indent-test let-when-while-forms + "(let-alist [x 1]\n ())" + "(while-alist [x 1]\n ())" + "(when-alist [x 1]\n ())") (defun indent-cond (indent-point state) (goto-char (elt state 1)) From 0534a3eaf4ba8b7a53cc42b0b92b4342264b07bd Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Fri, 27 Nov 2015 13:30:39 +0000 Subject: [PATCH 065/379] Document indentation and font-locking of (let|when|while)-* forms Also improve a test. --- clojure-mode-font-lock-test.el | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/clojure-mode-font-lock-test.el b/clojure-mode-font-lock-test.el index 04fd265..4b64053 100644 --- a/clojure-mode-font-lock-test.el +++ b/clojure-mode-font-lock-test.el @@ -127,8 +127,8 @@ POS." (ert-deftest clojure-mode-syntax-table/fontify-let-when-while-type-forms () :tags '(fontification syntax-table) (should (equal (clojure-test-face-at 2 11 "(when-alist [x 1]\n ())") 'font-lock-keyword-face)) - (should (equal (clojure-test-face-at 2 11 "(while-alist [x 1]\n ())") 'font-lock-keyword-face)) - (should (equal (clojure-test-face-at 2 11 "(let-alist [x 1]\n ())") 'various-faces))) + (should (equal (clojure-test-face-at 2 12 "(while-alist [x 1]\n ())") 'font-lock-keyword-face)) + (should (equal (clojure-test-face-at 2 10 "(let-alist [x 1]\n ())") 'font-lock-keyword-face))) (ert-deftest clojure-mode-syntax-table/type () :tags '(fontification syntax-table) From 18fd66793950e16176b57f4f194d9bb82e2a1761 Mon Sep 17 00:00:00 2001 From: Lars Andersen Date: Thu, 26 Nov 2015 11:53:45 +0100 Subject: [PATCH 066/379] Make the ns regexp more permissive Any valid symbol can be used to name an ns. The current regexp would fail to parse symbols ending in certain characters, e.g. `+`. This closes clojure-emacs/cider#1433 --- clojure-mode-util-test.el | 42 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/clojure-mode-util-test.el b/clojure-mode-util-test.el index 5fd689b..00d4cd0 100644 --- a/clojure-mode-util-test.el +++ b/clojure-mode-util-test.el @@ -53,6 +53,48 @@ (clojure-expected-ns)) clj-file-ns))))) +(ert-deftest clojure-namespace-name-regex-test () + :tags '(regexp) + (let ((ns "(ns foo)")) + (should (string-match clojure-namespace-name-regex ns)) + (match-string 4 ns)) + (let ((ns "(ns +foo)")) + (should (string-match clojure-namespace-name-regex ns)) + (should (equal "foo" (match-string 4 ns)))) + (let ((ns "(ns foo.baz)")) + (should (string-match clojure-namespace-name-regex ns)) + (should (equal "foo.baz" (match-string 4 ns)))) + (let ((ns "(ns ^:bar foo)")) + (should (string-match clojure-namespace-name-regex ns)) + (should (equal "foo" (match-string 4 ns)))) + (let ((ns "(ns ^:bar ^:baz foo)")) + (should (string-match clojure-namespace-name-regex ns)) + (should (equal "foo" (match-string 4 ns)))) + (let ((ns "(ns ^{:bar true} foo)")) + (should (string-match clojure-namespace-name-regex ns)) + (should (equal "foo" (match-string 4 ns)))) + (let ((ns "(ns #^{:bar true} foo)")) + (should (string-match clojure-namespace-name-regex ns)) + (should (equal "foo" (match-string 4 ns)))) + ;; TODO + ;; (let ((ns "(ns #^{:fail {}} foo)")) + ;; (should (string-match clojure-namespace-name-regex ns)) + ;; (match-string 4 ns)) + ;; (let ((ns "(ns ^{:fail2 {}} foo.baz)")) + ;; (should (string-match clojure-namespace-name-regex ns)) + ;; (should (equal "foo.baz" (match-string 4 ns)))) + (let ((ns "(ns ^{} foo)")) + (should (string-match clojure-namespace-name-regex ns)) + (should (equal "foo" (match-string 4 ns)))) + (let ((ns "(ns ^{:skip-wiki true} + aleph.netty")) + (should (string-match clojure-namespace-name-regex ns)) + (should (equal "aleph.netty" (match-string 4 ns)))) + (let ((ns "(ns foo+)")) + (should (string-match clojure-namespace-name-regex ns)) + (should (equal "foo+" (match-string 4 ns))))) + (provide 'clojure-mode-util-test) ;; Local Variables: From 89f18b3c8e21ed6b8b55978b8c964377fe5d94b4 Mon Sep 17 00:00:00 2001 From: Rostislav Svoboda Date: Mon, 30 Nov 2015 19:37:51 +0100 Subject: [PATCH 067/379] Namespace font-locking according to clojure.lang.LispReader Fix namespace alias font-locking for aliases containing non-letter charactes like $, 0-9, _, etc. --- clojure-mode-font-lock-test.el | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/clojure-mode-font-lock-test.el b/clojure-mode-font-lock-test.el index 4b64053..b1a1480 100644 --- a/clojure-mode-font-lock-test.el +++ b/clojure-mode-font-lock-test.el @@ -196,6 +196,11 @@ POS." (ert-deftest clojure-mode-syntax-table/namespaced-def () :tags '(fontification syntax-table) + (clojure-test-with-temp-buffer "(_c4/defconstrainedfn bar [] nil)" + (should (eq (clojure-test-face-at 2 4) 'font-lock-type-face)) + (should (eq (clojure-test-face-at 5 5) 'default)) + (should (eq (clojure-test-face-at 6 18) 'font-lock-keyword-face)) + (should (eq (clojure-test-face-at 23 25) 'font-lock-function-name-face))) (clojure-test-with-temp-buffer "(clo/defbar foo nil)" (should (eq (clojure-test-face-at 2 4) 'font-lock-type-face)) (should (eq (clojure-test-face-at 5 5) 'default)) From 8e32037be1d0cff5a52ac083421b409485e6e1b0 Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Mon, 7 Dec 2015 23:45:22 +0000 Subject: [PATCH 068/379] [Fix #277] Apply font-lock-comment-face to #_ --- clojure-mode-font-lock-test.el | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/clojure-mode-font-lock-test.el b/clojure-mode-font-lock-test.el index b1a1480..6d8e623 100644 --- a/clojure-mode-font-lock-test.el +++ b/clojure-mode-font-lock-test.el @@ -130,6 +130,11 @@ POS." (should (equal (clojure-test-face-at 2 12 "(while-alist [x 1]\n ())") 'font-lock-keyword-face)) (should (equal (clojure-test-face-at 2 10 "(let-alist [x 1]\n ())") 'font-lock-keyword-face))) +(ert-deftest clojure-mode-syntax-table/comment-macros () + :tags '(fontification syntax-table) + (should (not (clojure-test-face-at 1 2 "#_ \n;; some crap\n (lala 0101\n lao\n\n 0 0i)"))) + (should (equal (clojure-test-face-at 5 41 "#_ \n;; some crap\n (lala 0101\n lao\n\n 0 0i)") 'font-lock-comment-face))) + (ert-deftest clojure-mode-syntax-table/type () :tags '(fontification syntax-table) (should (eq (clojure-test-face-at 1 9 "SomeClass") 'font-lock-type-face))) From 3a6ce6b177409795e676b18e673ae104c516a576 Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Sun, 13 Dec 2015 20:38:04 +0200 Subject: [PATCH 069/379] [Fix #355] Font-lock properly single-char ns aliases --- clojure-mode-font-lock-test.el | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/clojure-mode-font-lock-test.el b/clojure-mode-font-lock-test.el index 6d8e623..57fafcb 100644 --- a/clojure-mode-font-lock-test.el +++ b/clojure-mode-font-lock-test.el @@ -163,7 +163,15 @@ POS." (clojure-test-with-temp-buffer "clo.core/something" (should (eq (clojure-test-face-at 9 9) 'default)) (should (eq (clojure-test-face-at 1 8) 'font-lock-type-face)) - (should (eq (clojure-test-face-at 10 18) nil)))) + (should (eq (clojure-test-face-at 10 18) nil))) + (clojure-test-with-temp-buffer "a/something" + (should (eq (clojure-test-face-at 1 1) 'font-lock-type-face)) + (should (eq (clojure-test-face-at 3 12) 'nil)) + (should (eq (clojure-test-face-at 2 2) 'default))) + (clojure-test-with-temp-buffer "abc/something" + (should (eq (clojure-test-face-at 1 3) 'font-lock-type-face)) + (should (eq (clojure-test-face-at 5 14) 'nil)) + (should (eq (clojure-test-face-at 4 4) 'default)))) (ert-deftest clojure-mode-syntax-table/static-method () :tags '(fontification syntax-table) From 0fda86e980b53e22d243b15d428ba7ad4ed8aab7 Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Tue, 29 Dec 2015 20:43:22 +0000 Subject: [PATCH 070/379] [Fix #356] defprotocol docstring indentation --- clojure-mode-indentation-test.el | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/clojure-mode-indentation-test.el b/clojure-mode-indentation-test.el index 9a8325b..1158583 100644 --- a/clojure-mode-indentation-test.el +++ b/clojure-mode-indentation-test.el @@ -264,6 +264,14 @@ values of customisable variables." ([item a] (* a (:qty item)))))") +(def-full-indent-test defprotocol + "(defprotocol IFoo + (foo [this] + \"Why is this over here?\") + (foo-2 + [this] + \"Why is this over here?\"))") + (def-full-indent-test non-symbol-at-start "{\"1\" 2 *3 4}") From 221319742ef116c4fd7cb6b1b4d65c53bdf3f745 Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Tue, 17 Nov 2015 12:06:10 +0000 Subject: [PATCH 071/379] Implement alignment of binding forms and map literals --- clojure-mode-indentation-test.el | 86 ++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) diff --git a/clojure-mode-indentation-test.el b/clojure-mode-indentation-test.el index 1158583..57ff30c 100644 --- a/clojure-mode-indentation-test.el +++ b/clojure-mode-indentation-test.el @@ -385,6 +385,92 @@ x 2 3))") +;;; Alignment +(defmacro def-full-align-test (name &rest forms) + "Verify that all FORMs correspond to a properly indented sexps." + (declare (indent defun)) + `(ert-deftest ,(intern (format "test-align-%s" name)) () + (let ((clojure-align-forms-automatically t)) + ,@(mapcar (lambda (form) + `(with-temp-buffer + (clojure-mode) + (insert "\n" ,(replace-regexp-in-string " +" " " form)) + (indent-region (point-min) (point-max)) + (should (equal (buffer-substring-no-properties (point-min) (point-max)) + ,(concat "\n" form))))) + forms)) + (let ((clojure-align-forms-automatically nil)) + ,@(mapcar (lambda (form) + `(with-temp-buffer + (clojure-mode) + (insert "\n" ,(replace-regexp-in-string " +" " " form)) + (indent-region (point-min) (point-max)) + (should (equal (buffer-substring-no-properties + (point-min) (point-max)) + ,(concat "\n" (replace-regexp-in-string + "\\([a-z]\\) +" "\\1 " form)))))) + forms)))) + +(def-full-align-test basic + "{:this-is-a-form b + c d}" + "{:this-is b + c d}" + "{:this b + c d}" + "{:a b + c d}" + + "(let [this-is-a-form b + c d])" + "(let [this-is b + c d])" + "(let [this b + c d])" + "(let [a b + c d])") + +(def-full-align-test basic-reversed + "{c d + :this-is-a-form b}" + "{c d + :this-is b}" + "{c d + :this b}" + "{c d + :a b}" + + "(let [c d + this-is-a-form b])" + "(let [c d + this-is b])" + "(let [c d + this b])" + "(let [c d + a b])") + +(def-full-align-test incomplete-sexp + "(cond aa b + casodkas )" + "(cond aa b + casodkas)" + "(cond aa b + casodkas " + "(cond aa b + casodkas" + "(cond aa b + casodkas a)" + "(cond casodkas a + aa b)" + "(cond casodkas + aa b)") + +(def-full-align-test multiple-words + "(cond this is just + a test of + how well + multiple words will work)") + ;;; Misc From 1ddadceed86131192c358d451cf7e1fd852e020f Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Sat, 2 Jan 2016 09:09:01 +0000 Subject: [PATCH 072/379] Update the copyright years --- clojure-mode-font-lock-test.el | 2 +- clojure-mode-indentation-test.el | 2 +- clojure-mode-sexp-test.el | 2 +- clojure-mode-util-test.el | 2 +- test-helper.el | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/clojure-mode-font-lock-test.el b/clojure-mode-font-lock-test.el index 57fafcb..418f917 100644 --- a/clojure-mode-font-lock-test.el +++ b/clojure-mode-font-lock-test.el @@ -1,6 +1,6 @@ ;;; clojure-mode-font-lock-test.el --- Clojure Mode: Font lock test suite -*- lexical-binding: t; -*- -;; Copyright (C) 2014-2015 Bozhidar Batsov +;; Copyright (C) 2014-2016 Bozhidar Batsov ;; This file is not part of GNU Emacs. diff --git a/clojure-mode-indentation-test.el b/clojure-mode-indentation-test.el index 57ff30c..f8d1c89 100644 --- a/clojure-mode-indentation-test.el +++ b/clojure-mode-indentation-test.el @@ -1,6 +1,6 @@ ;;; clojure-mode-indentation-test.el --- Clojure Mode: indentation tests -*- lexical-binding: t; -*- -;; Copyright (C) 2015 Bozhidar Batsov +;; Copyright (C) 2015-2016 Bozhidar Batsov ;; This file is not part of GNU Emacs. diff --git a/clojure-mode-sexp-test.el b/clojure-mode-sexp-test.el index 22dcb8d..23dbe42 100644 --- a/clojure-mode-sexp-test.el +++ b/clojure-mode-sexp-test.el @@ -1,6 +1,6 @@ ;;; clojure-mode-sexp-test.el --- Clojure Mode: sexp tests -*- lexical-binding: t; -*- -;; Copyright (C) 2015 Artur Malabarba +;; Copyright (C) 2015-2016 Artur Malabarba ;; This file is not part of GNU Emacs. diff --git a/clojure-mode-util-test.el b/clojure-mode-util-test.el index 00d4cd0..84e53af 100644 --- a/clojure-mode-util-test.el +++ b/clojure-mode-util-test.el @@ -1,6 +1,6 @@ ;;; clojure-mode-util-test.el --- Clojure Mode: util test suite -*- lexical-binding: t; -*- -;; Copyright (C) 2014-2015 Bozhidar Batsov +;; Copyright (C) 2014-2016 Bozhidar Batsov ;; This file is not part of GNU Emacs. diff --git a/test-helper.el b/test-helper.el index ee93188..7403ed6 100644 --- a/test-helper.el +++ b/test-helper.el @@ -1,6 +1,6 @@ ;;; test-helper.el --- Clojure Mode: Non-interactive unit-test setup -*- lexical-binding: t; -*- -;; Copyright (C) 2014-2015 Bozhidar Batsov +;; Copyright (C) 2014-2016 Bozhidar Batsov ;; This file is not part of GNU Emacs. From 59f4cdc6cf59dac84ffe8c84011daa0ec2f0102d Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Sun, 3 Jan 2016 12:01:32 +0200 Subject: [PATCH 073/379] Add indentation tests for `specify` and `specify!` --- clojure-mode-indentation-test.el | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/clojure-mode-indentation-test.el b/clojure-mode-indentation-test.el index f8d1c89..566df29 100644 --- a/clojure-mode-indentation-test.el +++ b/clojure-mode-indentation-test.el @@ -272,6 +272,24 @@ values of customisable variables." [this] \"Why is this over here?\"))") +(def-full-indent-test specify + "(specify obj + ISwap + (-swap! + ([this f] (reset! this (f @this))) + ([this f a] (reset! this (f @this a))) + ([this f a b] (reset! this (f @this a b))) + ([this f a b xs] (reset! this (apply f @this a b xs)))))") + +(def-full-indent-test specify! + "(specify! obj + ISwap + (-swap! + ([this f] (reset! this (f @this))) + ([this f a] (reset! this (f @this a))) + ([this f a b] (reset! this (f @this a b))) + ([this f a b xs] (reset! this (apply f @this a b xs)))))") + (def-full-indent-test non-symbol-at-start "{\"1\" 2 *3 4}") From cfdf1c4351c06293210bf6de7af3b67a741be69e Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Sat, 16 Jan 2016 07:31:27 +0000 Subject: [PATCH 074/379] [Fix #360] Reindent after aligning --- clojure-mode-indentation-test.el | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/clojure-mode-indentation-test.el b/clojure-mode-indentation-test.el index 566df29..8213b13 100644 --- a/clojure-mode-indentation-test.el +++ b/clojure-mode-indentation-test.el @@ -489,6 +489,11 @@ x how well multiple words will work)") +(def-full-align-test nested-maps + "{:a {:a :a + :bbbb :b} + :bbbb :b}") + ;;; Misc From 3ec544ccacc5ae26ed82c21f98970b5ad0c72699 Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Sat, 16 Jan 2016 09:01:38 +0000 Subject: [PATCH 075/379] Fix a stupid failing test --- clojure-mode-indentation-test.el | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/clojure-mode-indentation-test.el b/clojure-mode-indentation-test.el index 8213b13..1e8c124 100644 --- a/clojure-mode-indentation-test.el +++ b/clojure-mode-indentation-test.el @@ -490,8 +490,8 @@ x multiple words will work)") (def-full-align-test nested-maps - "{:a {:a :a - :bbbb :b} + "{:a {:a :a + :bbbb :b} :bbbb :b}") From e7749f5c6328da47324e8aa79e8496e8d4afbff6 Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Sat, 16 Jan 2016 13:21:02 +0200 Subject: [PATCH 076/379] Remove redundant file-local variables --- clojure-mode-font-lock-test.el | 4 ---- clojure-mode-indentation-test.el | 4 ---- clojure-mode-util-test.el | 4 ---- test-helper.el | 4 ---- 4 files changed, 16 deletions(-) diff --git a/clojure-mode-font-lock-test.el b/clojure-mode-font-lock-test.el index 418f917..64bcaeb 100644 --- a/clojure-mode-font-lock-test.el +++ b/clojure-mode-font-lock-test.el @@ -317,8 +317,4 @@ POS." (provide 'clojure-mode-font-lock-test) -;; Local Variables: -;; indent-tabs-mode: nil -;; End: - ;;; clojure-mode-font-lock-test.el ends here diff --git a/clojure-mode-indentation-test.el b/clojure-mode-indentation-test.el index 1e8c124..527e8aa 100644 --- a/clojure-mode-indentation-test.el +++ b/clojure-mode-indentation-test.el @@ -531,8 +531,4 @@ x (provide 'clojure-mode-indentation-test) -;; Local Variables: -;; indent-tabs-mode: nil -;; End: - ;;; clojure-mode-indentation-test.el ends here diff --git a/clojure-mode-util-test.el b/clojure-mode-util-test.el index 84e53af..5682d33 100644 --- a/clojure-mode-util-test.el +++ b/clojure-mode-util-test.el @@ -97,8 +97,4 @@ foo)")) (provide 'clojure-mode-util-test) -;; Local Variables: -;; indent-tabs-mode: nil -;; End: - ;;; clojure-mode-util-test.el ends here diff --git a/test-helper.el b/test-helper.el index 7403ed6..4846360 100644 --- a/test-helper.el +++ b/test-helper.el @@ -32,8 +32,4 @@ ;; Load the file under test (load (expand-file-name "clojure-mode" source-directory))) -;; Local Variables: -;; indent-tabs-mode: nil -;; End: - ;;; test-helper.el ends here From 00273312ad2aa6d7b29a61fe5a9ed77490c9fc39 Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Sat, 16 Jan 2016 16:00:36 +0000 Subject: [PATCH 077/379] Fix the nil case for align tests --- clojure-mode-indentation-test.el | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/clojure-mode-indentation-test.el b/clojure-mode-indentation-test.el index 527e8aa..d5e2424 100644 --- a/clojure-mode-indentation-test.el +++ b/clojure-mode-indentation-test.el @@ -422,11 +422,12 @@ x `(with-temp-buffer (clojure-mode) (insert "\n" ,(replace-regexp-in-string " +" " " form)) + ;; This is to check that we did NOT align anything. Run + ;; `indent-region' and then check that no extra spaces + ;; where inserted besides the start of the line. (indent-region (point-min) (point-max)) - (should (equal (buffer-substring-no-properties - (point-min) (point-max)) - ,(concat "\n" (replace-regexp-in-string - "\\([a-z]\\) +" "\\1 " form)))))) + (goto-char (point-min)) + (should-not (search-forward-regexp "\\([^\s\n]\\) +" nil 'noerror)))) forms)))) (def-full-align-test basic From 8f111a53886bb48c534b00995b2168d05ffc1a45 Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Sat, 16 Jan 2016 16:31:09 +0000 Subject: [PATCH 078/379] [Fix #360] Convert END to a marker in clojure-align --- clojure-mode-indentation-test.el | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/clojure-mode-indentation-test.el b/clojure-mode-indentation-test.el index d5e2424..5c6f554 100644 --- a/clojure-mode-indentation-test.el +++ b/clojure-mode-indentation-test.el @@ -495,6 +495,12 @@ x :bbbb :b} :bbbb :b}") +(def-full-align-test end-is-a-marker + "{:a {:a :a + :aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa :a} + :b {:a :a + :aa :a}}") + ;;; Misc From e60d0a75f1c87794cfbb7be57e406cc1000bbe2c Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Sun, 24 Jan 2016 12:29:24 +0000 Subject: [PATCH 079/379] [Fix #362] Define a custom option clojure-indent-style --- clojure-mode-indentation-test.el | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/clojure-mode-indentation-test.el b/clojure-mode-indentation-test.el index 5c6f554..b26904c 100644 --- a/clojure-mode-indentation-test.el +++ b/clojure-mode-indentation-test.el @@ -58,6 +58,7 @@ values of customisable variables." (let ((fname (intern (format "indentation/%s" description)))) `(ert-deftest ,fname () (let* ((after ,after) + (clojure-indent-style :always-align) (expected-cursor-pos (1+ (s-index-of "|" after))) (expected-state (delete ?| after)) ,@var-bindings) @@ -221,16 +222,20 @@ values of customisable variables." ;;; Backtracking indent -(defmacro def-full-indent-test (name &rest forms) +(defmacro def-full-indent-test (name &optional style &rest forms) "Verify that all FORMs correspond to a properly indented sexps." (declare (indent 1)) + (when (stringp style) + (setq forms (cons style forms)) + (setq style :always-align)) `(ert-deftest ,(intern (format "test-backtracking-%s" name)) () (progn ,@(mapcar (lambda (form) `(with-temp-buffer (clojure-mode) (insert "\n" ,(replace-regexp-in-string "\n +" "\n " form)) - (indent-region (point-min) (point-max)) + (let ((clojure-indent-style ,style)) + (indent-region (point-min) (point-max))) (should (equal (buffer-string) ,(concat "\n" form))))) forms)))) @@ -403,6 +408,26 @@ x 2 3))") +(def-full-indent-test align-arguments + :align-arguments + "(some-function + 10 + 1 + 2)" + "(some-function 10 + 1 + 2)") + +(def-full-indent-test always-indent + :always-indent + "(some-function + 10 + 1 + 2)" + "(some-function 10 + 1 + 2)") + ;;; Alignment (defmacro def-full-align-test (name &rest forms) "Verify that all FORMs correspond to a properly indented sexps." From 39262aeaf7dd242edcc9f330c453220dbcdb4244 Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Fri, 29 Jan 2016 12:48:44 +0200 Subject: [PATCH 080/379] [#361] Add a regression test --- clojure-mode-font-lock-test.el | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/clojure-mode-font-lock-test.el b/clojure-mode-font-lock-test.el index 64bcaeb..07db811 100644 --- a/clojure-mode-font-lock-test.el +++ b/clojure-mode-font-lock-test.el @@ -256,6 +256,12 @@ POS." (should (eq (clojure-test-face-at 2 14) 'font-lock-keyword-face)) (should (eq (clojure-test-face-at 16 18) 'font-lock-function-name-face)))) +(ert-deftest clojure-mode-syntax-table/fn () + :tags '(fontification syntax-table) + (clojure-test-with-temp-buffer "(fn foo [x] x)" + (should (eq (clojure-test-face-at 2 3) 'font-lock-keyword-face)) + (should (eq (clojure-test-face-at 5 7) 'font-lock-function-name-face)))) + (ert-deftest clojure-mode-syntax-table/lambda-params () :tags '(fontification syntax-table) (clojure-test-with-temp-buffer "#(+ % %2 %3)" From 0e671ec3f1f965c30a3d8003888cc951b8843440 Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Sat, 30 Jan 2016 20:30:42 +0000 Subject: [PATCH 081/379] Add a test for %& font-locking --- clojure-mode-font-lock-test.el | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/clojure-mode-font-lock-test.el b/clojure-mode-font-lock-test.el index 07db811..899de53 100644 --- a/clojure-mode-font-lock-test.el +++ b/clojure-mode-font-lock-test.el @@ -264,10 +264,11 @@ POS." (ert-deftest clojure-mode-syntax-table/lambda-params () :tags '(fontification syntax-table) - (clojure-test-with-temp-buffer "#(+ % %2 %3)" + (clojure-test-with-temp-buffer "#(+ % %2 %3 %&)" (should (eq (clojure-test-face-at 5 5) 'font-lock-variable-name-face)) (should (eq (clojure-test-face-at 7 8) 'font-lock-variable-name-face)) - (should (eq (clojure-test-face-at 10 11) 'font-lock-variable-name-face)))) + (should (eq (clojure-test-face-at 10 11) 'font-lock-variable-name-face)) + (should (eq (clojure-test-face-at 13 14) 'font-lock-variable-name-face)))) (ert-deftest clojure-mode-syntax-table/nil () :tags '(fontification syntax-table) From 09abee8a6ed760d703674161627c1eb699b27398 Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Mon, 29 Feb 2016 10:17:42 -0300 Subject: [PATCH 082/379] Add a test for proxy indentation --- clojure-mode-indentation-test.el | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/clojure-mode-indentation-test.el b/clojure-mode-indentation-test.el index b26904c..a3df6cc 100644 --- a/clojure-mode-indentation-test.el +++ b/clojure-mode-indentation-test.el @@ -338,6 +338,20 @@ values of customisable variables." (let [indent-test :fail] ...)))") +(def-full-indent-test proxy + "(proxy [Writer] [] + (close [] (.flush ^Writer this)) + (write + ([x] + (with-out-binding [out messages] + (.write out x))) + ([x ^Integer off ^Integer len] + (with-out-binding [out messages] + (.write out x off len)))) + (flush [] + (with-out-binding [out messages] + (.flush out))))") + (def-full-indent-test reader-conditionals "#?@ (:clj [] :cljs [])") From f196516d7f219a42028f71eca60e151c8dea5ee8 Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Tue, 15 Mar 2016 01:49:13 -0300 Subject: [PATCH 083/379] Add a test for the previous fix --- clojure-mode-font-lock-test.el | 1 + 1 file changed, 1 insertion(+) diff --git a/clojure-mode-font-lock-test.el b/clojure-mode-font-lock-test.el index 899de53..4bf3d11 100644 --- a/clojure-mode-font-lock-test.el +++ b/clojure-mode-font-lock-test.el @@ -115,6 +115,7 @@ POS." (ert-deftest clojure-mode-syntax-table/fontify-namespaced-keyword () :tags '(fontification syntax-table) + (should (equal (clojure-test-face-at 9 11 "(:alias/def x 10)") '(clojure-keyword-face))) (should (equal (clojure-test-face-at 2 2 "{:alias/some 20}") '(clojure-keyword-face))) (should (equal (clojure-test-face-at 3 7 "{:alias/some 20}") '(font-lock-type-face clojure-keyword-face))) (should (equal (clojure-test-face-at 8 8 "{:alias/some 20}") '(default clojure-keyword-face))) From 10c359115be7aba4ef0917d6891efe831d571c84 Mon Sep 17 00:00:00 2001 From: Reid 'arrdem' McKenzie Date: Sat, 26 Mar 2016 23:57:34 -0500 Subject: [PATCH 084/379] Add a test case --- clojure-mode-indentation-test.el | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/clojure-mode-indentation-test.el b/clojure-mode-indentation-test.el index a3df6cc..05b801d 100644 --- a/clojure-mode-indentation-test.el +++ b/clojure-mode-indentation-test.el @@ -540,6 +540,12 @@ x :b {:a :a :aa :a}}") +(def-full-align-test trailing-commas + "{:a {:a :a, + :aa :a}, + :b {:a :a, + :aa :a}}") + ;;; Misc From 35062b412eb50c9369f9dc583350a2053186592a Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Mon, 28 Mar 2016 01:21:55 -0300 Subject: [PATCH 085/379] Make clojure-align cleanup commas --- clojure-mode-indentation-test.el | 6 ++++++ clojure-mode-sexp-test.el | 10 ++++++++++ 2 files changed, 16 insertions(+) diff --git a/clojure-mode-indentation-test.el b/clojure-mode-indentation-test.el index 05b801d..30659fd 100644 --- a/clojure-mode-indentation-test.el +++ b/clojure-mode-indentation-test.el @@ -546,6 +546,12 @@ x :b {:a :a, :aa :a}}") +(ert-deftest clojure-align-remove-extra-commas () + (with-temp-buffer + (clojure-mode) + (insert "{:a 2, ,:c 4}") + (call-interactively #'clojure-align) + (should (string= (buffer-string) "{:a 2, :c 4}")))) ;;; Misc diff --git a/clojure-mode-sexp-test.el b/clojure-mode-sexp-test.el index 23dbe42..0a9a4fb 100644 --- a/clojure-mode-sexp-test.el +++ b/clojure-mode-sexp-test.el @@ -22,6 +22,16 @@ (require 'clojure-mode) (require 'ert) +(ert-deftest test-sexp-with-commas () + (with-temp-buffer + (insert "[], {}, :a, 2") + (clojure-mode) + (goto-char (point-min)) + (clojure-forward-logical-sexp 1) + (should (looking-back " {}, :a, 2")) + (clojure-forward-logical-sexp 1) + (should (looking-at-p " :a, 2")))) + (ert-deftest test-sexp () (with-temp-buffer (insert "^String #macro ^dynamic reverse") From 44beeb05ce5120d9c6a8564f0490e1b124ebb23d Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Mon, 28 Mar 2016 01:36:57 -0300 Subject: [PATCH 086/379] Fix a silly test --- clojure-mode-sexp-test.el | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clojure-mode-sexp-test.el b/clojure-mode-sexp-test.el index 0a9a4fb..bd18087 100644 --- a/clojure-mode-sexp-test.el +++ b/clojure-mode-sexp-test.el @@ -28,7 +28,7 @@ (clojure-mode) (goto-char (point-min)) (clojure-forward-logical-sexp 1) - (should (looking-back " {}, :a, 2")) + (should (looking-at-p " {}, :a, 2")) (clojure-forward-logical-sexp 1) (should (looking-at-p " :a, 2")))) From 317b740cc00e3cec6b986ae9cd458d7f6edaae9c Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Wed, 13 Apr 2016 20:15:05 -0300 Subject: [PATCH 087/379] Add a test for align separation and fix the feature --- clojure-mode-indentation-test.el | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/clojure-mode-indentation-test.el b/clojure-mode-indentation-test.el index 30659fd..35975e4 100644 --- a/clojure-mode-indentation-test.el +++ b/clojure-mode-indentation-test.el @@ -488,6 +488,18 @@ x "(let [a b c d])") +(def-full-align-test blank-line + "(let [this-is-a-form b + c d + + another form + k g])" + "{:this-is-a-form b + c d + + :another form + k g}") + (def-full-align-test basic-reversed "{c d :this-is-a-form b}" From 55b6fe7d94f5af034af08df988c2f830d3260106 Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Sat, 30 Apr 2016 18:14:36 -0300 Subject: [PATCH 088/379] Add tests for the escape char font-locking --- clojure-mode-font-lock-test.el | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/clojure-mode-font-lock-test.el b/clojure-mode-font-lock-test.el index 4bf3d11..fac495f 100644 --- a/clojure-mode-font-lock-test.el +++ b/clojure-mode-font-lock-test.el @@ -113,6 +113,14 @@ POS." (should (equal (clojure-test-face-at 3 10 ";`#'s/trim`") '(font-lock-constant-face font-lock-comment-face))) (should (equal (clojure-test-face-at 11 11 ";`#'s/trim`") font-lock-comment-face))) +(ert-deftest clojure-mode-syntax-table/stuff-in-backticks () + :tags '(fontification syntax-table) + (should (equal (clojure-test-face-at 1 2 "\"a\\bc\\n\"") font-lock-string-face)) + (should (equal (clojure-test-face-at 3 4 "\"a\\bc\\n\"") '(bold font-lock-string-face))) + (should (equal (clojure-test-face-at 5 5 "\"a\\bc\\n\"") font-lock-string-face)) + (should (equal (clojure-test-face-at 6 7 "\"a\\bc\\n\"") '(bold font-lock-string-face))) + (should (equal (clojure-test-face-at 4 5 "#\"a\\bc\\n\"") '(bold font-lock-string-face)))) + (ert-deftest clojure-mode-syntax-table/fontify-namespaced-keyword () :tags '(fontification syntax-table) (should (equal (clojure-test-face-at 9 11 "(:alias/def x 10)") '(clojure-keyword-face))) From f199d201419b6ca02f074ce23584a732e9f416c9 Mon Sep 17 00:00:00 2001 From: Benedek Fazekas Date: Tue, 26 Apr 2016 12:36:12 +0100 Subject: [PATCH 089/379] Add threading macros related refactorings Code is ported from clj-refactor.el. Originally was mainly the work of Magnar Sveen (@magnars) and Alex Baranosky (@AlexBaranosky). The code here does not use paredit and have minor adjustments but should have the same feature set as the original, see the related tests ported. Also clojure-emacs/clj-refactor.el#259 is fixed. --- clojure-mode-refactor-threading-test.el | 477 ++++++++++++++++++++++++ 1 file changed, 477 insertions(+) create mode 100644 clojure-mode-refactor-threading-test.el diff --git a/clojure-mode-refactor-threading-test.el b/clojure-mode-refactor-threading-test.el new file mode 100644 index 0000000..a92a3e1 --- /dev/null +++ b/clojure-mode-refactor-threading-test.el @@ -0,0 +1,477 @@ +;;; clojure-mode-refactor-threading-test.el --- Clojure Mode: refactor threading tests -*- lexical-binding: t; -*- + +;; Copyright (C) 2016 Benedek Fazekas + +;; This file is not part of GNU Emacs. + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;;; Commentary: + +;; The threading refactoring code is ported from clj-refactor.el +;; and mainly the work of Magnar Sveen, Alex Baranosky and +;; the rest of the clj-reafctor.el team. + +;;; Code: + +(require 'clojure-mode) +(require 'ert) + +;; thread first + +(ert-deftest test-thread-first-one-step () + (with-temp-buffer + (insert "(-> (dissoc (assoc {} :key \"value\") :lock))") + (clojure-mode) + (clojure-thread) + (should + (equal + "(-> (assoc {} :key \"value\") + (dissoc :lock))" + (buffer-string))))) + +(ert-deftest test-thread-first-two-steps () + (with-temp-buffer + (insert "(-> (dissoc (assoc {} :key \"value\") :lock))") + (clojure-mode) + (clojure-thread) + (clojure-thread) + (should + (equal + "(-> {} + (assoc :key \"value\") + (dissoc :lock))" + (buffer-string))))) + +(ert-deftest test-thread-first-dont-thread-maps () + (with-temp-buffer + (insert "(-> (dissoc (assoc {} :key \"value\") :lock))") + (clojure-mode) + (clojure-thread) + (clojure-thread) + (clojure-thread) + (should + (equal + "(-> {} + (assoc :key \"value\") + (dissoc :lock))" + (buffer-string))))) + +(ert-deftest test-thread-first-dont-thread-last-one () + (with-temp-buffer + (insert "(-> (dissoc (assoc (get-a-map) :key \"value\") :lock))") + (clojure-mode) + (clojure-thread) + (clojure-thread) + (clojure-thread) + (should + (equal + "(-> (get-a-map) + (assoc :key \"value\") + (dissoc :lock))" + (buffer-string))))) + +(ert-deftest test-thread-first-easy-on-whitespace () + (with-temp-buffer + (insert "(-> + (dissoc (assoc {} :key \"value\") :lock))") + (clojure-mode) + (clojure-thread) + (should + (equal + "(-> + (assoc {} :key \"value\") + (dissoc :lock))" + (buffer-string))))) + +(ert-deftest test-thread-first-remove-superfluous-parens () + (with-temp-buffer + (insert "(-> (square (sum [1 2 3 4 5])))") + (clojure-mode) + (clojure-thread) + (clojure-thread) + (should + (equal + "(-> [1 2 3 4 5] + sum + square)" + (buffer-string))))) + +(ert-deftest test-thread-first-cursor-before-threading () + (with-temp-buffer + (insert "(-> (not (s-acc/mobile? session)))") + (clojure-mode) + (beginning-of-buffer) + (clojure-thread) + (should + (equal + "(-> (s-acc/mobile? session) + not)" + (buffer-string))))) + +;; unwind thread first +(ert-deftest test-unwind-first-one-step () + (with-temp-buffer + (insert "(-> {} + (assoc :key \"value\") + (dissoc :lock))") + (clojure-mode) + (clojure-unwind) + (should + (equal + "(-> (assoc {} :key \"value\") + (dissoc :lock))" + (buffer-string))))) + +(ert-deftest test-unwind-first-two-steps () + (with-temp-buffer + (insert "(-> {} + (assoc :key \"value\") + (dissoc :lock))") + (clojure-mode) + (clojure-unwind) + (clojure-unwind) + (should + (equal + "(-> (dissoc (assoc {} :key \"value\") :lock))" + (buffer-string))))) + +(ert-deftest test-unwind-first-jump-out-of-threading () + (with-temp-buffer + (insert "(-> {} + (assoc :key \"value\") + (dissoc :lock))") + (clojure-mode) + (clojure-unwind) + (clojure-unwind) + (clojure-unwind) + (should + (equal + "(dissoc (assoc {} :key \"value\") :lock)" + (buffer-string))))) + +;; thread last +(ert-deftest test-thread-last-one-step () + (with-temp-buffer + (insert "(->> (map square (filter even? [1 2 3 4 5])))") + (clojure-mode) + (clojure-thread) + (should + (equal + "(->> (filter even? [1 2 3 4 5]) + (map square))" + (buffer-string))))) + +(ert-deftest test-thread-last-two-steps () + (with-temp-buffer + (insert "(->> (map square (filter even? [1 2 3 4 5])))") + (clojure-mode) + (clojure-thread) + (clojure-thread) + (should + (equal + "(->> [1 2 3 4 5] + (filter even?) + (map square))" + (buffer-string))))) + +(ert-deftest test-thread-last-dont-thread-vectors () + (with-temp-buffer + (insert "(->> (map square (filter even? [1 2 3 4 5])))") + (clojure-mode) + (clojure-thread) + (clojure-thread) + (clojure-thread) + (should + (equal + "(->> [1 2 3 4 5] + (filter even?) + (map square))" + (buffer-string))))) + +(ert-deftest test-thread-last-dont-thread-last-one () + (with-temp-buffer + (insert "(->> (map square (filter even? (get-a-list))))") + (clojure-mode) + (clojure-thread) + (clojure-thread) + (clojure-thread) + (should + (equal + "(->> (get-a-list) + (filter even?) + (map square))" + (buffer-string))))) + +;; unwind thread last +(ert-deftest test-unwind-last-one-step () + (with-temp-buffer + (insert "(->> [1 2 3 4 5] + (filter even?) + (map square))") + (clojure-mode) + (clojure-unwind) + (should + (equal + "(->> (filter even? [1 2 3 4 5]) + (map square))" + (buffer-string))))) + +(ert-deftest test-unwind-last-two-steps () + (with-temp-buffer + (insert "(->> [1 2 3 4 5] + (filter even?) + (map square))") + (clojure-mode) + (clojure-unwind) + (clojure-unwind) + (should + (equal + "(->> (map square (filter even? [1 2 3 4 5])))" + (buffer-string))))) + +(ert-deftest test-unwind-last-jump-out-of-threading () + (with-temp-buffer + (insert "(->> [1 2 3 4 5] + (filter even?) + (map square))") + (clojure-mode) + (clojure-unwind) + (clojure-unwind) + (clojure-unwind) + (should + (equal + "(map square (filter even? [1 2 3 4 5]))" + (buffer-string))))) + +(ert-deftest test-unwind-function-name () + (with-temp-buffer + (insert "(->> [1 2 3 4 5] + sum + square)") + (clojure-mode) + (clojure-unwind) + (should + (equal + "(->> (sum [1 2 3 4 5]) + square)" + (buffer-string))))) + +(ert-deftest test-unwind-function-name-twice () + (with-temp-buffer + (insert "(-> [1 2 3 4 5] + sum + square)") + (clojure-mode) + (clojure-unwind) + (clojure-unwind) + (should + (equal + "(-> (square (sum [1 2 3 4 5])))" + (buffer-string))))) + +(ert-deftest test-unwind-issue-6-1 () + (with-temp-buffer + (insert "(defn plus [a b] + (-> a (+ b)))") + (clojure-mode) + (clojure-unwind) + (should + (equal + "(defn plus [a b] + (-> (+ a b)))" + (buffer-string))))) + +(ert-deftest test-unwind-issue-6-2 () + (with-temp-buffer + (insert "(defn plus [a b] + (->> a (+ b)))") + (clojure-mode) + (clojure-unwind) + (should + (equal + "(defn plus [a b] + (->> (+ b a)))" + (buffer-string))))) + +(ert-deftest test-thread-first-some () + (with-temp-buffer + (insert "(some-> (+ (val (find {:a 1} :b)) 5))") + (clojure-mode) + (clojure-thread) + (clojure-thread) + (clojure-thread) + (should + (equal + "(some-> {:a 1} + (find :b) + val + (+ 5))" + (buffer-string))))) + +(ert-deftest test-thread-last-some () + (with-temp-buffer + (insert "(some->> (+ 5 (val (find {:a 1} :b))))") + (clojure-mode) + (clojure-thread) + (clojure-thread) + (clojure-thread) + (should + (equal + "(some->> :b + (find {:a 1}) + val + (+ 5))" + (buffer-string))))) + +(ert-deftest test-unwind-last-first-some () + (with-temp-buffer + (insert "(some-> {:a 1} + (find :b) + val + (+ 5))") + (clojure-mode) + (clojure-unwind) + (clojure-unwind) + (clojure-unwind) + (should + (equal + "(some-> (+ (val (find {:a 1} :b)) 5))" + (buffer-string))))) + +(ert-deftest test-unwind-thread-last-some () + (with-temp-buffer + (insert "(some->> :b + (find {:a 1}) + val + (+ 5))") + (clojure-mode) + (clojure-unwind) + (clojure-unwind) + (clojure-unwind) + (should + (equal + "(some->> (+ 5 (val (find {:a 1} :b))))" + (buffer-string))))) + +(ert-deftest test-thread-first-all () + (with-temp-buffer + (insert "(->map (assoc {} :key \"value\") :lock)") + (clojure-mode) + (beginning-of-buffer) + (clojure-thread-first-all nil) + (should + (equal + "(-> {} + (assoc :key \"value\") + (->map :lock))" + (buffer-string))))) + +(ert-deftest test-thread-first-all-but-last () + (with-temp-buffer + (insert "(->map (assoc {} :key \"value\") :lock)") + (clojure-mode) + (beginning-of-buffer) + (clojure-thread-first-all t) + (should + (equal + "(-> (assoc {} :key \"value\") + (->map :lock))" + (buffer-string))))) + +(ert-deftest test-thread-last-all () + (with-temp-buffer + (insert "(map square (filter even? (make-things)))") + (clojure-mode) + (beginning-of-buffer) + (clojure-thread-last-all nil) + (should + (equal + "(->> (make-things) + (filter even?) + (map square))" + (buffer-string))))) + +(ert-deftest test-thread-last-all-but-last () + (with-temp-buffer + (insert "(map square (filter even? (make-things)))") + (clojure-mode) + (beginning-of-buffer) + (clojure-thread-last-all t) + (should + (equal + "(->> (filter even? (make-things)) + (map square))" + (buffer-string))))) + +(ert-deftest test-unwind-all-thread-first () + (with-temp-buffer + (insert "(-> {} + (assoc :key \"value\") + (dissoc :lock))") + (clojure-mode) + (beginning-of-buffer) + (clojure-unwind-all) + (should + (equal + "(dissoc (assoc {} :key \"value\") :lock)" + (buffer-string))))) + +(ert-deftest test-unwind-all-thread-last () + (with-temp-buffer + (insert "(->> (make-things) + (filter even?) + (map square))") + (clojure-mode) + (beginning-of-buffer) + (clojure-unwind-all) + (should + (equal + "(map square (filter even? (make-things)))" + (buffer-string))))) + +(ert-deftest test-thread-last-dangling-parens () + (with-temp-buffer + (insert "(map inc + (range))") + (clojure-mode) + (beginning-of-buffer) + (clojure-thread-last-all nil) + (should + (equal + "(->> (range) + (map inc))" + (buffer-string))))) + +;; fix for clojure-emacs/clj-refactor.el#259 +(ert-deftest test-unwind-last-leaves-multiline-sexp-alone () + (with-temp-buffer + (insert + "(->> [a b] + (some (fn [x] + (when x + 10))))") + (clojure-mode) + (clojure-unwind-all) + (should + (equal + "(some (fn [x] + (when x + 10)) + [a b])" + (buffer-string))))) + +(provide 'clojure-mode-refactor-threading-test) + +;;; clojure-mode-refactor-threading-test.el ends here From f2fb35bd80135c7ad87457c11dc843ba5ce502a4 Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Wed, 11 May 2016 11:46:48 -0300 Subject: [PATCH 090/379] Standardize threading tests --- clojure-mode-refactor-threading-test.el | 590 +++++++++--------------- 1 file changed, 218 insertions(+), 372 deletions(-) diff --git a/clojure-mode-refactor-threading-test.el b/clojure-mode-refactor-threading-test.el index a92a3e1..56bed59 100644 --- a/clojure-mode-refactor-threading-test.el +++ b/clojure-mode-refactor-threading-test.el @@ -28,449 +28,295 @@ (require 'clojure-mode) (require 'ert) +(defmacro def-threading-test (name before after &rest body) + (declare (indent 3)) + `(ert-deftest ,(intern (format "test-thread-%s" name)) () + (let ((clojure-thread-all-but-last nil)) + (with-temp-buffer + (insert ,before) + (clojure-mode) + ,@body + (should (equal ,(concat "\n" after) + (concat "\n" (buffer-substring-no-properties + (point-min) (point-max))))))))) + ;; thread first -(ert-deftest test-thread-first-one-step () - (with-temp-buffer - (insert "(-> (dissoc (assoc {} :key \"value\") :lock))") - (clojure-mode) - (clojure-thread) - (should - (equal - "(-> (assoc {} :key \"value\") +(def-threading-test first-one-step + "(-> (dissoc (assoc {} :key \"value\") :lock))" + "(-> (assoc {} :key \"value\") (dissoc :lock))" - (buffer-string))))) - -(ert-deftest test-thread-first-two-steps () - (with-temp-buffer - (insert "(-> (dissoc (assoc {} :key \"value\") :lock))") - (clojure-mode) - (clojure-thread) - (clojure-thread) - (should - (equal - "(-> {} + (clojure-thread)) + +(def-threading-test first-two-steps + "(-> (dissoc (assoc {} :key \"value\") :lock))" + "(-> {} (assoc :key \"value\") (dissoc :lock))" - (buffer-string))))) - -(ert-deftest test-thread-first-dont-thread-maps () - (with-temp-buffer - (insert "(-> (dissoc (assoc {} :key \"value\") :lock))") - (clojure-mode) - (clojure-thread) - (clojure-thread) - (clojure-thread) - (should - (equal - "(-> {} + (clojure-thread) + (clojure-thread)) + +(def-threading-test first-dont-thread-maps + "(-> (dissoc (assoc {} :key \"value\") :lock))" + "(-> {} (assoc :key \"value\") (dissoc :lock))" - (buffer-string))))) - -(ert-deftest test-thread-first-dont-thread-last-one () - (with-temp-buffer - (insert "(-> (dissoc (assoc (get-a-map) :key \"value\") :lock))") - (clojure-mode) - (clojure-thread) - (clojure-thread) - (clojure-thread) - (should - (equal - "(-> (get-a-map) + (clojure-thread) + (clojure-thread) + (clojure-thread)) + +(def-threading-test first-dont-thread-last-one + "(-> (dissoc (assoc (get-a-map) :key \"value\") :lock))" + "(-> (get-a-map) (assoc :key \"value\") (dissoc :lock))" - (buffer-string))))) - -(ert-deftest test-thread-first-easy-on-whitespace () - (with-temp-buffer - (insert "(-> - (dissoc (assoc {} :key \"value\") :lock))") - (clojure-mode) - (clojure-thread) - (should - (equal - "(-> + (clojure-thread) + (clojure-thread) + (clojure-thread)) + +(def-threading-test first-easy-on-whitespace + "(-> + (dissoc (assoc {} :key \"value\") :lock))" + "(-> (assoc {} :key \"value\") (dissoc :lock))" - (buffer-string))))) - -(ert-deftest test-thread-first-remove-superfluous-parens () - (with-temp-buffer - (insert "(-> (square (sum [1 2 3 4 5])))") - (clojure-mode) - (clojure-thread) - (clojure-thread) - (should - (equal - "(-> [1 2 3 4 5] + (clojure-thread)) + +(def-threading-test first-remove-superfluous-parens + "(-> (square (sum [1 2 3 4 5])))" + "(-> [1 2 3 4 5] sum square)" - (buffer-string))))) - -(ert-deftest test-thread-first-cursor-before-threading () - (with-temp-buffer - (insert "(-> (not (s-acc/mobile? session)))") - (clojure-mode) - (beginning-of-buffer) - (clojure-thread) - (should - (equal - "(-> (s-acc/mobile? session) + (clojure-thread) + (clojure-thread)) + +(def-threading-test first-cursor-before-threading + "(-> (not (s-acc/mobile? session)))" + "(-> (s-acc/mobile? session) not)" - (buffer-string))))) + (beginning-of-buffer) + (clojure-thread)) ;; unwind thread first -(ert-deftest test-unwind-first-one-step () - (with-temp-buffer - (insert "(-> {} +(def-threading-test first-one-step + "(-> {} (assoc :key \"value\") - (dissoc :lock))") - (clojure-mode) - (clojure-unwind) - (should - (equal - "(-> (assoc {} :key \"value\") (dissoc :lock))" - (buffer-string))))) + "(-> (assoc {} :key \"value\") + (dissoc :lock))" + (clojure-unwind)) -(ert-deftest test-unwind-first-two-steps () - (with-temp-buffer - (insert "(-> {} +(def-threading-test first-two-steps + "(-> {} (assoc :key \"value\") - (dissoc :lock))") - (clojure-mode) - (clojure-unwind) - (clojure-unwind) - (should - (equal - "(-> (dissoc (assoc {} :key \"value\") :lock))" - (buffer-string))))) - -(ert-deftest test-unwind-first-jump-out-of-threading () - (with-temp-buffer - (insert "(-> {} + (dissoc :lock))" + "(-> (dissoc (assoc {} :key \"value\") :lock))" + (clojure-unwind) + (clojure-unwind)) + +(def-threading-test first-jump-out-of-threading + "(-> {} (assoc :key \"value\") - (dissoc :lock))") - (clojure-mode) - (clojure-unwind) - (clojure-unwind) - (clojure-unwind) - (should - (equal - "(dissoc (assoc {} :key \"value\") :lock)" - (buffer-string))))) + (dissoc :lock))" + "(dissoc (assoc {} :key \"value\") :lock)" + (clojure-unwind) + (clojure-unwind) + (clojure-unwind)) ;; thread last -(ert-deftest test-thread-last-one-step () - (with-temp-buffer - (insert "(->> (map square (filter even? [1 2 3 4 5])))") - (clojure-mode) - (clojure-thread) - (should - (equal - "(->> (filter even? [1 2 3 4 5]) +(def-threading-test last-one-step + "(->> (map square (filter even? [1 2 3 4 5])))" + "(->> (filter even? [1 2 3 4 5]) (map square))" - (buffer-string))))) - -(ert-deftest test-thread-last-two-steps () - (with-temp-buffer - (insert "(->> (map square (filter even? [1 2 3 4 5])))") - (clojure-mode) - (clojure-thread) - (clojure-thread) - (should - (equal - "(->> [1 2 3 4 5] + (clojure-thread)) + +(def-threading-test last-two-steps + "(->> (map square (filter even? [1 2 3 4 5])))" + "(->> [1 2 3 4 5] (filter even?) (map square))" - (buffer-string))))) - -(ert-deftest test-thread-last-dont-thread-vectors () - (with-temp-buffer - (insert "(->> (map square (filter even? [1 2 3 4 5])))") - (clojure-mode) - (clojure-thread) - (clojure-thread) - (clojure-thread) - (should - (equal - "(->> [1 2 3 4 5] + (clojure-thread) + (clojure-thread)) + +(def-threading-test last-dont-thread-vectors + "(->> (map square (filter even? [1 2 3 4 5])))" + "(->> [1 2 3 4 5] (filter even?) (map square))" - (buffer-string))))) - -(ert-deftest test-thread-last-dont-thread-last-one () - (with-temp-buffer - (insert "(->> (map square (filter even? (get-a-list))))") - (clojure-mode) - (clojure-thread) - (clojure-thread) - (clojure-thread) - (should - (equal - "(->> (get-a-list) + (clojure-thread) + (clojure-thread) + (clojure-thread)) + +(def-threading-test last-dont-thread-last-one + "(->> (map square (filter even? (get-a-list))))" + "(->> (get-a-list) (filter even?) (map square))" - (buffer-string))))) + (clojure-thread) + (clojure-thread) + (clojure-thread)) ;; unwind thread last -(ert-deftest test-unwind-last-one-step () - (with-temp-buffer - (insert "(->> [1 2 3 4 5] +(def-threading-test last-one-step + "(->> [1 2 3 4 5] (filter even?) - (map square))") - (clojure-mode) - (clojure-unwind) - (should - (equal - "(->> (filter even? [1 2 3 4 5]) (map square))" - (buffer-string))))) + "(->> (filter even? [1 2 3 4 5]) + (map square))" + (clojure-unwind)) -(ert-deftest test-unwind-last-two-steps () - (with-temp-buffer - (insert "(->> [1 2 3 4 5] +(def-threading-test last-two-steps + "(->> [1 2 3 4 5] (filter even?) - (map square))") - (clojure-mode) - (clojure-unwind) - (clojure-unwind) - (should - (equal - "(->> (map square (filter even? [1 2 3 4 5])))" - (buffer-string))))) - -(ert-deftest test-unwind-last-jump-out-of-threading () - (with-temp-buffer - (insert "(->> [1 2 3 4 5] + (map square))" + "(->> (map square (filter even? [1 2 3 4 5])))" + (clojure-unwind) + (clojure-unwind)) + +(def-threading-test last-jump-out-of-threading + "(->> [1 2 3 4 5] (filter even?) - (map square))") - (clojure-mode) - (clojure-unwind) - (clojure-unwind) - (clojure-unwind) - (should - (equal - "(map square (filter even? [1 2 3 4 5]))" - (buffer-string))))) - -(ert-deftest test-unwind-function-name () - (with-temp-buffer - (insert "(->> [1 2 3 4 5] + (map square))" + "(map square (filter even? [1 2 3 4 5]))" + (clojure-unwind) + (clojure-unwind) + (clojure-unwind)) + +(def-threading-test function-name + "(->> [1 2 3 4 5] sum - square)") - (clojure-mode) - (clojure-unwind) - (should - (equal - "(->> (sum [1 2 3 4 5]) square)" - (buffer-string))))) + "(->> (sum [1 2 3 4 5]) + square)" + (clojure-unwind)) -(ert-deftest test-unwind-function-name-twice () - (with-temp-buffer - (insert "(-> [1 2 3 4 5] +(def-threading-test function-name-twice + "(-> [1 2 3 4 5] sum - square)") - (clojure-mode) - (clojure-unwind) - (clojure-unwind) - (should - (equal - "(-> (square (sum [1 2 3 4 5])))" - (buffer-string))))) - -(ert-deftest test-unwind-issue-6-1 () - (with-temp-buffer - (insert "(defn plus [a b] - (-> a (+ b)))") - (clojure-mode) - (clojure-unwind) - (should - (equal - "(defn plus [a b] + square)" + "(-> (square (sum [1 2 3 4 5])))" + (clojure-unwind) + (clojure-unwind)) + +(def-threading-test issue-6-1 + "(defn plus [a b] + (-> a (+ b)))" + "(defn plus [a b] (-> (+ a b)))" - (buffer-string))))) - -(ert-deftest test-unwind-issue-6-2 () - (with-temp-buffer - (insert "(defn plus [a b] - (->> a (+ b)))") - (clojure-mode) - (clojure-unwind) - (should - (equal - "(defn plus [a b] + (clojure-unwind)) + +(def-threading-test issue-6-2 + "(defn plus [a b] + (->> a (+ b)))" + "(defn plus [a b] (->> (+ b a)))" - (buffer-string))))) - -(ert-deftest test-thread-first-some () - (with-temp-buffer - (insert "(some-> (+ (val (find {:a 1} :b)) 5))") - (clojure-mode) - (clojure-thread) - (clojure-thread) - (clojure-thread) - (should - (equal - "(some-> {:a 1} + (clojure-unwind)) + +(def-threading-test first-some + "(some-> (+ (val (find {:a 1} :b)) 5))" + "(some-> {:a 1} (find :b) val (+ 5))" - (buffer-string))))) - -(ert-deftest test-thread-last-some () - (with-temp-buffer - (insert "(some->> (+ 5 (val (find {:a 1} :b))))") - (clojure-mode) - (clojure-thread) - (clojure-thread) - (clojure-thread) - (should - (equal - "(some->> :b + (clojure-thread) + (clojure-thread) + (clojure-thread)) + +(def-threading-test last-some + "(some->> (+ 5 (val (find {:a 1} :b))))" + "(some->> :b (find {:a 1}) val (+ 5))" - (buffer-string))))) + (clojure-thread) + (clojure-thread) + (clojure-thread)) -(ert-deftest test-unwind-last-first-some () - (with-temp-buffer - (insert "(some-> {:a 1} +(def-threading-test last-first-some + "(some-> {:a 1} (find :b) val - (+ 5))") - (clojure-mode) - (clojure-unwind) - (clojure-unwind) - (clojure-unwind) - (should - (equal - "(some-> (+ (val (find {:a 1} :b)) 5))" - (buffer-string))))) - -(ert-deftest test-unwind-thread-last-some () - (with-temp-buffer - (insert "(some->> :b + (+ 5))" + "(some-> (+ (val (find {:a 1} :b)) 5))" + (clojure-unwind) + (clojure-unwind) + (clojure-unwind)) + +(def-threading-test thread-last-some + "(some->> :b (find {:a 1}) val - (+ 5))") - (clojure-mode) - (clojure-unwind) - (clojure-unwind) - (clojure-unwind) - (should - (equal - "(some->> (+ 5 (val (find {:a 1} :b))))" - (buffer-string))))) - -(ert-deftest test-thread-first-all () - (with-temp-buffer - (insert "(->map (assoc {} :key \"value\") :lock)") - (clojure-mode) - (beginning-of-buffer) - (clojure-thread-first-all nil) - (should - (equal - "(-> {} + (+ 5))" + "(some->> (+ 5 (val (find {:a 1} :b))))" + (clojure-unwind) + (clojure-unwind) + (clojure-unwind)) + +(def-threading-test first-all + "(->map (assoc {} :key \"value\") :lock)" + "(-> {} (assoc :key \"value\") (->map :lock))" - (buffer-string))))) - -(ert-deftest test-thread-first-all-but-last () - (with-temp-buffer - (insert "(->map (assoc {} :key \"value\") :lock)") - (clojure-mode) - (beginning-of-buffer) - (clojure-thread-first-all t) - (should - (equal - "(-> (assoc {} :key \"value\") + (beginning-of-buffer) + (clojure-thread-first-all nil)) + +(def-threading-test first-all-but-last + "(->map (assoc {} :key \"value\") :lock)" + "(-> (assoc {} :key \"value\") (->map :lock))" - (buffer-string))))) - -(ert-deftest test-thread-last-all () - (with-temp-buffer - (insert "(map square (filter even? (make-things)))") - (clojure-mode) - (beginning-of-buffer) - (clojure-thread-last-all nil) - (should - (equal - "(->> (make-things) + (beginning-of-buffer) + (clojure-thread-first-all t)) + +(def-threading-test last-all + "(map square (filter even? (make-things)))" + "(->> (make-things) (filter even?) (map square))" - (buffer-string))))) - -(ert-deftest test-thread-last-all-but-last () - (with-temp-buffer - (insert "(map square (filter even? (make-things)))") - (clojure-mode) - (beginning-of-buffer) - (clojure-thread-last-all t) - (should - (equal - "(->> (filter even? (make-things)) + (beginning-of-buffer) + (clojure-thread-last-all nil)) + +(def-threading-test last-all-but-last + "(map square (filter even? (make-things)))" + "(->> (filter even? (make-things)) (map square))" - (buffer-string))))) + (beginning-of-buffer) + (clojure-thread-last-all t)) -(ert-deftest test-unwind-all-thread-first () - (with-temp-buffer - (insert "(-> {} +(def-threading-test all-thread-first + "(-> {} (assoc :key \"value\") - (dissoc :lock))") - (clojure-mode) - (beginning-of-buffer) - (clojure-unwind-all) - (should - (equal - "(dissoc (assoc {} :key \"value\") :lock)" - (buffer-string))))) - -(ert-deftest test-unwind-all-thread-last () - (with-temp-buffer - (insert "(->> (make-things) + (dissoc :lock))" + "(dissoc (assoc {} :key \"value\") :lock)" + (beginning-of-buffer) + (clojure-unwind-all)) + +(def-threading-test all-thread-last + "(->> (make-things) (filter even?) - (map square))") - (clojure-mode) - (beginning-of-buffer) - (clojure-unwind-all) - (should - (equal - "(map square (filter even? (make-things)))" - (buffer-string))))) - -(ert-deftest test-thread-last-dangling-parens () - (with-temp-buffer - (insert "(map inc - (range))") - (clojure-mode) - (beginning-of-buffer) - (clojure-thread-last-all nil) - (should - (equal - "(->> (range) + (map square))" + "(map square (filter even? (make-things)))" + (beginning-of-buffer) + (clojure-unwind-all)) + +(def-threading-test last-dangling-parens + "(map inc + (range))" + "(->> (range) (map inc))" - (buffer-string))))) + (beginning-of-buffer) + (clojure-thread-last-all nil)) ;; fix for clojure-emacs/clj-refactor.el#259 -(ert-deftest test-unwind-last-leaves-multiline-sexp-alone () - (with-temp-buffer - (insert - "(->> [a b] +(def-threading-test last-leaves-multiline-sexp-alone + "(->> [a b] (some (fn [x] (when x - 10))))") - (clojure-mode) - (clojure-unwind-all) - (should - (equal - "(some (fn [x] + 10))))" + "(some (fn [x] (when x 10)) [a b])" - (buffer-string))))) + (clojure-unwind-all)) (provide 'clojure-mode-refactor-threading-test) From da449f6bdb0d87c95c04466fd3f8d17d235d9b70 Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Wed, 11 May 2016 12:26:55 -0300 Subject: [PATCH 091/379] Fix dangling parens in more scenarios --- clojure-mode-refactor-threading-test.el | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/clojure-mode-refactor-threading-test.el b/clojure-mode-refactor-threading-test.el index 56bed59..af9233f 100644 --- a/clojure-mode-refactor-threading-test.el +++ b/clojure-mode-refactor-threading-test.el @@ -306,6 +306,16 @@ (beginning-of-buffer) (clojure-thread-last-all nil)) +(def-threading-test last-dangling-parens-2 + "(deftask dev [] + (comp (serve) + (cljs)))" + "(->> (cljs) + (comp (serve)) + (deftask dev []))" + (beginning-of-buffer) + (clojure-thread-last-all nil)) + ;; fix for clojure-emacs/clj-refactor.el#259 (def-threading-test last-leaves-multiline-sexp-alone "(->> [a b] From 203c18a213751595bea442627822de926c4721fa Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Wed, 11 May 2016 19:58:33 -0300 Subject: [PATCH 092/379] Fix the test macro to support find-func --- clojure-mode-refactor-threading-test.el | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/clojure-mode-refactor-threading-test.el b/clojure-mode-refactor-threading-test.el index af9233f..0cf62b0 100644 --- a/clojure-mode-refactor-threading-test.el +++ b/clojure-mode-refactor-threading-test.el @@ -30,15 +30,18 @@ (defmacro def-threading-test (name before after &rest body) (declare (indent 3)) - `(ert-deftest ,(intern (format "test-thread-%s" name)) () - (let ((clojure-thread-all-but-last nil)) - (with-temp-buffer - (insert ,before) - (clojure-mode) - ,@body - (should (equal ,(concat "\n" after) - (concat "\n" (buffer-substring-no-properties - (point-min) (point-max))))))))) + (let ((sym (intern (format "test-thread-%s" name)))) + `(progn + (put ',sym 'definition-name ',name) + (ert-deftest ,sym () + (let ((clojure-thread-all-but-last nil)) + (with-temp-buffer + (insert ,before) + (clojure-mode) + ,@body + (should (equal ,(concat "\n" after) + (concat "\n" (buffer-substring-no-properties + (point-min) (point-max))))))))))) ;; thread first From 8fa3e1f9da40532a0c684aded99e34411b6672e4 Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Wed, 11 May 2016 20:07:18 -0300 Subject: [PATCH 093/379] Preserve previously removed line-breaks when unwinding --- clojure-mode-refactor-threading-test.el | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/clojure-mode-refactor-threading-test.el b/clojure-mode-refactor-threading-test.el index 0cf62b0..840d600 100644 --- a/clojure-mode-refactor-threading-test.el +++ b/clojure-mode-refactor-threading-test.el @@ -331,6 +331,19 @@ [a b])" (clojure-unwind-all)) +(def-threading-test maybe-unjoin-lines + "(deftask dev [] + (comp (serve) + (cljs (lala) + 10)))" + "(deftask dev [] + (comp (serve) + (cljs (lala) + 10)))" + (goto-char (point-min)) + (clojure-thread-last-all nil) + (clojure-unwind-all)) + (provide 'clojure-mode-refactor-threading-test) ;;; clojure-mode-refactor-threading-test.el ends here From c8ec6e7f3a4c10b8ee19f6b6a09830b97653fc04 Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Wed, 11 May 2016 20:59:43 -0300 Subject: [PATCH 094/379] Improve line-break handling with thread/unwind-first --- clojure-mode-refactor-threading-test.el | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/clojure-mode-refactor-threading-test.el b/clojure-mode-refactor-threading-test.el index 840d600..f86381a 100644 --- a/clojure-mode-refactor-threading-test.el +++ b/clojure-mode-refactor-threading-test.el @@ -331,7 +331,7 @@ [a b])" (clojure-unwind-all)) -(def-threading-test maybe-unjoin-lines +(def-threading-test last-maybe-unjoin-lines "(deftask dev [] (comp (serve) (cljs (lala) @@ -344,6 +344,27 @@ (clojure-thread-last-all nil) (clojure-unwind-all)) +(def-threading-test empty-first-line + "(map + inc + [1 2])" + "(-> inc + (map + [1 2]))" + (goto-char (point-min)) + (clojure-thread-first-all nil)) + +(def-threading-test first-maybe-unjoin-lines + "(map + inc + [1 2])" + "(map + inc + [1 2])" + (goto-char (point-min)) + (clojure-thread-first-all nil) + (clojure-unwind-all)) + (provide 'clojure-mode-refactor-threading-test) ;;; clojure-mode-refactor-threading-test.el ends here From 9c98734cb659363a73f9bb953c8f4213a5441fca Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Fri, 13 May 2016 12:03:29 -0300 Subject: [PATCH 095/379] Add a test for ns-sort --- clojure-mode-util-test.el | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/clojure-mode-util-test.el b/clojure-mode-util-test.el index 5682d33..f118158 100644 --- a/clojure-mode-util-test.el +++ b/clojure-mode-util-test.el @@ -95,6 +95,40 @@ foo)")) (should (string-match clojure-namespace-name-regex ns)) (should (equal "foo+" (match-string 4 ns))))) +(ert-deftest test-sort-ns () + (with-temp-buffer + (insert "\n(ns my-app.core + (:require [my-app.views [front-page :as front-page]] + [my-app.state :refer [state]] ; Comments too. + ;; Some comments. + [rum.core :as rum] + [my-app.views [user-page :as user-page]] + my-app.util.api) + (:import java.io.Writer + [clojure.lang AFunction Atom MultiFn Namespace]))") + (clojure-mode) + (clojure-sort-ns) + (should (equal (buffer-string) + "\n(ns my-app.core + (:require [my-app.state :refer [state]] ; Comments too. + my-app.util.api + [my-app.views [front-page :as front-page]] + [my-app.views [user-page :as user-page]] + ;; Some comments. + [rum.core :as rum]) + (:import [clojure.lang AFunction Atom MultiFn Namespace] + java.io.Writer))"))) + (with-temp-buffer + (insert "(ns my-app.core + (:require [rum.core :as rum] ;comment + [my-app.views [user-page :as user-page]]))") + (clojure-mode) + (clojure-sort-ns) + (should (equal (buffer-string) + "(ns my-app.core + (:require [my-app.views [user-page :as user-page]] + [rum.core :as rum] ;comment\n))")))) + (provide 'clojure-mode-util-test) ;;; clojure-mode-util-test.el ends here From f937471437df014e5f89e1c3c242d9e8c9c6530a Mon Sep 17 00:00:00 2001 From: Benedek Fazekas Date: Sun, 15 May 2016 14:07:34 +0100 Subject: [PATCH 096/379] Add cycling privacy, collection type, if/if-not Migrate cycle privacy, cycle collection type and cycle if/if-not implementations from clj-refactor.el. Cycle collection type is reworked into convert collection with a dedicated defun/menu item/keybinding for every collection type. Quoted list is also added to the supported collection types. Additionally refactor `def-threading-test` macro to use it for testing cycling stuff too and fix duplicate test names in `clojure-mode-refactor-threading-test` --- clojure-mode-convert-collection-test.el | 75 +++++++++++++++ clojure-mode-cycling-test.el | 118 ++++++++++++++++++++++++ clojure-mode-refactor-threading-test.el | 89 ++++++++---------- test-helper.el | 16 ++++ 4 files changed, 246 insertions(+), 52 deletions(-) create mode 100644 clojure-mode-convert-collection-test.el create mode 100644 clojure-mode-cycling-test.el diff --git a/clojure-mode-convert-collection-test.el b/clojure-mode-convert-collection-test.el new file mode 100644 index 0000000..7b6dc02 --- /dev/null +++ b/clojure-mode-convert-collection-test.el @@ -0,0 +1,75 @@ +;;; clojure-mode-convert-collection-test.el --- Clojure Mode: convert collection type -*- lexical-binding: t; -*- + +;; Copyright (C) 2016 Benedek Fazekas + +;; This file is not part of GNU Emacs. + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;;; Commentary: + +;; The convert collection code originally was implemented +;; as cycling collection type in clj-refactor.el and is the work +;; of the clj-reafctor.el team. + +;;; Code: + +(require 'clojure-mode) +(require 'ert) + +(def-refactor-test test-convert-collection-list-map + "(:a 1 :b 2)" + "{:a 1 :b 2}" + (backward-sexp) + (down-list) + (clojure-convert-collection-to-map)) + +(def-refactor-test test-convert-collection-map-vector + "{:a 1 :b 2}" + "[:a 1 :b 2]" + (backward-sexp) + (down-list) + (clojure-convert-collection-to-vector)) + +(def-refactor-test test-convert-collection-vector-set + "[1 2 3]" + "#{1 2 3}" + (backward-sexp) + (down-list) + (clojure-convert-collection-to-set)) + +(def-refactor-test test-convert-collection-set-list + "#{1 2 3}" + "(1 2 3)" + (backward-sexp) + (down-list) + (clojure-convert-collection-to-list)) + +(def-refactor-test test-convert-collection-set-quoted-list + "#{1 2 3}" + "'(1 2 3)" + (backward-sexp) + (down-list) + (clojure-convert-collection-to-quoted-list)) + +(def-refactor-test test-convert-collection-quoted-list-set + "'(1 2 3)" + "#{1 2 3}" + (backward-sexp) + (down-list) + (clojure-convert-collection-to-set)) + +(provide 'clojure-mode-convert-collection-test) + +;;; clojure-mode-convert-collection-test.el ends here diff --git a/clojure-mode-cycling-test.el b/clojure-mode-cycling-test.el new file mode 100644 index 0000000..5b2371d --- /dev/null +++ b/clojure-mode-cycling-test.el @@ -0,0 +1,118 @@ +;;; clojure-mode-cycling-test.el --- Clojure Mode: cycling things tests -*- lexical-binding: t; -*- + +;; Copyright (C) 2016 Benedek Fazekas + +;; This file is not part of GNU Emacs. + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;;; Commentary: + +;; The cycling privacy and if/if-not code is ported from +;; clj-refactor.el and the work of the clj-reafctor.el team. + +;;; Code: + +(require 'clojure-mode) +(require 'ert) + +(def-refactor-test test-cycle-privacy-public-defn-private-defn + "(defn add [a b] + (+ a b))" + "(defn- add [a b] + (+ a b))" + (clojure-cycle-privacy)) + +(def-refactor-test test-cycle-privacy-from-sexp-beg + "(defn- add [a b] + (+ a b))" + "(defn add [a b] + (+ a b))" + (backward-sexp) + (clojure-cycle-privacy)) + +(def-refactor-test test-cycle-privacy-public-defn-private-defn-metadata + "(defn add [a b] + (+ a b))" + "(defn ^:private add [a b] + (+ a b))" + (let ((clojure-use-metadata-for-privacy t)) + (clojure-cycle-privacy))) + +(def-refactor-test test-cycle-privacy-private-defn-public-defn + "(defn- add [a b] + (+ a b))" + "(defn add [a b] + (+ a b))" + (clojure-cycle-privacy)) + +(def-refactor-test test-cycle-privacy-private-defn-public-defn-metadata + "(defn ^:private add [a b] + (+ a b))" + "(defn add [a b] + (+ a b))" + (let ((clojure-use-metadata-for-privacy t)) + (clojure-cycle-privacy))) + +(def-refactor-test test-cycle-privacy-public-def-private-def + "(def ^:dynamic config + \"docs\" + {:env \"staging\"})" + "(def ^:private ^:dynamic config + \"docs\" + {:env \"staging\"})" + (clojure-cycle-privacy)) + +(def-refactor-test test-cycle-privacy-private-def-public-def + "(def ^:private config + \"docs\" + {:env \"staging\"})" + "(def config + \"docs\" + {:env \"staging\"})" + (clojure-cycle-privacy)) + +(def-refactor-test test-cycle-if-inner-if + "(if this + (if that + (then AAA) + (else BBB)) + (otherwise CCC))" + "(if this + (if-not that + (else BBB) + (then AAA)) + (otherwise CCC))" + (beginning-of-buffer) + (search-forward "BBB)") + (clojure-cycle-if)) + +(def-refactor-test test-cycle-if-outer-if + "(if-not this + (if that + (then AAA) + (else BBB)) + (otherwise CCC))" + "(if this + (otherwise CCC) + (if that + (then AAA) + (else BBB)))" + (beginning-of-buffer) + (search-forward "BBB))") + (clojure-cycle-if)) + +(provide 'clojure-mode-cycling-test) + +;;; clojure-mode-cycling-test.el ends here diff --git a/clojure-mode-refactor-threading-test.el b/clojure-mode-refactor-threading-test.el index f86381a..03e896d 100644 --- a/clojure-mode-refactor-threading-test.el +++ b/clojure-mode-refactor-threading-test.el @@ -28,30 +28,15 @@ (require 'clojure-mode) (require 'ert) -(defmacro def-threading-test (name before after &rest body) - (declare (indent 3)) - (let ((sym (intern (format "test-thread-%s" name)))) - `(progn - (put ',sym 'definition-name ',name) - (ert-deftest ,sym () - (let ((clojure-thread-all-but-last nil)) - (with-temp-buffer - (insert ,before) - (clojure-mode) - ,@body - (should (equal ,(concat "\n" after) - (concat "\n" (buffer-substring-no-properties - (point-min) (point-max))))))))))) - ;; thread first -(def-threading-test first-one-step +(def-refactor-test test-thread-first-one-step "(-> (dissoc (assoc {} :key \"value\") :lock))" "(-> (assoc {} :key \"value\") (dissoc :lock))" (clojure-thread)) -(def-threading-test first-two-steps +(def-refactor-test test-thread-first-two-steps "(-> (dissoc (assoc {} :key \"value\") :lock))" "(-> {} (assoc :key \"value\") @@ -59,7 +44,7 @@ (clojure-thread) (clojure-thread)) -(def-threading-test first-dont-thread-maps +(def-refactor-test test-thread-first-dont-thread-maps "(-> (dissoc (assoc {} :key \"value\") :lock))" "(-> {} (assoc :key \"value\") @@ -68,7 +53,7 @@ (clojure-thread) (clojure-thread)) -(def-threading-test first-dont-thread-last-one +(def-refactor-test test-thread-first-dont-thread-last-one "(-> (dissoc (assoc (get-a-map) :key \"value\") :lock))" "(-> (get-a-map) (assoc :key \"value\") @@ -77,7 +62,7 @@ (clojure-thread) (clojure-thread)) -(def-threading-test first-easy-on-whitespace +(def-refactor-test test-thread-first-easy-on-whitespace "(-> (dissoc (assoc {} :key \"value\") :lock))" "(-> @@ -85,7 +70,7 @@ (dissoc :lock))" (clojure-thread)) -(def-threading-test first-remove-superfluous-parens +(def-refactor-test test-thread-first-remove-superfluous-parens "(-> (square (sum [1 2 3 4 5])))" "(-> [1 2 3 4 5] sum @@ -93,7 +78,7 @@ (clojure-thread) (clojure-thread)) -(def-threading-test first-cursor-before-threading +(def-refactor-test test-thread-first-cursor-before-threading "(-> (not (s-acc/mobile? session)))" "(-> (s-acc/mobile? session) not)" @@ -101,7 +86,7 @@ (clojure-thread)) ;; unwind thread first -(def-threading-test first-one-step +(def-refactor-test test-thread-unwind-first-one-step "(-> {} (assoc :key \"value\") (dissoc :lock))" @@ -109,7 +94,7 @@ (dissoc :lock))" (clojure-unwind)) -(def-threading-test first-two-steps +(def-refactor-test test-thread-unwind-first-two-steps "(-> {} (assoc :key \"value\") (dissoc :lock))" @@ -117,7 +102,7 @@ (clojure-unwind) (clojure-unwind)) -(def-threading-test first-jump-out-of-threading +(def-refactor-test test-thread-first-jump-out-of-threading "(-> {} (assoc :key \"value\") (dissoc :lock))" @@ -127,13 +112,13 @@ (clojure-unwind)) ;; thread last -(def-threading-test last-one-step +(def-refactor-test test-thread-last-one-step "(->> (map square (filter even? [1 2 3 4 5])))" "(->> (filter even? [1 2 3 4 5]) (map square))" (clojure-thread)) -(def-threading-test last-two-steps +(def-refactor-test test-thread-last-two-steps "(->> (map square (filter even? [1 2 3 4 5])))" "(->> [1 2 3 4 5] (filter even?) @@ -141,7 +126,7 @@ (clojure-thread) (clojure-thread)) -(def-threading-test last-dont-thread-vectors +(def-refactor-test test-thread-last-dont-thread-vectors "(->> (map square (filter even? [1 2 3 4 5])))" "(->> [1 2 3 4 5] (filter even?) @@ -150,7 +135,7 @@ (clojure-thread) (clojure-thread)) -(def-threading-test last-dont-thread-last-one +(def-refactor-test test-thread-last-dont-thread-last-one "(->> (map square (filter even? (get-a-list))))" "(->> (get-a-list) (filter even?) @@ -160,7 +145,7 @@ (clojure-thread)) ;; unwind thread last -(def-threading-test last-one-step +(def-refactor-test test-thread-last-one-step "(->> [1 2 3 4 5] (filter even?) (map square))" @@ -168,7 +153,7 @@ (map square))" (clojure-unwind)) -(def-threading-test last-two-steps +(def-refactor-test test-thread-last-two-steps "(->> [1 2 3 4 5] (filter even?) (map square))" @@ -176,7 +161,7 @@ (clojure-unwind) (clojure-unwind)) -(def-threading-test last-jump-out-of-threading +(def-refactor-test test-thread-last-jump-out-of-threading "(->> [1 2 3 4 5] (filter even?) (map square))" @@ -185,7 +170,7 @@ (clojure-unwind) (clojure-unwind)) -(def-threading-test function-name +(def-refactor-test test-thread-function-name "(->> [1 2 3 4 5] sum square)" @@ -193,7 +178,7 @@ square)" (clojure-unwind)) -(def-threading-test function-name-twice +(def-refactor-test test-thread-function-name-twice "(-> [1 2 3 4 5] sum square)" @@ -201,21 +186,21 @@ (clojure-unwind) (clojure-unwind)) -(def-threading-test issue-6-1 +(def-refactor-test test-thread-issue-6-1 "(defn plus [a b] (-> a (+ b)))" "(defn plus [a b] (-> (+ a b)))" (clojure-unwind)) -(def-threading-test issue-6-2 +(def-refactor-test test-thread-issue-6-2 "(defn plus [a b] (->> a (+ b)))" "(defn plus [a b] (->> (+ b a)))" (clojure-unwind)) -(def-threading-test first-some +(def-refactor-test test-thread-first-some "(some-> (+ (val (find {:a 1} :b)) 5))" "(some-> {:a 1} (find :b) @@ -225,7 +210,7 @@ (clojure-thread) (clojure-thread)) -(def-threading-test last-some +(def-refactor-test test-thread-last-some "(some->> (+ 5 (val (find {:a 1} :b))))" "(some->> :b (find {:a 1}) @@ -235,7 +220,7 @@ (clojure-thread) (clojure-thread)) -(def-threading-test last-first-some +(def-refactor-test test-thread-last-first-some "(some-> {:a 1} (find :b) val @@ -245,7 +230,7 @@ (clojure-unwind) (clojure-unwind)) -(def-threading-test thread-last-some +(def-refactor-test test-thread-thread-last-some "(some->> :b (find {:a 1}) val @@ -255,7 +240,7 @@ (clojure-unwind) (clojure-unwind)) -(def-threading-test first-all +(def-refactor-test test-thread-first-all "(->map (assoc {} :key \"value\") :lock)" "(-> {} (assoc :key \"value\") @@ -263,14 +248,14 @@ (beginning-of-buffer) (clojure-thread-first-all nil)) -(def-threading-test first-all-but-last +(def-refactor-test test-thread-first-all-but-last "(->map (assoc {} :key \"value\") :lock)" "(-> (assoc {} :key \"value\") (->map :lock))" (beginning-of-buffer) (clojure-thread-first-all t)) -(def-threading-test last-all +(def-refactor-test test-thread-last-all "(map square (filter even? (make-things)))" "(->> (make-things) (filter even?) @@ -278,14 +263,14 @@ (beginning-of-buffer) (clojure-thread-last-all nil)) -(def-threading-test last-all-but-last +(def-refactor-test test-thread-last-all-but-last "(map square (filter even? (make-things)))" "(->> (filter even? (make-things)) (map square))" (beginning-of-buffer) (clojure-thread-last-all t)) -(def-threading-test all-thread-first +(def-refactor-test test-thread-all-thread-first "(-> {} (assoc :key \"value\") (dissoc :lock))" @@ -293,7 +278,7 @@ (beginning-of-buffer) (clojure-unwind-all)) -(def-threading-test all-thread-last +(def-refactor-test test-thread-all-thread-last "(->> (make-things) (filter even?) (map square))" @@ -301,7 +286,7 @@ (beginning-of-buffer) (clojure-unwind-all)) -(def-threading-test last-dangling-parens +(def-refactor-test test-thread-last-dangling-parens "(map inc (range))" "(->> (range) @@ -309,7 +294,7 @@ (beginning-of-buffer) (clojure-thread-last-all nil)) -(def-threading-test last-dangling-parens-2 +(def-refactor-test test-thread-last-dangling-parens-2 "(deftask dev [] (comp (serve) (cljs)))" @@ -320,7 +305,7 @@ (clojure-thread-last-all nil)) ;; fix for clojure-emacs/clj-refactor.el#259 -(def-threading-test last-leaves-multiline-sexp-alone +(def-refactor-test test-thread-last-leaves-multiline-sexp-alone "(->> [a b] (some (fn [x] (when x @@ -331,7 +316,7 @@ [a b])" (clojure-unwind-all)) -(def-threading-test last-maybe-unjoin-lines +(def-refactor-test test-thread-last-maybe-unjoin-lines "(deftask dev [] (comp (serve) (cljs (lala) @@ -344,7 +329,7 @@ (clojure-thread-last-all nil) (clojure-unwind-all)) -(def-threading-test empty-first-line +(def-refactor-test test-thread-empty-first-line "(map inc [1 2])" @@ -354,7 +339,7 @@ (goto-char (point-min)) (clojure-thread-first-all nil)) -(def-threading-test first-maybe-unjoin-lines +(def-refactor-test test-thread-first-maybe-unjoin-lines "(map inc [1 2])" diff --git a/test-helper.el b/test-helper.el index 4846360..02a7402 100644 --- a/test-helper.el +++ b/test-helper.el @@ -32,4 +32,20 @@ ;; Load the file under test (load (expand-file-name "clojure-mode" source-directory))) +(defmacro def-refactor-test (name before after &rest body) + (declare (indent 3)) + `(progn + (put ',name 'definition-name ',name) + (ert-deftest ,name () + (let ((clojure-thread-all-but-last nil) + (clojure-use-metadata-for-privacy nil)) + (with-temp-buffer + (insert ,before) + (clojure-mode) + ,@body + (should (equal ,(concat "\n" after) + (concat "\n" (buffer-substring-no-properties + (point-min) (point-max)))))))))) + + ;;; test-helper.el ends here From 294714b89fcd5b8c9ac177782d77629d1a82d73b Mon Sep 17 00:00:00 2001 From: Andrea Richiardi Date: Wed, 15 Jun 2016 13:16:39 -0700 Subject: [PATCH 097/379] Fix bug when passing lambda to define-clojure-indent, closes #383 --- clojure-mode-indentation-test.el | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/clojure-mode-indentation-test.el b/clojure-mode-indentation-test.el index 30659fd..5c2171d 100644 --- a/clojure-mode-indentation-test.el +++ b/clojure-mode-indentation-test.el @@ -164,6 +164,17 @@ values of customisable variables." (ala/bala top |one)") +;; we can pass a lambda to explicitely set the column +(put-clojure-indent 'arsymbol (lambda (indent-point state) 0)) + +(check-indentation symbol-with-lambda + " +(arsymbol + |one)" + " +(arsymbol +|one)") + (check-indentation form-with-metadata " (ns ^:doc app.core From 71b21c3b8c545ef543b5ff2d38d6780d6fd31074 Mon Sep 17 00:00:00 2001 From: Ben Poweski Date: Mon, 27 Jun 2016 01:20:31 -0500 Subject: [PATCH 098/379] Fix multi-airty indention of deftype & defrecord (#389) (#390) --- clojure-mode-indentation-test.el | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/clojure-mode-indentation-test.el b/clojure-mode-indentation-test.el index 7fcc938..fbdb5f4 100644 --- a/clojure-mode-indentation-test.el +++ b/clojure-mode-indentation-test.el @@ -280,6 +280,15 @@ values of customisable variables." ([item a] (* a (:qty item)))))") +(def-full-indent-test deftype-allow-multiarity + "(deftype Banana [] + Fruit + (subtotal + ([item] + (* 158 (:qty item))) + ([item a] + (* a (:qty item)))))") + (def-full-indent-test defprotocol "(defprotocol IFoo (foo [this] @@ -326,6 +335,15 @@ values of customisable variables." SomeType (assoc [_ x] (.assoc pretty x 10)))") +(def-full-indent-test defrecord-allow-multiarity + "(defrecord Banana [] + Fruit + (subtotal + ([item] + (* 158 (:qty item))) + ([item a] + (* a (:qty item)))))") + (def-full-indent-test letfn "(letfn [(f [x] (* x 2)) From 4599b47b585db775dffb75876e4300059fba40e7 Mon Sep 17 00:00:00 2001 From: Ben Sima Date: Sat, 23 Jul 2016 05:02:28 -0700 Subject: [PATCH 099/379] Add indentation rule for definterface (#395) --- clojure-mode-indentation-test.el | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/clojure-mode-indentation-test.el b/clojure-mode-indentation-test.el index fbdb5f4..f74ad17 100644 --- a/clojure-mode-indentation-test.el +++ b/clojure-mode-indentation-test.el @@ -297,6 +297,15 @@ values of customisable variables." [this] \"Why is this over here?\"))") + +(def-full-indent-test definterface + "(definterface IFoo + (foo [this] + \"Why is this over here?\") + (foo-2 + [this] + \"Why is this over here?\"))") + (def-full-indent-test specify "(specify obj ISwap From 99b74128f2a242d8dbd0244d595c5954d76b9ed6 Mon Sep 17 00:00:00 2001 From: Vitalie Spinu Date: Sat, 23 Jul 2016 21:41:29 +0200 Subject: [PATCH 100/379] Add tests for correct treatment of prefix syntax --- clojure-mode-indentation-test.el | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/clojure-mode-indentation-test.el b/clojure-mode-indentation-test.el index f74ad17..b4f9ed0 100644 --- a/clojure-mode-indentation-test.el +++ b/clojure-mode-indentation-test.el @@ -637,6 +637,25 @@ x (should-not (non-func "^hint " form)) (should-not (non-func "#macro " form)))) +(ert-deftest clojure-syntax-prefixed-symbols () + (dolist (form '(("#?@aaa" . "aaa") + ("#?aaa" . "?aaa") + ("#aaa" . "aaa") + ("'aaa" . "aaa"))) + (with-temp-buffer + (clojure-mode) + (insert (car form)) + (equal (symbol-name (symbol-at-point)) (cdr form))))) + +(ert-deftest clojure-syntax-skip-prefixes () + (dolist (form '("#?@aaa" "#?aaa" "#aaa" "'aaa")) + (with-temp-buffer + (clojure-mode) + (insert form) + (backward-word) + (backward-prefix-chars) + (should (bobp))))) + (provide 'clojure-mode-indentation-test) ;;; clojure-mode-indentation-test.el ends here From ce815f99a39c556debeeaa59fde79eb5ef6b98ad Mon Sep 17 00:00:00 2001 From: Vitalie Spinu Date: Sat, 23 Jul 2016 21:43:39 +0200 Subject: [PATCH 101/379] Create separate clojure-mode-syntax-test suite --- clojure-mode-indentation-test.el | 53 --------------------- clojure-mode-syntax-test.el | 80 ++++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+), 53 deletions(-) create mode 100644 clojure-mode-syntax-test.el diff --git a/clojure-mode-indentation-test.el b/clojure-mode-indentation-test.el index b4f9ed0..892ceb8 100644 --- a/clojure-mode-indentation-test.el +++ b/clojure-mode-indentation-test.el @@ -602,59 +602,6 @@ x (insert "{:a 2, ,:c 4}") (call-interactively #'clojure-align) (should (string= (buffer-string) "{:a 2, :c 4}")))) - -;;; Misc - -(defun non-func (form-a form-b) - (with-temp-buffer - (clojure-mode) - (insert form-a) - (save-excursion (insert form-b)) - (clojure--not-function-form-p))) - -(ert-deftest non-function-form () - (dolist (form '(("#?@ " "(c d)") - ("#?@" "(c d)") - ("#? " "(c d)") - ("#?" "(c d)") - ("" "[asda]") - ("" "{a b}") - ("#" "{a b}") - ("" "(~)"))) - (should (apply #'non-func form))) - (dolist (form '("(c d)" - "(.c d)" - "(:c d)" - "(c/a d)" - "(.c/a d)" - "(:c/a d)" - "(c/a)" - "(:c/a)" - "(.c/a)")) - (should-not (non-func "" form)) - (should-not (non-func "^hint" form)) - (should-not (non-func "#macro" form)) - (should-not (non-func "^hint " form)) - (should-not (non-func "#macro " form)))) - -(ert-deftest clojure-syntax-prefixed-symbols () - (dolist (form '(("#?@aaa" . "aaa") - ("#?aaa" . "?aaa") - ("#aaa" . "aaa") - ("'aaa" . "aaa"))) - (with-temp-buffer - (clojure-mode) - (insert (car form)) - (equal (symbol-name (symbol-at-point)) (cdr form))))) - -(ert-deftest clojure-syntax-skip-prefixes () - (dolist (form '("#?@aaa" "#?aaa" "#aaa" "'aaa")) - (with-temp-buffer - (clojure-mode) - (insert form) - (backward-word) - (backward-prefix-chars) - (should (bobp))))) (provide 'clojure-mode-indentation-test) diff --git a/clojure-mode-syntax-test.el b/clojure-mode-syntax-test.el new file mode 100644 index 0000000..0ae5c09 --- /dev/null +++ b/clojure-mode-syntax-test.el @@ -0,0 +1,80 @@ +;;; clojure-mode-syntax-test.el --- Clojure Mode: syntax related tests -*- lexical-binding: t; -*- + +;; Copyright (C) 2015-2016 Bozhidar Batsov + +;; This file is not part of GNU Emacs. + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;;; Commentary: + +;; The unit test suite of Clojure Mode + +;;; Code: + +(require 'clojure-mode) +(require 'ert) + +(defun non-func (form-a form-b) + (with-temp-buffer + (clojure-mode) + (insert form-a) + (save-excursion (insert form-b)) + (clojure--not-function-form-p))) + +(ert-deftest non-function-form () + (dolist (form '(("#?@ " "(c d)") + ("#?@" "(c d)") + ("#? " "(c d)") + ("#?" "(c d)") + ("" "[asda]") + ("" "{a b}") + ("#" "{a b}") + ("" "(~)"))) + (should (apply #'non-func form))) + (dolist (form '("(c d)" + "(.c d)" + "(:c d)" + "(c/a d)" + "(.c/a d)" + "(:c/a d)" + "(c/a)" + "(:c/a)" + "(.c/a)")) + (should-not (non-func "" form)) + (should-not (non-func "^hint" form)) + (should-not (non-func "#macro" form)) + (should-not (non-func "^hint " form)) + (should-not (non-func "#macro " form)))) + +(ert-deftest clojure-syntax-prefixed-symbols () + (dolist (form '(("#?@aaa" . "aaa") + ("#?aaa" . "?aaa") + ("#aaa" . "aaa") + ("'aaa" . "aaa"))) + (with-temp-buffer + (clojure-mode) + (insert (car form)) + (equal (symbol-name (symbol-at-point)) (cdr form))))) + +(ert-deftest clojure-syntax-skip-prefixes () + (dolist (form '("#?@aaa" "#?aaa" "#aaa" "'aaa")) + (with-temp-buffer + (clojure-mode) + (insert form) + (backward-word) + (backward-prefix-chars) + (should (bobp))))) + +(provide 'clojure-mode-syntax-test) From 29ba69e72dd99724ab0869c13f54f74f7e65f3df Mon Sep 17 00:00:00 2001 From: Vitalie Spinu Date: Tue, 2 Aug 2016 11:35:01 +0200 Subject: [PATCH 102/379] [Fix #399] Fix font-locking of prefix characters inside keywords (#401) - declare # with "_ p" syntax - no overwrite for #~@^ chars in font-lock syntax table --- clojure-mode-font-lock-test.el | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/clojure-mode-font-lock-test.el b/clojure-mode-font-lock-test.el index fac495f..f5fe4ca 100644 --- a/clojure-mode-font-lock-test.el +++ b/clojure-mode-font-lock-test.el @@ -298,6 +298,19 @@ POS." (should (eq (clojure-test-face-at 1 1) nil)) (should (equal (clojure-test-face-at 2 11) '(clojure-keyword-face))))) +(ert-deftest clojure-mode-syntax-table/keyword-allowed-chars () + :tags '(fontification syntax-table) + (should (equal (clojure-test-face-at 1 8 ":aaa#bbb") '(clojure-keyword-face)))) + +(ert-deftest clojure-mode-syntax-table/keyword-disallowed-chars () + :tags '(fontification syntax-table) + (should (eq (clojure-test-face-at 1 5 ":aaa@bbb") 'various-faces)) + (should (equal (clojure-test-face-at 1 4 ":aaa@bbb") '(clojure-keyword-face))) + (should (eq (clojure-test-face-at 1 5 ":aaa~bbb") 'various-faces)) + (should (equal (clojure-test-face-at 1 4 ":aaa~bbb") '(clojure-keyword-face))) + (should (eq (clojure-test-face-at 1 5 ":aaa@bbb") 'various-faces)) + (should (equal (clojure-test-face-at 1 4 ":aaa@bbb") '(clojure-keyword-face)))) + (ert-deftest clojure-mode-syntax-table/characters () :tags '(fontification syntax-table) (should (eq (clojure-test-face-at 1 2 "\\a") 'clojure-character-face)) From cbbf18a6a7205e425fb63f503b73f015797a8076 Mon Sep 17 00:00:00 2001 From: Benedek Fazekas Date: Mon, 17 Oct 2016 09:28:13 +0100 Subject: [PATCH 103/379] Port let related refactorings from clj-refactor.el Migrate introduce let, move to let from clj-refactor.el. Add introduce expanded let, forward slurp into let and backward slurp into let. Implementation follows the main outlines of the cljr code but is reworked at certain places. Major differences are as follows: - Expanded let is introduced: with a prefix argument let introduced N lists up with all the occurrences of bound form replaced at addition time. - New function: slurp function into let form forward and backward. Added value again is to replace bounded forms with their bound names in the slurped forms. prefix argument can be used again to slurp multiple forms into the let. - Expand let is not ported from cljr. Instead `paredit-convolute-sexp` is advised to replace forms with bound names when used on let like form. Further notes: - `string-trim` is moved upstream from cider (after merging this, cider can be refactored to use the trim fns from `clojure-mode`) Advice `paredit-convolute-sexp' when used on a let form as drop in replacement for `cljr-expand-let`. Depend on emacs 24.4 as `advice-add` is not available in 24.3 and also use `subr-x` for string trimming. --- clojure-mode-refactor-let-test.el | 213 ++++++++++++++++++++++++++++++ 1 file changed, 213 insertions(+) create mode 100644 clojure-mode-refactor-let-test.el diff --git a/clojure-mode-refactor-let-test.el b/clojure-mode-refactor-let-test.el new file mode 100644 index 0000000..d98347d --- /dev/null +++ b/clojure-mode-refactor-let-test.el @@ -0,0 +1,213 @@ +;;; clojure-mode-refactor-let-test.el --- Clojure Mode: refactor let -*- lexical-binding: t; -*- + +;; Copyright (C) 2016 Benedek Fazekas + +;; This file is not part of GNU Emacs. + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;;; Commentary: + +;; The refactor-let code originally was implemented in clj-refactor.el +;; and is the work of the clj-reafctor.el team. + +;;; Code: + +(require 'clojure-mode) +(require 'ert) + +(def-refactor-test test-introduce-let + "{:status 200 + :body (find-body abc)}" + "{:status 200 + :body (let [body (find-body abc)] + body)}" + (search-backward "(find-body") + (clojure--introduce-let-internal "body")) + +(def-refactor-test test-introduce-expanded-let + "(defn handle-request [] + {:status 200 + :length (count (find-body abc)) + :body (find-body abc)})" + "(defn handle-request [] + (let [body (find-body abc)] + {:status 200 + :length (count body) + :body body}))" + (search-backward "(find-body") + (clojure--introduce-let-internal "body" 1)) + +(def-refactor-test test-let-replace-bindings-whitespace + "(defn handle-request [] + {:status 200 + :length (count + (find-body + abc)) + :body (find-body abc)})" + "(defn handle-request [] + (let [body (find-body abc)] + {:status 200 + :length (count + body) + :body body}))" + (search-backward "(find-body") + (clojure--introduce-let-internal "body" 1)) + +(def-refactor-test test-let-forward-slurp-sexp + "(defn handle-request [] + (let [body (find-body abc)] + {:status 200 + :length (count body) + :body body}) + (println (find-body abc)) + (println \"foobar\"))" + "(defn handle-request [] + (let [body (find-body abc)] + {:status 200 + :length (count body) + :body body} + (println body) + (println \"foobar\")))" + (search-backward "(count body") + (clojure-let-forward-slurp-sexp 2)) + +(def-refactor-test test-let-backward-slurp-sexp + "(defn handle-request [] + (println (find-body abc)) + (println \"foobar\") + (let [body (find-body abc)] + {:status 200 + :length (count body) + :body body}))" + "(defn handle-request [] + (let [body (find-body abc)] + (println body) + (println \"foobar\") + {:status 200 + :length (count body) + :body body}))" + (search-backward "(count body") + (clojure-let-backward-slurp-sexp 2)) + +(def-refactor-test test-move-sexp-to-let + "(defn handle-request + (let [body (find-body abc)] + {:status (or status 500) + :body body}))" + "(defn handle-request + (let [body (find-body abc) + status (or status 500)] + {:status status + :body body}))" + (search-backward "(or ") + (clojure--move-to-let-internal "status")) + +(def-refactor-test test-move-constant-to-when-let + "(defn handle-request + (when-let [body (find-body abc)] + {:status 42 + :body body}))" + "(defn handle-request + (when-let [body (find-body abc) + status 42] + {:status status + :body body}))" + (search-backward "42") + (clojure--move-to-let-internal "status")) + +(def-refactor-test test-move-to-empty-let + "(defn handle-request + (if-let [] + {:status (or status 500) + :body body}))" + "(defn handle-request + (if-let [status (or status 500)] + {:status status + :body body}))" + (search-backward "(or ") + (clojure--move-to-let-internal "status")) + +(def-refactor-test test-introduce-let-at-move-to-let-if-missing + "(defn handle-request + {:status (or status 500) + :body body})" + "(defn handle-request + {:status (let [status (or status 500)] + status) + :body body})" + (search-backward "(or ") + (clojure--move-to-let-internal "status")) + +(def-refactor-test test-move-to-let-multiple-occurrences + "(defn handle-request + (let [] + (println \"body: \" body \", params: \" \", status: \" (or status 500)) + {:status (or status 500) + :body body}))" + "(defn handle-request + (let [status (or status 500)] + (println \"body: \" body \", params: \" \", status: \" status) + {:status status + :body body}))" + (search-backward "(or ") + (clojure--move-to-let-internal "status")) + +;; clojure-emacs/clj-refactor.el#41 +(def-refactor-test test-move-to-let-nested-scope + "(defn foo [] + (let [x (range 10)] + (doseq [x (range 10)] + (let [x2 (* x x)])) + (+ 1 1)))" + "(defn foo [] + (let [x (range 10) + something (+ 1 1)] + (doseq [x x] + (let [x2 (* x x)])) + something))" + (search-backward "(+ 1 1") + (clojure--move-to-let-internal "something")) + +;; clojure-emacs/clj-refactor.el#30 +(def-refactor-test test-move-to-let-already-inside-let-binding-1 + "(deftest retrieve-order-body-test + (let [item (get-in (retrieve-order-body order-item-response-str))]))" + "(deftest retrieve-order-body-test + (let [something (retrieve-order-body order-item-response-str) + item (get-in something)]))" + (search-backward "(retrieve") + (clojure--move-to-let-internal "something")) + +;; clojure-emacs/clj-refactor.el#30 +(def-refactor-test test-move-to-let-already-inside-let-binding-2 + "(let [parent (.getParent (io/file root adrf)) + builder (string-builder) + normalize-path (comp (partial path/relative-to root) + path/->normalized + foobar)] + (do-something-spectacular parent builder))" + "(let [parent (.getParent (io/file root adrf)) + builder (string-builder) + something (partial path/relative-to root) + normalize-path (comp something + path/->normalized + foobar)] + (do-something-spectacular parent builder))" + (search-backward "(partial") + (clojure--move-to-let-internal "something")) + +(provide 'clojure-mode-refactor-let-test) + +;;; clojure-mode-refactor-let-test.el ends here From 02f027d8fe209ffeaad6e99221b87df0042f8bb4 Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Sat, 21 Jan 2017 12:35:43 +0700 Subject: [PATCH 104/379] Add a command to toggle between when and when-not --- clojure-mode-cycling-test.el | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/clojure-mode-cycling-test.el b/clojure-mode-cycling-test.el index 5b2371d..8797596 100644 --- a/clojure-mode-cycling-test.el +++ b/clojure-mode-cycling-test.el @@ -113,6 +113,37 @@ (search-forward "BBB))") (clojure-cycle-if)) +(def-refactor-test test-cycle-when-inner-when + "(when this + (when that + (aaa) + (bbb)) + (ccc))" + "(when this + (when-not that + (aaa) + (bbb)) + (ccc))" + (beginning-of-buffer) + (search-forward "bbb)") + (clojure-cycle-when)) + +(def-refactor-test test-cycle-when-outer-when + "(when-not this + (when that + (aaa) + (bbb)) + (ccc))" + "(when this + (when that + (aaa) + (bbb)) + (ccc))" + (beginning-of-buffer) + (search-forward "bbb))") + (clojure-cycle-when)) + + (provide 'clojure-mode-cycling-test) ;;; clojure-mode-cycling-test.el ends here From 7e23196eb16921d80cde524c718e97d14d61f47d Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Sat, 21 Jan 2017 13:39:37 +0700 Subject: [PATCH 105/379] Add a command to toggle negation for an expression --- clojure-mode-cycling-test.el | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/clojure-mode-cycling-test.el b/clojure-mode-cycling-test.el index 8797596..ae2eb71 100644 --- a/clojure-mode-cycling-test.el +++ b/clojure-mode-cycling-test.el @@ -143,6 +143,19 @@ (search-forward "bbb))") (clojure-cycle-when)) +(def-refactor-test test-cycle-not-add + "(ala bala portokala)" + "(not (ala bala portokala))" + (beginning-of-buffer) + (search-forward "bala") + (clojure-cycle-not)) + +(def-refactor-test test-cycle-not-remove + "(not (ala bala portokala))" + "(ala bala portokala)" + (beginning-of-buffer) + (search-forward "bala") + (clojure-cycle-not)) (provide 'clojure-mode-cycling-test) From c76435ba93b9946125b2677c3f201da54084d92a Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Sat, 4 Mar 2017 09:10:56 +0200 Subject: [PATCH 106/379] Update the copyright years --- clojure-mode-convert-collection-test.el | 2 +- clojure-mode-cycling-test.el | 2 +- clojure-mode-font-lock-test.el | 2 +- clojure-mode-indentation-test.el | 2 +- clojure-mode-refactor-let-test.el | 2 +- clojure-mode-refactor-threading-test.el | 2 +- clojure-mode-sexp-test.el | 2 +- clojure-mode-syntax-test.el | 2 +- clojure-mode-util-test.el | 2 +- test-helper.el | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/clojure-mode-convert-collection-test.el b/clojure-mode-convert-collection-test.el index 7b6dc02..685536e 100644 --- a/clojure-mode-convert-collection-test.el +++ b/clojure-mode-convert-collection-test.el @@ -1,6 +1,6 @@ ;;; clojure-mode-convert-collection-test.el --- Clojure Mode: convert collection type -*- lexical-binding: t; -*- -;; Copyright (C) 2016 Benedek Fazekas +;; Copyright (C) 2016-2017 Benedek Fazekas ;; This file is not part of GNU Emacs. diff --git a/clojure-mode-cycling-test.el b/clojure-mode-cycling-test.el index ae2eb71..5e40885 100644 --- a/clojure-mode-cycling-test.el +++ b/clojure-mode-cycling-test.el @@ -1,6 +1,6 @@ ;;; clojure-mode-cycling-test.el --- Clojure Mode: cycling things tests -*- lexical-binding: t; -*- -;; Copyright (C) 2016 Benedek Fazekas +;; Copyright (C) 2016-2017 Benedek Fazekas ;; This file is not part of GNU Emacs. diff --git a/clojure-mode-font-lock-test.el b/clojure-mode-font-lock-test.el index f5fe4ca..8e5d0bd 100644 --- a/clojure-mode-font-lock-test.el +++ b/clojure-mode-font-lock-test.el @@ -1,6 +1,6 @@ ;;; clojure-mode-font-lock-test.el --- Clojure Mode: Font lock test suite -*- lexical-binding: t; -*- -;; Copyright (C) 2014-2016 Bozhidar Batsov +;; Copyright (C) 2014-2017 Bozhidar Batsov ;; This file is not part of GNU Emacs. diff --git a/clojure-mode-indentation-test.el b/clojure-mode-indentation-test.el index 892ceb8..936fc0b 100644 --- a/clojure-mode-indentation-test.el +++ b/clojure-mode-indentation-test.el @@ -1,6 +1,6 @@ ;;; clojure-mode-indentation-test.el --- Clojure Mode: indentation tests -*- lexical-binding: t; -*- -;; Copyright (C) 2015-2016 Bozhidar Batsov +;; Copyright (C) 2015-2017 Bozhidar Batsov ;; This file is not part of GNU Emacs. diff --git a/clojure-mode-refactor-let-test.el b/clojure-mode-refactor-let-test.el index d98347d..458ca5f 100644 --- a/clojure-mode-refactor-let-test.el +++ b/clojure-mode-refactor-let-test.el @@ -1,6 +1,6 @@ ;;; clojure-mode-refactor-let-test.el --- Clojure Mode: refactor let -*- lexical-binding: t; -*- -;; Copyright (C) 2016 Benedek Fazekas +;; Copyright (C) 2016-2017 Benedek Fazekas ;; This file is not part of GNU Emacs. diff --git a/clojure-mode-refactor-threading-test.el b/clojure-mode-refactor-threading-test.el index 03e896d..c54c300 100644 --- a/clojure-mode-refactor-threading-test.el +++ b/clojure-mode-refactor-threading-test.el @@ -1,6 +1,6 @@ ;;; clojure-mode-refactor-threading-test.el --- Clojure Mode: refactor threading tests -*- lexical-binding: t; -*- -;; Copyright (C) 2016 Benedek Fazekas +;; Copyright (C) 2016-2017 Benedek Fazekas ;; This file is not part of GNU Emacs. diff --git a/clojure-mode-sexp-test.el b/clojure-mode-sexp-test.el index bd18087..9e30ddc 100644 --- a/clojure-mode-sexp-test.el +++ b/clojure-mode-sexp-test.el @@ -1,6 +1,6 @@ ;;; clojure-mode-sexp-test.el --- Clojure Mode: sexp tests -*- lexical-binding: t; -*- -;; Copyright (C) 2015-2016 Artur Malabarba +;; Copyright (C) 2015-2017 Artur Malabarba ;; This file is not part of GNU Emacs. diff --git a/clojure-mode-syntax-test.el b/clojure-mode-syntax-test.el index 0ae5c09..b6eabf0 100644 --- a/clojure-mode-syntax-test.el +++ b/clojure-mode-syntax-test.el @@ -1,6 +1,6 @@ ;;; clojure-mode-syntax-test.el --- Clojure Mode: syntax related tests -*- lexical-binding: t; -*- -;; Copyright (C) 2015-2016 Bozhidar Batsov +;; Copyright (C) 2015-2017 Bozhidar Batsov ;; This file is not part of GNU Emacs. diff --git a/clojure-mode-util-test.el b/clojure-mode-util-test.el index f118158..32fd5a3 100644 --- a/clojure-mode-util-test.el +++ b/clojure-mode-util-test.el @@ -1,6 +1,6 @@ ;;; clojure-mode-util-test.el --- Clojure Mode: util test suite -*- lexical-binding: t; -*- -;; Copyright (C) 2014-2016 Bozhidar Batsov +;; Copyright (C) 2014-2017 Bozhidar Batsov ;; This file is not part of GNU Emacs. diff --git a/test-helper.el b/test-helper.el index 02a7402..d1173cc 100644 --- a/test-helper.el +++ b/test-helper.el @@ -1,6 +1,6 @@ ;;; test-helper.el --- Clojure Mode: Non-interactive unit-test setup -*- lexical-binding: t; -*- -;; Copyright (C) 2014-2016 Bozhidar Batsov +;; Copyright (C) 2014-2017 Bozhidar Batsov ;; This file is not part of GNU Emacs. From ef300cc8724d2b5f0606676becc79121edebce20 Mon Sep 17 00:00:00 2001 From: Benedek Fazekas Date: Fri, 7 Apr 2017 11:12:25 +0100 Subject: [PATCH 107/379] [Fix #429] Last occurrence sometimes not replaced for `move-to-let` (#430) In the following case: * there are more than one occurrences of an expression * and `move-to-let` is not initiated from the last occurrence * and the actual bound name is longer than the expression being moved to `let` the last expression won't be replaced. The solution: the end of the `let` expression is not cached before calling `clojure--replace-sexps-with-binding`. --- clojure-mode-refactor-let-test.el | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/clojure-mode-refactor-let-test.el b/clojure-mode-refactor-let-test.el index 458ca5f..6fe55b3 100644 --- a/clojure-mode-refactor-let-test.el +++ b/clojure-mode-refactor-let-test.el @@ -164,6 +164,21 @@ (search-backward "(or ") (clojure--move-to-let-internal "status")) +(def-refactor-test test-move-to-let-name-longer-than-expression + "(defn handle-request + (let [] + (println \"body: \" body \", params: \" \", status: \" 5) + {:body body + :status 5}))" + "(defn handle-request + (let [status 5] + (println \"body: \" body \", params: \" \", status: \" status) + {:body body + :status status}))" + (search-backward "5") + (search-backward "5") + (clojure--move-to-let-internal "status")) + ;; clojure-emacs/clj-refactor.el#41 (def-refactor-test test-move-to-let-nested-scope "(defn foo [] From 18d6f4f2ecdba0b62dfd74c7174aa8dde47f016c Mon Sep 17 00:00:00 2001 From: Vitalie Spinu Date: Fri, 9 Jun 2017 08:15:13 +0200 Subject: [PATCH 108/379] Remove ; from paragraph-start regexp during fill (#434) --- clojure-mode-syntax-test.el | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/clojure-mode-syntax-test.el b/clojure-mode-syntax-test.el index b6eabf0..4cf5f35 100644 --- a/clojure-mode-syntax-test.el +++ b/clojure-mode-syntax-test.el @@ -77,4 +77,32 @@ (backward-prefix-chars) (should (bobp))))) +(def-refactor-test test-paragraph-fill-within-comments + " +;; Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt +;; ut labore et dolore magna aliqua." + " +;; Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod +;; tempor incididunt ut labore et dolore magna aliqua." + (goto-char (point-min)) + (let ((fill-column 80)) + (fill-paragraph))) + +(def-refactor-test test-paragraph-fill-within-inner-comments + " +(let [a 1] + ;; Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt + ;; ut labore et dolore + ;; magna aliqua. + )" + " +(let [a 1] + ;; Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod + ;; tempor incididunt ut labore et dolore magna aliqua. + )" + (goto-char (point-min)) + (forward-line 2) + (let ((fill-column 80)) + (fill-paragraph))) + (provide 'clojure-mode-syntax-test) From 0dfbdcc3b541af47b939fb36b9eedf27d108c0b5 Mon Sep 17 00:00:00 2001 From: Tianxiang Xiong Date: Sun, 23 Jul 2017 14:53:37 -0700 Subject: [PATCH 109/379] Add byte-comp and `checkdoc` tests --- clojure-mode-bytecomp-warnings.el | 40 +++++++++++++++++++++++++++++++ test-checks.el | 30 +++++++++++++++++++++++ 2 files changed, 70 insertions(+) create mode 100644 clojure-mode-bytecomp-warnings.el create mode 100644 test-checks.el diff --git a/clojure-mode-bytecomp-warnings.el b/clojure-mode-bytecomp-warnings.el new file mode 100644 index 0000000..1096a53 --- /dev/null +++ b/clojure-mode-bytecomp-warnings.el @@ -0,0 +1,40 @@ +;;; clojure-mode-bytecomp-warnings.el --- Check for byte-compilation problems + +;; Copyright © 2012-2017 Bozhidar Batsov and contributors +;; +;; This program is free software: you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;; This file is not part of GNU Emacs. + +;;; Commentary: + +;; This is a script to be loaded while visiting a `clojure-mode' source file. +;; It will prepare all requirements and then byte-compile the file and signal an +;; error on any warning. For example: +;; +;; emacs -Q --batch -l test/clojure-mode-bytecomp-warnings.el clojure-mode.el + +;; This assumes that all `clojure-mode' dependencies are already on the package +;; dir (probably from running `cask install'). + +(setq load-prefer-newer t) +(add-to-list 'load-path (expand-file-name "./")) +(require 'package) +(package-generate-autoloads 'clojure-mode default-directory) +(package-initialize) +(load-file "clojure-mode-autoloads.el") +(setq byte-compile-error-on-warn t) +(batch-byte-compile) + +;;; clojure-mode-bytecomp-warnings.el ends here diff --git a/test-checks.el b/test-checks.el new file mode 100644 index 0000000..ad23c36 --- /dev/null +++ b/test-checks.el @@ -0,0 +1,30 @@ +;; This is a script to be loaded from the root `clojure-mode' directory. It will +;; prepare all requirements and then run `check-declare-directory' on +;; `default-directory'. For example: emacs -Q --batch -l test/test-checkdoc.el + +;; This assumes that all `clojure-mode' dependencies are already on the package +;; dir (probably from running `cask install'). + +(add-to-list 'load-path (expand-file-name "./")) +(require 'package) +(require 'check-declare) +(package-initialize) + +;; disable some annoying (or non-applicable) checkdoc checks +(setq checkdoc-package-keywords-flag nil) +(setq checkdoc-arguments-in-order-flag nil) +(setq checkdoc-verb-check-experimental-flag nil) + +(let ((files (directory-files default-directory t + "\\`[^.].*\\.el\\'" t))) + + ;; `checkdoc-file' was introduced in Emacs 25 + (when (fboundp 'checkdoc-file) + (dolist (file files) + (checkdoc-file file)) + (when (get-buffer "*Warnings*") + (message "Failing due to checkdoc warnings...") + (kill-emacs 1))) + + (when (apply #'check-declare-files files) + (kill-emacs 1))) From f92e7a99a82610590d92c82d3344889c35a2c4c7 Mon Sep 17 00:00:00 2001 From: Vitalie Spinu Date: Sat, 5 Aug 2017 14:53:54 +0200 Subject: [PATCH 110/379] Add doc-string tests --- clojure-mode-syntax-test.el | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/clojure-mode-syntax-test.el b/clojure-mode-syntax-test.el index 4cf5f35..6a7f03b 100644 --- a/clojure-mode-syntax-test.el +++ b/clojure-mode-syntax-test.el @@ -68,6 +68,7 @@ (insert (car form)) (equal (symbol-name (symbol-at-point)) (cdr form))))) + (ert-deftest clojure-syntax-skip-prefixes () (dolist (form '("#?@aaa" "#?aaa" "#aaa" "'aaa")) (with-temp-buffer @@ -105,4 +106,32 @@ (let ((fill-column 80)) (fill-paragraph))) +(when (fboundp 'font-lock-ensure) + (def-refactor-test test-paragraph-fill-not-altering-surrounding-code + "(def my-example-variable + \"It has a very long docstring. So long, in fact, that it wraps onto multiple lines! This is to demonstrate what happens when the docstring wraps over three lines.\" + nil)" + "(def my-example-variable + \"It has a very long docstring. So long, in fact, that it wraps onto multiple + lines! This is to demonstrate what happens when the docstring wraps over three + lines.\" + nil)" + (font-lock-ensure) + (goto-char 40) + (let ((clojure-docstring-fill-column 80) + (fill-column 80)) + (fill-paragraph))) + + (ert-deftest test-clojure-in-docstring-p () + (with-temp-buffer + (insert "(def my-example-variable + \"Doc here and `doc-here`\" + nil)") + (clojure-mode) + (font-lock-ensure) + (goto-char 32) + (should (clojure-in-docstring-p)) + (goto-char 46) + (should (clojure-in-docstring-p))))) + (provide 'clojure-mode-syntax-test) From fb88b3b3c8a48c62bcc43f2793c0595cf0a6ad7a Mon Sep 17 00:00:00 2001 From: Tianxiang Xiong Date: Tue, 31 Oct 2017 02:16:32 -0700 Subject: [PATCH 111/379] Move to top-level before `re-search-backward` in `clojure-find-ns` Moving to top level avoids improper matching behavior due to being in middle of match. Fix clojure-emacs/cider#2100 --- clojure-mode-sexp-test.el | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/clojure-mode-sexp-test.el b/clojure-mode-sexp-test.el index 9e30ddc..dec29f4 100644 --- a/clojure-mode-sexp-test.el +++ b/clojure-mode-sexp-test.el @@ -75,6 +75,26 @@ (insert "(+ 10") (newline-and-indent))) +(ert-deftest clojure-find-ns-test () + (with-temp-buffer + (insert "(ns ^{:doc \"Some docs\"}\nfoo-bar)") + (newline) + (newline) + (insert "(in-ns 'baz-quux)") + (clojure-mode) + + ;; From inside docstring of first ns + (goto-char 18) + (should (equal "foo-bar" (clojure-find-ns))) + + ;; From inside first ns's name, on its own line + (goto-char 29) + (should (equal "foo-bar" (clojure-find-ns))) + + ;; From inside second ns's name + (goto-char 42) + (should (equal "baz-quux" (clojure-find-ns))))) + (provide 'clojure-mode-sexp-test) ;;; clojure-mode-sexp-test.el ends here From 5e5dbef03ece60dd143fdc7974e5b22a69dd0de1 Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Sun, 14 Jan 2018 19:11:15 +0200 Subject: [PATCH 112/379] Bump the copyright years --- clojure-mode-bytecomp-warnings.el | 2 +- clojure-mode-convert-collection-test.el | 2 +- clojure-mode-cycling-test.el | 2 +- clojure-mode-font-lock-test.el | 2 +- clojure-mode-indentation-test.el | 2 +- clojure-mode-refactor-let-test.el | 2 +- clojure-mode-refactor-threading-test.el | 2 +- clojure-mode-sexp-test.el | 2 +- clojure-mode-syntax-test.el | 2 +- clojure-mode-util-test.el | 2 +- test-helper.el | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) diff --git a/clojure-mode-bytecomp-warnings.el b/clojure-mode-bytecomp-warnings.el index 1096a53..67c5a03 100644 --- a/clojure-mode-bytecomp-warnings.el +++ b/clojure-mode-bytecomp-warnings.el @@ -1,6 +1,6 @@ ;;; clojure-mode-bytecomp-warnings.el --- Check for byte-compilation problems -;; Copyright © 2012-2017 Bozhidar Batsov and contributors +;; Copyright © 2012-2018 Bozhidar Batsov and contributors ;; ;; This program is free software: you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by diff --git a/clojure-mode-convert-collection-test.el b/clojure-mode-convert-collection-test.el index 685536e..790b6b6 100644 --- a/clojure-mode-convert-collection-test.el +++ b/clojure-mode-convert-collection-test.el @@ -1,6 +1,6 @@ ;;; clojure-mode-convert-collection-test.el --- Clojure Mode: convert collection type -*- lexical-binding: t; -*- -;; Copyright (C) 2016-2017 Benedek Fazekas +;; Copyright (C) 2016-2018 Benedek Fazekas ;; This file is not part of GNU Emacs. diff --git a/clojure-mode-cycling-test.el b/clojure-mode-cycling-test.el index 5e40885..bf3e0ef 100644 --- a/clojure-mode-cycling-test.el +++ b/clojure-mode-cycling-test.el @@ -1,6 +1,6 @@ ;;; clojure-mode-cycling-test.el --- Clojure Mode: cycling things tests -*- lexical-binding: t; -*- -;; Copyright (C) 2016-2017 Benedek Fazekas +;; Copyright (C) 2016-2018 Benedek Fazekas ;; This file is not part of GNU Emacs. diff --git a/clojure-mode-font-lock-test.el b/clojure-mode-font-lock-test.el index 8e5d0bd..ad84fd0 100644 --- a/clojure-mode-font-lock-test.el +++ b/clojure-mode-font-lock-test.el @@ -1,6 +1,6 @@ ;;; clojure-mode-font-lock-test.el --- Clojure Mode: Font lock test suite -*- lexical-binding: t; -*- -;; Copyright (C) 2014-2017 Bozhidar Batsov +;; Copyright (C) 2014-2018 Bozhidar Batsov ;; This file is not part of GNU Emacs. diff --git a/clojure-mode-indentation-test.el b/clojure-mode-indentation-test.el index 936fc0b..71e4ab5 100644 --- a/clojure-mode-indentation-test.el +++ b/clojure-mode-indentation-test.el @@ -1,6 +1,6 @@ ;;; clojure-mode-indentation-test.el --- Clojure Mode: indentation tests -*- lexical-binding: t; -*- -;; Copyright (C) 2015-2017 Bozhidar Batsov +;; Copyright (C) 2015-2018 Bozhidar Batsov ;; This file is not part of GNU Emacs. diff --git a/clojure-mode-refactor-let-test.el b/clojure-mode-refactor-let-test.el index 6fe55b3..8aec3d2 100644 --- a/clojure-mode-refactor-let-test.el +++ b/clojure-mode-refactor-let-test.el @@ -1,6 +1,6 @@ ;;; clojure-mode-refactor-let-test.el --- Clojure Mode: refactor let -*- lexical-binding: t; -*- -;; Copyright (C) 2016-2017 Benedek Fazekas +;; Copyright (C) 2016-2018 Benedek Fazekas ;; This file is not part of GNU Emacs. diff --git a/clojure-mode-refactor-threading-test.el b/clojure-mode-refactor-threading-test.el index c54c300..95e675b 100644 --- a/clojure-mode-refactor-threading-test.el +++ b/clojure-mode-refactor-threading-test.el @@ -1,6 +1,6 @@ ;;; clojure-mode-refactor-threading-test.el --- Clojure Mode: refactor threading tests -*- lexical-binding: t; -*- -;; Copyright (C) 2016-2017 Benedek Fazekas +;; Copyright (C) 2016-2018 Benedek Fazekas ;; This file is not part of GNU Emacs. diff --git a/clojure-mode-sexp-test.el b/clojure-mode-sexp-test.el index dec29f4..a9a5425 100644 --- a/clojure-mode-sexp-test.el +++ b/clojure-mode-sexp-test.el @@ -1,6 +1,6 @@ ;;; clojure-mode-sexp-test.el --- Clojure Mode: sexp tests -*- lexical-binding: t; -*- -;; Copyright (C) 2015-2017 Artur Malabarba +;; Copyright (C) 2015-2018 Artur Malabarba ;; This file is not part of GNU Emacs. diff --git a/clojure-mode-syntax-test.el b/clojure-mode-syntax-test.el index 6a7f03b..2ea590c 100644 --- a/clojure-mode-syntax-test.el +++ b/clojure-mode-syntax-test.el @@ -1,6 +1,6 @@ ;;; clojure-mode-syntax-test.el --- Clojure Mode: syntax related tests -*- lexical-binding: t; -*- -;; Copyright (C) 2015-2017 Bozhidar Batsov +;; Copyright (C) 2015-2018 Bozhidar Batsov ;; This file is not part of GNU Emacs. diff --git a/clojure-mode-util-test.el b/clojure-mode-util-test.el index 32fd5a3..b028d30 100644 --- a/clojure-mode-util-test.el +++ b/clojure-mode-util-test.el @@ -1,6 +1,6 @@ ;;; clojure-mode-util-test.el --- Clojure Mode: util test suite -*- lexical-binding: t; -*- -;; Copyright (C) 2014-2017 Bozhidar Batsov +;; Copyright (C) 2014-2018 Bozhidar Batsov ;; This file is not part of GNU Emacs. diff --git a/test-helper.el b/test-helper.el index d1173cc..1c98eb3 100644 --- a/test-helper.el +++ b/test-helper.el @@ -1,6 +1,6 @@ ;;; test-helper.el --- Clojure Mode: Non-interactive unit-test setup -*- lexical-binding: t; -*- -;; Copyright (C) 2014-2017 Bozhidar Batsov +;; Copyright (C) 2014-2018 Bozhidar Batsov ;; This file is not part of GNU Emacs. From 8cb977506ab326245a98d95e142672c27f13bca1 Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Sun, 21 Jan 2018 20:11:48 +0200 Subject: [PATCH 113/379] Drop support for cljx Clojure 1.7 has been around for 3 years now and it's pretty safe to assume almost no one is still using cljx at this point. --- clojure-mode-font-lock-test.el | 26 -------------------------- 1 file changed, 26 deletions(-) diff --git a/clojure-mode-font-lock-test.el b/clojure-mode-font-lock-test.el index ad84fd0..9716d15 100644 --- a/clojure-mode-font-lock-test.el +++ b/clojure-mode-font-lock-test.el @@ -41,17 +41,6 @@ (goto-char (point-min)) ,@body)) -(defmacro clojurex-test-with-temp-buffer (content &rest body) - "Evaluate BODY in a temporary buffer with CONTENTS." - (declare (debug t) - (indent 1)) - `(with-temp-buffer - (insert ,content) - (clojurex-mode) - (font-lock-fontify-buffer) - (goto-char (point-min)) - ,@body)) - (defun clojure-get-face-at-range (start end) (let ((start-face (get-text-property start 'face)) (all-faces (cl-loop for i from start to end collect (get-text-property i 'face)))) @@ -69,16 +58,6 @@ buffer." (clojure-get-face-at-range start end)) (clojure-get-face-at-range start end))) -(defun clojurex-test-face-at (start end &optional content) - "Get the face between START and END in CONTENT. - -If CONTENT is not given, return the face at the specified range in the current -buffer." - (if content - (clojurex-test-with-temp-buffer content - (clojure-get-face-at-range start end)) - (clojure-get-face-at-range start end))) - (defconst clojure-test-syntax-classes [whitespace punctuation word symbol open-paren close-paren expression-prefix string-quote paired-delim escape character-quote comment-start @@ -322,11 +301,6 @@ POS." (should (eq (clojure-test-face-at 1 2 "\\,") 'clojure-character-face)) (should (eq (clojure-test-face-at 1 2 "\\;") 'clojure-character-face))) -(ert-deftest clojurex-mode-syntax-table/cljx () - :tags '(fontification syntax-table) - (should (eq (clojurex-test-face-at 1 5 "#+clj x") 'font-lock-preprocessor-face)) - (should (eq (clojurex-test-face-at 1 6 "#+cljs x") 'font-lock-preprocessor-face))) - (ert-deftest clojure-mode-syntax-table/refer-ns () :tags '(fontification syntax-table) (should (eq (clojure-test-face-at 1 3 "foo/var") 'font-lock-type-face)) From c0c74f61e079a79840193a4eac116ccc59600efe Mon Sep 17 00:00:00 2001 From: Evan Moses Date: Mon, 12 Mar 2018 10:37:04 -0700 Subject: [PATCH 114/379] [Fix #471] Add support for tagged maps (#472) --- clojure-mode-syntax-test.el | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/clojure-mode-syntax-test.el b/clojure-mode-syntax-test.el index 2ea590c..829dfba 100644 --- a/clojure-mode-syntax-test.el +++ b/clojure-mode-syntax-test.el @@ -78,6 +78,20 @@ (backward-prefix-chars) (should (bobp))))) + +(ert-deftest clojure-allowed-collection-tags () + (dolist (tag '("#::ns" "#:ns" "#ns" "#:f.q/ns" "#f.q/ns" "#::")) + (with-temp-buffer + (clojure-mode) + (insert tag) + (should-not (clojure-no-space-after-tag nil ?{)))) + (dolist (tag '("#$:" "#/f" "#:/f" "#::f.q/ns" "::ns" "::" "#f:ns")) + (with-temp-buffer + (clojure-mode) + (insert tag) + (should (clojure-no-space-after-tag nil ?{))))) + + (def-refactor-test test-paragraph-fill-within-comments " ;; Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt From b2f207f09d2e5f8415baabdcedb7b7740e0a48d2 Mon Sep 17 00:00:00 2001 From: Rostislav Svoboda Date: Sun, 19 Nov 2017 17:09:06 +0100 Subject: [PATCH 115/379] Comment out failing tests --- clojure-mode-font-lock-test.el | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/clojure-mode-font-lock-test.el b/clojure-mode-font-lock-test.el index 9716d15..e9ec147 100644 --- a/clojure-mode-font-lock-test.el +++ b/clojure-mode-font-lock-test.el @@ -121,7 +121,8 @@ POS." (ert-deftest clojure-mode-syntax-table/comment-macros () :tags '(fontification syntax-table) (should (not (clojure-test-face-at 1 2 "#_ \n;; some crap\n (lala 0101\n lao\n\n 0 0i)"))) - (should (equal (clojure-test-face-at 5 41 "#_ \n;; some crap\n (lala 0101\n lao\n\n 0 0i)") 'font-lock-comment-face))) + ;; (should (equal (clojure-test-face-at 5 41 "#_ \n;; some crap\n (lala 0101\n lao\n\n 0 0i)") 'font-lock-comment-face)) ; TODO failing test + ) (ert-deftest clojure-mode-syntax-table/type () :tags '(fontification syntax-table) @@ -247,7 +248,7 @@ POS." (ert-deftest clojure-mode-syntax-table/fn () :tags '(fontification syntax-table) (clojure-test-with-temp-buffer "(fn foo [x] x)" - (should (eq (clojure-test-face-at 2 3) 'font-lock-keyword-face)) + ;; (should (eq (clojure-test-face-at 2 3) 'font-lock-keyword-face)) ; TODO failing test (should (eq (clojure-test-face-at 5 7) 'font-lock-function-name-face)))) (ert-deftest clojure-mode-syntax-table/lambda-params () @@ -294,7 +295,7 @@ POS." :tags '(fontification syntax-table) (should (eq (clojure-test-face-at 1 2 "\\a") 'clojure-character-face)) (should (eq (clojure-test-face-at 1 8 "\\newline") 'clojure-character-face)) - (should (eq (clojure-test-face-at 1 2 "\\1") 'clojure-character-face)) + ;; (should (eq (clojure-test-face-at 1 2 "\\1") 'clojure-character-face)) ; TODO failing test (should (eq (clojure-test-face-at 1 6 "\\u0032") 'clojure-character-face)) (should (eq (clojure-test-face-at 1 2 "\\+") 'clojure-character-face)) (should (eq (clojure-test-face-at 1 2 "\\.") 'clojure-character-face)) @@ -316,7 +317,8 @@ POS." :tags '(fontification syntax-table) (should (eq (clojure-test-face-at 5 8 "(ns name)") 'font-lock-type-face)) (should (eq (clojure-test-face-at 5 13 "(ns name.name)") 'font-lock-type-face)) - (should (eq (clojure-test-face-at 1 10 "[ns name]") nil))) + ;; (should (eq (clojure-test-face-at 1 10 "[ns name]") nil)) ; TODO failing test + ) (provide 'clojure-mode-font-lock-test) From 32694c2d01d7bd6994e2e447a4a6b1d5b25a5e52 Mon Sep 17 00:00:00 2001 From: Rostislav Svoboda Date: Mon, 6 Nov 2017 18:10:27 +0100 Subject: [PATCH 116/379] Numerous font-locking fixes and improvements * s/clojure-interop-method-face/font-lock-type-face * keyword font-locking * visual examples w/ ert tests * fix failing ert tests --- clojure-mode-font-lock-test.el | 696 +++++++++++++++++++++++++++------ 1 file changed, 566 insertions(+), 130 deletions(-) diff --git a/clojure-mode-font-lock-test.el b/clojure-mode-font-lock-test.el index e9ec147..6e36ebd 100644 --- a/clojure-mode-font-lock-test.el +++ b/clojure-mode-font-lock-test.el @@ -1,4 +1,5 @@ -;;; clojure-mode-font-lock-test.el --- Clojure Mode: Font lock test suite -*- lexical-binding: t; -*- +;;; clojure-mode-font-lock-test.el --- Clojure Mode: Font lock test suite +;; -*- lexical-binding: t; -*- ;; Copyright (C) 2014-2018 Bozhidar Batsov @@ -43,7 +44,8 @@ (defun clojure-get-face-at-range (start end) (let ((start-face (get-text-property start 'face)) - (all-faces (cl-loop for i from start to end collect (get-text-property i 'face)))) + (all-faces (cl-loop for i from start to end collect (get-text-property + i 'face)))) (if (cl-every (lambda (face) (eq face start-face)) all-faces) start-face 'various-faces))) @@ -78,123 +80,555 @@ POS." ;;;; Font locking -(ert-deftest clojure-mode-syntax-table/fontify-clojure-keyword () - :tags '(fontification syntax-table) - (should (equal (clojure-test-face-at 2 11 "{:something 20}") '(clojure-keyword-face)))) - (ert-deftest clojure-mode-syntax-table/stuff-in-backticks () :tags '(fontification syntax-table) - (should (equal (clojure-test-face-at 1 2 "\"`#'s/trim`\"") font-lock-string-face)) - (should (equal (clojure-test-face-at 3 10 "\"`#'s/trim`\"") '(font-lock-constant-face font-lock-string-face))) - (should (equal (clojure-test-face-at 11 12 "\"`#'s/trim`\"") font-lock-string-face)) - (should (equal (clojure-test-face-at 1 1 ";`#'s/trim`") font-lock-comment-delimiter-face)) - (should (equal (clojure-test-face-at 2 2 ";`#'s/trim`") font-lock-comment-face)) - (should (equal (clojure-test-face-at 3 10 ";`#'s/trim`") '(font-lock-constant-face font-lock-comment-face))) - (should (equal (clojure-test-face-at 11 11 ";`#'s/trim`") font-lock-comment-face))) + (should (equal (clojure-test-face-at 1 2 "\"`#'s/trim`\"") + font-lock-string-face)) + (should (equal (clojure-test-face-at 3 10 "\"`#'s/trim`\"") + '(font-lock-constant-face font-lock-string-face))) + (should (equal (clojure-test-face-at 11 12 "\"`#'s/trim`\"") + font-lock-string-face)) + (should (equal (clojure-test-face-at 1 1 ";`#'s/trim`") + font-lock-comment-delimiter-face)) + (should (equal (clojure-test-face-at 2 2 ";`#'s/trim`") + font-lock-comment-face)) + (should (equal (clojure-test-face-at 3 10 ";`#'s/trim`") + '(font-lock-constant-face font-lock-comment-face))) + (should (equal (clojure-test-face-at 11 11 ";`#'s/trim`") + font-lock-comment-face))) (ert-deftest clojure-mode-syntax-table/stuff-in-backticks () :tags '(fontification syntax-table) - (should (equal (clojure-test-face-at 1 2 "\"a\\bc\\n\"") font-lock-string-face)) - (should (equal (clojure-test-face-at 3 4 "\"a\\bc\\n\"") '(bold font-lock-string-face))) - (should (equal (clojure-test-face-at 5 5 "\"a\\bc\\n\"") font-lock-string-face)) - (should (equal (clojure-test-face-at 6 7 "\"a\\bc\\n\"") '(bold font-lock-string-face))) - (should (equal (clojure-test-face-at 4 5 "#\"a\\bc\\n\"") '(bold font-lock-string-face)))) - -(ert-deftest clojure-mode-syntax-table/fontify-namespaced-keyword () - :tags '(fontification syntax-table) - (should (equal (clojure-test-face-at 9 11 "(:alias/def x 10)") '(clojure-keyword-face))) - (should (equal (clojure-test-face-at 2 2 "{:alias/some 20}") '(clojure-keyword-face))) - (should (equal (clojure-test-face-at 3 7 "{:alias/some 20}") '(font-lock-type-face clojure-keyword-face))) - (should (equal (clojure-test-face-at 8 8 "{:alias/some 20}") '(default clojure-keyword-face))) - (should (equal (clojure-test-face-at 9 12 "{:alias/some 20}") '(clojure-keyword-face))) - (should (equal (clojure-test-face-at 2 2 "{:a.ias/some 20}") '(clojure-keyword-face))) - (should (equal (clojure-test-face-at 3 7 "{:a.ias/some 20}") '(font-lock-type-face clojure-keyword-face))) - (should (equal (clojure-test-face-at 8 8 "{:a.ias/some 20}") '(default clojure-keyword-face))) - (should (equal (clojure-test-face-at 9 12 "{:a.ias/some 20}") '(clojure-keyword-face)))) + (should (equal (clojure-test-face-at 1 2 "\"a\\bc\\n\"") + font-lock-string-face)) + (should (equal (clojure-test-face-at 3 4 "\"a\\bc\\n\"") + '(bold font-lock-string-face))) + (should (equal (clojure-test-face-at 5 5 "\"a\\bc\\n\"") + font-lock-string-face)) + (should (equal (clojure-test-face-at 6 7 "\"a\\bc\\n\"") + '(bold font-lock-string-face))) + (should (equal (clojure-test-face-at 4 5 "#\"a\\bc\\n\"") + '(bold font-lock-string-face)))) (ert-deftest clojure-mode-syntax-table/fontify-let-when-while-type-forms () :tags '(fontification syntax-table) - (should (equal (clojure-test-face-at 2 11 "(when-alist [x 1]\n ())") 'font-lock-keyword-face)) - (should (equal (clojure-test-face-at 2 12 "(while-alist [x 1]\n ())") 'font-lock-keyword-face)) - (should (equal (clojure-test-face-at 2 10 "(let-alist [x 1]\n ())") 'font-lock-keyword-face))) + (should (equal (clojure-test-face-at 2 11 "(when-alist [x 1]\n ())") + 'font-lock-keyword-face)) + (should (equal (clojure-test-face-at 2 12 "(while-alist [x 1]\n ())") + 'font-lock-keyword-face)) + (should (equal (clojure-test-face-at 2 10 "(let-alist [x 1]\n ())") + 'font-lock-keyword-face))) (ert-deftest clojure-mode-syntax-table/comment-macros () :tags '(fontification syntax-table) - (should (not (clojure-test-face-at 1 2 "#_ \n;; some crap\n (lala 0101\n lao\n\n 0 0i)"))) - ;; (should (equal (clojure-test-face-at 5 41 "#_ \n;; some crap\n (lala 0101\n lao\n\n 0 0i)") 'font-lock-comment-face)) ; TODO failing test - ) - -(ert-deftest clojure-mode-syntax-table/type () - :tags '(fontification syntax-table) - (should (eq (clojure-test-face-at 1 9 "SomeClass") 'font-lock-type-face))) - -(ert-deftest clojure-mode-syntax-table/type-hint () - :tags '(fontification syntax-table) - (clojure-test-with-temp-buffer "#^SomeClass" - (should (eq (clojure-test-face-at 3 11) 'font-lock-type-face)) - (should (eq (clojure-test-face-at 1 2) nil)))) - -(ert-deftest clojure-mode-syntax-table/constructor () - :tags '(fontification syntax-table) - (should (eq (clojure-test-face-at 2 11 "(SomeClass.)") 'font-lock-type-face)) - (clojure-test-with-temp-buffer "(ns/SomeClass.)" - (should (eq (clojure-test-face-at 2 3) 'font-lock-type-face)) - (should (eq (clojure-test-face-at 5 14) 'font-lock-type-face)))) + (should (equal (clojure-test-face-at + 1 2 "#_") + 'default)) + (should (equal (clojure-test-face-at + 1 2 "#_ \n;; some crap\n (lala 0101\n lao\n\n 0 0i)") + 'default)) + (should (equal (clojure-test-face-at + 5 41 "#_ \n;; some crap\n (lala 0101\n lao\n\n 0 0i)") + 'font-lock-comment-face))) (ert-deftest clojure-mode-syntax-table/namespace () :tags '(fontification syntax-table) - (should (eq (clojure-test-face-at 1 5 "one.p") 'font-lock-type-face)) - (should (eq (clojure-test-face-at 1 11 "one.p.top13") 'font-lock-type-face)) - (should (eq (clojure-test-face-at 2 12 "^one.p.top13") 'font-lock-type-face))) - -(ert-deftest clojure-mode-syntax-table/namespaced-symbol () - :tags '(fontification syntax-table) - (clojure-test-with-temp-buffer "clo.core/something" - (should (eq (clojure-test-face-at 9 9) 'default)) - (should (eq (clojure-test-face-at 1 8) 'font-lock-type-face)) - (should (eq (clojure-test-face-at 10 18) nil))) - (clojure-test-with-temp-buffer "a/something" - (should (eq (clojure-test-face-at 1 1) 'font-lock-type-face)) - (should (eq (clojure-test-face-at 3 12) 'nil)) - (should (eq (clojure-test-face-at 2 2) 'default))) - (clojure-test-with-temp-buffer "abc/something" - (should (eq (clojure-test-face-at 1 3) 'font-lock-type-face)) - (should (eq (clojure-test-face-at 5 14) 'nil)) - (should (eq (clojure-test-face-at 4 4) 'default)))) - -(ert-deftest clojure-mode-syntax-table/static-method () - :tags '(fontification syntax-table) - (clojure-test-with-temp-buffer "Class/methodName" - (should (eq (clojure-test-face-at 1 5) 'font-lock-type-face)) - (should (eq (clojure-test-face-at 6 6) 'default)) - (should (eq (clojure-test-face-at 7 16) 'clojure-interop-method-face))) - (clojure-test-with-temp-buffer "SomeClass/methodName" - (should (eq (clojure-test-face-at 1 9) 'font-lock-type-face)) - (should (eq (clojure-test-face-at 10 10) 'default)) - (should (eq (clojure-test-face-at 11 20) 'clojure-interop-method-face))) - (clojure-test-with-temp-buffer "clojure.lang.Var/someMethod" - (should (eq (clojure-test-face-at 1 16) 'font-lock-type-face)) - (should (eq (clojure-test-face-at 17 17) 'default)) - (should (eq (clojure-test-face-at 18 27) 'clojure-interop-method-face)))) - -(ert-deftest clojure-mode-syntax-table/interop-method () - :tags '(fontification syntax-table) - (should (eq (clojure-test-face-at 1 11 ".someMethod") 'clojure-interop-method-face)) - (should (eq (clojure-test-face-at 1 10 "someMethod") 'clojure-interop-method-face)) - (should (eq (clojure-test-face-at 1 11 "topHttpTest") 'clojure-interop-method-face)) - (should (eq (clojure-test-face-at 1 4 "getX") 'clojure-interop-method-face))) - -(ert-deftest clojure-mode-syntax-table/constant () - :tags '(fontification syntax-table) - (should (eq (clojure-test-face-at 1 5 "CONST") 'font-lock-constant-face)) - (should (eq (clojure-test-face-at 1 10 "CONST_NAME") 'font-lock-constant-face))) - -(ert-deftest clojure-mode-syntax-table/class-constant () - :tags '(fontification syntax-table) - (clojure-test-with-temp-buffer "Class/CONST_NAME" - (should (eq (clojure-test-face-at 6 6) 'default)) - (should (eq (clojure-test-face-at 1 5) 'font-lock-type-face)) - (should (eq (clojure-test-face-at 7 16) 'font-lock-constant-face)))) + (should (eq (clojure-test-face-at 5 12 "(ns .validns)") 'font-lock-type-face)) + (should (eq (clojure-test-face-at 5 12 "(ns =validns)") 'font-lock-type-face)) + (should (eq (clojure-test-face-at 5 21 "(ns .ValidNs=<>?+|?*.)") + 'font-lock-type-face)) + (should (eq (clojure-test-face-at 5 28 "(ns ValidNs<>?+|?*.b*ar.ba*z)") + 'font-lock-type-face)) + (should (eq (clojure-test-face-at 5 18 "(ns other.valid.ns)") + 'font-lock-type-face)) + (should (eq (clojure-test-face-at 5 11 "(ns oneword)") 'font-lock-type-face)) + (should (eq (clojure-test-face-at 5 11 "(ns foo.bar)") 'font-lock-type-face)) + (should (eq (clojure-test-face-at 5 11 "(ns Foo.bar)") 'font-lock-type-face)) + (should (eq (clojure-test-face-at 5 11 "(ns Foo.Bar)") 'font-lock-type-face)) + (should (eq (clojure-test-face-at 5 11 "(ns foo.Bar)") 'font-lock-type-face)) + (should (eq (clojure-test-face-at 5 11 "(ns Foo-bar)") 'font-lock-type-face)) + (should (eq (clojure-test-face-at 5 11 "(ns Foo-Bar)") 'font-lock-type-face)) + (should (eq (clojure-test-face-at 5 11 "(ns foo-Bar)") 'font-lock-type-face)) + (should (eq (clojure-test-face-at 5 9 "(ns one.X)") 'font-lock-type-face))) + +(ert-deftest clojure-mode-syntax-table/oneword () + :tags '(fontification syntax-table) + (should (eq (clojure-test-face-at 2 8 " oneword") 'default)) + (should (eq (clojure-test-face-at 2 8 "@oneword") 'default)) + (should (eq (clojure-test-face-at 2 8 "#oneword") 'default)) + (should (eq (clojure-test-face-at 2 8 ".oneword") 'default)) + (should (eq (clojure-test-face-at 3 9 "#^oneword") + 'font-lock-type-face)) ;; type-hint + (should (eq (clojure-test-face-at 2 8 "(oneword)") 'default)) + + (should (eq (clojure-test-face-at 2 8 "(oneword/oneword)") + 'font-lock-type-face)) + (should (eq (clojure-test-face-at 9 10 "(oneword/oneword)") 'default)) + (should (eq (clojure-test-face-at 11 16 "(oneword/oneword)") 'default)) + + (should (eq (clojure-test-face-at 2 8 "(oneword/seg.mnt)") + 'font-lock-type-face)) + (should (eq (clojure-test-face-at 9 10 "(oneword/seg.mnt)") 'default)) + (should (eq (clojure-test-face-at 11 16 "(oneword/seg.mnt)") 'default)) + + (should (eq (clojure-test-face-at 2 8 "(oneword/mxdCase)") + 'font-lock-type-face)) + (should (eq (clojure-test-face-at 9 10 "(oneword/mxdCase)") 'default)) + (should (eq (clojure-test-face-at 11 16 "(oneword/mxdCase)") 'default)) + + (should (eq (clojure-test-face-at 2 8 "(oneword/CmlCase)") + 'font-lock-type-face)) + (should (eq (clojure-test-face-at 9 10 "(oneword/CmlCase)") 'default)) + (should (eq (clojure-test-face-at 11 16 "(oneword/CmlCase)") 'default)) + + (should (eq (clojure-test-face-at 2 8 "(oneword/veryCom|pLex.stu-ff)") + 'font-lock-type-face)) + (should (eq (clojure-test-face-at 9 10 "(oneword/veryCom|pLex.stu-ff)") + 'default)) + (should (eq (clojure-test-face-at 11 28 "(oneword/veryCom|pLex.stu-ff)") + 'default)) + + (should (eq (clojure-test-face-at 2 8 "(oneword/.veryCom|pLex.stu-ff)") + 'font-lock-type-face)) + (should (eq (clojure-test-face-at 9 10 "(oneword/.veryCom|pLex.stu-ff)") + 'default)) + (should (eq (clojure-test-face-at 12 29 "(oneword/.veryCom|pLex.stu-ff)") + 'default))) + +(ert-deftest clojure-mode-syntax-table/segment () + :tags '(fontification syntax-table) + (should (eq (clojure-test-face-at 2 8 " seg.mnt") 'default)) + (should (eq (clojure-test-face-at 2 8 "@seg.mnt") 'default)) + (should (eq (clojure-test-face-at 2 8 "#seg.mnt") 'default)) + (should (eq (clojure-test-face-at 2 8 ".seg.mnt") 'default)) + (should (eq (clojure-test-face-at 3 9 "#^seg.mnt") + 'font-lock-type-face)) ;; type-hint + (should (eq (clojure-test-face-at 2 8 "(seg.mnt)") 'default)) + (should (eq (clojure-test-face-at 2 8 "(seg.mnt/oneword)") + 'font-lock-type-face)) + (should (eq (clojure-test-face-at 9 10 "(seg.mnt/oneword)") 'default)) + (should (eq (clojure-test-face-at 11 16 "(seg.mnt/oneword)") 'default)) + + (should (eq (clojure-test-face-at 2 8 "(seg.mnt/seg.mnt)") + 'font-lock-type-face)) + (should (eq (clojure-test-face-at 9 10 "(seg.mnt/seg.mnt)") 'default)) + (should (eq (clojure-test-face-at 11 16 "(seg.mnt/seg.mnt)") 'default)) + + (should (eq (clojure-test-face-at 2 8 "(seg.mnt/mxdCase)") + 'font-lock-type-face)) + (should (eq (clojure-test-face-at 9 10 "(seg.mnt/mxdCase)") 'default)) + (should (eq (clojure-test-face-at 11 16 "(seg.mnt/mxdCase)") 'default)) + + (should (eq (clojure-test-face-at 2 8 "(seg.mnt/CmlCase)") + 'font-lock-type-face)) + (should (eq (clojure-test-face-at 9 10 "(seg.mnt/CmlCase)") 'default)) + (should (eq (clojure-test-face-at 11 16 "(seg.mnt/CmlCase)") 'default)) + + (should (eq (clojure-test-face-at 2 8 "(seg.mnt/veryCom|pLex.stu-ff)") + 'font-lock-type-face)) + (should (eq (clojure-test-face-at 9 10 "(seg.mnt/veryCom|pLex.stu-ff)") + 'default)) + (should (eq (clojure-test-face-at 11 28 "(seg.mnt/veryCom|pLex.stu-ff)") + 'default)) + + (should (eq (clojure-test-face-at 2 8 "(seg.mnt/.veryCom|pLex.stu-ff)") + 'font-lock-type-face)) + (should (eq (clojure-test-face-at 9 10 "(seg.mnt/.veryCom|pLex.stu-ff)") + 'default)) + (should (eq (clojure-test-face-at 12 29 "(seg.mnt/.veryCom|pLex.stu-ff)") + 'default))) + +(ert-deftest clojure-mode-syntax-table/camelcase () + :tags '(fontification syntax-table) + (should (eq (clojure-test-face-at 2 8 " CmlCase") 'default)) + (should (eq (clojure-test-face-at 2 8 "@CmlCase") 'default)) + (should (eq (clojure-test-face-at 2 8 "#CmlCase") 'default)) + (should (eq (clojure-test-face-at 2 8 ".CmlCase") 'default)) + (should (eq (clojure-test-face-at 3 9 "#^CmlCase") + 'font-lock-type-face)) ;; type-hint + (should (eq (clojure-test-face-at 2 8 "(CmlCase)") 'default)) + + (should (eq (clojure-test-face-at 2 8 "(CmlCase/oneword)") + 'font-lock-type-face)) + (should (eq (clojure-test-face-at 9 10 "(CmlCase/oneword)") 'default)) + (should (eq (clojure-test-face-at 11 16 "(CmlCase/oneword)") 'default)) + + (should (eq (clojure-test-face-at 2 8 "(CmlCase/seg.mnt)") + 'font-lock-type-face)) + (should (eq (clojure-test-face-at 9 10 "(CmlCase/seg.mnt)") 'default)) + (should (eq (clojure-test-face-at 11 16 "(CmlCase/seg.mnt)") 'default)) + + (should (eq (clojure-test-face-at 2 8 "(CmlCase/mxdCase)") + 'font-lock-type-face)) + (should (eq (clojure-test-face-at 9 10 "(CmlCase/mxdCase)") 'default)) + (should (eq (clojure-test-face-at 11 16 "(CmlCase/mxdCase)") 'default)) + + (should (eq (clojure-test-face-at 2 8 "(CmlCase/CmlCase)") + 'font-lock-type-face)) + (should (eq (clojure-test-face-at 9 10 "(CmlCase/CmlCase)") 'default)) + (should (eq (clojure-test-face-at 11 16 "(CmlCase/CmlCase)") 'default)) + + (should (eq (clojure-test-face-at 2 8 "(CmlCase/veryCom|pLex.stu-ff)") + 'font-lock-type-face)) + (should (eq (clojure-test-face-at 9 10 "(CmlCase/veryCom|pLex.stu-ff)") + 'default)) + (should (eq (clojure-test-face-at 11 28 "(CmlCase/veryCom|pLex.stu-ff)") + 'default)) + + (should (eq (clojure-test-face-at 2 8 "(CmlCase/.veryCom|pLex.stu-ff)") + 'font-lock-type-face)) + (should (eq (clojure-test-face-at 9 10 "(CmlCase/.veryCom|pLex.stu-ff)") + 'default)) + (should (eq (clojure-test-face-at 12 29 "(CmlCase/.veryCom|pLex.stu-ff)") + 'default))) + +(ert-deftest clojure-mode-syntax-table/mixedcase () + :tags '(fontification syntax-table) + (should (eq (clojure-test-face-at 2 8 " mxdCase") 'default)) + (should (eq (clojure-test-face-at 2 8 "@mxdCase") 'default)) + (should (eq (clojure-test-face-at 2 8 "#mxdCase") 'default)) + (should (eq (clojure-test-face-at 2 8 ".mxdCase") 'default)) + (should (eq (clojure-test-face-at 3 9 "#^mxdCase") + 'font-lock-type-face)) ;; type-hint + (should (eq (clojure-test-face-at 2 8 "(mxdCase)") 'default)) + + (should (eq (clojure-test-face-at 2 8 "(mxdCase/oneword)") + 'font-lock-type-face)) + (should (eq (clojure-test-face-at 9 10 "(mxdCase/oneword)") 'default)) + (should (eq (clojure-test-face-at 11 16 "(mxdCase/oneword)") 'default)) + + (should (eq (clojure-test-face-at 2 8 "(mxdCase/seg.mnt)") + 'font-lock-type-face)) + (should (eq (clojure-test-face-at 9 10 "(mxdCase/seg.mnt)") 'default)) + (should (eq (clojure-test-face-at 11 16 "(mxdCase/seg.mnt)") 'default)) + + (should (eq (clojure-test-face-at 2 8 "(mxdCase/mxdCase)") + 'font-lock-type-face)) + (should (eq (clojure-test-face-at 9 10 "(mxdCase/mxdCase)") 'default)) + (should (eq (clojure-test-face-at 11 16 "(mxdCase/mxdCase)") 'default)) + + (should (eq (clojure-test-face-at 2 8 "(mxdCase/CmlCase)") + 'font-lock-type-face)) + (should (eq (clojure-test-face-at 9 10 "(mxdCase/CmlCase)") 'default)) + (should (eq (clojure-test-face-at 11 16 "(mxdCase/CmlCase)") 'default)) + + (should (eq (clojure-test-face-at 2 8 "(mxdCase/veryCom|pLex.stu-ff)") + 'font-lock-type-face)) + (should (eq (clojure-test-face-at 9 10 "(mxdCase/veryCom|pLex.stu-ff)") + 'default)) + (should (eq (clojure-test-face-at 11 28 "(mxdCase/veryCom|pLex.stu-ff)") + 'default)) + + (should (eq (clojure-test-face-at 2 8 "(mxdCase/.veryCom|pLex.stu-ff)") + 'font-lock-type-face)) + (should (eq (clojure-test-face-at 9 10 "(mxdCase/.veryCom|pLex.stu-ff)") + 'default)) + (should (eq (clojure-test-face-at 12 29 "(mxdCase/.veryCom|pLex.stu-ff)") + 'default))) + +(ert-deftest clojure-mode-syntax-table/verycomplex () + :tags '(fontification syntax-table) + (should (eq (clojure-test-face-at 3 21 " veryCom|pLex.stu-ff") 'default)) + (should (eq (clojure-test-face-at 3 21 " @veryCom|pLex.stu-ff") 'default)) + (should (eq (clojure-test-face-at 3 21 " #veryCom|pLex.stu-ff") 'default)) + (should (eq (clojure-test-face-at 3 21 " .veryCom|pLex.stu-ff") 'default)) + (should (eq (clojure-test-face-at 3 21 "#^veryCom|pLex.stu-ff") + 'font-lock-type-face)) ;; type-hint + (should (eq (clojure-test-face-at 3 21 " (veryCom|pLex.stu-ff)") 'default)) + + (should (eq (clojure-test-face-at 3 21 " (veryCom|pLex.stu-ff/oneword)") + 'font-lock-type-face)) + (should (eq (clojure-test-face-at 22 22 " (veryCom|pLex.stu-ff/oneword)") + 'default)) + (should (eq (clojure-test-face-at 23 29 " (veryCom|pLex.stu-ff/oneword)") + 'default)) + + (should (eq (clojure-test-face-at 3 21 " (veryCom|pLex.stu-ff/seg.mnt)") + 'font-lock-type-face)) + (should (eq (clojure-test-face-at 22 22 " (veryCom|pLex.stu-ff/seg.mnt)") + 'default)) + (should (eq (clojure-test-face-at 22 29 " (veryCom|pLex.stu-ff/seg.mnt)") + 'default)) + + (should (eq (clojure-test-face-at 3 21 " (veryCom|pLex.stu-ff/mxdCase)") + 'font-lock-type-face)) + (should (eq (clojure-test-face-at 22 22 " (veryCom|pLex.stu-ff/mxdCase)") + 'default)) + (should (eq (clojure-test-face-at 22 29 " (veryCom|pLex.stu-ff/mxdCase)") + 'default)) + + (should (eq (clojure-test-face-at 3 21 " (veryCom|pLex.stu-ff/CmlCase)") + 'font-lock-type-face)) + (should (eq (clojure-test-face-at 22 22 " (veryCom|pLex.stu-ff/CmlCase)") + 'default)) + (should (eq (clojure-test-face-at 22 29 " (veryCom|pLex.stu-ff/CmlCase)") + 'default)) + + (should (eq (clojure-test-face-at + 3 21 " (veryCom|pLex.stu-ff/veryCom|pLex.stu-ff)") + 'font-lock-type-face)) + (should (eq (clojure-test-face-at + 22 22 " (veryCom|pLex.stu-ff/veryCom|pLex.stu-ff)") + 'default)) + (should (eq (clojure-test-face-at + 23 41 " (veryCom|pLex.stu-ff/veryCom|pLex.stu-ff)") + 'default)) + + (should (eq (clojure-test-face-at + 3 21 " (veryCom|pLex.stu-ff/.veryCom|pLex.stu-ff)") + 'font-lock-type-face)) + (should (eq (clojure-test-face-at + 22 22 " (veryCom|pLex.stu-ff/.veryCom|pLex.stu-ff)") + 'default)) + (should (eq (clojure-test-face-at + 23 42 " (veryCom|pLex.stu-ff/.veryCom|pLex.stu-ff)") + 'default))) + +(ert-deftest clojure-mode-syntax-table/kw-oneword () + :tags '(fontification syntax-table) + (should (eq (clojure-test-face-at 3 9 " :oneword") 'clojure-keyword-face)) + (should (eq (clojure-test-face-at 3 9 "{:oneword 0}") + 'clojure-keyword-face)) + (should (eq (clojure-test-face-at 3 10 "{:#oneword 0}") + 'clojure-keyword-face)) + (should (eq (clojure-test-face-at 3 10 "{:.oneword 0}") + 'clojure-keyword-face)) + + (should (eq (clojure-test-face-at 3 9 "{:oneword/oneword 0}") + 'font-lock-type-face)) + (should (eq (clojure-test-face-at 10 10 "{:oneword/oneword 0}") 'default)) + (should (eq (clojure-test-face-at 11 17 "{:oneword/oneword 0}") + 'clojure-keyword-face)) + + (should (eq (clojure-test-face-at 3 9 "{:oneword/seg.mnt 0}") + 'font-lock-type-face)) + (should (eq (clojure-test-face-at 10 10 "{:oneword/seg.mnt 0}") 'default)) + (should (eq (clojure-test-face-at 11 17 "{:oneword/seg.mnt 0}") + 'clojure-keyword-face)) + + (should (eq (clojure-test-face-at 3 9 "{:oneword/CmlCase 0}") + 'font-lock-type-face)) + (should (eq (clojure-test-face-at 10 10 "{:oneword/CmlCase 0}") 'default)) + (should (eq (clojure-test-face-at 11 17 "{:oneword/CmlCase 0}") + 'clojure-keyword-face)) + + (should (eq (clojure-test-face-at 3 9 "{:oneword/mxdCase 0}") + 'font-lock-type-face)) + (should (eq (clojure-test-face-at 10 10 "{:oneword/mxdCase 0}") 'default)) + (should (eq (clojure-test-face-at 11 17 "{:oneword/mxdCase 0}") + 'clojure-keyword-face)) + + (should (eq (clojure-test-face-at 3 9 "{:oneword/veryCom|pLex.stu-ff 0}") + 'font-lock-type-face)) + (should (eq (clojure-test-face-at 10 10 "{:oneword/veryCom|pLex.stu-ff 0}") + 'default)) + (should (eq (clojure-test-face-at 11 29 "{:oneword/veryCom|pLex.stu-ff 0}") + 'clojure-keyword-face)) + + (should (eq (clojure-test-face-at 3 9 "{:oneword/.veryCom|pLex.stu-ff 0}") + 'font-lock-type-face)) + (should (eq (clojure-test-face-at 10 10 "{:oneword/.veryCom|pLex.stu-ff 0}") + 'default)) + (should (eq (clojure-test-face-at 11 30 "{:oneword/.veryCom|pLex.stu-ff 0}") + 'clojure-keyword-face))) + +(ert-deftest clojure-mode-syntax-table/kw-segment () + :tags '(fontification syntax-table) + (should (eq (clojure-test-face-at 3 9 " :seg.mnt") 'clojure-keyword-face)) + (should (eq (clojure-test-face-at 3 9 "{:seg.mnt 0}") + 'clojure-keyword-face)) + (should (eq (clojure-test-face-at 3 10 "{:#seg.mnt 0}") + 'clojure-keyword-face)) + (should (eq (clojure-test-face-at 3 10 "{:.seg.mnt 0}") + 'clojure-keyword-face)) + + (should (eq (clojure-test-face-at 3 9 "{:seg.mnt/oneword 0}") + 'font-lock-type-face)) + (should (eq (clojure-test-face-at 10 10 "{:seg.mnt/oneword 0}") 'default)) + (should (eq (clojure-test-face-at 11 17 "{:seg.mnt/oneword 0}") + 'clojure-keyword-face)) + + (should (eq (clojure-test-face-at 3 9 "{:seg.mnt/seg.mnt 0}") + 'font-lock-type-face)) + (should (eq (clojure-test-face-at 10 10 "{:seg.mnt/seg.mnt 0}") 'default)) + (should (eq (clojure-test-face-at 11 17 "{:seg.mnt/seg.mnt 0}") + 'clojure-keyword-face)) + + (should (eq (clojure-test-face-at 3 9 "{:seg.mnt/CmlCase 0}") + 'font-lock-type-face)) + (should (eq (clojure-test-face-at 10 10 "{:seg.mnt/CmlCase 0}") 'default)) + (should (eq (clojure-test-face-at 11 17 "{:seg.mnt/CmlCase 0}") + 'clojure-keyword-face)) + + (should (eq (clojure-test-face-at 3 9 "{:seg.mnt/mxdCase 0}") + 'font-lock-type-face)) + (should (eq (clojure-test-face-at 10 10 "{:seg.mnt/mxdCase 0}") 'default)) + (should (eq (clojure-test-face-at 11 17 "{:seg.mnt/mxdCase 0}") + 'clojure-keyword-face)) + + (should (eq (clojure-test-face-at 3 9 "{:seg.mnt/veryCom|pLex.stu-ff 0}") + 'font-lock-type-face)) + (should (eq (clojure-test-face-at 10 10 "{:seg.mnt/veryCom|pLex.stu-ff 0}") + 'default)) + (should (eq (clojure-test-face-at 11 29 "{:seg.mnt/veryCom|pLex.stu-ff 0}") + 'clojure-keyword-face)) + + (should (eq (clojure-test-face-at 3 9 "{:seg.mnt/.veryCom|pLex.stu-ff 0}") + 'font-lock-type-face)) + (should (eq (clojure-test-face-at 10 10 "{:seg.mnt/.veryCom|pLex.stu-ff 0}") + 'default)) + (should (eq (clojure-test-face-at 11 30 "{:seg.mnt/.veryCom|pLex.stu-ff 0}") + 'clojure-keyword-face))) + +(ert-deftest clojure-mode-syntax-table/kw-camelcase () + :tags '(fontification syntax-table) + (should (eq (clojure-test-face-at 3 9 " :CmlCase") 'clojure-keyword-face)) + (should (eq (clojure-test-face-at 3 9 "{:CmlCase 0}") + 'clojure-keyword-face)) + (should (eq (clojure-test-face-at 3 10 "{:#CmlCase 0}") + 'clojure-keyword-face)) + (should (eq (clojure-test-face-at 3 10 "{:.CmlCase 0}") + 'clojure-keyword-face)) + (should (eq (clojure-test-face-at 3 9 "{:CmlCase/oneword 0}") + 'font-lock-type-face)) + (should (eq (clojure-test-face-at 10 10 "{:CmlCase/oneword 0}") 'default)) + (should (eq (clojure-test-face-at 11 17 "{:CmlCase/oneword 0}") + 'clojure-keyword-face)) + (should (eq (clojure-test-face-at 3 9 "{:CmlCase/seg.mnt 0}") + 'font-lock-type-face)) + (should (eq (clojure-test-face-at 10 10 "{:CmlCase/seg.mnt 0}") 'default)) + (should (eq (clojure-test-face-at 11 17 "{:CmlCase/seg.mnt 0}") + 'clojure-keyword-face)) + (should (eq (clojure-test-face-at 3 9 "{:CmlCase/CmlCase 0}") + 'font-lock-type-face)) + (should (eq (clojure-test-face-at 10 10 "{:CmlCase/CmlCase 0}") 'default)) + (should (eq (clojure-test-face-at 11 17 "{:CmlCase/CmlCase 0}") + 'clojure-keyword-face)) + (should (eq (clojure-test-face-at 3 9 "{:CmlCase/mxdCase 0}") + 'font-lock-type-face)) + (should (eq (clojure-test-face-at 10 10 "{:CmlCase/mxdCase 0}") 'default)) + (should (eq (clojure-test-face-at 11 17 "{:CmlCase/mxdCase 0}") + 'clojure-keyword-face)) + (should (eq (clojure-test-face-at 3 9 "{:CmlCase/veryCom|pLex.stu-ff 0}") + 'font-lock-type-face)) + (should (eq (clojure-test-face-at 10 10 "{:CmlCase/veryCom|pLex.stu-ff 0}") + 'default)) + (should (eq (clojure-test-face-at 11 29 "{:CmlCase/veryCom|pLex.stu-ff 0}") + 'clojure-keyword-face)) + + (should (eq (clojure-test-face-at 3 9 "{:CmlCase/.veryCom|pLex.stu-ff 0}") + 'font-lock-type-face)) + (should (eq (clojure-test-face-at 10 10 "{:CmlCase/.veryCom|pLex.stu-ff 0}") + 'default)) + (should (eq (clojure-test-face-at 11 30 "{:CmlCase/.veryCom|pLex.stu-ff 0}") + 'clojure-keyword-face))) + +(ert-deftest clojure-mode-syntax-table/kw-mixedcase () + :tags '(fontification syntax-table) + (should (eq (clojure-test-face-at 3 9 " :mxdCase") 'clojure-keyword-face)) + (should (eq (clojure-test-face-at 3 9 "{:mxdCase 0}") + 'clojure-keyword-face)) + (should (eq (clojure-test-face-at 3 10 "{:#mxdCase 0}") + 'clojure-keyword-face)) + (should (eq (clojure-test-face-at 3 10 "{:.mxdCase 0}") + 'clojure-keyword-face)) + + (should (eq (clojure-test-face-at 3 9 "{:mxdCase/oneword 0}") + 'font-lock-type-face)) + (should (eq (clojure-test-face-at 10 10 "{:mxdCase/oneword 0}") 'default)) + (should (eq (clojure-test-face-at 11 17 "{:mxdCase/oneword 0}") + 'clojure-keyword-face)) + + (should (eq (clojure-test-face-at 3 9 "{:mxdCase/seg.mnt 0}") + 'font-lock-type-face)) + (should (eq (clojure-test-face-at 10 10 "{:mxdCase/seg.mnt 0}") 'default)) + (should (eq (clojure-test-face-at 11 17 "{:mxdCase/seg.mnt 0}") + 'clojure-keyword-face)) + + (should (eq (clojure-test-face-at 3 9 "{:mxdCase/CmlCase 0}") + 'font-lock-type-face)) + (should (eq (clojure-test-face-at 10 10 "{:mxdCase/CmlCase 0}") 'default)) + (should (eq (clojure-test-face-at 11 17 "{:mxdCase/CmlCase 0}") + 'clojure-keyword-face)) + + (should (eq (clojure-test-face-at 3 9 "{:mxdCase/mxdCase 0}") + 'font-lock-type-face)) + (should (eq (clojure-test-face-at 10 10 "{:mxdCase/mxdCase 0}") 'default)) + (should (eq (clojure-test-face-at 11 17 "{:mxdCase/mxdCase 0}") + 'clojure-keyword-face)) + + (should (eq (clojure-test-face-at 3 9 "{:mxdCase/veryCom|pLex.stu-ff 0}") + 'font-lock-type-face)) + (should (eq (clojure-test-face-at 10 10 "{:mxdCase/veryCom|pLex.stu-ff 0}") + 'default)) + (should (eq (clojure-test-face-at 11 29 "{:mxdCase/veryCom|pLex.stu-ff 0}") + 'clojure-keyword-face)) + + (should (eq (clojure-test-face-at 3 9 "{:mxdCase/.veryCom|pLex.stu-ff 0}") + 'font-lock-type-face)) + (should (eq (clojure-test-face-at 10 10 "{:mxdCase/.veryCom|pLex.stu-ff 0}") + 'default)) + (should (eq (clojure-test-face-at 11 30 "{:mxdCase/.veryCom|pLex.stu-ff 0}") + 'clojure-keyword-face))) + +(ert-deftest clojure-mode-syntax-table/kw-verycomplex () + :tags '(fontification syntax-table) + (should (eq (clojure-test-face-at 3 9 " :veryCom|pLex.stu-ff") + 'clojure-keyword-face)) + (should (eq (clojure-test-face-at 3 9 "{:veryCom|pLex.stu-ff 0}") + 'clojure-keyword-face)) + (should (eq (clojure-test-face-at 3 9 "{:#veryCom|pLex.stu-ff 0}") + 'clojure-keyword-face)) + (should (eq (clojure-test-face-at 3 9 "{:.veryCom|pLex.stu-ff 0}") + 'clojure-keyword-face)) + + (should (eq (clojure-test-face-at 3 21 "{:veryCom|pLex.stu-ff/oneword 0}") + 'font-lock-type-face)) + (should (eq (clojure-test-face-at 22 22 "{:veryCom|pLex.stu-ff/oneword 0}") + 'default)) + (should (eq (clojure-test-face-at 23 29 "{:veryCom|pLex.stu-ff/oneword 0}") + 'clojure-keyword-face)) + + (should (eq (clojure-test-face-at 3 9 "{:veryCom|pLex.stu-ff/seg.mnt 0}") + 'font-lock-type-face)) + (should (eq (clojure-test-face-at 22 22 "{:veryCom|pLex.stu-ff/seg.mnt 0}") + 'default)) + (should (eq (clojure-test-face-at 23 29 "{:veryCom|pLex.stu-ff/seg.mnt 0}") + 'clojure-keyword-face)) + + (should (eq (clojure-test-face-at 3 9 "{:veryCom|pLex.stu-ff/CmlCase 0}") + 'font-lock-type-face)) + (should (eq (clojure-test-face-at 22 22 "{:veryCom|pLex.stu-ff/CmlCase 0}") + 'default)) + (should (eq (clojure-test-face-at 23 29 "{:veryCom|pLex.stu-ff/CmlCase 0}") + 'clojure-keyword-face)) + + (should (eq (clojure-test-face-at 3 9 "{:veryCom|pLex.stu-ff/mxdCase 0}") + 'font-lock-type-face)) + (should (eq (clojure-test-face-at 22 22 "{:veryCom|pLex.stu-ff/mxdCase 0}") + 'default)) + (should (eq (clojure-test-face-at 23 29 "{:veryCom|pLex.stu-ff/mxdCase 0}") + 'clojure-keyword-face)) + + (should (eq (clojure-test-face-at + 3 21 "{:veryCom|pLex.stu-ff/veryCom|pLex.stu-ff 0}") + 'font-lock-type-face)) + (should (eq (clojure-test-face-at + 22 22 "{:veryCom|pLex.stu-ff/veryCom|pLex.stu-ff 0}") + 'default)) + (should (eq (clojure-test-face-at + 23 41 "{:veryCom|pLex.stu-ff/veryCom|pLex.stu-ff 0}") + 'clojure-keyword-face)) + + (should (eq (clojure-test-face-at + 3 21 "{:veryCom|pLex.stu-ff/.veryCom|pLex.stu-ff 0}") + 'font-lock-type-face)) + (should (eq (clojure-test-face-at + 22 22 "{:veryCom|pLex.stu-ff/.veryCom|pLex.stu-ff 0}") + 'default)) + (should (eq (clojure-test-face-at + 23 42 "{:veryCom|pLex.stu-ff/.veryCom|pLex.stu-ff 0}") + 'clojure-keyword-face))) (ert-deftest clojure-mode-syntax-table/namespaced-def () :tags '(fontification syntax-table) @@ -211,9 +645,10 @@ POS." (ert-deftest clojure-mode-syntax-table/variable-def () :tags '(fontification syntax-table) - (clojure-test-with-temp-buffer "(def foo 10)" - (should (eq (clojure-test-face-at 2 4) 'font-lock-keyword-face)) - (should (eq (clojure-test-face-at 6 8) 'font-lock-variable-name-face)))) + (should (eq (clojure-test-face-at 2 4 "(def foo 10)") + 'font-lock-keyword-face)) + (should (eq (clojure-test-face-at 6 8 "(def foo 10)") + 'font-lock-variable-name-face))) (ert-deftest clojure-mode-syntax-table/type-def () :tags '(fontification syntax-table) @@ -247,9 +682,11 @@ POS." (ert-deftest clojure-mode-syntax-table/fn () :tags '(fontification syntax-table) - (clojure-test-with-temp-buffer "(fn foo [x] x)" - ;; (should (eq (clojure-test-face-at 2 3) 'font-lock-keyword-face)) ; TODO failing test - (should (eq (clojure-test-face-at 5 7) 'font-lock-function-name-face)))) + ;; try to byte-recompile the clojure-mode.el when the face of 'fn' is 't' + (should (eq (clojure-test-face-at 2 3 "(fn foo [x] x)") + 'font-lock-keyword-face)) + (should (eq (clojure-test-face-at 5 7 "(fn foo [x] x)") + 'font-lock-function-name-face))) (ert-deftest clojure-mode-syntax-table/lambda-params () :tags '(fontification syntax-table) @@ -262,40 +699,43 @@ POS." (ert-deftest clojure-mode-syntax-table/nil () :tags '(fontification syntax-table) (should (eq (clojure-test-face-at 4 6 "(= nil x)") 'font-lock-constant-face)) - (should-not (eq (clojure-test-face-at 3 5 "(fnil x)") 'font-lock-constant-face))) + (should-not (eq (clojure-test-face-at 3 5 "(fnil x)") + 'font-lock-constant-face))) (ert-deftest clojure-mode-syntax-table/true () :tags '(fontification syntax-table) - (should (eq (clojure-test-face-at 4 7 "(= true x)") 'font-lock-constant-face))) + (should (eq (clojure-test-face-at 4 7 "(= true x)") + 'font-lock-constant-face))) (ert-deftest clojure-mode-syntax-table/false () :tags '(fontification syntax-table) - (should (eq (clojure-test-face-at 4 8 "(= false x)") 'font-lock-constant-face))) + (should (eq (clojure-test-face-at 4 8 "(= false x)") + 'font-lock-constant-face))) (ert-deftest clojure-mode-syntax-table/keyword-meta () :tags '(fontification syntax-table) (clojure-test-with-temp-buffer "^:meta-data" (should (eq (clojure-test-face-at 1 1) nil)) - (should (equal (clojure-test-face-at 2 11) '(clojure-keyword-face))))) + (should (equal (clojure-test-face-at 2 11) 'clojure-keyword-face)))) (ert-deftest clojure-mode-syntax-table/keyword-allowed-chars () :tags '(fontification syntax-table) - (should (equal (clojure-test-face-at 1 8 ":aaa#bbb") '(clojure-keyword-face)))) + (should (equal (clojure-test-face-at 1 8 ":aaa#bbb") 'clojure-keyword-face))) (ert-deftest clojure-mode-syntax-table/keyword-disallowed-chars () :tags '(fontification syntax-table) (should (eq (clojure-test-face-at 1 5 ":aaa@bbb") 'various-faces)) - (should (equal (clojure-test-face-at 1 4 ":aaa@bbb") '(clojure-keyword-face))) + (should (equal (clojure-test-face-at 1 4 ":aaa@bbb") 'clojure-keyword-face)) (should (eq (clojure-test-face-at 1 5 ":aaa~bbb") 'various-faces)) - (should (equal (clojure-test-face-at 1 4 ":aaa~bbb") '(clojure-keyword-face))) + (should (equal (clojure-test-face-at 1 4 ":aaa~bbb") 'clojure-keyword-face)) (should (eq (clojure-test-face-at 1 5 ":aaa@bbb") 'various-faces)) - (should (equal (clojure-test-face-at 1 4 ":aaa@bbb") '(clojure-keyword-face)))) + (should (equal (clojure-test-face-at 1 4 ":aaa@bbb") 'clojure-keyword-face))) (ert-deftest clojure-mode-syntax-table/characters () :tags '(fontification syntax-table) (should (eq (clojure-test-face-at 1 2 "\\a") 'clojure-character-face)) (should (eq (clojure-test-face-at 1 8 "\\newline") 'clojure-character-face)) - ;; (should (eq (clojure-test-face-at 1 2 "\\1") 'clojure-character-face)) ; TODO failing test + (should (eq (clojure-test-face-at 1 2 "\\1") 'clojure-character-face)) (should (eq (clojure-test-face-at 1 6 "\\u0032") 'clojure-character-face)) (should (eq (clojure-test-face-at 1 2 "\\+") 'clojure-character-face)) (should (eq (clojure-test-face-at 1 2 "\\.") 'clojure-character-face)) @@ -309,16 +749,12 @@ POS." (ert-deftest clojure-mode-syntax-table/dynamic-var () :tags '(fontification syntax-table) - (should (eq (clojure-test-face-at 1 10 "*some-var*") 'font-lock-variable-name-face)) - (should (eq (clojure-test-face-at 2 11 "@*some-var*") 'font-lock-variable-name-face)) - (should (eq (clojure-test-face-at 9 13 "some.ns/*var*") 'font-lock-variable-name-face))) - -(ert-deftest clojure-mode-syntax-table/ns-macro () - :tags '(fontification syntax-table) - (should (eq (clojure-test-face-at 5 8 "(ns name)") 'font-lock-type-face)) - (should (eq (clojure-test-face-at 5 13 "(ns name.name)") 'font-lock-type-face)) - ;; (should (eq (clojure-test-face-at 1 10 "[ns name]") nil)) ; TODO failing test - ) + (should (eq (clojure-test-face-at 1 10 "*some-var*") + 'font-lock-variable-name-face)) + (should (eq (clojure-test-face-at 2 11 "@*some-var*") + 'font-lock-variable-name-face)) + (should (eq (clojure-test-face-at 9 13 "some.ns/*var*") + 'font-lock-variable-name-face))) (provide 'clojure-mode-font-lock-test) From 6aa5c4f1a4a6f64f23ce6d31b7f1159f114b0754 Mon Sep 17 00:00:00 2001 From: Rostislav Svoboda Date: Sat, 17 Mar 2018 15:33:44 +0100 Subject: [PATCH 117/379] Fix font-locking of namespaced keywords See #474 --- clojure-mode-font-lock-test.el | 273 ++++++++++++++++++++------------- 1 file changed, 170 insertions(+), 103 deletions(-) diff --git a/clojure-mode-font-lock-test.el b/clojure-mode-font-lock-test.el index 6e36ebd..a2a8155 100644 --- a/clojure-mode-font-lock-test.el +++ b/clojure-mode-font-lock-test.el @@ -181,18 +181,18 @@ POS." (should (eq (clojure-test-face-at 9 10 "(oneword/CmlCase)") 'default)) (should (eq (clojure-test-face-at 11 16 "(oneword/CmlCase)") 'default)) - (should (eq (clojure-test-face-at 2 8 "(oneword/veryCom|pLex.stu-ff)") + (should (eq (clojure-test-face-at 2 8 "(oneword/ve/yCom|pLex.stu-ff)") 'font-lock-type-face)) - (should (eq (clojure-test-face-at 9 10 "(oneword/veryCom|pLex.stu-ff)") + (should (eq (clojure-test-face-at 9 10 "(oneword/ve/yCom|pLex.stu-ff)") 'default)) - (should (eq (clojure-test-face-at 11 28 "(oneword/veryCom|pLex.stu-ff)") + (should (eq (clojure-test-face-at 11 28 "(oneword/ve/yCom|pLex.stu-ff)") 'default)) - (should (eq (clojure-test-face-at 2 8 "(oneword/.veryCom|pLex.stu-ff)") + (should (eq (clojure-test-face-at 2 8 "(oneword/.ve/yCom|pLex.stu-ff)") 'font-lock-type-face)) - (should (eq (clojure-test-face-at 9 10 "(oneword/.veryCom|pLex.stu-ff)") + (should (eq (clojure-test-face-at 9 10 "(oneword/.ve/yCom|pLex.stu-ff)") 'default)) - (should (eq (clojure-test-face-at 12 29 "(oneword/.veryCom|pLex.stu-ff)") + (should (eq (clojure-test-face-at 12 29 "(oneword/.ve/yCom|pLex.stu-ff)") 'default))) (ert-deftest clojure-mode-syntax-table/segment () @@ -224,18 +224,18 @@ POS." (should (eq (clojure-test-face-at 9 10 "(seg.mnt/CmlCase)") 'default)) (should (eq (clojure-test-face-at 11 16 "(seg.mnt/CmlCase)") 'default)) - (should (eq (clojure-test-face-at 2 8 "(seg.mnt/veryCom|pLex.stu-ff)") + (should (eq (clojure-test-face-at 2 8 "(seg.mnt/ve/yCom|pLex.stu-ff)") 'font-lock-type-face)) - (should (eq (clojure-test-face-at 9 10 "(seg.mnt/veryCom|pLex.stu-ff)") + (should (eq (clojure-test-face-at 9 10 "(seg.mnt/ve/yCom|pLex.stu-ff)") 'default)) - (should (eq (clojure-test-face-at 11 28 "(seg.mnt/veryCom|pLex.stu-ff)") + (should (eq (clojure-test-face-at 11 28 "(seg.mnt/ve/yCom|pLex.stu-ff)") 'default)) - (should (eq (clojure-test-face-at 2 8 "(seg.mnt/.veryCom|pLex.stu-ff)") + (should (eq (clojure-test-face-at 2 8 "(seg.mnt/.ve/yCom|pLex.stu-ff)") 'font-lock-type-face)) - (should (eq (clojure-test-face-at 9 10 "(seg.mnt/.veryCom|pLex.stu-ff)") + (should (eq (clojure-test-face-at 9 10 "(seg.mnt/.ve/yCom|pLex.stu-ff)") 'default)) - (should (eq (clojure-test-face-at 12 29 "(seg.mnt/.veryCom|pLex.stu-ff)") + (should (eq (clojure-test-face-at 12 29 "(seg.mnt/.ve/yCom|pLex.stu-ff)") 'default))) (ert-deftest clojure-mode-syntax-table/camelcase () @@ -268,18 +268,18 @@ POS." (should (eq (clojure-test-face-at 9 10 "(CmlCase/CmlCase)") 'default)) (should (eq (clojure-test-face-at 11 16 "(CmlCase/CmlCase)") 'default)) - (should (eq (clojure-test-face-at 2 8 "(CmlCase/veryCom|pLex.stu-ff)") + (should (eq (clojure-test-face-at 2 8 "(CmlCase/ve/yCom|pLex.stu-ff)") 'font-lock-type-face)) - (should (eq (clojure-test-face-at 9 10 "(CmlCase/veryCom|pLex.stu-ff)") + (should (eq (clojure-test-face-at 9 10 "(CmlCase/ve/yCom|pLex.stu-ff)") 'default)) - (should (eq (clojure-test-face-at 11 28 "(CmlCase/veryCom|pLex.stu-ff)") + (should (eq (clojure-test-face-at 11 28 "(CmlCase/ve/yCom|pLex.stu-ff)") 'default)) - (should (eq (clojure-test-face-at 2 8 "(CmlCase/.veryCom|pLex.stu-ff)") + (should (eq (clojure-test-face-at 2 8 "(CmlCase/.ve/yCom|pLex.stu-ff)") 'font-lock-type-face)) - (should (eq (clojure-test-face-at 9 10 "(CmlCase/.veryCom|pLex.stu-ff)") + (should (eq (clojure-test-face-at 9 10 "(CmlCase/.ve/yCom|pLex.stu-ff)") 'default)) - (should (eq (clojure-test-face-at 12 29 "(CmlCase/.veryCom|pLex.stu-ff)") + (should (eq (clojure-test-face-at 12 29 "(CmlCase/.ve/yCom|pLex.stu-ff)") 'default))) (ert-deftest clojure-mode-syntax-table/mixedcase () @@ -312,76 +312,81 @@ POS." (should (eq (clojure-test-face-at 9 10 "(mxdCase/CmlCase)") 'default)) (should (eq (clojure-test-face-at 11 16 "(mxdCase/CmlCase)") 'default)) - (should (eq (clojure-test-face-at 2 8 "(mxdCase/veryCom|pLex.stu-ff)") + (should (eq (clojure-test-face-at 2 8 "(mxdCase/ve/yCom|pLex.stu-ff)") 'font-lock-type-face)) - (should (eq (clojure-test-face-at 9 10 "(mxdCase/veryCom|pLex.stu-ff)") + (should (eq (clojure-test-face-at 9 10 "(mxdCase/ve/yCom|pLex.stu-ff)") 'default)) - (should (eq (clojure-test-face-at 11 28 "(mxdCase/veryCom|pLex.stu-ff)") + (should (eq (clojure-test-face-at 11 28 "(mxdCase/ve/yCom|pLex.stu-ff)") 'default)) - (should (eq (clojure-test-face-at 2 8 "(mxdCase/.veryCom|pLex.stu-ff)") + (should (eq (clojure-test-face-at 2 8 "(mxdCase/.ve/yCom|pLex.stu-ff)") 'font-lock-type-face)) - (should (eq (clojure-test-face-at 9 10 "(mxdCase/.veryCom|pLex.stu-ff)") + (should (eq (clojure-test-face-at 9 10 "(mxdCase/.ve/yCom|pLex.stu-ff)") 'default)) - (should (eq (clojure-test-face-at 12 29 "(mxdCase/.veryCom|pLex.stu-ff)") + (should (eq (clojure-test-face-at 12 29 "(mxdCase/.ve/yCom|pLex.stu-ff)") 'default))) (ert-deftest clojure-mode-syntax-table/verycomplex () :tags '(fontification syntax-table) - (should (eq (clojure-test-face-at 3 21 " veryCom|pLex.stu-ff") 'default)) - (should (eq (clojure-test-face-at 3 21 " @veryCom|pLex.stu-ff") 'default)) - (should (eq (clojure-test-face-at 3 21 " #veryCom|pLex.stu-ff") 'default)) - (should (eq (clojure-test-face-at 3 21 " .veryCom|pLex.stu-ff") 'default)) - (should (eq (clojure-test-face-at 3 21 "#^veryCom|pLex.stu-ff") - 'font-lock-type-face)) ;; type-hint - (should (eq (clojure-test-face-at 3 21 " (veryCom|pLex.stu-ff)") 'default)) + (should (eq (clojure-test-face-at 3 4 " ve/yCom|pLex.stu-ff") + 'font-lock-type-face)) + (should (eq (clojure-test-face-at 5 21 " ve/yCom|pLex.stu-ff") 'default)) - (should (eq (clojure-test-face-at 3 21 " (veryCom|pLex.stu-ff/oneword)") + (should (eq (clojure-test-face-at 2 2 " @ve/yCom|pLex.stu-ff") 'nil)) + (should (eq (clojure-test-face-at 3 4 " @ve/yCom|pLex.stu-ff") 'font-lock-type-face)) - (should (eq (clojure-test-face-at 22 22 " (veryCom|pLex.stu-ff/oneword)") - 'default)) - (should (eq (clojure-test-face-at 23 29 " (veryCom|pLex.stu-ff/oneword)") - 'default)) + (should (eq (clojure-test-face-at 5 21 " @ve/yCom|pLex.stu-ff") 'default)) - (should (eq (clojure-test-face-at 3 21 " (veryCom|pLex.stu-ff/seg.mnt)") + (should (eq (clojure-test-face-at 2 4 " #ve/yCom|pLex.stu-ff") 'font-lock-type-face)) - (should (eq (clojure-test-face-at 22 22 " (veryCom|pLex.stu-ff/seg.mnt)") - 'default)) - (should (eq (clojure-test-face-at 22 29 " (veryCom|pLex.stu-ff/seg.mnt)") - 'default)) + (should (eq (clojure-test-face-at 5 21 " #ve/yCom|pLex.stu-ff") 'default)) - (should (eq (clojure-test-face-at 3 21 " (veryCom|pLex.stu-ff/mxdCase)") + (should (eq (clojure-test-face-at 2 4 " .ve/yCom|pLex.stu-ff") 'font-lock-type-face)) - (should (eq (clojure-test-face-at 22 22 " (veryCom|pLex.stu-ff/mxdCase)") + (should (eq (clojure-test-face-at 5 21 " .ve/yCom|pLex.stu-ff") 'default)) + + ;; type-hint + (should (eq (clojure-test-face-at 1 2 "#^ve/yCom|pLex.stu-ff") 'default)) + (should (eq (clojure-test-face-at 3 4 "#^ve/yCom|pLex.stu-ff") + 'font-lock-type-face)) + (should (eq (clojure-test-face-at 5 21 "#^ve/yCom|pLex.stu-ff") 'default)) + + (should (eq (clojure-test-face-at 3 4 " (ve/yCom|pLex.stu-ff)") + 'font-lock-type-face)) + (should (eq (clojure-test-face-at 5 21 " (ve/yCom|pLex.stu-ff)") 'default)) + + (should (eq (clojure-test-face-at 3 4 " (ve/yCom|pLex.stu-ff/oneword)") + 'font-lock-type-face)) + (should (eq (clojure-test-face-at 5 29 " (ve/yCom|pLex.stu-ff/oneword)") 'default)) - (should (eq (clojure-test-face-at 22 29 " (veryCom|pLex.stu-ff/mxdCase)") + + (should (eq (clojure-test-face-at 3 4 " (ve/yCom|pLex.stu-ff/seg.mnt)") + 'font-lock-type-face)) + (should (eq (clojure-test-face-at 5 29 " (ve/yCom|pLex.stu-ff/seg.mnt)") 'default)) - (should (eq (clojure-test-face-at 3 21 " (veryCom|pLex.stu-ff/CmlCase)") + (should (eq (clojure-test-face-at 3 4 " (ve/yCom|pLex.stu-ff/mxdCase)") 'font-lock-type-face)) - (should (eq (clojure-test-face-at 22 22 " (veryCom|pLex.stu-ff/CmlCase)") + (should (eq (clojure-test-face-at 5 29 " (ve/yCom|pLex.stu-ff/mxdCase)") 'default)) - (should (eq (clojure-test-face-at 22 29 " (veryCom|pLex.stu-ff/CmlCase)") + + (should (eq (clojure-test-face-at 3 4 " (ve/yCom|pLex.stu-ff/CmlCase)") + 'font-lock-type-face)) + (should (eq (clojure-test-face-at 5 29 " (ve/yCom|pLex.stu-ff/CmlCase)") 'default)) (should (eq (clojure-test-face-at - 3 21 " (veryCom|pLex.stu-ff/veryCom|pLex.stu-ff)") + 3 4 " (ve/yCom|pLex.stu-ff/ve/yCom|pLex.stu-ff)") 'font-lock-type-face)) (should (eq (clojure-test-face-at - 22 22 " (veryCom|pLex.stu-ff/veryCom|pLex.stu-ff)") - 'default)) - (should (eq (clojure-test-face-at - 23 41 " (veryCom|pLex.stu-ff/veryCom|pLex.stu-ff)") + 5 41 " (ve/yCom|pLex.stu-ff/ve/yCom|pLex.stu-ff)") 'default)) (should (eq (clojure-test-face-at - 3 21 " (veryCom|pLex.stu-ff/.veryCom|pLex.stu-ff)") + 3 4 " (ve/yCom|pLex.stu-ff/.ve/yCom|pLex.stu-ff)") 'font-lock-type-face)) (should (eq (clojure-test-face-at - 22 22 " (veryCom|pLex.stu-ff/.veryCom|pLex.stu-ff)") - 'default)) - (should (eq (clojure-test-face-at - 23 42 " (veryCom|pLex.stu-ff/.veryCom|pLex.stu-ff)") + 5 42 " (ve/yCom|pLex.stu-ff/.ve/yCom|pLex.stu-ff)") 'default))) (ert-deftest clojure-mode-syntax-table/kw-oneword () @@ -418,18 +423,41 @@ POS." (should (eq (clojure-test-face-at 11 17 "{:oneword/mxdCase 0}") 'clojure-keyword-face)) - (should (eq (clojure-test-face-at 3 9 "{:oneword/veryCom|pLex.stu-ff 0}") + (should (eq (clojure-test-face-at 3 9 "{:oneword/ve/yCom|pLex.stu-ff 0}") 'font-lock-type-face)) - (should (eq (clojure-test-face-at 10 10 "{:oneword/veryCom|pLex.stu-ff 0}") + (should (eq (clojure-test-face-at 10 10 "{:oneword/ve/yCom|pLex.stu-ff 0}") 'default)) - (should (eq (clojure-test-face-at 11 29 "{:oneword/veryCom|pLex.stu-ff 0}") + (should (eq (clojure-test-face-at 11 29 "{:oneword/ve/yCom|pLex.stu-ff 0}") 'clojure-keyword-face)) - (should (eq (clojure-test-face-at 3 9 "{:oneword/.veryCom|pLex.stu-ff 0}") + (should (eq (clojure-test-face-at 3 9 "{:oneword/.ve/yCom|pLex.stu-ff 0}") 'font-lock-type-face)) - (should (eq (clojure-test-face-at 10 10 "{:oneword/.veryCom|pLex.stu-ff 0}") + (should (eq (clojure-test-face-at 10 10 "{:oneword/.ve/yCom|pLex.stu-ff 0}") 'default)) - (should (eq (clojure-test-face-at 11 30 "{:oneword/.veryCom|pLex.stu-ff 0}") + (should (eq (clojure-test-face-at 11 30 "{:oneword/.ve/yCom|pLex.stu-ff 0}") + 'clojure-keyword-face))) + +(ert-deftest clojure-mode-syntax-table/kw-namespaced () + :tags '(fontification syntax-table) + (should (eq (clojure-test-face-at 1 5 "::foo") 'clojure-keyword-face)) + (should (eq (clojure-test-face-at 1 9 ":_::_:foo") 'clojure-keyword-face)) + (should (eq (clojure-test-face-at 1 8 ":_:_:foo") 'clojure-keyword-face)) + (should (eq (clojure-test-face-at 1 9 ":foo/:bar") 'clojure-keyword-face)) + (should (eq (clojure-test-face-at 1 7 "::_:foo") 'clojure-keyword-face)) + (should (eq (clojure-test-face-at 1 9 "::_:_:foo") 'clojure-keyword-face)) + + (should (eq (clojure-test-face-at 1 1 ":_:_:foo/_") 'clojure-keyword-face)) + (should (eq (clojure-test-face-at 2 8 ":_:_:foo/_") 'font-lock-type-face)) + (should (eq (clojure-test-face-at 9 9 ":_:_:foo/_") 'default)) + (should (eq (clojure-test-face-at 10 10 ":_:_:foo/_") 'clojure-keyword-face)) + + (should (eq (clojure-test-face-at 10 12 ":_:_:foo/bar") + 'clojure-keyword-face)) + (should (eq (clojure-test-face-at 10 16 ":_:_:foo/bar/eee") + 'clojure-keyword-face)) + (should (eq (clojure-test-face-at 10 17 ":_:_:foo/bar_:foo") + 'clojure-keyword-face)) + (should (eq (clojure-test-face-at 10 19 ":_:_:foo/bar_:_:foo") 'clojure-keyword-face))) (ert-deftest clojure-mode-syntax-table/kw-segment () @@ -466,18 +494,18 @@ POS." (should (eq (clojure-test-face-at 11 17 "{:seg.mnt/mxdCase 0}") 'clojure-keyword-face)) - (should (eq (clojure-test-face-at 3 9 "{:seg.mnt/veryCom|pLex.stu-ff 0}") + (should (eq (clojure-test-face-at 3 9 "{:seg.mnt/ve/yCom|pLex.stu-ff 0}") 'font-lock-type-face)) - (should (eq (clojure-test-face-at 10 10 "{:seg.mnt/veryCom|pLex.stu-ff 0}") + (should (eq (clojure-test-face-at 10 10 "{:seg.mnt/ve/yCom|pLex.stu-ff 0}") 'default)) - (should (eq (clojure-test-face-at 11 29 "{:seg.mnt/veryCom|pLex.stu-ff 0}") + (should (eq (clojure-test-face-at 11 29 "{:seg.mnt/ve/yCom|pLex.stu-ff 0}") 'clojure-keyword-face)) - (should (eq (clojure-test-face-at 3 9 "{:seg.mnt/.veryCom|pLex.stu-ff 0}") + (should (eq (clojure-test-face-at 3 9 "{:seg.mnt/.ve/yCom|pLex.stu-ff 0}") 'font-lock-type-face)) - (should (eq (clojure-test-face-at 10 10 "{:seg.mnt/.veryCom|pLex.stu-ff 0}") + (should (eq (clojure-test-face-at 10 10 "{:seg.mnt/.ve/yCom|pLex.stu-ff 0}") 'default)) - (should (eq (clojure-test-face-at 11 30 "{:seg.mnt/.veryCom|pLex.stu-ff 0}") + (should (eq (clojure-test-face-at 11 30 "{:seg.mnt/.ve/yCom|pLex.stu-ff 0}") 'clojure-keyword-face))) (ert-deftest clojure-mode-syntax-table/kw-camelcase () @@ -509,18 +537,18 @@ POS." (should (eq (clojure-test-face-at 10 10 "{:CmlCase/mxdCase 0}") 'default)) (should (eq (clojure-test-face-at 11 17 "{:CmlCase/mxdCase 0}") 'clojure-keyword-face)) - (should (eq (clojure-test-face-at 3 9 "{:CmlCase/veryCom|pLex.stu-ff 0}") + (should (eq (clojure-test-face-at 3 9 "{:CmlCase/ve/yCom|pLex.stu-ff 0}") 'font-lock-type-face)) - (should (eq (clojure-test-face-at 10 10 "{:CmlCase/veryCom|pLex.stu-ff 0}") + (should (eq (clojure-test-face-at 10 10 "{:CmlCase/ve/yCom|pLex.stu-ff 0}") 'default)) - (should (eq (clojure-test-face-at 11 29 "{:CmlCase/veryCom|pLex.stu-ff 0}") + (should (eq (clojure-test-face-at 11 29 "{:CmlCase/ve/yCom|pLex.stu-ff 0}") 'clojure-keyword-face)) - (should (eq (clojure-test-face-at 3 9 "{:CmlCase/.veryCom|pLex.stu-ff 0}") + (should (eq (clojure-test-face-at 3 9 "{:CmlCase/.ve/yCom|pLex.stu-ff 0}") 'font-lock-type-face)) - (should (eq (clojure-test-face-at 10 10 "{:CmlCase/.veryCom|pLex.stu-ff 0}") + (should (eq (clojure-test-face-at 10 10 "{:CmlCase/.ve/yCom|pLex.stu-ff 0}") 'default)) - (should (eq (clojure-test-face-at 11 30 "{:CmlCase/.veryCom|pLex.stu-ff 0}") + (should (eq (clojure-test-face-at 11 30 "{:CmlCase/.ve/yCom|pLex.stu-ff 0}") 'clojure-keyword-face))) (ert-deftest clojure-mode-syntax-table/kw-mixedcase () @@ -557,77 +585,116 @@ POS." (should (eq (clojure-test-face-at 11 17 "{:mxdCase/mxdCase 0}") 'clojure-keyword-face)) - (should (eq (clojure-test-face-at 3 9 "{:mxdCase/veryCom|pLex.stu-ff 0}") + (should (eq (clojure-test-face-at 3 9 "{:mxdCase/ve/yCom|pLex.stu-ff 0}") 'font-lock-type-face)) - (should (eq (clojure-test-face-at 10 10 "{:mxdCase/veryCom|pLex.stu-ff 0}") + (should (eq (clojure-test-face-at 10 10 "{:mxdCase/ve/yCom|pLex.stu-ff 0}") 'default)) - (should (eq (clojure-test-face-at 11 29 "{:mxdCase/veryCom|pLex.stu-ff 0}") + (should (eq (clojure-test-face-at 11 29 "{:mxdCase/ve/yCom|pLex.stu-ff 0}") 'clojure-keyword-face)) - (should (eq (clojure-test-face-at 3 9 "{:mxdCase/.veryCom|pLex.stu-ff 0}") + (should (eq (clojure-test-face-at 3 9 "{:mxdCase/.ve/yCom|pLex.stu-ff 0}") 'font-lock-type-face)) - (should (eq (clojure-test-face-at 10 10 "{:mxdCase/.veryCom|pLex.stu-ff 0}") + (should (eq (clojure-test-face-at 10 10 "{:mxdCase/.ve/yCom|pLex.stu-ff 0}") 'default)) - (should (eq (clojure-test-face-at 11 30 "{:mxdCase/.veryCom|pLex.stu-ff 0}") + (should (eq (clojure-test-face-at 11 30 "{:mxdCase/.ve/yCom|pLex.stu-ff 0}") 'clojure-keyword-face))) (ert-deftest clojure-mode-syntax-table/kw-verycomplex () :tags '(fontification syntax-table) - (should (eq (clojure-test-face-at 3 9 " :veryCom|pLex.stu-ff") + (should (eq (clojure-test-face-at 3 4 " :ve/yCom|pLex.stu-ff") + 'font-lock-type-face)) + (should (eq (clojure-test-face-at 5 5 " :ve/yCom|pLex.stu-ff") + 'default)) + (should (eq (clojure-test-face-at 6 21 " :ve/yCom|pLex.stu-ff") 'clojure-keyword-face)) - (should (eq (clojure-test-face-at 3 9 "{:veryCom|pLex.stu-ff 0}") + + (should (eq (clojure-test-face-at 2 2 "{:ve/yCom|pLex.stu-ff 0}") 'clojure-keyword-face)) - (should (eq (clojure-test-face-at 3 9 "{:#veryCom|pLex.stu-ff 0}") + (should (eq (clojure-test-face-at 3 4 "{:ve/yCom|pLex.stu-ff 0}") + 'font-lock-type-face)) + (should (eq (clojure-test-face-at 5 5 "{:ve/yCom|pLex.stu-ff 0}") + 'default)) + (should (eq (clojure-test-face-at 6 21 "{:ve/yCom|pLex.stu-ff 0}") + 'clojure-keyword-face)) + + (should (eq (clojure-test-face-at 2 2 "{:#ve/yCom|pLex.stu-ff 0}") 'clojure-keyword-face)) - (should (eq (clojure-test-face-at 3 9 "{:.veryCom|pLex.stu-ff 0}") + (should (eq (clojure-test-face-at 3 5 "{:#ve/yCom|pLex.stu-ff 0}") + 'font-lock-type-face)) + (should (eq (clojure-test-face-at 6 6 "{:#ve/yCom|pLex.stu-ff 0}") + 'default)) + (should (eq (clojure-test-face-at 7 22 "{:#ve/yCom|pLex.stu-ff 0}") 'clojure-keyword-face)) - (should (eq (clojure-test-face-at 3 21 "{:veryCom|pLex.stu-ff/oneword 0}") + (should (eq (clojure-test-face-at 2 2 "{:.ve/yCom|pLex.stu-ff 0}") + 'clojure-keyword-face)) + (should (eq (clojure-test-face-at 3 5 "{:.ve/yCom|pLex.stu-ff 0}") 'font-lock-type-face)) - (should (eq (clojure-test-face-at 22 22 "{:veryCom|pLex.stu-ff/oneword 0}") + (should (eq (clojure-test-face-at 6 6 "{:.ve/yCom|pLex.stu-ff 0}") 'default)) - (should (eq (clojure-test-face-at 23 29 "{:veryCom|pLex.stu-ff/oneword 0}") + (should (eq (clojure-test-face-at 7 22 "{:.ve/yCom|pLex.stu-ff 0}") 'clojure-keyword-face)) - (should (eq (clojure-test-face-at 3 9 "{:veryCom|pLex.stu-ff/seg.mnt 0}") + (should (eq (clojure-test-face-at 2 2 "{:ve/yCom|pLex.stu-ff/oneword 0}") + 'clojure-keyword-face)) + (should (eq (clojure-test-face-at 3 4 "{:ve/yCom|pLex.stu-ff/oneword 0}") + 'font-lock-type-face)) + (should (eq (clojure-test-face-at 5 5 "{:ve/yCom|pLex.stu-ff/oneword 0}") + 'default)) + (should (eq (clojure-test-face-at 6 29 "{:ve/yCom|pLex.stu-ff/oneword 0}") + 'clojure-keyword-face)) + + (should (eq (clojure-test-face-at 2 2 "{:ve/yCom|pLex.stu-ff/seg.mnt 0}") + 'clojure-keyword-face)) + (should (eq (clojure-test-face-at 3 4 "{:ve/yCom|pLex.stu-ff/seg.mnt 0}") 'font-lock-type-face)) - (should (eq (clojure-test-face-at 22 22 "{:veryCom|pLex.stu-ff/seg.mnt 0}") + (should (eq (clojure-test-face-at 5 5 "{:ve/yCom|pLex.stu-ff/seg.mnt 0}") 'default)) - (should (eq (clojure-test-face-at 23 29 "{:veryCom|pLex.stu-ff/seg.mnt 0}") + (should (eq (clojure-test-face-at 6 29 "{:ve/yCom|pLex.stu-ff/seg.mnt 0}") 'clojure-keyword-face)) - (should (eq (clojure-test-face-at 3 9 "{:veryCom|pLex.stu-ff/CmlCase 0}") + (should (eq (clojure-test-face-at 2 2 "{:ve/yCom|pLex.stu-ff/ClmCase 0}") + 'clojure-keyword-face)) + (should (eq (clojure-test-face-at 3 4 "{:ve/yCom|pLex.stu-ff/ClmCase 0}") 'font-lock-type-face)) - (should (eq (clojure-test-face-at 22 22 "{:veryCom|pLex.stu-ff/CmlCase 0}") + (should (eq (clojure-test-face-at 5 5 "{:ve/yCom|pLex.stu-ff/ClmCase 0}") 'default)) - (should (eq (clojure-test-face-at 23 29 "{:veryCom|pLex.stu-ff/CmlCase 0}") + (should (eq (clojure-test-face-at 6 29 "{:ve/yCom|pLex.stu-ff/ClmCase 0}") 'clojure-keyword-face)) - (should (eq (clojure-test-face-at 3 9 "{:veryCom|pLex.stu-ff/mxdCase 0}") + (should (eq (clojure-test-face-at 2 2 "{:ve/yCom|pLex.stu-ff/mxdCase 0}") + 'clojure-keyword-face)) + (should (eq (clojure-test-face-at 3 4 "{:ve/yCom|pLex.stu-ff/mxdCase 0}") 'font-lock-type-face)) - (should (eq (clojure-test-face-at 22 22 "{:veryCom|pLex.stu-ff/mxdCase 0}") + (should (eq (clojure-test-face-at 5 5 "{:ve/yCom|pLex.stu-ff/mxdCase 0}") 'default)) - (should (eq (clojure-test-face-at 23 29 "{:veryCom|pLex.stu-ff/mxdCase 0}") + (should (eq (clojure-test-face-at 6 29 "{:ve/yCom|pLex.stu-ff/mxdCase 0}") 'clojure-keyword-face)) (should (eq (clojure-test-face-at - 3 21 "{:veryCom|pLex.stu-ff/veryCom|pLex.stu-ff 0}") + 2 2 "{:ve/yCom|pLex.stu-ff/ve/yCom|pLex.stu-ff 0}") + 'clojure-keyword-face)) + (should (eq (clojure-test-face-at + 3 4 "{:ve/yCom|pLex.stu-ff/ve/yCom|pLex.stu-ff 0}") 'font-lock-type-face)) (should (eq (clojure-test-face-at - 22 22 "{:veryCom|pLex.stu-ff/veryCom|pLex.stu-ff 0}") + 5 5 "{:ve/yCom|pLex.stu-ff/ve/yCom|pLex.stu-ff 0}") 'default)) (should (eq (clojure-test-face-at - 23 41 "{:veryCom|pLex.stu-ff/veryCom|pLex.stu-ff 0}") + 6 41 "{:ve/yCom|pLex.stu-ff/ve/yCom|pLex.stu-ff 0}") 'clojure-keyword-face)) (should (eq (clojure-test-face-at - 3 21 "{:veryCom|pLex.stu-ff/.veryCom|pLex.stu-ff 0}") + 2 2 "{:ve/yCom|pLex.stu-ff/.ve/yCom|pLex.stu-ff 0}") + 'clojure-keyword-face)) + (should (eq (clojure-test-face-at + 3 4 "{:ve/yCom|pLex.stu-ff/.ve/yCom|pLex.stu-ff 0}") 'font-lock-type-face)) (should (eq (clojure-test-face-at - 22 22 "{:veryCom|pLex.stu-ff/.veryCom|pLex.stu-ff 0}") + 5 5 "{:ve/yCom|pLex.stu-ff/.ve/yCom|pLex.stu-ff 0}") 'default)) (should (eq (clojure-test-face-at - 23 42 "{:veryCom|pLex.stu-ff/.veryCom|pLex.stu-ff 0}") + 6 42 "{:ve/yCom|pLex.stu-ff/.ve/yCom|pLex.stu-ff 0}") 'clojure-keyword-face))) (ert-deftest clojure-mode-syntax-table/namespaced-def () From 078cd1e5f9d27009ca2b5a9f8467f20f970c742e Mon Sep 17 00:00:00 2001 From: Bost Date: Mon, 26 Mar 2018 17:58:58 +0200 Subject: [PATCH 118/379] Fix regression to CIDER's dynamic syntax highlighting #474 --- clojure-mode-font-lock-test.el | 168 ++++++++++++++++----------------- 1 file changed, 84 insertions(+), 84 deletions(-) diff --git a/clojure-mode-font-lock-test.el b/clojure-mode-font-lock-test.el index a2a8155..69f5bf6 100644 --- a/clojure-mode-font-lock-test.el +++ b/clojure-mode-font-lock-test.el @@ -123,10 +123,10 @@ POS." :tags '(fontification syntax-table) (should (equal (clojure-test-face-at 1 2 "#_") - 'default)) + nil)) (should (equal (clojure-test-face-at 1 2 "#_ \n;; some crap\n (lala 0101\n lao\n\n 0 0i)") - 'default)) + nil)) (should (equal (clojure-test-face-at 5 41 "#_ \n;; some crap\n (lala 0101\n lao\n\n 0 0i)") 'font-lock-comment-face))) @@ -153,197 +153,197 @@ POS." (ert-deftest clojure-mode-syntax-table/oneword () :tags '(fontification syntax-table) - (should (eq (clojure-test-face-at 2 8 " oneword") 'default)) - (should (eq (clojure-test-face-at 2 8 "@oneword") 'default)) - (should (eq (clojure-test-face-at 2 8 "#oneword") 'default)) - (should (eq (clojure-test-face-at 2 8 ".oneword") 'default)) + (should (eq (clojure-test-face-at 2 8 " oneword") nil)) + (should (eq (clojure-test-face-at 2 8 "@oneword") nil)) + (should (eq (clojure-test-face-at 2 8 "#oneword") nil)) + (should (eq (clojure-test-face-at 2 8 ".oneword") nil)) (should (eq (clojure-test-face-at 3 9 "#^oneword") 'font-lock-type-face)) ;; type-hint - (should (eq (clojure-test-face-at 2 8 "(oneword)") 'default)) + (should (eq (clojure-test-face-at 2 8 "(oneword)") nil)) (should (eq (clojure-test-face-at 2 8 "(oneword/oneword)") 'font-lock-type-face)) - (should (eq (clojure-test-face-at 9 10 "(oneword/oneword)") 'default)) - (should (eq (clojure-test-face-at 11 16 "(oneword/oneword)") 'default)) + (should (eq (clojure-test-face-at 9 10 "(oneword/oneword)") nil)) + (should (eq (clojure-test-face-at 11 16 "(oneword/oneword)") nil)) (should (eq (clojure-test-face-at 2 8 "(oneword/seg.mnt)") 'font-lock-type-face)) - (should (eq (clojure-test-face-at 9 10 "(oneword/seg.mnt)") 'default)) - (should (eq (clojure-test-face-at 11 16 "(oneword/seg.mnt)") 'default)) + (should (eq (clojure-test-face-at 9 10 "(oneword/seg.mnt)") nil)) + (should (eq (clojure-test-face-at 11 16 "(oneword/seg.mnt)") nil)) (should (eq (clojure-test-face-at 2 8 "(oneword/mxdCase)") 'font-lock-type-face)) - (should (eq (clojure-test-face-at 9 10 "(oneword/mxdCase)") 'default)) - (should (eq (clojure-test-face-at 11 16 "(oneword/mxdCase)") 'default)) + (should (eq (clojure-test-face-at 9 10 "(oneword/mxdCase)") nil)) + (should (eq (clojure-test-face-at 11 16 "(oneword/mxdCase)") nil)) (should (eq (clojure-test-face-at 2 8 "(oneword/CmlCase)") 'font-lock-type-face)) - (should (eq (clojure-test-face-at 9 10 "(oneword/CmlCase)") 'default)) - (should (eq (clojure-test-face-at 11 16 "(oneword/CmlCase)") 'default)) + (should (eq (clojure-test-face-at 9 10 "(oneword/CmlCase)") nil)) + (should (eq (clojure-test-face-at 11 16 "(oneword/CmlCase)") nil)) (should (eq (clojure-test-face-at 2 8 "(oneword/ve/yCom|pLex.stu-ff)") 'font-lock-type-face)) (should (eq (clojure-test-face-at 9 10 "(oneword/ve/yCom|pLex.stu-ff)") - 'default)) + nil)) (should (eq (clojure-test-face-at 11 28 "(oneword/ve/yCom|pLex.stu-ff)") - 'default)) + nil)) (should (eq (clojure-test-face-at 2 8 "(oneword/.ve/yCom|pLex.stu-ff)") 'font-lock-type-face)) (should (eq (clojure-test-face-at 9 10 "(oneword/.ve/yCom|pLex.stu-ff)") - 'default)) + nil)) (should (eq (clojure-test-face-at 12 29 "(oneword/.ve/yCom|pLex.stu-ff)") - 'default))) + nil))) (ert-deftest clojure-mode-syntax-table/segment () :tags '(fontification syntax-table) - (should (eq (clojure-test-face-at 2 8 " seg.mnt") 'default)) - (should (eq (clojure-test-face-at 2 8 "@seg.mnt") 'default)) - (should (eq (clojure-test-face-at 2 8 "#seg.mnt") 'default)) - (should (eq (clojure-test-face-at 2 8 ".seg.mnt") 'default)) + (should (eq (clojure-test-face-at 2 8 " seg.mnt") nil)) + (should (eq (clojure-test-face-at 2 8 "@seg.mnt") nil)) + (should (eq (clojure-test-face-at 2 8 "#seg.mnt") nil)) + (should (eq (clojure-test-face-at 2 8 ".seg.mnt") nil)) (should (eq (clojure-test-face-at 3 9 "#^seg.mnt") 'font-lock-type-face)) ;; type-hint - (should (eq (clojure-test-face-at 2 8 "(seg.mnt)") 'default)) + (should (eq (clojure-test-face-at 2 8 "(seg.mnt)") nil)) (should (eq (clojure-test-face-at 2 8 "(seg.mnt/oneword)") 'font-lock-type-face)) - (should (eq (clojure-test-face-at 9 10 "(seg.mnt/oneword)") 'default)) - (should (eq (clojure-test-face-at 11 16 "(seg.mnt/oneword)") 'default)) + (should (eq (clojure-test-face-at 9 10 "(seg.mnt/oneword)") nil)) + (should (eq (clojure-test-face-at 11 16 "(seg.mnt/oneword)") nil)) (should (eq (clojure-test-face-at 2 8 "(seg.mnt/seg.mnt)") 'font-lock-type-face)) - (should (eq (clojure-test-face-at 9 10 "(seg.mnt/seg.mnt)") 'default)) - (should (eq (clojure-test-face-at 11 16 "(seg.mnt/seg.mnt)") 'default)) + (should (eq (clojure-test-face-at 9 10 "(seg.mnt/seg.mnt)") nil)) + (should (eq (clojure-test-face-at 11 16 "(seg.mnt/seg.mnt)") nil)) (should (eq (clojure-test-face-at 2 8 "(seg.mnt/mxdCase)") 'font-lock-type-face)) - (should (eq (clojure-test-face-at 9 10 "(seg.mnt/mxdCase)") 'default)) - (should (eq (clojure-test-face-at 11 16 "(seg.mnt/mxdCase)") 'default)) + (should (eq (clojure-test-face-at 9 10 "(seg.mnt/mxdCase)") nil)) + (should (eq (clojure-test-face-at 11 16 "(seg.mnt/mxdCase)") nil)) (should (eq (clojure-test-face-at 2 8 "(seg.mnt/CmlCase)") 'font-lock-type-face)) - (should (eq (clojure-test-face-at 9 10 "(seg.mnt/CmlCase)") 'default)) - (should (eq (clojure-test-face-at 11 16 "(seg.mnt/CmlCase)") 'default)) + (should (eq (clojure-test-face-at 9 10 "(seg.mnt/CmlCase)") nil)) + (should (eq (clojure-test-face-at 11 16 "(seg.mnt/CmlCase)") nil)) (should (eq (clojure-test-face-at 2 8 "(seg.mnt/ve/yCom|pLex.stu-ff)") 'font-lock-type-face)) (should (eq (clojure-test-face-at 9 10 "(seg.mnt/ve/yCom|pLex.stu-ff)") - 'default)) + nil)) (should (eq (clojure-test-face-at 11 28 "(seg.mnt/ve/yCom|pLex.stu-ff)") - 'default)) + nil)) (should (eq (clojure-test-face-at 2 8 "(seg.mnt/.ve/yCom|pLex.stu-ff)") 'font-lock-type-face)) (should (eq (clojure-test-face-at 9 10 "(seg.mnt/.ve/yCom|pLex.stu-ff)") - 'default)) + nil)) (should (eq (clojure-test-face-at 12 29 "(seg.mnt/.ve/yCom|pLex.stu-ff)") - 'default))) + nil))) (ert-deftest clojure-mode-syntax-table/camelcase () :tags '(fontification syntax-table) - (should (eq (clojure-test-face-at 2 8 " CmlCase") 'default)) - (should (eq (clojure-test-face-at 2 8 "@CmlCase") 'default)) - (should (eq (clojure-test-face-at 2 8 "#CmlCase") 'default)) - (should (eq (clojure-test-face-at 2 8 ".CmlCase") 'default)) + (should (eq (clojure-test-face-at 2 8 " CmlCase") nil)) + (should (eq (clojure-test-face-at 2 8 "@CmlCase") nil)) + (should (eq (clojure-test-face-at 2 8 "#CmlCase") nil)) + (should (eq (clojure-test-face-at 2 8 ".CmlCase") nil)) (should (eq (clojure-test-face-at 3 9 "#^CmlCase") 'font-lock-type-face)) ;; type-hint - (should (eq (clojure-test-face-at 2 8 "(CmlCase)") 'default)) + (should (eq (clojure-test-face-at 2 8 "(CmlCase)") nil)) (should (eq (clojure-test-face-at 2 8 "(CmlCase/oneword)") 'font-lock-type-face)) - (should (eq (clojure-test-face-at 9 10 "(CmlCase/oneword)") 'default)) - (should (eq (clojure-test-face-at 11 16 "(CmlCase/oneword)") 'default)) + (should (eq (clojure-test-face-at 9 10 "(CmlCase/oneword)") nil)) + (should (eq (clojure-test-face-at 11 16 "(CmlCase/oneword)") nil)) (should (eq (clojure-test-face-at 2 8 "(CmlCase/seg.mnt)") 'font-lock-type-face)) - (should (eq (clojure-test-face-at 9 10 "(CmlCase/seg.mnt)") 'default)) - (should (eq (clojure-test-face-at 11 16 "(CmlCase/seg.mnt)") 'default)) + (should (eq (clojure-test-face-at 9 10 "(CmlCase/seg.mnt)") nil)) + (should (eq (clojure-test-face-at 11 16 "(CmlCase/seg.mnt)") nil)) (should (eq (clojure-test-face-at 2 8 "(CmlCase/mxdCase)") 'font-lock-type-face)) - (should (eq (clojure-test-face-at 9 10 "(CmlCase/mxdCase)") 'default)) - (should (eq (clojure-test-face-at 11 16 "(CmlCase/mxdCase)") 'default)) + (should (eq (clojure-test-face-at 9 10 "(CmlCase/mxdCase)") nil)) + (should (eq (clojure-test-face-at 11 16 "(CmlCase/mxdCase)") nil)) (should (eq (clojure-test-face-at 2 8 "(CmlCase/CmlCase)") 'font-lock-type-face)) - (should (eq (clojure-test-face-at 9 10 "(CmlCase/CmlCase)") 'default)) - (should (eq (clojure-test-face-at 11 16 "(CmlCase/CmlCase)") 'default)) + (should (eq (clojure-test-face-at 9 10 "(CmlCase/CmlCase)") nil)) + (should (eq (clojure-test-face-at 11 16 "(CmlCase/CmlCase)") nil)) (should (eq (clojure-test-face-at 2 8 "(CmlCase/ve/yCom|pLex.stu-ff)") 'font-lock-type-face)) (should (eq (clojure-test-face-at 9 10 "(CmlCase/ve/yCom|pLex.stu-ff)") - 'default)) + nil)) (should (eq (clojure-test-face-at 11 28 "(CmlCase/ve/yCom|pLex.stu-ff)") - 'default)) + nil)) (should (eq (clojure-test-face-at 2 8 "(CmlCase/.ve/yCom|pLex.stu-ff)") 'font-lock-type-face)) (should (eq (clojure-test-face-at 9 10 "(CmlCase/.ve/yCom|pLex.stu-ff)") - 'default)) + nil)) (should (eq (clojure-test-face-at 12 29 "(CmlCase/.ve/yCom|pLex.stu-ff)") - 'default))) + nil))) (ert-deftest clojure-mode-syntax-table/mixedcase () :tags '(fontification syntax-table) - (should (eq (clojure-test-face-at 2 8 " mxdCase") 'default)) - (should (eq (clojure-test-face-at 2 8 "@mxdCase") 'default)) - (should (eq (clojure-test-face-at 2 8 "#mxdCase") 'default)) - (should (eq (clojure-test-face-at 2 8 ".mxdCase") 'default)) + (should (eq (clojure-test-face-at 2 8 " mxdCase") nil)) + (should (eq (clojure-test-face-at 2 8 "@mxdCase") nil)) + (should (eq (clojure-test-face-at 2 8 "#mxdCase") nil)) + (should (eq (clojure-test-face-at 2 8 ".mxdCase") nil)) (should (eq (clojure-test-face-at 3 9 "#^mxdCase") 'font-lock-type-face)) ;; type-hint - (should (eq (clojure-test-face-at 2 8 "(mxdCase)") 'default)) + (should (eq (clojure-test-face-at 2 8 "(mxdCase)") nil)) (should (eq (clojure-test-face-at 2 8 "(mxdCase/oneword)") 'font-lock-type-face)) - (should (eq (clojure-test-face-at 9 10 "(mxdCase/oneword)") 'default)) - (should (eq (clojure-test-face-at 11 16 "(mxdCase/oneword)") 'default)) + (should (eq (clojure-test-face-at 9 10 "(mxdCase/oneword)") nil)) + (should (eq (clojure-test-face-at 11 16 "(mxdCase/oneword)") nil)) (should (eq (clojure-test-face-at 2 8 "(mxdCase/seg.mnt)") 'font-lock-type-face)) - (should (eq (clojure-test-face-at 9 10 "(mxdCase/seg.mnt)") 'default)) - (should (eq (clojure-test-face-at 11 16 "(mxdCase/seg.mnt)") 'default)) + (should (eq (clojure-test-face-at 9 10 "(mxdCase/seg.mnt)") nil)) + (should (eq (clojure-test-face-at 11 16 "(mxdCase/seg.mnt)") nil)) (should (eq (clojure-test-face-at 2 8 "(mxdCase/mxdCase)") 'font-lock-type-face)) - (should (eq (clojure-test-face-at 9 10 "(mxdCase/mxdCase)") 'default)) - (should (eq (clojure-test-face-at 11 16 "(mxdCase/mxdCase)") 'default)) + (should (eq (clojure-test-face-at 9 10 "(mxdCase/mxdCase)") nil)) + (should (eq (clojure-test-face-at 11 16 "(mxdCase/mxdCase)") nil)) (should (eq (clojure-test-face-at 2 8 "(mxdCase/CmlCase)") 'font-lock-type-face)) - (should (eq (clojure-test-face-at 9 10 "(mxdCase/CmlCase)") 'default)) - (should (eq (clojure-test-face-at 11 16 "(mxdCase/CmlCase)") 'default)) + (should (eq (clojure-test-face-at 9 10 "(mxdCase/CmlCase)") nil)) + (should (eq (clojure-test-face-at 11 16 "(mxdCase/CmlCase)") nil)) (should (eq (clojure-test-face-at 2 8 "(mxdCase/ve/yCom|pLex.stu-ff)") 'font-lock-type-face)) (should (eq (clojure-test-face-at 9 10 "(mxdCase/ve/yCom|pLex.stu-ff)") - 'default)) + nil)) (should (eq (clojure-test-face-at 11 28 "(mxdCase/ve/yCom|pLex.stu-ff)") - 'default)) + nil)) (should (eq (clojure-test-face-at 2 8 "(mxdCase/.ve/yCom|pLex.stu-ff)") 'font-lock-type-face)) (should (eq (clojure-test-face-at 9 10 "(mxdCase/.ve/yCom|pLex.stu-ff)") - 'default)) + nil)) (should (eq (clojure-test-face-at 12 29 "(mxdCase/.ve/yCom|pLex.stu-ff)") - 'default))) + nil))) (ert-deftest clojure-mode-syntax-table/verycomplex () :tags '(fontification syntax-table) (should (eq (clojure-test-face-at 3 4 " ve/yCom|pLex.stu-ff") 'font-lock-type-face)) - (should (eq (clojure-test-face-at 5 21 " ve/yCom|pLex.stu-ff") 'default)) + (should (eq (clojure-test-face-at 5 21 " ve/yCom|pLex.stu-ff") nil)) - (should (eq (clojure-test-face-at 2 2 " @ve/yCom|pLex.stu-ff") 'nil)) + (should (eq (clojure-test-face-at 2 2 " @ve/yCom|pLex.stu-ff") nil)) (should (eq (clojure-test-face-at 3 4 " @ve/yCom|pLex.stu-ff") 'font-lock-type-face)) - (should (eq (clojure-test-face-at 5 21 " @ve/yCom|pLex.stu-ff") 'default)) + (should (eq (clojure-test-face-at 5 21 " @ve/yCom|pLex.stu-ff") nil)) (should (eq (clojure-test-face-at 2 4 " #ve/yCom|pLex.stu-ff") 'font-lock-type-face)) - (should (eq (clojure-test-face-at 5 21 " #ve/yCom|pLex.stu-ff") 'default)) + (should (eq (clojure-test-face-at 5 21 " #ve/yCom|pLex.stu-ff") nil)) (should (eq (clojure-test-face-at 2 4 " .ve/yCom|pLex.stu-ff") 'font-lock-type-face)) - (should (eq (clojure-test-face-at 5 21 " .ve/yCom|pLex.stu-ff") 'default)) + (should (eq (clojure-test-face-at 5 21 " .ve/yCom|pLex.stu-ff") nil)) ;; type-hint (should (eq (clojure-test-face-at 1 2 "#^ve/yCom|pLex.stu-ff") 'default)) @@ -353,41 +353,41 @@ POS." (should (eq (clojure-test-face-at 3 4 " (ve/yCom|pLex.stu-ff)") 'font-lock-type-face)) - (should (eq (clojure-test-face-at 5 21 " (ve/yCom|pLex.stu-ff)") 'default)) + (should (eq (clojure-test-face-at 5 21 " (ve/yCom|pLex.stu-ff)") nil)) (should (eq (clojure-test-face-at 3 4 " (ve/yCom|pLex.stu-ff/oneword)") 'font-lock-type-face)) (should (eq (clojure-test-face-at 5 29 " (ve/yCom|pLex.stu-ff/oneword)") - 'default)) + nil)) (should (eq (clojure-test-face-at 3 4 " (ve/yCom|pLex.stu-ff/seg.mnt)") 'font-lock-type-face)) (should (eq (clojure-test-face-at 5 29 " (ve/yCom|pLex.stu-ff/seg.mnt)") - 'default)) + nil)) (should (eq (clojure-test-face-at 3 4 " (ve/yCom|pLex.stu-ff/mxdCase)") 'font-lock-type-face)) (should (eq (clojure-test-face-at 5 29 " (ve/yCom|pLex.stu-ff/mxdCase)") - 'default)) + nil)) (should (eq (clojure-test-face-at 3 4 " (ve/yCom|pLex.stu-ff/CmlCase)") 'font-lock-type-face)) (should (eq (clojure-test-face-at 5 29 " (ve/yCom|pLex.stu-ff/CmlCase)") - 'default)) + nil)) (should (eq (clojure-test-face-at 3 4 " (ve/yCom|pLex.stu-ff/ve/yCom|pLex.stu-ff)") 'font-lock-type-face)) (should (eq (clojure-test-face-at 5 41 " (ve/yCom|pLex.stu-ff/ve/yCom|pLex.stu-ff)") - 'default)) + nil)) (should (eq (clojure-test-face-at 3 4 " (ve/yCom|pLex.stu-ff/.ve/yCom|pLex.stu-ff)") 'font-lock-type-face)) (should (eq (clojure-test-face-at 5 42 " (ve/yCom|pLex.stu-ff/.ve/yCom|pLex.stu-ff)") - 'default))) + nil))) (ert-deftest clojure-mode-syntax-table/kw-oneword () :tags '(fontification syntax-table) @@ -701,12 +701,12 @@ POS." :tags '(fontification syntax-table) (clojure-test-with-temp-buffer "(_c4/defconstrainedfn bar [] nil)" (should (eq (clojure-test-face-at 2 4) 'font-lock-type-face)) - (should (eq (clojure-test-face-at 5 5) 'default)) + (should (eq (clojure-test-face-at 5 5) nil)) (should (eq (clojure-test-face-at 6 18) 'font-lock-keyword-face)) (should (eq (clojure-test-face-at 23 25) 'font-lock-function-name-face))) (clojure-test-with-temp-buffer "(clo/defbar foo nil)" (should (eq (clojure-test-face-at 2 4) 'font-lock-type-face)) - (should (eq (clojure-test-face-at 5 5) 'default)) + (should (eq (clojure-test-face-at 5 5) nil)) (should (eq (clojure-test-face-at 6 11) 'font-lock-keyword-face)) (should (eq (clojure-test-face-at 13 15) 'font-lock-function-name-face)))) From d473dc10b2469a14e42d28be5ec67b80ec049b06 Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Sat, 26 May 2018 12:25:48 +0300 Subject: [PATCH 119/379] [clojure-emacs/cider#2281] Cache the results of clojure-project-dir --- clojure-mode-util-test.el | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/clojure-mode-util-test.el b/clojure-mode-util-test.el index b028d30..09ffb41 100644 --- a/clojure-mode-util-test.el +++ b/clojure-mode-util-test.el @@ -29,7 +29,8 @@ (let ((project-dir "/home/user/projects/my-project/") (clj-file-path "/home/user/projects/my-project/src/clj/my_project/my_ns/my_file.clj") (project-relative-clj-file-path "src/clj/my_project/my_ns/my_file.clj") - (clj-file-ns "my-project.my-ns.my-file")) + (clj-file-ns "my-project.my-ns.my-file") + (clojure-cache-project nil)) (ert-deftest project-relative-path () :tags '(utils) From 17238f94e6f9a73128b98a6b155d58c13afe5786 Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Tue, 26 Jun 2018 14:41:56 +0300 Subject: [PATCH 120/379] Don't cache ns in the test for clojure-find-ns The test uses a file with two namespaces in one source file, which doesn't work properly with ns caching. --- clojure-mode-sexp-test.el | 38 ++++++++++++++++++++------------------ 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/clojure-mode-sexp-test.el b/clojure-mode-sexp-test.el index a9a5425..f8c555b 100644 --- a/clojure-mode-sexp-test.el +++ b/clojure-mode-sexp-test.el @@ -76,24 +76,26 @@ (newline-and-indent))) (ert-deftest clojure-find-ns-test () - (with-temp-buffer - (insert "(ns ^{:doc \"Some docs\"}\nfoo-bar)") - (newline) - (newline) - (insert "(in-ns 'baz-quux)") - (clojure-mode) - - ;; From inside docstring of first ns - (goto-char 18) - (should (equal "foo-bar" (clojure-find-ns))) - - ;; From inside first ns's name, on its own line - (goto-char 29) - (should (equal "foo-bar" (clojure-find-ns))) - - ;; From inside second ns's name - (goto-char 42) - (should (equal "baz-quux" (clojure-find-ns))))) + ;; we should not cache the results of `clojure-find-ns' here + (let ((clojure-cache-ns nil)) + (with-temp-buffer + (insert "(ns ^{:doc \"Some docs\"}\nfoo-bar)") + (newline) + (newline) + (insert "(in-ns 'baz-quux)") + (clojure-mode) + + ;; From inside docstring of first ns + (goto-char 18) + (should (equal "foo-bar" (clojure-find-ns))) + + ;; From inside first ns's name, on its own line + (goto-char 29) + (should (equal "foo-bar" (clojure-find-ns))) + + ;; From inside second ns's name + (goto-char 42) + (should (equal "baz-quux" (clojure-find-ns)))))) (provide 'clojure-mode-sexp-test) From 07d9cf75f828e2f07862b4fce6b50af3ab604dab Mon Sep 17 00:00:00 2001 From: dan sutton Date: Sun, 12 Aug 2018 15:35:12 -0500 Subject: [PATCH 121/379] Make `beginning-of-defun` aware of clojure comment form Remove seq-find usage Move initialization of beginning-of-defun-function to correct spot Use new function name in tests Docstring for checkdoc --- clojure-mode-sexp-test.el | 45 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/clojure-mode-sexp-test.el b/clojure-mode-sexp-test.el index f8c555b..1faf103 100644 --- a/clojure-mode-sexp-test.el +++ b/clojure-mode-sexp-test.el @@ -22,6 +22,51 @@ (require 'clojure-mode) (require 'ert) +(defmacro clojure-buffer-with-text (text &rest body) + "Run body in a temporary clojure buffer with TEXT. +TEXT is a string with a | indicating where point is. The | will be erased +and point left there." + (declare (indent 2)) + `(progn + (with-temp-buffer + (erase-buffer) + (clojure-mode) + (insert ,text) + (goto-char (point-min)) + (re-search-forward "|") + (delete-char -1) + ,@body))) + +(ert-deftest test-clojure-top-level-form-p () + (clojure-buffer-with-text + "(comment + (wrong) + (rig|ht) + (wrong))" + ;; make this use the native beginning of defun since this is used to + ;; determine whether to use the comment aware version or not. + (should (let ((beginning-of-defun-function nil)) + (clojure-top-level-form-p "comment"))))) + +(ert-deftest test-clojure-beginning-of-defun-function () + (clojure-buffer-with-text + "(comment + (wrong) + (wrong) + (rig|ht) + (wrong))" + (beginning-of-defun) + (should (looking-at-p "(comment"))) + (clojure-buffer-with-text + "(comment + (wrong) + (wrong) + (rig|ht) + (wrong))" + (let ((clojure-toplevel-inside-comment-form t)) + (beginning-of-defun)) + (should (looking-at-p "(right)")))) + (ert-deftest test-sexp-with-commas () (with-temp-buffer (insert "[], {}, :a, 2") From 2a8e78618baecfe5ca9fd3332af500d6d7da06f7 Mon Sep 17 00:00:00 2001 From: dan sutton Date: Fri, 24 Aug 2018 08:09:38 -0500 Subject: [PATCH 122/379] Fix bug in end-of-defun Going to end of defun would skip over two forms. Needed to pass on the negative prefix that was used by the generic parts of this mechanism. Also fixed an issue with paredit would not insert parens in an empty buffer by handling the `end-of-buffer` condition. --- clojure-mode-sexp-test.el | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/clojure-mode-sexp-test.el b/clojure-mode-sexp-test.el index 1faf103..40f861c 100644 --- a/clojure-mode-sexp-test.el +++ b/clojure-mode-sexp-test.el @@ -65,7 +65,21 @@ and point left there." (wrong))" (let ((clojure-toplevel-inside-comment-form t)) (beginning-of-defun)) - (should (looking-at-p "(right)")))) + (should (looking-at-p "[[:space:]]*(right)")))) + +(ert-deftest test-clojure-end-of-defun-function () + (clojure-buffer-with-text + " +(first form) +| +(second form) + +(third form)" + + (end-of-defun) + (backward-char) + (should (looking-back "(second form)")))) + (ert-deftest test-sexp-with-commas () (with-temp-buffer From 7ad641e20c693abc355d9d8871fd46002ca1f9de Mon Sep 17 00:00:00 2001 From: vemv Date: Mon, 27 Aug 2018 20:26:22 +0200 Subject: [PATCH 123/379] [Fix #483] Support alignment for reader conditionals (#486) --- clojure-mode-indentation-test.el | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/clojure-mode-indentation-test.el b/clojure-mode-indentation-test.el index 71e4ab5..6f22503 100644 --- a/clojure-mode-indentation-test.el +++ b/clojure-mode-indentation-test.el @@ -485,7 +485,8 @@ x "Verify that all FORMs correspond to a properly indented sexps." (declare (indent defun)) `(ert-deftest ,(intern (format "test-align-%s" name)) () - (let ((clojure-align-forms-automatically t)) + (let ((clojure-align-forms-automatically t) + (clojure-align-reader-conditionals t)) ,@(mapcar (lambda (form) `(with-temp-buffer (clojure-mode) @@ -596,6 +597,28 @@ x :b {:a :a, :aa :a}}") +(def-full-align-test reader-conditional + "#?(:clj 2 + :cljs 2)") + +(def-full-align-test reader-conditional-splicing + "#?@(:clj [2] + :cljs [2])") + +(ert-deftest reader-conditional-alignment-disabled-by-default () + (let ((content "#?(:clj 2\n :cljs 2)")) + (with-temp-buffer + (clojure-mode) + (insert content) + (call-interactively #'clojure-align) + (should (string= (buffer-string) content))) + (with-temp-buffer + (clojure-mode) + (setq-local clojure-align-reader-conditionals t) + (insert content) + (call-interactively #'clojure-align) + (should-not (string= (buffer-string) content))))) + (ert-deftest clojure-align-remove-extra-commas () (with-temp-buffer (clojure-mode) From 5e4ec40f4d2b6e4acbcabcb2064dad9832a3aeaa Mon Sep 17 00:00:00 2001 From: dpsutton Date: Mon, 1 Oct 2018 14:01:40 -0500 Subject: [PATCH 124/379] [Fix #489] Inserting parens before comment form doesn't move point (#490) In a form like ``` | (comment (stuff)) ``` Entering parens with paredit would put the parens right before the comment block. Paredit determines if it is in a comment to insert parens so it doesn't automatically enter a closing when in a comment or a string. Part of this called beginning-of-defun which we have modified. The error here was that rater than just going to the beginning of the form, we went to the end and then back one logical form to be at the beginning. This is identical behavior _unless_ you are between two forms. Going straight to the beginning put you in the first form, going to the end and then the beginning puts you in the second form. I.e., ``` (formA) | (formB) ``` Our beginning of form went to formB but it should go to formA. --- clojure-mode-sexp-test.el | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/clojure-mode-sexp-test.el b/clojure-mode-sexp-test.el index 40f861c..a15a319 100644 --- a/clojure-mode-sexp-test.el +++ b/clojure-mode-sexp-test.el @@ -65,7 +65,15 @@ and point left there." (wrong))" (let ((clojure-toplevel-inside-comment-form t)) (beginning-of-defun)) - (should (looking-at-p "[[:space:]]*(right)")))) + (should (looking-at-p "[[:space:]]*(right)"))) + (clojure-buffer-with-text + " +(formA) +| +(formB)" + (let ((clojure-toplevel-inside-comment-form t)) + (beginning-of-defun) + (should (looking-at-p "(formA)"))))) (ert-deftest test-clojure-end-of-defun-function () (clojure-buffer-with-text From 037baeb77ab6b34982a85c4ff66de31038bb6e89 Mon Sep 17 00:00:00 2001 From: Vitalie Spinu Date: Wed, 24 Oct 2018 20:38:01 +0200 Subject: [PATCH 125/379] Fix font-lock of type hints #462 --- clojure-mode-font-lock-test.el | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/clojure-mode-font-lock-test.el b/clojure-mode-font-lock-test.el index 69f5bf6..f97c30f 100644 --- a/clojure-mode-font-lock-test.el +++ b/clojure-mode-font-lock-test.el @@ -347,9 +347,10 @@ POS." ;; type-hint (should (eq (clojure-test-face-at 1 2 "#^ve/yCom|pLex.stu-ff") 'default)) - (should (eq (clojure-test-face-at 3 4 "#^ve/yCom|pLex.stu-ff") - 'font-lock-type-face)) + (should (eq (clojure-test-face-at 3 4 "#^ve/yCom|pLex.stu-ff") 'font-lock-type-face)) (should (eq (clojure-test-face-at 5 21 "#^ve/yCom|pLex.stu-ff") 'default)) + (should (eq (clojure-test-face-at 2 3 "^ve/yCom|pLex.stu-ff") 'font-lock-type-face)) + (should (eq (clojure-test-face-at 5 20 "^ve/yCom|pLex.stu-ff") 'default)) (should (eq (clojure-test-face-at 3 4 " (ve/yCom|pLex.stu-ff)") 'font-lock-type-face)) From ec97912ae6a97cbb5ad3b0cd8ee34282cc2ed4e4 Mon Sep 17 00:00:00 2001 From: Markku Rontu Date: Sat, 5 Jan 2019 10:53:19 +0200 Subject: [PATCH 126/379] Indent "let", "when" and "while" as function form if not at start (#497) "let", "when" and "while" are considered to be a macro form normally: (when foo bar) Also you can introduce macros that start with "let", "when" or "while" that will be indented like macro forms: (when-foo-bar 1 2 3) But when "let", "when" and "while" are not in the beginning of a symbol they are now treated as function forms: (foo-when-bar 1 2 3) --- clojure-mode-indentation-test.el | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/clojure-mode-indentation-test.el b/clojure-mode-indentation-test.el index 6f22503..51af55c 100644 --- a/clojure-mode-indentation-test.el +++ b/clojure-mode-indentation-test.el @@ -421,7 +421,9 @@ values of customisable variables." (def-full-indent-test let-when-while-forms "(let-alist [x 1]\n ())" "(while-alist [x 1]\n ())" - "(when-alist [x 1]\n ())") + "(when-alist [x 1]\n ())" + "(if-alist [x 1]\n ())" + "(indents-like-fn-when-let-while-if-are-not-the-start [x 1]\n ())") (defun indent-cond (indent-point state) (goto-char (elt state 1)) From 855bc2c80d105e12bf8cf3be61205d1eaaa63c9d Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Sat, 5 Jan 2019 12:43:13 +0200 Subject: [PATCH 127/379] Clean up the indentation config logic * Converted the type of `clojure-indent-style` to symbol for consistency with Emacs configuration practices. * Removed some legacy code related to clojure-defun-style-default-indent. --- clojure-mode-indentation-test.el | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/clojure-mode-indentation-test.el b/clojure-mode-indentation-test.el index 51af55c..613e692 100644 --- a/clojure-mode-indentation-test.el +++ b/clojure-mode-indentation-test.el @@ -58,7 +58,7 @@ values of customisable variables." (let ((fname (intern (format "indentation/%s" description)))) `(ert-deftest ,fname () (let* ((after ,after) - (clojure-indent-style :always-align) + (clojure-indent-style 'always-align) (expected-cursor-pos (1+ (s-index-of "|" after))) (expected-state (delete ?| after)) ,@var-bindings) @@ -238,14 +238,14 @@ values of customisable variables." (declare (indent 1)) (when (stringp style) (setq forms (cons style forms)) - (setq style :always-align)) + (setq style 'always-align)) `(ert-deftest ,(intern (format "test-backtracking-%s" name)) () (progn ,@(mapcar (lambda (form) `(with-temp-buffer (clojure-mode) (insert "\n" ,(replace-regexp-in-string "\n +" "\n " form)) - (let ((clojure-indent-style ,style)) + (let ((clojure-indent-style (quote ,style))) (indent-region (point-min) (point-max))) (should (equal (buffer-string) ,(concat "\n" form))))) @@ -463,7 +463,7 @@ x 3))") (def-full-indent-test align-arguments - :align-arguments + 'align-arguments "(some-function 10 1 @@ -473,7 +473,7 @@ x 2)") (def-full-indent-test always-indent - :always-indent + 'always-indent "(some-function 10 1 From f88c05a5b23a253b1066730e688d7a23473d4bdc Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Sat, 5 Jan 2019 13:18:09 +0200 Subject: [PATCH 128/379] Fix the broken build --- clojure-mode-indentation-test.el | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/clojure-mode-indentation-test.el b/clojure-mode-indentation-test.el index 613e692..7c71f11 100644 --- a/clojure-mode-indentation-test.el +++ b/clojure-mode-indentation-test.el @@ -238,14 +238,14 @@ values of customisable variables." (declare (indent 1)) (when (stringp style) (setq forms (cons style forms)) - (setq style 'always-align)) + (setq style '(quote always-align))) `(ert-deftest ,(intern (format "test-backtracking-%s" name)) () (progn ,@(mapcar (lambda (form) `(with-temp-buffer (clojure-mode) (insert "\n" ,(replace-regexp-in-string "\n +" "\n " form)) - (let ((clojure-indent-style (quote ,style))) + (let ((clojure-indent-style ,style)) (indent-region (point-min) (point-max))) (should (equal (buffer-string) ,(concat "\n" form))))) From 2cf589c952e85c1d0a9b71ecbdaaa755f5efdd6c Mon Sep 17 00:00:00 2001 From: Michael Griffiths Date: Sat, 16 Feb 2019 23:19:11 +0000 Subject: [PATCH 129/379] Fix font locking for non-alphanumeric chars in dynamic var names --- clojure-mode-font-lock-test.el | 2 ++ 1 file changed, 2 insertions(+) diff --git a/clojure-mode-font-lock-test.el b/clojure-mode-font-lock-test.el index f97c30f..2eb1fa1 100644 --- a/clojure-mode-font-lock-test.el +++ b/clojure-mode-font-lock-test.el @@ -822,6 +822,8 @@ POS." (should (eq (clojure-test-face-at 2 11 "@*some-var*") 'font-lock-variable-name-face)) (should (eq (clojure-test-face-at 9 13 "some.ns/*var*") + 'font-lock-variable-name-face)) + (should (eq (clojure-test-face-at 1 11 "*some-var?*") 'font-lock-variable-name-face))) (provide 'clojure-mode-font-lock-test) From 002a82decbaedc1ae2d349f7ce69b386afc4f736 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Requena=20L=C3=B3pez?= Date: Mon, 25 Feb 2019 22:02:32 +0100 Subject: [PATCH 130/379] [Fix #506] Makes display version command return the actual version `clojure-mode-display-version` displays the correct version (was displaying nil) also added some tests --- clojure-mode-util-test.el | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/clojure-mode-util-test.el b/clojure-mode-util-test.el index 09ffb41..3d5e0ac 100644 --- a/clojure-mode-util-test.el +++ b/clojure-mode-util-test.el @@ -26,6 +26,10 @@ (require 'cl-lib) (require 'ert) + +(ert-deftest clojure-mode-version-should-be-non-nil () + (should (not (eq clojure-mode-version nil)))) + (let ((project-dir "/home/user/projects/my-project/") (clj-file-path "/home/user/projects/my-project/src/clj/my_project/my_ns/my_file.clj") (project-relative-clj-file-path "src/clj/my_project/my_ns/my_file.clj") From 58d2421d164739df335fa8279c3531a581c519c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Requena=20L=C3=B3pez?= Date: Mon, 25 Feb 2019 22:06:22 +0100 Subject: [PATCH 131/379] [Fix comment in #445] Proper font lock for (s/def ::keyword) forms added tests as well --- clojure-mode-font-lock-test.el | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/clojure-mode-font-lock-test.el b/clojure-mode-font-lock-test.el index 2eb1fa1..17548b4 100644 --- a/clojure-mode-font-lock-test.el +++ b/clojure-mode-font-lock-test.el @@ -709,7 +709,13 @@ POS." (should (eq (clojure-test-face-at 2 4) 'font-lock-type-face)) (should (eq (clojure-test-face-at 5 5) nil)) (should (eq (clojure-test-face-at 6 11) 'font-lock-keyword-face)) - (should (eq (clojure-test-face-at 13 15) 'font-lock-function-name-face)))) + (should (eq (clojure-test-face-at 13 15) 'font-lock-function-name-face))) + (clojure-test-with-temp-buffer "(s/def ::keyword)" + (should (eq (clojure-test-face-at 2 2) 'font-lock-type-face)) + (should (eq (clojure-test-face-at 3 3) nil)) + (should (eq (clojure-test-face-at 4 6) 'font-lock-keyword-face)) + (should (eq (clojure-test-face-at 8 16) 'clojure-keyword-face)))) + (ert-deftest clojure-mode-syntax-table/variable-def () :tags '(fontification syntax-table) From 4e4b03e2de1ef09d1ec506948ed318311e601dce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Requena=20L=C3=B3pez?= Date: Mon, 25 Feb 2019 23:44:44 +0100 Subject: [PATCH 132/379] [Fix #508] Correct font-lock for namespaces namespace metadata prevented the namespace name to be highlighted as such add font-lock tests as well. --- clojure-mode-font-lock-test.el | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/clojure-mode-font-lock-test.el b/clojure-mode-font-lock-test.el index 2eb1fa1..8eb187c 100644 --- a/clojure-mode-font-lock-test.el +++ b/clojure-mode-font-lock-test.el @@ -149,7 +149,14 @@ POS." (should (eq (clojure-test-face-at 5 11 "(ns Foo-bar)") 'font-lock-type-face)) (should (eq (clojure-test-face-at 5 11 "(ns Foo-Bar)") 'font-lock-type-face)) (should (eq (clojure-test-face-at 5 11 "(ns foo-Bar)") 'font-lock-type-face)) - (should (eq (clojure-test-face-at 5 9 "(ns one.X)") 'font-lock-type-face))) + (should (eq (clojure-test-face-at 5 9 "(ns one.X)") 'font-lock-type-face)) + (should (eq (clojure-test-face-at 10 16 "(ns ^:md ns-name)") 'font-lock-type-face)) + (should (eq (clojure-test-face-at 13 19 "(ns ^:md \n ns-name)") 'font-lock-type-face)) + (should (eq (clojure-test-face-at 17 23 "(ns ^:md1 ^:md2 ns-name)") 'font-lock-type-face)) + (should (eq (clojure-test-face-at 24 30 "(ns ^:md1 ^{:md2 true} ns-name)") 'font-lock-type-face)) + (should (eq (clojure-test-face-at 24 30 "(ns ^{:md2 true} ^:md1 ns-name)") 'font-lock-type-face)) + (should (eq (clojure-test-face-at 27 33 "(ns ^:md1 ^{:md2 true} \n ns-name)") 'font-lock-type-face)) + (should (eq (clojure-test-face-at 27 33 "(ns ^{:md2 true} ^:md1 \n ns-name)") 'font-lock-type-face))) (ert-deftest clojure-mode-syntax-table/oneword () :tags '(fontification syntax-table) From 49b6a480e826551c550e9037fe2b2db1ac84e63a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Requena=20L=C3=B3pez?= Date: Tue, 26 Feb 2019 00:52:27 +0100 Subject: [PATCH 133/379] [Fix #445] More accurate font locking for strings in def forms - def forms can now have docstrings and strings properly font-locked - they will not be incorrectly indented added tests as well --- clojure-mode-font-lock-test.el | 28 ++++++++++++++++++++++++++++ clojure-mode-indentation-test.el | 5 +++++ 2 files changed, 33 insertions(+) diff --git a/clojure-mode-font-lock-test.el b/clojure-mode-font-lock-test.el index 2eb1fa1..ac202b4 100644 --- a/clojure-mode-font-lock-test.el +++ b/clojure-mode-font-lock-test.el @@ -718,6 +718,34 @@ POS." (should (eq (clojure-test-face-at 6 8 "(def foo 10)") 'font-lock-variable-name-face))) +(ert-deftest clojure-mode-syntax-table/variable-def-string () + :tags '(fontification syntax-table) + (should (eq (clojure-test-face-at 10 16 "(def foo \"hello\")") + 'font-lock-string-face)) + (should (eq (clojure-test-face-at 10 16 "(def foo \"hello\" )") + 'font-lock-string-face)) + (should (eq (clojure-test-face-at 13 19 "(def foo \n \"hello\")") + 'font-lock-string-face)) + (should (eq (clojure-test-face-at 13 19 "(def foo \n \"hello\"\n)") + 'font-lock-string-face))) + +(ert-deftest clojure-mode-syntax-table/variable-def-string-with-docstring () + :tags '(fontification syntax-table) + (should (eq (clojure-test-face-at 10 16 "(def foo \"usage\" \"hello\")") + 'font-lock-doc-face)) + (should (eq (clojure-test-face-at 18 24 "(def foo \"usage\" \"hello\")") + 'font-lock-string-face)) + (should (eq (clojure-test-face-at 18 24 "(def foo \"usage\" \"hello\" )") + 'font-lock-string-face)) + (should (eq (clojure-test-face-at 21 27 "(def foo \"usage\" \n \"hello\")") + 'font-lock-string-face)) + (should (eq (clojure-test-face-at 13 19 "(def foo \n \"usage\" \"hello\")") + 'font-lock-doc-face)) + (should (eq (clojure-test-face-at 13 19 "(def foo \n \"usage\" \n \"hello\")") + 'font-lock-doc-face)) + (should (eq (clojure-test-face-at 24 30 "(def foo \n \"usage\" \n \"hello\")") + 'font-lock-string-face))) + (ert-deftest clojure-mode-syntax-table/type-def () :tags '(fontification syntax-table) (clojure-test-with-temp-buffer "(deftype Foo)" diff --git a/clojure-mode-indentation-test.el b/clojure-mode-indentation-test.el index 7c71f11..7afa3ff 100644 --- a/clojure-mode-indentation-test.el +++ b/clojure-mode-indentation-test.el @@ -68,6 +68,7 @@ values of customisable variables." (search-forward "|") (delete-char -1) (clojure-mode) + (font-lock-ensure) (indent-according-to-mode) (should (equal expected-state (buffer-string))) @@ -118,6 +119,10 @@ values of customisable variables." (->> |expr)") +(check-indentation no-indent-for-def-string + "(def foo \"hello|\")" + "(def foo \"hello|\")") + (check-indentation doc-strings-without-indent-specified " (defn some-fn From 5c594aeb18f052035e60ee270229878484e5450b Mon Sep 17 00:00:00 2001 From: Artur Malabarba Date: Mon, 25 Mar 2019 03:28:59 -0300 Subject: [PATCH 134/379] [Fix #518] Ignore ns forms inside strings in clojure-find-ns (#519) --- clojure-mode-sexp-test.el | 49 +++++++++++++++++++++++++-------------- 1 file changed, 32 insertions(+), 17 deletions(-) diff --git a/clojure-mode-sexp-test.el b/clojure-mode-sexp-test.el index a15a319..f8a97e3 100644 --- a/clojure-mode-sexp-test.el +++ b/clojure-mode-sexp-test.el @@ -146,23 +146,38 @@ and point left there." ;; we should not cache the results of `clojure-find-ns' here (let ((clojure-cache-ns nil)) (with-temp-buffer - (insert "(ns ^{:doc \"Some docs\"}\nfoo-bar)") - (newline) - (newline) - (insert "(in-ns 'baz-quux)") - (clojure-mode) - - ;; From inside docstring of first ns - (goto-char 18) - (should (equal "foo-bar" (clojure-find-ns))) - - ;; From inside first ns's name, on its own line - (goto-char 29) - (should (equal "foo-bar" (clojure-find-ns))) - - ;; From inside second ns's name - (goto-char 42) - (should (equal "baz-quux" (clojure-find-ns)))))) + (insert "(ns ^{:doc \"Some docs\"}\nfoo-bar)") + (newline) + (newline) + (insert "(in-ns 'baz-quux)") + (clojure-mode) + + ;; From inside docstring of first ns + (goto-char 18) + (should (equal "foo-bar" (clojure-find-ns))) + + ;; From inside first ns's name, on its own line + (goto-char 29) + (should (equal "foo-bar" (clojure-find-ns))) + + ;; From inside second ns's name + (goto-char 42) + (should (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" "\"\n(in-ns 'baz-quux)\"" "foo-bar") + ("(ns foo-bar)\n" ";(in-ns 'baz-quux)" "foo-bar")))) + (pcase-dolist (`(,form1 ,form2 ,expected) data) + (with-temp-buffer + (insert form1) + (save-excursion (insert form2)) + (clojure-mode) + ;; Between the two namespaces + (should (equal expected (clojure-find-ns))) + ;; After both namespaces + (goto-char (point-max)) + (should (equal expected (clojure-find-ns)))))))) (provide 'clojure-mode-sexp-test) From b111dfc54e33ef63b019f389227038f935cb820f Mon Sep 17 00:00:00 2001 From: Robin Karlsson Date: Tue, 9 Apr 2019 20:29:30 +0200 Subject: [PATCH 135/379] [Fix #496] Highlight [[var]] style comments --- clojure-mode-font-lock-test.el | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/clojure-mode-font-lock-test.el b/clojure-mode-font-lock-test.el index 48e9b4c..52fa601 100644 --- a/clojure-mode-font-lock-test.el +++ b/clojure-mode-font-lock-test.el @@ -110,6 +110,23 @@ POS." (should (equal (clojure-test-face-at 4 5 "#\"a\\bc\\n\"") '(bold font-lock-string-face)))) +(ert-deftest clojure-mode-syntax-table/stuff-in-double-brackets () + :tags '(fontification syntax-table) + (should (equal (clojure-test-face-at 1 3 "\"[[#'s/trim]]\"") + font-lock-string-face)) + (should (equal (clojure-test-face-at 4 11 "\"[[#'s/trim]]\"") + '(font-lock-constant-face font-lock-string-face))) + (should (equal (clojure-test-face-at 12 14 "\"[[#'s/trim]]\"") + font-lock-string-face)) + (should (equal (clojure-test-face-at 1 1 ";[[#'s/trim]]") + font-lock-comment-delimiter-face)) + (should (equal (clojure-test-face-at 2 3 ";[[#'s/trim]]") + font-lock-comment-face)) + (should (equal (clojure-test-face-at 4 11 ";[[#'s/trim]]") + '(font-lock-constant-face font-lock-comment-face))) + (should (equal (clojure-test-face-at 12 13 ";[[#'s/trim]]") + font-lock-comment-face))) + (ert-deftest clojure-mode-syntax-table/fontify-let-when-while-type-forms () :tags '(fontification syntax-table) (should (equal (clojure-test-face-at 2 11 "(when-alist [x 1]\n ())") From b2d6d39476f4f339736c76ad958bc0c01e4d0859 Mon Sep 17 00:00:00 2001 From: Anthony Galea Date: Mon, 1 Jul 2019 14:39:58 +0200 Subject: [PATCH 136/379] Add refactoring command `clojure-rename-ns-alias` (#529) Originally requested in clj-refactor: https://github.com/clojure-emacs/clj-refactor.el/issues/366. --- clojure-mode-refactor-rename-ns-alias-test.el | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 clojure-mode-refactor-rename-ns-alias-test.el diff --git a/clojure-mode-refactor-rename-ns-alias-test.el b/clojure-mode-refactor-rename-ns-alias-test.el new file mode 100644 index 0000000..8e7f8d8 --- /dev/null +++ b/clojure-mode-refactor-rename-ns-alias-test.el @@ -0,0 +1,55 @@ +;;; clojure-mode-refactor-rename-ns-alias-test.el --- Clojure Mode: refactor rename ns alias -*- lexical-binding: t; -*- + +;; This file is not part of GNU Emacs. + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;;; Code: + +(require 'clojure-mode) +(require 'ert) + +(def-refactor-test test-rename-ns-alias + "(ns cljr.core + (:require [my.lib :as lib])) + + (def m #::lib{:kw 1, :n/kw 2, :_/bare 3, 0 4}) + + (+ (lib/a 1) (b 2))" + "(ns cljr.core + (:require [my.lib :as foo])) + + (def m #::foo{:kw 1, :n/kw 2, :_/bare 3, 0 4}) + + (+ (foo/a 1) (b 2))" + (clojure--rename-ns-alias-internal "lib" "foo")) + +(def-refactor-test test-rename-ns-alias-with-missing-as + "(ns cljr.core + (:require [my.lib :as lib])) + + (def m #::lib{:kw 1, :n/kw 2, :_/bare 3, 0 4}) + + (+ (lib/a 1) (b 2))" + "(ns cljr.core + (:require [my.lib :as lib])) + + (def m #::lib{:kw 1, :n/kw 2, :_/bare 3, 0 4}) + + (+ (lib/a 1) (b 2))" + (clojure--rename-ns-alias-internal "foo" "bar")) + +(provide 'clojure-mode-refactor-rename-ns-alias-test) + +;;; clojure-mode-refactor-rename-ns-alias-test.el ends here From a126773966263f6723f65d84aa90acf2ec82cddf Mon Sep 17 00:00:00 2001 From: Anthony Galea Date: Tue, 9 Jul 2019 08:07:27 +0200 Subject: [PATCH 137/379] [#422] Split def-refactor-test macro into 2 separate macros. --- test-helper.el | 36 +++++++++++++++++++++--------------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/test-helper.el b/test-helper.el index 1c98eb3..840cfc9 100644 --- a/test-helper.el +++ b/test-helper.el @@ -19,7 +19,7 @@ ;;; Commentary: -;; Non-interactive test suite setup for ERT Runner. +;; Non-interactive test suite setup. ;;; Code: @@ -32,20 +32,26 @@ ;; Load the file under test (load (expand-file-name "clojure-mode" source-directory))) -(defmacro def-refactor-test (name before after &rest body) - (declare (indent 3)) - `(progn - (put ',name 'definition-name ',name) - (ert-deftest ,name () - (let ((clojure-thread-all-but-last nil) - (clojure-use-metadata-for-privacy nil)) - (with-temp-buffer - (insert ,before) - (clojure-mode) - ,@body - (should (equal ,(concat "\n" after) - (concat "\n" (buffer-substring-no-properties - (point-min) (point-max)))))))))) +(defmacro with-clojure-buffer (text &rest body) + "Create a temporary buffer, insert TEXT, switch to clojure-mode and evaluate BODY." + `(with-temp-buffer + (erase-buffer) + (insert ,text) + (clojure-mode) + ,@body)) +(defmacro when-refactoring-it (description before after &rest body) + "Return a buttercup spec. + +Insert BEFORE into a buffer, evaluate BODY and compare the resulting buffer to +AFTER. + +BODY should contain the refactoring that transforms BEFORE into AFTER. + +DESCRIPTION is the description of the spec." + `(it ,description + (with-clojure-buffer ,before + ,@body + (expect (buffer-string) :to-equal ,after)))) ;;; test-helper.el ends here From 9c82c7834b9711f099a44b49b313e5ff4e43780e Mon Sep 17 00:00:00 2001 From: Anthony Galea Date: Tue, 9 Jul 2019 08:08:36 +0200 Subject: [PATCH 138/379] [#422] Convert tests to buttercup. --- clojure-mode-convert-collection-test.el | 93 +- clojure-mode-cycling-test.el | 171 +- clojure-mode-font-lock-test.el | 1680 +++++++++-------- clojure-mode-indentation-test.el | 715 +++---- clojure-mode-refactor-let-test.el | 180 +- clojure-mode-refactor-rename-ns-alias-test.el | 23 +- clojure-mode-refactor-threading-test.el | 400 ++-- clojure-mode-sexp-test.el | 244 ++- clojure-mode-syntax-test.el | 193 +- clojure-mode-util-test.el | 180 +- test-helper.el | 2 + 11 files changed, 2041 insertions(+), 1840 deletions(-) diff --git a/clojure-mode-convert-collection-test.el b/clojure-mode-convert-collection-test.el index 790b6b6..545b2ef 100644 --- a/clojure-mode-convert-collection-test.el +++ b/clojure-mode-convert-collection-test.el @@ -26,49 +26,56 @@ ;;; Code: (require 'clojure-mode) -(require 'ert) - -(def-refactor-test test-convert-collection-list-map - "(:a 1 :b 2)" - "{:a 1 :b 2}" - (backward-sexp) - (down-list) - (clojure-convert-collection-to-map)) - -(def-refactor-test test-convert-collection-map-vector - "{:a 1 :b 2}" - "[:a 1 :b 2]" - (backward-sexp) - (down-list) - (clojure-convert-collection-to-vector)) - -(def-refactor-test test-convert-collection-vector-set - "[1 2 3]" - "#{1 2 3}" - (backward-sexp) - (down-list) - (clojure-convert-collection-to-set)) - -(def-refactor-test test-convert-collection-set-list - "#{1 2 3}" - "(1 2 3)" - (backward-sexp) - (down-list) - (clojure-convert-collection-to-list)) - -(def-refactor-test test-convert-collection-set-quoted-list - "#{1 2 3}" - "'(1 2 3)" - (backward-sexp) - (down-list) - (clojure-convert-collection-to-quoted-list)) - -(def-refactor-test test-convert-collection-quoted-list-set - "'(1 2 3)" - "#{1 2 3}" - (backward-sexp) - (down-list) - (clojure-convert-collection-to-set)) +(require 'test-helper) +(require 'buttercup) + +(describe "clojure-convert-collection-to-map" + (when-refactoring-it "should convert a list to a map" + "(:a 1 :b 2)" + "{:a 1 :b 2}" + (backward-sexp) + (down-list) + (clojure-convert-collection-to-map))) + +(describe "clojure-convert-collection-to-vector" + (when-refactoring-it "should convert a map to a vector" + "{:a 1 :b 2}" + "[:a 1 :b 2]" + (backward-sexp) + (down-list) + (clojure-convert-collection-to-vector))) + +(describe "clojure-convert-collection-to-set" + (when-refactoring-it "should convert a vector to a set" + "[1 2 3]" + "#{1 2 3}" + (backward-sexp) + (down-list) + (clojure-convert-collection-to-set))) + +(describe "clojure-convert-collection-to-list" + (when-refactoring-it "should convert a set to a list" + "#{1 2 3}" + "(1 2 3)" + (backward-sexp) + (down-list) + (clojure-convert-collection-to-list))) + +(describe "clojure-convert-collection-to-quoted-list" + (when-refactoring-it "should convert a set to a quoted list" + "#{1 2 3}" + "'(1 2 3)" + (backward-sexp) + (down-list) + (clojure-convert-collection-to-quoted-list))) + +(describe "clojure-convert-collection-to-set" + (when-refactoring-it "should convert a quoted list to a set" + "'(1 2 3)" + "#{1 2 3}" + (backward-sexp) + (down-list) + (clojure-convert-collection-to-set))) (provide 'clojure-mode-convert-collection-test) diff --git a/clojure-mode-cycling-test.el b/clojure-mode-cycling-test.el index bf3e0ef..40754a9 100644 --- a/clojure-mode-cycling-test.el +++ b/clojure-mode-cycling-test.el @@ -25,137 +25,170 @@ ;;; Code: (require 'clojure-mode) -(require 'ert) +(require 'test-helper) +(require 'buttercup) -(def-refactor-test test-cycle-privacy-public-defn-private-defn - "(defn add [a b] +(describe "clojure-cycle-privacy" + + (when-refactoring-it "should turn a public defn into a private defn" + "(defn add [a b] (+ a b))" - "(defn- add [a b] + + "(defn- add [a b] (+ a b))" - (clojure-cycle-privacy)) -(def-refactor-test test-cycle-privacy-from-sexp-beg - "(defn- add [a b] + (clojure-cycle-privacy)) + + (when-refactoring-it "should also work from the beginning of a sexp" + "(defn- add [a b] (+ a b))" - "(defn add [a b] + + "(defn add [a b] (+ a b))" - (backward-sexp) - (clojure-cycle-privacy)) -(def-refactor-test test-cycle-privacy-public-defn-private-defn-metadata - "(defn add [a b] + (backward-sexp) + (clojure-cycle-privacy)) + + (when-refactoring-it "should use metadata when clojure-use-metadata-for-privacy is set to true" + "(defn add [a b] (+ a b))" - "(defn ^:private add [a b] + + "(defn ^:private add [a b] (+ a b))" - (let ((clojure-use-metadata-for-privacy t)) + + (let ((clojure-use-metadata-for-privacy t)) (clojure-cycle-privacy))) -(def-refactor-test test-cycle-privacy-private-defn-public-defn - "(defn- add [a b] + (when-refactoring-it "should turn a private defn into a public defn" + "(defn- add [a b] (+ a b))" - "(defn add [a b] + + "(defn add [a b] (+ a b))" - (clojure-cycle-privacy)) -(def-refactor-test test-cycle-privacy-private-defn-public-defn-metadata - "(defn ^:private add [a b] + (clojure-cycle-privacy)) + + (when-refactoring-it "should turn a private defn with metadata into a public defn" + "(defn ^:private add [a b] (+ a b))" - "(defn add [a b] + + "(defn add [a b] (+ a b))" - (let ((clojure-use-metadata-for-privacy t)) - (clojure-cycle-privacy))) -(def-refactor-test test-cycle-privacy-public-def-private-def - "(def ^:dynamic config + (let ((clojure-use-metadata-for-privacy t)) + (clojure-cycle-privacy))) + + (when-refactoring-it "should also work with pre-existing metadata" + "(def ^:dynamic config \"docs\" {:env \"staging\"})" - "(def ^:private ^:dynamic config + + "(def ^:private ^:dynamic config \"docs\" {:env \"staging\"})" - (clojure-cycle-privacy)) -(def-refactor-test test-cycle-privacy-private-def-public-def - "(def ^:private config + (clojure-cycle-privacy)) + + (when-refactoring-it "should turn a private def with metadata into a public def" + "(def ^:private config \"docs\" {:env \"staging\"})" - "(def config + + "(def config \"docs\" {:env \"staging\"})" - (clojure-cycle-privacy)) -(def-refactor-test test-cycle-if-inner-if - "(if this + (clojure-cycle-privacy))) + +(describe "clojure-cycle-if" + + (when-refactoring-it "should cycle inner if" + "(if this (if that (then AAA) (else BBB)) (otherwise CCC))" - "(if this + + "(if this (if-not that (else BBB) (then AAA)) (otherwise CCC))" - (beginning-of-buffer) - (search-forward "BBB)") - (clojure-cycle-if)) -(def-refactor-test test-cycle-if-outer-if - "(if-not this + (beginning-of-buffer) + (search-forward "BBB)") + (clojure-cycle-if)) + + (when-refactoring-it "should cycle outer if" + "(if-not this (if that (then AAA) (else BBB)) (otherwise CCC))" - "(if this + + "(if this (otherwise CCC) (if that (then AAA) (else BBB)))" - (beginning-of-buffer) - (search-forward "BBB))") - (clojure-cycle-if)) -(def-refactor-test test-cycle-when-inner-when - "(when this + (beginning-of-buffer) + (search-forward "BBB))") + (clojure-cycle-if))) + +(describe "clojure-cycle-when" + + (when-refactoring-it "should cycle inner when" + "(when this (when that (aaa) (bbb)) (ccc))" - "(when this + + "(when this (when-not that (aaa) (bbb)) (ccc))" - (beginning-of-buffer) - (search-forward "bbb)") - (clojure-cycle-when)) -(def-refactor-test test-cycle-when-outer-when - "(when-not this + (beginning-of-buffer) + (search-forward "bbb)") + (clojure-cycle-when)) + + (when-refactoring-it "should cycle outer when" + "(when-not this (when that (aaa) (bbb)) (ccc))" - "(when this + + "(when this (when that (aaa) (bbb)) (ccc))" - (beginning-of-buffer) - (search-forward "bbb))") - (clojure-cycle-when)) - -(def-refactor-test test-cycle-not-add - "(ala bala portokala)" - "(not (ala bala portokala))" - (beginning-of-buffer) - (search-forward "bala") - (clojure-cycle-not)) - -(def-refactor-test test-cycle-not-remove - "(not (ala bala portokala))" - "(ala bala portokala)" - (beginning-of-buffer) - (search-forward "bala") - (clojure-cycle-not)) + + (beginning-of-buffer) + (search-forward "bbb))") + (clojure-cycle-when))) + +(describe "clojure-cycle-not" + + (when-refactoring-it "should add a not when missing" + "(ala bala portokala)" + "(not (ala bala portokala))" + + (beginning-of-buffer) + (search-forward "bala") + (clojure-cycle-not)) + + (when-refactoring-it "should remove a not when present" + "(not (ala bala portokala))" + "(ala bala portokala)" + + (beginning-of-buffer) + (search-forward "bala") + (clojure-cycle-not))) (provide 'clojure-mode-cycling-test) diff --git a/clojure-mode-font-lock-test.el b/clojure-mode-font-lock-test.el index 52fa601..10ebd38 100644 --- a/clojure-mode-font-lock-test.el +++ b/clojure-mode-font-lock-test.el @@ -25,40 +25,42 @@ ;;; Code: (require 'clojure-mode) +(require 'test-helper) (require 'cl-lib) -(require 'ert) +(require 'buttercup) ;;;; Utilities -(defmacro clojure-test-with-temp-buffer (content &rest body) - "Evaluate BODY in a temporary buffer with CONTENTS." +(defmacro with-fontified-clojure-buffer (content &rest body) + "Evaluate BODY in a temporary buffer with CONTENT." (declare (debug t) (indent 1)) - `(with-temp-buffer - (insert ,content) - (clojure-mode) + `(with-clojure-buffer ,content (font-lock-fontify-buffer) (goto-char (point-min)) ,@body)) -(defun clojure-get-face-at-range (start end) - (let ((start-face (get-text-property start 'face)) - (all-faces (cl-loop for i from start to end collect (get-text-property - i 'face)))) - (if (cl-every (lambda (face) (eq face start-face)) all-faces) - start-face - 'various-faces))) +(defun clojure-get-face-at (start end content) + "Get the face between START and END in CONTENT." + (with-fontified-clojure-buffer content + (let ((start-face (get-text-property start 'face)) + (all-faces (cl-loop for i from start to end collect (get-text-property + i 'face)))) + (if (cl-every (lambda (face) (eq face start-face)) all-faces) + start-face + 'various-faces)))) -(defun clojure-test-face-at (start end &optional content) - "Get the face between START and END in CONTENT. +(defun expect-face-at (content start end face) + "Expect face in CONTENT between START and END to be equal to FACE." + (expect (clojure-get-face-at start end content) :to-equal face)) -If CONTENT is not given, return the face at the specified range in the current -buffer." - (if content - (clojure-test-with-temp-buffer content - (clojure-get-face-at-range start end)) - (clojure-get-face-at-range start end))) +(defun expect-faces-at (content &rest faces) + "Expect FACES in CONTENT. + +FACES is a list of the form (content (start end expected-face)*)" + (dolist (face faces) + (apply (apply-partially #'expect-face-at content) face))) (defconst clojure-test-syntax-classes [whitespace punctuation word symbol open-paren close-paren expression-prefix @@ -69,820 +71,836 @@ buffer." Each symbol in this vector corresponding to the syntax code of its index.") -(defun clojure-test-syntax-at (pos) - "Get the syntax at POS. +(defmacro when-fontifying-it (description &rest tests) + "Return a buttercup spec. -Get the syntax class symbol at POS, or nil if there is no syntax a -POS." - (let ((code (syntax-class (syntax-after pos)))) - (aref clojure-test-syntax-classes code))) +TESTS are lists of the form (content (start end expected-face)*). For each test +check that each `expected-face` is found in `content` between `start` and `end`. + +DESCRIPTION is the description of the spec." + `(it ,description + (dolist (test (quote ,tests)) + (apply #'expect-faces-at test)))) - ;;;; Font locking -(ert-deftest clojure-mode-syntax-table/stuff-in-backticks () - :tags '(fontification syntax-table) - (should (equal (clojure-test-face-at 1 2 "\"`#'s/trim`\"") - font-lock-string-face)) - (should (equal (clojure-test-face-at 3 10 "\"`#'s/trim`\"") - '(font-lock-constant-face font-lock-string-face))) - (should (equal (clojure-test-face-at 11 12 "\"`#'s/trim`\"") - font-lock-string-face)) - (should (equal (clojure-test-face-at 1 1 ";`#'s/trim`") - font-lock-comment-delimiter-face)) - (should (equal (clojure-test-face-at 2 2 ";`#'s/trim`") - font-lock-comment-face)) - (should (equal (clojure-test-face-at 3 10 ";`#'s/trim`") - '(font-lock-constant-face font-lock-comment-face))) - (should (equal (clojure-test-face-at 11 11 ";`#'s/trim`") - font-lock-comment-face))) - -(ert-deftest clojure-mode-syntax-table/stuff-in-backticks () - :tags '(fontification syntax-table) - (should (equal (clojure-test-face-at 1 2 "\"a\\bc\\n\"") - font-lock-string-face)) - (should (equal (clojure-test-face-at 3 4 "\"a\\bc\\n\"") - '(bold font-lock-string-face))) - (should (equal (clojure-test-face-at 5 5 "\"a\\bc\\n\"") - font-lock-string-face)) - (should (equal (clojure-test-face-at 6 7 "\"a\\bc\\n\"") - '(bold font-lock-string-face))) - (should (equal (clojure-test-face-at 4 5 "#\"a\\bc\\n\"") - '(bold font-lock-string-face)))) - -(ert-deftest clojure-mode-syntax-table/stuff-in-double-brackets () - :tags '(fontification syntax-table) - (should (equal (clojure-test-face-at 1 3 "\"[[#'s/trim]]\"") - font-lock-string-face)) - (should (equal (clojure-test-face-at 4 11 "\"[[#'s/trim]]\"") - '(font-lock-constant-face font-lock-string-face))) - (should (equal (clojure-test-face-at 12 14 "\"[[#'s/trim]]\"") - font-lock-string-face)) - (should (equal (clojure-test-face-at 1 1 ";[[#'s/trim]]") - font-lock-comment-delimiter-face)) - (should (equal (clojure-test-face-at 2 3 ";[[#'s/trim]]") - font-lock-comment-face)) - (should (equal (clojure-test-face-at 4 11 ";[[#'s/trim]]") - '(font-lock-constant-face font-lock-comment-face))) - (should (equal (clojure-test-face-at 12 13 ";[[#'s/trim]]") - font-lock-comment-face))) - -(ert-deftest clojure-mode-syntax-table/fontify-let-when-while-type-forms () - :tags '(fontification syntax-table) - (should (equal (clojure-test-face-at 2 11 "(when-alist [x 1]\n ())") - 'font-lock-keyword-face)) - (should (equal (clojure-test-face-at 2 12 "(while-alist [x 1]\n ())") - 'font-lock-keyword-face)) - (should (equal (clojure-test-face-at 2 10 "(let-alist [x 1]\n ())") - 'font-lock-keyword-face))) - -(ert-deftest clojure-mode-syntax-table/comment-macros () - :tags '(fontification syntax-table) - (should (equal (clojure-test-face-at - 1 2 "#_") - nil)) - (should (equal (clojure-test-face-at - 1 2 "#_ \n;; some crap\n (lala 0101\n lao\n\n 0 0i)") - nil)) - (should (equal (clojure-test-face-at - 5 41 "#_ \n;; some crap\n (lala 0101\n lao\n\n 0 0i)") - 'font-lock-comment-face))) - -(ert-deftest clojure-mode-syntax-table/namespace () - :tags '(fontification syntax-table) - (should (eq (clojure-test-face-at 5 12 "(ns .validns)") 'font-lock-type-face)) - (should (eq (clojure-test-face-at 5 12 "(ns =validns)") 'font-lock-type-face)) - (should (eq (clojure-test-face-at 5 21 "(ns .ValidNs=<>?+|?*.)") - 'font-lock-type-face)) - (should (eq (clojure-test-face-at 5 28 "(ns ValidNs<>?+|?*.b*ar.ba*z)") - 'font-lock-type-face)) - (should (eq (clojure-test-face-at 5 18 "(ns other.valid.ns)") - 'font-lock-type-face)) - (should (eq (clojure-test-face-at 5 11 "(ns oneword)") 'font-lock-type-face)) - (should (eq (clojure-test-face-at 5 11 "(ns foo.bar)") 'font-lock-type-face)) - (should (eq (clojure-test-face-at 5 11 "(ns Foo.bar)") 'font-lock-type-face)) - (should (eq (clojure-test-face-at 5 11 "(ns Foo.Bar)") 'font-lock-type-face)) - (should (eq (clojure-test-face-at 5 11 "(ns foo.Bar)") 'font-lock-type-face)) - (should (eq (clojure-test-face-at 5 11 "(ns Foo-bar)") 'font-lock-type-face)) - (should (eq (clojure-test-face-at 5 11 "(ns Foo-Bar)") 'font-lock-type-face)) - (should (eq (clojure-test-face-at 5 11 "(ns foo-Bar)") 'font-lock-type-face)) - (should (eq (clojure-test-face-at 5 9 "(ns one.X)") 'font-lock-type-face)) - (should (eq (clojure-test-face-at 10 16 "(ns ^:md ns-name)") 'font-lock-type-face)) - (should (eq (clojure-test-face-at 13 19 "(ns ^:md \n ns-name)") 'font-lock-type-face)) - (should (eq (clojure-test-face-at 17 23 "(ns ^:md1 ^:md2 ns-name)") 'font-lock-type-face)) - (should (eq (clojure-test-face-at 24 30 "(ns ^:md1 ^{:md2 true} ns-name)") 'font-lock-type-face)) - (should (eq (clojure-test-face-at 24 30 "(ns ^{:md2 true} ^:md1 ns-name)") 'font-lock-type-face)) - (should (eq (clojure-test-face-at 27 33 "(ns ^:md1 ^{:md2 true} \n ns-name)") 'font-lock-type-face)) - (should (eq (clojure-test-face-at 27 33 "(ns ^{:md2 true} ^:md1 \n ns-name)") 'font-lock-type-face))) - -(ert-deftest clojure-mode-syntax-table/oneword () - :tags '(fontification syntax-table) - (should (eq (clojure-test-face-at 2 8 " oneword") nil)) - (should (eq (clojure-test-face-at 2 8 "@oneword") nil)) - (should (eq (clojure-test-face-at 2 8 "#oneword") nil)) - (should (eq (clojure-test-face-at 2 8 ".oneword") nil)) - (should (eq (clojure-test-face-at 3 9 "#^oneword") - 'font-lock-type-face)) ;; type-hint - (should (eq (clojure-test-face-at 2 8 "(oneword)") nil)) - - (should (eq (clojure-test-face-at 2 8 "(oneword/oneword)") - 'font-lock-type-face)) - (should (eq (clojure-test-face-at 9 10 "(oneword/oneword)") nil)) - (should (eq (clojure-test-face-at 11 16 "(oneword/oneword)") nil)) - - (should (eq (clojure-test-face-at 2 8 "(oneword/seg.mnt)") - 'font-lock-type-face)) - (should (eq (clojure-test-face-at 9 10 "(oneword/seg.mnt)") nil)) - (should (eq (clojure-test-face-at 11 16 "(oneword/seg.mnt)") nil)) - - (should (eq (clojure-test-face-at 2 8 "(oneword/mxdCase)") - 'font-lock-type-face)) - (should (eq (clojure-test-face-at 9 10 "(oneword/mxdCase)") nil)) - (should (eq (clojure-test-face-at 11 16 "(oneword/mxdCase)") nil)) - - (should (eq (clojure-test-face-at 2 8 "(oneword/CmlCase)") - 'font-lock-type-face)) - (should (eq (clojure-test-face-at 9 10 "(oneword/CmlCase)") nil)) - (should (eq (clojure-test-face-at 11 16 "(oneword/CmlCase)") nil)) - - (should (eq (clojure-test-face-at 2 8 "(oneword/ve/yCom|pLex.stu-ff)") - 'font-lock-type-face)) - (should (eq (clojure-test-face-at 9 10 "(oneword/ve/yCom|pLex.stu-ff)") - nil)) - (should (eq (clojure-test-face-at 11 28 "(oneword/ve/yCom|pLex.stu-ff)") - nil)) - - (should (eq (clojure-test-face-at 2 8 "(oneword/.ve/yCom|pLex.stu-ff)") - 'font-lock-type-face)) - (should (eq (clojure-test-face-at 9 10 "(oneword/.ve/yCom|pLex.stu-ff)") - nil)) - (should (eq (clojure-test-face-at 12 29 "(oneword/.ve/yCom|pLex.stu-ff)") - nil))) - -(ert-deftest clojure-mode-syntax-table/segment () - :tags '(fontification syntax-table) - (should (eq (clojure-test-face-at 2 8 " seg.mnt") nil)) - (should (eq (clojure-test-face-at 2 8 "@seg.mnt") nil)) - (should (eq (clojure-test-face-at 2 8 "#seg.mnt") nil)) - (should (eq (clojure-test-face-at 2 8 ".seg.mnt") nil)) - (should (eq (clojure-test-face-at 3 9 "#^seg.mnt") - 'font-lock-type-face)) ;; type-hint - (should (eq (clojure-test-face-at 2 8 "(seg.mnt)") nil)) - (should (eq (clojure-test-face-at 2 8 "(seg.mnt/oneword)") - 'font-lock-type-face)) - (should (eq (clojure-test-face-at 9 10 "(seg.mnt/oneword)") nil)) - (should (eq (clojure-test-face-at 11 16 "(seg.mnt/oneword)") nil)) - - (should (eq (clojure-test-face-at 2 8 "(seg.mnt/seg.mnt)") - 'font-lock-type-face)) - (should (eq (clojure-test-face-at 9 10 "(seg.mnt/seg.mnt)") nil)) - (should (eq (clojure-test-face-at 11 16 "(seg.mnt/seg.mnt)") nil)) - - (should (eq (clojure-test-face-at 2 8 "(seg.mnt/mxdCase)") - 'font-lock-type-face)) - (should (eq (clojure-test-face-at 9 10 "(seg.mnt/mxdCase)") nil)) - (should (eq (clojure-test-face-at 11 16 "(seg.mnt/mxdCase)") nil)) - - (should (eq (clojure-test-face-at 2 8 "(seg.mnt/CmlCase)") - 'font-lock-type-face)) - (should (eq (clojure-test-face-at 9 10 "(seg.mnt/CmlCase)") nil)) - (should (eq (clojure-test-face-at 11 16 "(seg.mnt/CmlCase)") nil)) - - (should (eq (clojure-test-face-at 2 8 "(seg.mnt/ve/yCom|pLex.stu-ff)") - 'font-lock-type-face)) - (should (eq (clojure-test-face-at 9 10 "(seg.mnt/ve/yCom|pLex.stu-ff)") - nil)) - (should (eq (clojure-test-face-at 11 28 "(seg.mnt/ve/yCom|pLex.stu-ff)") - nil)) - - (should (eq (clojure-test-face-at 2 8 "(seg.mnt/.ve/yCom|pLex.stu-ff)") - 'font-lock-type-face)) - (should (eq (clojure-test-face-at 9 10 "(seg.mnt/.ve/yCom|pLex.stu-ff)") - nil)) - (should (eq (clojure-test-face-at 12 29 "(seg.mnt/.ve/yCom|pLex.stu-ff)") - nil))) - -(ert-deftest clojure-mode-syntax-table/camelcase () - :tags '(fontification syntax-table) - (should (eq (clojure-test-face-at 2 8 " CmlCase") nil)) - (should (eq (clojure-test-face-at 2 8 "@CmlCase") nil)) - (should (eq (clojure-test-face-at 2 8 "#CmlCase") nil)) - (should (eq (clojure-test-face-at 2 8 ".CmlCase") nil)) - (should (eq (clojure-test-face-at 3 9 "#^CmlCase") - 'font-lock-type-face)) ;; type-hint - (should (eq (clojure-test-face-at 2 8 "(CmlCase)") nil)) - - (should (eq (clojure-test-face-at 2 8 "(CmlCase/oneword)") - 'font-lock-type-face)) - (should (eq (clojure-test-face-at 9 10 "(CmlCase/oneword)") nil)) - (should (eq (clojure-test-face-at 11 16 "(CmlCase/oneword)") nil)) - - (should (eq (clojure-test-face-at 2 8 "(CmlCase/seg.mnt)") - 'font-lock-type-face)) - (should (eq (clojure-test-face-at 9 10 "(CmlCase/seg.mnt)") nil)) - (should (eq (clojure-test-face-at 11 16 "(CmlCase/seg.mnt)") nil)) - - (should (eq (clojure-test-face-at 2 8 "(CmlCase/mxdCase)") - 'font-lock-type-face)) - (should (eq (clojure-test-face-at 9 10 "(CmlCase/mxdCase)") nil)) - (should (eq (clojure-test-face-at 11 16 "(CmlCase/mxdCase)") nil)) - - (should (eq (clojure-test-face-at 2 8 "(CmlCase/CmlCase)") - 'font-lock-type-face)) - (should (eq (clojure-test-face-at 9 10 "(CmlCase/CmlCase)") nil)) - (should (eq (clojure-test-face-at 11 16 "(CmlCase/CmlCase)") nil)) - - (should (eq (clojure-test-face-at 2 8 "(CmlCase/ve/yCom|pLex.stu-ff)") - 'font-lock-type-face)) - (should (eq (clojure-test-face-at 9 10 "(CmlCase/ve/yCom|pLex.stu-ff)") - nil)) - (should (eq (clojure-test-face-at 11 28 "(CmlCase/ve/yCom|pLex.stu-ff)") - nil)) - - (should (eq (clojure-test-face-at 2 8 "(CmlCase/.ve/yCom|pLex.stu-ff)") - 'font-lock-type-face)) - (should (eq (clojure-test-face-at 9 10 "(CmlCase/.ve/yCom|pLex.stu-ff)") - nil)) - (should (eq (clojure-test-face-at 12 29 "(CmlCase/.ve/yCom|pLex.stu-ff)") - nil))) - -(ert-deftest clojure-mode-syntax-table/mixedcase () - :tags '(fontification syntax-table) - (should (eq (clojure-test-face-at 2 8 " mxdCase") nil)) - (should (eq (clojure-test-face-at 2 8 "@mxdCase") nil)) - (should (eq (clojure-test-face-at 2 8 "#mxdCase") nil)) - (should (eq (clojure-test-face-at 2 8 ".mxdCase") nil)) - (should (eq (clojure-test-face-at 3 9 "#^mxdCase") - 'font-lock-type-face)) ;; type-hint - (should (eq (clojure-test-face-at 2 8 "(mxdCase)") nil)) - - (should (eq (clojure-test-face-at 2 8 "(mxdCase/oneword)") - 'font-lock-type-face)) - (should (eq (clojure-test-face-at 9 10 "(mxdCase/oneword)") nil)) - (should (eq (clojure-test-face-at 11 16 "(mxdCase/oneword)") nil)) - - (should (eq (clojure-test-face-at 2 8 "(mxdCase/seg.mnt)") - 'font-lock-type-face)) - (should (eq (clojure-test-face-at 9 10 "(mxdCase/seg.mnt)") nil)) - (should (eq (clojure-test-face-at 11 16 "(mxdCase/seg.mnt)") nil)) - - (should (eq (clojure-test-face-at 2 8 "(mxdCase/mxdCase)") - 'font-lock-type-face)) - (should (eq (clojure-test-face-at 9 10 "(mxdCase/mxdCase)") nil)) - (should (eq (clojure-test-face-at 11 16 "(mxdCase/mxdCase)") nil)) - - (should (eq (clojure-test-face-at 2 8 "(mxdCase/CmlCase)") - 'font-lock-type-face)) - (should (eq (clojure-test-face-at 9 10 "(mxdCase/CmlCase)") nil)) - (should (eq (clojure-test-face-at 11 16 "(mxdCase/CmlCase)") nil)) - - (should (eq (clojure-test-face-at 2 8 "(mxdCase/ve/yCom|pLex.stu-ff)") - 'font-lock-type-face)) - (should (eq (clojure-test-face-at 9 10 "(mxdCase/ve/yCom|pLex.stu-ff)") - nil)) - (should (eq (clojure-test-face-at 11 28 "(mxdCase/ve/yCom|pLex.stu-ff)") - nil)) - - (should (eq (clojure-test-face-at 2 8 "(mxdCase/.ve/yCom|pLex.stu-ff)") - 'font-lock-type-face)) - (should (eq (clojure-test-face-at 9 10 "(mxdCase/.ve/yCom|pLex.stu-ff)") - nil)) - (should (eq (clojure-test-face-at 12 29 "(mxdCase/.ve/yCom|pLex.stu-ff)") - nil))) - -(ert-deftest clojure-mode-syntax-table/verycomplex () - :tags '(fontification syntax-table) - (should (eq (clojure-test-face-at 3 4 " ve/yCom|pLex.stu-ff") - 'font-lock-type-face)) - (should (eq (clojure-test-face-at 5 21 " ve/yCom|pLex.stu-ff") nil)) - - (should (eq (clojure-test-face-at 2 2 " @ve/yCom|pLex.stu-ff") nil)) - (should (eq (clojure-test-face-at 3 4 " @ve/yCom|pLex.stu-ff") - 'font-lock-type-face)) - (should (eq (clojure-test-face-at 5 21 " @ve/yCom|pLex.stu-ff") nil)) - - (should (eq (clojure-test-face-at 2 4 " #ve/yCom|pLex.stu-ff") - 'font-lock-type-face)) - (should (eq (clojure-test-face-at 5 21 " #ve/yCom|pLex.stu-ff") nil)) - - (should (eq (clojure-test-face-at 2 4 " .ve/yCom|pLex.stu-ff") - 'font-lock-type-face)) - (should (eq (clojure-test-face-at 5 21 " .ve/yCom|pLex.stu-ff") nil)) - - ;; type-hint - (should (eq (clojure-test-face-at 1 2 "#^ve/yCom|pLex.stu-ff") 'default)) - (should (eq (clojure-test-face-at 3 4 "#^ve/yCom|pLex.stu-ff") 'font-lock-type-face)) - (should (eq (clojure-test-face-at 5 21 "#^ve/yCom|pLex.stu-ff") 'default)) - (should (eq (clojure-test-face-at 2 3 "^ve/yCom|pLex.stu-ff") 'font-lock-type-face)) - (should (eq (clojure-test-face-at 5 20 "^ve/yCom|pLex.stu-ff") 'default)) - - (should (eq (clojure-test-face-at 3 4 " (ve/yCom|pLex.stu-ff)") - 'font-lock-type-face)) - (should (eq (clojure-test-face-at 5 21 " (ve/yCom|pLex.stu-ff)") nil)) - - (should (eq (clojure-test-face-at 3 4 " (ve/yCom|pLex.stu-ff/oneword)") - 'font-lock-type-face)) - (should (eq (clojure-test-face-at 5 29 " (ve/yCom|pLex.stu-ff/oneword)") - nil)) - - (should (eq (clojure-test-face-at 3 4 " (ve/yCom|pLex.stu-ff/seg.mnt)") - 'font-lock-type-face)) - (should (eq (clojure-test-face-at 5 29 " (ve/yCom|pLex.stu-ff/seg.mnt)") - nil)) - - (should (eq (clojure-test-face-at 3 4 " (ve/yCom|pLex.stu-ff/mxdCase)") - 'font-lock-type-face)) - (should (eq (clojure-test-face-at 5 29 " (ve/yCom|pLex.stu-ff/mxdCase)") - nil)) - - (should (eq (clojure-test-face-at 3 4 " (ve/yCom|pLex.stu-ff/CmlCase)") - 'font-lock-type-face)) - (should (eq (clojure-test-face-at 5 29 " (ve/yCom|pLex.stu-ff/CmlCase)") - nil)) - - (should (eq (clojure-test-face-at - 3 4 " (ve/yCom|pLex.stu-ff/ve/yCom|pLex.stu-ff)") - 'font-lock-type-face)) - (should (eq (clojure-test-face-at - 5 41 " (ve/yCom|pLex.stu-ff/ve/yCom|pLex.stu-ff)") - nil)) - - (should (eq (clojure-test-face-at - 3 4 " (ve/yCom|pLex.stu-ff/.ve/yCom|pLex.stu-ff)") - 'font-lock-type-face)) - (should (eq (clojure-test-face-at - 5 42 " (ve/yCom|pLex.stu-ff/.ve/yCom|pLex.stu-ff)") - nil))) - -(ert-deftest clojure-mode-syntax-table/kw-oneword () - :tags '(fontification syntax-table) - (should (eq (clojure-test-face-at 3 9 " :oneword") 'clojure-keyword-face)) - (should (eq (clojure-test-face-at 3 9 "{:oneword 0}") - 'clojure-keyword-face)) - (should (eq (clojure-test-face-at 3 10 "{:#oneword 0}") - 'clojure-keyword-face)) - (should (eq (clojure-test-face-at 3 10 "{:.oneword 0}") - 'clojure-keyword-face)) - - (should (eq (clojure-test-face-at 3 9 "{:oneword/oneword 0}") - 'font-lock-type-face)) - (should (eq (clojure-test-face-at 10 10 "{:oneword/oneword 0}") 'default)) - (should (eq (clojure-test-face-at 11 17 "{:oneword/oneword 0}") - 'clojure-keyword-face)) - - (should (eq (clojure-test-face-at 3 9 "{:oneword/seg.mnt 0}") - 'font-lock-type-face)) - (should (eq (clojure-test-face-at 10 10 "{:oneword/seg.mnt 0}") 'default)) - (should (eq (clojure-test-face-at 11 17 "{:oneword/seg.mnt 0}") - 'clojure-keyword-face)) - - (should (eq (clojure-test-face-at 3 9 "{:oneword/CmlCase 0}") - 'font-lock-type-face)) - (should (eq (clojure-test-face-at 10 10 "{:oneword/CmlCase 0}") 'default)) - (should (eq (clojure-test-face-at 11 17 "{:oneword/CmlCase 0}") - 'clojure-keyword-face)) - - (should (eq (clojure-test-face-at 3 9 "{:oneword/mxdCase 0}") - 'font-lock-type-face)) - (should (eq (clojure-test-face-at 10 10 "{:oneword/mxdCase 0}") 'default)) - (should (eq (clojure-test-face-at 11 17 "{:oneword/mxdCase 0}") - 'clojure-keyword-face)) - - (should (eq (clojure-test-face-at 3 9 "{:oneword/ve/yCom|pLex.stu-ff 0}") - 'font-lock-type-face)) - (should (eq (clojure-test-face-at 10 10 "{:oneword/ve/yCom|pLex.stu-ff 0}") - 'default)) - (should (eq (clojure-test-face-at 11 29 "{:oneword/ve/yCom|pLex.stu-ff 0}") - 'clojure-keyword-face)) - - (should (eq (clojure-test-face-at 3 9 "{:oneword/.ve/yCom|pLex.stu-ff 0}") - 'font-lock-type-face)) - (should (eq (clojure-test-face-at 10 10 "{:oneword/.ve/yCom|pLex.stu-ff 0}") - 'default)) - (should (eq (clojure-test-face-at 11 30 "{:oneword/.ve/yCom|pLex.stu-ff 0}") - 'clojure-keyword-face))) - -(ert-deftest clojure-mode-syntax-table/kw-namespaced () - :tags '(fontification syntax-table) - (should (eq (clojure-test-face-at 1 5 "::foo") 'clojure-keyword-face)) - (should (eq (clojure-test-face-at 1 9 ":_::_:foo") 'clojure-keyword-face)) - (should (eq (clojure-test-face-at 1 8 ":_:_:foo") 'clojure-keyword-face)) - (should (eq (clojure-test-face-at 1 9 ":foo/:bar") 'clojure-keyword-face)) - (should (eq (clojure-test-face-at 1 7 "::_:foo") 'clojure-keyword-face)) - (should (eq (clojure-test-face-at 1 9 "::_:_:foo") 'clojure-keyword-face)) - - (should (eq (clojure-test-face-at 1 1 ":_:_:foo/_") 'clojure-keyword-face)) - (should (eq (clojure-test-face-at 2 8 ":_:_:foo/_") 'font-lock-type-face)) - (should (eq (clojure-test-face-at 9 9 ":_:_:foo/_") 'default)) - (should (eq (clojure-test-face-at 10 10 ":_:_:foo/_") 'clojure-keyword-face)) - - (should (eq (clojure-test-face-at 10 12 ":_:_:foo/bar") - 'clojure-keyword-face)) - (should (eq (clojure-test-face-at 10 16 ":_:_:foo/bar/eee") - 'clojure-keyword-face)) - (should (eq (clojure-test-face-at 10 17 ":_:_:foo/bar_:foo") - 'clojure-keyword-face)) - (should (eq (clojure-test-face-at 10 19 ":_:_:foo/bar_:_:foo") - 'clojure-keyword-face))) - -(ert-deftest clojure-mode-syntax-table/kw-segment () - :tags '(fontification syntax-table) - (should (eq (clojure-test-face-at 3 9 " :seg.mnt") 'clojure-keyword-face)) - (should (eq (clojure-test-face-at 3 9 "{:seg.mnt 0}") - 'clojure-keyword-face)) - (should (eq (clojure-test-face-at 3 10 "{:#seg.mnt 0}") - 'clojure-keyword-face)) - (should (eq (clojure-test-face-at 3 10 "{:.seg.mnt 0}") - 'clojure-keyword-face)) - - (should (eq (clojure-test-face-at 3 9 "{:seg.mnt/oneword 0}") - 'font-lock-type-face)) - (should (eq (clojure-test-face-at 10 10 "{:seg.mnt/oneword 0}") 'default)) - (should (eq (clojure-test-face-at 11 17 "{:seg.mnt/oneword 0}") - 'clojure-keyword-face)) - - (should (eq (clojure-test-face-at 3 9 "{:seg.mnt/seg.mnt 0}") - 'font-lock-type-face)) - (should (eq (clojure-test-face-at 10 10 "{:seg.mnt/seg.mnt 0}") 'default)) - (should (eq (clojure-test-face-at 11 17 "{:seg.mnt/seg.mnt 0}") - 'clojure-keyword-face)) - - (should (eq (clojure-test-face-at 3 9 "{:seg.mnt/CmlCase 0}") - 'font-lock-type-face)) - (should (eq (clojure-test-face-at 10 10 "{:seg.mnt/CmlCase 0}") 'default)) - (should (eq (clojure-test-face-at 11 17 "{:seg.mnt/CmlCase 0}") - 'clojure-keyword-face)) - - (should (eq (clojure-test-face-at 3 9 "{:seg.mnt/mxdCase 0}") - 'font-lock-type-face)) - (should (eq (clojure-test-face-at 10 10 "{:seg.mnt/mxdCase 0}") 'default)) - (should (eq (clojure-test-face-at 11 17 "{:seg.mnt/mxdCase 0}") - 'clojure-keyword-face)) - - (should (eq (clojure-test-face-at 3 9 "{:seg.mnt/ve/yCom|pLex.stu-ff 0}") - 'font-lock-type-face)) - (should (eq (clojure-test-face-at 10 10 "{:seg.mnt/ve/yCom|pLex.stu-ff 0}") - 'default)) - (should (eq (clojure-test-face-at 11 29 "{:seg.mnt/ve/yCom|pLex.stu-ff 0}") - 'clojure-keyword-face)) - - (should (eq (clojure-test-face-at 3 9 "{:seg.mnt/.ve/yCom|pLex.stu-ff 0}") - 'font-lock-type-face)) - (should (eq (clojure-test-face-at 10 10 "{:seg.mnt/.ve/yCom|pLex.stu-ff 0}") - 'default)) - (should (eq (clojure-test-face-at 11 30 "{:seg.mnt/.ve/yCom|pLex.stu-ff 0}") - 'clojure-keyword-face))) - -(ert-deftest clojure-mode-syntax-table/kw-camelcase () - :tags '(fontification syntax-table) - (should (eq (clojure-test-face-at 3 9 " :CmlCase") 'clojure-keyword-face)) - (should (eq (clojure-test-face-at 3 9 "{:CmlCase 0}") - 'clojure-keyword-face)) - (should (eq (clojure-test-face-at 3 10 "{:#CmlCase 0}") - 'clojure-keyword-face)) - (should (eq (clojure-test-face-at 3 10 "{:.CmlCase 0}") - 'clojure-keyword-face)) - (should (eq (clojure-test-face-at 3 9 "{:CmlCase/oneword 0}") - 'font-lock-type-face)) - (should (eq (clojure-test-face-at 10 10 "{:CmlCase/oneword 0}") 'default)) - (should (eq (clojure-test-face-at 11 17 "{:CmlCase/oneword 0}") - 'clojure-keyword-face)) - (should (eq (clojure-test-face-at 3 9 "{:CmlCase/seg.mnt 0}") - 'font-lock-type-face)) - (should (eq (clojure-test-face-at 10 10 "{:CmlCase/seg.mnt 0}") 'default)) - (should (eq (clojure-test-face-at 11 17 "{:CmlCase/seg.mnt 0}") - 'clojure-keyword-face)) - (should (eq (clojure-test-face-at 3 9 "{:CmlCase/CmlCase 0}") - 'font-lock-type-face)) - (should (eq (clojure-test-face-at 10 10 "{:CmlCase/CmlCase 0}") 'default)) - (should (eq (clojure-test-face-at 11 17 "{:CmlCase/CmlCase 0}") - 'clojure-keyword-face)) - (should (eq (clojure-test-face-at 3 9 "{:CmlCase/mxdCase 0}") - 'font-lock-type-face)) - (should (eq (clojure-test-face-at 10 10 "{:CmlCase/mxdCase 0}") 'default)) - (should (eq (clojure-test-face-at 11 17 "{:CmlCase/mxdCase 0}") - 'clojure-keyword-face)) - (should (eq (clojure-test-face-at 3 9 "{:CmlCase/ve/yCom|pLex.stu-ff 0}") - 'font-lock-type-face)) - (should (eq (clojure-test-face-at 10 10 "{:CmlCase/ve/yCom|pLex.stu-ff 0}") - 'default)) - (should (eq (clojure-test-face-at 11 29 "{:CmlCase/ve/yCom|pLex.stu-ff 0}") - 'clojure-keyword-face)) - - (should (eq (clojure-test-face-at 3 9 "{:CmlCase/.ve/yCom|pLex.stu-ff 0}") - 'font-lock-type-face)) - (should (eq (clojure-test-face-at 10 10 "{:CmlCase/.ve/yCom|pLex.stu-ff 0}") - 'default)) - (should (eq (clojure-test-face-at 11 30 "{:CmlCase/.ve/yCom|pLex.stu-ff 0}") - 'clojure-keyword-face))) - -(ert-deftest clojure-mode-syntax-table/kw-mixedcase () - :tags '(fontification syntax-table) - (should (eq (clojure-test-face-at 3 9 " :mxdCase") 'clojure-keyword-face)) - (should (eq (clojure-test-face-at 3 9 "{:mxdCase 0}") - 'clojure-keyword-face)) - (should (eq (clojure-test-face-at 3 10 "{:#mxdCase 0}") - 'clojure-keyword-face)) - (should (eq (clojure-test-face-at 3 10 "{:.mxdCase 0}") - 'clojure-keyword-face)) - - (should (eq (clojure-test-face-at 3 9 "{:mxdCase/oneword 0}") - 'font-lock-type-face)) - (should (eq (clojure-test-face-at 10 10 "{:mxdCase/oneword 0}") 'default)) - (should (eq (clojure-test-face-at 11 17 "{:mxdCase/oneword 0}") - 'clojure-keyword-face)) - - (should (eq (clojure-test-face-at 3 9 "{:mxdCase/seg.mnt 0}") - 'font-lock-type-face)) - (should (eq (clojure-test-face-at 10 10 "{:mxdCase/seg.mnt 0}") 'default)) - (should (eq (clojure-test-face-at 11 17 "{:mxdCase/seg.mnt 0}") - 'clojure-keyword-face)) - - (should (eq (clojure-test-face-at 3 9 "{:mxdCase/CmlCase 0}") - 'font-lock-type-face)) - (should (eq (clojure-test-face-at 10 10 "{:mxdCase/CmlCase 0}") 'default)) - (should (eq (clojure-test-face-at 11 17 "{:mxdCase/CmlCase 0}") - 'clojure-keyword-face)) - - (should (eq (clojure-test-face-at 3 9 "{:mxdCase/mxdCase 0}") - 'font-lock-type-face)) - (should (eq (clojure-test-face-at 10 10 "{:mxdCase/mxdCase 0}") 'default)) - (should (eq (clojure-test-face-at 11 17 "{:mxdCase/mxdCase 0}") - 'clojure-keyword-face)) - - (should (eq (clojure-test-face-at 3 9 "{:mxdCase/ve/yCom|pLex.stu-ff 0}") - 'font-lock-type-face)) - (should (eq (clojure-test-face-at 10 10 "{:mxdCase/ve/yCom|pLex.stu-ff 0}") - 'default)) - (should (eq (clojure-test-face-at 11 29 "{:mxdCase/ve/yCom|pLex.stu-ff 0}") - 'clojure-keyword-face)) - - (should (eq (clojure-test-face-at 3 9 "{:mxdCase/.ve/yCom|pLex.stu-ff 0}") - 'font-lock-type-face)) - (should (eq (clojure-test-face-at 10 10 "{:mxdCase/.ve/yCom|pLex.stu-ff 0}") - 'default)) - (should (eq (clojure-test-face-at 11 30 "{:mxdCase/.ve/yCom|pLex.stu-ff 0}") - 'clojure-keyword-face))) - -(ert-deftest clojure-mode-syntax-table/kw-verycomplex () - :tags '(fontification syntax-table) - (should (eq (clojure-test-face-at 3 4 " :ve/yCom|pLex.stu-ff") - 'font-lock-type-face)) - (should (eq (clojure-test-face-at 5 5 " :ve/yCom|pLex.stu-ff") - 'default)) - (should (eq (clojure-test-face-at 6 21 " :ve/yCom|pLex.stu-ff") - 'clojure-keyword-face)) - - (should (eq (clojure-test-face-at 2 2 "{:ve/yCom|pLex.stu-ff 0}") - 'clojure-keyword-face)) - (should (eq (clojure-test-face-at 3 4 "{:ve/yCom|pLex.stu-ff 0}") - 'font-lock-type-face)) - (should (eq (clojure-test-face-at 5 5 "{:ve/yCom|pLex.stu-ff 0}") - 'default)) - (should (eq (clojure-test-face-at 6 21 "{:ve/yCom|pLex.stu-ff 0}") - 'clojure-keyword-face)) - - (should (eq (clojure-test-face-at 2 2 "{:#ve/yCom|pLex.stu-ff 0}") - 'clojure-keyword-face)) - (should (eq (clojure-test-face-at 3 5 "{:#ve/yCom|pLex.stu-ff 0}") - 'font-lock-type-face)) - (should (eq (clojure-test-face-at 6 6 "{:#ve/yCom|pLex.stu-ff 0}") - 'default)) - (should (eq (clojure-test-face-at 7 22 "{:#ve/yCom|pLex.stu-ff 0}") - 'clojure-keyword-face)) - - (should (eq (clojure-test-face-at 2 2 "{:.ve/yCom|pLex.stu-ff 0}") - 'clojure-keyword-face)) - (should (eq (clojure-test-face-at 3 5 "{:.ve/yCom|pLex.stu-ff 0}") - 'font-lock-type-face)) - (should (eq (clojure-test-face-at 6 6 "{:.ve/yCom|pLex.stu-ff 0}") - 'default)) - (should (eq (clojure-test-face-at 7 22 "{:.ve/yCom|pLex.stu-ff 0}") - 'clojure-keyword-face)) - - (should (eq (clojure-test-face-at 2 2 "{:ve/yCom|pLex.stu-ff/oneword 0}") - 'clojure-keyword-face)) - (should (eq (clojure-test-face-at 3 4 "{:ve/yCom|pLex.stu-ff/oneword 0}") - 'font-lock-type-face)) - (should (eq (clojure-test-face-at 5 5 "{:ve/yCom|pLex.stu-ff/oneword 0}") - 'default)) - (should (eq (clojure-test-face-at 6 29 "{:ve/yCom|pLex.stu-ff/oneword 0}") - 'clojure-keyword-face)) - - (should (eq (clojure-test-face-at 2 2 "{:ve/yCom|pLex.stu-ff/seg.mnt 0}") - 'clojure-keyword-face)) - (should (eq (clojure-test-face-at 3 4 "{:ve/yCom|pLex.stu-ff/seg.mnt 0}") - 'font-lock-type-face)) - (should (eq (clojure-test-face-at 5 5 "{:ve/yCom|pLex.stu-ff/seg.mnt 0}") - 'default)) - (should (eq (clojure-test-face-at 6 29 "{:ve/yCom|pLex.stu-ff/seg.mnt 0}") - 'clojure-keyword-face)) - - (should (eq (clojure-test-face-at 2 2 "{:ve/yCom|pLex.stu-ff/ClmCase 0}") - 'clojure-keyword-face)) - (should (eq (clojure-test-face-at 3 4 "{:ve/yCom|pLex.stu-ff/ClmCase 0}") - 'font-lock-type-face)) - (should (eq (clojure-test-face-at 5 5 "{:ve/yCom|pLex.stu-ff/ClmCase 0}") - 'default)) - (should (eq (clojure-test-face-at 6 29 "{:ve/yCom|pLex.stu-ff/ClmCase 0}") - 'clojure-keyword-face)) - - (should (eq (clojure-test-face-at 2 2 "{:ve/yCom|pLex.stu-ff/mxdCase 0}") - 'clojure-keyword-face)) - (should (eq (clojure-test-face-at 3 4 "{:ve/yCom|pLex.stu-ff/mxdCase 0}") - 'font-lock-type-face)) - (should (eq (clojure-test-face-at 5 5 "{:ve/yCom|pLex.stu-ff/mxdCase 0}") - 'default)) - (should (eq (clojure-test-face-at 6 29 "{:ve/yCom|pLex.stu-ff/mxdCase 0}") - 'clojure-keyword-face)) - - (should (eq (clojure-test-face-at - 2 2 "{:ve/yCom|pLex.stu-ff/ve/yCom|pLex.stu-ff 0}") - 'clojure-keyword-face)) - (should (eq (clojure-test-face-at - 3 4 "{:ve/yCom|pLex.stu-ff/ve/yCom|pLex.stu-ff 0}") - 'font-lock-type-face)) - (should (eq (clojure-test-face-at - 5 5 "{:ve/yCom|pLex.stu-ff/ve/yCom|pLex.stu-ff 0}") - 'default)) - (should (eq (clojure-test-face-at - 6 41 "{:ve/yCom|pLex.stu-ff/ve/yCom|pLex.stu-ff 0}") - 'clojure-keyword-face)) - - (should (eq (clojure-test-face-at - 2 2 "{:ve/yCom|pLex.stu-ff/.ve/yCom|pLex.stu-ff 0}") - 'clojure-keyword-face)) - (should (eq (clojure-test-face-at - 3 4 "{:ve/yCom|pLex.stu-ff/.ve/yCom|pLex.stu-ff 0}") - 'font-lock-type-face)) - (should (eq (clojure-test-face-at - 5 5 "{:ve/yCom|pLex.stu-ff/.ve/yCom|pLex.stu-ff 0}") - 'default)) - (should (eq (clojure-test-face-at - 6 42 "{:ve/yCom|pLex.stu-ff/.ve/yCom|pLex.stu-ff 0}") - 'clojure-keyword-face))) - -(ert-deftest clojure-mode-syntax-table/namespaced-def () - :tags '(fontification syntax-table) - (clojure-test-with-temp-buffer "(_c4/defconstrainedfn bar [] nil)" - (should (eq (clojure-test-face-at 2 4) 'font-lock-type-face)) - (should (eq (clojure-test-face-at 5 5) nil)) - (should (eq (clojure-test-face-at 6 18) 'font-lock-keyword-face)) - (should (eq (clojure-test-face-at 23 25) 'font-lock-function-name-face))) - (clojure-test-with-temp-buffer "(clo/defbar foo nil)" - (should (eq (clojure-test-face-at 2 4) 'font-lock-type-face)) - (should (eq (clojure-test-face-at 5 5) nil)) - (should (eq (clojure-test-face-at 6 11) 'font-lock-keyword-face)) - (should (eq (clojure-test-face-at 13 15) 'font-lock-function-name-face))) - (clojure-test-with-temp-buffer "(s/def ::keyword)" - (should (eq (clojure-test-face-at 2 2) 'font-lock-type-face)) - (should (eq (clojure-test-face-at 3 3) nil)) - (should (eq (clojure-test-face-at 4 6) 'font-lock-keyword-face)) - (should (eq (clojure-test-face-at 8 16) 'clojure-keyword-face)))) - - -(ert-deftest clojure-mode-syntax-table/variable-def () - :tags '(fontification syntax-table) - (should (eq (clojure-test-face-at 2 4 "(def foo 10)") - 'font-lock-keyword-face)) - (should (eq (clojure-test-face-at 6 8 "(def foo 10)") - 'font-lock-variable-name-face))) - -(ert-deftest clojure-mode-syntax-table/variable-def-string () - :tags '(fontification syntax-table) - (should (eq (clojure-test-face-at 10 16 "(def foo \"hello\")") - 'font-lock-string-face)) - (should (eq (clojure-test-face-at 10 16 "(def foo \"hello\" )") - 'font-lock-string-face)) - (should (eq (clojure-test-face-at 13 19 "(def foo \n \"hello\")") - 'font-lock-string-face)) - (should (eq (clojure-test-face-at 13 19 "(def foo \n \"hello\"\n)") - 'font-lock-string-face))) - -(ert-deftest clojure-mode-syntax-table/variable-def-string-with-docstring () - :tags '(fontification syntax-table) - (should (eq (clojure-test-face-at 10 16 "(def foo \"usage\" \"hello\")") - 'font-lock-doc-face)) - (should (eq (clojure-test-face-at 18 24 "(def foo \"usage\" \"hello\")") - 'font-lock-string-face)) - (should (eq (clojure-test-face-at 18 24 "(def foo \"usage\" \"hello\" )") - 'font-lock-string-face)) - (should (eq (clojure-test-face-at 21 27 "(def foo \"usage\" \n \"hello\")") - 'font-lock-string-face)) - (should (eq (clojure-test-face-at 13 19 "(def foo \n \"usage\" \"hello\")") - 'font-lock-doc-face)) - (should (eq (clojure-test-face-at 13 19 "(def foo \n \"usage\" \n \"hello\")") - 'font-lock-doc-face)) - (should (eq (clojure-test-face-at 24 30 "(def foo \n \"usage\" \n \"hello\")") - 'font-lock-string-face))) - -(ert-deftest clojure-mode-syntax-table/type-def () - :tags '(fontification syntax-table) - (clojure-test-with-temp-buffer "(deftype Foo)" - (should (eq (clojure-test-face-at 2 8) 'font-lock-keyword-face)) - (should (eq (clojure-test-face-at 10 12) 'font-lock-type-face)))) - -(ert-deftest clojure-mode-syntax-table/function-def () - :tags '(fontification syntax-table) - (clojure-test-with-temp-buffer "(defn foo [x] x)" - (should (eq (clojure-test-face-at 2 5) 'font-lock-keyword-face)) - (should (eq (clojure-test-face-at 7 9) 'font-lock-function-name-face)))) - -(ert-deftest clojure-mode-syntax-table/custom-def-with-special-chars1 () - :tags '(fontification syntax-table) - (clojure-test-with-temp-buffer "(defn* foo [x] x)" - (should (eq (clojure-test-face-at 2 6) 'font-lock-keyword-face)) - (should (eq (clojure-test-face-at 8 10) 'font-lock-function-name-face)))) - -(ert-deftest clojure-mode-syntax-table/custom-def-with-special-chars2 () - :tags '(fontification syntax-table) - (clojure-test-with-temp-buffer "(defsomething! foo [x] x)" - (should (eq (clojure-test-face-at 2 14) 'font-lock-keyword-face)) - (should (eq (clojure-test-face-at 16 18) 'font-lock-function-name-face)))) - -(ert-deftest clojure-mode-syntax-table/custom-def-with-special-chars3 () - :tags '(fontification syntax-table) - (clojure-test-with-temp-buffer "(def-something foo [x] x)" - (should (eq (clojure-test-face-at 2 14) 'font-lock-keyword-face)) - (should (eq (clojure-test-face-at 16 18) 'font-lock-function-name-face)))) - -(ert-deftest clojure-mode-syntax-table/fn () - :tags '(fontification syntax-table) - ;; try to byte-recompile the clojure-mode.el when the face of 'fn' is 't' - (should (eq (clojure-test-face-at 2 3 "(fn foo [x] x)") - 'font-lock-keyword-face)) - (should (eq (clojure-test-face-at 5 7 "(fn foo [x] x)") - 'font-lock-function-name-face))) - -(ert-deftest clojure-mode-syntax-table/lambda-params () - :tags '(fontification syntax-table) - (clojure-test-with-temp-buffer "#(+ % %2 %3 %&)" - (should (eq (clojure-test-face-at 5 5) 'font-lock-variable-name-face)) - (should (eq (clojure-test-face-at 7 8) 'font-lock-variable-name-face)) - (should (eq (clojure-test-face-at 10 11) 'font-lock-variable-name-face)) - (should (eq (clojure-test-face-at 13 14) 'font-lock-variable-name-face)))) - -(ert-deftest clojure-mode-syntax-table/nil () - :tags '(fontification syntax-table) - (should (eq (clojure-test-face-at 4 6 "(= nil x)") 'font-lock-constant-face)) - (should-not (eq (clojure-test-face-at 3 5 "(fnil x)") - 'font-lock-constant-face))) - -(ert-deftest clojure-mode-syntax-table/true () - :tags '(fontification syntax-table) - (should (eq (clojure-test-face-at 4 7 "(= true x)") - 'font-lock-constant-face))) - -(ert-deftest clojure-mode-syntax-table/false () - :tags '(fontification syntax-table) - (should (eq (clojure-test-face-at 4 8 "(= false x)") - 'font-lock-constant-face))) - -(ert-deftest clojure-mode-syntax-table/keyword-meta () - :tags '(fontification syntax-table) - (clojure-test-with-temp-buffer "^:meta-data" - (should (eq (clojure-test-face-at 1 1) nil)) - (should (equal (clojure-test-face-at 2 11) 'clojure-keyword-face)))) - -(ert-deftest clojure-mode-syntax-table/keyword-allowed-chars () - :tags '(fontification syntax-table) - (should (equal (clojure-test-face-at 1 8 ":aaa#bbb") 'clojure-keyword-face))) - -(ert-deftest clojure-mode-syntax-table/keyword-disallowed-chars () - :tags '(fontification syntax-table) - (should (eq (clojure-test-face-at 1 5 ":aaa@bbb") 'various-faces)) - (should (equal (clojure-test-face-at 1 4 ":aaa@bbb") 'clojure-keyword-face)) - (should (eq (clojure-test-face-at 1 5 ":aaa~bbb") 'various-faces)) - (should (equal (clojure-test-face-at 1 4 ":aaa~bbb") 'clojure-keyword-face)) - (should (eq (clojure-test-face-at 1 5 ":aaa@bbb") 'various-faces)) - (should (equal (clojure-test-face-at 1 4 ":aaa@bbb") 'clojure-keyword-face))) - -(ert-deftest clojure-mode-syntax-table/characters () - :tags '(fontification syntax-table) - (should (eq (clojure-test-face-at 1 2 "\\a") 'clojure-character-face)) - (should (eq (clojure-test-face-at 1 8 "\\newline") 'clojure-character-face)) - (should (eq (clojure-test-face-at 1 2 "\\1") 'clojure-character-face)) - (should (eq (clojure-test-face-at 1 6 "\\u0032") 'clojure-character-face)) - (should (eq (clojure-test-face-at 1 2 "\\+") 'clojure-character-face)) - (should (eq (clojure-test-face-at 1 2 "\\.") 'clojure-character-face)) - (should (eq (clojure-test-face-at 1 2 "\\,") 'clojure-character-face)) - (should (eq (clojure-test-face-at 1 2 "\\;") 'clojure-character-face))) - -(ert-deftest clojure-mode-syntax-table/refer-ns () - :tags '(fontification syntax-table) - (should (eq (clojure-test-face-at 1 3 "foo/var") 'font-lock-type-face)) - (should (eq (clojure-test-face-at 2 4 "@foo/var") 'font-lock-type-face))) - -(ert-deftest clojure-mode-syntax-table/dynamic-var () - :tags '(fontification syntax-table) - (should (eq (clojure-test-face-at 1 10 "*some-var*") - 'font-lock-variable-name-face)) - (should (eq (clojure-test-face-at 2 11 "@*some-var*") - 'font-lock-variable-name-face)) - (should (eq (clojure-test-face-at 9 13 "some.ns/*var*") - 'font-lock-variable-name-face)) - (should (eq (clojure-test-face-at 1 11 "*some-var?*") - 'font-lock-variable-name-face))) +(describe "clojure-mode-syntax-table" + + (when-fontifying-it "should handle stuff in backticks" + ("\"`#'s/trim`\"" + (1 2 font-lock-string-face) + (3 10 (font-lock-constant-face font-lock-string-face)) + (11 12 font-lock-string-face)) + + (";`#'s/trim`" + (1 1 font-lock-comment-delimiter-face) + (2 2 font-lock-comment-face) + (3 10 (font-lock-constant-face font-lock-comment-face)) + (11 11 font-lock-comment-face))) + + (when-fontifying-it "should handle stuff in strings" + ("\"a\\bc\\n\"" + (1 2 font-lock-string-face) + (3 4 (bold font-lock-string-face)) + (5 5 font-lock-string-face) + (6 7 (bold font-lock-string-face))) + + ("#\"a\\bc\\n\"" + (4 5 (bold font-lock-string-face)))) + + (when-fontifying-it "should handle stuff in double brackets" + ("\"[[#'s/trim]]\"" + (1 3 font-lock-string-face) + (4 11 (font-lock-constant-face font-lock-string-face)) + (12 14 font-lock-string-face)) + + (";[[#'s/trim]]" + (1 1 font-lock-comment-delimiter-face) + (2 3 font-lock-comment-face) + (4 11 (font-lock-constant-face font-lock-comment-face)) + (12 13 font-lock-comment-face))) + + (when-fontifying-it "should fontify let, when, and while type forms" + ("(when-alist [x 1]\n ())" + (2 11 font-lock-keyword-face)) + + ("(while-alist [x 1]\n ())" + (2 12 font-lock-keyword-face)) + + ("(let-alist [x 1]\n ())" + (2 10 font-lock-keyword-face))) + + (when-fontifying-it "should handle comment macros" + ("#_" + (1 2 nil)) + + ("#_ \n;; some crap\n (lala 0101\n lao\n\n 0 0i)" + (1 2 nil)) + + ("#_ \n;; some crap\n (lala 0101\n lao\n\n 0 0i)" + (5 41 font-lock-comment-face))) + + (when-fontifying-it "should handle namespace declarations" + ("(ns .validns)" + (5 12 font-lock-type-face)) + + ("(ns =validns)" + (5 12 font-lock-type-face)) + + ("(ns .ValidNs=<>?+|?*.)" + (5 21 font-lock-type-face)) + + ("(ns ValidNs<>?+|?*.b*ar.ba*z)" + (5 28 font-lock-type-face)) + + ("(ns other.valid.ns)" + (5 18 font-lock-type-face)) + + ("(ns oneword)" + (5 11 font-lock-type-face)) + + ("(ns foo.bar)" + (5 11 font-lock-type-face)) + + ("(ns Foo.bar)" + (5 11 font-lock-type-face) + (5 11 font-lock-type-face) + (5 11 font-lock-type-face)) + + ("(ns Foo-bar)" + (5 11 font-lock-type-face) + (5 11 font-lock-type-face)) + + ("(ns foo-Bar)" + (5 11 font-lock-type-face)) + + ("(ns one.X)" + (5 9 font-lock-type-face)) + + ("(ns ^:md ns-name)" + (10 16 font-lock-type-face)) + + ("(ns ^:md \n ns-name)" + (13 19 font-lock-type-face)) + + ("(ns ^:md1 ^:md2 ns-name)" + (17 23 font-lock-type-face)) + + ("(ns ^:md1 ^{:md2 true} ns-name)" + (24 30 font-lock-type-face)) + + ("(ns ^{:md2 true} ^:md1 ns-name)" + (24 30 font-lock-type-face)) + + ("(ns ^:md1 ^{:md2 true} \n ns-name)" + (27 33 font-lock-type-face)) + + ("(ns ^{:md2 true} ^:md1 \n ns-name)" + (27 33 font-lock-type-face))) + + (when-fontifying-it "should handle one word" + (" oneword" + (2 8 nil)) + + ("@oneword" + (2 8 nil)) + + ("#oneword" + (2 8 nil)) + + (".oneword" + (2 8 nil)) + + ("#^oneword" + (3 9 font-lock-type-face)) ;; type-hint + + ("(oneword)" + (2 8 nil)) + + ("(oneword/oneword)" + (2 8 font-lock-type-face) + (9 10 nil) + (11 16 nil)) + + ("(oneword/seg.mnt)" + (2 8 font-lock-type-face) + (9 10 nil) + (11 16 nil)) + + ("(oneword/mxdCase)" + (2 8 font-lock-type-face) + (9 10 nil) + (11 16 nil)) + + ("(oneword/CmlCase)" + (2 8 font-lock-type-face) + (9 10 nil) + (11 16 nil)) + + ("(oneword/ve/yCom|pLex.stu-ff)" + (2 8 font-lock-type-face) + (9 10 nil) + (11 28 nil)) + + ("(oneword/.ve/yCom|pLex.stu-ff)" + (2 8 font-lock-type-face) + (9 10 nil) + (12 29 nil))) + + (when-fontifying-it "should handle a segment" + (" seg.mnt" + (2 8 nil)) + + ("@seg.mnt" + (2 8 nil)) + + ("#seg.mnt" + (2 8 nil)) + + (".seg.mnt" + (2 8 nil)) + + ("#^seg.mnt" + (3 9 font-lock-type-face)) ;; type-hint + + ("(seg.mnt)" + (2 8 nil)) + + ("(seg.mnt/oneword)" + (2 8 font-lock-type-face) + (9 10 nil) + (11 16 nil)) + + ("(seg.mnt/seg.mnt)" + (2 8 font-lock-type-face) + (9 10 nil) + (11 16 nil)) + + ("(seg.mnt/mxdCase)" + (2 8 font-lock-type-face) + (9 10 nil) + (11 16 nil)) + + ("(seg.mnt/CmlCase)" + (2 8 font-lock-type-face) + (9 10 nil) + (11 16 nil)) + + ("(seg.mnt/ve/yCom|pLex.stu-ff)" + (2 8 font-lock-type-face) + (9 10 nil) + (11 28 nil)) + + ("(seg.mnt/.ve/yCom|pLex.stu-ff)" + (2 8 font-lock-type-face) + (9 10 nil) + (12 29 nil))) + + (when-fontifying-it "should handle camelcase" + (" CmlCase" + (2 8 nil)) + + ("@CmlCase" + (2 8 nil)) + + ("#CmlCase" + (2 8 nil)) + + (".CmlCase" + (2 8 nil)) + + ("#^CmlCase" + (3 9 font-lock-type-face)) ;; type-hint + + ("(CmlCase)" + (2 8 nil)) + + ("(CmlCase/oneword)" + (2 8 font-lock-type-face) + (9 10 nil) + (11 16 nil)) + + ("(CmlCase/seg.mnt)" + (2 8 font-lock-type-face) + (9 10 nil) + (11 16 nil)) + + ("(CmlCase/mxdCase)" + (2 8 font-lock-type-face) + (9 10 nil) + (11 16 nil)) + + ("(CmlCase/CmlCase)" + (2 8 font-lock-type-face) + (9 10 nil) + (11 16 nil)) + + ("(CmlCase/ve/yCom|pLex.stu-ff)" + (2 8 font-lock-type-face) + (9 10 nil) + (11 28 nil)) + + ("(CmlCase/.ve/yCom|pLex.stu-ff)" + (2 8 font-lock-type-face) + (9 10 nil) + (12 29 nil))) + + (when-fontifying-it "should handle mixed case" + (" mxdCase" + (2 8 nil)) + + ("@mxdCase" + (2 8 nil)) + + ("#mxdCase" + (2 8 nil)) + + (".mxdCase" + (2 8 nil)) + + ("#^mxdCase" + (3 9 font-lock-type-face)) ;; type-hint + + ("(mxdCase)" + (2 8 nil)) + + ("(mxdCase/oneword)" + (2 8 font-lock-type-face) + (9 10 nil) + (11 16 nil)) + + ("(mxdCase/seg.mnt)" + (2 8 font-lock-type-face) + (9 10 nil) + (11 16 nil)) + + ("(mxdCase/mxdCase)" + (2 8 font-lock-type-face) + (9 10 nil) + (11 16 nil)) + + ("(mxdCase/CmlCase)" + (2 8 font-lock-type-face) + (9 10 nil) + (11 16 nil)) + + ("(mxdCase/ve/yCom|pLex.stu-ff)" + (2 8 font-lock-type-face) + (9 10 nil) + (11 28 nil)) + + ("(mxdCase/.ve/yCom|pLex.stu-ff)" + (2 8 font-lock-type-face) + (9 10 nil) + (12 29 nil))) + + (when-fontifying-it "should handle very complex stuff" + (" ve/yCom|pLex.stu-ff" + (3 4 font-lock-type-face) + (5 21 nil)) + + (" @ve/yCom|pLex.stu-ff" + (2 2 nil) + (3 4 font-lock-type-face) + (5 21 nil)) + + (" #ve/yCom|pLex.stu-ff" + (2 4 font-lock-type-face) + (5 21 nil)) + + (" .ve/yCom|pLex.stu-ff" + (2 4 font-lock-type-face) + (5 21 nil)) + + ;; type-hint + ("#^ve/yCom|pLex.stu-ff" + (1 2 default) + (3 4 font-lock-type-face) + (5 21 default)) + + ("^ve/yCom|pLex.stu-ff" + (2 3 font-lock-type-face) + (5 20 default)) + + (" (ve/yCom|pLex.stu-ff)" + (3 4 font-lock-type-face) + (5 21 nil)) + + (" (ve/yCom|pLex.stu-ff/oneword)" + (3 4 font-lock-type-face) + (5 29 nil)) + + (" (ve/yCom|pLex.stu-ff/seg.mnt)" + (3 4 font-lock-type-face) + (5 29 nil)) + + (" (ve/yCom|pLex.stu-ff/mxdCase)" + (3 4 font-lock-type-face) + (5 29 nil)) + + (" (ve/yCom|pLex.stu-ff/CmlCase)" + (3 4 font-lock-type-face) + (5 29 nil)) + + (" (ve/yCom|pLex.stu-ff/ve/yCom|pLex.stu-ff)" + (3 4 font-lock-type-face) + (5 41 nil)) + + (" (ve/yCom|pLex.stu-ff/.ve/yCom|pLex.stu-ff)" + (3 4 font-lock-type-face) + (5 42 nil))) + + (when-fontifying-it "should handle oneword keywords" + (" :oneword" + (3 9 clojure-keyword-face )) + + ("{:oneword 0}" + (3 9 clojure-keyword-face)) + + ("{:#oneword 0}" + (3 10 clojure-keyword-face)) + + ("{:.oneword 0}" + (3 10 clojure-keyword-face)) + + ("{:oneword/oneword 0}" + (3 9 font-lock-type-face) + (10 10 default) + (11 17 clojure-keyword-face)) + + ("{:oneword/seg.mnt 0}" + (3 9 font-lock-type-face) + (10 10 default) + (11 17 clojure-keyword-face)) + + ("{:oneword/CmlCase 0}" + (3 9 font-lock-type-face) + (10 10 default) + (11 17 clojure-keyword-face)) + + ("{:oneword/mxdCase 0}" + (3 9 font-lock-type-face) + (10 10 default) + (11 17 clojure-keyword-face)) + + ("{:oneword/ve/yCom|pLex.stu-ff 0}" + (3 9 font-lock-type-face) + (10 10 default) + (11 29 clojure-keyword-face)) + + ("{:oneword/.ve/yCom|pLex.stu-ff 0}" + (3 9 font-lock-type-face) + (10 10 default) + (11 30 clojure-keyword-face))) + + (when-fontifying-it "should handle namespaced keywords" + ("::foo" + (1 5 clojure-keyword-face)) + + (":_::_:foo" + (1 9 clojure-keyword-face)) + + (":_:_:foo" + (1 8 clojure-keyword-face)) + + (":foo/:bar" + (1 9 clojure-keyword-face)) + + ("::_:foo" + (1 7 clojure-keyword-face)) + + ("::_:_:foo" + (1 9 clojure-keyword-face)) + + (":_:_:foo/_" + (1 1 clojure-keyword-face) + (2 8 font-lock-type-face) + (9 9 default) + (10 10 clojure-keyword-face)) + + (":_:_:foo/bar" + (10 12 clojure-keyword-face)) + + (":_:_:foo/bar/eee" + (10 16 clojure-keyword-face)) + + (":_:_:foo/bar_:foo" + (10 17 clojure-keyword-face)) + + (":_:_:foo/bar_:_:foo" + (10 19 clojure-keyword-face))) + + (when-fontifying-it "should handle segment keywords" + (" :seg.mnt" + (3 9 clojure-keyword-face)) + + ("{:seg.mnt 0}" + (3 9 clojure-keyword-face)) + + ("{:#seg.mnt 0}" + (3 10 clojure-keyword-face)) + + ("{:.seg.mnt 0}" + (3 10 clojure-keyword-face)) + + ("{:seg.mnt/oneword 0}" + (3 9 font-lock-type-face) + (10 10 default) + (11 17 clojure-keyword-face)) + + ("{:seg.mnt/seg.mnt 0}" + (3 9 font-lock-type-face ) + (10 10 default) + (11 17 clojure-keyword-face)) + + ("{:seg.mnt/CmlCase 0}" + (3 9 font-lock-type-face) + (10 10 default) + (11 17 clojure-keyword-face)) + + ("{:seg.mnt/mxdCase 0}" + (3 9 font-lock-type-face) + (10 10 default) + (11 17 clojure-keyword-face)) + + ("{:seg.mnt/ve/yCom|pLex.stu-ff 0}" + (3 9 font-lock-type-face) + (10 10 default) + (11 29 clojure-keyword-face)) + + ("{:seg.mnt/.ve/yCom|pLex.stu-ff 0}" + (3 9 font-lock-type-face) + (10 10 default) + (11 30 clojure-keyword-face))) + + (when-fontifying-it "should handle camel case keywords" + (" :CmlCase" + (3 9 clojure-keyword-face)) + + ("{:CmlCase 0}" + (3 9 clojure-keyword-face)) + + ("{:#CmlCase 0}" + (3 10 clojure-keyword-face)) + + ("{:.CmlCase 0}" + (3 10 clojure-keyword-face)) + + ("{:CmlCase/oneword 0}" + (3 9 font-lock-type-face) + (10 10 default) + (11 17 clojure-keyword-face)) + + ("{:CmlCase/seg.mnt 0}" + (3 9 font-lock-type-face) + (10 10 default) + (11 17 clojure-keyword-face)) + + ("{:CmlCase/CmlCase 0}" + (3 9 font-lock-type-face) + (10 10 default) + (11 17 clojure-keyword-face)) + + ("{:CmlCase/mxdCase 0}" + (3 9 font-lock-type-face) + (10 10 default) + (11 17 clojure-keyword-face)) + + ("{:CmlCase/ve/yCom|pLex.stu-ff 0}" + (3 9 font-lock-type-face) + (10 10 default) + (11 29 clojure-keyword-face)) + + ("{:CmlCase/.ve/yCom|pLex.stu-ff 0}" + (3 9 font-lock-type-face) + (10 10 default) + (11 30 clojure-keyword-face))) + + (when-fontifying-it "should handle mixed case keywords" + (" :mxdCase" + (3 9 clojure-keyword-face)) + + ("{:mxdCase 0}" + (3 9 clojure-keyword-face)) + + ("{:#mxdCase 0}" + (3 10 clojure-keyword-face)) + + ("{:.mxdCase 0}" + (3 10 clojure-keyword-face)) + + ("{:mxdCase/oneword 0}" + (3 9 font-lock-type-face) + (10 10 default) + (11 17 clojure-keyword-face)) + + ("{:mxdCase/seg.mnt 0}" + (3 9 font-lock-type-face) + (10 10 default) + (11 17 clojure-keyword-face)) + + ("{:mxdCase/CmlCase 0}" + (3 9 font-lock-type-face) + (10 10 default) + (11 17 clojure-keyword-face)) + + ("{:mxdCase/mxdCase 0}" + (3 9 font-lock-type-face) + (10 10 default) + (11 17 clojure-keyword-face)) + + ("{:mxdCase/ve/yCom|pLex.stu-ff 0}" + (3 9 font-lock-type-face) + (10 10 default) + (11 29 clojure-keyword-face)) + + ("{:mxdCase/.ve/yCom|pLex.stu-ff 0}" + (3 9 font-lock-type-face) + (10 10 default) + (11 30 clojure-keyword-face))) + + (when-fontifying-it "should handle very complex keywords" + (" :ve/yCom|pLex.stu-ff" + (3 4 font-lock-type-face) + (5 5 default) + (6 21 clojure-keyword-face)) + + ("{:ve/yCom|pLex.stu-ff 0}" + (2 2 clojure-keyword-face) + (3 4 font-lock-type-face) + (5 5 default) + (6 21 clojure-keyword-face)) + + ("{:#ve/yCom|pLex.stu-ff 0}" + (2 2 clojure-keyword-face) + (3 5 font-lock-type-face) + (6 6 default) + (7 22 clojure-keyword-face)) + + ("{:.ve/yCom|pLex.stu-ff 0}" + (2 2 clojure-keyword-face) + (3 5 font-lock-type-face) + (6 6 default) + (7 22 clojure-keyword-face)) + + ("{:ve/yCom|pLex.stu-ff/oneword 0}" + (2 2 clojure-keyword-face) + (3 4 font-lock-type-face) + (5 5 default) + (6 29 clojure-keyword-face)) + + ("{:ve/yCom|pLex.stu-ff/seg.mnt 0}" + (2 2 clojure-keyword-face) + (3 4 font-lock-type-face) + (5 5 default) + (6 29 clojure-keyword-face)) + + ("{:ve/yCom|pLex.stu-ff/ClmCase 0}" + (2 2 clojure-keyword-face) + (3 4 font-lock-type-face) + (5 5 default) + (6 29 clojure-keyword-face)) + + ("{:ve/yCom|pLex.stu-ff/mxdCase 0}" + (2 2 clojure-keyword-face) + (3 4 font-lock-type-face) + (5 5 default) + (6 29 clojure-keyword-face)) + + ("{:ve/yCom|pLex.stu-ff/ve/yCom|pLex.stu-ff 0}" + (2 2 clojure-keyword-face) + (3 4 font-lock-type-face) + (5 5 default) + (6 41 clojure-keyword-face)) + + ("{:ve/yCom|pLex.stu-ff/.ve/yCom|pLex.stu-ff 0}" + (2 2 clojure-keyword-face) + (3 4 font-lock-type-face) + (5 5 default) + (6 42 clojure-keyword-face))) + + (when-fontifying-it "should handle namespaced defs" + ("(_c4/defconstrainedfn bar [] nil)" + (2 4 font-lock-type-face) + (5 5 nil) + (6 18 font-lock-keyword-face) + (23 25 font-lock-function-name-face)) + + ("(clo/defbar foo nil)" + (2 4 font-lock-type-face) + (5 5 nil) + (6 11 font-lock-keyword-face) + (13 15 font-lock-function-name-face)) + + ("(s/def ::keyword)" + (2 2 font-lock-type-face) + (3 3 nil) + (4 6 font-lock-keyword-face) + (8 16 clojure-keyword-face))) + + (when-fontifying-it "should handle variables defined with def" + ("(def foo 10)" + (2 4 font-lock-keyword-face) + (6 8 font-lock-variable-name-face))) + + (when-fontifying-it "should handle variables definitions of type string" + ("(def foo \"hello\")" + (10 16 font-lock-string-face)) + + ("(def foo \"hello\" )" + (10 16 font-lock-string-face)) + + ("(def foo \n \"hello\")" + (13 19 font-lock-string-face)) + + ("(def foo \n \"hello\"\n)" + (13 19 font-lock-string-face))) + + (when-fontifying-it "variable-def-string-with-docstring" + ("(def foo \"usage\" \"hello\")" + (10 16 font-lock-doc-face) + (18 24 font-lock-string-face)) + + ("(def foo \"usage\" \"hello\" )" + (18 24 font-lock-string-face)) + + ("(def foo \"usage\" \n \"hello\")" + (21 27 font-lock-string-face)) + + ("(def foo \n \"usage\" \"hello\")" + (13 19 font-lock-doc-face)) + + ("(def foo \n \"usage\" \n \"hello\")" + (13 19 font-lock-doc-face) + (24 30 font-lock-string-face))) + + (when-fontifying-it "should handle deftype" + ("(deftype Foo)" + (2 8 font-lock-keyword-face) + (10 12 font-lock-type-face))) + + (when-fontifying-it "should handle defn" + ("(defn foo [x] x)" + (2 5 font-lock-keyword-face) + (7 9 font-lock-function-name-face))) + + (when-fontifying-it "should handle a custom def with special chars 1" + ("(defn* foo [x] x)" + (2 6 font-lock-keyword-face) + (8 10 font-lock-function-name-face))) + + (when-fontifying-it "should handle a custom def with special chars 2" + ("(defsomething! foo [x] x)" + (2 14 font-lock-keyword-face) + (16 18 font-lock-function-name-face))) + + (when-fontifying-it "should handle a custom def with special chars 3" + ("(def-something foo [x] x)" + (2 14 font-lock-keyword-face)) + + ("(def-something foo [x] x)" + (16 18 font-lock-function-name-face))) + + (when-fontifying-it "should handle fn" + ;; try to byte-recompile the clojure-mode.el when the face of 'fn' is 't' + ("(fn foo [x] x)" + (2 3 font-lock-keyword-face) + ( 5 7 font-lock-function-name-face))) + + (when-fontifying-it "should handle lambda-params" + ("#(+ % %2 %3 %&)" + (5 5 font-lock-variable-name-face) + (7 8 font-lock-variable-name-face) + (10 11 font-lock-variable-name-face) + (13 14 font-lock-variable-name-face))) + + (when-fontifying-it "should handle nils" + ("(= nil x)" + (4 6 font-lock-constant-face)) + + ("(fnil x)" + (3 5 nil))) + + (when-fontifying-it "should handle true" + ("(= true x)" + (4 7 font-lock-constant-face))) + + (when-fontifying-it "should handle false" + ("(= false x)" + (4 8 font-lock-constant-face))) + + (when-fontifying-it "should handle keyword-meta" + ("^:meta-data" + (1 1 nil) + (2 11 clojure-keyword-face))) + + (when-fontifying-it "should handle a keyword with allowed characters" + (":aaa#bbb" + (1 8 clojure-keyword-face))) + + (when-fontifying-it "should handle a keyword with disallowed characters" + (":aaa@bbb" + (1 5 various-faces)) + + (":aaa@bbb" + (1 4 clojure-keyword-face)) + + (":aaa~bbb" + (1 5 various-faces)) + + (":aaa~bbb" + (1 4 clojure-keyword-face)) + + (":aaa@bbb" + (1 5 various-faces)) + + (":aaa@bbb" + (1 4 clojure-keyword-face))) + + (when-fontifying-it "should handle characters" + ("\\a" + (1 2 clojure-character-face)) + + ("\\newline" + (1 8 clojure-character-face)) + + ("\\1" + (1 2 clojure-character-face)) + + ("\\u0032" + (1 6 clojure-character-face)) + + ("\\+" + (1 2 clojure-character-face)) + + ("\\." + (1 2 clojure-character-face)) + + ("\\," + (1 2 clojure-character-face)) + + ("\\;" + (1 2 clojure-character-face))) + + (when-fontifying-it "should handle referred vars" + ("foo/var" + (1 3 font-lock-type-face)) + + ("@foo/var" + (2 4 font-lock-type-face))) + + (when-fontifying-it "should handle dynamic vars" + ("*some-var*" + (1 10 font-lock-variable-name-face)) + + ("@*some-var*" + (2 11 font-lock-variable-name-face)) + + ("some.ns/*var*" + (9 13 font-lock-variable-name-face)) + + ("*some-var?*" + (1 11 font-lock-variable-name-face)))) (provide 'clojure-mode-font-lock-test) diff --git a/clojure-mode-indentation-test.el b/clojure-mode-indentation-test.el index 7afa3ff..2ec7e26 100644 --- a/clojure-mode-indentation-test.el +++ b/clojure-mode-indentation-test.el @@ -24,27 +24,16 @@ ;;; Code: (require 'clojure-mode) +(require 'test-helper) (require 'cl-lib) -(require 'ert) +(require 'buttercup) (require 's) -(ert-deftest dont-hang-on-eob () - (with-temp-buffer - (insert "(let [a b]") - (clojure-mode) - (goto-char (point-max)) - (should - (with-timeout (2) - (newline-and-indent) - t)))) +(defmacro when-indenting-with-point-it (description before after) + "Return a buttercup spec. -(defmacro check-indentation (description before after &optional var-bindings) - "Declare an ert test for indentation behaviour. -The test will check that the swift indentation command changes the buffer -from one state to another. It will also test that point is moved to an -expected position. - -DESCRIPTION is a symbol describing the test. +Check whether the swift indentation command will correctly change the buffer. +Will also check whether point is moved to the expected position. BEFORE is the buffer string before indenting, where a pipe (|) represents point. @@ -52,232 +41,274 @@ point. AFTER is the expected buffer string after indenting, where a pipe (|) represents the expected position of point. -VAR-BINDINGS is an optional let-bindings list. It can be used to set the -values of customisable variables." +DESCRIPTION is a string with the description of the spec." + (declare (indent 1)) + `(it ,description + (let* ((after ,after) + (clojure-indent-style 'always-align) + (expected-cursor-pos (1+ (s-index-of "|" after))) + (expected-state (delete ?| after))) + (with-clojure-buffer ,before + (goto-char (point-min)) + (search-forward "|") + (delete-char -1) + (font-lock-ensure) + (indent-according-to-mode) + (expect (buffer-string) :to-equal expected-state) + (expect (point) :to-equal expected-cursor-pos))))) + +;; Backtracking indent +(defmacro when-indenting-it (description &optional style &rest forms) + "Return a buttercup spec. + +Check that all FORMS correspond to properly indented sexps. + +STYLE allows overriding the default clojure-indent-style 'always-align. + +DESCRIPTION is a string with the description of the spec." (declare (indent 1)) - (let ((fname (intern (format "indentation/%s" description)))) - `(ert-deftest ,fname () - (let* ((after ,after) - (clojure-indent-style 'always-align) - (expected-cursor-pos (1+ (s-index-of "|" after))) - (expected-state (delete ?| after)) - ,@var-bindings) - (with-temp-buffer - (insert ,before) - (goto-char (point-min)) - (search-forward "|") - (delete-char -1) - (clojure-mode) - (font-lock-ensure) - (indent-according-to-mode) - - (should (equal expected-state (buffer-string))) - (should (equal expected-cursor-pos (point)))))))) + (when (stringp style) + (setq forms (cons style forms)) + (setq style '(quote always-align))) + `(it ,description + (progn + ,@(mapcar (lambda (form) + `(with-temp-buffer + (clojure-mode) + (insert "\n" ,form);,(replace-regexp-in-string "\n +" "\n " form)) + (let ((clojure-indent-style ,style)) + (indent-region (point-min) (point-max))) + (expect (buffer-string) :to-equal ,(concat "\n" form)))) + forms)))) + +(defmacro when-aligning-it (description &rest forms) + "Return a buttercup spec. + +Check that all FORMS correspond to properly indented sexps. + +DESCRIPTION is a string with the description of the spec." + (declare (indent defun)) + `(it ,description + (let ((clojure-align-forms-automatically t) + (clojure-align-reader-conditionals t)) + ,@(mapcar (lambda (form) + `(with-temp-buffer + (clojure-mode) + (insert "\n" ,(replace-regexp-in-string " +" " " form)) + (indent-region (point-min) (point-max)) + (should (equal (buffer-substring-no-properties (point-min) (point-max)) + ,(concat "\n" form))))) + forms)) + (let ((clojure-align-forms-automatically nil)) + ,@(mapcar (lambda (form) + `(with-temp-buffer + (clojure-mode) + (insert "\n" ,(replace-regexp-in-string " +" " " form)) + ;; This is to check that we did NOT align anything. Run + ;; `indent-region' and then check that no extra spaces + ;; where inserted besides the start of the line. + (indent-region (point-min) (point-max)) + (goto-char (point-min)) + (should-not (search-forward-regexp "\\([^\s\n]\\) +" nil 'noerror)))) + forms)))) ;; Provide font locking for easier test editing. (font-lock-add-keywords 'emacs-lisp-mode - `((,(rx "(" (group "check-indentation") eow) + `((,(rx "(" (group "when-indenting-with-point-it") eow) (1 font-lock-keyword-face)) (,(rx "(" - (group "check-indentation") (+ space) + (group "when-indenting-with-point-it") (+ space) (group bow (+ (not space)) eow) ) (1 font-lock-keyword-face) (2 font-lock-function-name-face)))) - -;;; Tests - - -(check-indentation no-indentation-at-top-level - "|x" - "|x") - -(check-indentation cond-indentation - " -(cond -|x)" - " -(cond - |x)") - -(check-indentation threading-with-expression-on-first-line - " -(->> expr - |ala)" - " -(->> expr - |ala)") - -(check-indentation threading-with-expression-on-second-line - " -(->> -|expr)" - " -(->> - |expr)") - -(check-indentation no-indent-for-def-string - "(def foo \"hello|\")" - "(def foo \"hello|\")") - -(check-indentation doc-strings-without-indent-specified - " -(defn some-fn -|\"some doc string\")" - " -(defn some-fn - |\"some doc string\")") - -(check-indentation doc-strings-with-correct-indent-specified - " -(defn some-fn - |\"some doc string\")" - " -(defn some-fn - |\"some doc string\")") - -(check-indentation doc-strings-with-additional-indent-specified - " -(defn some-fn - |\"some doc string - - some note\")" - " -(defn some-fn - |\"some doc string - - some note\")") - -;; we can specify different indentation for symbol with some ns prefix -(put-clojure-indent 'bala 0) -(put-clojure-indent 'ala/bala 1) - -(check-indentation symbol-without-ns - " -(bala -|one)" - " -(bala - |one)") - -(check-indentation symbol-with-ns - " -(ala/bala top -|one)" - " -(ala/bala top - |one)") - -;; we can pass a lambda to explicitely set the column -(put-clojure-indent 'arsymbol (lambda (indent-point state) 0)) - -(check-indentation symbol-with-lambda - " +(describe "indentation" + (it "should not hang on end of buffer" + (with-clojure-buffer "(let [a b]" + (goto-char (point-max)) + (expect + (with-timeout (2) + (newline-and-indent) + t)))) + + (when-indenting-with-point-it "should have no indentation at top level" + "|x" + + "|x") + + (when-indenting-with-point-it "should indent cond" + " + (cond + |x)" + + " + (cond + |x)") + + (when-indenting-with-point-it "should indent threading macro with expression on first line" + " + (->> expr + |ala)" + + " + (->> expr + |ala)") + + (when-indenting-with-point-it "should indent threading macro with expression on second line" + " + (->> + |expr)" + + " + (->> + |expr)") + + (when-indenting-with-point-it "should not indent for def string" + "(def foo \"hello|\")" + "(def foo \"hello|\")") + + (when-indenting-with-point-it "should indent doc strings" + " + (defn some-fn + |\"some doc string\")" + " + (defn some-fn + |\"some doc string\")") + + (when-indenting-with-point-it "should not indent doc strings when correct indent already specified" + " + (defn some-fn + |\"some doc string\")" + " + (defn some-fn + |\"some doc string\")") + + (when-indenting-with-point-it "should handle doc strings with additional indent specified" + " + (defn some-fn + |\"some doc string + - some note\")" + " + (defn some-fn + |\"some doc string + - some note\")") + + (describe "specify different indentation for symbol with some ns prefix" + (put-clojure-indent 'bala 0) + (put-clojure-indent 'ala/bala 1) + + (when-indenting-with-point-it "should handle a symbol without ns" + " + (bala + |one)" + " + (bala + |one)") + + (when-indenting-with-point-it "should handle a symbol with ns" + " + (ala/bala top + |one)" + " + (ala/bala top + |one)")) + + (describe "we can pass a lambda to explicitly set the column" + (put-clojure-indent 'arsymbol (lambda (indent-point state) 0)) + + (when-indenting-with-point-it "should handle a symbol with lambda" + " (arsymbol - |one)" - " +|one)" + " (arsymbol -|one)") - -(check-indentation form-with-metadata - " -(ns ^:doc app.core -|(:gen-class))" -" -(ns ^:doc app.core - |(:gen-class))") - -(check-indentation multiline-sexps - " -[[ - 2] a -|x]" -" -[[ - 2] a - |x]") - -(check-indentation reader-conditionals - " -#?(:clj :foo -|:cljs :bar)" - " -#?(:clj :foo - |:cljs :bar)") - -(check-indentation backtracking-with-aliases - " -(clojure.core/letfn [(twice [x] -|(* x 2))] - :a)" - " -(clojure.core/letfn [(twice [x] - |(* x 2))] - :a)") - -(check-indentation fixed-normal-indent - "(cond - (or 1 - 2) 3 -|:else 4)" - "(cond - (or 1 - 2) 3 - |:else 4)") - -(check-indentation fixed-normal-indent-2 - "(fact {:spec-type +|one)")) + + (when-indenting-with-point-it "should indent a form with metadata" + " + (ns ^:doc app.core + |(:gen-class))" + " + (ns ^:doc app.core + |(:gen-class))") + + (when-indenting-with-point-it "should handle multiline sexps" + " + [[ + 2] a + |x]" + " + [[ + 2] a + |x]") + + (when-indenting-with-point-it "should indent reader conditionals" + " + #?(:clj :foo + |:cljs :bar)" + " + #?(:clj :foo + |:cljs :bar)") + + (when-indenting-with-point-it "should handle backtracking with aliases" + " + (clojure.core/letfn [(twice [x] + |(* x 2))] + :a)" + " + (clojure.core/letfn [(twice [x] + |(* x 2))] + :a)") + + (when-indenting-with-point-it "should handle fixed-normal-indent" + "(cond + (or 1 + 2) 3 + |:else 4)" + + "(cond + (or 1 + 2) 3 + |:else 4)") + + (when-indenting-with-point-it "should handle fixed-normal-indent-2" + "(fact {:spec-type :charnock-column-id} #{\"charnock\"} |{:spec-type :charnock-column-id} #{\"current_charnock\"})" - "(fact {:spec-type + + "(fact {:spec-type :charnock-column-id} #{\"charnock\"} |{:spec-type :charnock-column-id} #{\"current_charnock\"})") - -;;; Backtracking indent -(defmacro def-full-indent-test (name &optional style &rest forms) - "Verify that all FORMs correspond to a properly indented sexps." - (declare (indent 1)) - (when (stringp style) - (setq forms (cons style forms)) - (setq style '(quote always-align))) - `(ert-deftest ,(intern (format "test-backtracking-%s" name)) () - (progn - ,@(mapcar (lambda (form) - `(with-temp-buffer - (clojure-mode) - (insert "\n" ,(replace-regexp-in-string "\n +" "\n " form)) - (let ((clojure-indent-style ,style)) - (indent-region (point-min) (point-max))) - (should (equal (buffer-string) - ,(concat "\n" form))))) - forms)))) - -(def-full-indent-test closing-paren - "(ns ca + (when-indenting-it "closing-paren" + "(ns ca (:gen-class) )") -(def-full-indent-test default-is-not-a-define - "(default a + (when-indenting-it "default-is-not-a-define" + "(default a b b)" - "(some.namespace/default a + "(some.namespace/default a b b)") -(def-full-indent-test extend-type-allow-multiarity - "(extend-type Banana + + (when-indenting-it "should handle extend-type with multiarity" + "(extend-type Banana Fruit (subtotal ([item] (* 158 (:qty item))) ([item a] (* a (:qty item)))))" - "(extend-protocol Banana + + "(extend-protocol Banana Fruit (subtotal ([item] @@ -285,8 +316,9 @@ values of customisable variables." ([item a] (* a (:qty item)))))") -(def-full-indent-test deftype-allow-multiarity - "(deftype Banana [] + + (when-indenting-it "should handle deftype with multiarity" + "(deftype Banana [] Fruit (subtotal ([item] @@ -294,8 +326,8 @@ values of customisable variables." ([item a] (* a (:qty item)))))") -(def-full-indent-test defprotocol - "(defprotocol IFoo + (when-indenting-it "should handle defprotocol" + "(defprotocol IFoo (foo [this] \"Why is this over here?\") (foo-2 @@ -303,16 +335,16 @@ values of customisable variables." \"Why is this over here?\"))") -(def-full-indent-test definterface - "(definterface IFoo + (when-indenting-it "should handle definterface" + "(definterface IFoo (foo [this] \"Why is this over here?\") (foo-2 [this] \"Why is this over here?\"))") -(def-full-indent-test specify - "(specify obj + (when-indenting-it "should handle specify" + "(specify obj ISwap (-swap! ([this f] (reset! this (f @this))) @@ -320,8 +352,8 @@ values of customisable variables." ([this f a b] (reset! this (f @this a b))) ([this f a b xs] (reset! this (apply f @this a b xs)))))") -(def-full-indent-test specify! - "(specify! obj + (when-indenting-it "should handle specify!" + "(specify! obj ISwap (-swap! ([this f] (reset! this (f @this))) @@ -329,28 +361,28 @@ values of customisable variables." ([this f a b] (reset! this (f @this a b))) ([this f a b xs] (reset! this (apply f @this a b xs)))))") -(def-full-indent-test non-symbol-at-start - "{\"1\" 2 + (when-indenting-it "should handle non-symbol at start" + "{\"1\" 2 *3 4}") -(def-full-indent-test non-symbol-at-start-2 - "(\"1\" 2 + (when-indenting-it "should handle non-symbol at start 2" + "(\"1\" 2 *3 4)") -(def-full-indent-test defrecord - "(defrecord TheNameOfTheRecord + (when-indenting-it "should handle defrecord" + "(defrecord TheNameOfTheRecord [a pretty long argument list] SomeType (assoc [_ x] (.assoc pretty x 10)))") -(def-full-indent-test defrecord-2 - "(defrecord TheNameOfTheRecord [a pretty long argument list] + (when-indenting-it "should handle defrecord 2" + "(defrecord TheNameOfTheRecord [a pretty long argument list] SomeType (assoc [_ x] (.assoc pretty x 10)))") -(def-full-indent-test defrecord-allow-multiarity - "(defrecord Banana [] + (when-indenting-it "should handle defrecord with multiarity" + "(defrecord Banana [] Fruit (subtotal ([item] @@ -358,8 +390,8 @@ values of customisable variables." ([item a] (* a (:qty item)))))") -(def-full-indent-test letfn - "(letfn [(f [x] + (when-indenting-it "should handle letfn" + "(letfn [(f [x] (* x 2)) (f [x] (* x 2))] @@ -367,11 +399,12 @@ values of customisable variables." c) (d) e)") -(def-full-indent-test reify - "(reify Object + (when-indenting-it "should handle reify" + "(reify Object (x [_] 1))" - "(reify + + "(reify om/IRender (render [this] (let [indent-test :fail] @@ -381,8 +414,8 @@ values of customisable variables." (let [indent-test :fail] ...)))") -(def-full-indent-test proxy - "(proxy [Writer] [] + (when-indenting-it "proxy" + "(proxy [Writer] [] (close [] (.flush ^Writer this)) (write ([x] @@ -395,40 +428,42 @@ values of customisable variables." (with-out-binding [out messages] (.flush out))))") -(def-full-indent-test reader-conditionals - "#?@ (:clj [] + (when-indenting-it "should handle reader conditionals" + "#?@ (:clj [] :cljs [])") -(def-full-indent-test empty-close-paren - "(let [x] + (when-indenting-it "should handle an empty close paren" + "(let [x] )" - "(ns ok + "(ns ok )" - "(ns ^{:zen :dikar} + "(ns ^{:zen :dikar} ok )") -(def-full-indent-test unfinished-sexps - "(letfn [(tw [x] + (when-indenting-it "should handle unfinished sexps" + "(letfn [(tw [x] dd") -(def-full-indent-test symbols-ending-in-crap - "(msg? ExceptionInfo + (when-indenting-it "should handle symbols ending in crap" + "(msg? ExceptionInfo 10)" - "(thrown-with-msg? ExceptionInfo + + "(thrown-with-msg? ExceptionInfo #\"Storage must be initialized before use\" (f))" - "(msg' 1 + + "(msg' 1 10)") -(def-full-indent-test let-when-while-forms - "(let-alist [x 1]\n ())" - "(while-alist [x 1]\n ())" - "(when-alist [x 1]\n ())" - "(if-alist [x 1]\n ())" - "(indents-like-fn-when-let-while-if-are-not-the-start [x 1]\n ())") + (when-indenting-it "should handle let, when and while forms" + "(let-alist [x 1]\n ())" + "(while-alist [x 1]\n ())" + "(when-alist [x 1]\n ())" + "(if-alist [x 1]\n ())" + "(indents-like-fn-when-let-while-if-are-not-the-start [x 1]\n ())") (defun indent-cond (indent-point state) (goto-char (elt state 1)) @@ -453,101 +488,86 @@ values of customisable variables." (defun indent-cond-0 (_indent-point _state) 0) (put-clojure-indent 'test-cond-0 #'indent-cond-0) -(def-full-indent-test function-spec - "(when me + + (when-indenting-it "should handle function spec" + "(when me (test-cond x 1 2 3))" - "(when me + + "(when me (test-cond-0 x 1 2 3))") -(def-full-indent-test align-arguments - 'align-arguments - "(some-function + (when-indenting-it "should respect indent style 'align-arguments" + 'align-arguments + + "(some-function 10 1 2)" - "(some-function 10 + + "(some-function 10 1 2)") -(def-full-indent-test always-indent - 'always-indent - "(some-function + (when-indenting-it "should respect indent style 'always-indent" + 'always-indent + + "(some-function 10 1 2)" - "(some-function 10 + + "(some-function 10 1 2)") -;;; Alignment -(defmacro def-full-align-test (name &rest forms) - "Verify that all FORMs correspond to a properly indented sexps." - (declare (indent defun)) - `(ert-deftest ,(intern (format "test-align-%s" name)) () - (let ((clojure-align-forms-automatically t) - (clojure-align-reader-conditionals t)) - ,@(mapcar (lambda (form) - `(with-temp-buffer - (clojure-mode) - (insert "\n" ,(replace-regexp-in-string " +" " " form)) - (indent-region (point-min) (point-max)) - (should (equal (buffer-substring-no-properties (point-min) (point-max)) - ,(concat "\n" form))))) - forms)) - (let ((clojure-align-forms-automatically nil)) - ,@(mapcar (lambda (form) - `(with-temp-buffer - (clojure-mode) - (insert "\n" ,(replace-regexp-in-string " +" " " form)) - ;; This is to check that we did NOT align anything. Run - ;; `indent-region' and then check that no extra spaces - ;; where inserted besides the start of the line. - (indent-region (point-min) (point-max)) - (goto-char (point-min)) - (should-not (search-forward-regexp "\\([^\s\n]\\) +" nil 'noerror)))) - forms)))) - -(def-full-align-test basic - "{:this-is-a-form b + (when-aligning-it "should basic forms" + "{:this-is-a-form b c d}" - "{:this-is b + + "{:this-is b c d}" - "{:this b + + "{:this b c d}" - "{:a b + + "{:a b c d}" - "(let [this-is-a-form b + "(let [this-is-a-form b c d])" - "(let [this-is b + + "(let [this-is b c d])" - "(let [this b + + "(let [this b c d])" - "(let [a b + + "(let [a b c d])") -(def-full-align-test blank-line - "(let [this-is-a-form b + (when-aligning-it "should handle a blank line" + "(let [this-is-a-form b c d another form k g])" - "{:this-is-a-form b + + "{:this-is-a-form b c d :another form k g}") -(def-full-align-test basic-reversed - "{c d + (when-aligning-it "should handle basic forms (reversed)" + "{c d :this-is-a-form b}" "{c d :this-is b}" @@ -558,80 +578,87 @@ x "(let [c d this-is-a-form b])" + "(let [c d this-is b])" + "(let [c d this b])" + "(let [c d a b])") -(def-full-align-test incomplete-sexp - "(cond aa b + (when-aligning-it "should handle incomplete sexps" + "(cond aa b casodkas )" - "(cond aa b + + "(cond aa b casodkas)" - "(cond aa b + + "(cond aa b casodkas " - "(cond aa b + + "(cond aa b casodkas" - "(cond aa b + + "(cond aa b casodkas a)" - "(cond casodkas a + + "(cond casodkas a aa b)" - "(cond casodkas + + "(cond casodkas aa b)") -(def-full-align-test multiple-words - "(cond this is just + + (when-aligning-it "should handle multiple words" + "(cond this is just a test of how well multiple words will work)") -(def-full-align-test nested-maps - "{:a {:a :a + (when-aligning-it "should handle nested maps" + "{:a {:a :a :bbbb :b} :bbbb :b}") -(def-full-align-test end-is-a-marker - "{:a {:a :a + (when-aligning-it "should regard end as a marker" + "{:a {:a :a :aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa :a} :b {:a :a :aa :a}}") -(def-full-align-test trailing-commas - "{:a {:a :a, + (when-aligning-it "should handle trailing commas" + "{:a {:a :a, :aa :a}, :b {:a :a, :aa :a}}") -(def-full-align-test reader-conditional - "#?(:clj 2 + (when-aligning-it "should handle standard reader conditionals" + "#?(:clj 2 :cljs 2)") -(def-full-align-test reader-conditional-splicing - "#?@(:clj [2] + (when-aligning-it "should handle splicing reader conditional" + "#?@(:clj [2] :cljs [2])") -(ert-deftest reader-conditional-alignment-disabled-by-default () - (let ((content "#?(:clj 2\n :cljs 2)")) - (with-temp-buffer - (clojure-mode) - (insert content) - (call-interactively #'clojure-align) - (should (string= (buffer-string) content))) - (with-temp-buffer - (clojure-mode) - (setq-local clojure-align-reader-conditionals t) - (insert content) + (it "should not align reader conditionals by default" + (let ((content "#?(:clj 2\n :cljs 2)")) + (with-clojure-buffer content + (call-interactively #'clojure-align) + (expect (buffer-string) :to-equal content)))) + + (it "should align reader conditionals when clojure-align-reader-conditionals is true" + (let ((content "#?(:clj 2\n :cljs 2)")) + (with-clojure-buffer content + (setq-local clojure-align-reader-conditionals t) + (call-interactively #'clojure-align) + (expect (buffer-string) :not :to-equal content)))) + + (it "should remove extra commas" + (with-clojure-buffer "{:a 2, ,:c 4}" (call-interactively #'clojure-align) - (should-not (string= (buffer-string) content))))) - -(ert-deftest clojure-align-remove-extra-commas () - (with-temp-buffer - (clojure-mode) - (insert "{:a 2, ,:c 4}") - (call-interactively #'clojure-align) - (should (string= (buffer-string) "{:a 2, :c 4}")))) + (expect (string= (buffer-string) "{:a 2, :c 4}"))))) (provide 'clojure-mode-indentation-test) diff --git a/clojure-mode-refactor-let-test.el b/clojure-mode-refactor-let-test.el index 8aec3d2..e36654a 100644 --- a/clojure-mode-refactor-let-test.el +++ b/clojure-mode-refactor-let-test.el @@ -25,65 +25,76 @@ ;;; Code: (require 'clojure-mode) -(require 'ert) +(require 'test-helper) +(require 'buttercup) -(def-refactor-test test-introduce-let - "{:status 200 +(describe "clojure--introduce-let-internal" + (when-refactoring-it "should introduce a let form" + "{:status 200 :body (find-body abc)}" - "{:status 200 + + "{:status 200 :body (let [body (find-body abc)] body)}" - (search-backward "(find-body") - (clojure--introduce-let-internal "body")) -(def-refactor-test test-introduce-expanded-let - "(defn handle-request [] + (search-backward "(find-body") + (clojure--introduce-let-internal "body")) + + (when-refactoring-it "should introduce an expanded let form" + "(defn handle-request [] {:status 200 :length (count (find-body abc)) :body (find-body abc)})" - "(defn handle-request [] + + "(defn handle-request [] (let [body (find-body abc)] {:status 200 :length (count body) :body body}))" - (search-backward "(find-body") - (clojure--introduce-let-internal "body" 1)) -(def-refactor-test test-let-replace-bindings-whitespace - "(defn handle-request [] + (search-backward "(find-body") + (clojure--introduce-let-internal "body" 1)) + + (when-refactoring-it "should replace bindings whitespace" + "(defn handle-request [] {:status 200 :length (count (find-body abc)) :body (find-body abc)})" - "(defn handle-request [] + + "(defn handle-request [] (let [body (find-body abc)] {:status 200 :length (count body) :body body}))" - (search-backward "(find-body") - (clojure--introduce-let-internal "body" 1)) + (search-backward "(find-body") + (clojure--introduce-let-internal "body" 1))) -(def-refactor-test test-let-forward-slurp-sexp - "(defn handle-request [] +(describe "clojure-let-forward-slurp-sexp" + (when-refactoring-it "should slurp the next 2 sexps after the let into the let form" + "(defn handle-request [] (let [body (find-body abc)] {:status 200 :length (count body) :body body}) (println (find-body abc)) (println \"foobar\"))" - "(defn handle-request [] + + "(defn handle-request [] (let [body (find-body abc)] {:status 200 :length (count body) :body body} (println body) (println \"foobar\")))" - (search-backward "(count body") - (clojure-let-forward-slurp-sexp 2)) -(def-refactor-test test-let-backward-slurp-sexp + (search-backward "(count body") + (clojure-let-forward-slurp-sexp 2))) + +(describe "clojure-let-backward-slurp-sexp" + (when-refactoring-it "should slurp the previous 2 sexps before the let into the let form" "(defn handle-request [] (println (find-body abc)) (println \"foobar\") @@ -91,137 +102,158 @@ {:status 200 :length (count body) :body body}))" - "(defn handle-request [] + + "(defn handle-request [] (let [body (find-body abc)] (println body) (println \"foobar\") {:status 200 :length (count body) :body body}))" - (search-backward "(count body") - (clojure-let-backward-slurp-sexp 2)) -(def-refactor-test test-move-sexp-to-let - "(defn handle-request + (search-backward "(count body") + (clojure-let-backward-slurp-sexp 2))) + +(describe "clojure--move-to-let-internal" + (when-refactoring-it "should move sexp to let" + "(defn handle-request (let [body (find-body abc)] {:status (or status 500) :body body}))" - "(defn handle-request + + "(defn handle-request (let [body (find-body abc) status (or status 500)] {:status status :body body}))" - (search-backward "(or ") - (clojure--move-to-let-internal "status")) -(def-refactor-test test-move-constant-to-when-let - "(defn handle-request + (search-backward "(or ") + (clojure--move-to-let-internal "status")) + + (when-refactoring-it "should move constant to when let" + "(defn handle-request (when-let [body (find-body abc)] {:status 42 :body body}))" - "(defn handle-request + + "(defn handle-request (when-let [body (find-body abc) status 42] {:status status :body body}))" - (search-backward "42") - (clojure--move-to-let-internal "status")) -(def-refactor-test test-move-to-empty-let - "(defn handle-request + (search-backward "42") + (clojure--move-to-let-internal "status")) + + (when-refactoring-it "should move sexp to empty let" + "(defn handle-request (if-let [] {:status (or status 500) :body body}))" - "(defn handle-request + + "(defn handle-request (if-let [status (or status 500)] {:status status :body body}))" - (search-backward "(or ") - (clojure--move-to-let-internal "status")) -(def-refactor-test test-introduce-let-at-move-to-let-if-missing - "(defn handle-request + (search-backward "(or ") + (clojure--move-to-let-internal "status")) + + (when-refactoring-it "should introduce let if missing" + "(defn handle-request {:status (or status 500) :body body})" - "(defn handle-request + + "(defn handle-request {:status (let [status (or status 500)] status) :body body})" - (search-backward "(or ") - (clojure--move-to-let-internal "status")) -(def-refactor-test test-move-to-let-multiple-occurrences - "(defn handle-request + (search-backward "(or ") + (clojure--move-to-let-internal "status")) + + (when-refactoring-it "should move multiple occurrences of a sexp" + "(defn handle-request (let [] (println \"body: \" body \", params: \" \", status: \" (or status 500)) {:status (or status 500) :body body}))" - "(defn handle-request + + "(defn handle-request (let [status (or status 500)] (println \"body: \" body \", params: \" \", status: \" status) {:status status :body body}))" - (search-backward "(or ") - (clojure--move-to-let-internal "status")) -(def-refactor-test test-move-to-let-name-longer-than-expression - "(defn handle-request + (search-backward "(or ") + (clojure--move-to-let-internal "status")) + + (when-refactoring-it "should handle a name that is longer than the expression" + "(defn handle-request (let [] (println \"body: \" body \", params: \" \", status: \" 5) {:body body :status 5}))" - "(defn handle-request + + "(defn handle-request (let [status 5] (println \"body: \" body \", params: \" \", status: \" status) {:body body :status status}))" - (search-backward "5") - (search-backward "5") - (clojure--move-to-let-internal "status")) -;; clojure-emacs/clj-refactor.el#41 -(def-refactor-test test-move-to-let-nested-scope - "(defn foo [] + (search-backward "5") + (search-backward "5") + (clojure--move-to-let-internal "status")) + + ;; clojure-emacs/clj-refactor.el#41 + (when-refactoring-it "should not move to nested let" + "(defn foo [] (let [x (range 10)] (doseq [x (range 10)] (let [x2 (* x x)])) (+ 1 1)))" - "(defn foo [] + + "(defn foo [] (let [x (range 10) something (+ 1 1)] (doseq [x x] (let [x2 (* x x)])) something))" - (search-backward "(+ 1 1") - (clojure--move-to-let-internal "something")) -;; clojure-emacs/clj-refactor.el#30 -(def-refactor-test test-move-to-let-already-inside-let-binding-1 - "(deftest retrieve-order-body-test + (search-backward "(+ 1 1") + (clojure--move-to-let-internal "something")) + + ;; clojure-emacs/clj-refactor.el#30 + (when-refactoring-it "should move before current form when already inside let binding-1" + "(deftest retrieve-order-body-test (let [item (get-in (retrieve-order-body order-item-response-str))]))" - "(deftest retrieve-order-body-test + + "(deftest retrieve-order-body-test (let [something (retrieve-order-body order-item-response-str) item (get-in something)]))" - (search-backward "(retrieve") - (clojure--move-to-let-internal "something")) -;; clojure-emacs/clj-refactor.el#30 -(def-refactor-test test-move-to-let-already-inside-let-binding-2 - "(let [parent (.getParent (io/file root adrf)) + (search-backward "(retrieve") + (clojure--move-to-let-internal "something")) + + ;; clojure-emacs/clj-refactor.el#30 + (when-refactoring-it "should move before current form when already inside let binding-2" + "(let [parent (.getParent (io/file root adrf)) builder (string-builder) normalize-path (comp (partial path/relative-to root) path/->normalized foobar)] (do-something-spectacular parent builder))" - "(let [parent (.getParent (io/file root adrf)) + + "(let [parent (.getParent (io/file root adrf)) builder (string-builder) something (partial path/relative-to root) normalize-path (comp something path/->normalized foobar)] (do-something-spectacular parent builder))" - (search-backward "(partial") - (clojure--move-to-let-internal "something")) + + (search-backward "(partial") + (clojure--move-to-let-internal "something"))) (provide 'clojure-mode-refactor-let-test) diff --git a/clojure-mode-refactor-rename-ns-alias-test.el b/clojure-mode-refactor-rename-ns-alias-test.el index 8e7f8d8..a2875b4 100644 --- a/clojure-mode-refactor-rename-ns-alias-test.el +++ b/clojure-mode-refactor-rename-ns-alias-test.el @@ -18,37 +18,44 @@ ;;; Code: (require 'clojure-mode) +(require 'test-helper) (require 'ert) -(def-refactor-test test-rename-ns-alias - "(ns cljr.core +(describe "clojure--rename-ns-alias-internal" + + (when-refactoring-it "should rename an alias" + "(ns cljr.core (:require [my.lib :as lib])) (def m #::lib{:kw 1, :n/kw 2, :_/bare 3, 0 4}) (+ (lib/a 1) (b 2))" - "(ns cljr.core + + "(ns cljr.core (:require [my.lib :as foo])) (def m #::foo{:kw 1, :n/kw 2, :_/bare 3, 0 4}) (+ (foo/a 1) (b 2))" - (clojure--rename-ns-alias-internal "lib" "foo")) -(def-refactor-test test-rename-ns-alias-with-missing-as - "(ns cljr.core + (clojure--rename-ns-alias-internal "lib" "foo")) + + (when-refactoring-it "should handle ns declarations with missing as" + "(ns cljr.core (:require [my.lib :as lib])) (def m #::lib{:kw 1, :n/kw 2, :_/bare 3, 0 4}) (+ (lib/a 1) (b 2))" - "(ns cljr.core + + "(ns cljr.core (:require [my.lib :as lib])) (def m #::lib{:kw 1, :n/kw 2, :_/bare 3, 0 4}) (+ (lib/a 1) (b 2))" - (clojure--rename-ns-alias-internal "foo" "bar")) + + (clojure--rename-ns-alias-internal "foo" "bar"))) (provide 'clojure-mode-refactor-rename-ns-alias-test) diff --git a/clojure-mode-refactor-threading-test.el b/clojure-mode-refactor-threading-test.el index 95e675b..5e4b482 100644 --- a/clojure-mode-refactor-threading-test.el +++ b/clojure-mode-refactor-threading-test.el @@ -26,329 +26,411 @@ ;;; Code: (require 'clojure-mode) -(require 'ert) +(require 'test-helper) +(require 'buttercup) -;; thread first +(describe "clojure-thread" -(def-refactor-test test-thread-first-one-step + (when-refactoring-it "should work with -> when performed once" "(-> (dissoc (assoc {} :key \"value\") :lock))" + "(-> (assoc {} :key \"value\") (dissoc :lock))" - (clojure-thread)) -(def-refactor-test test-thread-first-two-steps + (clojure-thread)) + + (when-refactoring-it "should work with -> when performed twice" "(-> (dissoc (assoc {} :key \"value\") :lock))" + "(-> {} (assoc :key \"value\") (dissoc :lock))" - (clojure-thread) - (clojure-thread)) -(def-refactor-test test-thread-first-dont-thread-maps + (clojure-thread) + (clojure-thread)) + + (when-refactoring-it "should not thread maps" "(-> (dissoc (assoc {} :key \"value\") :lock))" + "(-> {} (assoc :key \"value\") (dissoc :lock))" - (clojure-thread) - (clojure-thread) - (clojure-thread)) -(def-refactor-test test-thread-first-dont-thread-last-one + (clojure-thread) + (clojure-thread) + (clojure-thread)) + + (when-refactoring-it "should not thread last sexp" "(-> (dissoc (assoc (get-a-map) :key \"value\") :lock))" + "(-> (get-a-map) (assoc :key \"value\") (dissoc :lock))" - (clojure-thread) - (clojure-thread) - (clojure-thread)) -(def-refactor-test test-thread-first-easy-on-whitespace + (clojure-thread) + (clojure-thread) + (clojure-thread)) + + (when-refactoring-it "should thread-first-easy-on-whitespace" "(-> (dissoc (assoc {} :key \"value\") :lock))" + "(-> (assoc {} :key \"value\") (dissoc :lock))" - (clojure-thread)) -(def-refactor-test test-thread-first-remove-superfluous-parens + (clojure-thread)) + + (when-refactoring-it "should remove superfluous parens" "(-> (square (sum [1 2 3 4 5])))" + "(-> [1 2 3 4 5] sum square)" - (clojure-thread) - (clojure-thread)) -(def-refactor-test test-thread-first-cursor-before-threading + (clojure-thread) + (clojure-thread)) + + (when-refactoring-it "should work with cursor before ->" "(-> (not (s-acc/mobile? session)))" + "(-> (s-acc/mobile? session) not)" - (beginning-of-buffer) - (clojure-thread)) -;; unwind thread first -(def-refactor-test test-thread-unwind-first-one-step - "(-> {} - (assoc :key \"value\") - (dissoc :lock))" - "(-> (assoc {} :key \"value\") - (dissoc :lock))" - (clojure-unwind)) + (beginning-of-buffer) + (clojure-thread)) -(def-refactor-test test-thread-unwind-first-two-steps - "(-> {} - (assoc :key \"value\") - (dissoc :lock))" - "(-> (dissoc (assoc {} :key \"value\") :lock))" - (clojure-unwind) - (clojure-unwind)) - -(def-refactor-test test-thread-first-jump-out-of-threading - "(-> {} - (assoc :key \"value\") - (dissoc :lock))" - "(dissoc (assoc {} :key \"value\") :lock)" - (clojure-unwind) - (clojure-unwind) - (clojure-unwind)) - -;; thread last -(def-refactor-test test-thread-last-one-step + (when-refactoring-it "should work with one step with ->>" "(->> (map square (filter even? [1 2 3 4 5])))" + "(->> (filter even? [1 2 3 4 5]) (map square))" - (clojure-thread)) -(def-refactor-test test-thread-last-two-steps + (clojure-thread)) + + (when-refactoring-it "should work with two steps with ->>" "(->> (map square (filter even? [1 2 3 4 5])))" + "(->> [1 2 3 4 5] (filter even?) (map square))" - (clojure-thread) - (clojure-thread)) -(def-refactor-test test-thread-last-dont-thread-vectors + (clojure-thread) + (clojure-thread)) + + (when-refactoring-it "should not thread vectors with ->>" "(->> (map square (filter even? [1 2 3 4 5])))" + "(->> [1 2 3 4 5] (filter even?) (map square))" - (clojure-thread) - (clojure-thread) - (clojure-thread)) -(def-refactor-test test-thread-last-dont-thread-last-one + (clojure-thread) + (clojure-thread) + (clojure-thread)) + + (when-refactoring-it "should not thread last sexp with ->>" "(->> (map square (filter even? (get-a-list))))" + "(->> (get-a-list) (filter even?) (map square))" - (clojure-thread) - (clojure-thread) - (clojure-thread)) -;; unwind thread last -(def-refactor-test test-thread-last-one-step + (clojure-thread) + (clojure-thread) + (clojure-thread)) + + (when-refactoring-it "should work with some->" + "(some-> (+ (val (find {:a 1} :b)) 5))" + + "(some-> {:a 1} + (find :b) + val + (+ 5))" + + (clojure-thread) + (clojure-thread) + (clojure-thread)) + + (when-refactoring-it "should work with some->>" + "(some->> (+ 5 (val (find {:a 1} :b))))" + + "(some->> :b + (find {:a 1}) + val + (+ 5))" + + (clojure-thread) + (clojure-thread) + (clojure-thread))) + +(describe "clojure-unwind" + + (when-refactoring-it "should unwind -> one step" + "(-> {} + (assoc :key \"value\") + (dissoc :lock))" + + "(-> (assoc {} :key \"value\") + (dissoc :lock))" + + (clojure-unwind)) + + (when-refactoring-it "should unwind -> two steps" + "(-> {} + (assoc :key \"value\") + (dissoc :lock))" + + "(-> (dissoc (assoc {} :key \"value\") :lock))" + + (clojure-unwind) + (clojure-unwind)) + + (when-refactoring-it "should unwind -> completely" + "(-> {} + (assoc :key \"value\") + (dissoc :lock))" + + "(dissoc (assoc {} :key \"value\") :lock)" + + (clojure-unwind) + (clojure-unwind) + (clojure-unwind)) + + (when-refactoring-it "should unwind ->> one step" "(->> [1 2 3 4 5] (filter even?) (map square))" + "(->> (filter even? [1 2 3 4 5]) (map square))" - (clojure-unwind)) -(def-refactor-test test-thread-last-two-steps + (clojure-unwind)) + + (when-refactoring-it "should unwind ->> two steps" "(->> [1 2 3 4 5] (filter even?) (map square))" + "(->> (map square (filter even? [1 2 3 4 5])))" - (clojure-unwind) - (clojure-unwind)) -(def-refactor-test test-thread-last-jump-out-of-threading + (clojure-unwind) + (clojure-unwind)) + + (when-refactoring-it "should unwind ->> completely" "(->> [1 2 3 4 5] (filter even?) (map square))" + "(map square (filter even? [1 2 3 4 5]))" - (clojure-unwind) - (clojure-unwind) - (clojure-unwind)) -(def-refactor-test test-thread-function-name + (clojure-unwind) + (clojure-unwind) + (clojure-unwind)) + + (when-refactoring-it "should unwind with function name" "(->> [1 2 3 4 5] sum square)" + "(->> (sum [1 2 3 4 5]) square)" - (clojure-unwind)) -(def-refactor-test test-thread-function-name-twice + (clojure-unwind)) + + (when-refactoring-it "should unwind with function name twice" "(-> [1 2 3 4 5] sum square)" + "(-> (square (sum [1 2 3 4 5])))" - (clojure-unwind) - (clojure-unwind)) -(def-refactor-test test-thread-issue-6-1 + (clojure-unwind) + (clojure-unwind)) + + (when-refactoring-it "should thread-issue-6-1" "(defn plus [a b] (-> a (+ b)))" + "(defn plus [a b] (-> (+ a b)))" - (clojure-unwind)) -(def-refactor-test test-thread-issue-6-2 + (clojure-unwind)) + + (when-refactoring-it "should thread-issue-6-2" "(defn plus [a b] (->> a (+ b)))" + "(defn plus [a b] (->> (+ b a)))" - (clojure-unwind)) -(def-refactor-test test-thread-first-some - "(some-> (+ (val (find {:a 1} :b)) 5))" - "(some-> {:a 1} - (find :b) - val - (+ 5))" - (clojure-thread) - (clojure-thread) - (clojure-thread)) + (clojure-unwind)) -(def-refactor-test test-thread-last-some - "(some->> (+ 5 (val (find {:a 1} :b))))" - "(some->> :b - (find {:a 1}) - val - (+ 5))" - (clojure-thread) - (clojure-thread) - (clojure-thread)) - -(def-refactor-test test-thread-last-first-some + (when-refactoring-it "should unwind some->" "(some-> {:a 1} (find :b) val (+ 5))" + "(some-> (+ (val (find {:a 1} :b)) 5))" - (clojure-unwind) - (clojure-unwind) - (clojure-unwind)) -(def-refactor-test test-thread-thread-last-some + (clojure-unwind) + (clojure-unwind) + (clojure-unwind)) + + (when-refactoring-it "should unwind some->>" "(some->> :b (find {:a 1}) val (+ 5))" + "(some->> (+ 5 (val (find {:a 1} :b))))" - (clojure-unwind) - (clojure-unwind) - (clojure-unwind)) -(def-refactor-test test-thread-first-all + (clojure-unwind) + (clojure-unwind) + (clojure-unwind))) + +(describe "clojure-thread-first-all" + + (when-refactoring-it "should thread first all sexps" "(->map (assoc {} :key \"value\") :lock)" + "(-> {} (assoc :key \"value\") (->map :lock))" - (beginning-of-buffer) - (clojure-thread-first-all nil)) -(def-refactor-test test-thread-first-all-but-last + (beginning-of-buffer) + (clojure-thread-first-all nil)) + + (when-refactoring-it "should thread a form except the last expression" "(->map (assoc {} :key \"value\") :lock)" + "(-> (assoc {} :key \"value\") (->map :lock))" - (beginning-of-buffer) - (clojure-thread-first-all t)) -(def-refactor-test test-thread-last-all + (beginning-of-buffer) + (clojure-thread-first-all t))) + +(describe "clojure-thread-last-all" + + (when-refactoring-it "should fully thread a form" "(map square (filter even? (make-things)))" + "(->> (make-things) (filter even?) (map square))" - (beginning-of-buffer) - (clojure-thread-last-all nil)) -(def-refactor-test test-thread-last-all-but-last + (beginning-of-buffer) + (clojure-thread-last-all nil)) + + (when-refactoring-it "should thread a form except the last expression" "(map square (filter even? (make-things)))" + "(->> (filter even? (make-things)) (map square))" - (beginning-of-buffer) - (clojure-thread-last-all t)) -(def-refactor-test test-thread-all-thread-first - "(-> {} - (assoc :key \"value\") - (dissoc :lock))" - "(dissoc (assoc {} :key \"value\") :lock)" - (beginning-of-buffer) - (clojure-unwind-all)) - -(def-refactor-test test-thread-all-thread-last - "(->> (make-things) - (filter even?) - (map square))" - "(map square (filter even? (make-things)))" - (beginning-of-buffer) - (clojure-unwind-all)) + (beginning-of-buffer) + (clojure-thread-last-all t)) -(def-refactor-test test-thread-last-dangling-parens + (when-refactoring-it "should handle dangling parens 1" "(map inc - (range))" + (range))" + "(->> (range) (map inc))" - (beginning-of-buffer) - (clojure-thread-last-all nil)) -(def-refactor-test test-thread-last-dangling-parens-2 + (beginning-of-buffer) + (clojure-thread-last-all nil)) + + (when-refactoring-it "should handle dangling parens 2" "(deftask dev [] (comp (serve) - (cljs)))" + (cljs)))" + "(->> (cljs) (comp (serve)) (deftask dev []))" - (beginning-of-buffer) - (clojure-thread-last-all nil)) -;; fix for clojure-emacs/clj-refactor.el#259 -(def-refactor-test test-thread-last-leaves-multiline-sexp-alone + (beginning-of-buffer) + (clojure-thread-last-all nil))) + +(describe "clojure-unwind-all" + + (when-refactoring-it "should unwind all in ->" + "(-> {} + (assoc :key \"value\") + (dissoc :lock))" + + "(dissoc (assoc {} :key \"value\") :lock)" + + (beginning-of-buffer) + (clojure-unwind-all)) + + (when-refactoring-it "should unwind all in ->>" + "(->> (make-things) + (filter even?) + (map square))" + + "(map square (filter even? (make-things)))" + + (beginning-of-buffer) + (clojure-unwind-all)) + + ;; fix for clojure-emacs/clj-refactor.el#259 + (when-refactoring-it "should leave multiline sexp alone" "(->> [a b] (some (fn [x] (when x 10))))" + "(some (fn [x] (when x 10)) [a b])" - (clojure-unwind-all)) -(def-refactor-test test-thread-last-maybe-unjoin-lines + (clojure-unwind-all)) + + (when-refactoring-it "should thread-last-maybe-unjoin-lines" "(deftask dev [] (comp (serve) (cljs (lala) 10)))" + "(deftask dev [] (comp (serve) (cljs (lala) 10)))" - (goto-char (point-min)) - (clojure-thread-last-all nil) - (clojure-unwind-all)) -(def-refactor-test test-thread-empty-first-line + (goto-char (point-min)) + (clojure-thread-last-all nil) + (clojure-unwind-all))) + +(describe "clojure-thread-first-all" + + (when-refactoring-it "should thread with an empty first line" "(map - inc - [1 2])" + inc + [1 2])" + "(-> inc (map [1 2]))" - (goto-char (point-min)) - (clojure-thread-first-all nil)) -(def-refactor-test test-thread-first-maybe-unjoin-lines + (goto-char (point-min)) + (clojure-thread-first-all nil)) + + (when-refactoring-it "should thread-first-maybe-unjoin-lines" "(map inc [1 2])" + "(map inc [1 2])" - (goto-char (point-min)) - (clojure-thread-first-all nil) - (clojure-unwind-all)) + + (goto-char (point-min)) + (clojure-thread-first-all nil) + (clojure-unwind-all))) (provide 'clojure-mode-refactor-threading-test) diff --git a/clojure-mode-sexp-test.el b/clojure-mode-sexp-test.el index f8a97e3..5574588 100644 --- a/clojure-mode-sexp-test.el +++ b/clojure-mode-sexp-test.el @@ -20,44 +20,47 @@ ;;; Code: (require 'clojure-mode) -(require 'ert) +(require 'test-helper) +(require 'buttercup) -(defmacro clojure-buffer-with-text (text &rest body) - "Run body in a temporary clojure buffer with TEXT. -TEXT is a string with a | indicating where point is. The | will be erased +(defmacro with-clojure-buffer-point (text &rest body) + "Run BODY in a temporary clojure buffer with TEXT. + +TEXT is a string with a | indicating where point is. The | will be erased and point left there." (declare (indent 2)) `(progn - (with-temp-buffer - (erase-buffer) - (clojure-mode) - (insert ,text) + (with-clojure-buffer ,text (goto-char (point-min)) (re-search-forward "|") (delete-char -1) ,@body))) -(ert-deftest test-clojure-top-level-form-p () - (clojure-buffer-with-text - "(comment - (wrong) - (rig|ht) - (wrong))" - ;; make this use the native beginning of defun since this is used to - ;; determine whether to use the comment aware version or not. - (should (let ((beginning-of-defun-function nil)) - (clojure-top-level-form-p "comment"))))) - -(ert-deftest test-clojure-beginning-of-defun-function () - (clojure-buffer-with-text +(describe "clojure-top-level-form-p" + (it "should return true when passed the correct form" + (with-clojure-buffer-point + "(comment + (wrong) + (rig|ht) + (wrong))" + ;; make this use the native beginning of defun since this is used to + ;; determine whether to use the comment aware version or not. + (expect (let ((beginning-of-defun-function nil)) + (clojure-top-level-form-p "comment")))))) + +(describe "clojure-beginning-of-defun-function" + (it "should go to top level form" + (with-clojure-buffer-point "(comment (wrong) (wrong) (rig|ht) (wrong))" (beginning-of-defun) - (should (looking-at-p "(comment"))) - (clojure-buffer-with-text + (expect (looking-at-p "(comment")))) + + (it "should eval top level forms inside comment forms when clojure-toplevel-inside-comment-form set to true" + (with-clojure-buffer-point "(comment (wrong) (wrong) @@ -65,119 +68,112 @@ and point left there." (wrong))" (let ((clojure-toplevel-inside-comment-form t)) (beginning-of-defun)) - (should (looking-at-p "[[:space:]]*(right)"))) - (clojure-buffer-with-text - " + (expect (looking-at-p "[[:space:]]*(right)")))) + + (it "should go to beginning of previous top level form" + (with-clojure-buffer-point + " (formA) | (formB)" - (let ((clojure-toplevel-inside-comment-form t)) - (beginning-of-defun) - (should (looking-at-p "(formA)"))))) + (let ((clojure-toplevel-inside-comment-form t)) + (beginning-of-defun) + (expect (looking-at-p "(formA)"))))) -(ert-deftest test-clojure-end-of-defun-function () - (clojure-buffer-with-text + (it "should move forward to next top level form" + (with-clojure-buffer-point " (first form) | (second form) (third form)" - + (end-of-defun) - (backward-char) - (should (looking-back "(second form)")))) - - -(ert-deftest test-sexp-with-commas () - (with-temp-buffer - (insert "[], {}, :a, 2") - (clojure-mode) - (goto-char (point-min)) - (clojure-forward-logical-sexp 1) - (should (looking-at-p " {}, :a, 2")) - (clojure-forward-logical-sexp 1) - (should (looking-at-p " :a, 2")))) - -(ert-deftest test-sexp () - (with-temp-buffer - (insert "^String #macro ^dynamic reverse") - (clojure-mode) - (clojure-backward-logical-sexp 1) - (should (looking-at-p "\\^String \\#macro \\^dynamic reverse")) - (clojure-forward-logical-sexp 1) - (should (looking-back "\\^String \\#macro \\^dynamic reverse")) - (insert " ^String biverse inverse") - (clojure-backward-logical-sexp 1) - (should (looking-at-p "inverse")) - (clojure-backward-logical-sexp 2) - (should (looking-at-p "\\^String \\#macro \\^dynamic reverse")) - (clojure-forward-logical-sexp 2) - (should (looking-back "\\^String biverse")) - (clojure-backward-logical-sexp 1) - (should (looking-at-p "\\^String biverse")))) - -(ert-deftest test-buffer-corners () - (with-temp-buffer - (insert "^String reverse") - (clojure-mode) - ;; Return nil and don't error - (should-not (clojure-backward-logical-sexp 100)) - (should (looking-at-p "\\^String reverse")) - (should-not (clojure-forward-logical-sexp 100)) - (should (looking-at-p "$"))) - (with-temp-buffer - (clojure-mode) - (insert "(+ 10") - (should-error (clojure-backward-logical-sexp 100)) - (goto-char (point-min)) - (should-error (clojure-forward-logical-sexp 100)) - ;; Just don't hang. - (goto-char (point-max)) - (should-not (clojure-forward-logical-sexp 1)) - (erase-buffer) - (insert "(+ 10") - (newline) - (erase-buffer) - (insert "(+ 10") - (newline-and-indent))) - -(ert-deftest clojure-find-ns-test () - ;; we should not cache the results of `clojure-find-ns' here - (let ((clojure-cache-ns nil)) - (with-temp-buffer - (insert "(ns ^{:doc \"Some docs\"}\nfoo-bar)") - (newline) + (backward-char) + (expect (looking-back "(second form)"))))) + +(describe "clojure-forward-logical-sexp" + (it "should work with commas" + (with-clojure-buffer "[], {}, :a, 2" + (goto-char (point-min)) + (clojure-forward-logical-sexp 1) + (expect (looking-at-p " {}, :a, 2")) + (clojure-forward-logical-sexp 1) + (expect (looking-at-p " :a, 2"))))) + +(describe "clojure-backward-logical-sexp" + (it "should work when used in conjunction with clojure-forward-logical-sexp" + (with-clojure-buffer "^String #macro ^dynamic reverse" + (clojure-backward-logical-sexp 1) + (expect (looking-at-p "\\^String \\#macro \\^dynamic reverse")) + (clojure-forward-logical-sexp 1) + (expect (looking-back "\\^String \\#macro \\^dynamic reverse")) + (insert " ^String biverse inverse") + (clojure-backward-logical-sexp 1) + (expect (looking-at-p "inverse")) + (clojure-backward-logical-sexp 2) + (expect (looking-at-p "\\^String \\#macro \\^dynamic reverse")) + (clojure-forward-logical-sexp 2) + (expect (looking-back "\\^String biverse")) + (clojure-backward-logical-sexp 1) + (expect (looking-at-p "\\^String biverse"))))) + +(describe "clojure-backward-logical-sexp" + (it "should work with buffer corners" + (with-clojure-buffer "^String reverse" + ;; Return nil and don't error + (expect (clojure-backward-logical-sexp 100) :to-be nil) + (expect (looking-at-p "\\^String reverse")) + (expect (clojure-forward-logical-sexp 100) :to-be nil) + (expect (looking-at-p "$"))) + (with-clojure-buffer "(+ 10" + (expect (clojure-backward-logical-sexp 100) :to-throw 'error) + (goto-char (point-min)) + (expect (clojure-forward-logical-sexp 100) :to-throw 'error) + ;; Just don't hang. + (goto-char (point-max)) + (expect (clojure-forward-logical-sexp 1) :to-be nil) + (erase-buffer) + (insert "(+ 10") (newline) - (insert "(in-ns 'baz-quux)") - (clojure-mode) - - ;; From inside docstring of first ns - (goto-char 18) - (should (equal "foo-bar" (clojure-find-ns))) - - ;; From inside first ns's name, on its own line - (goto-char 29) - (should (equal "foo-bar" (clojure-find-ns))) - - ;; From inside second ns's name - (goto-char 42) - (should (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" "\"\n(in-ns 'baz-quux)\"" "foo-bar") - ("(ns foo-bar)\n" ";(in-ns 'baz-quux)" "foo-bar")))) - (pcase-dolist (`(,form1 ,form2 ,expected) data) - (with-temp-buffer - (insert form1) - (save-excursion (insert form2)) - (clojure-mode) - ;; Between the two namespaces - (should (equal expected (clojure-find-ns))) - ;; After both namespaces - (goto-char (point-max)) - (should (equal expected (clojure-find-ns)))))))) + (erase-buffer) + (insert "(+ 10") + (newline-and-indent)))) + +(describe "clojure-find-ns" + (it "should return the namespace from various locations in the buffer" + ;; we should not cache the results of `clojure-find-ns' here + (let ((clojure-cache-ns nil)) + (with-clojure-buffer "(ns ^{:doc \"Some docs\"}\nfoo-bar)" + (newline) + (newline) + (insert "(in-ns 'baz-quux)") + + ;; From inside docstring of first ns + (goto-char 18) + (expect (clojure-find-ns) :to-equal "foo-bar") + + ;; From inside first ns's name, on its own line + (goto-char 29) + (expect (clojure-find-ns) :to-equal "foo-bar") + + ;; From inside second ns's name + (goto-char 42) + (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" "\"\n(in-ns 'baz-quux)\"" "foo-bar") + ("(ns foo-bar)\n" ";(in-ns 'baz-quux)" "foo-bar")))) + (pcase-dolist (`(,form1 ,form2 ,expected) data) + (with-clojure-buffer form1 + (save-excursion (insert form2)) + ;; Between the two namespaces + (expect (clojure-find-ns) :to-equal expected) + ;; After both namespaces + (goto-char (point-max)) + (expect (clojure-find-ns) :to-equal expected))))))) (provide 'clojure-mode-sexp-test) diff --git a/clojure-mode-syntax-test.el b/clojure-mode-syntax-test.el index 829dfba..0370f1a 100644 --- a/clojure-mode-syntax-test.el +++ b/clojure-mode-syntax-test.el @@ -24,128 +24,125 @@ ;;; Code: (require 'clojure-mode) -(require 'ert) +(require 'test-helper) +(require 'buttercup) (defun non-func (form-a form-b) - (with-temp-buffer - (clojure-mode) - (insert form-a) + (with-clojure-buffer form-a (save-excursion (insert form-b)) (clojure--not-function-form-p))) -(ert-deftest non-function-form () - (dolist (form '(("#?@ " "(c d)") - ("#?@" "(c d)") - ("#? " "(c d)") - ("#?" "(c d)") - ("" "[asda]") - ("" "{a b}") - ("#" "{a b}") - ("" "(~)"))) - (should (apply #'non-func form))) - (dolist (form '("(c d)" - "(.c d)" - "(:c d)" - "(c/a d)" - "(.c/a d)" - "(:c/a d)" - "(c/a)" - "(:c/a)" - "(.c/a)")) - (should-not (non-func "" form)) - (should-not (non-func "^hint" form)) - (should-not (non-func "#macro" form)) - (should-not (non-func "^hint " form)) - (should-not (non-func "#macro " form)))) - -(ert-deftest clojure-syntax-prefixed-symbols () - (dolist (form '(("#?@aaa" . "aaa") - ("#?aaa" . "?aaa") - ("#aaa" . "aaa") - ("'aaa" . "aaa"))) - (with-temp-buffer - (clojure-mode) - (insert (car form)) - (equal (symbol-name (symbol-at-point)) (cdr form))))) - - -(ert-deftest clojure-syntax-skip-prefixes () - (dolist (form '("#?@aaa" "#?aaa" "#aaa" "'aaa")) - (with-temp-buffer - (clojure-mode) - (insert form) - (backward-word) - (backward-prefix-chars) - (should (bobp))))) - - -(ert-deftest clojure-allowed-collection-tags () - (dolist (tag '("#::ns" "#:ns" "#ns" "#:f.q/ns" "#f.q/ns" "#::")) - (with-temp-buffer - (clojure-mode) - (insert tag) - (should-not (clojure-no-space-after-tag nil ?{)))) - (dolist (tag '("#$:" "#/f" "#:/f" "#::f.q/ns" "::ns" "::" "#f:ns")) - (with-temp-buffer - (clojure-mode) - (insert tag) - (should (clojure-no-space-after-tag nil ?{))))) - - -(def-refactor-test test-paragraph-fill-within-comments - " +(describe "clojure--not-function-form-p" + (it "should handle forms that are not funcions" + (dolist (form '(("#?@ " "(c d)") + ("#?@" "(c d)") + ("#? " "(c d)") + ("#?" "(c d)") + ("" "[asda]") + ("" "{a b}") + ("#" "{a b}") + ("" "(~)"))) + (expect (apply #'non-func form)))) + + (it "should handle forms that are funcions" + (dolist (form '("(c d)" + "(.c d)" + "(:c d)" + "(c/a d)" + "(.c/a d)" + "(:c/a d)" + "(c/a)" + "(:c/a)" + "(.c/a)")) + (expect (non-func "" form) :to-be nil) + (expect (non-func "^hint" form) :to-be nil) + (expect (non-func "#macro" form) :to-be nil) + (expect (non-func "^hint " form) :to-be nil) + (expect (non-func "#macro " form) :to-be nil)))) + +(describe "clojure syntax" + (it "handles prefixed symbols" + (dolist (form '(("#?@aaa" . "aaa") + ("#?aaa" . "?aaa") + ("#aaa" . "aaa") + ("'aaa" . "aaa"))) + (with-clojure-buffer (car form) + (equal (symbol-name (symbol-at-point)) (cdr form))))) + + (it "skips prefixes" + (dolist (form '("#?@aaa" "#?aaa" "#aaa" "'aaa")) + (with-clojure-buffer form + (backward-word) + (backward-prefix-chars) + (expect (bobp)))))) + +(describe "clojure-no-space-after-tag" + (it "should allow allow collection tags" + (dolist (tag '("#::ns" "#:ns" "#ns" "#:f.q/ns" "#f.q/ns" "#::")) + (with-clojure-buffer tag + (expect (clojure-no-space-after-tag nil ?{) :to-be nil))) + (dolist (tag '("#$:" "#/f" "#:/f" "#::f.q/ns" "::ns" "::" "#f:ns")) + (with-clojure-buffer tag + (expect (clojure-no-space-after-tag nil ?{)))))) + +(describe "fill-paragraph" + + (it "should work within comments" + (with-clojure-buffer " ;; Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ;; ut labore et dolore magna aliqua." - " + (goto-char (point-min)) + (let ((fill-column 80)) + (fill-paragraph)) + (expect (buffer-string) :to-equal " ;; Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod -;; tempor incididunt ut labore et dolore magna aliqua." - (goto-char (point-min)) - (let ((fill-column 80)) - (fill-paragraph))) +;; tempor incididunt ut labore et dolore magna aliqua."))) -(def-refactor-test test-paragraph-fill-within-inner-comments - " + (it "should work within inner comments" + (with-clojure-buffer " (let [a 1] ;; Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ;; ut labore et dolore ;; magna aliqua. )" - " + (goto-char (point-min)) + (forward-line 2) + (let ((fill-column 80)) + (fill-paragraph)) + (expect (buffer-string) :to-equal " (let [a 1] ;; Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod ;; tempor incididunt ut labore et dolore magna aliqua. - )" - (goto-char (point-min)) - (forward-line 2) - (let ((fill-column 80)) - (fill-paragraph))) + )"))) (when (fboundp 'font-lock-ensure) - (def-refactor-test test-paragraph-fill-not-altering-surrounding-code - "(def my-example-variable + (it "should not alter surrounding code" + (with-clojure-buffer "(def my-example-variable \"It has a very long docstring. So long, in fact, that it wraps onto multiple lines! This is to demonstrate what happens when the docstring wraps over three lines.\" nil)" - "(def my-example-variable + (font-lock-ensure) + (goto-char 40) + (let ((clojure-docstring-fill-column 80) + (fill-column 80)) + (fill-paragraph)) + (expect (buffer-string) :to-equal "(def my-example-variable \"It has a very long docstring. So long, in fact, that it wraps onto multiple lines! This is to demonstrate what happens when the docstring wraps over three lines.\" - nil)" - (font-lock-ensure) - (goto-char 40) - (let ((clojure-docstring-fill-column 80) - (fill-column 80)) - (fill-paragraph))) - - (ert-deftest test-clojure-in-docstring-p () - (with-temp-buffer - (insert "(def my-example-variable + nil)"))))) + +(when (fboundp 'font-lock-ensure) + (describe "clojure-in-docstring-p" + (it "should handle def with docstring" + (with-clojure-buffer "(def my-example-variable \"Doc here and `doc-here`\" - nil)") - (clojure-mode) - (font-lock-ensure) - (goto-char 32) - (should (clojure-in-docstring-p)) - (goto-char 46) - (should (clojure-in-docstring-p))))) + nil)" + (font-lock-ensure) + (goto-char 32) + (expect (clojure-in-docstring-p)) + (goto-char 46) + (expect (clojure-in-docstring-p)))))) (provide 'clojure-mode-syntax-test) + +;;; clojure-mode-syntax-test.el ends here diff --git a/clojure-mode-util-test.el b/clojure-mode-util-test.el index 3d5e0ac..9dd6516 100644 --- a/clojure-mode-util-test.el +++ b/clojure-mode-util-test.el @@ -23,12 +23,14 @@ ;;; Code: (require 'clojure-mode) +(require 'test-helper) (require 'cl-lib) -(require 'ert) +(require 'buttercup) -(ert-deftest clojure-mode-version-should-be-non-nil () - (should (not (eq clojure-mode-version nil)))) +(describe "clojure-mode-version" + (it "should not be nil" + (expect clojure-mode-version))) (let ((project-dir "/home/user/projects/my-project/") (clj-file-path "/home/user/projects/my-project/src/clj/my_project/my_ns/my_file.clj") @@ -36,52 +38,50 @@ (clj-file-ns "my-project.my-ns.my-file") (clojure-cache-project nil)) - (ert-deftest project-relative-path () - :tags '(utils) + (describe "clojure-project-relative-path" (cl-letf (((symbol-function 'clojure-project-dir) (lambda () project-dir))) - (should (string= (clojure-project-relative-path clj-file-path) + (expect (string= (clojure-project-relative-path clj-file-path) project-relative-clj-file-path)))) - (ert-deftest expected-ns () - :tags '(utils) - (cl-letf (((symbol-function 'clojure-project-relative-path) - (lambda (&optional current-buffer-file-name) - project-relative-clj-file-path))) - (should (string= (clojure-expected-ns clj-file-path) clj-file-ns)))) - - (ert-deftest expected-ns-without-argument () - :tags '(utils) - (cl-letf (((symbol-function 'clojure-project-relative-path) - (lambda (&optional current-buffer-file-name) - project-relative-clj-file-path))) - (should (string= (let ((buffer-file-name clj-file-path)) - (clojure-expected-ns)) - clj-file-ns))))) - -(ert-deftest clojure-namespace-name-regex-test () - :tags '(regexp) - (let ((ns "(ns foo)")) - (should (string-match clojure-namespace-name-regex ns)) - (match-string 4 ns)) - (let ((ns "(ns -foo)")) - (should (string-match clojure-namespace-name-regex ns)) - (should (equal "foo" (match-string 4 ns)))) - (let ((ns "(ns foo.baz)")) - (should (string-match clojure-namespace-name-regex ns)) - (should (equal "foo.baz" (match-string 4 ns)))) - (let ((ns "(ns ^:bar foo)")) - (should (string-match clojure-namespace-name-regex ns)) - (should (equal "foo" (match-string 4 ns)))) - (let ((ns "(ns ^:bar ^:baz foo)")) - (should (string-match clojure-namespace-name-regex ns)) - (should (equal "foo" (match-string 4 ns)))) - (let ((ns "(ns ^{:bar true} foo)")) - (should (string-match clojure-namespace-name-regex ns)) - (should (equal "foo" (match-string 4 ns)))) - (let ((ns "(ns #^{:bar true} foo)")) - (should (string-match clojure-namespace-name-regex ns)) - (should (equal "foo" (match-string 4 ns)))) + (describe "clojure-expected-ns" + (it "should return the namespace matching a path" + (cl-letf (((symbol-function 'clojure-project-relative-path) + (lambda (&optional current-buffer-file-name) + project-relative-clj-file-path))) + (expect (string= (clojure-expected-ns clj-file-path) clj-file-ns)))) + + (it "should return the namespace even without a path" + (cl-letf (((symbol-function 'clojure-project-relative-path) + (lambda (&optional current-buffer-file-name) + project-relative-clj-file-path))) + (expect (string= (let ((buffer-file-name clj-file-path)) + (clojure-expected-ns)) + clj-file-ns)))))) + +(describe "clojure-namespace-name-regex" + (it "should match common namespace declarations" + (let ((ns "(ns foo)")) + (expect (string-match clojure-namespace-name-regex ns)) + (match-string 4 ns)) + (let ((ns "(ns + foo)")) + (expect (string-match clojure-namespace-name-regex ns)) + (expect (match-string 4 ns) :to-equal "foo")) + (let ((ns "(ns foo.baz)")) + (expect (string-match clojure-namespace-name-regex ns)) + (expect (match-string 4 ns) :to-equal "foo.baz")) + (let ((ns "(ns ^:bar foo)")) + (expect (string-match clojure-namespace-name-regex ns)) + (expect (match-string 4 ns) :to-equal "foo")) + (let ((ns "(ns ^:bar ^:baz foo)")) + (expect (string-match clojure-namespace-name-regex ns)) + (expect (match-string 4 ns) :to-equal "foo")) + (let ((ns "(ns ^{:bar true} foo)")) + (expect (string-match clojure-namespace-name-regex ns)) + (expect (match-string 4 ns) :to-equal "foo")) + (let ((ns "(ns #^{:bar true} foo)")) + (expect (string-match clojure-namespace-name-regex ns)) + (expect (match-string 4 ns) :to-equal "foo")) ;; TODO ;; (let ((ns "(ns #^{:fail {}} foo)")) ;; (should (string-match clojure-namespace-name-regex ns)) @@ -89,50 +89,50 @@ foo)")) ;; (let ((ns "(ns ^{:fail2 {}} foo.baz)")) ;; (should (string-match clojure-namespace-name-regex ns)) ;; (should (equal "foo.baz" (match-string 4 ns)))) - (let ((ns "(ns ^{} foo)")) - (should (string-match clojure-namespace-name-regex ns)) - (should (equal "foo" (match-string 4 ns)))) - (let ((ns "(ns ^{:skip-wiki true} - aleph.netty")) - (should (string-match clojure-namespace-name-regex ns)) - (should (equal "aleph.netty" (match-string 4 ns)))) - (let ((ns "(ns foo+)")) - (should (string-match clojure-namespace-name-regex ns)) - (should (equal "foo+" (match-string 4 ns))))) - -(ert-deftest test-sort-ns () - (with-temp-buffer - (insert "\n(ns my-app.core - (:require [my-app.views [front-page :as front-page]] - [my-app.state :refer [state]] ; Comments too. - ;; Some comments. - [rum.core :as rum] - [my-app.views [user-page :as user-page]] - my-app.util.api) - (:import java.io.Writer - [clojure.lang AFunction Atom MultiFn Namespace]))") - (clojure-mode) - (clojure-sort-ns) - (should (equal (buffer-string) - "\n(ns my-app.core - (:require [my-app.state :refer [state]] ; Comments too. - my-app.util.api - [my-app.views [front-page :as front-page]] - [my-app.views [user-page :as user-page]] - ;; Some comments. - [rum.core :as rum]) - (:import [clojure.lang AFunction Atom MultiFn Namespace] - java.io.Writer))"))) - (with-temp-buffer - (insert "(ns my-app.core - (:require [rum.core :as rum] ;comment - [my-app.views [user-page :as user-page]]))") - (clojure-mode) - (clojure-sort-ns) - (should (equal (buffer-string) - "(ns my-app.core - (:require [my-app.views [user-page :as user-page]] - [rum.core :as rum] ;comment\n))")))) + (let ((ns "(ns ^{} foo)")) + (expect (string-match clojure-namespace-name-regex ns)) + (expect (match-string 4 ns) :to-equal "foo")) + (let ((ns "(ns ^{:skip-wiki true} + aleph.netty")) + (expect (string-match clojure-namespace-name-regex ns)) + (expect (match-string 4 ns) :to-equal "aleph.netty")) + (let ((ns "(ns foo+)")) + (expect (string-match clojure-namespace-name-regex ns)) + (expect (match-string 4 ns) :to-equal "foo+")))) + +(describe "clojure-sort-ns" + (it "should sort requires in a basic ns" + (with-clojure-buffer "(ns my-app.core + (:require [rum.core :as rum] ;comment + [my-app.views [user-page :as user-page]]))" + (clojure-sort-ns) + (expect (buffer-string) :to-equal + "(ns my-app.core + (:require [my-app.views [user-page :as user-page]] + [rum.core :as rum] ;comment\n))"))) + + (it "should also sort imports in a ns" + (with-clojure-buffer "\n(ns my-app.core + (:require [my-app.views [front-page :as front-page]] + [my-app.state :refer [state]] ; Comments too. + ;; Some comments. + [rum.core :as rum] + [my-app.views [user-page :as user-page]] + my-app.util.api) + (:import java.io.Writer + [clojure.lang AFunction Atom MultiFn Namespace]))" + (clojure-mode) + (clojure-sort-ns) + (expect (buffer-string) :to-equal + "\n(ns my-app.core + (:require [my-app.state :refer [state]] ; Comments too. + my-app.util.api + [my-app.views [front-page :as front-page]] + [my-app.views [user-page :as user-page]] + ;; Some comments. + [rum.core :as rum]) + (:import [clojure.lang AFunction Atom MultiFn Namespace] + java.io.Writer))")))) (provide 'clojure-mode-util-test) diff --git a/test-helper.el b/test-helper.el index 840cfc9..332560d 100644 --- a/test-helper.el +++ b/test-helper.el @@ -54,4 +54,6 @@ DESCRIPTION is the description of the spec." ,@body (expect (buffer-string) :to-equal ,after)))) +(provide 'test-helper) + ;;; test-helper.el ends here From 4dcf6b712422658e59e2dfaa5e2748eabbca4c12 Mon Sep 17 00:00:00 2001 From: Anthony Galea Date: Tue, 9 Jul 2019 16:33:47 +0200 Subject: [PATCH 139/379] [Fix #511] Fix incorrect indentation of namespaced map (#533) --- clojure-mode-indentation-test.el | 18 ++++++++++++++++++ clojure-mode-sexp-test.el | 10 +++++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/clojure-mode-indentation-test.el b/clojure-mode-indentation-test.el index 2ec7e26..5e71877 100644 --- a/clojure-mode-indentation-test.el +++ b/clojure-mode-indentation-test.el @@ -148,6 +148,24 @@ DESCRIPTION is a string with the description of the spec." (cond |x)") + (when-indenting-with-point-it "should indent cond-> with a namespaced map" + " +(cond-> #:a{:b 1} +|x 1)" + + " +(cond-> #:a{:b 1} + |x 1)") + + (when-indenting-with-point-it "should indent cond-> with a namespaced map 2" + " +(cond-> #::a{:b 1} +|x 1)" + + " +(cond-> #::a{:b 1} + |x 1)") + (when-indenting-with-point-it "should indent threading macro with expression on first line" " (->> expr diff --git a/clojure-mode-sexp-test.el b/clojure-mode-sexp-test.el index 5574588..09494dd 100644 --- a/clojure-mode-sexp-test.el +++ b/clojure-mode-sexp-test.el @@ -117,7 +117,15 @@ and point left there." (clojure-forward-logical-sexp 2) (expect (looking-back "\\^String biverse")) (clojure-backward-logical-sexp 1) - (expect (looking-at-p "\\^String biverse"))))) + (expect (looking-at-p "\\^String biverse")))) + + (it "should handle a namespaced map" + (with-clojure-buffer "first #:name/space{:k v}" + (clojure-backward-logical-sexp 1) + (expect (looking-at-p "#:name/space{:k v}")) + (insert " #::ns {:k v}") + (clojure-backward-logical-sexp 1) + (expect (looking-at-p "#::ns {:k v}"))))) (describe "clojure-backward-logical-sexp" (it "should work with buffer corners" From c629593675abf1146acf9624f7c811cc5fd17a94 Mon Sep 17 00:00:00 2001 From: Anthony Galea Date: Wed, 10 Jul 2019 14:39:16 +0200 Subject: [PATCH 140/379] [Fix #531] Don't match strings when using `clojure-rename-ns-alias` (#534) --- clojure-mode-refactor-rename-ns-alias-test.el | 40 ++++++++++++++++++- 1 file changed, 38 insertions(+), 2 deletions(-) diff --git a/clojure-mode-refactor-rename-ns-alias-test.el b/clojure-mode-refactor-rename-ns-alias-test.el index a2875b4..eeb96d5 100644 --- a/clojure-mode-refactor-rename-ns-alias-test.el +++ b/clojure-mode-refactor-rename-ns-alias-test.el @@ -55,8 +55,44 @@ (+ (lib/a 1) (b 2))" - (clojure--rename-ns-alias-internal "foo" "bar"))) + (clojure--rename-ns-alias-internal "foo" "bar")) -(provide 'clojure-mode-refactor-rename-ns-alias-test) + (when-refactoring-it "should skip strings" + "(ns cljr.core + (:require [my.lib :as lib])) + + (def dirname \"/usr/local/lib/python3.6/site-packages\") + + (+ (lib/a 1) (b 2))" + + "(ns cljr.core + (:require [my.lib :as foo])) + + (def dirname \"/usr/local/lib/python3.6/site-packages\") + + (+ (foo/a 1) (b 2))" + + (clojure--rename-ns-alias-internal "lib" "foo")) + + (when-refactoring-it "should not skip comments" + "(ns cljr.core + (:require [my.lib :as lib])) + + (def dirname \"/usr/local/lib/python3.6/site-packages\") + + ;; TODO refactor using lib/foo + (+ (lib/a 1) (b 2))" + + "(ns cljr.core + (:require [my.lib :as new-lib])) + + (def dirname \"/usr/local/lib/python3.6/site-packages\") + + ;; TODO refactor using new-lib/foo + (+ (new-lib/a 1) (b 2))" + + (clojure--rename-ns-alias-internal "lib" "new-lib"))) + + (provide 'clojure-mode-refactor-rename-ns-alias-test) ;;; clojure-mode-refactor-rename-ns-alias-test.el ends here From e203f91ec58212e4f4249da1ec47ea85f17fbb48 Mon Sep 17 00:00:00 2001 From: Anthony Galea Date: Thu, 11 Jul 2019 09:18:22 +0200 Subject: [PATCH 141/379] Remove calls to provide and require for test-helper. The require/provide machinery is intended for things that are to be loaded for the benefit of end-users. Placing test-helper under utils is enough to get buttercup to load it before the tests. --- clojure-mode-convert-collection-test.el | 1 - clojure-mode-cycling-test.el | 1 - clojure-mode-font-lock-test.el | 1 - clojure-mode-indentation-test.el | 1 - clojure-mode-refactor-let-test.el | 1 - clojure-mode-refactor-rename-ns-alias-test.el | 1 - clojure-mode-refactor-threading-test.el | 1 - clojure-mode-sexp-test.el | 1 - clojure-mode-syntax-test.el | 1 - clojure-mode-util-test.el | 1 - test-helper.el => utils/test-helper.el | 2 -- 11 files changed, 12 deletions(-) rename test-helper.el => utils/test-helper.el (98%) diff --git a/clojure-mode-convert-collection-test.el b/clojure-mode-convert-collection-test.el index 545b2ef..220eb9d 100644 --- a/clojure-mode-convert-collection-test.el +++ b/clojure-mode-convert-collection-test.el @@ -26,7 +26,6 @@ ;;; Code: (require 'clojure-mode) -(require 'test-helper) (require 'buttercup) (describe "clojure-convert-collection-to-map" diff --git a/clojure-mode-cycling-test.el b/clojure-mode-cycling-test.el index 40754a9..21688e4 100644 --- a/clojure-mode-cycling-test.el +++ b/clojure-mode-cycling-test.el @@ -25,7 +25,6 @@ ;;; Code: (require 'clojure-mode) -(require 'test-helper) (require 'buttercup) (describe "clojure-cycle-privacy" diff --git a/clojure-mode-font-lock-test.el b/clojure-mode-font-lock-test.el index 10ebd38..0f7a50f 100644 --- a/clojure-mode-font-lock-test.el +++ b/clojure-mode-font-lock-test.el @@ -25,7 +25,6 @@ ;;; Code: (require 'clojure-mode) -(require 'test-helper) (require 'cl-lib) (require 'buttercup) diff --git a/clojure-mode-indentation-test.el b/clojure-mode-indentation-test.el index 5e71877..39ed8a4 100644 --- a/clojure-mode-indentation-test.el +++ b/clojure-mode-indentation-test.el @@ -24,7 +24,6 @@ ;;; Code: (require 'clojure-mode) -(require 'test-helper) (require 'cl-lib) (require 'buttercup) (require 's) diff --git a/clojure-mode-refactor-let-test.el b/clojure-mode-refactor-let-test.el index e36654a..e1dd0bd 100644 --- a/clojure-mode-refactor-let-test.el +++ b/clojure-mode-refactor-let-test.el @@ -25,7 +25,6 @@ ;;; Code: (require 'clojure-mode) -(require 'test-helper) (require 'buttercup) (describe "clojure--introduce-let-internal" diff --git a/clojure-mode-refactor-rename-ns-alias-test.el b/clojure-mode-refactor-rename-ns-alias-test.el index eeb96d5..0bfb546 100644 --- a/clojure-mode-refactor-rename-ns-alias-test.el +++ b/clojure-mode-refactor-rename-ns-alias-test.el @@ -18,7 +18,6 @@ ;;; Code: (require 'clojure-mode) -(require 'test-helper) (require 'ert) (describe "clojure--rename-ns-alias-internal" diff --git a/clojure-mode-refactor-threading-test.el b/clojure-mode-refactor-threading-test.el index 5e4b482..be86eb1 100644 --- a/clojure-mode-refactor-threading-test.el +++ b/clojure-mode-refactor-threading-test.el @@ -26,7 +26,6 @@ ;;; Code: (require 'clojure-mode) -(require 'test-helper) (require 'buttercup) (describe "clojure-thread" diff --git a/clojure-mode-sexp-test.el b/clojure-mode-sexp-test.el index 09494dd..cca4048 100644 --- a/clojure-mode-sexp-test.el +++ b/clojure-mode-sexp-test.el @@ -20,7 +20,6 @@ ;;; Code: (require 'clojure-mode) -(require 'test-helper) (require 'buttercup) (defmacro with-clojure-buffer-point (text &rest body) diff --git a/clojure-mode-syntax-test.el b/clojure-mode-syntax-test.el index 0370f1a..b8503a5 100644 --- a/clojure-mode-syntax-test.el +++ b/clojure-mode-syntax-test.el @@ -24,7 +24,6 @@ ;;; Code: (require 'clojure-mode) -(require 'test-helper) (require 'buttercup) (defun non-func (form-a form-b) diff --git a/clojure-mode-util-test.el b/clojure-mode-util-test.el index 9dd6516..fca0a6c 100644 --- a/clojure-mode-util-test.el +++ b/clojure-mode-util-test.el @@ -23,7 +23,6 @@ ;;; Code: (require 'clojure-mode) -(require 'test-helper) (require 'cl-lib) (require 'buttercup) diff --git a/test-helper.el b/utils/test-helper.el similarity index 98% rename from test-helper.el rename to utils/test-helper.el index 332560d..840cfc9 100644 --- a/test-helper.el +++ b/utils/test-helper.el @@ -54,6 +54,4 @@ DESCRIPTION is the description of the spec." ,@body (expect (buffer-string) :to-equal ,after)))) -(provide 'test-helper) - ;;; test-helper.el ends here From 35bdde40d983a37734fb590466e93f9530178e5e Mon Sep 17 00:00:00 2001 From: Anthony Galea Date: Thu, 11 Jul 2019 10:31:28 +0200 Subject: [PATCH 142/379] [Fix #410] Add refactoring: add an arity to a function. --- clojure-mode-refactor-add-arity-test.el | 156 ++++++++++++++++++ clojure-mode-refactor-rename-ns-alias-test.el | 5 + 2 files changed, 161 insertions(+) create mode 100644 clojure-mode-refactor-add-arity-test.el diff --git a/clojure-mode-refactor-add-arity-test.el b/clojure-mode-refactor-add-arity-test.el new file mode 100644 index 0000000..8bef9af --- /dev/null +++ b/clojure-mode-refactor-add-arity-test.el @@ -0,0 +1,156 @@ +;;; clojure-mode-refactor-add-arity.el --- Clojure Mode: refactor add arity -*- lexical-binding: t; -*- + +;; This file is not part of GNU Emacs. + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + + +;;; Commentary: + +;; Tests for clojure-add-arity + +;;; Code: + +(require 'clojure-mode) +(require 'buttercup) + +(defmacro when-refactoring-with-point-it (description before after &rest body) + "Return a buttercup spec. + +Like when-refactor-it but also checks whether point is moved to the expected +position. + +BEFORE is the buffer string before refactoring, where a pipe (|) represents +point. + +AFTER is the expected buffer string after refactoring, where a pipe (|) +represents the expected position of point. + +DESCRIPTION is a string with the description of the spec." + `(it ,description + (let* ((after ,after) + (expected-cursor-pos (1+ (s-index-of "|" after))) + (expected-state (delete ?| after))) + (with-clojure-buffer ,before + (goto-char (point-min)) + (search-forward "|") + (delete-char -1) + ,@body + (expect (buffer-string) :to-equal expected-state) + (expect (point) :to-equal expected-cursor-pos))))) + +(describe "clojure-add-arity" + + (when-refactoring-with-point-it "should add an arity to a single-arity defn with args on same line" + "(defn foo [arg] + body|)" + + "(defn foo + ([|]) + ([arg] + body))" + + (clojure-add-arity)) + + (when-refactoring-with-point-it "should add an arity to a single-arity defn with args on next line" + "(defn foo + [arg] + bo|dy)" + + "(defn foo + ([|]) + ([arg] + body))" + + (clojure-add-arity)) + + (when-refactoring-with-point-it "should handle a single-arity defn with a docstring" + "(defn foo + \"some docst|ring\" + [arg] + body)" + + "(defn foo + \"some docstring\" + ([|]) + ([arg] + body))" + + (clojure-add-arity)) + + (when-refactoring-with-point-it "should handle a single-arity defn with metadata" + "(defn fo|o + ^{:bla \"meta\"} + [arg] + body)" + + "(defn foo + ^{:bla \"meta\"} + ([|]) + ([arg] + body))" + + (clojure-add-arity)) + + (when-refactoring-with-point-it "should add an arity to a multi-arity defn" + "(defn foo + ([arg1]) + ([ar|g1 arg2] + body))" + + "(defn foo + ([|]) + ([arg1]) + ([arg1 arg2] + body))" + + (clojure-add-arity)) + + (when-refactoring-with-point-it "should handle a multi-arity defn with a docstring" + "(defn foo + \"some docstring\" + ([]) + ([arg|] + body))" + + "(defn foo + \"some docstring\" + ([|]) + ([]) + ([arg] + body))" + + (clojure-add-arity)) + + (when-refactoring-with-point-it "should handle a multi-arity defn with metadata" + "(defn foo + \"some docstring\" + ^{:bla \"meta\"} + ([]) + |([arg] + body))" + + "(defn foo + \"some docstring\" + ^{:bla \"meta\"} + ([|]) + ([]) + ([arg] + body))" + + (clojure-add-arity))) + +(provide 'clojure-mode-refactor-add-arity-test) + +;;; clojure-mode-refactor-add-arity-test.el ends here diff --git a/clojure-mode-refactor-rename-ns-alias-test.el b/clojure-mode-refactor-rename-ns-alias-test.el index 0bfb546..aba75b3 100644 --- a/clojure-mode-refactor-rename-ns-alias-test.el +++ b/clojure-mode-refactor-rename-ns-alias-test.el @@ -15,6 +15,11 @@ ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . + +;;; Commentary: + +;; Tests for clojure-rename-ns-alias + ;;; Code: (require 'clojure-mode) From 756d6941d839efade60945f178ce2aee3c8798f5 Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Fri, 12 Jul 2019 09:39:30 +0300 Subject: [PATCH 143/379] Bump the copyright years --- clojure-mode-bytecomp-warnings.el | 2 +- clojure-mode-convert-collection-test.el | 2 +- clojure-mode-cycling-test.el | 2 +- clojure-mode-font-lock-test.el | 2 +- clojure-mode-indentation-test.el | 2 +- clojure-mode-refactor-let-test.el | 2 +- clojure-mode-refactor-threading-test.el | 2 +- clojure-mode-sexp-test.el | 2 +- clojure-mode-syntax-test.el | 2 +- clojure-mode-util-test.el | 2 +- utils/test-helper.el | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) diff --git a/clojure-mode-bytecomp-warnings.el b/clojure-mode-bytecomp-warnings.el index 67c5a03..c6fb344 100644 --- a/clojure-mode-bytecomp-warnings.el +++ b/clojure-mode-bytecomp-warnings.el @@ -1,6 +1,6 @@ ;;; clojure-mode-bytecomp-warnings.el --- Check for byte-compilation problems -;; Copyright © 2012-2018 Bozhidar Batsov and contributors +;; Copyright © 2012-2019 Bozhidar Batsov and contributors ;; ;; This program is free software: you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by diff --git a/clojure-mode-convert-collection-test.el b/clojure-mode-convert-collection-test.el index 220eb9d..bf7a644 100644 --- a/clojure-mode-convert-collection-test.el +++ b/clojure-mode-convert-collection-test.el @@ -1,6 +1,6 @@ ;;; clojure-mode-convert-collection-test.el --- Clojure Mode: convert collection type -*- lexical-binding: t; -*- -;; Copyright (C) 2016-2018 Benedek Fazekas +;; Copyright (C) 2016-2019 Benedek Fazekas ;; This file is not part of GNU Emacs. diff --git a/clojure-mode-cycling-test.el b/clojure-mode-cycling-test.el index 21688e4..6acd047 100644 --- a/clojure-mode-cycling-test.el +++ b/clojure-mode-cycling-test.el @@ -1,6 +1,6 @@ ;;; clojure-mode-cycling-test.el --- Clojure Mode: cycling things tests -*- lexical-binding: t; -*- -;; Copyright (C) 2016-2018 Benedek Fazekas +;; Copyright (C) 2016-2019 Benedek Fazekas ;; This file is not part of GNU Emacs. diff --git a/clojure-mode-font-lock-test.el b/clojure-mode-font-lock-test.el index 0f7a50f..715fb2f 100644 --- a/clojure-mode-font-lock-test.el +++ b/clojure-mode-font-lock-test.el @@ -1,7 +1,7 @@ ;;; clojure-mode-font-lock-test.el --- Clojure Mode: Font lock test suite ;; -*- lexical-binding: t; -*- -;; Copyright (C) 2014-2018 Bozhidar Batsov +;; Copyright (C) 2014-2019 Bozhidar Batsov ;; This file is not part of GNU Emacs. diff --git a/clojure-mode-indentation-test.el b/clojure-mode-indentation-test.el index 39ed8a4..006d6f7 100644 --- a/clojure-mode-indentation-test.el +++ b/clojure-mode-indentation-test.el @@ -1,6 +1,6 @@ ;;; clojure-mode-indentation-test.el --- Clojure Mode: indentation tests -*- lexical-binding: t; -*- -;; Copyright (C) 2015-2018 Bozhidar Batsov +;; Copyright (C) 2015-2019 Bozhidar Batsov ;; This file is not part of GNU Emacs. diff --git a/clojure-mode-refactor-let-test.el b/clojure-mode-refactor-let-test.el index e1dd0bd..6b9df13 100644 --- a/clojure-mode-refactor-let-test.el +++ b/clojure-mode-refactor-let-test.el @@ -1,6 +1,6 @@ ;;; clojure-mode-refactor-let-test.el --- Clojure Mode: refactor let -*- lexical-binding: t; -*- -;; Copyright (C) 2016-2018 Benedek Fazekas +;; Copyright (C) 2016-2019 Benedek Fazekas ;; This file is not part of GNU Emacs. diff --git a/clojure-mode-refactor-threading-test.el b/clojure-mode-refactor-threading-test.el index be86eb1..61f4409 100644 --- a/clojure-mode-refactor-threading-test.el +++ b/clojure-mode-refactor-threading-test.el @@ -1,6 +1,6 @@ ;;; clojure-mode-refactor-threading-test.el --- Clojure Mode: refactor threading tests -*- lexical-binding: t; -*- -;; Copyright (C) 2016-2018 Benedek Fazekas +;; Copyright (C) 2016-2019 Benedek Fazekas ;; This file is not part of GNU Emacs. diff --git a/clojure-mode-sexp-test.el b/clojure-mode-sexp-test.el index cca4048..a7d5b85 100644 --- a/clojure-mode-sexp-test.el +++ b/clojure-mode-sexp-test.el @@ -1,6 +1,6 @@ ;;; clojure-mode-sexp-test.el --- Clojure Mode: sexp tests -*- lexical-binding: t; -*- -;; Copyright (C) 2015-2018 Artur Malabarba +;; Copyright (C) 2015-2019 Artur Malabarba ;; This file is not part of GNU Emacs. diff --git a/clojure-mode-syntax-test.el b/clojure-mode-syntax-test.el index b8503a5..60286d4 100644 --- a/clojure-mode-syntax-test.el +++ b/clojure-mode-syntax-test.el @@ -1,6 +1,6 @@ ;;; clojure-mode-syntax-test.el --- Clojure Mode: syntax related tests -*- lexical-binding: t; -*- -;; Copyright (C) 2015-2018 Bozhidar Batsov +;; Copyright (C) 2015-2019 Bozhidar Batsov ;; This file is not part of GNU Emacs. diff --git a/clojure-mode-util-test.el b/clojure-mode-util-test.el index fca0a6c..ee5591a 100644 --- a/clojure-mode-util-test.el +++ b/clojure-mode-util-test.el @@ -1,6 +1,6 @@ ;;; clojure-mode-util-test.el --- Clojure Mode: util test suite -*- lexical-binding: t; -*- -;; Copyright (C) 2014-2018 Bozhidar Batsov +;; Copyright (C) 2014-2019 Bozhidar Batsov ;; This file is not part of GNU Emacs. diff --git a/utils/test-helper.el b/utils/test-helper.el index 840cfc9..f16379b 100644 --- a/utils/test-helper.el +++ b/utils/test-helper.el @@ -1,6 +1,6 @@ ;;; test-helper.el --- Clojure Mode: Non-interactive unit-test setup -*- lexical-binding: t; -*- -;; Copyright (C) 2014-2018 Bozhidar Batsov +;; Copyright (C) 2014-2019 Bozhidar Batsov ;; This file is not part of GNU Emacs. From 9da2f8d716ecd9098808a0e45b4d7844cf2e61e0 Mon Sep 17 00:00:00 2001 From: yuhan0 Date: Tue, 16 Jul 2019 10:07:18 +0800 Subject: [PATCH 144/379] Update changelog and tests for clojure-unwind changes --- clojure-mode-refactor-threading-test.el | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/clojure-mode-refactor-threading-test.el b/clojure-mode-refactor-threading-test.el index 61f4409..6124634 100644 --- a/clojure-mode-refactor-threading-test.el +++ b/clojure-mode-refactor-threading-test.el @@ -227,6 +227,26 @@ (clojure-unwind) (clojure-unwind)) + (when-refactoring-it "should unwind N steps with numeric prefix arg" + "(->> [1 2 3 4 5] + (filter even?) + (map square) + sum)" + + "(->> (sum (map square (filter even? [1 2 3 4 5]))))" + + (clojure-unwind 3)) + + (when-refactoring-it "should unwind completely with universal prefix arg" + "(->> [1 2 3 4 5] + (filter even?) + (map square) + sum)" + + "(sum (map square (filter even? [1 2 3 4 5])))" + + (clojure-unwind '(4))) + (when-refactoring-it "should unwind with function name" "(->> [1 2 3 4 5] sum From 93b9e7e7f9e5f825676e25d3df237197cd3b07a8 Mon Sep 17 00:00:00 2001 From: Anthony Galea Date: Thu, 25 Jul 2019 08:54:23 +0200 Subject: [PATCH 145/379] Enhance add arity refactoring (#541) Support reader conditionals, letfn, fn, defmacro, defmethod, defprotocol, reify and proxy in addition to defn. --- clojure-mode-refactor-add-arity-test.el | 196 ++++++++++++++++++++++++ 1 file changed, 196 insertions(+) diff --git a/clojure-mode-refactor-add-arity-test.el b/clojure-mode-refactor-add-arity-test.el index 8bef9af..2b3cbc5 100644 --- a/clojure-mode-refactor-add-arity-test.el +++ b/clojure-mode-refactor-add-arity-test.el @@ -149,6 +149,202 @@ DESCRIPTION is a string with the description of the spec." ([arg] body))" + (clojure-add-arity)) + + (when-refactoring-with-point-it "should handle a single-arity fn" + "(fn foo [arg] + body|)" + + "(fn foo + ([|]) + ([arg] + body))" + + (clojure-add-arity)) + + (when-refactoring-with-point-it "should handle a multi-arity fn" + "(fn foo + ([x y] + body) + ([a|rg] + body))" + + "(fn foo + ([|]) + ([x y] + body) + ([arg] + body))" + + (clojure-add-arity)) + + (when-refactoring-with-point-it "should handle a single-arity defmacro" + "(defmacro foo [arg] + body|)" + + "(defmacro foo + ([|]) + ([arg] + body))" + + (clojure-add-arity)) + + (when-refactoring-with-point-it "should handle a multi-arity defmacro" + "(defmacro foo + ([x y] + body) + ([a|rg] + body))" + + "(defmacro foo + ([|]) + ([x y] + body) + ([arg] + body))" + + (clojure-add-arity)) + + (when-refactoring-with-point-it "should handle a single-arity defmethod" + "(defmethod foo :bar [arg] + body|)" + + "(defmethod foo :bar + ([|]) + ([arg] + body))" + + (clojure-add-arity)) + + (when-refactoring-with-point-it "should handle a multi-arity defmethod" + "(defmethod foo :bar + ([x y] + body) + ([a|rg] + body))" + + "(defmethod foo :bar + ([|]) + ([x y] + body) + ([arg] + body))" + + (clojure-add-arity)) + + (when-refactoring-with-point-it "should handle a defn inside a reader conditional" + "#?(:clj + (defn foo + \"some docstring\" + ^{:bla \"meta\"} + |([arg] + body)))" + + "#?(:clj + (defn foo + \"some docstring\" + ^{:bla \"meta\"} + ([|]) + ([arg] + body)))" + + (clojure-add-arity)) + + (when-refactoring-with-point-it "should handle a defn inside a reader conditional with 2 platform tags" + "#?(:clj + (defn foo + \"some docstring\" + ^{:bla \"meta\"} + |([arg] + body)) + :cljs + (defn foo + \"some docstring\" + ^{:bla \"meta\"} + ([arg] + body)))" + + "#?(:clj + (defn foo + \"some docstring\" + ^{:bla \"meta\"} + ([|]) + ([arg] + body)) + :cljs + (defn foo + \"some docstring\" + ^{:bla \"meta\"} + ([arg] + body)))" + + (clojure-add-arity)) + + (when-refactoring-with-point-it "should handle a single-arity fn inside a letfn" + "(letfn [(foo [x] + bo|dy)] + (foo 3))" + + "(letfn [(foo + ([|]) + ([x] + body))] + (foo 3))" + + (clojure-add-arity)) + + (when-refactoring-with-point-it "should handle a multi-arity fn inside a letfn" + "(letfn [(foo + ([x] + body) + |([x y] + body))] + (foo 3))" + + "(letfn [(foo + ([|]) + ([x] + body) + ([x y] + body))] + (foo 3))" + + (clojure-add-arity)) + + (when-refactoring-with-point-it "should handle a proxy" + "(proxy [Foo] [] + (bar [arg] + body|))" + + "(proxy [Foo] [] + (bar + ([|]) + ([arg] + body)))" + + (clojure-add-arity)) + + (when-refactoring-with-point-it "should handle a defprotocol" + "(defprotocol Foo + \"some docstring\" + (bar [arg] [x |y] \"some docstring\"))" + + "(defprotocol Foo + \"some docstring\" + (bar [|] [arg] [x y] \"some docstring\"))" + + (clojure-add-arity)) + + (when-refactoring-with-point-it "should handle a reify" + "(reify Foo + (bar [arg] body) + (blahs [arg]| body))" + + "(reify Foo + (bar [arg] body) + (blahs [|]) + (blahs [arg] body))" + (clojure-add-arity))) (provide 'clojure-mode-refactor-add-arity-test) From c933a4e2fe521a4556ac3098e5db6f90ffaf3dbd Mon Sep 17 00:00:00 2001 From: Robin Schroer Date: Tue, 1 Oct 2019 16:46:30 +0200 Subject: [PATCH 146/379] Support multiple consecutive reader comments (#_#_a b) Modified the default regexps and the heuristic to find the end of the region to comment out. Previously Emacs would treat the second `#_` as the commented form, and then highlight the following two forms as usual. Now it (mostly) matches what Clojure actually evaluates. Things get weird when you start mixing `#_` and forms, but this fixes the most common use cases, like key-value-pairs. --- clojure-mode-font-lock-test.el | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/clojure-mode-font-lock-test.el b/clojure-mode-font-lock-test.el index 715fb2f..484caa5 100644 --- a/clojure-mode-font-lock-test.el +++ b/clojure-mode-font-lock-test.el @@ -133,11 +133,35 @@ DESCRIPTION is the description of the spec." ("#_" (1 2 nil)) + ("#_#_" + (1 2 nil)) + + ("#_#_" + (3 2 font-lock-comment-face)) + + ("#_ #_" + (1 3 nil)) + + ("#_ #_" + (4 2 font-lock-comment-face)) + ("#_ \n;; some crap\n (lala 0101\n lao\n\n 0 0i)" (1 2 nil)) ("#_ \n;; some crap\n (lala 0101\n lao\n\n 0 0i)" - (5 41 font-lock-comment-face))) + (5 41 font-lock-comment-face)) + + ("#_#_ \n;; some crap\n (lala 0101\n lao\n\n 0 0i)\n;; more crap\n (foobar tnseriao)" + (1 4 nil)) + + ("#_ #_ \n;; some crap\n (lala 0101\n lao\n\n 0 0i)\n;; more crap\n (foobar tnseriao)" + (1 5 nil)) + + ("#_#_ \n;; some crap\n (lala 0101\n lao\n\n 0 0i)\n;; more crap\n (foobar tnseriao)" + (7 75 font-lock-comment-face)) + + ("#_ #_ \n;; some crap\n (lala 0101\n lao\n\n 0 0i)\n;; more crap\n (foobar tnseriao)" + (8 75 font-lock-comment-face))) (when-fontifying-it "should handle namespace declarations" ("(ns .validns)" From fad40cc55bcdb921f98ee25812d99306a56de7e3 Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Fri, 20 Mar 2020 10:23:37 +0200 Subject: [PATCH 147/379] Update the copyright years --- clojure-mode-bytecomp-warnings.el | 2 +- clojure-mode-convert-collection-test.el | 2 +- clojure-mode-cycling-test.el | 2 +- clojure-mode-font-lock-test.el | 2 +- clojure-mode-indentation-test.el | 2 +- clojure-mode-refactor-let-test.el | 2 +- clojure-mode-refactor-threading-test.el | 2 +- clojure-mode-sexp-test.el | 2 +- clojure-mode-syntax-test.el | 2 +- clojure-mode-util-test.el | 2 +- utils/test-helper.el | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) diff --git a/clojure-mode-bytecomp-warnings.el b/clojure-mode-bytecomp-warnings.el index c6fb344..7c65dda 100644 --- a/clojure-mode-bytecomp-warnings.el +++ b/clojure-mode-bytecomp-warnings.el @@ -1,6 +1,6 @@ ;;; clojure-mode-bytecomp-warnings.el --- Check for byte-compilation problems -;; Copyright © 2012-2019 Bozhidar Batsov and contributors +;; Copyright © 2012-2020 Bozhidar Batsov and contributors ;; ;; This program is free software: you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by diff --git a/clojure-mode-convert-collection-test.el b/clojure-mode-convert-collection-test.el index bf7a644..34d6b12 100644 --- a/clojure-mode-convert-collection-test.el +++ b/clojure-mode-convert-collection-test.el @@ -1,6 +1,6 @@ ;;; clojure-mode-convert-collection-test.el --- Clojure Mode: convert collection type -*- lexical-binding: t; -*- -;; Copyright (C) 2016-2019 Benedek Fazekas +;; Copyright (C) 2016-2020 Benedek Fazekas ;; This file is not part of GNU Emacs. diff --git a/clojure-mode-cycling-test.el b/clojure-mode-cycling-test.el index 6acd047..f4d6297 100644 --- a/clojure-mode-cycling-test.el +++ b/clojure-mode-cycling-test.el @@ -1,6 +1,6 @@ ;;; clojure-mode-cycling-test.el --- Clojure Mode: cycling things tests -*- lexical-binding: t; -*- -;; Copyright (C) 2016-2019 Benedek Fazekas +;; Copyright (C) 2016-2020 Benedek Fazekas ;; This file is not part of GNU Emacs. diff --git a/clojure-mode-font-lock-test.el b/clojure-mode-font-lock-test.el index 484caa5..a45e3de 100644 --- a/clojure-mode-font-lock-test.el +++ b/clojure-mode-font-lock-test.el @@ -1,7 +1,7 @@ ;;; clojure-mode-font-lock-test.el --- Clojure Mode: Font lock test suite ;; -*- lexical-binding: t; -*- -;; Copyright (C) 2014-2019 Bozhidar Batsov +;; Copyright (C) 2014-2020 Bozhidar Batsov ;; This file is not part of GNU Emacs. diff --git a/clojure-mode-indentation-test.el b/clojure-mode-indentation-test.el index 006d6f7..cb37941 100644 --- a/clojure-mode-indentation-test.el +++ b/clojure-mode-indentation-test.el @@ -1,6 +1,6 @@ ;;; clojure-mode-indentation-test.el --- Clojure Mode: indentation tests -*- lexical-binding: t; -*- -;; Copyright (C) 2015-2019 Bozhidar Batsov +;; Copyright (C) 2015-2020 Bozhidar Batsov ;; This file is not part of GNU Emacs. diff --git a/clojure-mode-refactor-let-test.el b/clojure-mode-refactor-let-test.el index 6b9df13..0e178f7 100644 --- a/clojure-mode-refactor-let-test.el +++ b/clojure-mode-refactor-let-test.el @@ -1,6 +1,6 @@ ;;; clojure-mode-refactor-let-test.el --- Clojure Mode: refactor let -*- lexical-binding: t; -*- -;; Copyright (C) 2016-2019 Benedek Fazekas +;; Copyright (C) 2016-2020 Benedek Fazekas ;; This file is not part of GNU Emacs. diff --git a/clojure-mode-refactor-threading-test.el b/clojure-mode-refactor-threading-test.el index 6124634..76e5810 100644 --- a/clojure-mode-refactor-threading-test.el +++ b/clojure-mode-refactor-threading-test.el @@ -1,6 +1,6 @@ ;;; clojure-mode-refactor-threading-test.el --- Clojure Mode: refactor threading tests -*- lexical-binding: t; -*- -;; Copyright (C) 2016-2019 Benedek Fazekas +;; Copyright (C) 2016-2020 Benedek Fazekas ;; This file is not part of GNU Emacs. diff --git a/clojure-mode-sexp-test.el b/clojure-mode-sexp-test.el index a7d5b85..da0c08f 100644 --- a/clojure-mode-sexp-test.el +++ b/clojure-mode-sexp-test.el @@ -1,6 +1,6 @@ ;;; clojure-mode-sexp-test.el --- Clojure Mode: sexp tests -*- lexical-binding: t; -*- -;; Copyright (C) 2015-2019 Artur Malabarba +;; Copyright (C) 2015-2020 Artur Malabarba ;; This file is not part of GNU Emacs. diff --git a/clojure-mode-syntax-test.el b/clojure-mode-syntax-test.el index 60286d4..af00c1a 100644 --- a/clojure-mode-syntax-test.el +++ b/clojure-mode-syntax-test.el @@ -1,6 +1,6 @@ ;;; clojure-mode-syntax-test.el --- Clojure Mode: syntax related tests -*- lexical-binding: t; -*- -;; Copyright (C) 2015-2019 Bozhidar Batsov +;; Copyright (C) 2015-2020 Bozhidar Batsov ;; This file is not part of GNU Emacs. diff --git a/clojure-mode-util-test.el b/clojure-mode-util-test.el index ee5591a..f9dd1f6 100644 --- a/clojure-mode-util-test.el +++ b/clojure-mode-util-test.el @@ -1,6 +1,6 @@ ;;; clojure-mode-util-test.el --- Clojure Mode: util test suite -*- lexical-binding: t; -*- -;; Copyright (C) 2014-2019 Bozhidar Batsov +;; Copyright (C) 2014-2020 Bozhidar Batsov ;; This file is not part of GNU Emacs. diff --git a/utils/test-helper.el b/utils/test-helper.el index f16379b..9076312 100644 --- a/utils/test-helper.el +++ b/utils/test-helper.el @@ -1,6 +1,6 @@ ;;; test-helper.el --- Clojure Mode: Non-interactive unit-test setup -*- lexical-binding: t; -*- -;; Copyright (C) 2014-2019 Bozhidar Batsov +;; Copyright (C) 2014-2020 Bozhidar Batsov ;; This file is not part of GNU Emacs. From 28024c5d400f95cde195134d0814241c5dffc2ed Mon Sep 17 00:00:00 2001 From: yuhan0 Date: Fri, 20 Mar 2020 07:05:30 +0800 Subject: [PATCH 148/379] update find-ns tests --- clojure-mode-util-test.el | 92 ++++++++++++++++++++++----------------- 1 file changed, 51 insertions(+), 41 deletions(-) diff --git a/clojure-mode-util-test.el b/clojure-mode-util-test.el index f9dd1f6..8d1c059 100644 --- a/clojure-mode-util-test.el +++ b/clojure-mode-util-test.el @@ -57,47 +57,57 @@ (clojure-expected-ns)) clj-file-ns)))))) -(describe "clojure-namespace-name-regex" - (it "should match common namespace declarations" - (let ((ns "(ns foo)")) - (expect (string-match clojure-namespace-name-regex ns)) - (match-string 4 ns)) - (let ((ns "(ns - foo)")) - (expect (string-match clojure-namespace-name-regex ns)) - (expect (match-string 4 ns) :to-equal "foo")) - (let ((ns "(ns foo.baz)")) - (expect (string-match clojure-namespace-name-regex ns)) - (expect (match-string 4 ns) :to-equal "foo.baz")) - (let ((ns "(ns ^:bar foo)")) - (expect (string-match clojure-namespace-name-regex ns)) - (expect (match-string 4 ns) :to-equal "foo")) - (let ((ns "(ns ^:bar ^:baz foo)")) - (expect (string-match clojure-namespace-name-regex ns)) - (expect (match-string 4 ns) :to-equal "foo")) - (let ((ns "(ns ^{:bar true} foo)")) - (expect (string-match clojure-namespace-name-regex ns)) - (expect (match-string 4 ns) :to-equal "foo")) - (let ((ns "(ns #^{:bar true} foo)")) - (expect (string-match clojure-namespace-name-regex ns)) - (expect (match-string 4 ns) :to-equal "foo")) - ;; TODO - ;; (let ((ns "(ns #^{:fail {}} foo)")) - ;; (should (string-match clojure-namespace-name-regex ns)) - ;; (match-string 4 ns)) - ;; (let ((ns "(ns ^{:fail2 {}} foo.baz)")) - ;; (should (string-match clojure-namespace-name-regex ns)) - ;; (should (equal "foo.baz" (match-string 4 ns)))) - (let ((ns "(ns ^{} foo)")) - (expect (string-match clojure-namespace-name-regex ns)) - (expect (match-string 4 ns) :to-equal "foo")) - (let ((ns "(ns ^{:skip-wiki true} - aleph.netty")) - (expect (string-match clojure-namespace-name-regex ns)) - (expect (match-string 4 ns) :to-equal "aleph.netty")) - (let ((ns "(ns foo+)")) - (expect (string-match clojure-namespace-name-regex ns)) - (expect (match-string 4 ns) :to-equal "foo+")))) +(describe "clojure-find-ns" + (it "should find common namespace declarations" + (with-clojure-buffer "(ns foo)" + (expect (clojure-find-ns) :to-equal "foo")) + (with-clojure-buffer "(ns + foo)" + (expect (clojure-find-ns) :to-equal "foo")) + (with-clojure-buffer "(ns foo.baz)" + (expect (clojure-find-ns) :to-equal "foo.baz")) + (with-clojure-buffer "(ns ^:bar foo)" + (expect (clojure-find-ns) :to-equal "foo")) + (with-clojure-buffer "(ns ^:bar ^:baz foo)" + (expect (clojure-find-ns) :to-equal "foo"))) + (it "should find namespace declarations with nested metadata and docstrings" + (with-clojure-buffer "(ns ^{:bar true} foo)" + (expect (clojure-find-ns) :to-equal "foo")) + (with-clojure-buffer "(ns #^{:bar true} foo)" + (expect (clojure-find-ns) :to-equal "foo")) + (with-clojure-buffer "(ns #^{:fail {}} foo)" + (expect (clojure-find-ns) :to-equal "foo")) + (with-clojure-buffer "(ns ^{:fail2 {}} foo.baz)" + (expect (clojure-find-ns) :to-equal "foo.baz")) + (with-clojure-buffer "(ns ^{} foo)" + (expect (clojure-find-ns) :to-equal "foo")) + (with-clojure-buffer "(ns ^{:skip-wiki true} + aleph.netty" + (expect (clojure-find-ns) :to-equal "aleph.netty")) + (with-clojure-buffer "(ns ^{:foo {:bar :baz} :fake (ns in.meta)} foo + \"docstring +(ns misleading)\")" + (expect (clojure-find-ns) :to-equal "foo"))) + (it "should support non-alphanumeric characters" + (with-clojure-buffer "(ns foo+)" + (expect (clojure-find-ns) :to-equal "foo+")) + (with-clojure-buffer "(ns bar**baz$-_quux)" + (expect (clojure-find-ns) :to-equal "bar**baz$-_quux"))) + (it "should support in-ns forms" + (with-clojure-buffer "(in-ns 'bar.baz)" + (expect (clojure-find-ns) :to-equal "bar.baz"))) + (it "should take the closest ns before point" + (with-clojure-buffer " (ns foo1) + +(ns foo2)" + (expect (clojure-find-ns) :to-equal "foo2")) + (with-clojure-buffer " (in-ns foo1) +(ns 'foo2) +(in-ns 'foo3) +| +(ns foo4)" + (re-search-backward "|") + (expect (clojure-find-ns) :to-equal "foo3")))) (describe "clojure-sort-ns" (it "should sort requires in a basic ns" From fa3389a811e3cbcbd2513694a86a804e61b0819d Mon Sep 17 00:00:00 2001 From: Kyle Cesare Date: Sat, 21 Mar 2020 01:29:13 -0700 Subject: [PATCH 149/379] [Fix #551] Indent `clojure-align` region before aligning (#552) This prevents issues when aligning improperly indented code. Before, the region would be aligned while retaining the improper indentation. The follow-up indentation step would then break the alignment that was just performed. With this commit, we switch the order by indenting first then aligning. This retains compatibility with the test cases in #360. --- clojure-mode-indentation-test.el | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/clojure-mode-indentation-test.el b/clojure-mode-indentation-test.el index cb37941..abbb350 100644 --- a/clojure-mode-indentation-test.el +++ b/clojure-mode-indentation-test.el @@ -659,6 +659,13 @@ x "#?@(:clj [2] :cljs [2])") + (it "should handle improperly indented content" + (let ((content "(let [a-long-name 10\nb 20])") + (aligned-content "(let [a-long-name 10\n b 20])")) + (with-clojure-buffer content + (call-interactively #'clojure-align) + (expect (buffer-string) :to-equal aligned-content)))) + (it "should not align reader conditionals by default" (let ((content "#?(:clj 2\n :cljs 2)")) (with-clojure-buffer content From c85c16fa246f3aa9bf96378133cb530fff1698d1 Mon Sep 17 00:00:00 2001 From: yuhan0 Date: Sat, 21 Mar 2020 13:40:03 +0800 Subject: [PATCH 150/379] Fix ns detection with namespaces containing numbers --- clojure-mode-util-test.el | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/clojure-mode-util-test.el b/clojure-mode-util-test.el index 8d1c059..ad843a5 100644 --- a/clojure-mode-util-test.el +++ b/clojure-mode-util-test.el @@ -92,7 +92,9 @@ (with-clojure-buffer "(ns foo+)" (expect (clojure-find-ns) :to-equal "foo+")) (with-clojure-buffer "(ns bar**baz$-_quux)" - (expect (clojure-find-ns) :to-equal "bar**baz$-_quux"))) + (expect (clojure-find-ns) :to-equal "bar**baz$-_quux")) + (with-clojure-buffer "(ns aoc-2019.puzzles.day14)" + (expect (clojure-find-ns) :to-equal "aoc-2019.puzzles.day14"))) (it "should support in-ns forms" (with-clojure-buffer "(in-ns 'bar.baz)" (expect (clojure-find-ns) :to-equal "bar.baz"))) From 7b1e9a248123facadb6a9be24f0e32e305a36904 Mon Sep 17 00:00:00 2001 From: yuhan0 Date: Fri, 20 Mar 2020 18:42:40 +0800 Subject: [PATCH 151/379] Add tests for new rename-ns-alias functionality --- clojure-mode-refactor-rename-ns-alias-test.el | 32 +++++++++++++++++-- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/clojure-mode-refactor-rename-ns-alias-test.el b/clojure-mode-refactor-rename-ns-alias-test.el index aba75b3..2f68f44 100644 --- a/clojure-mode-refactor-rename-ns-alias-test.el +++ b/clojure-mode-refactor-rename-ns-alias-test.el @@ -95,8 +95,34 @@ ;; TODO refactor using new-lib/foo (+ (new-lib/a 1) (b 2))" - (clojure--rename-ns-alias-internal "lib" "new-lib"))) - - (provide 'clojure-mode-refactor-rename-ns-alias-test) + (clojure--rename-ns-alias-internal "lib" "new-lib")) + + (when-refactoring-it "should escape regex characters" + "(ns test.ns + (:require [my.math.subtraction :as math.-] + [my.math.multiplication :as math.*])) + +(math.*/operator 1 (math.-/subtract 2 3))" + "(ns test.ns + (:require [my.math.subtraction :as math.-] + [my.math.multiplication :as m*])) + +(m*/operator 1 (math.-/subtract 2 3))" + (clojure--rename-ns-alias-internal "math.*" "m*")) + + (it "should offer completions" + (expect + (clojure-collect-ns-aliases + "(ns test.ns + (:require [my.math.subtraction :as math.-] + [my.math.multiplication :as math.*] + [clojure.spec.alpha :as s] + ;; [clojure.spec.alpha2 :as s2] + [symbols :as abc123.-$#.%*+!@])) + +(math.*/operator 1 (math.-/subtract 2 3))") + :to-equal '("abc123.-$#.%*+!@" "s" "math.*" "math.-")))) + +(provide 'clojure-mode-refactor-rename-ns-alias-test) ;;; clojure-mode-refactor-rename-ns-alias-test.el ends here From d39d5d58b008743b5ed7b0a80b8f55c93ec087d4 Mon Sep 17 00:00:00 2001 From: yuhan0 Date: Tue, 24 Mar 2020 01:57:31 +0800 Subject: [PATCH 152/379] Fix bug when renaming ns aliases with common prefixes --- clojure-mode-refactor-rename-ns-alias-test.el | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/clojure-mode-refactor-rename-ns-alias-test.el b/clojure-mode-refactor-rename-ns-alias-test.el index 2f68f44..d5d99cb 100644 --- a/clojure-mode-refactor-rename-ns-alias-test.el +++ b/clojure-mode-refactor-rename-ns-alias-test.el @@ -43,6 +43,23 @@ (+ (foo/a 1) (b 2))" (clojure--rename-ns-alias-internal "lib" "foo")) + (when-refactoring-it "should handle multiple aliases with common prefixes" + + "(ns foo + (:require [clojure.string :as string] + [clojure.spec.alpha :as s] + [clojure.java.shell :as shell])) + +(s/def ::abc string/blank?) +" + "(ns foo + (:require [clojure.string :as string] + [clojure.spec.alpha :as spec] + [clojure.java.shell :as shell])) + +(spec/def ::abc string/blank?) +" + (clojure--rename-ns-alias-internal "s" "spec")) (when-refactoring-it "should handle ns declarations with missing as" "(ns cljr.core From 8261674e5b76e794bb12b1899721c1d0e48e123e Mon Sep 17 00:00:00 2001 From: yuhan0 Date: Thu, 26 Mar 2020 22:44:16 +0800 Subject: [PATCH 153/379] [Fix #547] Fix character literal regexp (#560) --- clojure-mode-font-lock-test.el | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/clojure-mode-font-lock-test.el b/clojure-mode-font-lock-test.el index a45e3de..23dade2 100644 --- a/clojure-mode-font-lock-test.el +++ b/clojure-mode-font-lock-test.el @@ -884,15 +884,30 @@ DESCRIPTION is the description of the spec." ("\\a" (1 2 clojure-character-face)) + ("\\A" + (1 2 clojure-character-face)) + ("\\newline" (1 8 clojure-character-face)) + ("\\abc" + (1 4 nil)) + + ("\\newlin" + (1 7 nil)) + + ("\\newlinex" + (1 9 nil)) + ("\\1" (1 2 clojure-character-face)) ("\\u0032" (1 6 clojure-character-face)) + ("\\o127" + (1 4 clojure-character-face)) + ("\\+" (1 2 clojure-character-face)) @@ -903,6 +918,12 @@ DESCRIPTION is the description of the spec." (1 2 clojure-character-face)) ("\\;" + (1 2 clojure-character-face)) + + ("\\Ω" + (1 2 clojure-character-face)) + + ("\\ク" (1 2 clojure-character-face))) (when-fontifying-it "should handle referred vars" From 7b06524b40f1a91b2d7b597cd810639289a20b1a Mon Sep 17 00:00:00 2001 From: yuhan0 Date: Thu, 26 Mar 2020 18:45:57 +0800 Subject: [PATCH 154/379] Add test for quotes in tail of symbols/keywords --- clojure-mode-font-lock-test.el | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/clojure-mode-font-lock-test.el b/clojure-mode-font-lock-test.el index 23dade2..3cd9604 100644 --- a/clojure-mode-font-lock-test.el +++ b/clojure-mode-font-lock-test.el @@ -417,6 +417,16 @@ DESCRIPTION is the description of the spec." (9 10 nil) (12 29 nil))) + (when-fontifying-it "should handle quotes in tail of symbols and keywords" + ("'quot'ed'/sy'm'bol''" + (2 9 font-lock-type-face) + (10 20 nil)) + + (":qu'ote'd''/key'word'" + (2 11 font-lock-type-face) + (12 12 default) + (13 21 clojure-keyword-face))) + (when-fontifying-it "should handle very complex stuff" (" ve/yCom|pLex.stu-ff" (3 4 font-lock-type-face) From 25d8595237e29622aa77541b9c1888832771aafc Mon Sep 17 00:00:00 2001 From: yuhan0 Date: Tue, 24 Mar 2020 01:42:03 +0800 Subject: [PATCH 155/379] Declare indentation spec for test util functions And auto-indent some lines --- clojure-mode-font-lock-test.el | 9 +++++---- utils/test-helper.el | 2 ++ 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/clojure-mode-font-lock-test.el b/clojure-mode-font-lock-test.el index 3cd9604..ecf5ac8 100644 --- a/clojure-mode-font-lock-test.el +++ b/clojure-mode-font-lock-test.el @@ -77,6 +77,7 @@ TESTS are lists of the form (content (start end expected-face)*). For each test check that each `expected-face` is found in `content` between `start` and `end`. DESCRIPTION is the description of the spec." + (declare (indent 1)) `(it ,description (dolist (test (quote ,tests)) (apply #'expect-faces-at test)))) @@ -797,8 +798,8 @@ DESCRIPTION is the description of the spec." ("(def foo \"usage\" \"hello\" )" (18 24 font-lock-string-face)) - ("(def foo \"usage\" \n \"hello\")" - (21 27 font-lock-string-face)) + ("(def foo \"usage\" \n \"hello\")" + (21 27 font-lock-string-face)) ("(def foo \n \"usage\" \"hello\")" (13 19 font-lock-doc-face)) @@ -864,8 +865,8 @@ DESCRIPTION is the description of the spec." (when-fontifying-it "should handle keyword-meta" ("^:meta-data" - (1 1 nil) - (2 11 clojure-keyword-face))) + (1 1 nil) + (2 11 clojure-keyword-face))) (when-fontifying-it "should handle a keyword with allowed characters" (":aaa#bbb" diff --git a/utils/test-helper.el b/utils/test-helper.el index 9076312..2cb2071 100644 --- a/utils/test-helper.el +++ b/utils/test-helper.el @@ -34,6 +34,7 @@ (defmacro with-clojure-buffer (text &rest body) "Create a temporary buffer, insert TEXT, switch to clojure-mode and evaluate BODY." + (declare (indent 1)) `(with-temp-buffer (erase-buffer) (insert ,text) @@ -49,6 +50,7 @@ AFTER. BODY should contain the refactoring that transforms BEFORE into AFTER. DESCRIPTION is the description of the spec." + (declare (indent 1)) `(it ,description (with-clojure-buffer ,before ,@body From d489b2704f1bb766b995de7aacea43f1b801a2a0 Mon Sep 17 00:00:00 2001 From: yuhan0 Date: Sat, 4 Apr 2020 01:22:24 +0800 Subject: [PATCH 156/379] Add test and changelog entry --- clojure-mode-font-lock-test.el | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/clojure-mode-font-lock-test.el b/clojure-mode-font-lock-test.el index ecf5ac8..5baa555 100644 --- a/clojure-mode-font-lock-test.el +++ b/clojure-mode-font-lock-test.el @@ -806,7 +806,12 @@ DESCRIPTION is the description of the spec." ("(def foo \n \"usage\" \n \"hello\")" (13 19 font-lock-doc-face) - (24 30 font-lock-string-face))) + (24 30 font-lock-string-face)) + + ("(def test-string\n \"this\\n\n is\n my\n string\")" + (20 24 font-lock-string-face) + (25 26 (bold font-lock-string-face)) + (27 46 font-lock-string-face))) (when-fontifying-it "should handle deftype" ("(deftype Foo)" From fb436f6cc67e8f1719525ec4d8183f3b75223491 Mon Sep 17 00:00:00 2001 From: yuhan0 Date: Sun, 5 Apr 2020 22:05:09 +0800 Subject: [PATCH 157/379] Fix ns detection bug The new regex was missing symbol-end, causing it to match (ns-... functions --- clojure-mode-util-test.el | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/clojure-mode-util-test.el b/clojure-mode-util-test.el index ad843a5..27ce4c5 100644 --- a/clojure-mode-util-test.el +++ b/clojure-mode-util-test.el @@ -109,7 +109,11 @@ | (ns foo4)" (re-search-backward "|") - (expect (clojure-find-ns) :to-equal "foo3")))) + (expect (clojure-find-ns) :to-equal "foo3")) + (with-clojure-buffer "(ns foo) +(ns-unmap *ns* 'map) +(ns.misleading 1 2 3)" + (expect (clojure-find-ns) :to-equal "foo")))) (describe "clojure-sort-ns" (it "should sort requires in a basic ns" From e9c328a33a8839e1bb193a66780703d43b5afc90 Mon Sep 17 00:00:00 2001 From: yuhan0 Date: Sat, 18 Apr 2020 15:04:55 +0800 Subject: [PATCH 158/379] Add tests for paredit interaction --- clojure-mode-external-interaction-test.el | 90 +++++++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 clojure-mode-external-interaction-test.el diff --git a/clojure-mode-external-interaction-test.el b/clojure-mode-external-interaction-test.el new file mode 100644 index 0000000..5e3d854 --- /dev/null +++ b/clojure-mode-external-interaction-test.el @@ -0,0 +1,90 @@ +;;; clojure-mode-external-interaction-test.el --- Clojure Mode interactions with external packages test suite -*- lexical-binding: t; -*- + +;; Copyright (C) 2014-2020 Bozhidar Batsov + +;; This file is not part of GNU Emacs. + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;;; Code: + +(require 'clojure-mode) +(require 'buttercup) +(require 'paredit) + +(describe "Interactions with Paredit" + ;; reuse existing when-refactoring-it macro + (describe "it should insert a space" + (when-refactoring-it "before lists" + "foo" + "foo ()" + (paredit-mode) + (paredit-open-round)) + (when-refactoring-it "before vectors" + "foo" + "foo []" + (paredit-mode) + (paredit-open-square)) + (when-refactoring-it "before maps" + "foo" + "foo {}" + (paredit-mode) + (paredit-open-curly)) + (when-refactoring-it "before strings" + "foo" + "foo \"\"" + (paredit-mode) + (paredit-doublequote)) + (when-refactoring-it "after gensym" + "foo#" + "foo# ()" + (paredit-mode) + (paredit-open-round)) + (when-refactoring-it "after symbols ending with '" + "foo'" + "foo' ()" + (paredit-mode) + (paredit-open-round))) + (describe "should not insert a space" + (when-refactoring-it "for anonymous fn syntax" + "foo #" + "foo #()" + (paredit-mode) + (paredit-open-round)) + (when-refactoring-it "for hash sets" + "foo #" + "foo #{}" + (paredit-mode) + (paredit-open-curly)) + (when-refactoring-it "for regexes" + "foo #" + "foo #\"\"" + (paredit-mode) + (paredit-doublequote)) + (when-refactoring-it "for quoted collections" + "foo '" + "foo '()" + (paredit-mode) + (paredit-open-round)) + (when-refactoring-it "for reader conditionals" + "foo #?" + "foo #?()" + (paredit-mode) + (paredit-open-round)))) + + +(provide 'clojure-mode-external-interaction-test) + + +;;; clojure-mode-external-interaction-test.el ends here From 61f10885779a1815a9584b7c25c5ad1635a4bb7e Mon Sep 17 00:00:00 2001 From: yuhan0 Date: Sat, 18 Apr 2020 15:05:41 +0800 Subject: [PATCH 159/379] Add delete-trailing-whitespace tests --- clojure-mode-external-interaction-test.el | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/clojure-mode-external-interaction-test.el b/clojure-mode-external-interaction-test.el index 5e3d854..41b1aeb 100644 --- a/clojure-mode-external-interaction-test.el +++ b/clojure-mode-external-interaction-test.el @@ -84,6 +84,24 @@ (paredit-open-round)))) +(describe "Interactions with delete-trailing-whitespace" + (when-refactoring-it "should not delete trailing commas" + "(def foo + \\\"foo\\\": 1, + \\\"bar\\\": 2} + +(-> m + (assoc ,,, + :foo 123))" + "(def foo + \\\"foo\\\": 1, + \\\"bar\\\": 2} + +(-> m + (assoc ,,, + :foo 123))" + (delete-trailing-whitespace))) + (provide 'clojure-mode-external-interaction-test) From 234cadedf594000d3b29a43bc0fa58a13c212170 Mon Sep 17 00:00:00 2001 From: yuhan0 Date: Sat, 18 Apr 2020 16:34:33 +0800 Subject: [PATCH 160/379] Refactor clojure-no-space-after-tag Consolidate into single predicate, make collection-tag-regexp obsolete --- clojure-mode-syntax-test.el | 9 --------- 1 file changed, 9 deletions(-) diff --git a/clojure-mode-syntax-test.el b/clojure-mode-syntax-test.el index af00c1a..f3699f6 100644 --- a/clojure-mode-syntax-test.el +++ b/clojure-mode-syntax-test.el @@ -75,15 +75,6 @@ (backward-prefix-chars) (expect (bobp)))))) -(describe "clojure-no-space-after-tag" - (it "should allow allow collection tags" - (dolist (tag '("#::ns" "#:ns" "#ns" "#:f.q/ns" "#f.q/ns" "#::")) - (with-clojure-buffer tag - (expect (clojure-no-space-after-tag nil ?{) :to-be nil))) - (dolist (tag '("#$:" "#/f" "#:/f" "#::f.q/ns" "::ns" "::" "#f:ns")) - (with-clojure-buffer tag - (expect (clojure-no-space-after-tag nil ?{)))))) - (describe "fill-paragraph" (it "should work within comments" From 04267d8230680d64da07d0e94b5cf87a0a34f63e Mon Sep 17 00:00:00 2001 From: yuhan0 Date: Sat, 18 Apr 2020 16:52:01 +0800 Subject: [PATCH 161/379] Add tests for paredit reader tags --- clojure-mode-external-interaction-test.el | 32 ++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/clojure-mode-external-interaction-test.el b/clojure-mode-external-interaction-test.el index 41b1aeb..8451abe 100644 --- a/clojure-mode-external-interaction-test.el +++ b/clojure-mode-external-interaction-test.el @@ -23,7 +23,7 @@ (require 'buttercup) (require 'paredit) -(describe "Interactions with Paredit" +(describe "Interactions with Paredit:" ;; reuse existing when-refactoring-it macro (describe "it should insert a space" (when-refactoring-it "before lists" @@ -56,7 +56,7 @@ "foo' ()" (paredit-mode) (paredit-open-round))) - (describe "should not insert a space" + (describe "it should not insert a space" (when-refactoring-it "for anonymous fn syntax" "foo #" "foo #()" @@ -81,7 +81,33 @@ "foo #?" "foo #?()" (paredit-mode) - (paredit-open-round)))) + (paredit-open-round))) + (describe "reader tags" + (when-refactoring-it "should insert a space before strings" + "#uuid" + "#uuid \"\"" + (paredit-mode) + (paredit-doublequote)) + (when-refactoring-it "should not insert a space before namespaced maps" + "#::my-ns" + "#::my-ns{}" + (paredit-mode) + (paredit-open-curly)) + (when-refactoring-it "should not insert a space before namespaced maps 2" + "#::" + "#::{}" + (paredit-mode) + (paredit-open-curly)) + (when-refactoring-it "should not insert a space before namespaced maps 3" + "#:fully.qualified.ns123.-$#.%*+!" + "#:fully.qualified.ns123.-$#.%*+!{}" + (paredit-mode) + (paredit-open-curly)) + (when-refactoring-it "should not insert a space before tagged vectors" + "#tag123.-$#.%*+!" + "#tag123.-$#.%*+![]" + (paredit-mode) + (paredit-open-square)))) (describe "Interactions with delete-trailing-whitespace" From 2fa5aae310f95b8776be9bb1e800336df81d6cb6 Mon Sep 17 00:00:00 2001 From: yuhan0 Date: Tue, 23 Feb 2021 02:20:45 +0800 Subject: [PATCH 162/379] Move test macros to utils file --- clojure-mode-refactor-add-arity-test.el | 25 ---------------- clojure-mode-sexp-test.el | 13 --------- utils/test-helper.el | 38 +++++++++++++++++++++++++ 3 files changed, 38 insertions(+), 38 deletions(-) diff --git a/clojure-mode-refactor-add-arity-test.el b/clojure-mode-refactor-add-arity-test.el index 2b3cbc5..e4deefc 100644 --- a/clojure-mode-refactor-add-arity-test.el +++ b/clojure-mode-refactor-add-arity-test.el @@ -25,31 +25,6 @@ (require 'clojure-mode) (require 'buttercup) -(defmacro when-refactoring-with-point-it (description before after &rest body) - "Return a buttercup spec. - -Like when-refactor-it but also checks whether point is moved to the expected -position. - -BEFORE is the buffer string before refactoring, where a pipe (|) represents -point. - -AFTER is the expected buffer string after refactoring, where a pipe (|) -represents the expected position of point. - -DESCRIPTION is a string with the description of the spec." - `(it ,description - (let* ((after ,after) - (expected-cursor-pos (1+ (s-index-of "|" after))) - (expected-state (delete ?| after))) - (with-clojure-buffer ,before - (goto-char (point-min)) - (search-forward "|") - (delete-char -1) - ,@body - (expect (buffer-string) :to-equal expected-state) - (expect (point) :to-equal expected-cursor-pos))))) - (describe "clojure-add-arity" (when-refactoring-with-point-it "should add an arity to a single-arity defn with args on same line" diff --git a/clojure-mode-sexp-test.el b/clojure-mode-sexp-test.el index da0c08f..f9c15a3 100644 --- a/clojure-mode-sexp-test.el +++ b/clojure-mode-sexp-test.el @@ -22,19 +22,6 @@ (require 'clojure-mode) (require 'buttercup) -(defmacro with-clojure-buffer-point (text &rest body) - "Run BODY in a temporary clojure buffer with TEXT. - -TEXT is a string with a | indicating where point is. The | will be erased -and point left there." - (declare (indent 2)) - `(progn - (with-clojure-buffer ,text - (goto-char (point-min)) - (re-search-forward "|") - (delete-char -1) - ,@body))) - (describe "clojure-top-level-form-p" (it "should return true when passed the correct form" (with-clojure-buffer-point diff --git a/utils/test-helper.el b/utils/test-helper.el index 2cb2071..f15b285 100644 --- a/utils/test-helper.el +++ b/utils/test-helper.el @@ -41,6 +41,19 @@ (clojure-mode) ,@body)) +(defmacro with-clojure-buffer-point (text &rest body) + "Run BODY in a temporary clojure buffer with TEXT. + +TEXT is a string with a | indicating where point is. The | will be erased +and point left there." + (declare (indent 2)) + `(progn + (with-clojure-buffer ,text + (goto-char (point-min)) + (re-search-forward "|") + (delete-char -1) + ,@body))) + (defmacro when-refactoring-it (description before after &rest body) "Return a buttercup spec. @@ -56,4 +69,29 @@ DESCRIPTION is the description of the spec." ,@body (expect (buffer-string) :to-equal ,after)))) +(defmacro when-refactoring-with-point-it (description before after &rest body) + "Return a buttercup spec. + +Like when-refactor-it but also checks whether point is moved to the expected +position. + +BEFORE is the buffer string before refactoring, where a pipe (|) represents +point. + +AFTER is the expected buffer string after refactoring, where a pipe (|) +represents the expected position of point. + +DESCRIPTION is a string with the description of the spec." + `(it ,description + (let* ((after ,after) + (expected-cursor-pos (1+ (s-index-of "|" after))) + (expected-state (delete ?| after))) + (with-clojure-buffer ,before + (goto-char (point-min)) + (search-forward "|") + (delete-char -1) + ,@body + (expect (buffer-string) :to-equal expected-state) + (expect (point) :to-equal expected-cursor-pos))))) + ;;; test-helper.el ends here From 8f08712ee5c41b6ad7ecc1e32f38f41b227980ef Mon Sep 17 00:00:00 2001 From: yuhan0 Date: Tue, 23 Feb 2021 02:22:06 +0800 Subject: [PATCH 163/379] add indent spec to when-refactoring-with-point-it --- utils/test-helper.el | 1 + 1 file changed, 1 insertion(+) diff --git a/utils/test-helper.el b/utils/test-helper.el index f15b285..756def3 100644 --- a/utils/test-helper.el +++ b/utils/test-helper.el @@ -82,6 +82,7 @@ AFTER is the expected buffer string after refactoring, where a pipe (|) represents the expected position of point. DESCRIPTION is a string with the description of the spec." + (declare (indent 1)) `(it ,description (let* ((after ,after) (expected-cursor-pos (1+ (s-index-of "|" after))) From ef047846f968b6ecfb15681720718caed248e1f9 Mon Sep 17 00:00:00 2001 From: yuhan0 Date: Tue, 23 Feb 2021 04:51:28 +0800 Subject: [PATCH 164/379] Add tests for toggling ignore forms --- clojure-mode-util-test.el | 87 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 86 insertions(+), 1 deletion(-) diff --git a/clojure-mode-util-test.el b/clojure-mode-util-test.el index 27ce4c5..4a0bbbb 100644 --- a/clojure-mode-util-test.el +++ b/clojure-mode-util-test.el @@ -139,7 +139,7 @@ (clojure-mode) (clojure-sort-ns) (expect (buffer-string) :to-equal - "\n(ns my-app.core + "\n(ns my-app.core (:require [my-app.state :refer [state]] ; Comments too. my-app.util.api [my-app.views [front-page :as front-page]] @@ -149,6 +149,91 @@ (:import [clojure.lang AFunction Atom MultiFn Namespace] java.io.Writer))")))) +(describe "clojure-toggle-ignore" + (when-refactoring-with-point-it "should add #_ to literals" + "[1 |2 3]" "[1 #_|2 3]" + (clojure-toggle-ignore)) + (when-refactoring-with-point-it "should work with point in middle of symbol" + "[foo b|ar baz]" "[foo #_b|ar baz]" + (clojure-toggle-ignore)) + (when-refactoring-with-point-it "should remove #_ after cursor" + "[1 |#_2 3]" "[1 |2 3]" + (clojure-toggle-ignore)) + (when-refactoring-with-point-it "should remove #_ before cursor" + "[#_:fo|o :bar :baz]" "[:fo|o :bar :baz]" + (clojure-toggle-ignore)) + (when-refactoring-with-point-it "should insert multiple #_" + "{:foo| 1 :bar 2 :baz 3}" + "{#_#_#_#_:foo| 1 :bar 2 :baz 3}" + (clojure-toggle-ignore 4)) + (when-refactoring-with-point-it "should remove multiple #_" + "{#_#_#_#_:foo| 1 :bar 2 :baz 3}" + "{#_#_:foo| 1 :bar 2 :baz 3}" + (clojure-toggle-ignore 2)) + (when-refactoring-with-point-it "should handle spaces and newlines" + "[foo #_ \n #_ \r\n b|ar baz]" "[foo b|ar baz]" + (clojure-toggle-ignore 2)) + (when-refactoring-with-point-it "should toggle entire string" + "[:div \"lorem ips|um text\"]" + "[:div #_\"lorem ips|um text\"]" + (clojure-toggle-ignore)) + (when-refactoring-with-point-it "should toggle regexps" + "[|#\".*\"]" + "[#_|#\".*\"]" + (clojure-toggle-ignore)) + (when-refactoring-with-point-it "should toggle collections" + "[foo |[bar baz]]" + "[foo #_|[bar baz]]" + (clojure-toggle-ignore)) + (when-refactoring-with-point-it "should toggle hash sets" + "[foo #|{bar baz}]" + "[foo #_#|{bar baz}]" + (clojure-toggle-ignore)) + (when-refactoring-with-point-it "should work on last-sexp" + "[foo '(bar baz)| quux]" + "[foo #_'(bar baz)| quux]" + (clojure-toggle-ignore)) + (when-refactoring-with-point-it "should insert newline before top-level form" + "|[foo bar baz]" + "#_ +|[foo bar baz]" + (clojure-toggle-ignore))) + +(describe "clojure-toggle-ignore-surrounding-form" + (when-refactoring-with-point-it "should toggle lists" + "(li|st [vector {map #{set}}])" + "#_\n(li|st [vector {map #{set}}])" + (clojure-toggle-ignore-surrounding-form)) + (when-refactoring-with-point-it "should toggle vectors" + "(list #_[vector| {map #{set}}])" + "(list [vector| {map #{set}}])" + (clojure-toggle-ignore-surrounding-form)) + (when-refactoring-with-point-it "should toggle maps" + "(list [vector #_ \n {map #{set}|}])" + "(list [vector {map #{set}|}])" + (clojure-toggle-ignore-surrounding-form)) + (when-refactoring-with-point-it "should toggle sets" + "(list [vector {map #{set|}}])" + "(list [vector {map #_#{set|}}])" + (clojure-toggle-ignore-surrounding-form)) + (when-refactoring-with-point-it "should work with numeric arg" + "(four (three (two (on|e)))" + "(four (three #_(two (on|e)))" + (clojure-toggle-ignore-surrounding-form 2)) + (when-refactoring-with-point-it "should remove #_ with numeric arg" + "(four #_(three (two (on|e)))" + "(four (three (two (on|e)))" + (clojure-toggle-ignore-surrounding-form 3))) + +(describe "clojure-toggle-ignore-defun" + (when-refactoring-with-point-it "should ignore defun with newline" + "(defn foo [x] + {:nested (in|c x)})" + "#_ +(defn foo [x] + {:nested (in|c x)})" + (clojure-toggle-ignore-defun))) + (provide 'clojure-mode-util-test) ;;; clojure-mode-util-test.el ends here From 214bf02d3e0fab52dd0c014fa1e56d099aba442e Mon Sep 17 00:00:00 2001 From: Hugo Duncan Date: Mon, 1 Mar 2021 14:41:43 -0500 Subject: [PATCH 165/379] Make indentation of special arguments customisable (#582) Allow control of the indentation of special arguments through a defcustom. - introduces clojure-special-arg-indent-factor, which is used as a factor of lisp-body-ident to indent special arguments. Defaults to 2, the currently hard coded value for this. --- clojure-mode-indentation-test.el | 45 ++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/clojure-mode-indentation-test.el b/clojure-mode-indentation-test.el index abbb350..b908172 100644 --- a/clojure-mode-indentation-test.el +++ b/clojure-mode-indentation-test.el @@ -233,6 +233,51 @@ DESCRIPTION is a string with the description of the spec." (ala/bala top |one)")) + (describe "specify an indentation for symbol" + (put-clojure-indent 'cala 1) + + (when-indenting-with-point-it "should handle a symbol with ns" + " + (cala top + |one)" + " + (cala top + |one)") + (when-indenting-with-point-it "should handle special arguments" + " + (cala + |top + one)" + " + (cala + |top + one)")) + (describe "should respect special argument indentation" + :var (clojure-special-arg-indent-factor) + (before-each + (setq clojure-special-arg-indent-factor 1)) + (after-each + (setq clojure-special-arg-indent-factor 2)) + + (put-clojure-indent 'cala 1) + + (when-indenting-with-point-it "should handle a symbol with ns" + " + (cala top + |one)" + " + (cala top + |one)") + (when-indenting-with-point-it "should handle special arguments" + " + (cala + |top + one)" + " + (cala + |top + one)")) + (describe "we can pass a lambda to explicitly set the column" (put-clojure-indent 'arsymbol (lambda (indent-point state) 0)) From a3c442a82c9ca6a88df370239148d1d4da5598de Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Mon, 22 Mar 2021 09:04:25 +0200 Subject: [PATCH 166/379] Bump the copyright years --- clojure-mode-bytecomp-warnings.el | 2 +- clojure-mode-convert-collection-test.el | 2 +- clojure-mode-cycling-test.el | 2 +- clojure-mode-external-interaction-test.el | 2 +- clojure-mode-font-lock-test.el | 2 +- clojure-mode-indentation-test.el | 2 +- clojure-mode-refactor-let-test.el | 2 +- clojure-mode-refactor-threading-test.el | 2 +- clojure-mode-sexp-test.el | 2 +- clojure-mode-syntax-test.el | 2 +- clojure-mode-util-test.el | 2 +- utils/test-helper.el | 2 +- 12 files changed, 12 insertions(+), 12 deletions(-) diff --git a/clojure-mode-bytecomp-warnings.el b/clojure-mode-bytecomp-warnings.el index 7c65dda..d3cc19a 100644 --- a/clojure-mode-bytecomp-warnings.el +++ b/clojure-mode-bytecomp-warnings.el @@ -1,6 +1,6 @@ ;;; clojure-mode-bytecomp-warnings.el --- Check for byte-compilation problems -;; Copyright © 2012-2020 Bozhidar Batsov and contributors +;; Copyright © 2012-2021 Bozhidar Batsov and contributors ;; ;; This program is free software: you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by diff --git a/clojure-mode-convert-collection-test.el b/clojure-mode-convert-collection-test.el index 34d6b12..cd28f51 100644 --- a/clojure-mode-convert-collection-test.el +++ b/clojure-mode-convert-collection-test.el @@ -1,6 +1,6 @@ ;;; clojure-mode-convert-collection-test.el --- Clojure Mode: convert collection type -*- lexical-binding: t; -*- -;; Copyright (C) 2016-2020 Benedek Fazekas +;; Copyright (C) 2016-2021 Benedek Fazekas ;; This file is not part of GNU Emacs. diff --git a/clojure-mode-cycling-test.el b/clojure-mode-cycling-test.el index f4d6297..afc2476 100644 --- a/clojure-mode-cycling-test.el +++ b/clojure-mode-cycling-test.el @@ -1,6 +1,6 @@ ;;; clojure-mode-cycling-test.el --- Clojure Mode: cycling things tests -*- lexical-binding: t; -*- -;; Copyright (C) 2016-2020 Benedek Fazekas +;; Copyright (C) 2016-2021 Benedek Fazekas ;; This file is not part of GNU Emacs. diff --git a/clojure-mode-external-interaction-test.el b/clojure-mode-external-interaction-test.el index 8451abe..f6dba50 100644 --- a/clojure-mode-external-interaction-test.el +++ b/clojure-mode-external-interaction-test.el @@ -1,6 +1,6 @@ ;;; clojure-mode-external-interaction-test.el --- Clojure Mode interactions with external packages test suite -*- lexical-binding: t; -*- -;; Copyright (C) 2014-2020 Bozhidar Batsov +;; Copyright (C) 2014-2021 Bozhidar Batsov ;; This file is not part of GNU Emacs. diff --git a/clojure-mode-font-lock-test.el b/clojure-mode-font-lock-test.el index 5baa555..c626122 100644 --- a/clojure-mode-font-lock-test.el +++ b/clojure-mode-font-lock-test.el @@ -1,7 +1,7 @@ ;;; clojure-mode-font-lock-test.el --- Clojure Mode: Font lock test suite ;; -*- lexical-binding: t; -*- -;; Copyright (C) 2014-2020 Bozhidar Batsov +;; Copyright (C) 2014-2021 Bozhidar Batsov ;; This file is not part of GNU Emacs. diff --git a/clojure-mode-indentation-test.el b/clojure-mode-indentation-test.el index b908172..61e4867 100644 --- a/clojure-mode-indentation-test.el +++ b/clojure-mode-indentation-test.el @@ -1,6 +1,6 @@ ;;; clojure-mode-indentation-test.el --- Clojure Mode: indentation tests -*- lexical-binding: t; -*- -;; Copyright (C) 2015-2020 Bozhidar Batsov +;; Copyright (C) 2015-2021 Bozhidar Batsov ;; This file is not part of GNU Emacs. diff --git a/clojure-mode-refactor-let-test.el b/clojure-mode-refactor-let-test.el index 0e178f7..196db79 100644 --- a/clojure-mode-refactor-let-test.el +++ b/clojure-mode-refactor-let-test.el @@ -1,6 +1,6 @@ ;;; clojure-mode-refactor-let-test.el --- Clojure Mode: refactor let -*- lexical-binding: t; -*- -;; Copyright (C) 2016-2020 Benedek Fazekas +;; Copyright (C) 2016-2021 Benedek Fazekas ;; This file is not part of GNU Emacs. diff --git a/clojure-mode-refactor-threading-test.el b/clojure-mode-refactor-threading-test.el index 76e5810..65b2a78 100644 --- a/clojure-mode-refactor-threading-test.el +++ b/clojure-mode-refactor-threading-test.el @@ -1,6 +1,6 @@ ;;; clojure-mode-refactor-threading-test.el --- Clojure Mode: refactor threading tests -*- lexical-binding: t; -*- -;; Copyright (C) 2016-2020 Benedek Fazekas +;; Copyright (C) 2016-2021 Benedek Fazekas ;; This file is not part of GNU Emacs. diff --git a/clojure-mode-sexp-test.el b/clojure-mode-sexp-test.el index f9c15a3..de4bea7 100644 --- a/clojure-mode-sexp-test.el +++ b/clojure-mode-sexp-test.el @@ -1,6 +1,6 @@ ;;; clojure-mode-sexp-test.el --- Clojure Mode: sexp tests -*- lexical-binding: t; -*- -;; Copyright (C) 2015-2020 Artur Malabarba +;; Copyright (C) 2015-2021 Artur Malabarba ;; This file is not part of GNU Emacs. diff --git a/clojure-mode-syntax-test.el b/clojure-mode-syntax-test.el index f3699f6..174b93e 100644 --- a/clojure-mode-syntax-test.el +++ b/clojure-mode-syntax-test.el @@ -1,6 +1,6 @@ ;;; clojure-mode-syntax-test.el --- Clojure Mode: syntax related tests -*- lexical-binding: t; -*- -;; Copyright (C) 2015-2020 Bozhidar Batsov +;; Copyright (C) 2015-2021 Bozhidar Batsov ;; This file is not part of GNU Emacs. diff --git a/clojure-mode-util-test.el b/clojure-mode-util-test.el index 4a0bbbb..da7aec0 100644 --- a/clojure-mode-util-test.el +++ b/clojure-mode-util-test.el @@ -1,6 +1,6 @@ ;;; clojure-mode-util-test.el --- Clojure Mode: util test suite -*- lexical-binding: t; -*- -;; Copyright (C) 2014-2020 Bozhidar Batsov +;; Copyright (C) 2014-2021 Bozhidar Batsov ;; This file is not part of GNU Emacs. diff --git a/utils/test-helper.el b/utils/test-helper.el index 756def3..d822fef 100644 --- a/utils/test-helper.el +++ b/utils/test-helper.el @@ -1,6 +1,6 @@ ;;; test-helper.el --- Clojure Mode: Non-interactive unit-test setup -*- lexical-binding: t; -*- -;; Copyright (C) 2014-2020 Bozhidar Batsov +;; Copyright (C) 2014-2021 Bozhidar Batsov ;; This file is not part of GNU Emacs. From c211a27f7bbcac692547ed440b64ba3554e3cc7e Mon Sep 17 00:00:00 2001 From: yuhan0 Date: Sun, 2 May 2021 15:52:20 +0800 Subject: [PATCH 167/379] Rename ns aliases in selected region (#590) --- clojure-mode-refactor-rename-ns-alias-test.el | 40 ++++++++++++++++--- 1 file changed, 35 insertions(+), 5 deletions(-) diff --git a/clojure-mode-refactor-rename-ns-alias-test.el b/clojure-mode-refactor-rename-ns-alias-test.el index d5d99cb..37fe90c 100644 --- a/clojure-mode-refactor-rename-ns-alias-test.el +++ b/clojure-mode-refactor-rename-ns-alias-test.el @@ -127,18 +127,48 @@ (m*/operator 1 (math.-/subtract 2 3))" (clojure--rename-ns-alias-internal "math.*" "m*")) - (it "should offer completions" + (when-refactoring-it "should replace aliases in region" + "(str/join []) + +(s/with-gen #(string/includes? % \"gen/nope\") + #(gen/fmap (fn [[s1 s2]] (str s1 \"hello\" s2)) + (gen/tuple (gen/string-alphanumeric) (gen/string-alphanumeric)))) + +(gen/different-library)" + "(string/join []) + +(s/with-gen #(string/includes? % \"gen/nope\") + #(s.gen/fmap (fn [[s1 s2]] (str s1 \"hello\" s2)) + (s.gen/tuple (s.gen/string-alphanumeric) (s.gen/string-alphanumeric)))) + +(gen/different-library)" + + (clojure--rename-ns-alias-usages "str" "string" (point-min) 13) + (clojure--rename-ns-alias-usages "gen" "s.gen" (point-min) (- (point-max) 23))) + + (it "should offer completions for ns forms" (expect - (clojure-collect-ns-aliases - "(ns test.ns + (with-clojure-buffer + "(ns test.ns (:require [my.math.subtraction :as math.-] [my.math.multiplication :as math.*] [clojure.spec.alpha :as s] ;; [clojure.spec.alpha2 :as s2] [symbols :as abc123.-$#.%*+!@])) -(math.*/operator 1 (math.-/subtract 2 3))") - :to-equal '("abc123.-$#.%*+!@" "s" "math.*" "math.-")))) +(math.*/operator 1 (math.-/subtract 2 3))" + (clojure--collect-ns-aliases (point-min) (point-max) 'ns-form)) + :to-equal '("math.-" "math.*" "s" "abc123.-$#.%*+!@"))) + + (it "should offer completions for usages in region" + (expect + (with-clojure-buffer + "(s/with-gen #(string/includes? % \"hello\") + #(gen/fmap (fn [[s1 s2]] (str s1 \"hello\" s2)) + (gen/tuple (gen/string-alphanumeric) (gen/string-alphanumeric))))" + (clojure--collect-ns-aliases (point-min) (point-max) nil)) + :to-equal '("s" "string" "gen")))) + (provide 'clojure-mode-refactor-rename-ns-alias-test) From 70c1ac65d1dcbfe623979d1f1f734d34db6e5e46 Mon Sep 17 00:00:00 2001 From: Marconi Rivello Date: Mon, 22 Mar 2021 16:40:59 -0300 Subject: [PATCH 168/379] Fix for character literal font-lock (#588) When inside a vector or before white space, some escaped characters, like comma, square and curly brackets, weren't being formatted. * Added tests. * Added examples in test.clj for visual inspection. --- clojure-mode-font-lock-test.el | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/clojure-mode-font-lock-test.el b/clojure-mode-font-lock-test.el index c626122..5e2b63a 100644 --- a/clojure-mode-font-lock-test.el +++ b/clojure-mode-font-lock-test.el @@ -942,6 +942,22 @@ DESCRIPTION is the description of the spec." ("\\ク" (1 2 clojure-character-face))) + (when-fontifying-it "should handle characters not by themselves" + ("[\\,,]" + (1 1 nil) + (2 3 clojure-character-face) + (4 5 nil)) + + ("[\\[]" + (1 1 nil) + (2 3 clojure-character-face) + (4 4 nil))) + + (when-fontifying-it "should handle % character literal" + ("#(str \\% %)" + (7 8 clojure-character-face) + (10 10 font-lock-variable-name-face))) + (when-fontifying-it "should handle referred vars" ("foo/var" (1 3 font-lock-type-face)) From 347dd1f58c7bb3907b9a45dda2e7ae86786c5310 Mon Sep 17 00:00:00 2001 From: Rob Browning Date: Fri, 2 Jul 2021 13:17:39 -0500 Subject: [PATCH 169/379] Mark put-clojure-indent safe for use in .dir-locals.el, etc. Add a put-clojure-indent form validator and attach it as a safe-local-eval-function property so that safe invocations won't trigger open-file prompts when used in local variables, or when added to .dir-locals.el like this: ((clojure-mode (eval . (put-clojure-indent 'defrecord '(2 :form :form (1)))))) For now, only support specs specified as lists, not vectors. --- clojure-mode-safe-eval-test.el | 74 ++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 clojure-mode-safe-eval-test.el diff --git a/clojure-mode-safe-eval-test.el b/clojure-mode-safe-eval-test.el new file mode 100644 index 0000000..39c94ed --- /dev/null +++ b/clojure-mode-safe-eval-test.el @@ -0,0 +1,74 @@ +;;; clojure-mode-safe-eval-test.el --- Clojure Mode: safe eval test suite -*- lexical-binding: t; -*- + +;; Copyright (C) 2014-2021 Bozhidar Batsov +;; Copyright (C) 2021 Rob Browning + +;; This file is not part of GNU Emacs. + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;;; Commentary: + +;; The safe eval test suite of Clojure Mode + +;;; Code: +(require 'clojure-mode) +(require 'buttercup) + +(describe "put-clojure-indent safe-local-eval-function property" + (it "should be set to clojure--valid-put-clojure-indent-call-p" + (expect (get 'put-clojure-indent 'safe-local-eval-function) + :to-be 'clojure--valid-put-clojure-indent-call-p))) + +(describe "clojure--valid-put-clojure-indent-call-p" + (it "should approve valid forms" + (expect (clojure--valid-put-clojure-indent-call-p + '(put-clojure-indent 'foo 1))) + (expect (clojure--valid-put-clojure-indent-call-p + '(put-clojure-indent 'foo :defn))) + (expect (clojure--valid-put-clojure-indent-call-p + '(put-clojure-indent 'foo :form))) + (expect (clojure--valid-put-clojure-indent-call-p + '(put-clojure-indent 'foo '(1)))) + (expect (clojure--valid-put-clojure-indent-call-p + '(put-clojure-indent 'foo '(:defn)))) + (expect (clojure--valid-put-clojure-indent-call-p + '(put-clojure-indent 'foo '(:form)))) + (expect (clojure--valid-put-clojure-indent-call-p + '(put-clojure-indent 'foo '(1 1)))) + (expect (clojure--valid-put-clojure-indent-call-p + '(put-clojure-indent 'foo '(2 :form :form (1)))))) + (it "should reject invalid forms" + (expect (clojure--valid-put-clojure-indent-call-p + '(put-clojure-indent 1 1)) + :to-throw 'error) + (expect (clojure--valid-put-clojure-indent-call-p + '(put-clojure-indent 'foo :foo)) + :to-throw 'error) + (expect (clojure--valid-put-clojure-indent-call-p + '(put-clojure-indent 'foo (:defn))) + :to-throw 'error) + (expect (clojure--valid-put-clojure-indent-call-p + '(put-clojure-indent 'foo '(:foo))) + :to-throw 'error) + (expect (clojure--valid-put-clojure-indent-call-p + '(put-clojure-indent 'foo '(1 :foo))) + :to-throw 'error) + (expect (clojure--valid-put-clojure-indent-call-p + '(put-clojure-indent 'foo '(1 "foo"))) + :to-throw 'error))) + +(provide 'clojure-mode-safe-eval-test) + +;;; clojure-mode-safe-eval-test.el ends here From a815ab9afe42c87d33136650954d88cd12c63301 Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Wed, 10 Nov 2021 11:15:16 +0100 Subject: [PATCH 170/379] Update my email --- clojure-mode-external-interaction-test.el | 2 +- clojure-mode-font-lock-test.el | 2 +- clojure-mode-indentation-test.el | 2 +- clojure-mode-safe-eval-test.el | 2 +- clojure-mode-syntax-test.el | 2 +- clojure-mode-util-test.el | 2 +- utils/test-helper.el | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/clojure-mode-external-interaction-test.el b/clojure-mode-external-interaction-test.el index f6dba50..f8c8caf 100644 --- a/clojure-mode-external-interaction-test.el +++ b/clojure-mode-external-interaction-test.el @@ -1,6 +1,6 @@ ;;; clojure-mode-external-interaction-test.el --- Clojure Mode interactions with external packages test suite -*- lexical-binding: t; -*- -;; Copyright (C) 2014-2021 Bozhidar Batsov +;; Copyright (C) 2014-2021 Bozhidar Batsov ;; This file is not part of GNU Emacs. diff --git a/clojure-mode-font-lock-test.el b/clojure-mode-font-lock-test.el index 5e2b63a..da9cae2 100644 --- a/clojure-mode-font-lock-test.el +++ b/clojure-mode-font-lock-test.el @@ -1,7 +1,7 @@ ;;; clojure-mode-font-lock-test.el --- Clojure Mode: Font lock test suite ;; -*- lexical-binding: t; -*- -;; Copyright (C) 2014-2021 Bozhidar Batsov +;; Copyright (C) 2014-2021 Bozhidar Batsov ;; This file is not part of GNU Emacs. diff --git a/clojure-mode-indentation-test.el b/clojure-mode-indentation-test.el index 61e4867..c1c918a 100644 --- a/clojure-mode-indentation-test.el +++ b/clojure-mode-indentation-test.el @@ -1,6 +1,6 @@ ;;; clojure-mode-indentation-test.el --- Clojure Mode: indentation tests -*- lexical-binding: t; -*- -;; Copyright (C) 2015-2021 Bozhidar Batsov +;; Copyright (C) 2015-2021 Bozhidar Batsov ;; This file is not part of GNU Emacs. diff --git a/clojure-mode-safe-eval-test.el b/clojure-mode-safe-eval-test.el index 39c94ed..47b7c5b 100644 --- a/clojure-mode-safe-eval-test.el +++ b/clojure-mode-safe-eval-test.el @@ -1,6 +1,6 @@ ;;; clojure-mode-safe-eval-test.el --- Clojure Mode: safe eval test suite -*- lexical-binding: t; -*- -;; Copyright (C) 2014-2021 Bozhidar Batsov +;; Copyright (C) 2014-2021 Bozhidar Batsov ;; Copyright (C) 2021 Rob Browning ;; This file is not part of GNU Emacs. diff --git a/clojure-mode-syntax-test.el b/clojure-mode-syntax-test.el index 174b93e..2b03b04 100644 --- a/clojure-mode-syntax-test.el +++ b/clojure-mode-syntax-test.el @@ -1,6 +1,6 @@ ;;; clojure-mode-syntax-test.el --- Clojure Mode: syntax related tests -*- lexical-binding: t; -*- -;; Copyright (C) 2015-2021 Bozhidar Batsov +;; Copyright (C) 2015-2021 Bozhidar Batsov ;; This file is not part of GNU Emacs. diff --git a/clojure-mode-util-test.el b/clojure-mode-util-test.el index da7aec0..2ff86f2 100644 --- a/clojure-mode-util-test.el +++ b/clojure-mode-util-test.el @@ -1,6 +1,6 @@ ;;; clojure-mode-util-test.el --- Clojure Mode: util test suite -*- lexical-binding: t; -*- -;; Copyright (C) 2014-2021 Bozhidar Batsov +;; Copyright (C) 2014-2021 Bozhidar Batsov ;; This file is not part of GNU Emacs. diff --git a/utils/test-helper.el b/utils/test-helper.el index d822fef..a8fb38d 100644 --- a/utils/test-helper.el +++ b/utils/test-helper.el @@ -1,6 +1,6 @@ ;;; test-helper.el --- Clojure Mode: Non-interactive unit-test setup -*- lexical-binding: t; -*- -;; Copyright (C) 2014-2021 Bozhidar Batsov +;; Copyright (C) 2014-2021 Bozhidar Batsov ;; This file is not part of GNU Emacs. From 748e68504f3212998e397536d39603215c99b559 Mon Sep 17 00:00:00 2001 From: yuhan0 Date: Sat, 20 Nov 2021 03:04:01 +0800 Subject: [PATCH 171/379] Add refactoring command for converting #() shorthand to (fn ...) (#601) --- clojure-mode-promote-fn-literal-test.el | 72 +++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 clojure-mode-promote-fn-literal-test.el diff --git a/clojure-mode-promote-fn-literal-test.el b/clojure-mode-promote-fn-literal-test.el new file mode 100644 index 0000000..07b5dba --- /dev/null +++ b/clojure-mode-promote-fn-literal-test.el @@ -0,0 +1,72 @@ +;;; clojure-mode-promote-fn-literal-test.el --- Clojure Mode: convert fn syntax -*- lexical-binding: t; -*- + +;; This file is not part of GNU Emacs. + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;;; Commentary: + +;; Tests for clojure-promote-fn-literal + +;;; Code: + +(require 'clojure-mode) +(require 'buttercup) + +(describe "clojure-promote-fn-literal" + :var (names) + + (before-each + (spy-on 'read-string + :and-call-fake (lambda (_) (or (pop names) (error ""))))) + + (when-refactoring-it "should convert 0-arg fns" + "#(rand)" + "(fn [] (rand))" + (clojure-promote-fn-literal)) + + (when-refactoring-it "should convert 1-arg fns" + "#(= % 1)" + "(fn [x] (= x 1))" + (setq names '("x")) + (clojure-promote-fn-literal)) + + (when-refactoring-it "should convert 2-arg fns" + "#(conj (pop %) (assoc (peek %1) %2 (* %2 %2)))" + "(fn [acc x] (conj (pop acc) (assoc (peek acc) x (* x x))))" + (setq names '("acc" "x")) + (clojure-promote-fn-literal)) + + (when-refactoring-it "should convert variadic fns" + ;; from https://hypirion.com/musings/swearjure + "#(* (`[~@%&] (+)) + ((% (+)) % (- (`[~@%&] (+)) (*))))" + "(fn [v & vs] (* (`[~@vs] (+)) + ((v (+)) v (- (`[~@vs] (+)) (*)))))" + (setq names '("v" "vs")) + (clojure-promote-fn-literal)) + + (when-refactoring-it "should ignore strings and comments" + "#(format \"%2\" ;; FIXME: %2 is an illegal specifier + %7) " + "(fn [_ _ _ _ _ _ id] (format \"%2\" ;; FIXME: %2 is an illegal specifier + id)) " + (setq names '("_" "_" "_" "_" "_" "_" "id")) + (clojure-promote-fn-literal))) + + +(provide 'clojure-mode-convert-fn-test) + + +;;; clojure-mode-promote-fn-literal-test.el ends here From c1ae4bed355516701d2d6ca7e8e285625bb8ef2d Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Thu, 30 Dec 2021 10:17:37 +0200 Subject: [PATCH 172/379] Address some byte-compilation warnings Thanks for the patch, Stefan! --- clojure-mode-bytecomp-warnings.el | 2 +- clojure-mode-external-interaction-test.el | 1 + clojure-mode-font-lock-test.el | 3 ++- clojure-mode-indentation-test.el | 5 +++-- clojure-mode-promote-fn-literal-test.el | 1 + clojure-mode-syntax-test.el | 2 ++ clojure-mode-util-test.el | 8 +++++--- test-checks.el | 2 +- utils/test-helper.el | 2 ++ 9 files changed, 18 insertions(+), 8 deletions(-) diff --git a/clojure-mode-bytecomp-warnings.el b/clojure-mode-bytecomp-warnings.el index d3cc19a..2b94d07 100644 --- a/clojure-mode-bytecomp-warnings.el +++ b/clojure-mode-bytecomp-warnings.el @@ -1,4 +1,4 @@ -;;; clojure-mode-bytecomp-warnings.el --- Check for byte-compilation problems +;;; clojure-mode-bytecomp-warnings.el --- Check for byte-compilation problems -*- lexical-binding: t; -*- ;; Copyright © 2012-2021 Bozhidar Batsov and contributors ;; diff --git a/clojure-mode-external-interaction-test.el b/clojure-mode-external-interaction-test.el index f8c8caf..da038e5 100644 --- a/clojure-mode-external-interaction-test.el +++ b/clojure-mode-external-interaction-test.el @@ -22,6 +22,7 @@ (require 'clojure-mode) (require 'buttercup) (require 'paredit) +(require 'test-helper "test/utils/test-helper") (describe "Interactions with Paredit:" ;; reuse existing when-refactoring-it macro diff --git a/clojure-mode-font-lock-test.el b/clojure-mode-font-lock-test.el index da9cae2..b3687aa 100644 --- a/clojure-mode-font-lock-test.el +++ b/clojure-mode-font-lock-test.el @@ -27,6 +27,7 @@ (require 'clojure-mode) (require 'cl-lib) (require 'buttercup) +(require 'test-helper "test/utils/test-helper") ;;;; Utilities @@ -36,7 +37,7 @@ (declare (debug t) (indent 1)) `(with-clojure-buffer ,content - (font-lock-fontify-buffer) + (font-lock-ensure) (goto-char (point-min)) ,@body)) diff --git a/clojure-mode-indentation-test.el b/clojure-mode-indentation-test.el index c1c918a..9c12b9a 100644 --- a/clojure-mode-indentation-test.el +++ b/clojure-mode-indentation-test.el @@ -26,7 +26,8 @@ (require 'clojure-mode) (require 'cl-lib) (require 'buttercup) -(require 's) +(require 's nil t) ;Don't burp if it's missing during compilation. +(require 'test-helper "test/utils/test-helper") (defmacro when-indenting-with-point-it (description before after) "Return a buttercup spec. @@ -279,7 +280,7 @@ DESCRIPTION is a string with the description of the spec." one)")) (describe "we can pass a lambda to explicitly set the column" - (put-clojure-indent 'arsymbol (lambda (indent-point state) 0)) + (put-clojure-indent 'arsymbol (lambda (_indent-point _state) 0)) (when-indenting-with-point-it "should handle a symbol with lambda" " diff --git a/clojure-mode-promote-fn-literal-test.el b/clojure-mode-promote-fn-literal-test.el index 07b5dba..194e493 100644 --- a/clojure-mode-promote-fn-literal-test.el +++ b/clojure-mode-promote-fn-literal-test.el @@ -23,6 +23,7 @@ (require 'clojure-mode) (require 'buttercup) +(require 'test-helper "test/utils/test-helper") (describe "clojure-promote-fn-literal" :var (names) diff --git a/clojure-mode-syntax-test.el b/clojure-mode-syntax-test.el index 2b03b04..bc71eaa 100644 --- a/clojure-mode-syntax-test.el +++ b/clojure-mode-syntax-test.el @@ -25,6 +25,7 @@ (require 'clojure-mode) (require 'buttercup) +(require 'test-helper "test/utils/test-helper") (defun non-func (form-a form-b) (with-clojure-buffer form-a @@ -66,6 +67,7 @@ ("#aaa" . "aaa") ("'aaa" . "aaa"))) (with-clojure-buffer (car form) + ;; FIXME: Shouldn't there be an `expect' here? (equal (symbol-name (symbol-at-point)) (cdr form))))) (it "skips prefixes" diff --git a/clojure-mode-util-test.el b/clojure-mode-util-test.el index 2ff86f2..95931d9 100644 --- a/clojure-mode-util-test.el +++ b/clojure-mode-util-test.el @@ -25,12 +25,14 @@ (require 'clojure-mode) (require 'cl-lib) (require 'buttercup) - +(require 'test-helper "test/utils/test-helper") (describe "clojure-mode-version" (it "should not be nil" (expect clojure-mode-version))) +(defvar clojure-cache-project) + (let ((project-dir "/home/user/projects/my-project/") (clj-file-path "/home/user/projects/my-project/src/clj/my_project/my_ns/my_file.clj") (project-relative-clj-file-path "src/clj/my_project/my_ns/my_file.clj") @@ -45,13 +47,13 @@ (describe "clojure-expected-ns" (it "should return the namespace matching a path" (cl-letf (((symbol-function 'clojure-project-relative-path) - (lambda (&optional current-buffer-file-name) + (lambda (&optional _current-buffer-file-name) project-relative-clj-file-path))) (expect (string= (clojure-expected-ns clj-file-path) clj-file-ns)))) (it "should return the namespace even without a path" (cl-letf (((symbol-function 'clojure-project-relative-path) - (lambda (&optional current-buffer-file-name) + (lambda (&optional _current-buffer-file-name) project-relative-clj-file-path))) (expect (string= (let ((buffer-file-name clj-file-path)) (clojure-expected-ns)) diff --git a/test-checks.el b/test-checks.el index ad23c36..a4b4208 100644 --- a/test-checks.el +++ b/test-checks.el @@ -1,4 +1,4 @@ -;; This is a script to be loaded from the root `clojure-mode' directory. It will +;; This is a script to be loaded from the root `clojure-mode' directory. It will -*- lexical-binding: t; -*- ;; prepare all requirements and then run `check-declare-directory' on ;; `default-directory'. For example: emacs -Q --batch -l test/test-checkdoc.el diff --git a/utils/test-helper.el b/utils/test-helper.el index a8fb38d..fd3db30 100644 --- a/utils/test-helper.el +++ b/utils/test-helper.el @@ -95,4 +95,6 @@ DESCRIPTION is a string with the description of the spec." (expect (buffer-string) :to-equal expected-state) (expect (point) :to-equal expected-cursor-pos))))) + +(provide 'test-helper) ;;; test-helper.el ends here From 1f87e5c6c375084df9e954348af4a43052fd507d Mon Sep 17 00:00:00 2001 From: Sam Waggoner Date: Sun, 23 Jan 2022 11:58:06 -0800 Subject: [PATCH 173/379] [Fix #608] Fix alignment issue involving margin comments. Alignment wasn't working properly when nested, multi-line sexps were followed by margin comments. --- clojure-mode-indentation-test.el | 37 ++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/clojure-mode-indentation-test.el b/clojure-mode-indentation-test.el index 9c12b9a..91a9dcb 100644 --- a/clojure-mode-indentation-test.el +++ b/clojure-mode-indentation-test.el @@ -705,6 +705,43 @@ x "#?@(:clj [2] :cljs [2])") + (when-aligning-it "should handle sexps broken up by line comments" + " +(let [x 1 + ;; comment + xx 1] + xx)" + + " +{:x 1 + ;; comment + :xxx 2}" + + " +(case x + :aa 1 + ;; comment + :a 2)") + + (when-aligning-it "should work correctly when margin comments appear after nested, multi-line, non-terminal sexps" + " +(let [x {:a 1 + :b 2} ; comment + xx 3] + x)" + + " +{:aa {:b 1 + :cc 2} ;; comment + :a 1}}" + + " +(case x + :a (let [a 1 + aa (+ a 1)] + aa); comment + :aa 2)") + (it "should handle improperly indented content" (let ((content "(let [a-long-name 10\nb 20])") (aligned-content "(let [a-long-name 10\n b 20])")) From 17ecddbc0cffc81c725e42aa3282632ce2b2cab0 Mon Sep 17 00:00:00 2001 From: Sam Waggoner Date: Thu, 3 Feb 2022 22:09:33 -0800 Subject: [PATCH 174/379] Update indentation tests for consistent style. Begin multi-line example strings with a newline so that the indentation of the first line in relation to the rest of the string is plain to see. --- clojure-mode-indentation-test.el | 201 ++++++++++++++++++++----------- 1 file changed, 134 insertions(+), 67 deletions(-) diff --git a/clojure-mode-indentation-test.el b/clojure-mode-indentation-test.el index 91a9dcb..ac9485e 100644 --- a/clojure-mode-indentation-test.el +++ b/clojure-mode-indentation-test.el @@ -327,43 +327,51 @@ DESCRIPTION is a string with the description of the spec." :a)") (when-indenting-with-point-it "should handle fixed-normal-indent" - "(cond + " + (cond (or 1 2) 3 |:else 4)" - "(cond + " + (cond (or 1 2) 3 |:else 4)") (when-indenting-with-point-it "should handle fixed-normal-indent-2" - "(fact {:spec-type + " +(fact {:spec-type :charnock-column-id} #{\"charnock\"} |{:spec-type :charnock-column-id} #{\"current_charnock\"})" - "(fact {:spec-type + " +(fact {:spec-type :charnock-column-id} #{\"charnock\"} |{:spec-type :charnock-column-id} #{\"current_charnock\"})") (when-indenting-it "closing-paren" - "(ns ca + " +(ns ca (:gen-class) )") (when-indenting-it "default-is-not-a-define" - "(default a + " +(default a b b)" - "(some.namespace/default a + " +(some.namespace/default a b b)") (when-indenting-it "should handle extend-type with multiarity" - "(extend-type Banana + " +(extend-type Banana Fruit (subtotal ([item] @@ -371,7 +379,8 @@ DESCRIPTION is a string with the description of the spec." ([item a] (* a (:qty item)))))" - "(extend-protocol Banana + " +(extend-protocol Banana Fruit (subtotal ([item] @@ -381,7 +390,8 @@ DESCRIPTION is a string with the description of the spec." (when-indenting-it "should handle deftype with multiarity" - "(deftype Banana [] + " +(deftype Banana [] Fruit (subtotal ([item] @@ -390,7 +400,8 @@ DESCRIPTION is a string with the description of the spec." (* a (:qty item)))))") (when-indenting-it "should handle defprotocol" - "(defprotocol IFoo + " +(defprotocol IFoo (foo [this] \"Why is this over here?\") (foo-2 @@ -399,7 +410,8 @@ DESCRIPTION is a string with the description of the spec." (when-indenting-it "should handle definterface" - "(definterface IFoo + " +(definterface IFoo (foo [this] \"Why is this over here?\") (foo-2 @@ -407,7 +419,8 @@ DESCRIPTION is a string with the description of the spec." \"Why is this over here?\"))") (when-indenting-it "should handle specify" - "(specify obj + " +(specify obj ISwap (-swap! ([this f] (reset! this (f @this))) @@ -416,7 +429,8 @@ DESCRIPTION is a string with the description of the spec." ([this f a b xs] (reset! this (apply f @this a b xs)))))") (when-indenting-it "should handle specify!" - "(specify! obj + " +(specify! obj ISwap (-swap! ([this f] (reset! this (f @this))) @@ -425,27 +439,32 @@ DESCRIPTION is a string with the description of the spec." ([this f a b xs] (reset! this (apply f @this a b xs)))))") (when-indenting-it "should handle non-symbol at start" - "{\"1\" 2 + " +{\"1\" 2 *3 4}") (when-indenting-it "should handle non-symbol at start 2" - "(\"1\" 2 + " +(\"1\" 2 *3 4)") (when-indenting-it "should handle defrecord" - "(defrecord TheNameOfTheRecord + " +(defrecord TheNameOfTheRecord [a pretty long argument list] SomeType (assoc [_ x] (.assoc pretty x 10)))") (when-indenting-it "should handle defrecord 2" - "(defrecord TheNameOfTheRecord [a pretty long argument list] + " +(defrecord TheNameOfTheRecord [a pretty long argument list] SomeType (assoc [_ x] (.assoc pretty x 10)))") (when-indenting-it "should handle defrecord with multiarity" - "(defrecord Banana [] + " +(defrecord Banana [] Fruit (subtotal ([item] @@ -454,7 +473,8 @@ DESCRIPTION is a string with the description of the spec." (* a (:qty item)))))") (when-indenting-it "should handle letfn" - "(letfn [(f [x] + " +(letfn [(f [x] (* x 2)) (f [x] (* x 2))] @@ -463,11 +483,13 @@ DESCRIPTION is a string with the description of the spec." e)") (when-indenting-it "should handle reify" - "(reify Object + " +(reify Object (x [_] 1))" - "(reify + " +(reify om/IRender (render [this] (let [indent-test :fail] @@ -478,7 +500,8 @@ DESCRIPTION is a string with the description of the spec." ...)))") (when-indenting-it "proxy" - "(proxy [Writer] [] + " +(proxy [Writer] [] (close [] (.flush ^Writer this)) (write ([x] @@ -496,29 +519,36 @@ DESCRIPTION is a string with the description of the spec." :cljs [])") (when-indenting-it "should handle an empty close paren" - "(let [x] + " +(let [x] )" - "(ns ok + " +(ns ok )" - "(ns ^{:zen :dikar} + " +(ns ^{:zen :dikar} ok )") (when-indenting-it "should handle unfinished sexps" - "(letfn [(tw [x] + " +(letfn [(tw [x] dd") (when-indenting-it "should handle symbols ending in crap" - "(msg? ExceptionInfo + " +(msg? ExceptionInfo 10)" - "(thrown-with-msg? ExceptionInfo + " +(thrown-with-msg? ExceptionInfo #\"Storage must be initialized before use\" (f))" - "(msg' 1 + " +(msg' 1 10)") (when-indenting-it "should handle let, when and while forms" @@ -553,14 +583,16 @@ DESCRIPTION is a string with the description of the spec." (when-indenting-it "should handle function spec" - "(when me + " +(when me (test-cond x 1 2 3))" - "(when me + " +(when me (test-cond-0 x 1 @@ -570,139 +602,174 @@ x (when-indenting-it "should respect indent style 'align-arguments" 'align-arguments - "(some-function + " +(some-function 10 1 2)" - "(some-function 10 + " +(some-function 10 1 2)") (when-indenting-it "should respect indent style 'always-indent" 'always-indent - "(some-function + " +(some-function 10 1 2)" - "(some-function 10 + " +(some-function 10 1 2)") (when-aligning-it "should basic forms" - "{:this-is-a-form b + " +{:this-is-a-form b c d}" - "{:this-is b + " +{:this-is b c d}" - "{:this b + " +{:this b c d}" - "{:a b + " +{:a b c d}" - "(let [this-is-a-form b + " +(let [this-is-a-form b c d])" - "(let [this-is b + " +(let [this-is b c d])" - "(let [this b + " +(let [this b c d])" - "(let [a b + " +(let [a b c d])") (when-aligning-it "should handle a blank line" - "(let [this-is-a-form b + " +(let [this-is-a-form b c d another form k g])" - "{:this-is-a-form b + " +{:this-is-a-form b c d :another form k g}") (when-aligning-it "should handle basic forms (reversed)" - "{c d + " +{c d :this-is-a-form b}" - "{c d + " +{c d :this-is b}" - "{c d + " +{c d :this b}" - "{c d + " +{c d :a b}" - "(let [c d + " +(let [c d this-is-a-form b])" - "(let [c d + " +(let [c d this-is b])" - "(let [c d + " +(let [c d this b])" - "(let [c d + " +(let [c d a b])") (when-aligning-it "should handle incomplete sexps" - "(cond aa b + " +(cond aa b casodkas )" - "(cond aa b + " +(cond aa b casodkas)" - "(cond aa b + " +(cond aa b casodkas " - "(cond aa b + " +(cond aa b casodkas" - "(cond aa b + " +(cond aa b casodkas a)" - "(cond casodkas a + " +(cond casodkas a aa b)" - "(cond casodkas + " +(cond casodkas aa b)") (when-aligning-it "should handle multiple words" - "(cond this is just + " +(cond this is just a test of how well multiple words will work)") (when-aligning-it "should handle nested maps" - "{:a {:a :a + " +{:a {:a :a :bbbb :b} :bbbb :b}") (when-aligning-it "should regard end as a marker" - "{:a {:a :a + " +{:a {:a :a :aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa :a} :b {:a :a :aa :a}}") (when-aligning-it "should handle trailing commas" - "{:a {:a :a, + " +{:a {:a :a, :aa :a}, :b {:a :a, :aa :a}}") (when-aligning-it "should handle standard reader conditionals" - "#?(:clj 2 + " +#?(:clj 2 :cljs 2)") (when-aligning-it "should handle splicing reader conditional" - "#?@(:clj [2] + " +#?@(:clj [2] :cljs [2])") (when-aligning-it "should handle sexps broken up by line comments" From aae566e578f4e7e8230f97ac5b7c38216c683203 Mon Sep 17 00:00:00 2001 From: Vadim <47952597+OknoLombarda@users.noreply.github.com> Date: Tue, 19 Jul 2022 23:09:05 +0600 Subject: [PATCH 175/379] Fix infinite loop when reverse searching for next definition (#624) Fixes #595 Fixes #612 --- clojure-mode-syntax-test.el | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/clojure-mode-syntax-test.el b/clojure-mode-syntax-test.el index bc71eaa..8ac8938 100644 --- a/clojure-mode-syntax-test.el +++ b/clojure-mode-syntax-test.el @@ -60,6 +60,37 @@ (expect (non-func "^hint " form) :to-be nil) (expect (non-func "#macro " form) :to-be nil)))) +(describe "clojure-match-next-def" + (let ((some-sexp "\n(list [1 2 3])")) + (it "handles vars with metadata" + (dolist (form '("(def ^Integer a 1)" + "(def ^:a a 1)" + "(def ^::a a 1)" + "(def ^::a/b a 1)" + "(def ^{:macro true} a 1)")) + (with-clojure-buffer (concat form some-sexp) + (end-of-buffer) + (clojure-match-next-def) + (expect (looking-at "(def"))))) + + (it "handles vars without metadata" + (with-clojure-buffer (concat "(def a 1)" some-sexp) + (end-of-buffer) + (clojure-match-next-def) + (expect (looking-at "(def")))) + + (it "handles invalid def forms" + (dolist (form '("(def ^Integer)" + "(def)" + "(def ^{:macro})" + "(def ^{:macro true})" + "(def ^{:macro true} foo)" + "(def ^{:macro} foo)")) + (with-clojure-buffer (concat form some-sexp) + (end-of-buffer) + (clojure-match-next-def) + (expect (looking-at "(def"))))))) + (describe "clojure syntax" (it "handles prefixed symbols" (dolist (form '(("#?@aaa" . "aaa") From 372787359fb629f82fff511a496ee83ebb936792 Mon Sep 17 00:00:00 2001 From: Vadim <47952597+OknoLombarda@users.noreply.github.com> Date: Fri, 29 Jul 2022 14:11:59 +0600 Subject: [PATCH 176/379] [Fix #625] Fix imenu displaying metadata instead of var name (#626) Fix metadata being captured instead of var name in clojure-match-next-def. Co-authored-by: Vadim Rodionov --- clojure-mode-syntax-test.el | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/clojure-mode-syntax-test.el b/clojure-mode-syntax-test.el index 8ac8938..1766e14 100644 --- a/clojure-mode-syntax-test.el +++ b/clojure-mode-syntax-test.el @@ -89,7 +89,31 @@ (with-clojure-buffer (concat form some-sexp) (end-of-buffer) (clojure-match-next-def) - (expect (looking-at "(def"))))))) + (expect (looking-at "(def")))))) + + (it "captures var name" + (let ((var-name "some-name")) + (dolist (form '("(def %s 1)" + "(def %s)" + "(def ^:private %s 2)" + "(def ^{:private true} %s 3)")) + (with-clojure-buffer (format form var-name) + (end-of-buffer) + (clojure-match-next-def) + (cl-destructuring-bind (name-beg name-end) (match-data) + (expect (string= var-name (buffer-substring name-beg name-end)))))))) + + (it "captures var name with dispatch value for defmethod" + (let ((name "some-name :key")) + (dolist (form '("(defmethod %s [a])" + "(defmethod ^:meta %s [a])" + "(defmethod ^{:meta true} %s [a])" + "(defmethod %s)")) + (with-clojure-buffer (format form name) + (end-of-buffer) + (clojure-match-next-def) + (cl-destructuring-bind (name-beg name-end) (match-data) + (expect (string= name (buffer-substring name-beg name-end))))))))) (describe "clojure syntax" (it "handles prefixed symbols" From 016c7b22af6f00f2b89287fd00dde331d4cbfd95 Mon Sep 17 00:00:00 2001 From: Vadim <47952597+OknoLombarda@users.noreply.github.com> Date: Fri, 29 Jul 2022 15:33:43 +0600 Subject: [PATCH 177/379] Use constant values instead of variables in clojure-match-next-def tests (#627) --- clojure-mode-syntax-test.el | 38 ++++++++++++++++++------------------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/clojure-mode-syntax-test.el b/clojure-mode-syntax-test.el index 1766e14..934af5e 100644 --- a/clojure-mode-syntax-test.el +++ b/clojure-mode-syntax-test.el @@ -92,28 +92,26 @@ (expect (looking-at "(def")))))) (it "captures var name" - (let ((var-name "some-name")) - (dolist (form '("(def %s 1)" - "(def %s)" - "(def ^:private %s 2)" - "(def ^{:private true} %s 3)")) - (with-clojure-buffer (format form var-name) - (end-of-buffer) - (clojure-match-next-def) - (cl-destructuring-bind (name-beg name-end) (match-data) - (expect (string= var-name (buffer-substring name-beg name-end)))))))) + (dolist (form '("(def some-name 1)" + "(def some-name)" + "(def ^:private some-name 2)" + "(def ^{:private true} some-name 3)")) + (with-clojure-buffer form + (end-of-buffer) + (clojure-match-next-def) + (cl-destructuring-bind (name-beg name-end) (match-data) + (expect (string= "some-name" (buffer-substring name-beg name-end))))))) (it "captures var name with dispatch value for defmethod" - (let ((name "some-name :key")) - (dolist (form '("(defmethod %s [a])" - "(defmethod ^:meta %s [a])" - "(defmethod ^{:meta true} %s [a])" - "(defmethod %s)")) - (with-clojure-buffer (format form name) - (end-of-buffer) - (clojure-match-next-def) - (cl-destructuring-bind (name-beg name-end) (match-data) - (expect (string= name (buffer-substring name-beg name-end))))))))) + (dolist (form '("(defmethod some-name :key [a])" + "(defmethod ^:meta some-name :key [a])" + "(defmethod ^{:meta true} some-name :key [a])" + "(defmethod some-name :key)")) + (with-clojure-buffer form + (end-of-buffer) + (clojure-match-next-def) + (cl-destructuring-bind (name-beg name-end) (match-data) + (expect (string= "some-name :key" (buffer-substring name-beg name-end)))))))) (describe "clojure syntax" (it "handles prefixed symbols" From 123e569b99506d232b36037d4d1bb54dbebed379 Mon Sep 17 00:00:00 2001 From: Vadim Rodionov <47952597+OknoLombarda@users.noreply.github.com> Date: Wed, 24 Aug 2022 23:06:51 +0600 Subject: [PATCH 178/379] [Fix #581] Fix font locking for keywords starting with a number (#628) --- clojure-mode-font-lock-test.el | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/clojure-mode-font-lock-test.el b/clojure-mode-font-lock-test.el index b3687aa..c8304a0 100644 --- a/clojure-mode-font-lock-test.el +++ b/clojure-mode-font-lock-test.el @@ -487,11 +487,17 @@ DESCRIPTION is the description of the spec." (when-fontifying-it "should handle oneword keywords" (" :oneword" - (3 9 clojure-keyword-face )) + (3 9 clojure-keyword-face)) + + (" :1oneword" + (3 10 clojure-keyword-face)) ("{:oneword 0}" (3 9 clojure-keyword-face)) + ("{:1oneword 0}" + (3 10 clojure-keyword-face)) + ("{:#oneword 0}" (3 10 clojure-keyword-face)) @@ -563,7 +569,22 @@ DESCRIPTION is the description of the spec." (10 17 clojure-keyword-face)) (":_:_:foo/bar_:_:foo" - (10 19 clojure-keyword-face))) + (10 19 clojure-keyword-face)) + + (":1foo/bar" + (2 5 font-lock-type-face) + (6 6 default) + (7 9 clojure-keyword-face)) + + (":foo/1bar" + (2 4 font-lock-type-face) + (5 5 default) + (6 9 clojure-keyword-face)) + + (":1foo/1bar" + (2 5 font-lock-type-face) + (6 6 default) + (7 10 clojure-keyword-face))) (when-fontifying-it "should handle segment keywords" (" :seg.mnt" From 264c40ecf2a6b66d6b1097942b861ffa98e4bc08 Mon Sep 17 00:00:00 2001 From: Vadim Rodionov <47952597+OknoLombarda@users.noreply.github.com> Date: Tue, 30 Aug 2022 16:36:45 +0600 Subject: [PATCH 179/379] [Fix #377] Fix font locking for def forms (#630) --- clojure-mode-font-lock-test.el | 56 ++++++++++++++++++++-------------- 1 file changed, 33 insertions(+), 23 deletions(-) diff --git a/clojure-mode-font-lock-test.el b/clojure-mode-font-lock-test.el index c8304a0..cd4f21f 100644 --- a/clojure-mode-font-lock-test.el +++ b/clojure-mode-font-lock-test.el @@ -776,17 +776,17 @@ DESCRIPTION is the description of the spec." (6 42 clojure-keyword-face))) (when-fontifying-it "should handle namespaced defs" - ("(_c4/defconstrainedfn bar [] nil)" + ("(_c4/defn bar [] nil)" (2 4 font-lock-type-face) (5 5 nil) - (6 18 font-lock-keyword-face) - (23 25 font-lock-function-name-face)) + (6 9 font-lock-keyword-face) + (11 13 font-lock-function-name-face)) - ("(clo/defbar foo nil)" + ("(clo/defrecord foo nil)" (2 4 font-lock-type-face) (5 5 nil) - (6 11 font-lock-keyword-face) - (13 15 font-lock-function-name-face)) + (6 14 font-lock-keyword-face) + (16 18 font-lock-function-name-face)) ("(s/def ::keyword)" (2 2 font-lock-type-face) @@ -794,6 +794,33 @@ DESCRIPTION is the description of the spec." (4 6 font-lock-keyword-face) (8 16 clojure-keyword-face))) + (when-fontifying-it "should handle any known def form" + ("(def a 1)" (2 4 font-lock-keyword-face)) + ("(defonce a 1)" (2 8 font-lock-keyword-face)) + ("(defn a [b])" (2 5 font-lock-keyword-face)) + ("(defmacro a [b])" (2 9 font-lock-keyword-face)) + ("(definline a [b])" (2 10 font-lock-keyword-face)) + ("(defmulti a identity)" (2 9 font-lock-keyword-face)) + ("(defmethod a :foo [b] (println \"bar\"))" (2 10 font-lock-keyword-face)) + ("(defprotocol a (b [this] \"that\"))" (2 12 font-lock-keyword-face)) + ("(definterface a (b [c]))" (2 13 font-lock-keyword-face)) + ("(defrecord a [b c])" (2 10 font-lock-keyword-face)) + ("(deftype a [b c])" (2 8 font-lock-keyword-face)) + ("(defstruct a :b :c)" (2 10 font-lock-keyword-face)) + ("(deftest a (is (= 1 1)))" (2 8 font-lock-keyword-face)) + ("(defne [x y])" (2 6 font-lock-keyword-face)) + ("(defnm a b)" (2 6 font-lock-keyword-face)) + ("(defnu)" (2 6 font-lock-keyword-face)) + ("(defnc [a])" (2 6 font-lock-keyword-face)) + ("(defna)" (2 6 font-lock-keyword-face)) + ("(deftask a)" (2 8 font-lock-keyword-face)) + ("(defstate a :start \"b\" :stop \"c\")" (2 9 font-lock-keyword-face))) + + (when-fontifying-it "should ignore unknown def forms" + ("(defbugproducer me)" (2 15 nil)) + ("(default-user-settings {:a 1})" (2 24 nil)) + ("(s/deftartar :foo)" (4 10 nil))) + (when-fontifying-it "should handle variables defined with def" ("(def foo 10)" (2 4 font-lock-keyword-face) @@ -845,23 +872,6 @@ DESCRIPTION is the description of the spec." (2 5 font-lock-keyword-face) (7 9 font-lock-function-name-face))) - (when-fontifying-it "should handle a custom def with special chars 1" - ("(defn* foo [x] x)" - (2 6 font-lock-keyword-face) - (8 10 font-lock-function-name-face))) - - (when-fontifying-it "should handle a custom def with special chars 2" - ("(defsomething! foo [x] x)" - (2 14 font-lock-keyword-face) - (16 18 font-lock-function-name-face))) - - (when-fontifying-it "should handle a custom def with special chars 3" - ("(def-something foo [x] x)" - (2 14 font-lock-keyword-face)) - - ("(def-something foo [x] x)" - (16 18 font-lock-function-name-face))) - (when-fontifying-it "should handle fn" ;; try to byte-recompile the clojure-mode.el when the face of 'fn' is 't' ("(fn foo [x] x)" From b41b92efac3535d107ad948cabafb16effcb1279 Mon Sep 17 00:00:00 2001 From: Vadim Rodionov Date: Sat, 3 Sep 2022 14:59:02 +0600 Subject: [PATCH 180/379] Make function definition regexp only look for 'clojure.core/defn' forms --- clojure-mode-font-lock-test.el | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/clojure-mode-font-lock-test.el b/clojure-mode-font-lock-test.el index cd4f21f..4a7877b 100644 --- a/clojure-mode-font-lock-test.el +++ b/clojure-mode-font-lock-test.el @@ -776,17 +776,17 @@ DESCRIPTION is the description of the spec." (6 42 clojure-keyword-face))) (when-fontifying-it "should handle namespaced defs" - ("(_c4/defn bar [] nil)" - (2 4 font-lock-type-face) - (5 5 nil) - (6 9 font-lock-keyword-face) - (11 13 font-lock-function-name-face)) - - ("(clo/defrecord foo nil)" - (2 4 font-lock-type-face) - (5 5 nil) - (6 14 font-lock-keyword-face) - (16 18 font-lock-function-name-face)) + ("(clojure.core/defn bar [] nil)" + (2 13 font-lock-type-face) + (14 14 nil) + (15 18 font-lock-keyword-face) + (20 22 font-lock-function-name-face)) + + ("(clojure.core/defrecord foo nil)" + (2 13 font-lock-type-face) + (14 14 nil) + (15 23 font-lock-keyword-face) + (25 27 font-lock-type-face)) ("(s/def ::keyword)" (2 2 font-lock-type-face) From 0d5ce21d16e1fd38b475a3d1f753df336364e685 Mon Sep 17 00:00:00 2001 From: chaos Date: Sat, 3 Dec 2022 13:49:08 +0000 Subject: [PATCH 181/379] Nbb support --- clojure-mode-util-test.el | 10 ++++++++++ utils/test-helper.el | 10 ++++++++++ 2 files changed, 20 insertions(+) diff --git a/clojure-mode-util-test.el b/clojure-mode-util-test.el index 95931d9..bf17d02 100644 --- a/clojure-mode-util-test.el +++ b/clojure-mode-util-test.el @@ -39,6 +39,16 @@ (clj-file-ns "my-project.my-ns.my-file") (clojure-cache-project nil)) + (describe "clojure-project-root-path" + (it "nbb subdir" + (with-temp-dir temp-dir + (let* ((bb-edn (expand-file-name "nbb.edn" temp-dir)) + (bb-edn-src (expand-file-name "src" temp-dir))) + (write-region "{}" nil bb-edn) + (make-directory bb-edn-src) + (expect (clojure-project-dir bb-edn-src) + :to-equal (file-name-as-directory temp-dir)))))) + (describe "clojure-project-relative-path" (cl-letf (((symbol-function 'clojure-project-dir) (lambda () project-dir))) (expect (string= (clojure-project-relative-path clj-file-path) diff --git a/utils/test-helper.el b/utils/test-helper.el index fd3db30..c8dfed8 100644 --- a/utils/test-helper.el +++ b/utils/test-helper.el @@ -96,5 +96,15 @@ DESCRIPTION is a string with the description of the spec." (expect (point) :to-equal expected-cursor-pos))))) +;; https://emacs.stackexchange.com/a/55031 +(defmacro with-temp-dir (temp-dir &rest body) + "Create a temporary directory and bind its to TEMP-DIR while evaluating BODY. +Removes the temp directory at the end of evaluation." + `(let ((,temp-dir (make-temp-file "" t))) + (unwind-protect + (progn + ,@body) + (delete-directory ,temp-dir t)))) + (provide 'test-helper) ;;; test-helper.el ends here From f5bc38bc915febf399b35fe7574ff2197de6e134 Mon Sep 17 00:00:00 2001 From: Vadim Rodionov <47952597+OknoLombarda@users.noreply.github.com> Date: Sat, 24 Jun 2023 02:48:42 +0600 Subject: [PATCH 182/379] Fix infinite loop when opening file containing "comment" (#651) Closes #586 --- clojure-mode-sexp-test.el | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/clojure-mode-sexp-test.el b/clojure-mode-sexp-test.el index de4bea7..f82552a 100644 --- a/clojure-mode-sexp-test.el +++ b/clojure-mode-sexp-test.el @@ -169,6 +169,24 @@ (goto-char (point-max)) (expect (clojure-find-ns) :to-equal expected))))))) +(describe "clojure-sexp-starts-until-position" + (it "should return starting points for forms after POINT until POSITION" + (with-clojure-buffer "(run 1) (def b 2) (slurp \"file\")\n" + (goto-char (point-min)) + (expect (not (cl-set-difference '(19 9 1) + (clojure-sexp-starts-until-position (point-max))))))) + + (it "should return starting point for a single form in buffer after POINT" + (with-clojure-buffer "comment\n" + (goto-char (point-min)) + (expect (not (cl-set-difference '(1) + (clojure-sexp-starts-until-position (point-max))))))) + + (it "should return nil if POSITION is behind POINT" + (with-clojure-buffer "(run 1) (def b 2)\n" + (goto-char (point-max)) + (expect (not (clojure-sexp-starts-until-position (- (point-max) 1))))))) + (provide 'clojure-mode-sexp-test) ;;; clojure-mode-sexp-test.el ends here From 6be228ea99f78586ec96291319c9c8fd7b4a86b6 Mon Sep 17 00:00:00 2001 From: p4v4n Date: Sun, 25 Jun 2023 00:27:54 +0530 Subject: [PATCH 183/379] Fix clojure-sort-ns with comments in the end (#646) Closes #645 --- clojure-mode-util-test.el | 44 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/clojure-mode-util-test.el b/clojure-mode-util-test.el index bf17d02..353f0e5 100644 --- a/clojure-mode-util-test.el +++ b/clojure-mode-util-test.el @@ -136,7 +136,49 @@ (expect (buffer-string) :to-equal "(ns my-app.core (:require [my-app.views [user-page :as user-page]] - [rum.core :as rum] ;comment\n))"))) + [rum.core :as rum] ;comment +))"))) + + (it "should sort requires in a basic ns with comments in the end" + (with-clojure-buffer "(ns my-app.core + (:require [rum.core :as rum] ;comment + [my-app.views [user-page :as user-page]] + ;;[comment2] +))" + (clojure-sort-ns) + (expect (buffer-string) :to-equal + "(ns my-app.core + (:require [my-app.views [user-page :as user-page]] + [rum.core :as rum] ;comment + + ;;[comment2] +))"))) + (it "should sort requires in ns with copyright disclamer and comments" + (with-clojure-buffer ";; Copyright (c) John Doe. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +(ns clojure.core + (:require + ;; The first comment + [foo] ;; foo comment + ;; Middle comment + [bar] ;; bar comment + ;; A last comment + ))" + (clojure-sort-ns) + (expect (buffer-string) :to-equal + ";; Copyright (c) John Doe. All rights reserved. +;; The use and distribution terms for this software are covered by the +;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +(ns clojure.core + (:require + ;; Middle comment + [bar] ;; bar comment + ;; The first comment + [foo] ;; foo comment + + ;; A last comment + ))"))) (it "should also sort imports in a ns" (with-clojure-buffer "\n(ns my-app.core From f9dd268987d51fc1508f62a29f78790144a92d62 Mon Sep 17 00:00:00 2001 From: vemv Date: Wed, 23 Aug 2023 13:41:26 +0200 Subject: [PATCH 184/379] `clojure-find-ns`: add an option to suppress errors (#654) --- clojure-mode-sexp-test.el | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/clojure-mode-sexp-test.el b/clojure-mode-sexp-test.el index f82552a..edec580 100644 --- a/clojure-mode-sexp-test.el +++ b/clojure-mode-sexp-test.el @@ -167,7 +167,22 @@ (expect (clojure-find-ns) :to-equal expected) ;; After both namespaces (goto-char (point-max)) - (expect (clojure-find-ns) :to-equal expected))))))) + (expect (clojure-find-ns) :to-equal expected)))))) + + (describe "`suppress-errors' argument" + (let ((clojure-cache-ns nil)) + (describe "given a faulty ns form" + (let ((ns-form "(ns )")) + (describe "when the argument is `t'" + (it "causes `clojure-find-ns' to return nil" + (with-clojure-buffer ns-form + (expect (equal nil (clojure-find-ns t)))))) + + (describe "when the argument is `nil'" + (it "causes `clojure-find-ns' to return raise an error" + (with-clojure-buffer ns-form + (expect (clojure-find-ns nil) + :to-throw 'error))))))))) (describe "clojure-sexp-starts-until-position" (it "should return starting points for forms after POINT until POSITION" From b823f3acd017cbe21b5bc24793f432ed7b5ea8d6 Mon Sep 17 00:00:00 2001 From: vemv Date: Wed, 23 Aug 2023 19:20:41 +0200 Subject: [PATCH 185/379] Replace `http` -> `https` everywhere (#655) --- clojure-mode-bytecomp-warnings.el | 2 +- clojure-mode-convert-collection-test.el | 2 +- clojure-mode-cycling-test.el | 2 +- clojure-mode-external-interaction-test.el | 2 +- clojure-mode-font-lock-test.el | 2 +- clojure-mode-indentation-test.el | 2 +- clojure-mode-promote-fn-literal-test.el | 2 +- clojure-mode-refactor-add-arity-test.el | 2 +- clojure-mode-refactor-let-test.el | 2 +- clojure-mode-refactor-rename-ns-alias-test.el | 2 +- clojure-mode-refactor-threading-test.el | 2 +- clojure-mode-safe-eval-test.el | 2 +- clojure-mode-sexp-test.el | 2 +- clojure-mode-syntax-test.el | 2 +- clojure-mode-util-test.el | 6 +++--- utils/test-helper.el | 2 +- 16 files changed, 18 insertions(+), 18 deletions(-) diff --git a/clojure-mode-bytecomp-warnings.el b/clojure-mode-bytecomp-warnings.el index 2b94d07..41b3230 100644 --- a/clojure-mode-bytecomp-warnings.el +++ b/clojure-mode-bytecomp-warnings.el @@ -13,7 +13,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with this program. If not, see . +;; along with this program. If not, see . ;; This file is not part of GNU Emacs. diff --git a/clojure-mode-convert-collection-test.el b/clojure-mode-convert-collection-test.el index cd28f51..28c5977 100644 --- a/clojure-mode-convert-collection-test.el +++ b/clojure-mode-convert-collection-test.el @@ -15,7 +15,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with this program. If not, see . +;; along with this program. If not, see . ;;; Commentary: diff --git a/clojure-mode-cycling-test.el b/clojure-mode-cycling-test.el index afc2476..e1dcc46 100644 --- a/clojure-mode-cycling-test.el +++ b/clojure-mode-cycling-test.el @@ -15,7 +15,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with this program. If not, see . +;; along with this program. If not, see . ;;; Commentary: diff --git a/clojure-mode-external-interaction-test.el b/clojure-mode-external-interaction-test.el index da038e5..e394f9d 100644 --- a/clojure-mode-external-interaction-test.el +++ b/clojure-mode-external-interaction-test.el @@ -15,7 +15,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with this program. If not, see . +;; along with this program. If not, see . ;;; Code: diff --git a/clojure-mode-font-lock-test.el b/clojure-mode-font-lock-test.el index 4a7877b..bb6ef74 100644 --- a/clojure-mode-font-lock-test.el +++ b/clojure-mode-font-lock-test.el @@ -16,7 +16,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with this program. If not, see . +;; along with this program. If not, see . ;;; Commentary: diff --git a/clojure-mode-indentation-test.el b/clojure-mode-indentation-test.el index ac9485e..1a03656 100644 --- a/clojure-mode-indentation-test.el +++ b/clojure-mode-indentation-test.el @@ -15,7 +15,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with this program. If not, see . +;; along with this program. If not, see . ;;; Commentary: diff --git a/clojure-mode-promote-fn-literal-test.el b/clojure-mode-promote-fn-literal-test.el index 194e493..13aa006 100644 --- a/clojure-mode-promote-fn-literal-test.el +++ b/clojure-mode-promote-fn-literal-test.el @@ -13,7 +13,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with this program. If not, see . +;; along with this program. If not, see . ;;; Commentary: diff --git a/clojure-mode-refactor-add-arity-test.el b/clojure-mode-refactor-add-arity-test.el index e4deefc..9c75f12 100644 --- a/clojure-mode-refactor-add-arity-test.el +++ b/clojure-mode-refactor-add-arity-test.el @@ -13,7 +13,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with this program. If not, see . +;; along with this program. If not, see . ;;; Commentary: diff --git a/clojure-mode-refactor-let-test.el b/clojure-mode-refactor-let-test.el index 196db79..a197012 100644 --- a/clojure-mode-refactor-let-test.el +++ b/clojure-mode-refactor-let-test.el @@ -15,7 +15,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with this program. If not, see . +;; along with this program. If not, see . ;;; Commentary: diff --git a/clojure-mode-refactor-rename-ns-alias-test.el b/clojure-mode-refactor-rename-ns-alias-test.el index 37fe90c..919a3cd 100644 --- a/clojure-mode-refactor-rename-ns-alias-test.el +++ b/clojure-mode-refactor-rename-ns-alias-test.el @@ -13,7 +13,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with this program. If not, see . +;; along with this program. If not, see . ;;; Commentary: diff --git a/clojure-mode-refactor-threading-test.el b/clojure-mode-refactor-threading-test.el index 65b2a78..61ad598 100644 --- a/clojure-mode-refactor-threading-test.el +++ b/clojure-mode-refactor-threading-test.el @@ -15,7 +15,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with this program. If not, see . +;; along with this program. If not, see . ;;; Commentary: diff --git a/clojure-mode-safe-eval-test.el b/clojure-mode-safe-eval-test.el index 47b7c5b..fe1e2a6 100644 --- a/clojure-mode-safe-eval-test.el +++ b/clojure-mode-safe-eval-test.el @@ -16,7 +16,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with this program. If not, see . +;; along with this program. If not, see . ;;; Commentary: diff --git a/clojure-mode-sexp-test.el b/clojure-mode-sexp-test.el index edec580..1db0e70 100644 --- a/clojure-mode-sexp-test.el +++ b/clojure-mode-sexp-test.el @@ -15,7 +15,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with this program. If not, see . +;; along with this program. If not, see . ;;; Code: diff --git a/clojure-mode-syntax-test.el b/clojure-mode-syntax-test.el index 934af5e..dfe2505 100644 --- a/clojure-mode-syntax-test.el +++ b/clojure-mode-syntax-test.el @@ -15,7 +15,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with this program. If not, see . +;; along with this program. If not, see . ;;; Commentary: diff --git a/clojure-mode-util-test.el b/clojure-mode-util-test.el index 353f0e5..a35babb 100644 --- a/clojure-mode-util-test.el +++ b/clojure-mode-util-test.el @@ -15,7 +15,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with this program. If not, see . +;; along with this program. If not, see . ;;; Commentary: @@ -156,7 +156,7 @@ (it "should sort requires in ns with copyright disclamer and comments" (with-clojure-buffer ";; Copyright (c) John Doe. All rights reserved. ;; The use and distribution terms for this software are covered by the -;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; Eclipse Public License 1.0 (https://opensource.org/license/epl-1-0/) (ns clojure.core (:require ;; The first comment @@ -169,7 +169,7 @@ (expect (buffer-string) :to-equal ";; Copyright (c) John Doe. All rights reserved. ;; The use and distribution terms for this software are covered by the -;; Eclipse Public License 1.0 (http://opensource.org/licenses/eclipse-1.0.php) +;; Eclipse Public License 1.0 (https://opensource.org/license/epl-1-0/) (ns clojure.core (:require ;; Middle comment diff --git a/utils/test-helper.el b/utils/test-helper.el index c8dfed8..b359277 100644 --- a/utils/test-helper.el +++ b/utils/test-helper.el @@ -15,7 +15,7 @@ ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License -;; along with this program. If not, see . +;; along with this program. If not, see . ;;; Commentary: From 2f834cc7d5f695e5e061963fa01b6f3af1df3006 Mon Sep 17 00:00:00 2001 From: p4v4n Date: Wed, 6 Sep 2023 22:05:01 +0530 Subject: [PATCH 186/379] Add and modify tests for checking multiple forms on same line --- clojure-mode-refactor-threading-test.el | 13 +++++++++++-- clojure-mode-sexp-test.el | 21 ++++++++++++++------- 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/clojure-mode-refactor-threading-test.el b/clojure-mode-refactor-threading-test.el index 61ad598..efd7eb1 100644 --- a/clojure-mode-refactor-threading-test.el +++ b/clojure-mode-refactor-threading-test.el @@ -247,6 +247,16 @@ (clojure-unwind '(4))) + (when-refactoring-it "should unwind correctly when multiple ->> are present on same line" + "(->> 1 inc) (->> [1 2 3 4 5] + (filter even?) + (map square))" + + "(->> 1 inc) (->> (map square (filter even? [1 2 3 4 5])))" + + (clojure-unwind) + (clojure-unwind)) + (when-refactoring-it "should unwind with function name" "(->> [1 2 3 4 5] sum @@ -299,8 +309,7 @@ (when-refactoring-it "should unwind some->>" "(some->> :b - (find {:a 1}) - val + (find {:a 1}) val (+ 5))" "(some->> (+ 5 (val (find {:a 1} :b))))" diff --git a/clojure-mode-sexp-test.el b/clojure-mode-sexp-test.el index 1db0e70..aaeb798 100644 --- a/clojure-mode-sexp-test.el +++ b/clojure-mode-sexp-test.el @@ -31,30 +31,37 @@ (wrong))" ;; make this use the native beginning of defun since this is used to ;; determine whether to use the comment aware version or not. + (expect (let ((beginning-of-defun-function nil)) + (clojure-top-level-form-p "comment"))))) + (it "should return true when multiple forms are present" + (with-clojure-buffer-point + "(+ 1 2) (comment + (wrong) + (rig|ht) + (wrong))" (expect (let ((beginning-of-defun-function nil)) (clojure-top-level-form-p "comment")))))) (describe "clojure-beginning-of-defun-function" (it "should go to top level form" (with-clojure-buffer-point - "(comment + " (comment (wrong) (wrong) (rig|ht) (wrong))" - (beginning-of-defun) + (clojure-beginning-of-defun-function) (expect (looking-at-p "(comment")))) (it "should eval top level forms inside comment forms when clojure-toplevel-inside-comment-form set to true" (with-clojure-buffer-point - "(comment - (wrong) + "(+ inc 1) (comment (wrong) - (rig|ht) + (wrong) (rig|ht) (wrong))" (let ((clojure-toplevel-inside-comment-form t)) - (beginning-of-defun)) - (expect (looking-at-p "[[:space:]]*(right)")))) + (clojure-beginning-of-defun-function)) + (expect (looking-at-p "(right)")))) (it "should go to beginning of previous top level form" (with-clojure-buffer-point From 788cc5a77caec4675cc5de7c7a2d59c2704f6330 Mon Sep 17 00:00:00 2001 From: p4v4n Date: Thu, 7 Sep 2023 13:26:29 +0530 Subject: [PATCH 187/379] Fix clojure-find-ns for ns forms preceded by whitespace (#661) Closes https://github.com/clojure-emacs/clojure-mode/issues/593 --- clojure-mode-util-test.el | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/clojure-mode-util-test.el b/clojure-mode-util-test.el index a35babb..3f6e2de 100644 --- a/clojure-mode-util-test.el +++ b/clojure-mode-util-test.el @@ -82,6 +82,23 @@ (expect (clojure-find-ns) :to-equal "foo")) (with-clojure-buffer "(ns ^:bar ^:baz foo)" (expect (clojure-find-ns) :to-equal "foo"))) + (it "should find namespaces with spaces before ns form" + (with-clojure-buffer " (ns foo)" + (expect (clojure-find-ns) :to-equal "foo"))) + (it "should skip namespaces within any comment forms" + (with-clojure-buffer "(comment + (ns foo))" + (expect (clojure-find-ns) :to-equal nil)) + (with-clojure-buffer " (ns foo) + (comment + (ns bar))" + (expect (clojure-find-ns) :to-equal "foo")) + (with-clojure-buffer " (comment + (ns foo)) + (ns bar) + (comment + (ns baz))" + (expect (clojure-find-ns) :to-equal "bar"))) (it "should find namespace declarations with nested metadata and docstrings" (with-clojure-buffer "(ns ^{:bar true} foo)" (expect (clojure-find-ns) :to-equal "foo")) From 61fc513590ca3ed788a5568623ae419cd3f554f7 Mon Sep 17 00:00:00 2001 From: Danny Freeman Date: Sun, 10 Sep 2023 13:12:41 -0400 Subject: [PATCH 188/379] clojure-ts--ensure-grammars: Add comment about use of dynamic scope --- clojure-ts-mode.el | 3 +++ 1 file changed, 3 insertions(+) diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index 10efd3e..1033810 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -828,6 +828,9 @@ forms like deftype, defrecord, reify, proxy, etc." (defun clojure-ts--ensure-grammars () "Install required language grammars if not already available." (when clojure-ts-ensure-grammars + ;; `treesit-language-source-alist' is dynamically scoped. + ;; Setting it here allows `treesit-install-language-gramamr' to pick up + ;; the grammar recipes we want without modifying what the user has set. (let ((treesit-language-source-alist clojure-ts-grammar-recipes)) (unless (treesit-language-available-p 'clojure nil) (message "Installing clojure tree-sitter grammar.") From b4b79e7c2ef44ff98d127fe27ca1be76081574ec Mon Sep 17 00:00:00 2001 From: Danny Freeman Date: Sun, 10 Sep 2023 13:29:26 -0400 Subject: [PATCH 189/379] clojure-ts--ensure-grammars: prefer looping over grammar recipes instead of manually typing each out. should be more future proof if we decide to pull in more. See suggestion https://github.com/clojure-emacs/clojure-ts-mode/commit/9af0a6b35c708309acdfeb4c0c79061b0fd4eb44#r126929527 --- clojure-ts-mode.el | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index 1033810..1122883 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -828,16 +828,16 @@ forms like deftype, defrecord, reify, proxy, etc." (defun clojure-ts--ensure-grammars () "Install required language grammars if not already available." (when clojure-ts-ensure-grammars - ;; `treesit-language-source-alist' is dynamically scoped. - ;; Setting it here allows `treesit-install-language-gramamr' to pick up - ;; the grammar recipes we want without modifying what the user has set. - (let ((treesit-language-source-alist clojure-ts-grammar-recipes)) - (unless (treesit-language-available-p 'clojure nil) - (message "Installing clojure tree-sitter grammar.") - (treesit-install-language-grammar 'clojure)) - (unless (treesit-language-available-p 'markdown_inline nil) - (message "Installing markdown tree-sitter grammar.") - (treesit-install-language-grammar 'markdown_inline))))) + (dolist (recipe clojure-ts-grammar-recipes) + (let ((grammar (car recipe))) + (unless (treesit-language-available-p grammar nil) + (message "Installing %s tree-sitter grammar" grammar) + ;; `treesit-language-source-alist' is dynamically scoped. + ;; Binding it in this let expression allows + ;; `treesit-install-language-gramamr' to pick up the grammar recipes + ;; without modifying what the user has configured themselves. + (let ((treesit-language-source-alist clojure-ts-grammar-recipes)) + (treesit-install-language-grammar grammar))))))) (defun clojure-ts-mode-variables (&optional markdown-available) "Set up initial buffer-local variables for clojure-ts-mode. From f3b8d7de0006305cffe86d5237d1c7680654fe86 Mon Sep 17 00:00:00 2001 From: Danny Freeman Date: Sun, 10 Sep 2023 13:30:58 -0400 Subject: [PATCH 190/379] Address some docstring prose warnings from flymake --- clojure-ts-mode.el | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index 1122883..05b77dd 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -138,7 +138,7 @@ Only intended for use at development time.") (modify-syntax-entry ?\\ "\\" table) ; escape table) - "Syntax table for clojure-ts-mode.") + "Syntax table for `clojure-ts-mode'.") (defconst clojure-ts--builtin-dynamic-var-regexp @@ -224,7 +224,7 @@ Only intended for use at development time.") (or "defprotocol" "defmulti" "deftype" "defrecord" "definterface" "defmethod" "defstruct") line-end)) - "A regular expression matching a symbol used to define a type") + "A regular expression matching a symbol used to define a type.") (defconst clojure-ts-type-symbol-regexp (eval-and-compile @@ -840,7 +840,7 @@ forms like deftype, defrecord, reify, proxy, etc." (treesit-install-language-grammar grammar))))))) (defun clojure-ts-mode-variables (&optional markdown-available) - "Set up initial buffer-local variables for clojure-ts-mode. + "Initialize buffer-local variables for `clojure-ts-mode'. See `clojure-ts--font-lock-settings' for usage of MARKDOWN-AVAILABLE." (setq-local comment-start ";") (setq-local treesit-font-lock-settings From d6ac60f17a702ee74b87ce3785b1fccb37a95e03 Mon Sep 17 00:00:00 2001 From: Danny Freeman Date: Sun, 10 Sep 2023 13:41:29 -0400 Subject: [PATCH 191/379] declare new treesit C functions used in this package remove declared function that is no longer in use --- clojure-ts-mode.el | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index 05b77dd..41d09be 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -57,10 +57,11 @@ (require 'lisp-mnt) (declare-function treesit-parser-create "treesit.c") +(declare-function treesit-node-eq "treesit.c") (declare-function treesit-node-type "treesit.c") +(declare-function treesit-node-parent "treesit.c") (declare-function treesit-node-child "treesit.c") (declare-function treesit-node-child-by-field-name "treesit.c") -(declare-function treesit-node-prev-sibling "treesit.c") (defgroup clojure-ts nil "Major mode for editing Clojure code with tree-sitter." From e3642284816cd4a175e390ec8daaeed9a934d3f3 Mon Sep 17 00:00:00 2001 From: p4v4n Date: Mon, 11 Sep 2023 22:20:39 +0530 Subject: [PATCH 192/379] Make `clojure-find-ns` work when preceded by other forms (#664) Fixes #656 --- clojure-mode-sexp-test.el | 25 ++++++++++++++++++++++--- clojure-mode-util-test.el | 21 +++++++++++++++++++++ 2 files changed, 43 insertions(+), 3 deletions(-) diff --git a/clojure-mode-sexp-test.el b/clojure-mode-sexp-test.el index aaeb798..11bf519 100644 --- a/clojure-mode-sexp-test.el +++ b/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/clojure-mode-util-test.el b/clojure-mode-util-test.el index 3f6e2de..565773d 100644 --- a/clojure-mode-util-test.el +++ b/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" From a90be05b522a4153de617d3294d90e9ef58000f8 Mon Sep 17 00:00:00 2001 From: Danny Freeman Date: Tue, 12 Sep 2023 00:21:40 -0400 Subject: [PATCH 193/379] Update issue template with information about grammar versions --- .github/ISSUE_TEMPLATE.md | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index e3e5cda..aa0669a 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -1,5 +1,5 @@ *Use the template below when reporting bugs. Please, make sure that -you're running the latest stable clojure-mode and that the problem you're reporting +you're running the latest stable clojure-ts-mode and that the problem you're reporting hasn't been reported (and potentially fixed) already.* **Please, remove all of the placeholder text (the one in italics) in your final report!** @@ -21,9 +21,24 @@ a problem will expedite its solution.* clojure-ts-mode-display-version`. Here's an example:* ``` -clojure-ts-mode (version 5.2.0) +clojure-ts-mode (version 0.1.5) ``` +### tree-sitter-clojure grammar version + +*E.g. v0.0.12* + +*Please make sure you are using compatible tree-sitter grammars. +See the variable clojure-ts-grammar-recipes for the current recommend versions. +They should be installed automatically if not found. +However, some linux distributions package these same grammars and Emacs will use them if found.* + +**If you are not sure what version you are using, try running +M-x treesit-install-language-grammar clojure y, and use the values +https://github.com/sogaiu/tree-sitter-clojure.git for the URL, +v0.0.12 for the TAG and default values for the remaining options. +Then see if the problem still persists.** + ### Emacs version *E.g. 29.1* (use C-h C-a to see it) From 5231c348e509cff91edd1ec59d7a59645395da15 Mon Sep 17 00:00:00 2001 From: Roman Rudakov Date: Tue, 12 Sep 2023 12:57:35 +0200 Subject: [PATCH 194/379] [#20] Highlight methods in some forms --- CHANGELOG.md | 4 +++- clojure-ts-mode.el | 45 +++++++++++++++++++++++++++++++++++++++++---- test/test.clj | 43 ++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 86 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 59fedbd..ae421b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ - Highlight "\`quoted-symbols\` in docs strings like this." - This feature uses a nested markdown parser. If the parser is not available this feature should be silently disabled. +- Highlight methods for `deftype`, `defrecord`, `defprotocol`, `reify` and `definterface` + forms ([#20](https://github.com/clojure-emacs/clojure-ts-mode/issues/20)). ## 0.1.5 @@ -29,7 +31,7 @@ ## 0.1.2 -- Add a syntax table from clojure-mode. [712dc772fd38111c1e35fe60e4dbe7ac83032bd6](https://github.com/clojure-emacs/clojure-ts-mode/commit/712dc772fd38111c1e35fe60e4dbe7ac83032bd6). +- Add a syntax table from clojure-mode. [712dc772fd38111c1e35fe60e4dbe7ac83032bd6](https://github.com/clojure-emacs/clojure-ts-mode/commit/712dc772fd38111c1e35fe60e4dbe7ac83032bd6). - Better support for `thing-at-point` driven functionality. - Thank you @jasonjckn for this contribution. - Add 3 derived major modes [4dc853df16ba09d10dc3a648865e681679c17606](https://github.com/clojure-emacs/clojure-ts-mode/commit/4dc853df16ba09d10dc3a648865e681679c17606) diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index 41d09be..70c7f02 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -205,7 +205,7 @@ Only intended for use at development time.") (defconst clojure-ts--definition-symbol-regexp (rx line-start - (or (group (or "ns" "fn")) + (or (group "fn") (group "def" (+ (or alnum ;; What are valid characters for symbols? @@ -347,9 +347,42 @@ with the markdown_inline grammar." :language 'clojure `(((list_lit :anchor (sym_lit (sym_name) @def) :anchor (sym_lit (sym_name) @font-lock-function-name-face)) - (:match ,clojure-ts--definition-symbol-regexp @def)) + (:match ,(rx-to-string + `(seq bol + (or + "defn" + "defn-" + "defmulti" + "defmethod" + "deftest" + "deftest-" + "defmacro" + "definline") + eol)) + @def)) ((anon_fn_lit - marker: "#" @font-lock-property-face))) + marker: "#" @font-lock-property-face)) + ;; Methods implementation + ((list_lit + ((sym_lit name: (sym_name) @def) + ((:match ,(rx-to-string + `(seq bol + (or + "defrecord" + "definterface" + "deftype" + "defprotocol") + eol)) + @def))) + :anchor + (sym_lit (sym_name) @font-lock-type-face) + (list_lit + (sym_lit name: (sym_name) @font-lock-function-name-face)))) + ((list_lit + ((sym_lit name: (sym_name) @def) + ((:equal "reify" @def))) + (list_lit + (sym_lit name: (sym_name) @font-lock-function-name-face))))) :feature 'variable ;; def, defonce :language 'clojure @@ -370,7 +403,11 @@ with the markdown_inline grammar." value: (sym_lit (sym_name) @font-lock-type-face)) (old_meta_lit marker: "#^" @font-lock-operator-face - value: (sym_lit (sym_name) @font-lock-type-face))) + value: (sym_lit (sym_name) @font-lock-type-face)) + ;; Highlight namespace + ((list_lit :anchor (sym_lit (sym_name) @def) + :anchor (sym_lit (sym_name) @font-lock-type-face)) + (:equal "ns" @def))) :feature 'metadata :language 'clojure diff --git a/test/test.clj b/test/test.clj index ae25da8..ce4c029 100644 --- a/test/test.clj +++ b/test/test.clj @@ -29,7 +29,7 @@ ;; examples of valid namespace definitions (comment (ns .validns) - + (ns =validns) (ns .ValidNs=<>?+|?*.) (ns ValidNs<>?+|?*.b*ar.ba*z) @@ -289,3 +289,44 @@ clojure.core/map (def ^Integer x 1) +(comment + (defrecord TestRecord [field] + AutoCloseable + (close [this] + (.close this))) + + (reify + AutoCloseable + (close [this] (.close this)) + + (another [this arg] + (implement this arg))) + + (definterface MyInterface + (^String name []) + (^double mass [])) + + (defmulti my-method :hello :default ::default) + + (defmethod my-method :world + [_] + (println "Hi")) + + (deftype ImageSelection [data] + Transferable + (getTransferDataFlavors + [this] + (into-array DataFlavor [DataFlavor/imageFlavor])) + + (isDataFlavorSupported + [this flavor] + (= DataFlavor/imageFlavor flavor)) + + (getTransferData + [this flavor] + (when (= DataFlavor/imageFlavor flavor) + (.getImage (ImageIcon. data))))) + + (defprotocol P + (foo [this]) + (bar-me [this] [this y]))) From 5f16fb8dbcaf9476cc6d5baab958b730cb60e854 Mon Sep 17 00:00:00 2001 From: Danny Freeman Date: Tue, 12 Sep 2023 22:06:09 -0400 Subject: [PATCH 195/379] Re-enable workaround for treesit-transpose-sexps bug See issue #17, specifically https://github.com/clojure-emacs/clojure-ts-mode/issues/17#issuecomment-1705699794 --- CHANGELOG.md | 1 - clojure-ts-mode.el | 11 ++++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 59fedbd..33254bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,6 @@ ## main (unreleased) -- Re-enable treesit-transpose-sexps on Emacs 30 after fixes released by @casouri. - Pin grammar revision in treesit-language-source-alist - Make font lock feature list more conforming with recommendations - (See treesit-font-lock-level documentation for more information.) diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index 41d09be..54a808e 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -790,8 +790,8 @@ forms like deftype, defrecord, reify, proxy, etc." (defconst clojure-ts--thing-settings `((clojure - ((sexp ,(regexp-opt clojure-ts--sexp-nodes)) - (text ,(regexp-opt '("comment"))))))) + (sexp ,(regexp-opt clojure-ts--sexp-nodes) + text ,(regexp-opt '("comment")))))) (defvar clojure-ts-mode-map (let ((map (make-sparse-keymap))) @@ -883,7 +883,12 @@ See `clojure-ts--font-lock-settings' for usage of MARKDOWN-AVAILABLE." (when (eq clojure-ts--debug 'font-lock) (setq-local treesit--font-lock-verbose t)) (treesit-inspect-mode)) - (treesit-major-mode-setup)))) + (treesit-major-mode-setup) + ;; Workaround for treesit-transpose-sexps not correctly working with + ;; treesit-thing-settings on Emacs 30. + ;; Once treesit-transpose-sexps it working again this can be removed + (when (fboundp 'transpose-sexps-default-function) + (setq-local transpose-sexps-function #'transpose-sexps-default-function))))) ;;;###autoload (define-derived-mode clojurescript-ts-mode clojure-ts-mode "ClojureScript[TS]" From 6ea4196ebc4a5fca6df5593e8ed3a45afc4ed593 Mon Sep 17 00:00:00 2001 From: Danny Freeman Date: Thu, 14 Sep 2023 23:03:40 -0400 Subject: [PATCH 196/379] Indicate internal defconst with clojure-ts-- prefix --- clojure-ts-mode.el | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index 6b588e6..224f3b9 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -227,7 +227,7 @@ Only intended for use at development time.") line-end)) "A regular expression matching a symbol used to define a type.") -(defconst clojure-ts-type-symbol-regexp +(defconst clojure-ts--type-symbol-regexp (eval-and-compile (rx line-start (or "deftype" "defrecord" @@ -727,7 +727,7 @@ forms like deftype, defrecord, reify, proxy, etc." ;; auncle: gender neutral sibling of parent, aka child of grandparent (first-auncle (treesit-node-child grandparent 0 t))) (and (clojure-ts--list-node-p grandparent) - (clojure-ts--symbol-matches-p clojure-ts-type-symbol-regexp + (clojure-ts--symbol-matches-p clojure-ts--type-symbol-regexp first-auncle))))) (defvar clojure-ts--threading-macro From ec48877dd0394d176dd08334ee48d1ff48643453 Mon Sep 17 00:00:00 2001 From: Danny Freeman Date: Fri, 15 Sep 2023 00:22:19 -0400 Subject: [PATCH 197/379] Fix ns docstring highlighting regression This also begins the process of making our symbol matching regular expressions user extensible (see issue #15). There are more to convert from regexps to normal lists, but I want to take my time and make sure I get the names down correctly. It is important to get maximum reuse so users don't have to add their fancy def-whatever to 4 different lists. --- clojure-ts-mode.el | 72 ++++++++++++++++++++++++++++----------------- test/docstrings.clj | 67 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 112 insertions(+), 27 deletions(-) create mode 100644 test/docstrings.clj diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index 224f3b9..299fa42 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -202,17 +202,26 @@ Only intended for use at development time.") '((t (:inherit font-lock-string-face))) "Face used to font-lock Clojure character literals.") -(defconst clojure-ts--definition-symbol-regexp - (rx - line-start - (or (group "fn") - (group "def" - (+ (or alnum - ;; What are valid characters for symbols? - ;; is a negative match better? - "-" "_" "!" "@" "#" "$" "%" "^" "&" - "*" "|" "?" "<" ">" "+" "=" ":")))) - line-end)) +(defun clojure-ts-symbol-regexp (symbols) + "Return a regular expression that matches one of SYMBOLS exactly." + (concat "^" (regexp-opt symbols) "$")) + +(defvar clojure-ts-function-docstring-symbols + '("definline" + "defmulti" + "defmacro" + "defn" + "defn-" + "defprotocol" + "ns") + "Symbols that accept an optional docstring as their second argument.") + +(defvar clojure-ts-definition-docstring-symbols + '("def") + "Symbols that accept an optional docstring as their second argument. +Any symbols added here should only treat their second argument as a docstring +if a third argument (the value) is provided. +\"def\" is the only builtin Clojure symbol that behaves like this.") (defconst clojure-ts--variable-definition-symbol-regexp (eval-and-compile @@ -244,40 +253,49 @@ Only intended for use at development time.") (defun clojure-ts--docstring-query (capture-symbol) "Return a query that captures docstrings with CAPTURE-SYMBOL." - `(;; Captures docstrings in def, defonce - ((list_lit :anchor (sym_lit) @def_symbol + `(;; Captures docstrings in def + ((list_lit :anchor (sym_lit) @_def_symbol + :anchor (comment) :? :anchor (sym_lit) ; variable name + :anchor (comment) :? :anchor (str_lit) ,capture-symbol :anchor (_)) ; the variable's value - (:match ,clojure-ts--variable-definition-symbol-regexp @def_symbol)) + (:match ,(clojure-ts-symbol-regexp clojure-ts-definition-docstring-symbols) + @_def_symbol)) ;; Captures docstrings in metadata of definitions - ((list_lit :anchor (sym_lit) @def_symbol + ((list_lit :anchor (sym_lit) @_def_symbol + :anchor (comment) :? :anchor (sym_lit (meta_lit value: (map_lit - (kwd_lit) @doc-keyword + (kwd_lit) @_doc-keyword :anchor (str_lit) ,capture-symbol)))) ;; We're only supporting this on a fixed set of defining symbols ;; Existing regexes don't encompass def and defn ;; Naming another regex is very cumbersome. - (:match ,(regexp-opt '("def" "defonce" "defn" "defn-" "defmacro" "ns" - "defmulti" "definterface" "defprotocol" - "deftype" "defrecord" "defstruct")) - @def_symbol) - (:equal @doc-keyword ":doc")) + (:match ,(clojure-ts-symbol-regexp + '("def" "defonce" "defn" "defn-" "defmacro" "ns" + "defmulti" "definterface" "defprotocol" + "deftest" "deftest-" + "deftype" "defrecord" "defstruct")) + @_def_symbol) + (:equal @_doc-keyword ":doc")) ;; Captures docstrings defn, defmacro, ns, and things like that - ((list_lit :anchor (sym_lit) @def_symbol + ((list_lit :anchor (sym_lit) @_def_symbol + :anchor (comment) :? :anchor (sym_lit) ; function_name + :anchor (comment) :? :anchor (str_lit) ,capture-symbol) - (:match ,clojure-ts--definition-symbol-regexp @def_symbol)) + (:match ,(clojure-ts-symbol-regexp clojure-ts-function-docstring-symbols) + @_def_symbol)) ;; Captures docstrings in defprotcol, definterface - ((list_lit :anchor (sym_lit) @def_symbol + ((list_lit :anchor (sym_lit) @_def_symbol (list_lit :anchor (sym_lit) (vec_lit) :* (str_lit) ,capture-symbol :anchor) :*) - (:match ,clojure-ts--interface-def-symbol-regexp @def_symbol)))) + (:match ,clojure-ts--interface-def-symbol-regexp @_def_symbol)))) (defvar clojure-ts--treesit-range-settings (treesit-range-rules @@ -752,7 +770,7 @@ forms like deftype, defrecord, reify, proxy, etc." (and (treesit-node-eq node (treesit-node-child parent 2 t)) (let ((first-auncle (treesit-node-child parent 0 t))) (clojure-ts--symbol-matches-p - clojure-ts--definition-symbol-regexp + (regexp-opt clojure-ts-function-docstring-symbols) first-auncle))))) (defun clojure-ts--match-def-docstring (node) @@ -765,7 +783,7 @@ forms like deftype, defrecord, reify, proxy, etc." (treesit-node-child parent 3 t) (let ((first-auncle (treesit-node-child parent 0 t))) (clojure-ts--symbol-matches-p - clojure-ts--variable-definition-symbol-regexp + (regexp-opt clojure-ts-definition-docstring-symbols) first-auncle))))) (defun clojure-ts--match-method-docstring (node) diff --git a/test/docstrings.clj b/test/docstrings.clj new file mode 100644 index 0000000..c3bb2a7 --- /dev/null +++ b/test/docstrings.clj @@ -0,0 +1,67 @@ +(ns clojure-ts-mode.docstrings + "This is a namespace + See my famous `fix-bug` macro if you need help." + (:require [clojure.test :refer [deftest]]) + (:import (java.util UUID))) + +(def foo ;;asdf + "I'm a value") +(def bar "I'm a docstring" "and I'm a value") + +(defonce ^{:doc "gotta document in metadata."} baz + "Did you know defonce doesn't have a docstring arity like def?") + +(def foobar + ;; Comments shouldn't disrupt docstring highlighting + "I'm a docstring" + 123) + +(defn ;;asdf + foobarbaz ;;asdf + "I'm the docstring!" ;;asdf + [x] + (inc x)) + +(;; starting comments break docstrings + defn busted! + "We really need to anchor symbols like defn to the front of the list. +I don't want every query to have to check for comments. +Don't format code this way." + [] + nil) + +(defn buzz "Looking for `fizz`" + [x] + (when (zero? (% x 5)) + "buzz")) + +(defn- fizz + "Pairs well with `buzz`" + [x] + (when (zero? (% x 3)) + "fizz")) + +(defmacro fix-bug + "Fixes most known bugs." + [& body] + `(try + ~@body + (catch Throwable _ + nil))) + +(definline never-used-this ":)" [x] x) + +(deftype ^{:doc "asdf" :something-else "asdf"} T + java.lang.Closeable + (close [this] + (print "done"))) + +(defprotocol Fooable + (foo [this] + "Does foo")) + +(definterface Barable + (^String bar [] "Does bar")) + +(deftest ^{:doc "doctest"} some-test + (is (= 1 2))) From 5e7506e61401f92df1f86677051b55fec0ad6ce8 Mon Sep 17 00:00:00 2001 From: Danny Freeman Date: Fri, 15 Sep 2023 00:35:18 -0400 Subject: [PATCH 198/379] Highlight a couple clojure.test symbols as builtins --- clojure-ts-mode.el | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index 299fa42..d4111d5 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -191,7 +191,9 @@ Only intended for use at development time.") "when" "when-first" "when-let" "when-not" "when-some" "while" "with-bindings" "with-in-str" "with-loading-context" "with-local-vars" "with-open" "with-out-str" "with-precision" - "with-redefs" "with-redefs-fn")) + "with-redefs" "with-redefs-fn" + ;; Commonly used clojure.test functions + "deftest" "deftest-" "is" "are" "testing")) "$"))) (defface clojure-ts-keyword-face From a7b9654488693cdc9057a91410f74de42a397d1b Mon Sep 17 00:00:00 2001 From: Danny Freeman Date: Sun, 24 Sep 2023 09:46:50 -0400 Subject: [PATCH 199/379] Add preliminary support for jank This creates a new derived mode for the clojure dialect jank https://jank-lang.org/ See issue #23 for future work and https://github.com/jank-lang/jank/issues/24 for the expressed desire to support nested c++ --- clojure-ts-mode.el | 14 +++++++++++++- test/native.jank | 10 ++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 test/native.jank diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index d4111d5..8d40c75 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -870,6 +870,11 @@ forms like deftype, defrecord, reify, proxy, etc." (set-keymap-parent map clojure-ts-mode-map) map)) +(defvar clojure-jank-ts-mode-map + (let ((map (make-sparse-keymap))) + (set-keymap-parent map clojure-ts-mode-map) + map)) + (defun clojure-ts-mode-display-version () "Display the current `clojure-mode-version' in the minibuffer." (interactive) @@ -965,9 +970,16 @@ See `clojure-ts--font-lock-settings' for usage of MARKDOWN-AVAILABLE." \\{clojure-dart-ts-mode-map}") +;;;###autoload +(define-derived-mode clojure-jank-ts-mode clojure-ts-mode "Jank[TS]" + "Major mode for editing Jank code. + +\\{clojure-jank-ts-mode-map}") + (defun clojure-ts--register-novel-modes () "Set up Clojure modes not present in progenitor clojure-mode.el." - (add-to-list 'auto-mode-alist '("\\.cljd\\'" . clojure-dart-ts-mode))) + (add-to-list 'auto-mode-alist '("\\.cljd\\'" . clojure-dart-ts-mode)) + (add-to-list 'auto-mode-alist '("\\.jank\\'" . clojure-jank-ts-mode))) ;; Redirect clojure-mode to clojure-ts-mode if clojure-mode is present (if (require 'clojure-mode nil 'noerror) diff --git a/test/native.jank b/test/native.jank new file mode 100644 index 0000000..bf07596 --- /dev/null +++ b/test/native.jank @@ -0,0 +1,10 @@ +(defn create-vertex-shader! [] + (native/raw "__value = make_box(glCreateShader(GL_VERTEX_SHADER));")) + +(defn set-shader-source! [shader source] + (native/raw "auto const shader(detail::to_int(~{ shader })); + auto const &source(detail::to_string(~{ source })); + __value = make_box(glShaderSource(shader, 1, &source.data, nullptr));")) + +(defn compile-shader! [shader] + (native/raw "__value = make_box(glCompileShader(detail::to_int(~{ shader })));")) From 8e61fe8ff4795975ec9e225af931d3a514c99445 Mon Sep 17 00:00:00 2001 From: Danny Freeman Date: Sun, 24 Sep 2023 23:15:42 -0400 Subject: [PATCH 200/379] Release v0.2.0 --- CHANGELOG.md | 16 +++++++++++++++- clojure-ts-mode.el | 2 +- doc/release-process | 25 +++++++++++++++++++++++++ 3 files changed, 41 insertions(+), 2 deletions(-) create mode 100644 doc/release-process diff --git a/CHANGELOG.md b/CHANGELOG.md index 8fd7879..c6bd008 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,16 +2,30 @@ ## main (unreleased) +## 0.2.0 + - Pin grammar revision in treesit-language-source-alist + - [bd61a7fb281b7b0b1d2e20d19ab5d46cbcdc6c1e](https://github.com/clojure-emacs/clojure-ts-mode/commit/bd61a7fb281b7b0b1d2e20d19ab5d46cbcdc6c1e) - Make font lock feature list more conforming with recommendations - - (See treesit-font-lock-level documentation for more information.) + - (See treesit-font-lock-level documentation for more information.) + - [2225190ee57ef667d69f2cd740e0137810bc38e7](https://github.com/clojure-emacs/clojure-ts-mode/commit/2225190ee57ef667d69f2cd740e0137810bc38e7) - Highlight docstrings in interface, protocol, and variable definitions + - [9af0a6b35c708309acdfeb4c0c79061b0fd4eb44](https://github.com/clojure-emacs/clojure-ts-mode/commit/9af0a6b35c708309acdfeb4c0c79061b0fd4eb44) - Add support for semantic indentation (now the default) + - [ae2e2486010554cfeb12f06a1485b4d81609d964](https://github.com/clojure-emacs/clojure-ts-mode/commit/ae2e2486010554cfeb12f06a1485b4d81609d964) + - [ca3914aa7aa9645ab244658f8db781cc6f95111e](https://github.com/clojure-emacs/clojure-ts-mode/commit/ca3914aa7aa9645ab244658f8db781cc6f95111e) + - [85871fdbc831b3129dae5762e9c247d453c35e15](https://github.com/clojure-emacs/clojure-ts-mode/commit/85871fdbc831b3129dae5762e9c247d453c35e15) + - [ff5d7e13dc53cc5da0e8139b04e02d90f61d9065](https://github.com/clojure-emacs/clojure-ts-mode/commit/ff5d7e13dc53cc5da0e8139b04e02d90f61d9065) - Highlight "\`quoted-symbols\` in docs strings like this." - This feature uses a nested markdown parser. If the parser is not available this feature should be silently disabled. + - [9af0a6b35c708309acdfeb4c0c79061b0fd4eb44](https://github.com/clojure-emacs/clojure-ts-mode/commit/9af0a6b35c708309acdfeb4c0c79061b0fd4eb44) - Highlight methods for `deftype`, `defrecord`, `defprotocol`, `reify` and `definterface` forms ([#20](https://github.com/clojure-emacs/clojure-ts-mode/issues/20)). + - [5231c348e509cff91edd1ec59d7a59645395da15](https://github.com/clojure-emacs/clojure-ts-mode/commit/5231c348e509cff91edd1ec59d7a59645395da15) + - Thank you rrudakov for this contribution. +- Add derived `clojure-jank-ts-mode` for the [Jank](https://github.com/jank-lang/jank) dialect of clojure + - [a7b9654488693cdc9057a91410f74de42a397d1b](https://github.com/clojure-emacs/clojure-ts-mode/commit/a7b9654488693cdc9057a91410f74de42a397d1b) ## 0.1.5 diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index 8d40c75..1df682e 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -6,7 +6,7 @@ ;; Maintainer: Danny Freeman ;; URL: http://github.com/clojure-emacs/clojure-ts-mode ;; Keywords: languages clojure clojurescript lisp -;; Version: 0.1.5 +;; Version: 0.2.0 ;; Package-Requires: ((emacs "29")) ;; This file is not part of GNU Emacs. diff --git a/doc/release-process b/doc/release-process new file mode 100644 index 0000000..37668c0 --- /dev/null +++ b/doc/release-process @@ -0,0 +1,25 @@ +Instructions for releasing a new version. + +Review the ## main (unreleased) heading in CHANGELOG.md. Add links to commits +for each entry so users can reference them. Add a thank you note for entries +contributed by people who are not primary maintainers. + +Add a new heading in the CHANGELOG.md file corresponding to the next version +number. Following this the ## main (unreleased) heading should be empty + +Update clojure-ts-mode.el Version: property in the package comment at the top of +the file to match the upcomming version number. + +Create a new commit on main branch with all the above changes. + +Add a new tag for the corresponding version on the commit just created. This is +needed by MELPA. The tag should follow the format (without quotes) "vM.m.p" +where M is the major number, m is the minor number, and p is the patch number. +The tag should have a comment referring readers to the CHANGELOG.md file. It +should read something like + + Release v.M.m.p + + See CHANGELOG.md for more details + +Make sure gpg signing is enabled when creating the commit and tags. From 6bccf8c25ec3e7f074d2563273cb8de57e201d79 Mon Sep 17 00:00:00 2001 From: p4v4n Date: Sun, 5 Nov 2023 18:24:09 +0530 Subject: [PATCH 201/379] Use Eldev (#669) Closes https://github.com/clojure-emacs/clojure-mode/issues/666 --- clojure-mode-bytecomp-warnings.el | 40 ------------------------- clojure-mode-convert-collection-test.el | 1 + clojure-mode-refactor-add-arity-test.el | 1 + clojure-mode-util-test.el | 2 +- test-checks.el | 30 ------------------- utils/test-helper.el | 2 +- 6 files changed, 4 insertions(+), 72 deletions(-) delete mode 100644 clojure-mode-bytecomp-warnings.el delete mode 100644 test-checks.el diff --git a/clojure-mode-bytecomp-warnings.el b/clojure-mode-bytecomp-warnings.el deleted file mode 100644 index 41b3230..0000000 --- a/clojure-mode-bytecomp-warnings.el +++ /dev/null @@ -1,40 +0,0 @@ -;;; clojure-mode-bytecomp-warnings.el --- Check for byte-compilation problems -*- lexical-binding: t; -*- - -;; Copyright © 2012-2021 Bozhidar Batsov and contributors -;; -;; This program is free software: you can redistribute it and/or modify -;; it under the terms of the GNU General Public License as published by -;; the Free Software Foundation, either version 3 of the License, or -;; (at your option) any later version. - -;; This program is distributed in the hope that it will be useful, -;; but WITHOUT ANY WARRANTY; without even the implied warranty of -;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -;; GNU General Public License for more details. - -;; You should have received a copy of the GNU General Public License -;; along with this program. If not, see . - -;; This file is not part of GNU Emacs. - -;;; Commentary: - -;; This is a script to be loaded while visiting a `clojure-mode' source file. -;; It will prepare all requirements and then byte-compile the file and signal an -;; error on any warning. For example: -;; -;; emacs -Q --batch -l test/clojure-mode-bytecomp-warnings.el clojure-mode.el - -;; This assumes that all `clojure-mode' dependencies are already on the package -;; dir (probably from running `cask install'). - -(setq load-prefer-newer t) -(add-to-list 'load-path (expand-file-name "./")) -(require 'package) -(package-generate-autoloads 'clojure-mode default-directory) -(package-initialize) -(load-file "clojure-mode-autoloads.el") -(setq byte-compile-error-on-warn t) -(batch-byte-compile) - -;;; clojure-mode-bytecomp-warnings.el ends here diff --git a/clojure-mode-convert-collection-test.el b/clojure-mode-convert-collection-test.el index 28c5977..14e5291 100644 --- a/clojure-mode-convert-collection-test.el +++ b/clojure-mode-convert-collection-test.el @@ -27,6 +27,7 @@ (require 'clojure-mode) (require 'buttercup) +(require 'test-helper "test/utils/test-helper") (describe "clojure-convert-collection-to-map" (when-refactoring-it "should convert a list to a map" diff --git a/clojure-mode-refactor-add-arity-test.el b/clojure-mode-refactor-add-arity-test.el index 9c75f12..5f1c5fb 100644 --- a/clojure-mode-refactor-add-arity-test.el +++ b/clojure-mode-refactor-add-arity-test.el @@ -24,6 +24,7 @@ (require 'clojure-mode) (require 'buttercup) +(require 'test-helper "test/utils/test-helper") (describe "clojure-add-arity" diff --git a/clojure-mode-util-test.el b/clojure-mode-util-test.el index 565773d..6157a3d 100644 --- a/clojure-mode-util-test.el +++ b/clojure-mode-util-test.el @@ -46,7 +46,7 @@ (bb-edn-src (expand-file-name "src" temp-dir))) (write-region "{}" nil bb-edn) (make-directory bb-edn-src) - (expect (clojure-project-dir bb-edn-src) + (expect (expand-file-name (clojure-project-dir bb-edn-src)) :to-equal (file-name-as-directory temp-dir)))))) (describe "clojure-project-relative-path" diff --git a/test-checks.el b/test-checks.el deleted file mode 100644 index a4b4208..0000000 --- a/test-checks.el +++ /dev/null @@ -1,30 +0,0 @@ -;; This is a script to be loaded from the root `clojure-mode' directory. It will -*- lexical-binding: t; -*- -;; prepare all requirements and then run `check-declare-directory' on -;; `default-directory'. For example: emacs -Q --batch -l test/test-checkdoc.el - -;; This assumes that all `clojure-mode' dependencies are already on the package -;; dir (probably from running `cask install'). - -(add-to-list 'load-path (expand-file-name "./")) -(require 'package) -(require 'check-declare) -(package-initialize) - -;; disable some annoying (or non-applicable) checkdoc checks -(setq checkdoc-package-keywords-flag nil) -(setq checkdoc-arguments-in-order-flag nil) -(setq checkdoc-verb-check-experimental-flag nil) - -(let ((files (directory-files default-directory t - "\\`[^.].*\\.el\\'" t))) - - ;; `checkdoc-file' was introduced in Emacs 25 - (when (fboundp 'checkdoc-file) - (dolist (file files) - (checkdoc-file file)) - (when (get-buffer "*Warnings*") - (message "Failing due to checkdoc warnings...") - (kill-emacs 1))) - - (when (apply #'check-declare-files files) - (kill-emacs 1))) diff --git a/utils/test-helper.el b/utils/test-helper.el index b359277..e7894f0 100644 --- a/utils/test-helper.el +++ b/utils/test-helper.el @@ -26,7 +26,7 @@ (message "Running tests on Emacs %s" emacs-version) (let* ((current-file (if load-in-progress load-file-name (buffer-file-name))) - (source-directory (locate-dominating-file current-file "Cask")) + (source-directory (locate-dominating-file current-file "Eldev")) ;; Do not load outdated byte code for tests (load-prefer-newer t)) ;; Load the file under test From 27915a0e40be75c9f112d213ae30dec52831a6da Mon Sep 17 00:00:00 2001 From: Dave Liepmann Date: Fri, 24 Nov 2023 08:27:44 +0100 Subject: [PATCH 202/379] Don't highlight vars with colons as keywords (#670) Changes syntax highlighting regexp for keywords to match a colon/double-colon only at the beginning of a word, not in the middle. This allows local vars like `foo:bar` to be highlighted correctly instead of like an unknown symbol for the part before the colon and a keyword for the rest. Fixes #653 --- clojure-mode-font-lock-test.el | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/clojure-mode-font-lock-test.el b/clojure-mode-font-lock-test.el index bb6ef74..5e578ba 100644 --- a/clojure-mode-font-lock-test.el +++ b/clojure-mode-font-lock-test.el @@ -262,6 +262,13 @@ DESCRIPTION is the description of the spec." (9 10 nil) (11 16 nil)) + ("(colons:are:okay)" + (2 16 nil)) + + ("(some-ns/colons:are:okay)" + (2 8 font-lock-type-face) + (9 24 nil)) + ("(oneword/ve/yCom|pLex.stu-ff)" (2 8 font-lock-type-face) (9 10 nil) @@ -715,6 +722,19 @@ DESCRIPTION is the description of the spec." (10 10 default) (11 30 clojure-keyword-face))) + (when-fontifying-it "should handle keywords with colons" + (":a:a" + (1 4 clojure-keyword-face)) + + (":a:a/:a" + (1 7 clojure-keyword-face)) + + ("::a:a" + (1 5 clojure-keyword-face)) + + ("::a.a:a" + (1 7 clojure-keyword-face))) + (when-fontifying-it "should handle very complex keywords" (" :ve/yCom|pLex.stu-ff" (3 4 font-lock-type-face) @@ -824,7 +844,10 @@ DESCRIPTION is the description of the spec." (when-fontifying-it "should handle variables defined with def" ("(def foo 10)" (2 4 font-lock-keyword-face) - (6 8 font-lock-variable-name-face))) + (6 8 font-lock-variable-name-face)) + ("(def foo:bar 10)" + (2 4 font-lock-keyword-face) + (6 12 font-lock-variable-name-face))) (when-fontifying-it "should handle variables definitions of type string" ("(def foo \"hello\")" From 29e37c2b0040e165774fb0641eb40f523d1cbb72 Mon Sep 17 00:00:00 2001 From: Dave Liepmann Date: Fri, 24 Nov 2023 18:21:41 +0100 Subject: [PATCH 203/379] [Fix #671] Font-lock properly multi-digit lambda args (#672) --- clojure-mode-font-lock-test.el | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/clojure-mode-font-lock-test.el b/clojure-mode-font-lock-test.el index 5e578ba..e25fb57 100644 --- a/clojure-mode-font-lock-test.el +++ b/clojure-mode-font-lock-test.el @@ -901,13 +901,24 @@ DESCRIPTION is the description of the spec." (2 3 font-lock-keyword-face) ( 5 7 font-lock-function-name-face))) - (when-fontifying-it "should handle lambda-params" + (when-fontifying-it "should handle lambda-params %, %1, %n..." ("#(+ % %2 %3 %&)" (5 5 font-lock-variable-name-face) (7 8 font-lock-variable-name-face) (10 11 font-lock-variable-name-face) (13 14 font-lock-variable-name-face))) + (when-fontifying-it "should handle multi-digit lambda-params" + ;; % args with >1 digit are rare and unidiomatic but legal up to + ;; `MAX_POSITIONAL_ARITY` in Clojure's compiler, which as of today is 20 + ("#(* %10 %15 %19 %20)" + ;; it would be better if this were just `font-lock-variable-name-face` but + ;; it seems to work as-is + (5 7 various-faces) + (9 11 font-lock-variable-name-face) + (13 15 font-lock-variable-name-face) + (17 19 various-faces))) + (when-fontifying-it "should handle nils" ("(= nil x)" (4 6 font-lock-constant-face)) From 5c52695acf18b4f400b684fe2730d38be0b779e8 Mon Sep 17 00:00:00 2001 From: Dieter Komendera Date: Wed, 7 Feb 2024 20:12:06 +0100 Subject: [PATCH 204/379] [Fix #30] Add clojure-ts-toplevel-inside-comment-form (#31) This is the equivalent to clojure-toplevel-inside-comment-form in clojure-mode. --- CHANGELOG.md | 2 ++ README.md | 8 ++++++++ clojure-ts-mode.el | 11 ++++++++++- 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c6bd008..238bb73 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## main (unreleased) +- Add custom option `clojure-ts-toplevel-inside-comment-form` as an equivalent to `clojure-toplevel-inside-comment-form` in clojure-mode (#30) + ## 0.2.0 - Pin grammar revision in treesit-language-source-alist diff --git a/README.md b/README.md index 9dc5438..15dfffa 100644 --- a/README.md +++ b/README.md @@ -39,6 +39,14 @@ Too highlight entire rich `comment` expression with the comment font face, set By default this is `nil`, so that anything within a `comment` expression is highlighted like regular clojure code. +### Navigation and Evaluation + +To make forms inside of `(comment ...)` forms appear as toplevel forms for evaluation and navigation, set + +``` emacs-lisp +(setq clojure-ts-toplevel-inside-comment-form t) +``` + ## Rationale [clojure-mode](https://github.com/clojure-emacs/clojure-mode) has served us well diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index 1df682e..5e50ffa 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -93,6 +93,12 @@ itself." :type 'boolean :package-version '(clojure-ts-mode . "0.2.0")) +(defcustom clojure-ts-toplevel-inside-comment-form nil + "Eval top level forms inside comment forms instead of the comment form itself." + :type 'boolean + :safe #'booleanp + :package-version '(clojure-ts-mode . "0.2.1")) + (defvar clojure-ts--debug nil "Enables debugging messages, shows current node in mode-line. Only intended for use at development time.") @@ -911,7 +917,10 @@ See `clojure-ts--font-lock-settings' for usage of MARKDOWN-AVAILABLE." (setq-local treesit-defun-prefer-top-level t) (setq-local treesit-defun-tactic 'top-level) (setq-local treesit-defun-type-regexp - (rx (or "list_lit" "vec_lit" "map_lit"))) + (cons (rx (or "list_lit" "vec_lit" "map_lit")) + (lambda (node) + (or (not clojure-ts-toplevel-inside-comment-form) + (not (clojure-ts--definition-node-p "comment" node)))))) (setq-local treesit-simple-indent-rules (clojure-ts--configured-indent-rules)) (setq-local treesit-defun-name-function From 264ba7b9f5f66dbee708d5da785cb7008c59e8f8 Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Sun, 11 Feb 2024 09:12:50 +0200 Subject: [PATCH 205/379] Add a note about semantic indentation --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 15dfffa..2b8726e 100644 --- a/README.md +++ b/README.md @@ -20,15 +20,19 @@ Most configuration changes will require reverting any active clojure-ts-mode buf ### Indentation -clojure-ts-mode currently supports 2 different indentation strategies +clojure-ts-mode currently supports 2 different indentation strategies: + - `semantic`, the default, which tries to match the indentation of clojure-mode and cljfmt - `fixed`, [a simple indentation strategy outlined by Tonsky in a blog post](https://tonsky.me/blog/clojurefmt/) Set the var `clojure-ts-indent-style` to change it. + ``` emacs-lisp (setq clojure-ts-indent-style 'fixed) ``` +**Note:** You can find [this article](https://metaredux.com/posts/2020/12/06/semantic-clojure-formatting.html) comparing semantic and fixed indentation useful. + ### Font Locking Too highlight entire rich `comment` expression with the comment font face, set From b523e8354bd6a231ca4b607976c5a0add3a483eb Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Sun, 11 Feb 2024 09:15:15 +0200 Subject: [PATCH 206/379] Tweak markup --- README.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 2b8726e..8942c60 100644 --- a/README.md +++ b/README.md @@ -16,13 +16,13 @@ highlighting), indentation, and navigation support for the To see a list of available configuration options do `M-x customize-group clojure-ts`. -Most configuration changes will require reverting any active clojure-ts-mode buffers. +Most configuration changes will require reverting any active `clojure-ts-mode` buffers. ### Indentation -clojure-ts-mode currently supports 2 different indentation strategies: +`clojure-ts-mode` currently supports 2 different indentation strategies: -- `semantic`, the default, which tries to match the indentation of clojure-mode and cljfmt +- `semantic`, the default, which tries to match the indentation of `clojure-mode` and cljfmt - `fixed`, [a simple indentation strategy outlined by Tonsky in a blog post](https://tonsky.me/blog/clojurefmt/) Set the var `clojure-ts-indent-style` to change it. @@ -36,6 +36,7 @@ Set the var `clojure-ts-indent-style` to change it. ### Font Locking Too highlight entire rich `comment` expression with the comment font face, set + ``` emacs-lisp (setq clojure-ts-comment-macro-font-lock-body t) ``` @@ -170,7 +171,7 @@ I don't know how to do this on Windows. Patches welcome! #### Finally, in emacs -Then tell Emacs where to find the shared library by adding something like this to your init file +Then tell Emacs where to find the shared library by adding something like this to your init file: ```emacs-lisp (setq treesit-extra-load-path '( "~/path/to/tree-sitter-clojure/dist")) From b2c79f6b0204e19723c7d045eba50aebb0231ac8 Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Sun, 11 Feb 2024 12:32:49 +0200 Subject: [PATCH 207/379] Bump the copyright years --- README.md | 2 +- clojure-ts-mode.el | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 8942c60..bd3908f 100644 --- a/README.md +++ b/README.md @@ -182,7 +182,7 @@ under your `user-emacs-directory` (typically `~/.emacs.d` on Unix systems). ## License -Copyright © 2022-2023 Danny Freeman and [contributors][]. +Copyright © 2022-2024 Danny Freeman and [contributors][]. Distributed under the GNU General Public License; type C-h C-c to view it. diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index 5e50ffa..6a648be 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -1,6 +1,6 @@ ;;; clojure-ts-mode.el --- Major mode for Clojure code -*- lexical-binding: t; -*- -;; Copyright © 2022-2023 Danny Freeman +;; Copyright © 2022-2024 Danny Freeman ;; ;; Authors: Danny Freeman ;; Maintainer: Danny Freeman From 7910428577c8d4ed0328aee933048661051b07f4 Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Sun, 11 Feb 2024 12:37:56 +0200 Subject: [PATCH 208/379] Add notes about CIDER and inf-clojure --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index bd3908f..f8781b1 100644 --- a/README.md +++ b/README.md @@ -180,6 +180,16 @@ Then tell Emacs where to find the shared library by adding something like this t OR you can move the `libtree-sitter-clojure.so`/`libtree-sitter-clojure.dylib` to a directory named `tree-sitter` under your `user-emacs-directory` (typically `~/.emacs.d` on Unix systems). +## Frequently Asked Questions + +### Does `clojure-ts-mode` work with CIDER? + +Not yet, but [should change soon](https://github.com/clojure-emacs/cider/pull/3461). Feel free to help out with the remaining work, so we can expedite the process. + +### Does `clojure-ts-mode` work with `inf-clojure`? + +[Ditto.](https://github.com/clojure-emacs/inf-clojure/pull/215) + ## License Copyright © 2022-2024 Danny Freeman and [contributors][]. From ca102cc88d5eae77e59665e3b152418d14e46d94 Mon Sep 17 00:00:00 2001 From: Dieter Komendera Date: Thu, 8 Feb 2024 20:31:51 +0100 Subject: [PATCH 209/379] Accept all sexp-nodes as defuns --- clojure-ts-mode.el | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index 6a648be..c98ca45 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -917,7 +917,7 @@ See `clojure-ts--font-lock-settings' for usage of MARKDOWN-AVAILABLE." (setq-local treesit-defun-prefer-top-level t) (setq-local treesit-defun-tactic 'top-level) (setq-local treesit-defun-type-regexp - (cons (rx (or "list_lit" "vec_lit" "map_lit")) + (cons (regexp-opt clojure-ts--sexp-nodes) (lambda (node) (or (not clojure-ts-toplevel-inside-comment-form) (not (clojure-ts--definition-node-p "comment" node)))))) From f11b680b6b6adaf6748800b7e64ed7e265d6e9ba Mon Sep 17 00:00:00 2001 From: Dieter Komendera Date: Sun, 11 Feb 2024 16:38:40 +0100 Subject: [PATCH 210/379] Add changelog entry and a note for reasoning about `treesit-defun-type-regexp` --- CHANGELOG.md | 1 + clojure-ts-mode.el | 11 +++++++---- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 238bb73..7e654cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## main (unreleased) - Add custom option `clojure-ts-toplevel-inside-comment-form` as an equivalent to `clojure-toplevel-inside-comment-form` in clojure-mode (#30) +- Change behavior of `beginning-of-defun` and `end-of-defun` to consider all Clojure sexps as defuns (#32) ## 0.2.0 diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index c98ca45..65328e7 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -917,10 +917,13 @@ See `clojure-ts--font-lock-settings' for usage of MARKDOWN-AVAILABLE." (setq-local treesit-defun-prefer-top-level t) (setq-local treesit-defun-tactic 'top-level) (setq-local treesit-defun-type-regexp - (cons (regexp-opt clojure-ts--sexp-nodes) - (lambda (node) - (or (not clojure-ts-toplevel-inside-comment-form) - (not (clojure-ts--definition-node-p "comment" node)))))) + (cons + ;; consider all clojure sexps as valid top level forms... + (regexp-opt clojure-ts--sexp-nodes) + ;; ...except `comment' forms if `clojure-ts-toplevel-inside-comment-form' is set + (lambda (node) + (or (not clojure-ts-toplevel-inside-comment-form) + (not (clojure-ts--definition-node-p "comment" node)))))) (setq-local treesit-simple-indent-rules (clojure-ts--configured-indent-rules)) (setq-local treesit-defun-name-function From b5e70662cfe49e0af0f78b61b594b02f3aa960cd Mon Sep 17 00:00:00 2001 From: Dieter Komendera Date: Mon, 12 Feb 2024 16:35:52 +0100 Subject: [PATCH 211/379] Tweak FAQ about cider --- README.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index f8781b1..5cb68d9 100644 --- a/README.md +++ b/README.md @@ -184,7 +184,13 @@ under your `user-emacs-directory` (typically `~/.emacs.d` on Unix systems). ### Does `clojure-ts-mode` work with CIDER? -Not yet, but [should change soon](https://github.com/clojure-emacs/cider/pull/3461). Feel free to help out with the remaining work, so we can expedite the process. +Not yet out of the box, but that [should change soon](https://github.com/clojure-emacs/cider/pull/3461). Feel free to help out with the remaining work, so we can expedite the process. + +For now, when you take care of the keybindings for the cider functions you use and ensure `cider-mode` is enabled for `clojure-ts-mode` buffers in your config, most functinality should already work: + +```emacs-lisp +(add-hook 'clojure-ts-mode-hook #'cider-mode) +``` ### Does `clojure-ts-mode` work with `inf-clojure`? From 0e3b0c5506ed9b45ad0d8152c0c1643a109e27fb Mon Sep 17 00:00:00 2001 From: p4v4n Date: Tue, 13 Feb 2024 06:33:35 +0530 Subject: [PATCH 212/379] Fix minor typos in readme --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 5cb68d9..9356d9d 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ Most configuration changes will require reverting any active `clojure-ts-mode` b `clojure-ts-mode` currently supports 2 different indentation strategies: -- `semantic`, the default, which tries to match the indentation of `clojure-mode` and cljfmt +- `semantic`, the default, which tries to match the indentation of `clojure-mode` and `cljfmt` - `fixed`, [a simple indentation strategy outlined by Tonsky in a blog post](https://tonsky.me/blog/clojurefmt/) Set the var `clojure-ts-indent-style` to change it. @@ -46,7 +46,7 @@ highlighted like regular clojure code. ### Navigation and Evaluation -To make forms inside of `(comment ...)` forms appear as toplevel forms for evaluation and navigation, set +To make forms inside of `(comment ...)` forms appear as top-level forms for evaluation and navigation, set ``` emacs-lisp (setq clojure-ts-toplevel-inside-comment-form t) @@ -186,7 +186,7 @@ under your `user-emacs-directory` (typically `~/.emacs.d` on Unix systems). Not yet out of the box, but that [should change soon](https://github.com/clojure-emacs/cider/pull/3461). Feel free to help out with the remaining work, so we can expedite the process. -For now, when you take care of the keybindings for the cider functions you use and ensure `cider-mode` is enabled for `clojure-ts-mode` buffers in your config, most functinality should already work: +For now, when you take care of the keybindings for the cider functions you use and ensure `cider-mode` is enabled for `clojure-ts-mode` buffers in your config, most functionality should already work: ```emacs-lisp (add-hook 'clojure-ts-mode-hook #'cider-mode) From b42dc61364a7916e40aa7ed19804788be52a3819 Mon Sep 17 00:00:00 2001 From: p4v4n Date: Tue, 13 Feb 2024 06:57:45 +0530 Subject: [PATCH 213/379] Update emacs dependency to 29.1 --- clojure-ts-mode.el | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index 65328e7..2372b0e 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -7,7 +7,7 @@ ;; URL: http://github.com/clojure-emacs/clojure-ts-mode ;; Keywords: languages clojure clojurescript lisp ;; Version: 0.2.0 -;; Package-Requires: ((emacs "29")) +;; Package-Requires: ((emacs "29.1")) ;; This file is not part of GNU Emacs. From 050c4eea8858d18629d61057e22e64f33c7b74d1 Mon Sep 17 00:00:00 2001 From: p4v4n Date: Tue, 13 Feb 2024 06:59:59 +0530 Subject: [PATCH 214/379] Rename derived mode vars to match package prefix - clojurescript-ts-mode -> clojure-ts-clojurescript-mode - clojurec-ts-mode -> clojure-ts-clojurec-mode - clojure-dart-ts-mode -> clojure-ts-clojuredart-mode - clojure-jank-ts-mode -> clojure-ts-jank-mode --- clojure-ts-mode.el | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index 2372b0e..481a82b 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -861,22 +861,22 @@ forms like deftype, defrecord, reify, proxy, etc." ;(set-keymap-parent map clojure-mode-map) map)) -(defvar clojurescript-ts-mode-map +(defvar clojure-ts-clojurescript-mode-map (let ((map (make-sparse-keymap))) (set-keymap-parent map clojure-ts-mode-map) map)) -(defvar clojurec-ts-mode-map +(defvar clojure-ts-clojurec-mode-map (let ((map (make-sparse-keymap))) (set-keymap-parent map clojure-ts-mode-map) map)) -(defvar clojure-dart-ts-mode-map +(defvar clojure-ts-clojuredart-mode-map (let ((map (make-sparse-keymap))) (set-keymap-parent map clojure-ts-mode-map) map)) -(defvar clojure-jank-ts-mode-map +(defvar clojure-ts-jank-mode-map (let ((map (make-sparse-keymap))) (set-keymap-parent map clojure-ts-mode-map) map)) @@ -965,25 +965,25 @@ See `clojure-ts--font-lock-settings' for usage of MARKDOWN-AVAILABLE." (setq-local transpose-sexps-function #'transpose-sexps-default-function))))) ;;;###autoload -(define-derived-mode clojurescript-ts-mode clojure-ts-mode "ClojureScript[TS]" +(define-derived-mode clojure-ts-clojurescript-mode clojure-ts-mode "ClojureScript[TS]" "Major mode for editing ClojureScript code. \\{clojurescript-ts-mode-map}") ;;;###autoload -(define-derived-mode clojurec-ts-mode clojure-ts-mode "ClojureC[TS]" +(define-derived-mode clojure-ts-clojurec-mode clojure-ts-mode "ClojureC[TS]" "Major mode for editing ClojureC code. \\{clojurec-ts-mode-map}") ;;;###autoload -(define-derived-mode clojure-dart-ts-mode clojure-ts-mode "ClojureDart[TS]" +(define-derived-mode clojure-ts-clojuredart-mode clojure-ts-mode "ClojureDart[TS]" "Major mode for editing Clojure Dart code. \\{clojure-dart-ts-mode-map}") ;;;###autoload -(define-derived-mode clojure-jank-ts-mode clojure-ts-mode "Jank[TS]" +(define-derived-mode clojure-ts-jank-mode clojure-ts-mode "Jank[TS]" "Major mode for editing Jank code. \\{clojure-jank-ts-mode-map}") From 569c464150bc54ef768108d0f7884a545ac93ad1 Mon Sep 17 00:00:00 2001 From: p4v4n Date: Tue, 13 Feb 2024 17:23:37 +0530 Subject: [PATCH 215/379] Update Changelog --- CHANGELOG.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e654cf..4983bc9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,11 @@ # Changelog ## main (unreleased) - +- Rename all derived mode vars to match the package prefix. ([#36](https://github.com/clojure-emacs/clojure-ts-mode/pull/36)) +`clojurescript-ts-mode` -> `clojure-ts-clojurescript-mode` +`clojurec-ts-mode` -> `clojure-ts-clojurec-mode` +`clojure-dart-ts-mode` -> `clojure-ts-clojuredart-mode` +`clojure-jank-ts-mode` -> `clojure-ts-jank-mode` - Add custom option `clojure-ts-toplevel-inside-comment-form` as an equivalent to `clojure-toplevel-inside-comment-form` in clojure-mode (#30) - Change behavior of `beginning-of-defun` and `end-of-defun` to consider all Clojure sexps as defuns (#32) From 33eda84822fd6385cec5d2601a210bf1c5250ccc Mon Sep 17 00:00:00 2001 From: p4v4n Date: Wed, 14 Feb 2024 02:31:28 +0530 Subject: [PATCH 216/379] Setup Eldev --- .gitignore | 3 +++ Eldev | 25 +++++++++++++++++++++++++ 2 files changed, 28 insertions(+) create mode 100644 Eldev diff --git a/.gitignore b/.gitignore index 7807b63..c3df805 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,6 @@ elpa* /clojure-ts-mode-autoloads.el /clojure-ts-mode-pkg.el + +/.eldev +/Eldev-local diff --git a/Eldev b/Eldev new file mode 100644 index 0000000..e9243c6 --- /dev/null +++ b/Eldev @@ -0,0 +1,25 @@ +; -*- mode: emacs-lisp; lexical-binding: t -*- + +(eldev-require-version "1.8.2") + +(eldev-use-package-archive 'gnu-elpa) +(eldev-use-package-archive 'nongnu-elpa) + +(eldev-use-package-archive 'melpa-stable) +(eldev-use-package-archive 'melpa-unstable) + +(eldev-use-plugin 'autoloads) + +(setq byte-compile-docstring-max-column 240) +(setq checkdoc-force-docstrings-flag nil) +(setq checkdoc-permit-comma-termination-flag t) +(setq checkdoc--interactive-docstring-flag nil) + +(setf eldev-lint-default '(elisp)) + +(with-eval-after-load 'elisp-lint + ;; We will byte-compile with Eldev. + (setf elisp-lint-ignored-validators '("fill-column") + enable-local-variables :safe)) + +(setq eldev-project-main-file "clojure-ts-mode.el") From 8f17736e678581ea6c12fab0dc43af8a698a574c Mon Sep 17 00:00:00 2001 From: p4v4n Date: Wed, 14 Feb 2024 02:31:50 +0530 Subject: [PATCH 217/379] Add Makefile --- Makefile | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 Makefile diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..3e61353 --- /dev/null +++ b/Makefile @@ -0,0 +1,17 @@ +.PHONY: clean compile lint test all +.DEFAULT_GOAL := all + +clean: + eldev clean + +lint: clean + eldev lint -c + +# Checks for byte-compilation warnings. +compile: clean + eldev -dtT compile --warnings-as-errors + +test: clean + eldev -dtT -p test + +all: clean compile lint test From 4caa7bb244c36f87df12a51d9f1bd0aa70a2c34f Mon Sep 17 00:00:00 2001 From: p4v4n Date: Wed, 14 Feb 2024 02:33:14 +0530 Subject: [PATCH 218/379] Add first unit test with buttercup --- Eldev | 2 ++ test/clojure-ts-mode-util-test.el | 29 +++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+) create mode 100644 test/clojure-ts-mode-util-test.el diff --git a/Eldev b/Eldev index e9243c6..dc1612d 100644 --- a/Eldev +++ b/Eldev @@ -10,6 +10,8 @@ (eldev-use-plugin 'autoloads) +(eldev-add-extra-dependencies 'test 'buttercup) + (setq byte-compile-docstring-max-column 240) (setq checkdoc-force-docstrings-flag nil) (setq checkdoc-permit-comma-termination-flag t) diff --git a/test/clojure-ts-mode-util-test.el b/test/clojure-ts-mode-util-test.el new file mode 100644 index 0000000..9d02bcc --- /dev/null +++ b/test/clojure-ts-mode-util-test.el @@ -0,0 +1,29 @@ +;;; clojure-ts-mode-util-test.el --- Clojure TS Mode: util test suite -*- lexical-binding: t; -*- + +;; Copyright © 2022-2024 Danny Freeman + +;; This file is not part of GNU Emacs. + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;;; Commentary: + +;; The unit test suite of Clojure TS Mode + +(require 'clojure-ts-mode) +(require 'buttercup) + +(describe "clojure-ts-mode-version" + (it "should not be nil" + (expect clojure-ts-mode-version))) From 59e003ad2234b4641b120bb755184a7a895df684 Mon Sep 17 00:00:00 2001 From: p4v4n Date: Wed, 14 Feb 2024 02:34:19 +0530 Subject: [PATCH 219/379] Setup github actions for linting and tests - Use emacs29.1 instead of snapshot for lint to fix compat installation issue https://github.com/emacs-compat/compat/commit/c98e141d14114509caeda4e6752a1aad39a0cdb1 --- .github/workflows/lint-emacs.yml | 54 +++++++++++++++++++++++++++++--- 1 file changed, 50 insertions(+), 4 deletions(-) diff --git a/.github/workflows/lint-emacs.yml b/.github/workflows/lint-emacs.yml index 872a125..266add8 100644 --- a/.github/workflows/lint-emacs.yml +++ b/.github/workflows/lint-emacs.yml @@ -1,4 +1,4 @@ -name: Lint Emacs +name: CI on: push: @@ -7,7 +7,7 @@ on: paths: ['**.el'] jobs: - test: + compile: runs-on: ubuntu-latest # continue-on-error: ${{matrix.emacs_version == 'snapshot'}} @@ -25,7 +25,53 @@ jobs: run: curl -fsSL https://raw.github.com/doublep/eldev/master/webinstall/github-eldev | sh - name: Check out the source code - uses: actions/checkout@v2 + uses: actions/checkout@v4 + + - name: Compile the project + run: make compile + + lint: + runs-on: ubuntu-latest + # continue-on-error: ${{matrix.emacs_version == 'snapshot'}} + + strategy: + matrix: + emacs_version: ['29.1'] + + steps: + - name: Set up Emacs + uses: purcell/setup-emacs@master + with: + version: ${{matrix.emacs_version}} + + - name: Install Eldev + run: curl -fsSL https://raw.github.com/doublep/eldev/master/webinstall/github-eldev | sh + + - name: Check out the source code + uses: actions/checkout@v4 - name: Lint the project - run: eldev -dtT -C compile --warnings-as-errors + run: make lint + + test: + runs-on: ubuntu-latest + # continue-on-error: ${{matrix.emacs_version == 'snapshot'}} + + strategy: + matrix: + emacs_version: ['snapshot'] + + steps: + - name: Set up Emacs + uses: purcell/setup-emacs@master + with: + version: ${{matrix.emacs_version}} + + - name: Install Eldev + run: curl -fsSL https://raw.github.com/doublep/eldev/master/webinstall/github-eldev | sh + + - name: Check out the source code + uses: actions/checkout@v4 + + - name: Run tests + run: make test From 4ec0c61d6404a17ec7a9a1dc406df763364e9337 Mon Sep 17 00:00:00 2001 From: p4v4n Date: Wed, 14 Feb 2024 02:36:06 +0530 Subject: [PATCH 220/379] Fix indentation warnings from eldev --- clojure-ts-mode.el | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index 481a82b..0700640 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -239,9 +239,9 @@ if a third argument (the value) is provided. (defconst clojure-ts--typedef-symbol-regexp (eval-and-compile (rx line-start - (or "defprotocol" "defmulti" "deftype" "defrecord" - "definterface" "defmethod" "defstruct") - line-end)) + (or "defprotocol" "defmulti" "deftype" "defrecord" + "definterface" "defmethod" "defstruct") + line-end)) "A regular expression matching a symbol used to define a type.") (defconst clojure-ts--type-symbol-regexp @@ -773,7 +773,7 @@ forms like deftype, defrecord, reify, proxy, etc." (defun clojure-ts--match-fn-docstring (node) "Match NODE when it is a docstring for PARENT function definition node." - ;; A string that is the third node in a function defn block + ;; A string that is the third node in a function defn block (let ((parent (treesit-node-parent node))) (and (treesit-node-eq node (treesit-node-child parent 2 t)) (let ((first-auncle (treesit-node-child parent 0 t))) @@ -854,11 +854,11 @@ forms like deftype, defrecord, reify, proxy, etc." (defconst clojure-ts--thing-settings `((clojure (sexp ,(regexp-opt clojure-ts--sexp-nodes) - text ,(regexp-opt '("comment")))))) + text ,(regexp-opt '("comment")))))) (defvar clojure-ts-mode-map (let ((map (make-sparse-keymap))) - ;(set-keymap-parent map clojure-mode-map) + ;;(set-keymap-parent map clojure-mode-map) map)) (defvar clojure-ts-clojurescript-mode-map From 9bbae695ec3de38874a01043e8b4386a04f42f53 Mon Sep 17 00:00:00 2001 From: p4v4n Date: Wed, 14 Feb 2024 02:38:05 +0530 Subject: [PATCH 221/379] Ignore check-declare validator for linting - to fix "treesit.c file not found" error from eldev --- Eldev | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Eldev b/Eldev index dc1612d..e6b3c00 100644 --- a/Eldev +++ b/Eldev @@ -21,7 +21,7 @@ (with-eval-after-load 'elisp-lint ;; We will byte-compile with Eldev. - (setf elisp-lint-ignored-validators '("fill-column") + (setf elisp-lint-ignored-validators '("fill-column" "check-declare") enable-local-variables :safe)) (setq eldev-project-main-file "clojure-ts-mode.el") From 32304543b5fac3481bf6928a4ca48c054903580d Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Wed, 14 Feb 2024 10:52:18 +0200 Subject: [PATCH 222/379] Release 0.2.1 --- CHANGELOG.md | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4983bc9..4e5df56 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,13 +1,16 @@ # Changelog ## main (unreleased) -- Rename all derived mode vars to match the package prefix. ([#36](https://github.com/clojure-emacs/clojure-ts-mode/pull/36)) -`clojurescript-ts-mode` -> `clojure-ts-clojurescript-mode` -`clojurec-ts-mode` -> `clojure-ts-clojurec-mode` -`clojure-dart-ts-mode` -> `clojure-ts-clojuredart-mode` -`clojure-jank-ts-mode` -> `clojure-ts-jank-mode` -- Add custom option `clojure-ts-toplevel-inside-comment-form` as an equivalent to `clojure-toplevel-inside-comment-form` in clojure-mode (#30) -- Change behavior of `beginning-of-defun` and `end-of-defun` to consider all Clojure sexps as defuns (#32) + +## 0.2.1 (2024-02-14) + +- [#36]: Rename all derived mode vars to match the package prefix. + - `clojurescript-ts-mode` -> `clojure-ts-clojurescript-mode` + - `clojurec-ts-mode` -> `clojure-ts-clojurec-mode` + - `clojure-dart-ts-mode` -> `clojure-ts-clojuredart-mode` + - `clojure-jank-ts-mode` -> `clojure-ts-jank-mode` +- [#30]: Add custom option `clojure-ts-toplevel-inside-comment-form` as an equivalent to `clojure-toplevel-inside-comment-form` in `clojure-mode`. +- [#32]: Change behavior of `beginning-of-defun` and `end-of-defun` to consider all Clojure sexps as defuns. ## 0.2.0 From 95520cb12bc2af57b55cb8a049906c7e40805a42 Mon Sep 17 00:00:00 2001 From: p4v4n Date: Thu, 15 Feb 2024 05:29:17 +0530 Subject: [PATCH 223/379] Fix all derived modes - after breaking them earlier in this PR https://github.com/clojure-emacs/clojure-ts-mode/pull/36 --- CHANGELOG.md | 2 ++ clojure-ts-mode.el | 22 +++++++++++----------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e5df56..63ea2dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## main (unreleased) +- [#37]: Fix derived modes broken with [#36] + ## 0.2.1 (2024-02-14) - [#36]: Rename all derived mode vars to match the package prefix. diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index 0700640..97a9345 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -968,51 +968,51 @@ See `clojure-ts--font-lock-settings' for usage of MARKDOWN-AVAILABLE." (define-derived-mode clojure-ts-clojurescript-mode clojure-ts-mode "ClojureScript[TS]" "Major mode for editing ClojureScript code. -\\{clojurescript-ts-mode-map}") +\\{clojure-ts-clojurescript-mode-map}") ;;;###autoload (define-derived-mode clojure-ts-clojurec-mode clojure-ts-mode "ClojureC[TS]" "Major mode for editing ClojureC code. -\\{clojurec-ts-mode-map}") +\\{clojure-ts-clojurec-mode-map}") ;;;###autoload (define-derived-mode clojure-ts-clojuredart-mode clojure-ts-mode "ClojureDart[TS]" "Major mode for editing Clojure Dart code. -\\{clojure-dart-ts-mode-map}") +\\{clojure-ts-clojuredart-mode-map}") ;;;###autoload (define-derived-mode clojure-ts-jank-mode clojure-ts-mode "Jank[TS]" "Major mode for editing Jank code. -\\{clojure-jank-ts-mode-map}") +\\{clojure-ts-jank-mode-map}") (defun clojure-ts--register-novel-modes () "Set up Clojure modes not present in progenitor clojure-mode.el." - (add-to-list 'auto-mode-alist '("\\.cljd\\'" . clojure-dart-ts-mode)) - (add-to-list 'auto-mode-alist '("\\.jank\\'" . clojure-jank-ts-mode))) + (add-to-list 'auto-mode-alist '("\\.cljd\\'" . clojure-ts-clojuredart-mode)) + (add-to-list 'auto-mode-alist '("\\.jank\\'" . clojure-ts-jank-mode))) ;; Redirect clojure-mode to clojure-ts-mode if clojure-mode is present (if (require 'clojure-mode nil 'noerror) (progn (add-to-list 'major-mode-remap-alist '(clojure-mode . clojure-ts-mode)) - (add-to-list 'major-mode-remap-alist '(clojurescript-mode . clojurescript-ts-mode)) - (add-to-list 'major-mode-remap-alist '(clojurec-mode . clojurec-ts-mode)) + (add-to-list 'major-mode-remap-alist '(clojurescript-mode . clojure-ts-clojurescript-mode)) + (add-to-list 'major-mode-remap-alist '(clojurec-mode . clojure-ts-clojurec-mode)) (clojure-ts--register-novel-modes)) ;; Clojure-mode is not present, setup auto-modes ourselves ;; Regular clojure/edn files ;; I believe dtm is for datomic queries and datoms, which are just edn. (add-to-list 'auto-mode-alist '("\\.\\(clj\\|dtm\\|edn\\)\\'" . clojure-ts-mode)) - (add-to-list 'auto-mode-alist '("\\.cljs\\'" . clojurescript-ts-mode)) - (add-to-list 'auto-mode-alist '("\\.cljc\\'" . clojurec-ts-mode)) + (add-to-list 'auto-mode-alist '("\\.cljs\\'" . clojure-ts-clojurescript-mode)) + (add-to-list 'auto-mode-alist '("\\.cljc\\'" . clojure-ts-clojurec-mode)) ;; boot build scripts are Clojure source files (add-to-list 'auto-mode-alist '("\\(?:build\\|profile\\)\\.boot\\'" . clojure-ts-mode)) ;; babashka scripts are Clojure source files (add-to-list 'interpreter-mode-alist '("bb" . clojure-ts-mode)) ;; nbb scripts are ClojureScript source files - (add-to-list 'interpreter-mode-alist '("nbb" . clojurescript-ts-mode)) + (add-to-list 'interpreter-mode-alist '("nbb" . clojure-ts-clojurescript-mode)) (clojure-ts--register-novel-modes)) (defvar clojure-ts--find-ns-query From a923aa83a61751a588ed3afc133d45898995762e Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Fri, 16 Feb 2024 15:23:30 +0200 Subject: [PATCH 224/379] Release 0.2.2 --- CHANGELOG.md | 4 +++- clojure-ts-mode.el | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 63ea2dd..2504e4c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,9 @@ ## main (unreleased) -- [#37]: Fix derived modes broken with [#36] +## 0.2.2 (2024-02-16) + +- [#37]: Fix derived modes broken with [#36]. ## 0.2.1 (2024-02-14) diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index 97a9345..48a3945 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -6,7 +6,7 @@ ;; Maintainer: Danny Freeman ;; URL: http://github.com/clojure-emacs/clojure-ts-mode ;; Keywords: languages clojure clojurescript lisp -;; Version: 0.2.0 +;; Version: 0.2.2 ;; Package-Requires: ((emacs "29.1")) ;; This file is not part of GNU Emacs. From 0c69c93bb2e7543c5f99e919d8a433a68b6fabf4 Mon Sep 17 00:00:00 2001 From: p4v4n Date: Mon, 19 Feb 2024 21:18:57 +0530 Subject: [PATCH 225/379] Support `in-ns` in clojure-ts-find-ns --- clojure-ts-mode.el | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index 48a3945..539b16a 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -1021,7 +1021,12 @@ See `clojure-ts--font-lock-settings' for usage of MARKDOWN-AVAILABLE." '(((source (list_lit :anchor (sym_lit name: (sym_name) @ns) :anchor (sym_lit name: (sym_name) @ns-name))) - (:equal @ns "ns"))))) + (:equal @ns "ns")) + ((source (list_lit + :anchor (sym_lit name: (sym_name) @in-ns) + :anchor (quoting_lit + :anchor (sym_lit name: (sym_name) @ns-name)))) + (:equal @in-ns "in-ns"))))) (defun clojure-ts-find-ns () "Return the name of the current namespace." From f3d9e98dd018a3140efc9b8fb8a96ba829a7e644 Mon Sep 17 00:00:00 2001 From: p4v4n Date: Mon, 19 Feb 2024 21:19:49 +0530 Subject: [PATCH 226/379] Remove text property for clojure-ts-find-ns --- clojure-ts-mode.el | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index 539b16a..0297501 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -1031,7 +1031,7 @@ See `clojure-ts--font-lock-settings' for usage of MARKDOWN-AVAILABLE." (defun clojure-ts-find-ns () "Return the name of the current namespace." (let ((nodes (treesit-query-capture 'clojure clojure-ts--find-ns-query))) - (treesit-node-text (cdr (assoc 'ns-name nodes))))) + (treesit-node-text (cdr (assoc 'ns-name nodes)) t))) (provide 'clojure-ts-mode) From ccce18a34535f17afcc446b462322430ae4fb172 Mon Sep 17 00:00:00 2001 From: p4v4n Date: Mon, 19 Feb 2024 21:20:30 +0530 Subject: [PATCH 227/379] Import a couple of test-helpers from clojure-mode --- test/utils/test-helper.el | 50 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 test/utils/test-helper.el diff --git a/test/utils/test-helper.el b/test/utils/test-helper.el new file mode 100644 index 0000000..306de61 --- /dev/null +++ b/test/utils/test-helper.el @@ -0,0 +1,50 @@ +;;; test-helper.el --- Clojure TS Mode: Non-interactive unit-test setup -*- lexical-binding: t; -*- + +;; Copyright © 2022-2024 Danny Freeman + +;; This file is not part of GNU Emacs. + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;;; Commentary: + +;; Non-interactive test suite setup. + +;;; Code: + +(defmacro with-clojure-ts-buffer (text &rest body) + "Create a temporary buffer, insert TEXT,switch to clojure-ts-mode. +And evaluate BODY." + (declare (indent 1)) + `(with-temp-buffer + (erase-buffer) + (insert ,text) + (clojure-ts-mode) + ,@body)) + +(defmacro with-clojure-ts-buffer-point (text &rest body) + "Run BODY in a temporary clojure buffer with TEXT. + +TEXT is a string with a | indicating where point is. The | will be erased +and point left there." + (declare (indent 2)) + `(progn + (with-clojure-ts-buffer ,text + (goto-char (point-min)) + (re-search-forward "|") + (delete-char -1) + ,@body))) + +(provide 'test-helper) +;;; test-helper.el ends here From dbad83231249fd5f596ac9c479da212f550be3e4 Mon Sep 17 00:00:00 2001 From: p4v4n Date: Mon, 19 Feb 2024 21:21:37 +0530 Subject: [PATCH 228/379] Add tests for clojure-ts-find-ns - The tests cases are copied from clojure-mode and updated --- test/clojure-ts-mode-util-test.el | 103 ++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) diff --git a/test/clojure-ts-mode-util-test.el b/test/clojure-ts-mode-util-test.el index 9d02bcc..b4c2e13 100644 --- a/test/clojure-ts-mode-util-test.el +++ b/test/clojure-ts-mode-util-test.el @@ -23,7 +23,110 @@ (require 'clojure-ts-mode) (require 'buttercup) +(require 'test-helper "test/utils/test-helper") (describe "clojure-ts-mode-version" (it "should not be nil" (expect clojure-ts-mode-version))) + +(describe "clojure-ts-find-ns" + (it "should find common namespace declarations" + (with-clojure-ts-buffer "(ns foo)" + (expect (clojure-ts-find-ns) :to-equal "foo")) + (with-clojure-ts-buffer "(ns + foo)" + (expect (clojure-ts-find-ns) :to-equal "foo")) + (with-clojure-ts-buffer "(ns foo.baz)" + (expect (clojure-ts-find-ns) :to-equal "foo.baz")) + (with-clojure-ts-buffer "(ns ^:bar foo)" + (expect (clojure-ts-find-ns) :to-equal "foo")) + (with-clojure-ts-buffer "(ns ^:bar ^:baz foo)" + (expect (clojure-ts-find-ns) :to-equal "foo"))) + + (it "should find namespaces with spaces before ns form" + (with-clojure-ts-buffer " (ns foo)" + (expect (clojure-ts-find-ns) :to-equal "foo"))) + + (it "should skip namespaces within any comment forms" + (with-clojure-ts-buffer "(comment + (ns foo))" + (expect (clojure-ts-find-ns) :to-equal nil)) + (with-clojure-ts-buffer " (ns foo) + (comment + (ns bar))" + (expect (clojure-ts-find-ns) :to-equal "foo")) + (with-clojure-ts-buffer " (comment + (ns foo)) + (ns bar) + (comment + (ns baz))" + (expect (clojure-ts-find-ns) :to-equal "bar"))) + + (it "should find namespace declarations with nested metadata and docstrings" + (with-clojure-ts-buffer "(ns ^{:bar true} foo)" + (expect (clojure-ts-find-ns) :to-equal "foo")) + (with-clojure-ts-buffer "(ns #^{:bar true} foo)" + (expect (clojure-ts-find-ns) :to-equal "foo")) + (with-clojure-ts-buffer "(ns #^{:fail {}} foo)" + (expect (clojure-ts-find-ns) :to-equal "foo")) + (with-clojure-ts-buffer "(ns ^{:fail2 {}} foo.baz)" + (expect (clojure-ts-find-ns) :to-equal "foo.baz")) + (with-clojure-ts-buffer "(ns ^{} foo)" + (expect (clojure-ts-find-ns) :to-equal "foo")) + (with-clojure-ts-buffer "(ns ^{:skip-wiki true} + aleph.netty)" + (expect (clojure-ts-find-ns) :to-equal "aleph.netty")) + (with-clojure-ts-buffer "(ns ^{:foo {:bar :baz} :fake (ns in.meta)} foo + \"docstring +(ns misleading)\")" + (expect (clojure-ts-find-ns) :to-equal "foo"))) + + (it "should support non-alphanumeric characters" + (with-clojure-ts-buffer "(ns foo+)" + (expect (clojure-ts-find-ns) :to-equal "foo+")) + (with-clojure-ts-buffer "(ns bar**baz$-_quux)" + (expect (clojure-ts-find-ns) :to-equal "bar**baz$-_quux")) + (with-clojure-ts-buffer "(ns aoc-2019.puzzles.day14)" + (expect (clojure-ts-find-ns) :to-equal "aoc-2019.puzzles.day14"))) + + (it "should support in-ns forms" + (with-clojure-ts-buffer "(in-ns 'bar.baz)" + (expect (clojure-ts-find-ns) :to-equal "bar.baz"))) + + (it "should take the first ns instead of closest unlike clojure-mode" + (with-clojure-ts-buffer " (ns foo1) + +(ns foo2)" + (expect (clojure-ts-find-ns) :to-equal "foo1")) + (with-clojure-ts-buffer-point " (in-ns foo1) +(ns 'foo2) +(in-ns 'foo3) +| +(ns foo4)" + (expect (clojure-ts-find-ns) :to-equal "foo3")) + (with-clojure-ts-buffer "(ns foo) +(ns-unmap *ns* 'map) +(ns.misleading 1 2 3)" + (expect (clojure-ts-find-ns) :to-equal "foo"))) + + (it "should skip leading garbage" + (with-clojure-ts-buffer " (ns foo)" + (expect (clojure-ts-find-ns) :to-equal "foo")) + (with-clojure-ts-buffer "1(ns foo)" + (expect (clojure-ts-find-ns) :to-equal "foo")) + (with-clojure-ts-buffer "1 (ns foo)" + (expect (clojure-ts-find-ns) :to-equal "foo")) + (with-clojure-ts-buffer "1 +(ns foo)" + (expect (clojure-ts-find-ns) :to-equal "foo")) + (with-clojure-ts-buffer "[1] +(ns foo)" + (expect (clojure-ts-find-ns) :to-equal "foo")) + (with-clojure-ts-buffer "[1] (ns foo)" + (expect (clojure-ts-find-ns) :to-equal "foo")) + (with-clojure-ts-buffer "[1](ns foo)" + (expect (clojure-ts-find-ns) :to-equal "foo")) + (with-clojure-ts-buffer "(ns)(ns foo)" + (expect (clojure-ts-find-ns) :to-equal "foo")) + (with-clojure-ts-buffer "(ns 'foo)(ns bar)" + (expect (clojure-ts-find-ns) :to-equal "bar")))) From 0fa4944f9f03cfe02b316f08d0a172d6e97dcc45 Mon Sep 17 00:00:00 2001 From: p4v4n Date: Tue, 20 Feb 2024 01:30:28 +0530 Subject: [PATCH 229/379] Add Changelog entry and Update Copyright --- CHANGELOG.md | 2 ++ test/utils/test-helper.el | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2504e4c..c3ee46f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## main (unreleased) +- Add support for `in-ns` forms in `clojure-ts-find-ns` + ## 0.2.2 (2024-02-16) - [#37]: Fix derived modes broken with [#36]. diff --git a/test/utils/test-helper.el b/test/utils/test-helper.el index 306de61..b7ac0d4 100644 --- a/test/utils/test-helper.el +++ b/test/utils/test-helper.el @@ -1,6 +1,6 @@ ;;; test-helper.el --- Clojure TS Mode: Non-interactive unit-test setup -*- lexical-binding: t; -*- -;; Copyright © 2022-2024 Danny Freeman +;; Copyright © 2022-2024 Bozhidar Batsov ;; This file is not part of GNU Emacs. From 928c8316c8bb7d0c7a3e93c2e0b9663668645647 Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Mon, 19 Feb 2024 22:07:09 +0200 Subject: [PATCH 230/379] Tweak a changelog entry --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c3ee46f..7af2ea8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ## main (unreleased) -- Add support for `in-ns` forms in `clojure-ts-find-ns` +- [#38]: Add support for `in-ns` forms in `clojure-ts-find-ns`. ## 0.2.2 (2024-02-16) From 9877d222b31389854e3b697e09524e8429ea600a Mon Sep 17 00:00:00 2001 From: p4v4n Date: Thu, 29 Feb 2024 02:38:13 +0530 Subject: [PATCH 231/379] Add some migration instructions --- README.md | 38 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9356d9d..4b6fa23 100644 --- a/README.md +++ b/README.md @@ -35,7 +35,7 @@ Set the var `clojure-ts-indent-style` to change it. ### Font Locking -Too highlight entire rich `comment` expression with the comment font face, set +To highlight entire rich `comment` expression with the comment font face, set ``` emacs-lisp (setq clojure-ts-comment-macro-font-lock-body t) @@ -85,6 +85,12 @@ If you decide to build Emacs from source there's some useful information on this - [Emacs tree-sitter starter-guide](https://git.savannah.gnu.org/cgit/emacs.git/tree/admin/notes/tree-sitter/starter-guide?h=emacs-29) - [Emacs install instructions](https://git.savannah.gnu.org/cgit/emacs.git/tree/INSTALL.REPO). +To check if your emacs already supports tree sitter run + +``` emacs-lisp +(treesit-available-p) +``` + ### Install clojure-ts-mode clojure-ts-mode is available on [MElPA](https://melpa.org/#/clojure-ts-mode) and @@ -180,11 +186,39 @@ Then tell Emacs where to find the shared library by adding something like this t OR you can move the `libtree-sitter-clojure.so`/`libtree-sitter-clojure.dylib` to a directory named `tree-sitter` under your `user-emacs-directory` (typically `~/.emacs.d` on Unix systems). +## Migrating to clojure-ts-mode + +If you are migrating to `clojure-ts-mode` note that `clojure-mode` is still required for cider and clj-refactor packages to work properly. + +After installing the package do the following. + +- Check the value of `clojure-mode-hook` and copy all relevant hooks to `clojure-ts-mode-hook`. + +example: +``` emacs-lisp +(add-hook 'clojure-ts-mode-hook #'cider-mode) +(add-hook 'clojure-ts-mode-hook #'enable-paredit-mode) +(add-hook 'clojure-ts-mode-hook #'rainbow-delimiters-mode) +(add-hook 'clojure-ts-mode-hook #'clj-refactor-mode) +``` + +- Update `.dir-locals.el` in all of your clojure projects to activate directory local variables in clojure-ts-mode. + +example: +``` emacs-lisp +((clojure-mode + (cider-clojure-cli-aliases . ":test:repl")) + (clojure-ts-mode + (cider-clojure-cli-aliases . ":test:repl"))) +``` + ## Frequently Asked Questions ### Does `clojure-ts-mode` work with CIDER? -Not yet out of the box, but that [should change soon](https://github.com/clojure-emacs/cider/pull/3461). Feel free to help out with the remaining work, so we can expedite the process. +~~Not yet out of the box, but that [should change soon](https://github.com/clojure-emacs/cider/pull/3461). Feel free to help out with the remaining work, so we can expedite the process.~~ + +Support for clojure-ts-mode has landed on the master branch. Make sure to grab the latest cider from melpa/github. For now, when you take care of the keybindings for the cider functions you use and ensure `cider-mode` is enabled for `clojure-ts-mode` buffers in your config, most functionality should already work: From 1208278913c61d5b11bcda5b8e545330b18b2db8 Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Thu, 29 Feb 2024 21:14:51 +0100 Subject: [PATCH 232/379] Touch up the previous commit --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 4b6fa23..bbe54cc 100644 --- a/README.md +++ b/README.md @@ -85,7 +85,7 @@ If you decide to build Emacs from source there's some useful information on this - [Emacs tree-sitter starter-guide](https://git.savannah.gnu.org/cgit/emacs.git/tree/admin/notes/tree-sitter/starter-guide?h=emacs-29) - [Emacs install instructions](https://git.savannah.gnu.org/cgit/emacs.git/tree/INSTALL.REPO). -To check if your emacs already supports tree sitter run +To check if your Emacs supports tree sitter run the following (e.g. by using `M-:`): ``` emacs-lisp (treesit-available-p) @@ -194,7 +194,6 @@ After installing the package do the following. - Check the value of `clojure-mode-hook` and copy all relevant hooks to `clojure-ts-mode-hook`. -example: ``` emacs-lisp (add-hook 'clojure-ts-mode-hook #'cider-mode) (add-hook 'clojure-ts-mode-hook #'enable-paredit-mode) @@ -202,9 +201,8 @@ example: (add-hook 'clojure-ts-mode-hook #'clj-refactor-mode) ``` -- Update `.dir-locals.el` in all of your clojure projects to activate directory local variables in clojure-ts-mode. +- Update `.dir-locals.el` in all of your Clojure projects to activate directory local variables in `clojure-ts-mode`. -example: ``` emacs-lisp ((clojure-mode (cider-clojure-cli-aliases . ":test:repl")) @@ -218,14 +216,16 @@ example: ~~Not yet out of the box, but that [should change soon](https://github.com/clojure-emacs/cider/pull/3461). Feel free to help out with the remaining work, so we can expedite the process.~~ -Support for clojure-ts-mode has landed on the master branch. Make sure to grab the latest cider from melpa/github. +Support for `clojure-ts-mode` has landed on the `master` branch of CIDER (and will be part of CIDER 1.14 when it's released). Make sure to grab the latest CIDER from MELPA/GitHub. -For now, when you take care of the keybindings for the cider functions you use and ensure `cider-mode` is enabled for `clojure-ts-mode` buffers in your config, most functionality should already work: +For now, when you take care of the keybindings for the CIDER commands you use and ensure `cider-mode` is enabled for `clojure-ts-mode` buffers in your config, most functionality should already work: ```emacs-lisp (add-hook 'clojure-ts-mode-hook #'cider-mode) ``` +Check out [this article](https://metaredux.com/posts/2024/02/19/cider-preliminary-support-for-clojure-ts-mode.html) for more details. + ### Does `clojure-ts-mode` work with `inf-clojure`? [Ditto.](https://github.com/clojure-emacs/inf-clojure/pull/215) From eab7dc3d507bf730bc86b215a249fe4ecf008c54 Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Thu, 29 Feb 2024 21:15:49 +0100 Subject: [PATCH 233/379] Fix a typo --- clojure-ts-mode.el | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index 0297501..753e97b 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -745,7 +745,7 @@ See `treesit-simple-indent-rules'." (defun clojure-ts--match-method-body (_node parent _bol) "Matches a `NODE' in the body of a `PARENT' method implementation. -A method implementation referes to concrete implemntations being defined in +A method implementation referes to concrete implementations being defined in forms like deftype, defrecord, reify, proxy, etc." (and (clojure-ts--list-node-p parent) From ee3b619f7f4d401782cf841467d008c50b181f44 Mon Sep 17 00:00:00 2001 From: p4v4n Date: Fri, 1 Mar 2024 09:34:09 +0530 Subject: [PATCH 234/379] Check for tree-sitter before activating clojure-ts-mode --- clojure-ts-mode.el | 45 ++++++++++++++++++++++++--------------------- 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index 753e97b..716c09a 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -993,27 +993,30 @@ See `clojure-ts--font-lock-settings' for usage of MARKDOWN-AVAILABLE." (add-to-list 'auto-mode-alist '("\\.cljd\\'" . clojure-ts-clojuredart-mode)) (add-to-list 'auto-mode-alist '("\\.jank\\'" . clojure-ts-jank-mode))) -;; Redirect clojure-mode to clojure-ts-mode if clojure-mode is present -(if (require 'clojure-mode nil 'noerror) - (progn - (add-to-list 'major-mode-remap-alist '(clojure-mode . clojure-ts-mode)) - (add-to-list 'major-mode-remap-alist '(clojurescript-mode . clojure-ts-clojurescript-mode)) - (add-to-list 'major-mode-remap-alist '(clojurec-mode . clojure-ts-clojurec-mode)) - (clojure-ts--register-novel-modes)) - ;; Clojure-mode is not present, setup auto-modes ourselves - ;; Regular clojure/edn files - ;; I believe dtm is for datomic queries and datoms, which are just edn. - (add-to-list 'auto-mode-alist - '("\\.\\(clj\\|dtm\\|edn\\)\\'" . clojure-ts-mode)) - (add-to-list 'auto-mode-alist '("\\.cljs\\'" . clojure-ts-clojurescript-mode)) - (add-to-list 'auto-mode-alist '("\\.cljc\\'" . clojure-ts-clojurec-mode)) - ;; boot build scripts are Clojure source files - (add-to-list 'auto-mode-alist '("\\(?:build\\|profile\\)\\.boot\\'" . clojure-ts-mode)) - ;; babashka scripts are Clojure source files - (add-to-list 'interpreter-mode-alist '("bb" . clojure-ts-mode)) - ;; nbb scripts are ClojureScript source files - (add-to-list 'interpreter-mode-alist '("nbb" . clojure-ts-clojurescript-mode)) - (clojure-ts--register-novel-modes)) +(if (treesit-available-p) + ;; Redirect clojure-mode to clojure-ts-mode if clojure-mode is present + (if (require 'clojure-mode nil 'noerror) + (progn + (add-to-list 'major-mode-remap-alist '(clojure-mode . clojure-ts-mode)) + (add-to-list 'major-mode-remap-alist '(clojurescript-mode . clojure-ts-clojurescript-mode)) + (add-to-list 'major-mode-remap-alist '(clojurec-mode . clojure-ts-clojurec-mode)) + (clojure-ts--register-novel-modes)) + ;; When Clojure-mode is not present, setup auto-modes ourselves + (progn + ;; Regular clojure/edn files + ;; I believe dtm is for datomic queries and datoms, which are just edn. + (add-to-list 'auto-mode-alist + '("\\.\\(clj\\|dtm\\|edn\\)\\'" . clojure-ts-mode)) + (add-to-list 'auto-mode-alist '("\\.cljs\\'" . clojure-ts-clojurescript-mode)) + (add-to-list 'auto-mode-alist '("\\.cljc\\'" . clojure-ts-clojurec-mode)) + ;; boot build scripts are Clojure source files + (add-to-list 'auto-mode-alist '("\\(?:build\\|profile\\)\\.boot\\'" . clojure-ts-mode)) + ;; babashka scripts are Clojure source files + (add-to-list 'interpreter-mode-alist '("bb" . clojure-ts-mode)) + ;; nbb scripts are ClojureScript source files + (add-to-list 'interpreter-mode-alist '("nbb" . clojure-ts-clojurescript-mode)) + (clojure-ts--register-novel-modes))) + (message "Clojure TS Mode is not activated as tree-sitter support is missing.")) (defvar clojure-ts--find-ns-query (treesit-query-compile From 52c8626af4174ebf39d78fb50ec948526d1a87bd Mon Sep 17 00:00:00 2001 From: Ola Nilsson Date: Tue, 5 Mar 2024 21:32:42 +0100 Subject: [PATCH 235/379] Set lexical-binding in clojure-mode-font-lock-test.el Must have been moved to the second line by a well-meaning fill operation. Unfortunately, it has no effect on the second line and the tests broke on buttercup 1.34. --- clojure-mode-font-lock-test.el | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/clojure-mode-font-lock-test.el b/clojure-mode-font-lock-test.el index e25fb57..3477190 100644 --- a/clojure-mode-font-lock-test.el +++ b/clojure-mode-font-lock-test.el @@ -1,5 +1,4 @@ -;;; clojure-mode-font-lock-test.el --- Clojure Mode: Font lock test suite -;; -*- lexical-binding: t; -*- +;;; clojure-mode-font-lock-test.el --- Clojure Mode: Font lock test suite -*- lexical-binding: t; -*- ;; Copyright (C) 2014-2021 Bozhidar Batsov From 8afa5656955814193b3b27020faf4edf00abda88 Mon Sep 17 00:00:00 2001 From: dalu Date: Thu, 14 Mar 2024 13:52:25 +0800 Subject: [PATCH 236/379] Add `defn-` to imenu's definition detection (#41) --- clojure-ts-mode.el | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index 753e97b..99f28cd 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -580,7 +580,7 @@ Can be called directly, but intended for use as `treesit-defun-name-function'." (treesit-node-text name))))))) (defvar clojure-ts--function-type-regexp - (rx string-start (or "defn" "defmethod") string-end) + (rx string-start (or (seq "defn" (opt "-")) "defmethod") string-end) "Regular expression for matching definition nodes that resemble functions.") (defun clojure-ts--function-node-p (node) From 897dd6c78de6709015968c18d3fbad0e7be2f234 Mon Sep 17 00:00:00 2001 From: yuhan0 Date: Thu, 16 May 2024 04:42:17 +0800 Subject: [PATCH 237/379] Add tests and changelog --- clojure-mode-util-test.el | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/clojure-mode-util-test.el b/clojure-mode-util-test.el index 6157a3d..78a2ac1 100644 --- a/clojure-mode-util-test.el +++ b/clojure-mode-util-test.el @@ -163,6 +163,11 @@ (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"))) + (it "should ignore carriage returns" + (with-clojure-buffer "(ns \r\n foo)" + (expect (clojure-find-ns) :to-equal "foo")) + (with-clojure-buffer "(ns\r\n ^{:doc \"meta\r\n\"}\r\n foo\r\n)" (expect (clojure-find-ns) :to-equal "foo")))) (describe "clojure-sort-ns" From 649bf1120f10250d464d4e9ad1905b481d2e504c Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Tue, 4 Jun 2024 10:46:09 +0300 Subject: [PATCH 238/379] Improve the package version reporting --- clojure-ts-mode.el | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index 99f28cd..d2be3e7 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -54,7 +54,6 @@ ;;; Code: (require 'treesit) -(require 'lisp-mnt) (declare-function treesit-parser-create "treesit.c") (declare-function treesit-node-eq "treesit.c") @@ -71,8 +70,7 @@ :link '(emacs-commentary-link :tag "Commentary" "clojure-mode")) (defconst clojure-ts-mode-version - (eval-when-compile - (lm-version (or load-file-name buffer-file-name))) + "0.2.2" "The current version of `clojure-ts-mode'.") (defcustom clojure-ts-comment-macro-font-lock-body nil @@ -884,7 +882,10 @@ forms like deftype, defrecord, reify, proxy, etc." (defun clojure-ts-mode-display-version () "Display the current `clojure-mode-version' in the minibuffer." (interactive) - (message "clojure-ts-mode (version %s)" clojure-ts-mode-version)) + (let ((pkg-version (package-get-version))) + (if pkg-version + (message "clojure-ts-mode %s (package: %s)" clojure-ts-mode-version pkg-version) + (message "clojure-ts-mode %s" clojure-ts-mode-version)))) (defconst clojure-ts-grammar-recipes '((clojure "https://github.com/sogaiu/tree-sitter-clojure.git" From a49f9e17d202b90d5f4576373efe693b845a1e97 Mon Sep 17 00:00:00 2001 From: Xiyue Deng Date: Mon, 3 Jun 2024 02:35:25 -0700 Subject: [PATCH 239/379] Don't use custom logic to find source directory of clojure-mode.el * Or this will prevent buttercup tests from working with installed clojure-mode under ELPA directories. * A bit more background: in Debian there is autopkgtest which tests the installed package. dh-elpa enables a mode that runs the same ERT or Buttercup tests against the installed package instead of the source tree. In this case, it will let the tests pass during build phase, but autopkgtest will fail as this custom code will still try to locate the source code which will no longer be available. * I have tested under Debian sbuild that removing the code will let the buttercup tests pass under autopkgtest, but not sure whether this is still required for Eldev to work. --- utils/test-helper.el | 7 ------- 1 file changed, 7 deletions(-) diff --git a/utils/test-helper.el b/utils/test-helper.el index e7894f0..af5273a 100644 --- a/utils/test-helper.el +++ b/utils/test-helper.el @@ -25,13 +25,6 @@ (message "Running tests on Emacs %s" emacs-version) -(let* ((current-file (if load-in-progress load-file-name (buffer-file-name))) - (source-directory (locate-dominating-file current-file "Eldev")) - ;; Do not load outdated byte code for tests - (load-prefer-newer t)) - ;; Load the file under test - (load (expand-file-name "clojure-mode" source-directory))) - (defmacro with-clojure-buffer (text &rest body) "Create a temporary buffer, insert TEXT, switch to clojure-mode and evaluate BODY." (declare (indent 1)) From 3c0f8be0daf2701e70a2712cd7d5548988121a0a Mon Sep 17 00:00:00 2001 From: Danny Freeman Date: Mon, 17 Jun 2024 22:58:41 -0400 Subject: [PATCH 240/379] Update warning when tree-sitter is not supported --- clojure-ts-mode.el | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index 716c09a..d4b6acb 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -1016,7 +1016,7 @@ See `clojure-ts--font-lock-settings' for usage of MARKDOWN-AVAILABLE." ;; nbb scripts are ClojureScript source files (add-to-list 'interpreter-mode-alist '("nbb" . clojure-ts-clojurescript-mode)) (clojure-ts--register-novel-modes))) - (message "Clojure TS Mode is not activated as tree-sitter support is missing.")) + (message "Clojure TS Mode will not be activated as tree-sitter support is missing.")) (defvar clojure-ts--find-ns-query (treesit-query-compile From 19df2d536d56dfc799aec1e534472e3fe10fec29 Mon Sep 17 00:00:00 2001 From: Danny Freeman Date: Mon, 1 Jul 2024 08:21:28 -0400 Subject: [PATCH 241/379] Do not require test-helper.el ert-runner will load this file when it is directly under the project test/ directory. I've moved some of the test clojure files under test/samples to help keep test directory clean and a little more focused Fixes #44 --- test/clojure-ts-mode-util-test.el | 1 - test/samples/bug43.clj | 7 +++++++ test/{ => samples}/docstrings.clj | 0 test/{ => samples}/indentation.clj | 0 test/{ => samples}/native.jank | 0 test/{ => samples}/test.clj | 0 test/{utils => }/test-helper.el | 1 - 7 files changed, 7 insertions(+), 2 deletions(-) create mode 100644 test/samples/bug43.clj rename test/{ => samples}/docstrings.clj (100%) rename test/{ => samples}/indentation.clj (100%) rename test/{ => samples}/native.jank (100%) rename test/{ => samples}/test.clj (100%) rename test/{utils => }/test-helper.el (98%) diff --git a/test/clojure-ts-mode-util-test.el b/test/clojure-ts-mode-util-test.el index b4c2e13..5581a2c 100644 --- a/test/clojure-ts-mode-util-test.el +++ b/test/clojure-ts-mode-util-test.el @@ -23,7 +23,6 @@ (require 'clojure-ts-mode) (require 'buttercup) -(require 'test-helper "test/utils/test-helper") (describe "clojure-ts-mode-version" (it "should not be nil" diff --git a/test/samples/bug43.clj b/test/samples/bug43.clj new file mode 100644 index 0000000..85cfda1 --- /dev/null +++ b/test/samples/bug43.clj @@ -0,0 +1,7 @@ +^{:a 1} + (def b 2) + +^{:a 1} +(defn a + "hello" ;; <- + [] "world") diff --git a/test/docstrings.clj b/test/samples/docstrings.clj similarity index 100% rename from test/docstrings.clj rename to test/samples/docstrings.clj diff --git a/test/indentation.clj b/test/samples/indentation.clj similarity index 100% rename from test/indentation.clj rename to test/samples/indentation.clj diff --git a/test/native.jank b/test/samples/native.jank similarity index 100% rename from test/native.jank rename to test/samples/native.jank diff --git a/test/test.clj b/test/samples/test.clj similarity index 100% rename from test/test.clj rename to test/samples/test.clj diff --git a/test/utils/test-helper.el b/test/test-helper.el similarity index 98% rename from test/utils/test-helper.el rename to test/test-helper.el index b7ac0d4..38bce56 100644 --- a/test/utils/test-helper.el +++ b/test/test-helper.el @@ -46,5 +46,4 @@ and point left there." (delete-char -1) ,@body))) -(provide 'test-helper) ;;; test-helper.el ends here From d577270f458e26ab9719d34af213f471306e8d39 Mon Sep 17 00:00:00 2001 From: Daniel Compton Date: Thu, 25 Jul 2024 10:25:52 +1200 Subject: [PATCH 242/379] Update README.md to show CIDER and inf-clojure status --- README.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index bbe54cc..62d8414 100644 --- a/README.md +++ b/README.md @@ -214,9 +214,7 @@ After installing the package do the following. ### Does `clojure-ts-mode` work with CIDER? -~~Not yet out of the box, but that [should change soon](https://github.com/clojure-emacs/cider/pull/3461). Feel free to help out with the remaining work, so we can expedite the process.~~ - -Support for `clojure-ts-mode` has landed on the `master` branch of CIDER (and will be part of CIDER 1.14 when it's released). Make sure to grab the latest CIDER from MELPA/GitHub. +Yes! Preliminary support for `clojure-ts-mode` was released in [CIDER 1.14](https://github.com/clojure-emacs/cider/releases/tag/v1.14.0). Make sure to grab the latest CIDER from MELPA/GitHub. Note that `clojure-mode` is still needed for some APIs that haven't yet been ported to `clojure-ts-mode`. For now, when you take care of the keybindings for the CIDER commands you use and ensure `cider-mode` is enabled for `clojure-ts-mode` buffers in your config, most functionality should already work: @@ -228,7 +226,7 @@ Check out [this article](https://metaredux.com/posts/2024/02/19/cider-preliminar ### Does `clojure-ts-mode` work with `inf-clojure`? -[Ditto.](https://github.com/clojure-emacs/inf-clojure/pull/215) +Currently, there is an [open PR](https://github.com/clojure-emacs/inf-clojure/pull/215) adding support for inf-clojure. ## License From eb9aab37163395d92ce4f950c802de31b91ee3a3 Mon Sep 17 00:00:00 2001 From: Kolmas <169401425+Kolmas225@users.noreply.github.com> Date: Thu, 25 Jul 2024 12:57:13 +0800 Subject: [PATCH 243/379] Fix missing `comment-add` variable in `clojure-ts-mode-variables` (#46) Set `comment-add` to 1 in `clojure-ts-mode-variables`. This resolves #26. --- CHANGELOG.md | 1 + clojure-ts-mode.el | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7af2ea8..cdf049d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## main (unreleased) - [#38]: Add support for `in-ns` forms in `clojure-ts-find-ns`. +- [#46]: Fix missing `comment-add` variable in `clojure-ts-mode-variables` mentioned in [#26] ## 0.2.2 (2024-02-16) diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index 97abfbb..2390cf9 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -912,6 +912,7 @@ forms like deftype, defrecord, reify, proxy, etc." (defun clojure-ts-mode-variables (&optional markdown-available) "Initialize buffer-local variables for `clojure-ts-mode'. See `clojure-ts--font-lock-settings' for usage of MARKDOWN-AVAILABLE." + (setq-local comment-add 1) (setq-local comment-start ";") (setq-local treesit-font-lock-settings (clojure-ts--font-lock-settings markdown-available)) From 0e6816e76ea31c0f0e4d39d8f016c262e57dcb10 Mon Sep 17 00:00:00 2001 From: Roman Rudakov Date: Thu, 25 Jul 2024 13:33:09 +0200 Subject: [PATCH 244/379] Add imenu support for deftest --- CHANGELOG.md | 1 + clojure-ts-mode.el | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cdf049d..65aa179 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ - [#38]: Add support for `in-ns` forms in `clojure-ts-find-ns`. - [#46]: Fix missing `comment-add` variable in `clojure-ts-mode-variables` mentioned in [#26] +- Add imenu support for `deftest` definitions. ## 0.2.2 (2024-02-16) diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index 2390cf9..60fbcb7 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -578,7 +578,7 @@ Can be called directly, but intended for use as `treesit-defun-name-function'." (treesit-node-text name))))))) (defvar clojure-ts--function-type-regexp - (rx string-start (or (seq "defn" (opt "-")) "defmethod") string-end) + (rx string-start (or (seq "defn" (opt "-")) "defmethod" "deftest") string-end) "Regular expression for matching definition nodes that resemble functions.") (defun clojure-ts--function-node-p (node) From fd7e5dab9efe08c0e2bdf7ca6ada2a063915f2ec Mon Sep 17 00:00:00 2001 From: Daanturo Date: Sun, 29 Sep 2024 17:38:37 +0700 Subject: [PATCH 245/379] Let clojure-ts-mode derive from clojure-mode for Emacs 30+ Emacs 30 defines the function `derived-mode-add-parents` that is used for the built-in *-ts-mode, this will make (provided-mode-derived-p 'clojure-ts-mode 'clojure-mode) return true just like other treesit major modes. --- CHANGELOG.md | 1 + clojure-ts-mode.el | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 65aa179..c669d0e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ - [#38]: Add support for `in-ns` forms in `clojure-ts-find-ns`. - [#46]: Fix missing `comment-add` variable in `clojure-ts-mode-variables` mentioned in [#26] - Add imenu support for `deftest` definitions. +- [#53]: Let `clojure-ts-mode` derive from `clojure-mode` for Emacs 30+. ## 0.2.2 (2024-02-16) diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index 60fbcb7..4fe25e9 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -966,6 +966,11 @@ See `clojure-ts--font-lock-settings' for usage of MARKDOWN-AVAILABLE." (when (fboundp 'transpose-sexps-default-function) (setq-local transpose-sexps-function #'transpose-sexps-default-function))))) +;; For Emacs 30+, so that `clojure-ts-mode' is treated as deriving from +;; `clojure-mode' +(when (fboundp #'derived-mode-add-parents) + (derived-mode-add-parents 'clojure-ts-mode '(clojure-mode))) + ;;;###autoload (define-derived-mode clojure-ts-clojurescript-mode clojure-ts-mode "ClojureScript[TS]" "Major mode for editing ClojureScript code. From dc74c4546f3bd9e026eef5b0519e8b5aff9781cc Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Mon, 30 Sep 2024 15:16:00 +0200 Subject: [PATCH 246/379] Fix a lint warning --- clojure-ts-mode.el | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index 4fe25e9..8a68839 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -968,7 +968,7 @@ See `clojure-ts--font-lock-settings' for usage of MARKDOWN-AVAILABLE." ;; For Emacs 30+, so that `clojure-ts-mode' is treated as deriving from ;; `clojure-mode' -(when (fboundp #'derived-mode-add-parents) +(when (fboundp 'derived-mode-add-parents) (derived-mode-add-parents 'clojure-ts-mode '(clojure-mode))) ;;;###autoload From 76438f6ea412c6d80e6b8b21bc23c574eddfbc01 Mon Sep 17 00:00:00 2001 From: Dieter Komendera Date: Wed, 23 Oct 2024 21:10:51 +0200 Subject: [PATCH 247/379] Fix definitions with metadata not being index by imenu Partly addresses #42 --- CHANGELOG.md | 1 + clojure-ts-mode.el | 15 +++++++++--- test/clojure-ts-mode-imenu-test.el | 38 ++++++++++++++++++++++++++++++ 3 files changed, 51 insertions(+), 3 deletions(-) create mode 100644 test/clojure-ts-mode-imenu-test.el diff --git a/CHANGELOG.md b/CHANGELOG.md index c669d0e..743d6b7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - [#46]: Fix missing `comment-add` variable in `clojure-ts-mode-variables` mentioned in [#26] - Add imenu support for `deftest` definitions. - [#53]: Let `clojure-ts-mode` derive from `clojure-mode` for Emacs 30+. +- [#42]: Fix imenu support for definitions with metadata. ## 0.2.2 (2024-02-16) diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index 8a68839..a562f03 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -520,6 +520,10 @@ with the markdown_inline grammar." "Return non-nil if NODE is a Clojure keyword." (string-equal "kwd_lit" (treesit-node-type node))) +(defun clojure-ts--metadata-node-p (node) + "Return non-nil if NODE is a Clojure metadata node." + (string-equal "meta_lit" (treesit-node-type node))) + (defun clojure-ts--named-node-text (node) "Gets the name of a symbol or keyword NODE. This does not include the NODE's namespace." @@ -530,6 +534,11 @@ This does not include the NODE's namespace." (and (clojure-ts--symbol-node-p node) (string-equal expected-symbol-name (clojure-ts--named-node-text node)))) +(defun clojure-ts--node-child-skip-metadata (node n) + "Return the Nth child of NODE like `treesit-node-child`, skipping the optional metadata node at pos 0 if present." + (let ((first-child (treesit-node-child node 0 t))) + (treesit-node-child node (if (clojure-ts--metadata-node-p first-child) (1+ n) n) t))) + (defun clojure-ts--symbol-matches-p (symbol-regexp node) "Return non-nil if NODE is a symbol that matches SYMBOL-REGEXP." (and (clojure-ts--symbol-node-p node) @@ -550,7 +559,7 @@ like \"defn\". See `clojure-ts--definition-node-p' when an exact match is possible." (and (clojure-ts--list-node-p node) - (let* ((child (treesit-node-child node 0 t)) + (let* ((child (clojure-ts--node-child-skip-metadata node 0)) (child-txt (clojure-ts--named-node-text child))) (and (clojure-ts--symbol-node-p child) (string-match-p definition-type-regexp child-txt))))) @@ -565,8 +574,8 @@ that a node is a definition is intended to be done elsewhere. Can be called directly, but intended for use as `treesit-defun-name-function'." (when (and (clojure-ts--list-node-p node) - (clojure-ts--symbol-node-p (treesit-node-child node 0 t))) - (let ((sym (treesit-node-child node 1 t))) + (clojure-ts--symbol-node-p (clojure-ts--node-child-skip-metadata node 0))) + (let ((sym (clojure-ts--node-child-skip-metadata node 1))) (when (clojure-ts--symbol-node-p sym) ;; Extracts ns and name, and recreates the full var name. ;; We can't just get the node-text of the full symbol because diff --git a/test/clojure-ts-mode-imenu-test.el b/test/clojure-ts-mode-imenu-test.el new file mode 100644 index 0000000..1dbe71c --- /dev/null +++ b/test/clojure-ts-mode-imenu-test.el @@ -0,0 +1,38 @@ +;;; clojure-ts-mode-util-test.el --- Clojure TS Mode: util test suite -*- lexical-binding: t; -*- + +;; Copyright © 2022-2024 Danny Freeman + +;; This file is not part of GNU Emacs. + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;;; Commentary: + +;; The unit test suite of Clojure TS Mode + +(require 'clojure-ts-mode) +(require 'buttercup) +(require 'imenu) + + +(describe "clojure-ts-mode imenu integration" + (it "should index def with meta data" + (with-clojure-ts-buffer "^{:foo 1}(def a 1)" + (expect (imenu--in-alist "a" (imenu--make-index-alist)) + :not :to-be nil))) + + (it "should index defn with meta data" + (with-clojure-ts-buffer "^{:foo 1}(defn a [])" + (expect (imenu--in-alist "a" (imenu--make-index-alist)) + :not :to-be nil)))) From 98060c51a24767f7f48e7a12f049d407930e6a40 Mon Sep 17 00:00:00 2001 From: Dieter Komendera Date: Fri, 25 Oct 2024 23:14:00 +0200 Subject: [PATCH 248/379] Fix font locking of defintions with metadata --- CHANGELOG.md | 1 + clojure-ts-mode.el | 14 +++++++++----- test/samples/test.clj | 3 +++ 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 743d6b7..17e5c96 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ - Add imenu support for `deftest` definitions. - [#53]: Let `clojure-ts-mode` derive from `clojure-mode` for Emacs 30+. - [#42]: Fix imenu support for definitions with metadata. +- [#42]: Fix font locking of definitions with metadata ## 0.2.2 (2024-02-16) diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index a562f03..a538b67 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -260,7 +260,8 @@ if a third argument (the value) is provided. (defun clojure-ts--docstring-query (capture-symbol) "Return a query that captures docstrings with CAPTURE-SYMBOL." `(;; Captures docstrings in def - ((list_lit :anchor (sym_lit) @_def_symbol + ((list_lit :anchor (meta_lit) :? + :anchor (sym_lit) @_def_symbol :anchor (comment) :? :anchor (sym_lit) ; variable name :anchor (comment) :? @@ -288,7 +289,8 @@ if a third argument (the value) is provided. @_def_symbol) (:equal @_doc-keyword ":doc")) ;; Captures docstrings defn, defmacro, ns, and things like that - ((list_lit :anchor (sym_lit) @_def_symbol + ((list_lit :anchor (meta_lit) :? + :anchor (sym_lit) @_def_symbol :anchor (comment) :? :anchor (sym_lit) ; function_name :anchor (comment) :? @@ -347,7 +349,7 @@ with the markdown_inline grammar." :feature 'builtin :language 'clojure - `(((list_lit :anchor (sym_lit (sym_name) @font-lock-keyword-face)) + `(((list_lit meta: _ :? :anchor (sym_lit (sym_name) @font-lock-keyword-face)) (:match ,clojure-ts--builtin-symbol-regexp @font-lock-keyword-face)) ((sym_name) @font-lock-builtin-face (:match ,clojure-ts--builtin-dynamic-var-regexp @font-lock-builtin-face))) @@ -369,7 +371,8 @@ with the markdown_inline grammar." ;; No wonder the tree-sitter-clojure grammar only touches syntax, and not semantics :feature 'definition ;; defn and defn like macros :language 'clojure - `(((list_lit :anchor (sym_lit (sym_name) @def) + `(((list_lit :anchor meta: _ :? + :anchor (sym_lit (sym_name) @def) :anchor (sym_lit (sym_name) @font-lock-function-name-face)) (:match ,(rx-to-string `(seq bol @@ -410,7 +413,8 @@ with the markdown_inline grammar." :feature 'variable ;; def, defonce :language 'clojure - `(((list_lit :anchor (sym_lit (sym_name) @def) + `(((list_lit :anchor meta: _ :? + :anchor (sym_lit (sym_name) @def) :anchor (sym_lit (sym_name) @font-lock-variable-name-face)) (:match ,clojure-ts--variable-definition-symbol-regexp @def))) diff --git a/test/samples/test.clj b/test/samples/test.clj index ce4c029..1ab5efa 100644 --- a/test/samples/test.clj +++ b/test/samples/test.clj @@ -289,6 +289,9 @@ clojure.core/map (def ^Integer x 1) +^{:foo true} +(defn b "hello" [] "world") + (comment (defrecord TestRecord [field] AutoCloseable From 885c4953e24afb0d6cf3f18e6c98ec433cef3feb Mon Sep 17 00:00:00 2001 From: Dieter Komendera Date: Sat, 26 Oct 2024 12:35:36 +0200 Subject: [PATCH 249/379] Fix comment --- test/clojure-ts-mode-imenu-test.el | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/clojure-ts-mode-imenu-test.el b/test/clojure-ts-mode-imenu-test.el index 1dbe71c..ede2747 100644 --- a/test/clojure-ts-mode-imenu-test.el +++ b/test/clojure-ts-mode-imenu-test.el @@ -1,4 +1,4 @@ -;;; clojure-ts-mode-util-test.el --- Clojure TS Mode: util test suite -*- lexical-binding: t; -*- +;;; clojure-ts-mode-imenu-test.el --- Clojure TS Mode: imenu test suite -*- lexical-binding: t; -*- ;; Copyright © 2022-2024 Danny Freeman From 12f27cb4ae7c10daf975c368bb39fe15e371c0fa Mon Sep 17 00:00:00 2001 From: Dieter Komendera Date: Sat, 26 Oct 2024 12:35:55 +0200 Subject: [PATCH 250/379] Port font lock test infrastructure and some basic tests over from clojure-mode --- test/clojure-ts-mode-font-lock-test.el | 101 +++++++++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 test/clojure-ts-mode-font-lock-test.el diff --git a/test/clojure-ts-mode-font-lock-test.el b/test/clojure-ts-mode-font-lock-test.el new file mode 100644 index 0000000..eb90995 --- /dev/null +++ b/test/clojure-ts-mode-font-lock-test.el @@ -0,0 +1,101 @@ +;;; clojure-ts-mode-font-lock-test.el --- Clojure TS Mode: font lock test suite -*- lexical-binding: t; -*- + +;; Copyright © 2022-2024 Danny Freeman + +;; This file is not part of GNU Emacs. + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;;; Commentary: + +;; The unit test suite of Clojure TS Mode + +(require 'clojure-ts-mode) +(require 'cl-lib) +(require 'buttercup) + +;; (use-package buttercup) + +;;;; Utilities + +(defmacro with-fontified-clojure-ts-buffer (content &rest body) + "Evaluate BODY in a temporary buffer with CONTENT." + (declare (debug t) + (indent 1)) + `(with-clojure-ts-buffer ,content + (font-lock-ensure) + (goto-char (point-min)) + ,@body)) + +(defun clojure-ts-get-face-at (start end content) + "Get the face between START and END in CONTENT." + (with-fontified-clojure-ts-buffer content + (let ((start-face (get-text-property start 'face)) + (all-faces (cl-loop for i from start to end collect (get-text-property + i 'face)))) + (if (cl-every (lambda (face) (eq face start-face)) all-faces) + start-face + 'various-faces)))) + +(defun expect-face-at (content start end face) + "Expect face in CONTENT between START and END to be equal to FACE." + (expect (clojure-ts-get-face-at start end content) :to-equal face)) + +(defun expect-faces-at (content &rest faces) + "Expect FACES in CONTENT. + +FACES is a list of the form (content (start end expected-face)*)" + (dolist (face faces) + (apply (apply-partially #'expect-face-at content) face))) + +(defmacro when-fontifying-it (description &rest tests) + "Return a buttercup spec. + +TESTS are lists of the form (content (start end expected-face)*). For each test +check that each `expected-face` is found in `content` between `start` and `end`. + +DESCRIPTION is the description of the spec." + (declare (indent 1)) + `(it ,description + (dolist (test (quote ,tests)) + (apply #'expect-faces-at test)))) + +;;;; Font locking + +(describe "clojure-ts-mode-syntax-table" + (when-fontifying-it "should handle any known def form" + ("(def a 1)" (2 4 font-lock-keyword-face)) + ("(defonce a 1)" (2 8 font-lock-keyword-face)) + ("(defn a [b])" (2 5 font-lock-keyword-face)) + ("(defmacro a [b])" (2 9 font-lock-keyword-face)) + ("(definline a [b])" (2 10 font-lock-keyword-face)) + ("(defmulti a identity)" (2 9 font-lock-keyword-face)) + ("(defmethod a :foo [b] (println \"bar\"))" (2 10 font-lock-keyword-face)) + ("(defprotocol a (b [this] \"that\"))" (2 12 font-lock-keyword-face)) + ("(definterface a (b [c]))" (2 13 font-lock-keyword-face)) + ("(defrecord a [b c])" (2 10 font-lock-keyword-face)) + ("(deftype a [b c])" (2 8 font-lock-keyword-face)) + ("(defstruct a :b :c)" (2 10 font-lock-keyword-face)) + ("(deftest a (is (= 1 1)))" (2 8 font-lock-keyword-face)) + + + ;; TODO: copied from clojure-mode, but failing + ;; ("(defne [x y])" (2 6 font-lock-keyword-face)) + ;; ("(defnm a b)" (2 6 font-lock-keyword-face)) + ;; ("(defnu)" (2 6 font-lock-keyword-face)) + ;; ("(defnc [a])" (2 6 font-lock-keyword-face)) + ;; ("(defna)" (2 6 font-lock-keyword-face)) + ;; ("(deftask a)" (2 8 font-lock-keyword-face)) + ;; ("(defstate a :start \"b\" :stop \"c\")" (2 9 font-lock-keyword-face))) + ) From f4d0a9fb1cb429eb1348a4240006a400aa59e6a6 Mon Sep 17 00:00:00 2001 From: Dieter Komendera Date: Sat, 26 Oct 2024 13:17:06 +0200 Subject: [PATCH 251/379] Port variable-def-string-with-docstring font lock test from clojure-mode --- test/clojure-ts-mode-font-lock-test.el | 46 +++++++++++++++++++------- 1 file changed, 34 insertions(+), 12 deletions(-) diff --git a/test/clojure-ts-mode-font-lock-test.el b/test/clojure-ts-mode-font-lock-test.el index eb90995..3c904f0 100644 --- a/test/clojure-ts-mode-font-lock-test.el +++ b/test/clojure-ts-mode-font-lock-test.el @@ -87,15 +87,37 @@ DESCRIPTION is the description of the spec." ("(defrecord a [b c])" (2 10 font-lock-keyword-face)) ("(deftype a [b c])" (2 8 font-lock-keyword-face)) ("(defstruct a :b :c)" (2 10 font-lock-keyword-face)) - ("(deftest a (is (= 1 1)))" (2 8 font-lock-keyword-face)) - - - ;; TODO: copied from clojure-mode, but failing - ;; ("(defne [x y])" (2 6 font-lock-keyword-face)) - ;; ("(defnm a b)" (2 6 font-lock-keyword-face)) - ;; ("(defnu)" (2 6 font-lock-keyword-face)) - ;; ("(defnc [a])" (2 6 font-lock-keyword-face)) - ;; ("(defna)" (2 6 font-lock-keyword-face)) - ;; ("(deftask a)" (2 8 font-lock-keyword-face)) - ;; ("(defstate a :start \"b\" :stop \"c\")" (2 9 font-lock-keyword-face))) - ) + ("(deftest a (is (= 1 1)))" (2 8 font-lock-keyword-face))) + + (when-fontifying-it "variable-def-string-with-docstring" + ("(def foo \"usage\" \"hello\")" + (10 16 font-lock-doc-face) + (18 24 font-lock-string-face)) + + ("(def foo \"usage\" \"hello\" )" + (18 24 font-lock-string-face)) + + ("(def foo \"usage\" \n \"hello\")" + (21 27 font-lock-string-face)) + + ("(def foo \n \"usage\" \"hello\")" + (13 19 font-lock-doc-face)) + + ("(def foo \n \"usage\" \n \"hello\")" + (13 19 font-lock-doc-face) + (24 30 font-lock-string-face)) + + ("(def test-string\n \"this\\n\n is\n my\n string\")" + (20 24 font-lock-string-face) + (25 26 font-lock-string-face) + (27 46 font-lock-string-face))) + + ;; TODO: copied from clojure-mode, but failing + ;; ("(defne [x y])" (2 6 font-lock-keyword-face)) + ;; ("(defnm a b)" (2 6 font-lock-keyword-face)) + ;; ("(defnu)" (2 6 font-lock-keyword-face)) + ;; ("(defnc [a])" (2 6 font-lock-keyword-face)) + ;; ("(defna)" (2 6 font-lock-keyword-face)) + ;; ("(deftask a)" (2 8 font-lock-keyword-face)) + ;; ("(defstate a :start \"b\" :stop \"c\")" (2 9 font-lock-keyword-face)) +) From 49844a061a7b61d44bb90ba386edf0980eff3baf Mon Sep 17 00:00:00 2001 From: Dieter Komendera Date: Sat, 26 Oct 2024 14:11:25 +0200 Subject: [PATCH 252/379] Add font lock tests for def and defn with metadata --- test/clojure-ts-mode-font-lock-test.el | 36 +++++++++++++++++++------- 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/test/clojure-ts-mode-font-lock-test.el b/test/clojure-ts-mode-font-lock-test.el index 3c904f0..f966652 100644 --- a/test/clojure-ts-mode-font-lock-test.el +++ b/test/clojure-ts-mode-font-lock-test.el @@ -87,7 +87,19 @@ DESCRIPTION is the description of the spec." ("(defrecord a [b c])" (2 10 font-lock-keyword-face)) ("(deftype a [b c])" (2 8 font-lock-keyword-face)) ("(defstruct a :b :c)" (2 10 font-lock-keyword-face)) - ("(deftest a (is (= 1 1)))" (2 8 font-lock-keyword-face))) + ("(deftest a (is (= 1 1)))" (2 8 font-lock-keyword-face)) + + + ;; TODO: copied from clojure-mode, but failing + ;; ("(defne [x y])" (2 6 font-lock-keyword-face)) + ;; ("(defnm a b)" (2 6 font-lock-keyword-face)) + ;; ("(defnu)" (2 6 font-lock-keyword-face)) + ;; ("(defnc [a])" (2 6 font-lock-keyword-face)) + ;; ("(defna)" (2 6 font-lock-keyword-face)) + ;; ("(deftask a)" (2 8 font-lock-keyword-face)) + ;; ("(defstate a :start \"b\" :stop \"c\")" (2 9 font-lock-keyword-face)) + + ) (when-fontifying-it "variable-def-string-with-docstring" ("(def foo \"usage\" \"hello\")" @@ -112,12 +124,16 @@ DESCRIPTION is the description of the spec." (25 26 font-lock-string-face) (27 46 font-lock-string-face))) - ;; TODO: copied from clojure-mode, but failing - ;; ("(defne [x y])" (2 6 font-lock-keyword-face)) - ;; ("(defnm a b)" (2 6 font-lock-keyword-face)) - ;; ("(defnu)" (2 6 font-lock-keyword-face)) - ;; ("(defnc [a])" (2 6 font-lock-keyword-face)) - ;; ("(defna)" (2 6 font-lock-keyword-face)) - ;; ("(deftask a)" (2 8 font-lock-keyword-face)) - ;; ("(defstate a :start \"b\" :stop \"c\")" (2 9 font-lock-keyword-face)) -) + (when-fontifying-it "variable-def-with-metadata-and-docstring" + ("^{:foo bar}(def foo \n \"usage\" \n \"hello\")" + (13 15 font-lock-keyword-face) + (17 19 font-lock-variable-name-face) + (24 30 font-lock-doc-face) + (35 41 font-lock-string-face))) + + (when-fontifying-it "defn-with-metadata-and-docstring" + ("^{:foo bar}(defn foo \n \"usage\" \n [] \n \"hello\")" + (13 16 font-lock-keyword-face) + (18 20 font-lock-function-name-face) + (25 31 font-lock-doc-face) + (40 46 font-lock-string-face)))) From 2e7765cc389979deaf2b0488a6dbb2a42ea842df Mon Sep 17 00:00:00 2001 From: Dieter Komendera Date: Sun, 27 Oct 2024 23:50:56 +0100 Subject: [PATCH 253/379] Fix indentation of definitions with metadata --- CHANGELOG.md | 1 + clojure-ts-mode.el | 12 ++++++++++-- test/samples/indentation.clj | 25 +++++++++++++++++++++++++ 3 files changed, 36 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 17e5c96..666bc9a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ - [#53]: Let `clojure-ts-mode` derive from `clojure-mode` for Emacs 30+. - [#42]: Fix imenu support for definitions with metadata. - [#42]: Fix font locking of definitions with metadata +- [#42]: Fix indentation of definitions with metadata ## 0.2.2 (2024-02-16) diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index a538b67..09247cc 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -737,19 +737,20 @@ https://github.com/weavejester/cljfmt/blob/fb26b22f569724b05c93eb2502592dfc2de89 (or (clojure-ts--symbol-node-p first-child) (clojure-ts--keyword-node-p first-child))))) -(defun clojure-ts--match-expression-in-body (_node parent _bol) +(defun clojure-ts--match-expression-in-body (node parent _bol) "Match NODE if it is an expression used in a body argument. PARENT is expected to be a list literal. See `treesit-simple-indent-rules'." (and (clojure-ts--list-node-p parent) - (let ((first-child (treesit-node-child parent 0 t))) + (let ((first-child (clojure-ts--node-child-skip-metadata parent 0))) (and (not (clojure-ts--symbol-matches-p ;; Symbols starting with this are false positives (rx line-start (or "default" "deflate" "defer")) first-child)) + (not (clojure-ts--match-with-metadata node)) (clojure-ts--symbol-matches-p clojure-ts--symbols-with-body-expressions-regexp first-child))))) @@ -821,6 +822,12 @@ forms like deftype, defrecord, reify, proxy, etc." (clojure-ts--match-fn-docstring parent) (clojure-ts--match-method-docstring parent)))) +(defun clojure-ts--match-with-metadata (node &optional _parent _bol) + "Match NODE when it has metadata." + (let ((prev-sibling (treesit-node-prev-sibling node))) + (and prev-sibling + (clojure-ts--metadata-node-p prev-sibling)))) + (defun clojure-ts--semantic-indent-rules () "Return a list of indentation rules for `treesit-simple-indent-rules'." `((clojure @@ -833,6 +840,7 @@ forms like deftype, defrecord, reify, proxy, etc." (clojure-ts--match-threading-macro-arg prev-sibling 0) ;; https://guide.clojure.style/#vertically-align-fn-args (clojure-ts--match-function-call-arg (nth-sibling 2 nil) 0) + (clojure-ts--match-with-metadata parent 0) ;; Literal Sequences ((parent-is "list_lit") parent 1) ;; https://guide.clojure.style/#one-space-indent ((parent-is "vec_lit") parent 1) ;; https://guide.clojure.style/#bindings-alignment diff --git a/test/samples/indentation.clj b/test/samples/indentation.clj index a5fe041..e655384 100644 --- a/test/samples/indentation.clj +++ b/test/samples/indentation.clj @@ -118,3 +118,28 @@ ([a] a) ([a b] b))}) + + +^:foo +(def a 1) + +^{:foo true} +(def b 2) + +^{:foo true} +[1 2] + +(comment + ^{:a 1} + (def a 2)) + +(defn hinted + (^String []) + (^java.util.List + [a & args])) + +^{:foo true} +(defn c + "hello" + [_foo] + (+ 1 1)) From d824fcd2d86ca012ab7ff65af78d16d589f78df8 Mon Sep 17 00:00:00 2001 From: Dieter Komendera Date: Sun, 27 Oct 2024 23:53:12 +0100 Subject: [PATCH 254/379] Port indentation testing infrastructure over from clojure-mode --- Eldev | 2 +- test/clojure-ts-mode-indentation-test.el | 110 +++++++++++++++++++++++ 2 files changed, 111 insertions(+), 1 deletion(-) create mode 100644 test/clojure-ts-mode-indentation-test.el diff --git a/Eldev b/Eldev index e6b3c00..2030e26 100644 --- a/Eldev +++ b/Eldev @@ -10,7 +10,7 @@ (eldev-use-plugin 'autoloads) -(eldev-add-extra-dependencies 'test 'buttercup) +(eldev-add-extra-dependencies 'test 'buttercup 's) (setq byte-compile-docstring-max-column 240) (setq checkdoc-force-docstrings-flag nil) diff --git a/test/clojure-ts-mode-indentation-test.el b/test/clojure-ts-mode-indentation-test.el new file mode 100644 index 0000000..e23bd17 --- /dev/null +++ b/test/clojure-ts-mode-indentation-test.el @@ -0,0 +1,110 @@ +;;; clojure-ts-mode-indentation-test.el --- Clojure TS Mode: indentation test suite -*- lexical-binding: t; -*- + +;; Copyright © 2022-2024 Danny Freeman + +;; This file is not part of GNU Emacs. + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;;; Commentary: + +;; The unit test suite of Clojure TS Mode + +(require 'clojure-ts-mode) +(require 'cl-lib) +(require 'buttercup) +(require 's nil t) ;Don't burp if it's missing during compilation. + + +(defmacro when-indenting-with-point-it (description before after) + "Return a buttercup spec. + +Check whether the swift indentation command will correctly change the buffer. +Will also check whether point is moved to the expected position. + +BEFORE is the buffer string before indenting, where a pipe (|) represents +point. + +AFTER is the expected buffer string after indenting, where a pipe (|) +represents the expected position of point. + +DESCRIPTION is a string with the description of the spec." + (declare (indent 1)) + `(it ,description + (let* ((after ,after) + (expected-cursor-pos (1+ (s-index-of "|" after))) + (expected-state (delete ?| after))) + (with-clojure-ts-buffer ,before + (goto-char (point-min)) + (search-forward "|") + (delete-char -1) + (font-lock-ensure) + (indent-according-to-mode) + (expect (buffer-string) :to-equal expected-state) + (expect (point) :to-equal expected-cursor-pos))))) + + + +;; Backtracking indent +(defmacro when-indenting-it (description &rest forms) + "Return a buttercup spec. + +Check that all FORMS correspond to properly indented sexps. + +DESCRIPTION is a string with the description of the spec." + (declare (indent 1)) + `(it ,description + (progn + ,@(mapcar (lambda (form) + `(with-temp-buffer + (clojure-ts-mode) + (insert "\n" ,form);,(replace-regexp-in-string "\n +" "\n " form)) + (indent-region (point-min) (point-max)) + (expect (buffer-string) :to-equal ,(concat "\n" form)))) + forms)))) + + +;; Provide font locking for easier test editing. + +(font-lock-add-keywords + 'emacs-lisp-mode + `((,(rx "(" (group "when-indenting-with-point-it") eow) + (1 font-lock-keyword-face)) + (,(rx "(" + (group "when-indenting-with-point-it") (+ space) + (group bow (+ (not space)) eow) + ) + (1 font-lock-keyword-face) + (2 font-lock-function-name-face)))) + + +(describe "indentation" + (it "should not hang on end of buffer" + (with-clojure-ts-buffer "(let [a b]" + (goto-char (point-max)) + (expect + (with-timeout (2) + (newline-and-indent) + t)))) + + (when-indenting-with-point-it "should have no indentation at top level" + "|x" + + "|x") + + (when-indenting-it "should handle non-symbol at start" + " +{\"1\" 2 + *3 4}") +) From 869790b4268cfb8985728400f1407631b6f4a69a Mon Sep 17 00:00:00 2001 From: Dieter Komendera Date: Mon, 28 Oct 2024 22:26:47 +0100 Subject: [PATCH 255/379] Add tests for indentation of forms with metadata --- test/clojure-ts-mode-indentation-test.el | 30 +++++++++++++++++++++++- test/samples/indentation.clj | 2 +- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/test/clojure-ts-mode-indentation-test.el b/test/clojure-ts-mode-indentation-test.el index e23bd17..be77588 100644 --- a/test/clojure-ts-mode-indentation-test.el +++ b/test/clojure-ts-mode-indentation-test.el @@ -107,4 +107,32 @@ DESCRIPTION is a string with the description of the spec." " {\"1\" 2 *3 4}") -) + + (when-indenting-it "should have no indentation at top level lists with metadata" + " +^{:foo true} +(def b 2)") + + (when-indenting-it "should have no indentation at top level vectors with metadata" + " +^{:foo true} +[1 2]") + + (when-indenting-it "should have no indentation at top level maps with metadata" + " +^{:foo true} +{:a 1}") + + (when-indenting-it "should have no indentation with metadata inside comment" + " +(comment + ^{:a 1} + (def a 2))") + + (when-indenting-it "should have params, docstring and body correctly indented in presence of metadata" + " +^{:foo true} +(defn c + \"hello\" + [_foo] + (+ 1 1))")) diff --git a/test/samples/indentation.clj b/test/samples/indentation.clj index e655384..2996229 100644 --- a/test/samples/indentation.clj +++ b/test/samples/indentation.clj @@ -142,4 +142,4 @@ (defn c "hello" [_foo] - (+ 1 1)) + (+ 1 1)) From e69b0ac63b864f82df3f194aebf4d453d82ccb9c Mon Sep 17 00:00:00 2001 From: Dieter Komendera Date: Wed, 30 Oct 2024 22:12:54 +0100 Subject: [PATCH 256/379] Vendor s-index-of in test utils instead of requiring s --- Eldev | 2 +- test/clojure-ts-mode-indentation-test.el | 2 +- test/test-helper.el | 9 +++++++++ 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/Eldev b/Eldev index 2030e26..e6b3c00 100644 --- a/Eldev +++ b/Eldev @@ -10,7 +10,7 @@ (eldev-use-plugin 'autoloads) -(eldev-add-extra-dependencies 'test 'buttercup 's) +(eldev-add-extra-dependencies 'test 'buttercup) (setq byte-compile-docstring-max-column 240) (setq checkdoc-force-docstrings-flag nil) diff --git a/test/clojure-ts-mode-indentation-test.el b/test/clojure-ts-mode-indentation-test.el index be77588..e4d73a6 100644 --- a/test/clojure-ts-mode-indentation-test.el +++ b/test/clojure-ts-mode-indentation-test.el @@ -43,7 +43,7 @@ DESCRIPTION is a string with the description of the spec." (declare (indent 1)) `(it ,description (let* ((after ,after) - (expected-cursor-pos (1+ (s-index-of "|" after))) + (expected-cursor-pos (1+ (clojure-ts--s-index-of "|" after))) (expected-state (delete ?| after))) (with-clojure-ts-buffer ,before (goto-char (point-min)) diff --git a/test/test-helper.el b/test/test-helper.el index 38bce56..3866003 100644 --- a/test/test-helper.el +++ b/test/test-helper.el @@ -46,4 +46,13 @@ and point left there." (delete-char -1) ,@body))) +(defun clojure-ts--s-index-of (needle s &optional ignore-case) + "Returns first index of NEEDLE in S, or nil. + +If IGNORE-CASE is non-nil, the comparison is done without paying +attention to case differences." + (declare (pure t) (side-effect-free t)) + (let ((case-fold-search ignore-case)) + (string-match-p (regexp-quote needle) s))) + ;;; test-helper.el ends here From a39b4d95feb45dfb55c3f3aa04a425aac86abac0 Mon Sep 17 00:00:00 2001 From: Dieter Komendera Date: Thu, 31 Oct 2024 17:35:06 +0100 Subject: [PATCH 257/379] Disable tests merged from clojure-mode for now using eldev --- Eldev | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Eldev b/Eldev index e6b3c00..9793a9b 100644 --- a/Eldev +++ b/Eldev @@ -25,3 +25,8 @@ enable-local-variables :safe)) (setq eldev-project-main-file "clojure-ts-mode.el") + +;; Exclude tests merged from clojure-mode +(setf eldev-test-fileset + `(:and ,eldev-test-fileset + (:not "./clojure-mode-*"))) From b3817dca78df3a0fa124c17cf08e99a84b056eaa Mon Sep 17 00:00:00 2001 From: Dieter Komendera Date: Thu, 31 Oct 2024 19:20:32 +0100 Subject: [PATCH 258/379] Don't lint tests merged from clojure-mode --- Eldev | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Eldev b/Eldev index 9793a9b..69f390a 100644 --- a/Eldev +++ b/Eldev @@ -26,7 +26,9 @@ (setq eldev-project-main-file "clojure-ts-mode.el") -;; Exclude tests merged from clojure-mode +;; Exclude tests merged from clojure-mode from running and linting (setf eldev-test-fileset `(:and ,eldev-test-fileset (:not "./clojure-mode-*"))) +(setf eldev-lint-ignored-fileset + `(:or ,eldev-lint-ignored-fileset "./clojure-mode-*")) From c259d308cbfa33b6dddfd3d17e452d8488a3c419 Mon Sep 17 00:00:00 2001 From: Dieter Komendera Date: Thu, 31 Oct 2024 19:21:04 +0100 Subject: [PATCH 259/379] Move over remaining test utils from the clojure-mode test-helper --- test/test-helper.el | 52 ++++++++++++++++++++++ utils/test-helper.el | 103 ------------------------------------------- 2 files changed, 52 insertions(+), 103 deletions(-) delete mode 100644 utils/test-helper.el diff --git a/test/test-helper.el b/test/test-helper.el index 3866003..cb0a2ad 100644 --- a/test/test-helper.el +++ b/test/test-helper.el @@ -55,4 +55,56 @@ attention to case differences." (let ((case-fold-search ignore-case)) (string-match-p (regexp-quote needle) s))) +(defmacro when-refactoring-it (description before after &rest body) + "Return a buttercup spec. + +Insert BEFORE into a buffer, evaluate BODY and compare the resulting buffer to +AFTER. + +BODY should contain the refactoring that transforms BEFORE into AFTER. + +DESCRIPTION is the description of the spec." + (declare (indent 1)) + `(it ,description + (with-clojure-ts-buffer ,before + ,@body + (expect (buffer-string) :to-equal ,after)))) + +(defmacro when-refactoring-with-point-it (description before after &rest body) + "Return a buttercup spec. + +Like when-refactor-it but also checks whether point is moved to the expected +position. + +BEFORE is the buffer string before refactoring, where a pipe (|) represents +point. + +AFTER is the expected buffer string after refactoring, where a pipe (|) +represents the expected position of point. + +DESCRIPTION is a string with the description of the spec." + (declare (indent 1)) + `(it ,description + (let* ((after ,after) + (expected-cursor-pos (1+ (clojure-ts--s-index-of "|" after))) + (expected-state (delete ?| after))) + (with-clojure-ts-buffer ,before + (goto-char (point-min)) + (search-forward "|") + (delete-char -1) + ,@body + (expect (buffer-string) :to-equal expected-state) + (expect (point) :to-equal expected-cursor-pos))))) + + +;; https://emacs.stackexchange.com/a/55031 +(defmacro with-temp-dir (temp-dir &rest body) + "Create a temporary directory and bind its to TEMP-DIR while evaluating BODY. +Removes the temp directory at the end of evaluation." + `(let ((,temp-dir (make-temp-file "" t))) + (unwind-protect + (progn + ,@body) + (delete-directory ,temp-dir t)))) + ;;; test-helper.el ends here diff --git a/utils/test-helper.el b/utils/test-helper.el deleted file mode 100644 index af5273a..0000000 --- a/utils/test-helper.el +++ /dev/null @@ -1,103 +0,0 @@ -;;; test-helper.el --- Clojure Mode: Non-interactive unit-test setup -*- lexical-binding: t; -*- - -;; Copyright (C) 2014-2021 Bozhidar Batsov - -;; This file is not part of GNU Emacs. - -;; This program is free software; you can redistribute it and/or modify -;; it under the terms of the GNU General Public License as published by -;; the Free Software Foundation, either version 3 of the License, or -;; (at your option) any later version. - -;; This program is distributed in the hope that it will be useful, -;; but WITHOUT ANY WARRANTY; without even the implied warranty of -;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -;; GNU General Public License for more details. - -;; You should have received a copy of the GNU General Public License -;; along with this program. If not, see . - -;;; Commentary: - -;; Non-interactive test suite setup. - -;;; Code: - -(message "Running tests on Emacs %s" emacs-version) - -(defmacro with-clojure-buffer (text &rest body) - "Create a temporary buffer, insert TEXT, switch to clojure-mode and evaluate BODY." - (declare (indent 1)) - `(with-temp-buffer - (erase-buffer) - (insert ,text) - (clojure-mode) - ,@body)) - -(defmacro with-clojure-buffer-point (text &rest body) - "Run BODY in a temporary clojure buffer with TEXT. - -TEXT is a string with a | indicating where point is. The | will be erased -and point left there." - (declare (indent 2)) - `(progn - (with-clojure-buffer ,text - (goto-char (point-min)) - (re-search-forward "|") - (delete-char -1) - ,@body))) - -(defmacro when-refactoring-it (description before after &rest body) - "Return a buttercup spec. - -Insert BEFORE into a buffer, evaluate BODY and compare the resulting buffer to -AFTER. - -BODY should contain the refactoring that transforms BEFORE into AFTER. - -DESCRIPTION is the description of the spec." - (declare (indent 1)) - `(it ,description - (with-clojure-buffer ,before - ,@body - (expect (buffer-string) :to-equal ,after)))) - -(defmacro when-refactoring-with-point-it (description before after &rest body) - "Return a buttercup spec. - -Like when-refactor-it but also checks whether point is moved to the expected -position. - -BEFORE is the buffer string before refactoring, where a pipe (|) represents -point. - -AFTER is the expected buffer string after refactoring, where a pipe (|) -represents the expected position of point. - -DESCRIPTION is a string with the description of the spec." - (declare (indent 1)) - `(it ,description - (let* ((after ,after) - (expected-cursor-pos (1+ (s-index-of "|" after))) - (expected-state (delete ?| after))) - (with-clojure-buffer ,before - (goto-char (point-min)) - (search-forward "|") - (delete-char -1) - ,@body - (expect (buffer-string) :to-equal expected-state) - (expect (point) :to-equal expected-cursor-pos))))) - - -;; https://emacs.stackexchange.com/a/55031 -(defmacro with-temp-dir (temp-dir &rest body) - "Create a temporary directory and bind its to TEMP-DIR while evaluating BODY. -Removes the temp directory at the end of evaluation." - `(let ((,temp-dir (make-temp-file "" t))) - (unwind-protect - (progn - ,@body) - (delete-directory ,temp-dir t)))) - -(provide 'test-helper) -;;; test-helper.el ends here From 75349eccbda99c5f5332bb6236936cbeb0de5b56 Mon Sep 17 00:00:00 2001 From: Dieter Komendera Date: Thu, 31 Oct 2024 19:35:29 +0100 Subject: [PATCH 260/379] Fix byte compile by excluding tests merged from clojure-mode --- Eldev | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Eldev b/Eldev index 69f390a..68641f9 100644 --- a/Eldev +++ b/Eldev @@ -26,9 +26,11 @@ (setq eldev-project-main-file "clojure-ts-mode.el") -;; Exclude tests merged from clojure-mode from running and linting +;; Exclude tests merged from clojure-mode from running, linting and byte compiling (setf eldev-test-fileset `(:and ,eldev-test-fileset (:not "./clojure-mode-*"))) +(setf eldev-standard-excludes + `(:or ,eldev-standard-excludes "./clojure-mode-*")) (setf eldev-lint-ignored-fileset `(:or ,eldev-lint-ignored-fileset "./clojure-mode-*")) From 1d7f84db02b4f4b787fb4da9db908001f52642e4 Mon Sep 17 00:00:00 2001 From: Dieter Komendera Date: Thu, 31 Oct 2024 21:35:41 +0100 Subject: [PATCH 261/379] Move clojure-mode tests into their own directory to better keep track of them --- Eldev | 6 +++--- .../clojure-mode-convert-collection-test.el | 0 .../clojure-mode-cycling-test.el | 0 .../clojure-mode-external-interaction-test.el | 0 .../clojure-mode-font-lock-test.el | 0 .../clojure-mode-indentation-test.el | 0 .../clojure-mode-promote-fn-literal-test.el | 0 .../clojure-mode-refactor-add-arity-test.el | 0 .../clojure-mode-refactor-let-test.el | 0 .../clojure-mode-refactor-rename-ns-alias-test.el | 0 .../clojure-mode-refactor-threading-test.el | 0 .../clojure-mode-safe-eval-test.el | 0 .../clojure-mode-sexp-test.el | 0 .../clojure-mode-syntax-test.el | 0 .../clojure-mode-util-test.el | 0 15 files changed, 3 insertions(+), 3 deletions(-) rename clojure-mode-convert-collection-test.el => clojure-mode-tests/clojure-mode-convert-collection-test.el (100%) rename clojure-mode-cycling-test.el => clojure-mode-tests/clojure-mode-cycling-test.el (100%) rename clojure-mode-external-interaction-test.el => clojure-mode-tests/clojure-mode-external-interaction-test.el (100%) rename clojure-mode-font-lock-test.el => clojure-mode-tests/clojure-mode-font-lock-test.el (100%) rename clojure-mode-indentation-test.el => clojure-mode-tests/clojure-mode-indentation-test.el (100%) rename clojure-mode-promote-fn-literal-test.el => clojure-mode-tests/clojure-mode-promote-fn-literal-test.el (100%) rename clojure-mode-refactor-add-arity-test.el => clojure-mode-tests/clojure-mode-refactor-add-arity-test.el (100%) rename clojure-mode-refactor-let-test.el => clojure-mode-tests/clojure-mode-refactor-let-test.el (100%) rename clojure-mode-refactor-rename-ns-alias-test.el => clojure-mode-tests/clojure-mode-refactor-rename-ns-alias-test.el (100%) rename clojure-mode-refactor-threading-test.el => clojure-mode-tests/clojure-mode-refactor-threading-test.el (100%) rename clojure-mode-safe-eval-test.el => clojure-mode-tests/clojure-mode-safe-eval-test.el (100%) rename clojure-mode-sexp-test.el => clojure-mode-tests/clojure-mode-sexp-test.el (100%) rename clojure-mode-syntax-test.el => clojure-mode-tests/clojure-mode-syntax-test.el (100%) rename clojure-mode-util-test.el => clojure-mode-tests/clojure-mode-util-test.el (100%) diff --git a/Eldev b/Eldev index 68641f9..7a30881 100644 --- a/Eldev +++ b/Eldev @@ -29,8 +29,8 @@ ;; Exclude tests merged from clojure-mode from running, linting and byte compiling (setf eldev-test-fileset `(:and ,eldev-test-fileset - (:not "./clojure-mode-*"))) + (:not "./clojure-mode-tests/*"))) (setf eldev-standard-excludes - `(:or ,eldev-standard-excludes "./clojure-mode-*")) + `(:or ,eldev-standard-excludes "./clojure-mode-tests/*")) (setf eldev-lint-ignored-fileset - `(:or ,eldev-lint-ignored-fileset "./clojure-mode-*")) + `(:or ,eldev-lint-ignored-fileset "./clojure-mode-tests/*")) diff --git a/clojure-mode-convert-collection-test.el b/clojure-mode-tests/clojure-mode-convert-collection-test.el similarity index 100% rename from clojure-mode-convert-collection-test.el rename to clojure-mode-tests/clojure-mode-convert-collection-test.el diff --git a/clojure-mode-cycling-test.el b/clojure-mode-tests/clojure-mode-cycling-test.el similarity index 100% rename from clojure-mode-cycling-test.el rename to clojure-mode-tests/clojure-mode-cycling-test.el diff --git a/clojure-mode-external-interaction-test.el b/clojure-mode-tests/clojure-mode-external-interaction-test.el similarity index 100% rename from clojure-mode-external-interaction-test.el rename to clojure-mode-tests/clojure-mode-external-interaction-test.el diff --git a/clojure-mode-font-lock-test.el b/clojure-mode-tests/clojure-mode-font-lock-test.el similarity index 100% rename from clojure-mode-font-lock-test.el rename to clojure-mode-tests/clojure-mode-font-lock-test.el diff --git a/clojure-mode-indentation-test.el b/clojure-mode-tests/clojure-mode-indentation-test.el similarity index 100% rename from clojure-mode-indentation-test.el rename to clojure-mode-tests/clojure-mode-indentation-test.el diff --git a/clojure-mode-promote-fn-literal-test.el b/clojure-mode-tests/clojure-mode-promote-fn-literal-test.el similarity index 100% rename from clojure-mode-promote-fn-literal-test.el rename to clojure-mode-tests/clojure-mode-promote-fn-literal-test.el diff --git a/clojure-mode-refactor-add-arity-test.el b/clojure-mode-tests/clojure-mode-refactor-add-arity-test.el similarity index 100% rename from clojure-mode-refactor-add-arity-test.el rename to clojure-mode-tests/clojure-mode-refactor-add-arity-test.el diff --git a/clojure-mode-refactor-let-test.el b/clojure-mode-tests/clojure-mode-refactor-let-test.el similarity index 100% rename from clojure-mode-refactor-let-test.el rename to clojure-mode-tests/clojure-mode-refactor-let-test.el diff --git a/clojure-mode-refactor-rename-ns-alias-test.el b/clojure-mode-tests/clojure-mode-refactor-rename-ns-alias-test.el similarity index 100% rename from clojure-mode-refactor-rename-ns-alias-test.el rename to clojure-mode-tests/clojure-mode-refactor-rename-ns-alias-test.el diff --git a/clojure-mode-refactor-threading-test.el b/clojure-mode-tests/clojure-mode-refactor-threading-test.el similarity index 100% rename from clojure-mode-refactor-threading-test.el rename to clojure-mode-tests/clojure-mode-refactor-threading-test.el diff --git a/clojure-mode-safe-eval-test.el b/clojure-mode-tests/clojure-mode-safe-eval-test.el similarity index 100% rename from clojure-mode-safe-eval-test.el rename to clojure-mode-tests/clojure-mode-safe-eval-test.el diff --git a/clojure-mode-sexp-test.el b/clojure-mode-tests/clojure-mode-sexp-test.el similarity index 100% rename from clojure-mode-sexp-test.el rename to clojure-mode-tests/clojure-mode-sexp-test.el diff --git a/clojure-mode-syntax-test.el b/clojure-mode-tests/clojure-mode-syntax-test.el similarity index 100% rename from clojure-mode-syntax-test.el rename to clojure-mode-tests/clojure-mode-syntax-test.el diff --git a/clojure-mode-util-test.el b/clojure-mode-tests/clojure-mode-util-test.el similarity index 100% rename from clojure-mode-util-test.el rename to clojure-mode-tests/clojure-mode-util-test.el From 3ca382c3ccf2ad560d0974229ec88963e82d2fe7 Mon Sep 17 00:00:00 2001 From: Ryan Schmukler Date: Thu, 25 Jul 2024 17:35:06 -0600 Subject: [PATCH 262/379] Fix semantic indentation of quoted functions Fixes an error where quoted functions would not align correctly with semantic indentation. Adds an example test and updates the changelog --- CHANGELOG.md | 1 + clojure-ts-mode.el | 15 ++++++++++----- test/clojure-ts-mode-indentation-test.el | 7 ++++++- test/samples/indentation.clj | 2 ++ 4 files changed, 19 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 666bc9a..c167910 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ - [#42]: Fix imenu support for definitions with metadata. - [#42]: Fix font locking of definitions with metadata - [#42]: Fix indentation of definitions with metadata +- Fix semantic indentation of quoted functions ## 0.2.2 (2024-02-16) diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index 09247cc..2c56c19 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -528,6 +528,10 @@ with the markdown_inline grammar." "Return non-nil if NODE is a Clojure metadata node." (string-equal "meta_lit" (treesit-node-type node))) +(defun clojure-ts--var-node-p (node) + "Return non-nil if NODE is a var (eg. #\\'foo)." + (string-equal "var_quoting_lit" (treesit-node-type node))) + (defun clojure-ts--named-node-text (node) "Gets the name of a symbol or keyword NODE. This does not include the NODE's namespace." @@ -616,13 +620,13 @@ Includes a dispatch value when applicable (defmethods)." "Return non-nil if NODE is a ns form." (clojure-ts--definition-node-p "ns" node)) -(defvar clojure-ts--variable-type-regexp +(defvar clojure-ts--variable-definition-type-regexp (rx string-start (or "def" "defonce") string-end) "Regular expression for matching definition nodes that resemble variables.") -(defun clojure-ts--variable-node-p (node) +(defun clojure-ts--variable-definition-node-p (node) "Return non-nil if NODE is a def or defonce form." - (clojure-ts--definition-node-match-p clojure-ts--variable-type-regexp node)) + (clojure-ts--definition-node-match-p clojure-ts--variable-definition-type-regexp node)) (defvar clojure-ts--class-type-regexp (rx string-start (or "deftype" "defrecord" "defstruct") string-end) @@ -647,7 +651,7 @@ Includes a dispatch value when applicable (defmethods)." ;; Used instead of treesit-defun-name-function. clojure-ts--function-node-name) ("Macro" "list_lit" clojure-ts--defmacro-node-p) - ("Variable" "list_lit" clojure-ts--variable-node-p) + ("Variable" "list_lit" clojure-ts--variable-definition-node-p) ("Interface" "list_lit" clojure-ts--interface-node-p) ("Class" "list_lit" clojure-ts--class-node-p)) "The value for `treesit-simple-imenu-settings'. @@ -735,7 +739,8 @@ https://github.com/weavejester/cljfmt/blob/fb26b22f569724b05c93eb2502592dfc2de89 (not (treesit-node-eq (treesit-node-child parent 1 t) node)) (let ((first-child (treesit-node-child parent 0 t))) (or (clojure-ts--symbol-node-p first-child) - (clojure-ts--keyword-node-p first-child))))) + (clojure-ts--keyword-node-p first-child) + (clojure-ts--var-node-p first-child))))) (defun clojure-ts--match-expression-in-body (node parent _bol) "Match NODE if it is an expression used in a body argument. diff --git a/test/clojure-ts-mode-indentation-test.el b/test/clojure-ts-mode-indentation-test.el index e4d73a6..8fbc50c 100644 --- a/test/clojure-ts-mode-indentation-test.el +++ b/test/clojure-ts-mode-indentation-test.el @@ -135,4 +135,9 @@ DESCRIPTION is a string with the description of the spec." (defn c \"hello\" [_foo] - (+ 1 1))")) + (+ 1 1))") + +(when-indenting-it "should support function calls via vars" + " +(#'foo 5 + 6)")) diff --git a/test/samples/indentation.clj b/test/samples/indentation.clj index 2996229..f87870d 100644 --- a/test/samples/indentation.clj +++ b/test/samples/indentation.clj @@ -60,6 +60,8 @@ (clojure.core/filter even? (range 1 10)) +(#'filter even? + (range 10)) (filter even? From ee8baed6437452c76e9ce778038e2a28219c76e5 Mon Sep 17 00:00:00 2001 From: Roman Rudakov Date: Sat, 15 Feb 2025 19:03:51 +0100 Subject: [PATCH 263/379] Add custom fill-paragraph function to respect docstrings --- CHANGELOG.md | 2 + README.md | 11 ++++ clojure-ts-mode.el | 37 ++++++++++++ test/clojure-ts-mode-fill-paragraph-test.el | 62 +++++++++++++++++++++ 4 files changed, 112 insertions(+) create mode 100644 test/clojure-ts-mode-fill-paragraph-test.el diff --git a/CHANGELOG.md b/CHANGELOG.md index c167910..3f0f61a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ - [#42]: Fix font locking of definitions with metadata - [#42]: Fix indentation of definitions with metadata - Fix semantic indentation of quoted functions +- Add custom `fill-paragraph-function` which respects docstrings similar to + `clojure-mode`. ## 0.2.2 (2024-02-16) diff --git a/README.md b/README.md index 62d8414..a3d762c 100644 --- a/README.md +++ b/README.md @@ -52,6 +52,17 @@ To make forms inside of `(comment ...)` forms appear as top-level forms for eval (setq clojure-ts-toplevel-inside-comment-form t) ``` +### Fill paragraph + +To change the maximal line length used by `M-x prog-fill-reindent-defun` (also +bound to `M-q` by default) to reformat docstrings and comments it's possible to +customize `clojure-ts-fill-paragraph` variable (by default set to the value of +Emacs' `fill-paragraph` value). + +Every new line in the docstrings is indented by +`clojure-ts-docstring-fill-prefix-width` number of spaces (set to 2 by default +which matches the `clojure-mode` settings). + ## Rationale [clojure-mode](https://github.com/clojure-emacs/clojure-mode) has served us well diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index 2c56c19..1d6fa21 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -97,6 +97,21 @@ itself." :safe #'booleanp :package-version '(clojure-ts-mode . "0.2.1")) +(defcustom clojure-ts-docstring-fill-column fill-column + "Value of `fill-column' to use when filling a docstring." + :type 'integer + :safe #'integerp + :package-version '(clojure-ts-mode . "0.2.3")) + +(defcustom clojure-ts-docstring-fill-prefix-width 2 + "Width of `fill-prefix' when filling a docstring. +The default value conforms with the de facto convention for +Clojure docstrings, aligning the second line with the opening +double quotes on the third column." + :type 'integer + :safe #'integerp + :package-version '(clojure-ts-mode . "0.2.3")) + (defvar clojure-ts--debug nil "Enables debugging messages, shows current node in mode-line. Only intended for use at development time.") @@ -863,6 +878,27 @@ forms like deftype, defrecord, reify, proxy, etc." '(semantic fixed) clojure-ts-indent-style))))) +(defun clojure-ts--docstring-fill-prefix () + "The prefix string used by `clojure-ts--fill-paragraph'. +It is simply `clojure-ts-docstring-fill-prefix-width' number of spaces." + (make-string clojure-ts-docstring-fill-prefix-width ? )) + +(defun clojure-ts--fill-paragraph (&optional justify) + "Like `fill-paragraph', but can handler Clojure docstrings. +If JUSTIFY is non-nil, justify as well as fill the paragraph." + (let ((current-node (treesit-node-at (point)))) + (if (clojure-ts--match-docstring nil current-node nil) + (let ((fill-column (or clojure-ts-docstring-fill-column fill-column)) + (fill-prefix (clojure-ts--docstring-fill-prefix)) + (beg-doc (treesit-node-start current-node)) + (end-doc (treesit-node-end current-node))) + (save-restriction + (narrow-to-region beg-doc end-doc) + (fill-paragraph justify))) + (or (fill-comment-paragraph justify) + (fill-paragraph justify))) + t)) + (defconst clojure-ts--sexp-nodes '("#_" ;; transpose-sexp near a discard macro moves it around. "num_lit" "sym_lit" "kwd_lit" "nil_lit" "bool_lit" @@ -963,6 +999,7 @@ See `clojure-ts--font-lock-settings' for usage of MARKDOWN-AVAILABLE." (keyword string char symbol builtin type) (constant number quote metadata doc) (bracket deref function regex tagged-literals))) + (setq-local fill-paragraph-function #'clojure-ts--fill-paragraph) (when (boundp 'treesit-thing-settings) ;; Emacs 30+ (setq-local treesit-thing-settings clojure-ts--thing-settings))) diff --git a/test/clojure-ts-mode-fill-paragraph-test.el b/test/clojure-ts-mode-fill-paragraph-test.el new file mode 100644 index 0000000..a7e2dde --- /dev/null +++ b/test/clojure-ts-mode-fill-paragraph-test.el @@ -0,0 +1,62 @@ +;;; clojure-ts-mode-fill-paragraph-test.el --- -*- lexical-binding: t; -*- + +;; Copyright (C) 2025 Roman Rudakov + +;; This file is not part of GNU Emacs. + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;;; Commentary: + +;; The unit test suite of CLojure TS Mode + +;;; Code: + +(require 'clojure-ts-mode) +(require 'buttercup) + +(describe "clojure-ts--fill-paragraph" + (it "should reformat only the docstring" + (with-clojure-ts-buffer "(ns foo) + +(defn hello-world + \"This is a very long docstring that should be reformatted using fill-paragraph function.\" + [] + (pringln \"Hello world\"))" + (goto-char 40) + (prog-fill-reindent-defun) + (expect (buffer-substring-no-properties (point-min) (point-max)) + :to-equal + "(ns foo) + +(defn hello-world + \"This is a very long docstring that should be reformatted using + fill-paragraph function.\" + [] + (pringln \"Hello world\"))"))) + + (it "should reformat normal comments properly" + (with-clojure-ts-buffer "(ns foo) + +;; This is a very long comment that should be reformatted using fill-paragraph function." + (goto-char 20) + (prog-fill-reindent-defun) + (expect (buffer-substring-no-properties (point-min) (point-max)) + :to-equal + "(ns foo) + +;; This is a very long comment that should be reformatted using +;; fill-paragraph function.")))) + +;;; clojure-ts-mode-fill-paragraph-test.el ends here From 5794188e44459cf4c6ca6f2c4d0d1521d810b9a8 Mon Sep 17 00:00:00 2001 From: Roman Rudakov Date: Sat, 15 Feb 2025 20:47:26 +0100 Subject: [PATCH 264/379] Add customization option to disable markdown syntax highlighting --- CHANGELOG.md | 2 ++ README.md | 9 +++++++++ clojure-ts-mode.el | 13 ++++++++++--- 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f0f61a..01304a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,8 @@ - Fix semantic indentation of quoted functions - Add custom `fill-paragraph-function` which respects docstrings similar to `clojure-mode`. +- Add customization option to disable markdown syntax highlighting in the + docstrings. ## 0.2.2 (2024-02-16) diff --git a/README.md b/README.md index a3d762c..ca2f670 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,15 @@ To highlight entire rich `comment` expression with the comment font face, set By default this is `nil`, so that anything within a `comment` expression is highlighted like regular clojure code. +### Highlight markdown syntax in docstrings + +By default markdown syntax is highlighted in the docstrings using +`markdown_inline` grammar. To disable this feature set + +``` emacs-lisp +(setopt clojure-ts-use-markdown-inline nil) +``` + ### Navigation and Evaluation To make forms inside of `(comment ...)` forms appear as top-level forms for evaluation and navigation, set diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index 1d6fa21..10bb3f5 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -112,6 +112,12 @@ double quotes on the third column." :safe #'integerp :package-version '(clojure-ts-mode . "0.2.3")) +(defcustom clojure-ts-use-markdown-inline t + "When non-nil, use Markdown inline grammar for docstrings." + :type 'boolean + :safe #'booleanp + :package-version '(clojure-ts-mode . "0.2.3")) + (defvar clojure-ts--debug nil "Enables debugging messages, shows current node in mode-line. Only intended for use at development time.") @@ -1010,13 +1016,14 @@ See `clojure-ts--font-lock-settings' for usage of MARKDOWN-AVAILABLE." \\{clojure-ts-mode-map}" :syntax-table clojure-ts-mode-syntax-table (clojure-ts--ensure-grammars) - (let ((markdown-available (treesit-ready-p 'markdown_inline t))) - (when markdown-available + (let ((use-markdown-inline (and clojure-ts-use-markdown-inline + (treesit-ready-p 'markdown_inline t)))) + (when use-markdown-inline (treesit-parser-create 'markdown_inline) (setq-local treesit-range-settings clojure-ts--treesit-range-settings)) (when (treesit-ready-p 'clojure) (treesit-parser-create 'clojure) - (clojure-ts-mode-variables markdown-available) + (clojure-ts-mode-variables use-markdown-inline) (when clojure-ts--debug (setq-local treesit--indent-verbose t) (when (eq clojure-ts--debug 'font-lock) From a8ba67477ff888b4709d5d6ca5f37e4e3ade0a8d Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Sat, 1 Mar 2025 16:53:15 +0200 Subject: [PATCH 265/379] Reflow a couple of long lines in the README --- README.md | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index ca2f670..7cb02ba 100644 --- a/README.md +++ b/README.md @@ -87,7 +87,9 @@ Keep in mind that the transition to `clojure-ts-mode` won't happen overnight for - tools that depend on `clojure-mode` will need to be updated to work with `clojure-ts-mode` - we still need to support users of older Emacs versions that don't support Tree-sitter -That's why `clojure-ts-mode` is being developed independently of `clojure-mode` and will one day replace it when the time is right. (e.g. 3 major Emacs version down the road, so circa Emacs 32) +That's why `clojure-ts-mode` is being developed independently of `clojure-mode` +and will one day replace it when the time is right. (e.g. 3 major Emacs version +down the road, so circa Emacs 32) You can read more about the vision for `clojure-ts-mode` [here](https://metaredux.com/posts/2023/03/12/clojure-mode-meets-tree-sitter.html). @@ -102,6 +104,7 @@ You can read more about the vision for `clojure-ts-mode` [here](https://metaredu This package requires Emacs 29 built with tree-sitter support from the [emacs-29 branch](https://git.savannah.gnu.org/cgit/emacs.git/log/?h=emacs-29). If you decide to build Emacs from source there's some useful information on this in the Emacs repository: + - [Emacs tree-sitter starter-guide](https://git.savannah.gnu.org/cgit/emacs.git/tree/admin/notes/tree-sitter/starter-guide?h=emacs-29) - [Emacs install instructions](https://git.savannah.gnu.org/cgit/emacs.git/tree/INSTALL.REPO). @@ -148,11 +151,14 @@ Once installed, evaluate clojure-ts-mode.el and you should be ready to go. ### Install tree-sitter grammars The compile tree-sitter clojure shared library must be available to Emacs. -Additionally, the tree-sitter [markdown_inline](https://github.com/MDeiml/tree-sitter-markdown) shared library will also be used for docstrings if available. +Additionally, the tree-sitter +[markdown_inline](https://github.com/MDeiml/tree-sitter-markdown) shared library +will also be used for docstrings if available. -If you have `git` and a C compiler (`cc`) available on your system's `PATH`, **then these steps should not be necessary**. -clojure-ts-mode will install the grammars when you first open a Clojure file and -`clojure-ts-ensure-grammars` is set to `t` (the default). +If you have `git` and a C compiler (`cc`) available on your system's `PATH`, +**then these steps should not be necessary**. clojure-ts-mode will install the +grammars when you first open a Clojure file and `clojure-ts-ensure-grammars` is +set to `t` (the default). If clojure-ts-mode fails to automatically install the grammar, you have the option to install it manually. @@ -234,9 +240,14 @@ After installing the package do the following. ### Does `clojure-ts-mode` work with CIDER? -Yes! Preliminary support for `clojure-ts-mode` was released in [CIDER 1.14](https://github.com/clojure-emacs/cider/releases/tag/v1.14.0). Make sure to grab the latest CIDER from MELPA/GitHub. Note that `clojure-mode` is still needed for some APIs that haven't yet been ported to `clojure-ts-mode`. +Yes! Preliminary support for `clojure-ts-mode` was released in [CIDER +1.14](https://github.com/clojure-emacs/cider/releases/tag/v1.14.0). Make sure to +grab the latest CIDER from MELPA/GitHub. Note that `clojure-mode` is still +needed for some APIs that haven't yet been ported to `clojure-ts-mode`. -For now, when you take care of the keybindings for the CIDER commands you use and ensure `cider-mode` is enabled for `clojure-ts-mode` buffers in your config, most functionality should already work: +For now, when you take care of the keybindings for the CIDER commands you use +and ensure `cider-mode` is enabled for `clojure-ts-mode` buffers in your config, +most functionality should already work: ```emacs-lisp (add-hook 'clojure-ts-mode-hook #'cider-mode) From ad7c89bc57c9e4370f934621c193651fff70ae15 Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Sat, 1 Mar 2025 16:55:25 +0200 Subject: [PATCH 266/379] Bump the copyright years --- README.md | 2 +- clojure-ts-mode.el | 2 +- test/clojure-ts-mode-font-lock-test.el | 2 +- test/clojure-ts-mode-imenu-test.el | 2 +- test/clojure-ts-mode-indentation-test.el | 2 +- test/clojure-ts-mode-util-test.el | 2 +- test/test-helper.el | 2 +- 7 files changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 7cb02ba..d420b2f 100644 --- a/README.md +++ b/README.md @@ -261,7 +261,7 @@ Currently, there is an [open PR](https://github.com/clojure-emacs/inf-clojure/pu ## License -Copyright © 2022-2024 Danny Freeman and [contributors][]. +Copyright © 2022-2025 Danny Freeman and [contributors][]. Distributed under the GNU General Public License; type C-h C-c to view it. diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index 10bb3f5..374cace 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -1,6 +1,6 @@ ;;; clojure-ts-mode.el --- Major mode for Clojure code -*- lexical-binding: t; -*- -;; Copyright © 2022-2024 Danny Freeman +;; Copyright © 2022-2025 Danny Freeman ;; ;; Authors: Danny Freeman ;; Maintainer: Danny Freeman diff --git a/test/clojure-ts-mode-font-lock-test.el b/test/clojure-ts-mode-font-lock-test.el index f966652..60b4e3e 100644 --- a/test/clojure-ts-mode-font-lock-test.el +++ b/test/clojure-ts-mode-font-lock-test.el @@ -1,6 +1,6 @@ ;;; clojure-ts-mode-font-lock-test.el --- Clojure TS Mode: font lock test suite -*- lexical-binding: t; -*- -;; Copyright © 2022-2024 Danny Freeman +;; Copyright © 2022-2025 Danny Freeman ;; This file is not part of GNU Emacs. diff --git a/test/clojure-ts-mode-imenu-test.el b/test/clojure-ts-mode-imenu-test.el index ede2747..4567596 100644 --- a/test/clojure-ts-mode-imenu-test.el +++ b/test/clojure-ts-mode-imenu-test.el @@ -1,6 +1,6 @@ ;;; clojure-ts-mode-imenu-test.el --- Clojure TS Mode: imenu test suite -*- lexical-binding: t; -*- -;; Copyright © 2022-2024 Danny Freeman +;; Copyright © 2022-2025 Danny Freeman ;; This file is not part of GNU Emacs. diff --git a/test/clojure-ts-mode-indentation-test.el b/test/clojure-ts-mode-indentation-test.el index 8fbc50c..d4d3517 100644 --- a/test/clojure-ts-mode-indentation-test.el +++ b/test/clojure-ts-mode-indentation-test.el @@ -1,6 +1,6 @@ ;;; clojure-ts-mode-indentation-test.el --- Clojure TS Mode: indentation test suite -*- lexical-binding: t; -*- -;; Copyright © 2022-2024 Danny Freeman +;; Copyright © 2022-2025 Danny Freeman ;; This file is not part of GNU Emacs. diff --git a/test/clojure-ts-mode-util-test.el b/test/clojure-ts-mode-util-test.el index 5581a2c..8156c1a 100644 --- a/test/clojure-ts-mode-util-test.el +++ b/test/clojure-ts-mode-util-test.el @@ -1,6 +1,6 @@ ;;; clojure-ts-mode-util-test.el --- Clojure TS Mode: util test suite -*- lexical-binding: t; -*- -;; Copyright © 2022-2024 Danny Freeman +;; Copyright © 2022-2025 Danny Freeman ;; This file is not part of GNU Emacs. diff --git a/test/test-helper.el b/test/test-helper.el index cb0a2ad..c1cace3 100644 --- a/test/test-helper.el +++ b/test/test-helper.el @@ -1,6 +1,6 @@ ;;; test-helper.el --- Clojure TS Mode: Non-interactive unit-test setup -*- lexical-binding: t; -*- -;; Copyright © 2022-2024 Bozhidar Batsov +;; Copyright © 2022-2025 Bozhidar Batsov ;; This file is not part of GNU Emacs. From fa889a996ac407e2b75ac42ccbc5aed6683dca6c Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Sat, 1 Mar 2025 16:59:38 +0200 Subject: [PATCH 267/379] Tweak some changelog entries --- CHANGELOG.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 01304a3..cdfc08b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,9 +7,9 @@ - Add imenu support for `deftest` definitions. - [#53]: Let `clojure-ts-mode` derive from `clojure-mode` for Emacs 30+. - [#42]: Fix imenu support for definitions with metadata. -- [#42]: Fix font locking of definitions with metadata -- [#42]: Fix indentation of definitions with metadata -- Fix semantic indentation of quoted functions +- [#42]: Fix font locking of definitions with metadata. +- [#42]: Fix indentation of definitions with metadata. +- Fix semantic indentation of quoted functions. - Add custom `fill-paragraph-function` which respects docstrings similar to `clojure-mode`. - Add customization option to disable markdown syntax highlighting in the From b01c20ababc68ea10530d0a55d16b54c94c90218 Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Mon, 3 Mar 2025 22:55:51 +0200 Subject: [PATCH 268/379] Add missing blank line --- clojure-ts-mode.el | 1 + 1 file changed, 1 insertion(+) diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index 374cace..ea3dbdb 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -53,6 +53,7 @@ ;; Boston, MA 02110-1301, USA. ;;; Code: + (require 'treesit) (declare-function treesit-parser-create "treesit.c") From bec7871c123e379e561c822852847b25cddcd40b Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Tue, 4 Mar 2025 12:26:12 +0200 Subject: [PATCH 269/379] Make the mode setup a bit more structured --- clojure-ts-mode.el | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index ea3dbdb..43b7bf4 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -983,8 +983,15 @@ If JUSTIFY is non-nil, justify as well as fill the paragraph." See `clojure-ts--font-lock-settings' for usage of MARKDOWN-AVAILABLE." (setq-local comment-add 1) (setq-local comment-start ";") + (setq-local treesit-font-lock-settings (clojure-ts--font-lock-settings markdown-available)) + (setq-local treesit-font-lock-feature-list + '((comment definition variable) + (keyword string char symbol builtin type) + (constant number quote metadata doc) + (bracket deref function regex tagged-literals))) + (setq-local treesit-defun-prefer-top-level t) (setq-local treesit-defun-tactic 'top-level) (setq-local treesit-defun-type-regexp @@ -995,18 +1002,16 @@ See `clojure-ts--font-lock-settings' for usage of MARKDOWN-AVAILABLE." (lambda (node) (or (not clojure-ts-toplevel-inside-comment-form) (not (clojure-ts--definition-node-p "comment" node)))))) - (setq-local treesit-simple-indent-rules - (clojure-ts--configured-indent-rules)) (setq-local treesit-defun-name-function #'clojure-ts--standard-definition-node-name) + + (setq-local treesit-simple-indent-rules + (clojure-ts--configured-indent-rules)) + (setq-local fill-paragraph-function #'clojure-ts--fill-paragraph) + (setq-local treesit-simple-imenu-settings clojure-ts--imenu-settings) - (setq-local treesit-font-lock-feature-list - '((comment definition variable) - (keyword string char symbol builtin type) - (constant number quote metadata doc) - (bracket deref function regex tagged-literals))) - (setq-local fill-paragraph-function #'clojure-ts--fill-paragraph) + (when (boundp 'treesit-thing-settings) ;; Emacs 30+ (setq-local treesit-thing-settings clojure-ts--thing-settings))) @@ -1022,15 +1027,19 @@ See `clojure-ts--font-lock-settings' for usage of MARKDOWN-AVAILABLE." (when use-markdown-inline (treesit-parser-create 'markdown_inline) (setq-local treesit-range-settings clojure-ts--treesit-range-settings)) + (when (treesit-ready-p 'clojure) (treesit-parser-create 'clojure) (clojure-ts-mode-variables use-markdown-inline) + (when clojure-ts--debug (setq-local treesit--indent-verbose t) (when (eq clojure-ts--debug 'font-lock) (setq-local treesit--font-lock-verbose t)) (treesit-inspect-mode)) + (treesit-major-mode-setup) + ;; Workaround for treesit-transpose-sexps not correctly working with ;; treesit-thing-settings on Emacs 30. ;; Once treesit-transpose-sexps it working again this can be removed From ff203105bf7e76a8f47111705c28f3505bd5ce60 Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Tue, 4 Mar 2025 15:38:25 +0200 Subject: [PATCH 270/379] Tweak several changelog entries --- CHANGELOG.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cdfc08b..7ad5f70 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,10 +9,10 @@ - [#42]: Fix imenu support for definitions with metadata. - [#42]: Fix font locking of definitions with metadata. - [#42]: Fix indentation of definitions with metadata. -- Fix semantic indentation of quoted functions. -- Add custom `fill-paragraph-function` which respects docstrings similar to +- [#49]: Fix semantic indentation of quoted functions. +- [#58]: Add custom `fill-paragraph-function` which respects docstrings similar to `clojure-mode`. -- Add customization option to disable markdown syntax highlighting in the +- [#59]: Add customization option to disable markdown syntax highlighting in the docstrings. ## 0.2.2 (2024-02-16) From 9662b6caa86b67b51aa883b35e21645233ca6f68 Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Tue, 4 Mar 2025 15:42:54 +0200 Subject: [PATCH 271/379] Release 0.2.3 --- CHANGELOG.md | 2 ++ clojure-ts-mode.el | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ad5f70..b498929 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## main (unreleased) +## 0.2.3 (2025-03-04) + - [#38]: Add support for `in-ns` forms in `clojure-ts-find-ns`. - [#46]: Fix missing `comment-add` variable in `clojure-ts-mode-variables` mentioned in [#26] - Add imenu support for `deftest` definitions. diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index 43b7bf4..d07d75b 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -6,7 +6,7 @@ ;; Maintainer: Danny Freeman ;; URL: http://github.com/clojure-emacs/clojure-ts-mode ;; Keywords: languages clojure clojurescript lisp -;; Version: 0.2.2 +;; Version: 0.2.3 ;; Package-Requires: ((emacs "29.1")) ;; This file is not part of GNU Emacs. @@ -71,7 +71,7 @@ :link '(emacs-commentary-link :tag "Commentary" "clojure-mode")) (defconst clojure-ts-mode-version - "0.2.2" + "0.2.3" "The current version of `clojure-ts-mode'.") (defcustom clojure-ts-comment-macro-font-lock-body nil From 187e51253d6b5af375805d034fae7fbb1994a0b7 Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Fri, 7 Mar 2025 10:06:55 +0200 Subject: [PATCH 272/379] [Fix #29] Remove the manual grammar installation instructions Very few people will have to do this, so it doesn't really make sense to go into so much details. --- README.md | 68 +++++++++---------------------------------------------- 1 file changed, 11 insertions(+), 57 deletions(-) diff --git a/README.md b/README.md index d420b2f..30ed5dd 100644 --- a/README.md +++ b/README.md @@ -95,7 +95,7 @@ You can read more about the vision for `clojure-ts-mode` [here](https://metaredu ## Current Status -**This library is still under development. Breaking changes should be expected.** +**This library is still under active development. Breaking changes should be expected.** ## Installation @@ -150,67 +150,21 @@ Once installed, evaluate clojure-ts-mode.el and you should be ready to go. ### Install tree-sitter grammars -The compile tree-sitter clojure shared library must be available to Emacs. -Additionally, the tree-sitter -[markdown_inline](https://github.com/MDeiml/tree-sitter-markdown) shared library -will also be used for docstrings if available. +`clojure-ts-mode` makes use of two TreeSitter grammars to work properly: + +- The Clojure grammar, mentioned earlier +- [markdown_inline](https://github.com/MDeiml/tree-sitter-markdown), which +will be used for docstrings if available and if `clojure-ts-use-markdown-inline` is enabled. If you have `git` and a C compiler (`cc`) available on your system's `PATH`, -**then these steps should not be necessary**. clojure-ts-mode will install the +`clojure-ts-mode` will install the grammars when you first open a Clojure file and `clojure-ts-ensure-grammars` is set to `t` (the default). -If clojure-ts-mode fails to automatically install the grammar, you have the option to install it manually. - -#### From your OS - -Some distributions may package the tree-sitter-clojure grammar in their package repositories. -If yours does you may be able to install tree-sitter-clojure with your system package manager. - -If the version packaged by your OS is out of date, you may see errors in the `*Messages*` buffer or your clojure buffers will not have any syntax highlighting. - -If this happens you should install the grammar manually with `M-x treesit-install-language-grammar clojure` and follow the prompts. -Recommended values for these prompts can be seen in `clojure-ts-grammar-recipes`. - -#### Compile From Source - -If all else fails, you can attempt to download and compile manually. -All you need is `git` and a C compiler (GCC works well). - -To start, clone [tree-sitter-clojure](https://github.com/sogaiu/tree-sitter-clojure). - -Then run the following code (depending on your OS) from the tree-sitter-clojure repository on your machine. - -#### Linux - -```bash -mkdir -p dist -cc -c -I./src src/parser.c -o "parser.o" -cc -fPIC -shared src/parser.o -o "dist/libtree-sitter-clojure.so" -``` - -#### macOS - -```bash -mkdir -p dist -cc -c -I./src src/parser.c -o "parser.o" -cc -fPIC -shared src/parser.o -o "dist/libtree-sitter-clojure.dylib" -``` - -#### Windows - -I don't know how to do this on Windows. Patches welcome! - -#### Finally, in emacs - -Then tell Emacs where to find the shared library by adding something like this to your init file: - -```emacs-lisp -(setq treesit-extra-load-path '( "~/path/to/tree-sitter-clojure/dist")) -``` - -OR you can move the `libtree-sitter-clojure.so`/`libtree-sitter-clojure.dylib` to a directory named `tree-sitter` -under your `user-emacs-directory` (typically `~/.emacs.d` on Unix systems). +If `clojure-ts-mode` fails to automatically install the grammar, you have the +option to install it manually, Please, refer to the installation instructions of +each required grammar and make sure you're install the versions expected. (see +`clojure-ts-grammar-recipes` for details) ## Migrating to clojure-ts-mode From 72b0a606f57907f385fcda703b7c51358c44c5ba Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Fri, 7 Mar 2025 10:09:49 +0200 Subject: [PATCH 273/379] Move the configuration instructions after the installation ones --- README.md | 120 +++++++++++++++++++++++++++--------------------------- 1 file changed, 60 insertions(+), 60 deletions(-) diff --git a/README.md b/README.md index 30ed5dd..1302da3 100644 --- a/README.md +++ b/README.md @@ -12,66 +12,6 @@ highlighting), indentation, and navigation support for the [tree-sitter-clojure](https://github.com/sogaiu/tree-sitter-clojure) [tree-sitter](https://tree-sitter.github.io/tree-sitter/) grammar. -## Configuration - -To see a list of available configuration options do `M-x customize-group clojure-ts`. - -Most configuration changes will require reverting any active `clojure-ts-mode` buffers. - -### Indentation - -`clojure-ts-mode` currently supports 2 different indentation strategies: - -- `semantic`, the default, which tries to match the indentation of `clojure-mode` and `cljfmt` -- `fixed`, [a simple indentation strategy outlined by Tonsky in a blog post](https://tonsky.me/blog/clojurefmt/) - -Set the var `clojure-ts-indent-style` to change it. - -``` emacs-lisp -(setq clojure-ts-indent-style 'fixed) -``` - -**Note:** You can find [this article](https://metaredux.com/posts/2020/12/06/semantic-clojure-formatting.html) comparing semantic and fixed indentation useful. - -### Font Locking - -To highlight entire rich `comment` expression with the comment font face, set - -``` emacs-lisp -(setq clojure-ts-comment-macro-font-lock-body t) -``` - -By default this is `nil`, so that anything within a `comment` expression is -highlighted like regular clojure code. - -### Highlight markdown syntax in docstrings - -By default markdown syntax is highlighted in the docstrings using -`markdown_inline` grammar. To disable this feature set - -``` emacs-lisp -(setopt clojure-ts-use-markdown-inline nil) -``` - -### Navigation and Evaluation - -To make forms inside of `(comment ...)` forms appear as top-level forms for evaluation and navigation, set - -``` emacs-lisp -(setq clojure-ts-toplevel-inside-comment-form t) -``` - -### Fill paragraph - -To change the maximal line length used by `M-x prog-fill-reindent-defun` (also -bound to `M-q` by default) to reformat docstrings and comments it's possible to -customize `clojure-ts-fill-paragraph` variable (by default set to the value of -Emacs' `fill-paragraph` value). - -Every new line in the docstrings is indented by -`clojure-ts-docstring-fill-prefix-width` number of spaces (set to 2 by default -which matches the `clojure-mode` settings). - ## Rationale [clojure-mode](https://github.com/clojure-emacs/clojure-mode) has served us well @@ -166,6 +106,66 @@ option to install it manually, Please, refer to the installation instructions of each required grammar and make sure you're install the versions expected. (see `clojure-ts-grammar-recipes` for details) +## Configuration + +To see a list of available configuration options do `M-x customize-group clojure-ts`. + +Most configuration changes will require reverting any active `clojure-ts-mode` buffers. + +### Indentation + +`clojure-ts-mode` currently supports 2 different indentation strategies: + +- `semantic`, the default, which tries to match the indentation of `clojure-mode` and `cljfmt` +- `fixed`, [a simple indentation strategy outlined by Tonsky in a blog post](https://tonsky.me/blog/clojurefmt/) + +Set the var `clojure-ts-indent-style` to change it. + +``` emacs-lisp +(setq clojure-ts-indent-style 'fixed) +``` + +**Note:** You can find [this article](https://metaredux.com/posts/2020/12/06/semantic-clojure-formatting.html) comparing semantic and fixed indentation useful. + +### Font Locking + +To highlight entire rich `comment` expression with the comment font face, set + +``` emacs-lisp +(setq clojure-ts-comment-macro-font-lock-body t) +``` + +By default this is `nil`, so that anything within a `comment` expression is +highlighted like regular clojure code. + +### Highlight markdown syntax in docstrings + +By default markdown syntax is highlighted in the docstrings using +`markdown_inline` grammar. To disable this feature set + +``` emacs-lisp +(setopt clojure-ts-use-markdown-inline nil) +``` + +### Navigation and Evaluation + +To make forms inside of `(comment ...)` forms appear as top-level forms for evaluation and navigation, set + +``` emacs-lisp +(setq clojure-ts-toplevel-inside-comment-form t) +``` + +### Fill paragraph + +To change the maximal line length used by `M-x prog-fill-reindent-defun` (also +bound to `M-q` by default) to reformat docstrings and comments it's possible to +customize `clojure-ts-fill-paragraph` variable (by default set to the value of +Emacs' `fill-paragraph` value). + +Every new line in the docstrings is indented by +`clojure-ts-docstring-fill-prefix-width` number of spaces (set to 2 by default +which matches the `clojure-mode` settings). + ## Migrating to clojure-ts-mode If you are migrating to `clojure-ts-mode` note that `clojure-mode` is still required for cider and clj-refactor packages to work properly. From 9bca1c360a113031dbc57fb9038e5773bce31eb8 Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Fri, 7 Mar 2025 10:21:46 +0200 Subject: [PATCH 274/379] Expand the README a bit --- README.md | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 1302da3..72fe315 100644 --- a/README.md +++ b/README.md @@ -18,8 +18,15 @@ highlighting), indentation, and navigation support for the for a very long time, but it suffers from a few [long-standing problems](https://github.com/clojure-emacs/clojure-mode#caveats), related to Emacs limitations baked into its design. The introduction of built-in support -for Tree-sitter in Emacs 29 provides a natural opportunity to address many of -them. Enter `clojure-ts-mode`. +for Tree-sitter in Emacs 29 presents a natural opportunity to address many of +them. Enter `clojure-ts-mode`, which makes use of TreeSitter to provide: + +- fast, accurate and more granular font-locking +- fast indentation +- common Emacs functionality like structured navigation, `imenu` (an outline of a source buffer), current form inference (used internally by various Emacs modes and utilities), etc + +Working with TreeSitter is significantly easier than the legacy Emacs APIs for font-locking and +indentation, which makes it easier to contribute to `clojure-ts-mode`, and to improve it in general. Keep in mind that the transition to `clojure-ts-mode` won't happen overnight for several reasons: @@ -37,6 +44,11 @@ You can read more about the vision for `clojure-ts-mode` [here](https://metaredu **This library is still under active development. Breaking changes should be expected.** +The currently provided functionality should cover the needs of most Clojure programmers, but you +can expect to encounter some bugs and missing functionality here and there. + +Those will be addressed over the time, as more and more people use `clojure-ts-mode`. + ## Installation ### Emacs 29 From 1fa9ced658acbd8b715787983b7aaaf44d165ee0 Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Fri, 7 Mar 2025 10:54:10 +0200 Subject: [PATCH 275/379] Improve the installation instructions --- README.md | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 72fe315..56b2194 100644 --- a/README.md +++ b/README.md @@ -51,23 +51,29 @@ Those will be addressed over the time, as more and more people use `clojure-ts-m ## Installation -### Emacs 29 - -This package requires Emacs 29 built with tree-sitter support from the [emacs-29 branch](https://git.savannah.gnu.org/cgit/emacs.git/log/?h=emacs-29). - -If you decide to build Emacs from source there's some useful information on this in the Emacs repository: - -- [Emacs tree-sitter starter-guide](https://git.savannah.gnu.org/cgit/emacs.git/tree/admin/notes/tree-sitter/starter-guide?h=emacs-29) -- [Emacs install instructions](https://git.savannah.gnu.org/cgit/emacs.git/tree/INSTALL.REPO). +### Requirements +For `clojure-ts-mode` to work, you need Emacs 29+ built with TreeSitter support. To check if your Emacs supports tree sitter run the following (e.g. by using `M-:`): ``` emacs-lisp (treesit-available-p) ``` +Additionally, you'll need to have Git and some C compiler (`cc`) installed and available +in your `$PATH` (or Emacs's `exec-path`), for `clojure-ts-mode` to be able to install the required +TreeSitter grammars automatically. + ### Install clojure-ts-mode +> [!NOTE] +> +> That's the recommended way to install `clojure-ts-mode`. + +If you have `git` and a C compiler (`cc`) available on your system's `PATH`, +`clojure-ts-mode` will install the +grammars + clojure-ts-mode is available on [MElPA](https://melpa.org/#/clojure-ts-mode) and [NonGNU ELPA](https://elpa.nongnu.org/nongnu/clojure-ts-mode.html). It can be installed with @@ -102,6 +108,11 @@ Once installed, evaluate clojure-ts-mode.el and you should be ready to go. ### Install tree-sitter grammars +> [!NOTE] +> +> `clojure-ts-mode` install the required grammars automatically, so for most +> people no manual actions will be required. + `clojure-ts-mode` makes use of two TreeSitter grammars to work properly: - The Clojure grammar, mentioned earlier From 88fc10f247857cd5f833d1a67033aa4369d0e6f3 Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Fri, 7 Mar 2025 16:37:46 +0200 Subject: [PATCH 276/379] Convert a note to an admonition --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 56b2194..c4fe2ee 100644 --- a/README.md +++ b/README.md @@ -148,7 +148,9 @@ Set the var `clojure-ts-indent-style` to change it. (setq clojure-ts-indent-style 'fixed) ``` -**Note:** You can find [this article](https://metaredux.com/posts/2020/12/06/semantic-clojure-formatting.html) comparing semantic and fixed indentation useful. +> [!TIP] +> +> You can find [this article](https://metaredux.com/posts/2020/12/06/semantic-clojure-formatting.html) comparing semantic and fixed indentation useful. ### Font Locking From 592f708d5d30d69b0a25d8a6625d1ae0d05c0cba Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Fri, 7 Mar 2025 16:40:48 +0200 Subject: [PATCH 277/379] Extend the font-locking documentation --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index c4fe2ee..d583059 100644 --- a/README.md +++ b/README.md @@ -163,6 +163,14 @@ To highlight entire rich `comment` expression with the comment font face, set By default this is `nil`, so that anything within a `comment` expression is highlighted like regular clojure code. +> [!TIP] +> +> You can customize the exact level of font-locking via the variables +> `treesit-font-lock-level` (the default value is 3) and +> `treesit-font-lock-features-list`. Check [this +> section](https://www.gnu.org/software/emacs/manual/html_node/emacs/Parser_002dbased-Font-Lock.html) +> of the Emacs manual for more details. + ### Highlight markdown syntax in docstrings By default markdown syntax is highlighted in the docstrings using From e0703fff14de7cdf923093b727b80c465d2c1343 Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Fri, 7 Mar 2025 16:48:14 +0200 Subject: [PATCH 278/379] Add a "Caveats" section the to the README --- README.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/README.md b/README.md index d583059..03e00e4 100644 --- a/README.md +++ b/README.md @@ -223,8 +223,22 @@ After installing the package do the following. (cider-clojure-cli-aliases . ":test:repl"))) ``` +## Caveats + +As the TreeSitter Emacs APIs are new and keep evolving there are some +differences in the behavior of `clojure-ts-mode` on different Emacs versions. +Here are some notable examples: + +- On Emacs 29 the parent mode is `prog-mode`, but on Emacs 30+ it's both `prog-mode` +and `clojure-mode` (this is very helpful when dealing with `derived-mode-p` checks) + ## Frequently Asked Questions +### What `clojure-mode` features are currently missing? + +As of version 0.2.x, the most obvious missing feature are the various +refactoring commands in `clojure-mode`. + ### Does `clojure-ts-mode` work with CIDER? Yes! Preliminary support for `clojure-ts-mode` was released in [CIDER From 51ee8a46737798dbe99613624024958ebe2ce296 Mon Sep 17 00:00:00 2001 From: Roman Rudakov Date: Fri, 7 Mar 2025 14:32:52 +0100 Subject: [PATCH 279/379] [#62] Define list thing to improve navigation in Emacs 31 If list thing is defined, Emacs 31 will set a few options automatically to improve navigation by lists/sexp etc. Big thanks for the patch to Juri Linkov . --- CHANGELOG.md | 2 ++ README.md | 3 +++ clojure-ts-mode.el | 13 ++++++++++--- 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b498929..9c6bc7b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## main (unreleased) +- [[#62](https://github.com/clojure-emacs/clojure-ts-mode/issues/62)]: Define `list` "thing" to improve navigation in Emacs 31. + ## 0.2.3 (2025-03-04) - [#38]: Add support for `in-ns` forms in `clojure-ts-find-ns`. diff --git a/README.md b/README.md index 03e00e4..a5b0105 100644 --- a/README.md +++ b/README.md @@ -231,6 +231,9 @@ Here are some notable examples: - On Emacs 29 the parent mode is `prog-mode`, but on Emacs 30+ it's both `prog-mode` and `clojure-mode` (this is very helpful when dealing with `derived-mode-p` checks) +- Navigation by sexp/lists might work differently on Emacs versions lower + than 31. Starting with version 31, Emacs uses TreeSitter 'things' settings, if + available, to rebind some commands. ## Frequently Asked Questions diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index d07d75b..7480cb9 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -918,10 +918,16 @@ If JUSTIFY is non-nil, justify as well as fill the paragraph." "unquote_splicing_lit" "unquoting_lit") "A regular expression that matches nodes that can be treated as s-expressions.") +(defconst clojure-ts--list-nodes + '("list_lit" "anon_fn_lit" "read_cond_lit" "splicing_read_cond_lit" + "map_lit" "ns_map_lit" "vec_lit" "set_lit") + "A regular expression that matches nodes that can be treated as lists.") + (defconst clojure-ts--thing-settings `((clojure - (sexp ,(regexp-opt clojure-ts--sexp-nodes) - text ,(regexp-opt '("comment")))))) + (sexp ,(regexp-opt clojure-ts--sexp-nodes)) + (list ,(regexp-opt clojure-ts--list-nodes)) + (text ,(regexp-opt '("comment")))))) (defvar clojure-ts-mode-map (let ((map (make-sparse-keymap))) @@ -1043,7 +1049,8 @@ See `clojure-ts--font-lock-settings' for usage of MARKDOWN-AVAILABLE." ;; Workaround for treesit-transpose-sexps not correctly working with ;; treesit-thing-settings on Emacs 30. ;; Once treesit-transpose-sexps it working again this can be removed - (when (fboundp 'transpose-sexps-default-function) + (when (and (fboundp 'transpose-sexps-default-function) + (< emacs-major-version 31)) (setq-local transpose-sexps-function #'transpose-sexps-default-function))))) ;; For Emacs 30+, so that `clojure-ts-mode' is treated as deriving from From b05c33af317257df2b8f4ca0c4776820b4cc6aaa Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Sat, 8 Mar 2025 13:28:22 +0200 Subject: [PATCH 280/379] Convert a note to a proper admonition --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a5b0105..cb9a75d 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,9 @@ You can read more about the vision for `clojure-ts-mode` [here](https://metaredu ## Current Status -**This library is still under active development. Breaking changes should be expected.** +> [!WARNING] +> +> This library is still under active development. Breaking changes should be expected. The currently provided functionality should cover the needs of most Clojure programmers, but you can expect to encounter some bugs and missing functionality here and there. From 842ddd69e7f59d4cfc4f8ce67525673d7b963d44 Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Sat, 8 Mar 2025 13:30:56 +0200 Subject: [PATCH 281/379] Add a suggestion to use the latest Emacs when possible --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index cb9a75d..982cc2a 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,12 @@ Additionally, you'll need to have Git and some C compiler (`cc`) installed and a in your `$PATH` (or Emacs's `exec-path`), for `clojure-ts-mode` to be able to install the required TreeSitter grammars automatically. +> [!TIP] +> +> As the TreeSitter support in Emacs is still fairly new and under active development itself, for optimal +> results you should use the latest stable Emacs release or even the development version of Emacs. +> See the "Caveats" section for more on the subject. + ### Install clojure-ts-mode > [!NOTE] From f5f377ac84ab09f4453b6712fc341ddd8db3af0a Mon Sep 17 00:00:00 2001 From: yuhan0 Date: Sun, 9 Mar 2025 02:35:59 +0800 Subject: [PATCH 282/379] Add defcustom for major-mode remapping (#64) --- CHANGELOG.md | 1 + README.md | 10 ++++++++++ clojure-ts-mode.el | 29 ++++++++++++++++++++++++++--- 3 files changed, 37 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c6bc7b..3ce4774 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## main (unreleased) - [[#62](https://github.com/clojure-emacs/clojure-ts-mode/issues/62)]: Define `list` "thing" to improve navigation in Emacs 31. +- [#64]: Add defcustom `clojure-ts-auto-remap` to control remapping of `clojure-mode` buffers. ## 0.2.3 (2025-03-04) diff --git a/README.md b/README.md index 982cc2a..fafc6ba 100644 --- a/README.md +++ b/README.md @@ -143,6 +143,16 @@ To see a list of available configuration options do `M-x customize-group c Most configuration changes will require reverting any active `clojure-ts-mode` buffers. +### Remapping of `clojure-mode` buffers + +By default, `clojure-ts-mode` assumes command over all buffers and file extensions previously associated with `clojure-mode` (and derived major modes like `clojurescript-mode`). To disable this remapping, set + +``` emacs-lisp +(setopt clojure-ts-auto-remap nil) +``` + +You can also use the commands `clojure-ts-activate` / `clojure-ts-deactivate` to interactively change this behavior. + ### Indentation `clojure-ts-mode` currently supports 2 different indentation strategies: diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index 7480cb9..ac89814 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -119,6 +119,18 @@ double quotes on the third column." :safe #'booleanp :package-version '(clojure-ts-mode . "0.2.3")) +(defcustom clojure-ts-auto-remap t + "When non-nil, redirect all `clojure-mode' buffers to `clojure-ts-mode'." + :safe #'booleanp + :type 'boolean + :package-version '(clojure-ts-mode . "0.2.4")) + +(defvar clojure-ts-mode-remappings + '((clojure-mode . clojure-ts-mode) + (clojurescript-mode . clojure-ts-clojurescript-mode) + (clojurec-mode . clojure-ts-clojurec-mode)) + "Alist of entries to `major-mode-remap-alist'.") + (defvar clojure-ts--debug nil "Enables debugging messages, shows current node in mode-line. Only intended for use at development time.") @@ -1087,13 +1099,24 @@ See `clojure-ts--font-lock-settings' for usage of MARKDOWN-AVAILABLE." (add-to-list 'auto-mode-alist '("\\.cljd\\'" . clojure-ts-clojuredart-mode)) (add-to-list 'auto-mode-alist '("\\.jank\\'" . clojure-ts-jank-mode))) +(defun clojure-ts-activate () + "Redirect all `clojure-mode' buffers to use `clojure-ts-mode'." + (interactive) + (dolist (entry clojure-ts-mode-remappings) + (add-to-list 'major-mode-remap-alist entry))) + +(defun clojure-ts-deactivate () + "Revert the redirecting of of `clojure-mode' buffers to `clojure-ts-mode'." + (interactive) + (dolist (entry clojure-ts-mode-remappings) + (setq major-mode-remap-alist (remove entry major-mode-remap-alist)))) + (if (treesit-available-p) ;; Redirect clojure-mode to clojure-ts-mode if clojure-mode is present (if (require 'clojure-mode nil 'noerror) (progn - (add-to-list 'major-mode-remap-alist '(clojure-mode . clojure-ts-mode)) - (add-to-list 'major-mode-remap-alist '(clojurescript-mode . clojure-ts-clojurescript-mode)) - (add-to-list 'major-mode-remap-alist '(clojurec-mode . clojure-ts-clojurec-mode)) + (when clojure-ts-auto-remap + (clojure-ts-activate)) (clojure-ts--register-novel-modes)) ;; When Clojure-mode is not present, setup auto-modes ourselves (progn From 7543284987889e410e27ea3af8254a2150cf1783 Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Mon, 10 Mar 2025 13:23:17 +0200 Subject: [PATCH 283/379] Use major-mode-remap-defaults instead of major-mode-remap-alist major-mode-remap-alist is meant to be configured by the end users, and major-mode-remap-defaults is meant to be configured from Elisp code (e.g. major modes). --- clojure-ts-mode.el | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index ac89814..a5e24f0 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -129,7 +129,10 @@ double quotes on the third column." '((clojure-mode . clojure-ts-mode) (clojurescript-mode . clojure-ts-clojurescript-mode) (clojurec-mode . clojure-ts-clojurec-mode)) - "Alist of entries to `major-mode-remap-alist'.") + "Alist of entries to `major-mode-remap-defaults'. + +See also `clojure-ts-activate-mode-remappings' and +`clojure-ts-definition-docstring-symbols'.") (defvar clojure-ts--debug nil "Enables debugging messages, shows current node in mode-line. @@ -1099,24 +1102,29 @@ See `clojure-ts--font-lock-settings' for usage of MARKDOWN-AVAILABLE." (add-to-list 'auto-mode-alist '("\\.cljd\\'" . clojure-ts-clojuredart-mode)) (add-to-list 'auto-mode-alist '("\\.jank\\'" . clojure-ts-jank-mode))) -(defun clojure-ts-activate () - "Redirect all `clojure-mode' buffers to use `clojure-ts-mode'." +(defun clojure-ts-activate-mode-remappings () + "Remap all `clojure-mode' file-specified modes to use `clojure-ts-mode'. + +Useful if you want to try out `clojure-ts-mode' without having to manually +update the mode mappings." (interactive) (dolist (entry clojure-ts-mode-remappings) - (add-to-list 'major-mode-remap-alist entry))) + (add-to-list 'major-mode-remap-defaults entry))) + +(defun clojure-ts-deactivate-mode-remappings () + "Undo `clojure-ts-mode' file-specified mode remappings. -(defun clojure-ts-deactivate () - "Revert the redirecting of of `clojure-mode' buffers to `clojure-ts-mode'." +Useful if you want to switch to the `clojure-mode's mode mappings." (interactive) (dolist (entry clojure-ts-mode-remappings) - (setq major-mode-remap-alist (remove entry major-mode-remap-alist)))) + (setq major-mode-remap-defaults (remove entry major-mode-remap-defaults)))) (if (treesit-available-p) ;; Redirect clojure-mode to clojure-ts-mode if clojure-mode is present (if (require 'clojure-mode nil 'noerror) (progn (when clojure-ts-auto-remap - (clojure-ts-activate)) + (clojure-ts-activate-mode-remappings)) (clojure-ts--register-novel-modes)) ;; When Clojure-mode is not present, setup auto-modes ourselves (progn From 0da4c1d15fc8a8847e08cf84283b660c3b147b6f Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Mon, 10 Mar 2025 13:25:46 +0200 Subject: [PATCH 284/379] Bump the required Emacs version to 30 This simplifies the maintenance and ensure most users will get optimal experience with the mode. --- .github/workflows/lint-emacs.yml | 2 +- README.md | 2 +- clojure-ts-mode.el | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/lint-emacs.yml b/.github/workflows/lint-emacs.yml index 266add8..2483fb9 100644 --- a/.github/workflows/lint-emacs.yml +++ b/.github/workflows/lint-emacs.yml @@ -36,7 +36,7 @@ jobs: strategy: matrix: - emacs_version: ['29.1'] + emacs_version: ['30.1'] steps: - name: Set up Emacs diff --git a/README.md b/README.md index fafc6ba..f4e53ef 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ Those will be addressed over the time, as more and more people use `clojure-ts-m ### Requirements -For `clojure-ts-mode` to work, you need Emacs 29+ built with TreeSitter support. +For `clojure-ts-mode` to work, you need Emacs 30+ built with TreeSitter support. To check if your Emacs supports tree sitter run the following (e.g. by using `M-:`): ``` emacs-lisp diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index a5e24f0..1b689ea 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -7,7 +7,7 @@ ;; URL: http://github.com/clojure-emacs/clojure-ts-mode ;; Keywords: languages clojure clojurescript lisp ;; Version: 0.2.3 -;; Package-Requires: ((emacs "29.1")) +;; Package-Requires: ((emacs "30")) ;; This file is not part of GNU Emacs. From 00ee0684a4b5af1eaa7cbf7af6cd05f95298757c Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Mon, 10 Mar 2025 13:28:52 +0200 Subject: [PATCH 285/379] Remove the an obsolete check --- clojure-ts-mode.el | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index 1b689ea..be00147 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -1069,9 +1069,8 @@ See `clojure-ts--font-lock-settings' for usage of MARKDOWN-AVAILABLE." (setq-local transpose-sexps-function #'transpose-sexps-default-function))))) ;; For Emacs 30+, so that `clojure-ts-mode' is treated as deriving from -;; `clojure-mode' -(when (fboundp 'derived-mode-add-parents) - (derived-mode-add-parents 'clojure-ts-mode '(clojure-mode))) +;; `clojure-mode' in the context of `derived-mode-p' +(derived-mode-add-parents 'clojure-ts-mode '(clojure-mode)) ;;;###autoload (define-derived-mode clojure-ts-clojurescript-mode clojure-ts-mode "ClojureScript[TS]" From 2458b4c837c3f927900477a6aa99b504bc5e3c97 Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Mon, 10 Mar 2025 13:29:18 +0200 Subject: [PATCH 286/379] Bump the development version --- clojure-ts-mode.el | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index be00147..4fc1a2a 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -6,7 +6,7 @@ ;; Maintainer: Danny Freeman ;; URL: http://github.com/clojure-emacs/clojure-ts-mode ;; Keywords: languages clojure clojurescript lisp -;; Version: 0.2.3 +;; Version: 0.2.4-snapshot ;; Package-Requires: ((emacs "30")) ;; This file is not part of GNU Emacs. @@ -71,7 +71,7 @@ :link '(emacs-commentary-link :tag "Commentary" "clojure-mode")) (defconst clojure-ts-mode-version - "0.2.3" + "0.2.4-snapshot" "The current version of `clojure-ts-mode'.") (defcustom clojure-ts-comment-macro-font-lock-body nil From b165c6e8d6040e8701f1e3798c1b33f8549b823f Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Mon, 10 Mar 2025 14:33:56 +0200 Subject: [PATCH 287/379] Try to appease package-lint --- clojure-ts-mode.el | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index 4fc1a2a..dba6058 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -7,7 +7,7 @@ ;; URL: http://github.com/clojure-emacs/clojure-ts-mode ;; Keywords: languages clojure clojurescript lisp ;; Version: 0.2.4-snapshot -;; Package-Requires: ((emacs "30")) +;; Package-Requires: ((emacs "30.1")) ;; This file is not part of GNU Emacs. From 4f52eae031fa36ed36dee945a10407d6228b0eca Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Mon, 10 Mar 2025 14:38:44 +0200 Subject: [PATCH 288/379] Fix a changelog entry --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ce4774..eb10058 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,7 @@ ## main (unreleased) -- [[#62](https://github.com/clojure-emacs/clojure-ts-mode/issues/62)]: Define `list` "thing" to improve navigation in Emacs 31. +- [#62](https://github.com/clojure-emacs/clojure-ts-mode/issues/62): Define `list` "thing" to improve navigation in Emacs 31. - [#64]: Add defcustom `clojure-ts-auto-remap` to control remapping of `clojure-mode` buffers. ## 0.2.3 (2025-03-04) From f932fc30f7194d8fcdc2e245c5705e7b9430d4e8 Mon Sep 17 00:00:00 2001 From: Roman Rudakov Date: Sat, 22 Mar 2025 20:38:23 +0100 Subject: [PATCH 289/379] Improve syntax highlighting --- CHANGELOG.md | 6 ++++++ clojure-ts-mode.el | 26 +++++++++++++++--------- test/clojure-ts-mode-font-lock-test.el | 28 +++++++++++++++++++++++++- test/samples/test.clj | 11 +++++++++- 4 files changed, 60 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index eb10058..503d8c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,12 @@ - [#62](https://github.com/clojure-emacs/clojure-ts-mode/issues/62): Define `list` "thing" to improve navigation in Emacs 31. - [#64]: Add defcustom `clojure-ts-auto-remap` to control remapping of `clojure-mode` buffers. +- Improve syntax highlighting: + - Highlight metadata with single keyword with `clojure-ts-keyword-face`. + - Only highlight built-ins from `clojure.core` namespace. + - Highlight named lambda functions properly. + - Fix syntax highlighting for functions and vars with metadata on the previous + line. ## 0.2.3 (2025-03-04) diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index dba6058..30d65cc 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -384,9 +384,16 @@ with the markdown_inline grammar." marker: _ @clojure-ts-keyword-face delimiter: _ :? @default)) + ;; Highlight as built-in only if there is no namespace or namespace is + ;; `clojure.core'. :feature 'builtin :language 'clojure - `(((list_lit meta: _ :? :anchor (sym_lit (sym_name) @font-lock-keyword-face)) + `(((list_lit meta: _ :? :anchor (sym_lit !namespace name: (sym_name) @font-lock-keyword-face)) + (:match ,clojure-ts--builtin-symbol-regexp @font-lock-keyword-face)) + ((list_lit meta: _ :? :anchor + (sym_lit namespace: ((sym_ns) @ns + (:equal "clojure.core" @ns)) + name: (sym_name) @font-lock-keyword-face)) (:match ,clojure-ts--builtin-symbol-regexp @font-lock-keyword-face)) ((sym_name) @font-lock-builtin-face (:match ,clojure-ts--builtin-dynamic-var-regexp @font-lock-builtin-face))) @@ -408,12 +415,13 @@ with the markdown_inline grammar." ;; No wonder the tree-sitter-clojure grammar only touches syntax, and not semantics :feature 'definition ;; defn and defn like macros :language 'clojure - `(((list_lit :anchor meta: _ :? - :anchor (sym_lit (sym_name) @def) + `(((list_lit :anchor meta: _ :* + :anchor (sym_lit (sym_name) @font-lock-keyword-face) :anchor (sym_lit (sym_name) @font-lock-function-name-face)) (:match ,(rx-to-string `(seq bol (or + "fn" "defn" "defn-" "defmulti" @@ -423,7 +431,7 @@ with the markdown_inline grammar." "defmacro" "definline") eol)) - @def)) + @font-lock-keyword-face)) ((anon_fn_lit marker: "#" @font-lock-property-face)) ;; Methods implementation @@ -450,10 +458,10 @@ with the markdown_inline grammar." :feature 'variable ;; def, defonce :language 'clojure - `(((list_lit :anchor meta: _ :? - :anchor (sym_lit (sym_name) @def) + `(((list_lit :anchor meta: _ :* + :anchor (sym_lit (sym_name) @font-lock-keyword-face) :anchor (sym_lit (sym_name) @font-lock-variable-name-face)) - (:match ,clojure-ts--variable-definition-symbol-regexp @def))) + (:match ,clojure-ts--variable-definition-symbol-regexp @font-lock-keyword-face))) ;; Can we support declarations in the namespace form? :feature 'type @@ -479,10 +487,10 @@ with the markdown_inline grammar." :override t `((meta_lit marker: "^" @font-lock-operator-face - value: (kwd_lit (kwd_name) @font-lock-property-name-face)) + value: (kwd_lit (kwd_name) @clojure-ts-keyword-face)) (old_meta_lit marker: "#^" @font-lock-operator-face - value: (kwd_lit (kwd_name) @font-lock-property-name-face))) + value: (kwd_lit (kwd_name) @clojure-ts-keyword-face))) :feature 'tagged-literals :language 'clojure diff --git a/test/clojure-ts-mode-font-lock-test.el b/test/clojure-ts-mode-font-lock-test.el index 60b4e3e..c3cafd5 100644 --- a/test/clojure-ts-mode-font-lock-test.el +++ b/test/clojure-ts-mode-font-lock-test.el @@ -136,4 +136,30 @@ DESCRIPTION is the description of the spec." (13 16 font-lock-keyword-face) (18 20 font-lock-function-name-face) (25 31 font-lock-doc-face) - (40 46 font-lock-string-face)))) + (40 46 font-lock-string-face))) + + (when-fontifying-it "fn-with-name" + ("(fn named-lambda [x] x)" + (2 3 font-lock-keyword-face) + (5 16 font-lock-function-name-face))) + + (when-fontifying-it "single-keyword-metadata" + ("(def ^:private my-private-var true)" + (2 4 font-lock-keyword-face) + (6 6 font-lock-operator-face) + (7 14 clojure-ts-keyword-face) + (16 29 font-lock-variable-name-face) + (31 34 font-lock-constant-face))) + + (when-fontifying-it "built-ins" + ("(for [x [1 2 3]] x)" + (2 4 font-lock-keyword-face)) + + ("(clojure.core/for [x [1 2 3]] x)" + (2 13 font-lock-type-face) + (15 17 font-lock-keyword-face))) + + (when-fontifying-it "non-built-ins-with-same-name" + ("(h/for query {})" + (2 2 font-lock-type-face) + (4 6 nil)))) diff --git a/test/samples/test.clj b/test/samples/test.clj index 1ab5efa..a8a1ae9 100644 --- a/test/samples/test.clj +++ b/test/samples/test.clj @@ -13,6 +13,8 @@ (fn ^:m hello [x] @x) +(def ^:private my-var true) + (def ^Boolean x true) (clojure.core/defmacro my-mac [] @@ -20,6 +22,11 @@ ~x ~@x)) +;; Built-ins should be highlighted only for `clojure.core` namespace. +(for []) +(clojure.core/for []) +(honey.sql/for {}) + ;; the myfn sexp should have a comment face (mysfn 101 foo @@ -237,7 +244,6 @@ ([x y & more] (reduce1 max (max x y) more))) - ;; definitions with metadata only don't cause freezing (def ^String) ;; clojure-mode regression: the hanging metadata doesn't cause freezing @@ -292,6 +298,9 @@ clojure.core/map ^{:foo true} (defn b "hello" [] "world") +^{:foo bar} +(def foo "usage" "hello") + (comment (defrecord TestRecord [field] AutoCloseable From 4cf4bd11428595b62002f5a587a53deaf21123b2 Mon Sep 17 00:00:00 2001 From: Roman Rudakov Date: Tue, 25 Mar 2025 23:00:28 +0100 Subject: [PATCH 290/379] Improve semantic indentation rules to be more consistent with cljfmt --- CHANGELOG.md | 2 + README.md | 39 +++++ clojure-ts-mode.el | 195 +++++++++++++++++------ test/clojure-ts-mode-indentation-test.el | 101 +++++++++++- test/samples/indentation.clj | 71 ++++++++- test/test-helper.el | 3 +- 6 files changed, 358 insertions(+), 53 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 503d8c6..a4cf968 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ - Highlight named lambda functions properly. - Fix syntax highlighting for functions and vars with metadata on the previous line. +- Improve semantic indentation rules to be more consistent with cljfmt. +- Introduce `clojure-ts-semantic-indent-rules` customization option. ## 0.2.3 (2025-03-04) diff --git a/README.md b/README.md index f4e53ef..4fb47b2 100644 --- a/README.md +++ b/README.md @@ -170,6 +170,45 @@ Set the var `clojure-ts-indent-style` to change it. > > You can find [this article](https://metaredux.com/posts/2020/12/06/semantic-clojure-formatting.html) comparing semantic and fixed indentation useful. +#### Customizing semantic indentation + +The indentation of special forms and macros with bodies is controlled via +`clojure-ts-semantic-indent-rules`. Nearly all special forms and built-in macros +with bodies have special indentation settings in clojure-ts-mode, which are +aligned with cljfmt indent rules. You can add/alter the indentation settings in +your personal config. Let's assume you want to indent `->>` and `->` like this: + +```clojure +(->> something + ala + bala + portokala) +``` + +You can do so by putting the following in your config: + +```emacs-lisp +(setopt clojure-ts-semantic-indent-rules '(("->" . (:block 1)) + ("->>" . (:block 1)))) +``` + +This means that the body of the `->`/`->>` is after the first argument. + +The default set of rules is defined as +`clojure-ts--semantic-indent-rules-defaults`, any rule can be overridden using +customization option. + +There are 2 types of rules supported: `:block` and `:inner`, similarly to +cljfmt. If rule is defined as `:block n`, `n` means a number of arguments after +which begins the body. If rule is defined as `:inner n`, each form in the body +is indented with 2 spaces regardless of `n` value (currently all default rules +has 0 value). + +For example: +- `do` has a rule `:block 0`. +- `when` has a rule `:block 1`. +- `defn` and `fn` have a rule `:inner 0`. + ### Font Locking To highlight entire rich `comment` expression with the comment font face, set diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index 30d65cc..19fa605 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -125,6 +125,23 @@ double quotes on the third column." :type 'boolean :package-version '(clojure-ts-mode . "0.2.4")) +(defcustom clojure-ts-semantic-indent-rules nil + "Custom rules to extend default indentation rules for `semantic' style. + +Each rule is an alist entry which looks like `(\"symbol-name\" +. (rule-type rule-value))', where rule-type is one either `:block' or +`:inner' and rule-value is an integer. The semantic is similar to +cljfmt indentation rules. + +Default set of rules is defined in +`clojure-ts--semantic-indent-rules-defaults'." + :safe #'listp + :type '(alist :key-type string + :value-type (list (choice (const :tag "Block indentation rule" :block) + (const :tag "Inner indentation rule" :inner)) + integer)) + :package-version '(clojure-ts-mode . "0.2.4")) + (defvar clojure-ts-mode-remappings '((clojure-mode . clojure-ts-mode) (clojurescript-mode . clojure-ts-clojurescript-mode) @@ -182,7 +199,6 @@ Only intended for use at development time.") table) "Syntax table for `clojure-ts-mode'.") - (defconst clojure-ts--builtin-dynamic-var-regexp (eval-and-compile (concat "^" @@ -746,34 +762,135 @@ The possible values for this variable are ((parent-is "list_lit") parent 1) ((parent-is "set_lit") parent 2)))) -(defvar clojure-ts--symbols-with-body-expressions-regexp - (eval-and-compile - (rx (or - ;; Match def* symbols, - ;; we also explicitly do not match symbols beginning with - ;; "default" "deflate" and "defer", like cljfmt - (and line-start "def") - ;; Match with-* symbols - (and line-start "with-") - ;; Exact matches - (and line-start - (or "alt!" "alt!!" "are" "as->" - "binding" "bound-fn" - "case" "catch" "comment" "cond" "condp" "cond->" "cond->>" - "delay" "do" "doseq" "dotimes" "doto" - "extend" "extend-protocol" "extend-type" - "fdef" "finally" "fn" "for" "future" - "go" "go-loop" - "if" "if-let" "if-not" "if-some" - "let" "letfn" "locking" "loop" - "match" "ns" "proxy" "reify" "struct-map" - "testing" "thread" "try" - "use-fixtures" - "when" "when-first" "when-let" "when-not" "when-some" "while") - line-end)))) - "A regex to match symbols that are functions/macros with a body argument. -Taken from cljfmt: -https://github.com/weavejester/cljfmt/blob/fb26b22f569724b05c93eb2502592dfc2de898c3/cljfmt/resources/cljfmt/indents/clojure.clj") +(defvar clojure-ts--semantic-indent-rules-defaults + '(("alt!" . (:block 0)) + ("alt!!" . (:block 0)) + ("comment" . (:block 0)) + ("cond" . (:block 0)) + ("delay" . (:block 0)) + ("do" . (:block 0)) + ("finally" . (:block 0)) + ("future" . (:block 0)) + ("go" . (:block 0)) + ("thread" . (:block 0)) + ("try" . (:block 0)) + ("with-out-str" . (:block 0)) + ("defprotocol" . (:block 1)) + ("binding" . (:block 1)) + ("defprotocol" . (:block 1)) + ("binding" . (:block 1)) + ("case" . (:block 1)) + ("cond->" . (:block 1)) + ("cond->>" . (:block 1)) + ("doseq" . (:block 1)) + ("dotimes" . (:block 1)) + ("doto" . (:block 1)) + ("extend" . (:block 1)) + ("extend-protocol" . (:block 1)) + ("extend-type" . (:block 1)) + ("for" . (:block 1)) + ("go-loop" . (:block 1)) + ("if" . (:block 1)) + ("if-let" . (:block 1)) + ("if-not" . (:block 1)) + ("if-some" . (:block 1)) + ("let" . (:block 1)) + ("letfn" . (:block 1)) + ("locking" . (:block 1)) + ("loop" . (:block 1)) + ("match" . (:block 1)) + ("ns" . (:block 1)) + ("struct-map" . (:block 1)) + ("testing" . (:block 1)) + ("when" . (:block 1)) + ("when-first" . (:block 1)) + ("when-let" . (:block 1)) + ("when-not" . (:block 1)) + ("when-some" . (:block 1)) + ("while" . (:block 1)) + ("with-local-vars" . (:block 1)) + ("with-open" . (:block 1)) + ("with-precision" . (:block 1)) + ("with-redefs" . (:block 1)) + ("defrecord" . (:block 2)) + ("deftype" . (:block 2)) + ("are" . (:block 2)) + ("as->" . (:block 2)) + ("catch" . (:block 2)) + ("condp" . (:block 2)) + ("bound-fn" . (:inner 0)) + ("def" . (:inner 0)) + ("defmacro" . (:inner 0)) + ("defmethod" . (:inner 0)) + ("defmulti" . (:inner 0)) + ("defn" . (:inner 0)) + ("defn-" . (:inner 0)) + ("defonce" . (:inner 0)) + ("deftest" . (:inner 0)) + ("fdef" . (:inner 0)) + ("fn" . (:inner 0)) + ("reify" . (:inner 0)) + ("use-fixtures" . (:inner 0))) + "Default semantic indentation rules. + +The format reflects cljfmt indentation rules. All the default rules are +aligned with +https://github.com/weavejester/cljfmt/blob/0.13.0/cljfmt/resources/cljfmt/indents/clojure.clj") + +(defun clojure-ts--match-block-0-body (bol first-child) + "Match if expression body is not at the same line as FIRST-CHILD. + +If there is no body, check that BOL is not at the same line." + (let* ((body-pos (if-let* ((body (treesit-node-next-sibling first-child))) + (treesit-node-start body) + bol))) + (< (line-number-at-pos (treesit-node-start first-child)) + (line-number-at-pos body-pos)))) + +(defun clojure-ts--node-pos-match-block (node parent bol block) + "Return TRUE if NODE index in the PARENT matches requested BLOCK. + +NODE might be nil (when we insert an empty line for example), in this +case we look for next available child node in the PARENT after BOL +position. + +The first node in the expression is usually an opening paren, the last +node is usually a closing paren (unless some automatic parens mode is +not enabled). If requested BLOCK is 1, the NODE index should be at +least 3 (first node is opening paren, second node is matched symbol, +third node is first argument, and the rest is body which should be +indented.)" + (if node + (> (treesit-node-index node) (1+ block)) + (when-let* ((node-after-bol (treesit-node-first-child-for-pos parent bol))) + (> (treesit-node-index node-after-bol) (1+ block))))) + +(defun clojure-ts--match-form-body (node parent bol) + "Match if NODE has to be indented as a for body. + +PARENT not should be a list. If first symbol in the expression has an +indentation rule in `clojure-ts--semantic-indent-rules-defaults' or +`clojure-ts-semantic-indent-rules' check if NODE should be indented +according to the rule. If NODE is nil, use next node after BOL." + (and (clojure-ts--list-node-p parent) + (let ((first-child (clojure-ts--node-child-skip-metadata parent 0))) + (when-let* ((rule (alist-get (clojure-ts--named-node-text first-child) + (seq-union clojure-ts-semantic-indent-rules + clojure-ts--semantic-indent-rules-defaults + (lambda (e1 e2) (equal (car e1) (car e2)))) + nil + nil + #'equal))) + (and (not (clojure-ts--match-with-metadata node)) + (let ((rule-type (car rule)) + (rule-value (cadr rule))) + (if (equal rule-type :block) + (if (zerop rule-value) + ;; Special treatment for block 0 rule. + (clojure-ts--match-block-0-body bol first-child) + (clojure-ts--node-pos-match-block node parent bol rule-value)) + ;; Return true for any inner rule. + t))))))) (defun clojure-ts--match-function-call-arg (node parent _bol) "Match NODE if PARENT is a list expressing a function or macro call." @@ -787,24 +904,6 @@ https://github.com/weavejester/cljfmt/blob/fb26b22f569724b05c93eb2502592dfc2de89 (clojure-ts--keyword-node-p first-child) (clojure-ts--var-node-p first-child))))) -(defun clojure-ts--match-expression-in-body (node parent _bol) - "Match NODE if it is an expression used in a body argument. -PARENT is expected to be a list literal. -See `treesit-simple-indent-rules'." - (and - (clojure-ts--list-node-p parent) - (let ((first-child (clojure-ts--node-child-skip-metadata parent 0))) - (and - (not - (clojure-ts--symbol-matches-p - ;; Symbols starting with this are false positives - (rx line-start (or "default" "deflate" "defer")) - first-child)) - (not (clojure-ts--match-with-metadata node)) - (clojure-ts--symbol-matches-p - clojure-ts--symbols-with-body-expressions-regexp - first-child))))) - (defun clojure-ts--match-method-body (_node parent _bol) "Matches a `NODE' in the body of a `PARENT' method implementation. A method implementation referes to concrete implementations being defined in @@ -885,7 +984,7 @@ forms like deftype, defrecord, reify, proxy, etc." (clojure-ts--match-docstring parent 0) ;; https://guide.clojure.style/#body-indentation (clojure-ts--match-method-body parent 2) - (clojure-ts--match-expression-in-body parent 2) + (clojure-ts--match-form-body parent 2) ;; https://guide.clojure.style/#threading-macros-alignment (clojure-ts--match-threading-macro-arg prev-sibling 0) ;; https://guide.clojure.style/#vertically-align-fn-args diff --git a/test/clojure-ts-mode-indentation-test.el b/test/clojure-ts-mode-indentation-test.el index d4d3517..23a432b 100644 --- a/test/clojure-ts-mode-indentation-test.el +++ b/test/clojure-ts-mode-indentation-test.el @@ -140,4 +140,103 @@ DESCRIPTION is a string with the description of the spec." (when-indenting-it "should support function calls via vars" " (#'foo 5 - 6)")) + 6)") + +(when-indenting-it "should support block-0 expressions" + " +(do (aligned) + (vertically))" + + " +(do + (indented) + (with-2-spaces))" + + " +(future + (body is indented))" + + " +(try + (something) + ;; A bit of block 2 rule + (catch Exception e + \"Third argument is indented with 2 spaces.\") + (catch ExceptionInfo + e-info + \"Second argument is aligned vertically with the first one.\"))") + +(when-indenting-it "should support block-1 expressions" + " +(case x + 2 (print 2) + 3 (print 3) + (print \"Default\"))" + + " +(cond-> {} + :always (assoc :hello \"World\") + false (do nothing))" + + " +(with-precision 32 + (/ (bigdec 20) (bigdec 30)))" + + " +(testing \"Something should work\" + (is (something-working?)))") + +(when-indenting-it "should support block-2 expressions" + " +(are [x y] + (= x y) + 2 3 + 4 5 + 6 6)" + + " +(as-> {} $ + (assoc $ :hello \"World\"))" + + " +(as-> {} + my-map + (assoc my-map :hello \"World\"))" + + " +(defrecord MyThingR [] + IProto + (foo [this x] x))") + +(when-indenting-it "should support inner-0 expressions" + " +(fn named-lambda [x] + (+ x x))" + + " +(defmethod hello :world + [arg1 arg2] + (+ arg1 arg2))" + + " +(reify + AutoCloseable + (close + [this] + (is properly indented)))") + +(it "should prioritize custom semantic indentation rules" + (with-clojure-ts-buffer " +(are [x y] + (= x y) + 2 3 + 4 5 + 6 6)" + (setopt clojure-ts-semantic-indent-rules '(("are" . (:block 1)))) + (indent-region (point-min) (point-max)) + (expect (buffer-string) :to-equal " +(are [x y] + (= x y) + 2 3 + 4 5 + 6 6)")))) diff --git a/test/samples/indentation.clj b/test/samples/indentation.clj index f87870d..78a7aa6 100644 --- a/test/samples/indentation.clj +++ b/test/samples/indentation.clj @@ -79,8 +79,6 @@ :another-keyword 2} "default value") - - (defprotocol IProto (foo [this x] "`this` is a docstring.") @@ -121,7 +119,6 @@ ([a b] b))}) - ^:foo (def a 1) @@ -145,3 +142,71 @@ "hello" [_foo] (+ 1 1)) + +;;; Block 0 rule + +(do (aligned) + (vertically)) + +(do + (indented) + (with-2-spaces)) + +(future + (body is indented)) + +(try + (something) + ;; A bit of block 2 rule + (catch Exception e + "Third argument is indented with 2 spaces.") + (catch ExceptionInfo + e-info + "Second argument is aligned vertically with the first one.")) + +;;; Block 1 rule + +(case x + 2 (print 2) + 3 (print 3) + (print "Default")) + +(cond-> {} + :always (assoc :hello "World") + false (do nothing)) + +(with-precision 32 + (/ (bigdec 20) (bigdec 30))) + +(testing "Something should work" + (is (something-working?))) + +;;; Block 2 rule + +(are [x y] + (= x y) + 2 3 + 4 5 + 6 6) + +(as-> {} $ + (assoc $ :hello "World")) + +(as-> {} + my-map + (assoc my-map :hello "World")) + +;;; Inner 0 rule + +(fn named-lambda [x] + (+ x x)) + +(defmethod hello :world + [arg1 arg2] + (+ arg1 arg2)) + +(reify + AutoCloseable + (close + [this] + (is properly indented))) diff --git a/test/test-helper.el b/test/test-helper.el index c1cace3..f363644 100644 --- a/test/test-helper.el +++ b/test/test-helper.el @@ -24,7 +24,8 @@ ;;; Code: (defmacro with-clojure-ts-buffer (text &rest body) - "Create a temporary buffer, insert TEXT,switch to clojure-ts-mode. + "Create a temporary buffer, insert TEXT, switch to `clojure-ts-mode'. + And evaluate BODY." (declare (indent 1)) `(with-temp-buffer From 7497ae9c41a7ad264baf9b08f6590726f75f227c Mon Sep 17 00:00:00 2001 From: Roman Rudakov Date: Fri, 28 Mar 2025 12:58:15 +0100 Subject: [PATCH 291/379] [#61] Fix some issues with indentation for items with metadata - Collection elements (except of lists) are properly indented. - Body of special forms when the entire form has metadata is properly indented. - Additionally fix syntax highlighting of special forms when the entire form has metadata. --- CHANGELOG.md | 2 + README.md | 5 +++ clojure-ts-mode.el | 48 ++++++++++++++++++-- test/clojure-ts-mode-font-lock-test.el | 7 ++- test/clojure-ts-mode-indentation-test.el | 27 +++++++++++- test/samples/indentation.clj | 56 ++++++++++++++++++++++++ 6 files changed, 140 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a4cf968..8d7953f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,8 @@ line. - Improve semantic indentation rules to be more consistent with cljfmt. - Introduce `clojure-ts-semantic-indent-rules` customization option. +- [#61](https://github.com/clojure-emacs/clojure-ts-mode/issues/61): Fix issue with indentation of collection items with metadata. +- Proper syntax highlighting for expressions with metadata. ## 0.2.3 (2025-03-04) diff --git a/README.md b/README.md index 4fb47b2..b1aa3cf 100644 --- a/README.md +++ b/README.md @@ -291,6 +291,11 @@ and `clojure-mode` (this is very helpful when dealing with `derived-mode-p` chec - Navigation by sexp/lists might work differently on Emacs versions lower than 31. Starting with version 31, Emacs uses TreeSitter 'things' settings, if available, to rebind some commands. +- The indentation of list elements with metadata is inconsistent with other + collections. This inconsistency stems from the grammar's interpretation of + nearly every definition or function call as a list. Therefore, modifying the + indentation for list elements would adversely affect the indentation of + numerous other forms. ## Frequently Asked Questions diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index 19fa605..d0fa310 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -404,9 +404,9 @@ with the markdown_inline grammar." ;; `clojure.core'. :feature 'builtin :language 'clojure - `(((list_lit meta: _ :? :anchor (sym_lit !namespace name: (sym_name) @font-lock-keyword-face)) + `(((list_lit meta: _ :* :anchor (sym_lit !namespace name: (sym_name) @font-lock-keyword-face)) (:match ,clojure-ts--builtin-symbol-regexp @font-lock-keyword-face)) - ((list_lit meta: _ :? :anchor + ((list_lit meta: _ :* :anchor (sym_lit namespace: ((sym_ns) @ns (:equal "clojure.core" @ns)) name: (sym_name) @font-lock-keyword-face)) @@ -608,6 +608,12 @@ This does not include the NODE's namespace." (let ((first-child (treesit-node-child node 0 t))) (treesit-node-child node (if (clojure-ts--metadata-node-p first-child) (1+ n) n) t))) +(defun clojure-ts--node-with-metadata-parent (node) + "Return parent for NODE only if NODE has metadata, otherwise returns nil." + (when-let* ((prev-sibling (treesit-node-prev-sibling node)) + ((clojure-ts--metadata-node-p prev-sibling))) + (treesit-node-parent (treesit-node-parent node)))) + (defun clojure-ts--symbol-matches-p (symbol-regexp node) "Return non-nil if NODE is a symbol that matches SYMBOL-REGEXP." (and (clojure-ts--symbol-node-p node) @@ -977,6 +983,30 @@ forms like deftype, defrecord, reify, proxy, etc." (and prev-sibling (clojure-ts--metadata-node-p prev-sibling)))) +(defun clojure-ts--anchor-parent-skip-metadata (_node parent _bol) + "Anchor function that returns position of PARENT start for NODE. + +If PARENT has optional metadata we skip it and return starting position +of the first child's opening paren. + +NOTE: This anchor is used to fix indentation issue for forms with type +hints." + (let ((first-child (treesit-node-child parent 0 t))) + (if (clojure-ts--metadata-node-p first-child) + ;; We don't need named node here + (treesit-node-start (treesit-node-child parent 1)) + (treesit-node-start parent)))) + +(defun clojure-ts--match-collection-item-with-metadata (node-type) + "Returns a matcher for a collection item with metadata by NODE-TYPE. + +The returned matcher accepts NODE, PARENT and BOL and returns true only +if NODE has metadata and its parent has type NODE-TYPE." + (lambda (node _parent _bol) + (string-equal node-type + (treesit-node-type + (clojure-ts--node-with-metadata-parent node))))) + (defun clojure-ts--semantic-indent-rules () "Return a list of indentation rules for `treesit-simple-indent-rules'." `((clojure @@ -984,11 +1014,23 @@ forms like deftype, defrecord, reify, proxy, etc." (clojure-ts--match-docstring parent 0) ;; https://guide.clojure.style/#body-indentation (clojure-ts--match-method-body parent 2) - (clojure-ts--match-form-body parent 2) + (clojure-ts--match-form-body clojure-ts--anchor-parent-skip-metadata 2) ;; https://guide.clojure.style/#threading-macros-alignment (clojure-ts--match-threading-macro-arg prev-sibling 0) ;; https://guide.clojure.style/#vertically-align-fn-args (clojure-ts--match-function-call-arg (nth-sibling 2 nil) 0) + ;; Collections items with metadata. + ;; + ;; This should be before `clojure-ts--match-with-metadata', otherwise they + ;; will never be matched. + (,(clojure-ts--match-collection-item-with-metadata "vec_lit") grand-parent 1) + (,(clojure-ts--match-collection-item-with-metadata "map_lit") grand-parent 1) + (,(clojure-ts--match-collection-item-with-metadata "set_lit") grand-parent 2) + ;; + ;; If we enable this rule for lists, it will break many things. + ;; (,(clojure-ts--match-collection-item-with-metadata "list_lit") grand-parent 1) + ;; + ;; All other forms with metadata. (clojure-ts--match-with-metadata parent 0) ;; Literal Sequences ((parent-is "list_lit") parent 1) ;; https://guide.clojure.style/#one-space-indent diff --git a/test/clojure-ts-mode-font-lock-test.el b/test/clojure-ts-mode-font-lock-test.el index c3cafd5..b6ea46c 100644 --- a/test/clojure-ts-mode-font-lock-test.el +++ b/test/clojure-ts-mode-font-lock-test.el @@ -162,4 +162,9 @@ DESCRIPTION is the description of the spec." (when-fontifying-it "non-built-ins-with-same-name" ("(h/for query {})" (2 2 font-lock-type-face) - (4 6 nil)))) + (4 6 nil))) + + (when-fontifying-it "special-forms-with-metadata" + ("^long (if true 1 2)" + (2 5 font-lock-type-face) + (8 9 font-lock-keyword-face)))) diff --git a/test/clojure-ts-mode-indentation-test.el b/test/clojure-ts-mode-indentation-test.el index 23a432b..8375f80 100644 --- a/test/clojure-ts-mode-indentation-test.el +++ b/test/clojure-ts-mode-indentation-test.el @@ -239,4 +239,29 @@ DESCRIPTION is a string with the description of the spec." (= x y) 2 3 4 5 - 6 6)")))) + 6 6)"))) + +(it "should indent collections elements with metadata correctly" + " +(def x + [a b [c ^:foo + d + e]])" + + " +#{x + y ^:foo + z}" + + " +{:hello ^:foo + \"world\" + :foo + \"bar\"}") + +(it "should indent body of special forms correctly considering metadata" + " +(let [result ^long + (if true + 1 + 2)])")) diff --git a/test/samples/indentation.clj b/test/samples/indentation.clj index 78a7aa6..79d7809 100644 --- a/test/samples/indentation.clj +++ b/test/samples/indentation.clj @@ -210,3 +210,59 @@ (close [this] (is properly indented))) + +(def x + [a b [c ^:foo + d + e]]) + +#{x + y ^:foo + z} + +{:hello ^:foo + "world" + :foo + "bar"} + +;; NOTE: List elements with metadata are not indented correctly. +'(one + two ^:foo + three) + +^{:nextjournal.clerk/visibility {:code :hide}} +(defn actual + [args]) + +(def ^:private hello + "World") + +;; A few examples from clojure core. + +;; NOTE: This one is not indented correctly, I'm keeping it here as a reminder +;; to fix it later. +(defonce ^:dynamic + ^{:private true + :doc "A ref to a sorted set of symbols representing loaded libs"} + *loaded-libs* (ref (sorted-set))) + +(defn index-of + "Return index of value (string or char) in s, optionally searching + forward from from-index. Return nil if value not found." + {:added "1.8"} + ([^CharSequence s value] + (let [result ^long + (if (instance? Character value) + (.indexOf (.toString s) ^int (.charValue ^Character value)) + (.indexOf (.toString s) ^String value))] + (if (= result -1) + nil + result))) + ([^CharSequence s value ^long from-index] + (let [result ^long + (if (instance? Character value) + (.indexOf (.toString s) ^int (.charValue ^Character value) (unchecked-int from-index)) + (.indexOf (.toString s) ^String value (unchecked-int from-index)))] + (if (= result -1) + nil + result)))) From 9ae1f42f9648ecac187266bf65a5d8993e6c0765 Mon Sep 17 00:00:00 2001 From: Roman Rudakov Date: Thu, 3 Apr 2025 18:51:09 +0200 Subject: [PATCH 292/379] Basic support for dynamic indentation --- CHANGELOG.md | 1 + clojure-ts-mode.el | 46 ++++++++++++++---- test/clojure-ts-mode-indentation-test.el | 59 +++++++++++++++++++++++- 3 files changed, 97 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8d7953f..f41749f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ - Introduce `clojure-ts-semantic-indent-rules` customization option. - [#61](https://github.com/clojure-emacs/clojure-ts-mode/issues/61): Fix issue with indentation of collection items with metadata. - Proper syntax highlighting for expressions with metadata. +- Add basic support for dynamic indentation via `clojure-ts-get-indent-function`. ## 0.2.3 (2025-03-04) diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index d0fa310..e7198b4 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -871,6 +871,34 @@ indented.)" (when-let* ((node-after-bol (treesit-node-first-child-for-pos parent bol))) (> (treesit-node-index node-after-bol) (1+ block))))) +(defvar clojure-ts-get-indent-function nil + "Function to get the indent spec of a symbol. + +This function should take one argument, the name of the symbol as a +string. This name will be exactly as it appears in the buffer, so it +might start with a namespace alias. + +The returned value is expected to be the same as +`clojure-get-indent-function' from `clojure-mode' for compatibility +reasons.") + +(defun clojure-ts--dynamic-indent-for-symbol (symbol-name) + "Return dynamic indentation spec for SYMBOL-NAME if found. + +If function `clojure-ts-get-indent-function' is not nil, call it and +produce a valid indentation spec from the returned value. + +The indentation rules for `clojure-ts-mode' are simpler than for +`clojure-mode' so we only take the first integer N and produce `(:block +N)' rule. If an integer cannot be found, this function returns nil and +the default rule is used." + (when (functionp clojure-ts-get-indent-function) + (let ((spec (funcall clojure-ts-get-indent-function symbol-name))) + (if (consp spec) + `(:block ,(car spec)) + (when (integerp spec) + `(:block ,spec)))))) + (defun clojure-ts--match-form-body (node parent bol) "Match if NODE has to be indented as a for body. @@ -879,14 +907,16 @@ indentation rule in `clojure-ts--semantic-indent-rules-defaults' or `clojure-ts-semantic-indent-rules' check if NODE should be indented according to the rule. If NODE is nil, use next node after BOL." (and (clojure-ts--list-node-p parent) - (let ((first-child (clojure-ts--node-child-skip-metadata parent 0))) - (when-let* ((rule (alist-get (clojure-ts--named-node-text first-child) - (seq-union clojure-ts-semantic-indent-rules - clojure-ts--semantic-indent-rules-defaults - (lambda (e1 e2) (equal (car e1) (car e2)))) - nil - nil - #'equal))) + (let* ((first-child (clojure-ts--node-child-skip-metadata parent 0)) + (symbol-name (clojure-ts--named-node-text first-child))) + (when-let* ((rule (or (clojure-ts--dynamic-indent-for-symbol symbol-name) + (alist-get symbol-name + (seq-union clojure-ts-semantic-indent-rules + clojure-ts--semantic-indent-rules-defaults + (lambda (e1 e2) (equal (car e1) (car e2)))) + nil + nil + #'equal)))) (and (not (clojure-ts--match-with-metadata node)) (let ((rule-type (car rule)) (rule-value (cadr rule))) diff --git a/test/clojure-ts-mode-indentation-test.el b/test/clojure-ts-mode-indentation-test.el index 8375f80..605ec76 100644 --- a/test/clojure-ts-mode-indentation-test.el +++ b/test/clojure-ts-mode-indentation-test.el @@ -89,6 +89,16 @@ DESCRIPTION is a string with the description of the spec." (2 font-lock-function-name-face)))) +;; Mock `cider--get-symbol-indent' function + +(defun cider--get-symbol-indent-mock (symbol-name) + "Returns static mocked indentation specs for SYMBOL-NAME if available." + (when (stringp symbol-name) + (cond + ((string-equal symbol-name "my-with-in-str") 1) + ((string-equal symbol-name "my-letfn") '(1 ((:defn) (:form))))))) + + (describe "indentation" (it "should not hang on end of buffer" (with-clojure-ts-buffer "(let [a b]" @@ -264,4 +274,51 @@ DESCRIPTION is a string with the description of the spec." (let [result ^long (if true 1 - 2)])")) + 2)])") + +(it "should pick up dynamic indentation rules from clojure-ts-get-indent-function" + (with-clojure-ts-buffer " +(defmacro my-with-in-str + \"[DOCSTRING]\" + {:style/indent 1} + [s & body] + ~@body) + +(my-with-in-str \"34\" +(prompt \"How old are you?\"))" + (setq-local clojure-ts-get-indent-function #'cider--get-symbol-indent-mock) + (indent-region (point-min) (point-max)) + (expect (buffer-string) :to-equal " +(defmacro my-with-in-str + \"[DOCSTRING]\" + {:style/indent 1} + [s & body] + ~@body) + +(my-with-in-str \"34\" + (prompt \"How old are you?\"))")) + + (with-clojure-ts-buffer " +(defmacro my-letfn + \"[DOCSTRING]\" + {:style/indent [1 [[:defn]] :form]} + [fnspecs & body] + ~@body) + +(my-letfn [(twice [x] (* x 2)) + (six-times [y] (* (twice y) 3))] +(println \"Twice 15 =\" (twice 15)) +(println \"Six times 15 =\" (six-times 15)))" + (setq-local clojure-ts-get-indent-function #'cider--get-symbol-indent-mock) + (indent-region (point-min) (point-max)) + (expect (buffer-string) :to-equal " +(defmacro my-letfn + \"[DOCSTRING]\" + {:style/indent [1 [[:defn]] :form]} + [fnspecs & body] + ~@body) + +(my-letfn [(twice [x] (* x 2)) + (six-times [y] (* (twice y) 3))] + (println \"Twice 15 =\" (twice 15)) + (println \"Six times 15 =\" (six-times 15)))")))) From c8286e23e20ac2bc4d08664578d22ea2c2bb172f Mon Sep 17 00:00:00 2001 From: Roman Rudakov Date: Fri, 4 Apr 2025 18:38:48 +0200 Subject: [PATCH 293/379] Support nested indentation rules --- CHANGELOG.md | 1 + README.md | 21 +- clojure-ts-mode.el | 257 +++++++++++++---------- test/clojure-ts-mode-indentation-test.el | 16 +- test/samples/indentation.clj | 23 +- 5 files changed, 192 insertions(+), 126 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f41749f..71cd655 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ - [#61](https://github.com/clojure-emacs/clojure-ts-mode/issues/61): Fix issue with indentation of collection items with metadata. - Proper syntax highlighting for expressions with metadata. - Add basic support for dynamic indentation via `clojure-ts-get-indent-function`. +- Add support for nested indentation rules. ## 0.2.3 (2025-03-04) diff --git a/README.md b/README.md index b1aa3cf..cad93f3 100644 --- a/README.md +++ b/README.md @@ -188,8 +188,8 @@ your personal config. Let's assume you want to indent `->>` and `->` like this: You can do so by putting the following in your config: ```emacs-lisp -(setopt clojure-ts-semantic-indent-rules '(("->" . (:block 1)) - ("->>" . (:block 1)))) +(setopt clojure-ts-semantic-indent-rules '(("->" . ((:block 1))) + ("->>" . ((:block 1))))) ``` This means that the body of the `->`/`->>` is after the first argument. @@ -198,16 +198,17 @@ The default set of rules is defined as `clojure-ts--semantic-indent-rules-defaults`, any rule can be overridden using customization option. -There are 2 types of rules supported: `:block` and `:inner`, similarly to -cljfmt. If rule is defined as `:block n`, `n` means a number of arguments after -which begins the body. If rule is defined as `:inner n`, each form in the body -is indented with 2 spaces regardless of `n` value (currently all default rules -has 0 value). +Two types of rules are supported: `:block` and `:inner`, mirroring those in +cljfmt. When a rule is defined as `:block n`, `n` represents the number of +arguments preceding the body. When a rule is defined as `:inner n`, each form +within the expression's body, nested `n` levels deep, is indented by two +spaces. These rule definitions fully reflect the [cljfmt rules](https://github.com/weavejester/cljfmt/blob/0.13.0/docs/INDENTS.md). For example: -- `do` has a rule `:block 0`. -- `when` has a rule `:block 1`. -- `defn` and `fn` have a rule `:inner 0`. +- `do` has a rule `((:block 0))`. +- `when` has a rule `((:block 1))`. +- `defn` and `fn` have a rule `((:inner 0))`. +- `letfn` has a rule `((:block 1) (:inner 2 0))`. ### Font Locking diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index e7198b4..1cfa6bd 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -137,9 +137,12 @@ Default set of rules is defined in `clojure-ts--semantic-indent-rules-defaults'." :safe #'listp :type '(alist :key-type string - :value-type (list (choice (const :tag "Block indentation rule" :block) - (const :tag "Inner indentation rule" :inner)) - integer)) + :value-type (repeat (choice (list (choice (const :tag "Block indentation rule" :block) + (const :tag "Inner indentation rule" :inner)) + integer) + (list (const :tag "Inner indentation rule" :inner) + integer + integer)))) :package-version '(clojure-ts-mode . "0.2.4")) (defvar clojure-ts-mode-remappings @@ -769,74 +772,73 @@ The possible values for this variable are ((parent-is "set_lit") parent 2)))) (defvar clojure-ts--semantic-indent-rules-defaults - '(("alt!" . (:block 0)) - ("alt!!" . (:block 0)) - ("comment" . (:block 0)) - ("cond" . (:block 0)) - ("delay" . (:block 0)) - ("do" . (:block 0)) - ("finally" . (:block 0)) - ("future" . (:block 0)) - ("go" . (:block 0)) - ("thread" . (:block 0)) - ("try" . (:block 0)) - ("with-out-str" . (:block 0)) - ("defprotocol" . (:block 1)) - ("binding" . (:block 1)) - ("defprotocol" . (:block 1)) - ("binding" . (:block 1)) - ("case" . (:block 1)) - ("cond->" . (:block 1)) - ("cond->>" . (:block 1)) - ("doseq" . (:block 1)) - ("dotimes" . (:block 1)) - ("doto" . (:block 1)) - ("extend" . (:block 1)) - ("extend-protocol" . (:block 1)) - ("extend-type" . (:block 1)) - ("for" . (:block 1)) - ("go-loop" . (:block 1)) - ("if" . (:block 1)) - ("if-let" . (:block 1)) - ("if-not" . (:block 1)) - ("if-some" . (:block 1)) - ("let" . (:block 1)) - ("letfn" . (:block 1)) - ("locking" . (:block 1)) - ("loop" . (:block 1)) - ("match" . (:block 1)) - ("ns" . (:block 1)) - ("struct-map" . (:block 1)) - ("testing" . (:block 1)) - ("when" . (:block 1)) - ("when-first" . (:block 1)) - ("when-let" . (:block 1)) - ("when-not" . (:block 1)) - ("when-some" . (:block 1)) - ("while" . (:block 1)) - ("with-local-vars" . (:block 1)) - ("with-open" . (:block 1)) - ("with-precision" . (:block 1)) - ("with-redefs" . (:block 1)) - ("defrecord" . (:block 2)) - ("deftype" . (:block 2)) - ("are" . (:block 2)) - ("as->" . (:block 2)) - ("catch" . (:block 2)) - ("condp" . (:block 2)) - ("bound-fn" . (:inner 0)) - ("def" . (:inner 0)) - ("defmacro" . (:inner 0)) - ("defmethod" . (:inner 0)) - ("defmulti" . (:inner 0)) - ("defn" . (:inner 0)) - ("defn-" . (:inner 0)) - ("defonce" . (:inner 0)) - ("deftest" . (:inner 0)) - ("fdef" . (:inner 0)) - ("fn" . (:inner 0)) - ("reify" . (:inner 0)) - ("use-fixtures" . (:inner 0))) + '(("alt!" . ((:block 0))) + ("alt!!" . ((:block 0))) + ("comment" . ((:block 0))) + ("cond" . ((:block 0))) + ("delay" . ((:block 0))) + ("do" . ((:block 0))) + ("finally" . ((:block 0))) + ("future" . ((:block 0))) + ("go" . ((:block 0))) + ("thread" . ((:block 0))) + ("try" . ((:block 0))) + ("with-out-str" . ((:block 0))) + ("defprotocol" . ((:block 1) (:inner 1))) + ("binding" . ((:block 1))) + ("case" . ((:block 1))) + ("cond->" . ((:block 1))) + ("cond->>" . ((:block 1))) + ("doseq" . ((:block 1))) + ("dotimes" . ((:block 1))) + ("doto" . ((:block 1))) + ("extend" . ((:block 1))) + ("extend-protocol" . ((:block 1) (:inner 1))) + ("extend-type" . ((:block 1) (:inner 1))) + ("for" . ((:block 1))) + ("go-loop" . ((:block 1))) + ("if" . ((:block 1))) + ("if-let" . ((:block 1))) + ("if-not" . ((:block 1))) + ("if-some" . ((:block 1))) + ("let" . ((:block 1))) + ("letfn" . ((:block 1) (:inner 2 0))) + ("locking" . ((:block 1))) + ("loop" . ((:block 1))) + ("match" . ((:block 1))) + ("ns" . ((:block 1))) + ("struct-map" . ((:block 1))) + ("testing" . ((:block 1))) + ("when" . ((:block 1))) + ("when-first" . ((:block 1))) + ("when-let" . ((:block 1))) + ("when-not" . ((:block 1))) + ("when-some" . ((:block 1))) + ("while" . ((:block 1))) + ("with-local-vars" . ((:block 1))) + ("with-open" . ((:block 1))) + ("with-precision" . ((:block 1))) + ("with-redefs" . ((:block 1))) + ("defrecord" . ((:block 2) (:inner 1))) + ("deftype" . ((:block 2) (:inner 1))) + ("are" . ((:block 2))) + ("as->" . ((:block 2))) + ("catch" . ((:block 2))) + ("condp" . ((:block 2))) + ("bound-fn" . ((:inner 0))) + ("def" . ((:inner 0))) + ("defmacro" . ((:inner 0))) + ("defmethod" . ((:inner 0))) + ("defmulti" . ((:inner 0))) + ("defn" . ((:inner 0))) + ("defn-" . ((:inner 0))) + ("defonce" . ((:inner 0))) + ("deftest" . ((:inner 0))) + ("fdef" . ((:inner 0))) + ("fn" . ((:inner 0))) + ("reify" . ((:inner 0) (:inner 1))) + ("proxy" . ((:block 2) (:inner 1))) + ("use-fixtures" . ((:inner 0)))) "Default semantic indentation rules. The format reflects cljfmt indentation rules. All the default rules are @@ -882,22 +884,87 @@ The returned value is expected to be the same as `clojure-get-indent-function' from `clojure-mode' for compatibility reasons.") +(defun clojure-ts--unwrap-dynamic-spec (spec current-depth) + "Recursively unwrap SPEC, incrementally increasing the CURRENT-DEPTH. + +This function accepts a list SPEC, like ((:defn)) and produce a proper +indent rule. For example, ((:defn)) is converted to (:inner 2), +and (:defn) is converted to (:inner 1)." + (if (consp spec) + (clojure-ts--unwrap-dynamic-spec (car spec) (1+ current-depth)) + (cond + ((equal spec :defn) (list :inner current-depth)) + (t nil)))) + (defun clojure-ts--dynamic-indent-for-symbol (symbol-name) - "Return dynamic indentation spec for SYMBOL-NAME if found. + "Returns the dynamic indentation specification for SYMBOL-NAME, if found. + +If the function `clojure-ts-get-indent-function' is defined, call it and +produce a valid indentation specification from its return value. -If function `clojure-ts-get-indent-function' is not nil, call it and -produce a valid indentation spec from the returned value. +The `clojure-ts-get-indent-function' should return an indentation +specification compatible with `clojure-mode', which will then be +converted to a suitable `clojure-ts-mode' specification. -The indentation rules for `clojure-ts-mode' are simpler than for -`clojure-mode' so we only take the first integer N and produce `(:block -N)' rule. If an integer cannot be found, this function returns nil and -the default rule is used." +For example, (1 ((:defn)) nil) is converted to ((:block 1) (:inner 2))." (when (functionp clojure-ts-get-indent-function) (let ((spec (funcall clojure-ts-get-indent-function symbol-name))) - (if (consp spec) - `(:block ,(car spec)) - (when (integerp spec) - `(:block ,spec)))))) + (if (integerp spec) + (list (list :block spec)) + (when (sequencep spec) + (thread-last spec + (seq-map (lambda (el) + (cond + ((integerp el) (list :block el)) + ((equal el :defn) (list :inner 0)) + ((consp el) (clojure-ts--unwrap-dynamic-spec el 0)) + (t nil)))) + (seq-remove #'null) + ;; Always put `:block' to the beginning. + (seq-sort (lambda (spec1 _spec2) + (equal (car spec1) :block))))))))) + +(defun clojure-ts--find-semantic-rule (node parent current-depth) + "Returns a suitable indentation rule for NODE, considering the CURRENT-DEPTH. + +Attempts to find an indentation rule by examining the symbol name of the +PARENT's first child. If a rule is not found, it navigates up the +syntax tree and recursively attempts to find a rule, incrementally +increasing the CURRENT-DEPTH. If a rule is not found upon reaching the +root of the syntax tree, it returns nil. A rule is considered a match +only if the CURRENT-DEPTH matches the rule's required depth." + (let* ((first-child (clojure-ts--node-child-skip-metadata parent 0)) + (symbol-name (clojure-ts--named-node-text first-child)) + (idx (- (treesit-node-index node) 2))) + (if-let* ((rule-set (or (clojure-ts--dynamic-indent-for-symbol symbol-name) + (alist-get symbol-name + (seq-union clojure-ts-semantic-indent-rules + clojure-ts--semantic-indent-rules-defaults + (lambda (e1 e2) (equal (car e1) (car e2)))) + nil + nil + #'equal)))) + (if (zerop current-depth) + (let ((rule (car rule-set))) + (if (equal (car rule) :block) + rule + (pcase-let ((`(,_ ,rule-depth ,rule-idx) rule)) + (when (and (equal rule-depth current-depth) + (or (null rule-idx) + (equal rule-idx idx))) + rule)))) + (thread-last rule-set + (seq-filter (lambda (rule) + (pcase-let ((`(,rule-type ,rule-depth ,rule-idx) rule)) + (and (equal rule-type :inner) + (equal rule-depth current-depth) + (or (null rule-idx) + (equal rule-idx idx)))))) + (seq-first))) + (when-let* ((new-parent (treesit-node-parent parent))) + (clojure-ts--find-semantic-rule parent + new-parent + (1+ current-depth)))))) (defun clojure-ts--match-form-body (node parent bol) "Match if NODE has to be indented as a for body. @@ -907,16 +974,8 @@ indentation rule in `clojure-ts--semantic-indent-rules-defaults' or `clojure-ts-semantic-indent-rules' check if NODE should be indented according to the rule. If NODE is nil, use next node after BOL." (and (clojure-ts--list-node-p parent) - (let* ((first-child (clojure-ts--node-child-skip-metadata parent 0)) - (symbol-name (clojure-ts--named-node-text first-child))) - (when-let* ((rule (or (clojure-ts--dynamic-indent-for-symbol symbol-name) - (alist-get symbol-name - (seq-union clojure-ts-semantic-indent-rules - clojure-ts--semantic-indent-rules-defaults - (lambda (e1 e2) (equal (car e1) (car e2)))) - nil - nil - #'equal)))) + (let* ((first-child (clojure-ts--node-child-skip-metadata parent 0))) + (when-let* ((rule (clojure-ts--find-semantic-rule node parent 0))) (and (not (clojure-ts--match-with-metadata node)) (let ((rule-type (car rule)) (rule-value (cadr rule))) @@ -940,19 +999,6 @@ according to the rule. If NODE is nil, use next node after BOL." (clojure-ts--keyword-node-p first-child) (clojure-ts--var-node-p first-child))))) -(defun clojure-ts--match-method-body (_node parent _bol) - "Matches a `NODE' in the body of a `PARENT' method implementation. -A method implementation referes to concrete implementations being defined in -forms like deftype, defrecord, reify, proxy, etc." - (and - (clojure-ts--list-node-p parent) - (let* ((grandparent (treesit-node-parent parent)) - ;; auncle: gender neutral sibling of parent, aka child of grandparent - (first-auncle (treesit-node-child grandparent 0 t))) - (and (clojure-ts--list-node-p grandparent) - (clojure-ts--symbol-matches-p clojure-ts--type-symbol-regexp - first-auncle))))) - (defvar clojure-ts--threading-macro (eval-and-compile (rx (and "->" (? ">") line-end))) @@ -1043,7 +1089,6 @@ if NODE has metadata and its parent has type NODE-TYPE." ((parent-is "source") parent-bol 0) (clojure-ts--match-docstring parent 0) ;; https://guide.clojure.style/#body-indentation - (clojure-ts--match-method-body parent 2) (clojure-ts--match-form-body clojure-ts--anchor-parent-skip-metadata 2) ;; https://guide.clojure.style/#threading-macros-alignment (clojure-ts--match-threading-macro-arg prev-sibling 0) diff --git a/test/clojure-ts-mode-indentation-test.el b/test/clojure-ts-mode-indentation-test.el index 605ec76..738f2a0 100644 --- a/test/clojure-ts-mode-indentation-test.el +++ b/test/clojure-ts-mode-indentation-test.el @@ -96,7 +96,7 @@ DESCRIPTION is a string with the description of the spec." (when (stringp symbol-name) (cond ((string-equal symbol-name "my-with-in-str") 1) - ((string-equal symbol-name "my-letfn") '(1 ((:defn) (:form))))))) + ((string-equal symbol-name "my-letfn") '(1 ((:defn)) :form))))) (describe "indentation" @@ -242,7 +242,7 @@ DESCRIPTION is a string with the description of the spec." 2 3 4 5 6 6)" - (setopt clojure-ts-semantic-indent-rules '(("are" . (:block 1)))) + (setopt clojure-ts-semantic-indent-rules '(("are" . ((:block 1))))) (indent-region (point-min) (point-max)) (expect (buffer-string) :to-equal " (are [x y] @@ -305,8 +305,10 @@ DESCRIPTION is a string with the description of the spec." [fnspecs & body] ~@body) -(my-letfn [(twice [x] (* x 2)) - (six-times [y] (* (twice y) 3))] +(my-letfn [(twice [x] + (* x 2)) + (six-times [y] + (* (twice y) 3))] (println \"Twice 15 =\" (twice 15)) (println \"Six times 15 =\" (six-times 15)))" (setq-local clojure-ts-get-indent-function #'cider--get-symbol-indent-mock) @@ -318,7 +320,9 @@ DESCRIPTION is a string with the description of the spec." [fnspecs & body] ~@body) -(my-letfn [(twice [x] (* x 2)) - (six-times [y] (* (twice y) 3))] +(my-letfn [(twice [x] + (* x 2)) + (six-times [y] + (* (twice y) 3))] (println \"Twice 15 =\" (twice 15)) (println \"Six times 15 =\" (six-times 15)))")))) diff --git a/test/samples/indentation.clj b/test/samples/indentation.clj index 79d7809..53e8269 100644 --- a/test/samples/indentation.clj +++ b/test/samples/indentation.clj @@ -89,11 +89,13 @@ (foo [this x] x)) -(defrecord MyThingR [] +(defrecord MyThingR + [] IProto - (foo [this x] x)) + (foo [this x] + x)) -(defn foo2 [x]b) +(defn foo2 [x] b) (reify IProto @@ -102,7 +104,8 @@ (extend-type MyThing clojure.lang.IFn - (invoke [this] 1)) + (invoke [this] + 1)) (extend-protocol clojure.lang.IFn MyThingR @@ -266,3 +269,15 @@ (if (= result -1) nil result)))) + +;; Nested rules + +(letfn [(add [x y] + (+ x y)) + (hello [user] + (println "Hello" user))] + (let [x 2 + y 3 + user "John Doe"] + (dotimes [_ (add x y)] + (hello user)))) From c08e2dda2cce7b9c1897076de556fabb99c4c194 Mon Sep 17 00:00:00 2001 From: Roman Rudakov Date: Sat, 5 Apr 2025 09:25:39 +0200 Subject: [PATCH 294/379] Properly highlight function name in letfn form --- CHANGELOG.md | 15 +++---- clojure-ts-mode.el | 9 ++++- test/clojure-ts-mode-font-lock-test.el | 54 +++++++++++++++++++++++++- test/samples/test.clj | 8 ++++ 4 files changed, 77 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 71cd655..69b63d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,19 +3,20 @@ ## main (unreleased) - [#62](https://github.com/clojure-emacs/clojure-ts-mode/issues/62): Define `list` "thing" to improve navigation in Emacs 31. -- [#64]: Add defcustom `clojure-ts-auto-remap` to control remapping of `clojure-mode` buffers. -- Improve syntax highlighting: +- [#64](https://github.com/clojure-emacs/clojure-ts-mode/pull/64): Add defcustom `clojure-ts-auto-remap` to control remapping of `clojure-mode` buffers. +- [#66](https://github.com/clojure-emacs/clojure-ts-mode/pull/66): Improve syntax highlighting: - Highlight metadata with single keyword with `clojure-ts-keyword-face`. - Only highlight built-ins from `clojure.core` namespace. - Highlight named lambda functions properly. - Fix syntax highlighting for functions and vars with metadata on the previous line. -- Improve semantic indentation rules to be more consistent with cljfmt. -- Introduce `clojure-ts-semantic-indent-rules` customization option. +- [#67](https://github.com/clojure-emacs/clojure-ts-mode/pull/67): Improve semantic indentation rules to be more consistent with cljfmt. +- [#67](https://github.com/clojure-emacs/clojure-ts-mode/pull/67): Introduce `clojure-ts-semantic-indent-rules` customization option. - [#61](https://github.com/clojure-emacs/clojure-ts-mode/issues/61): Fix issue with indentation of collection items with metadata. -- Proper syntax highlighting for expressions with metadata. -- Add basic support for dynamic indentation via `clojure-ts-get-indent-function`. -- Add support for nested indentation rules. +- [#68](https://github.com/clojure-emacs/clojure-ts-mode/pull/68): Proper syntax highlighting for expressions with metadata. +- [#69](https://github.com/clojure-emacs/clojure-ts-mode/pull/69): Add basic support for dynamic indentation via `clojure-ts-get-indent-function`. +- [#70](https://github.com/clojure-emacs/clojure-ts-mode/pull/70): Add support for nested indentation rules. +- [#71](https://github.com/clojure-emacs/clojure-ts-mode/pull/71): Properly highlight function name in `letfn` form. ## 0.2.3 (2025-03-04) diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index 1cfa6bd..8862265 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -473,7 +473,14 @@ with the markdown_inline grammar." ((sym_lit name: (sym_name) @def) ((:equal "reify" @def))) (list_lit - (sym_lit name: (sym_name) @font-lock-function-name-face))))) + (sym_lit name: (sym_name) @font-lock-function-name-face)))) + ;; letfn + ((list_lit + ((sym_lit name: (sym_name) @symbol) + ((:equal "letfn" @symbol))) + (vec_lit + (list_lit + (sym_lit name: (sym_name) @font-lock-function-name-face)))))) :feature 'variable ;; def, defonce :language 'clojure diff --git a/test/clojure-ts-mode-font-lock-test.el b/test/clojure-ts-mode-font-lock-test.el index b6ea46c..02e0fa4 100644 --- a/test/clojure-ts-mode-font-lock-test.el +++ b/test/clojure-ts-mode-font-lock-test.el @@ -167,4 +167,56 @@ DESCRIPTION is the description of the spec." (when-fontifying-it "special-forms-with-metadata" ("^long (if true 1 2)" (2 5 font-lock-type-face) - (8 9 font-lock-keyword-face)))) + (8 9 font-lock-keyword-face))) + + (when-fontifying-it "should highlight function name in all known forms" + ("(letfn [(add [x y] + (+ x y)) + (hello [user] + (println \"Hello\" user))] + (dotimes [_ (add 6 8)] + (hello \"John Doe\")))" + (2 6 font-lock-keyword-face) + (10 12 font-lock-function-name-face) + (48 52 font-lock-function-name-face)) + + ("(reify + AutoCloseable + (close [this] (.close this)))" + (2 6 font-lock-keyword-face) + (27 31 font-lock-function-name-face)) + + ("(defrecord TestRecord [field] + AutoCloseable + (close [this] + (.close this)))" + (2 10 font-lock-keyword-face) + (12 21 font-lock-type-face) + (50 54 font-lock-function-name-face)) + + ("(definterface MyInterface + (^String name []) + (^double mass []))" + (2 13 font-lock-keyword-face) + (15 25 font-lock-type-face) + (31 36 font-lock-type-face) + (38 41 font-lock-function-name-face) + (51 56 font-lock-type-face) + (58 61 font-lock-function-name-face)) + + ("(deftype ImageSelection [data] + Transferable + (getTransferDataFlavors + [this] + (into-array DataFlavor [DataFlavor/imageFlavor])))" + (2 8 font-lock-keyword-face) + (10 23 font-lock-type-face) + (50 71 font-lock-function-name-face)) + + ("(defprotocol P + (foo [this]) + (bar-me [this] [this y]))" + (2 12 font-lock-keyword-face) + (14 14 font-lock-type-face) + (19 21 font-lock-function-name-face) + (34 39 font-lock-function-name-face)))) diff --git a/test/samples/test.clj b/test/samples/test.clj index a8a1ae9..842ff5a 100644 --- a/test/samples/test.clj +++ b/test/samples/test.clj @@ -27,6 +27,14 @@ (clojure.core/for []) (honey.sql/for {}) +;; the "add" and "hello" should both have a function name face +(letfn [(add [x y] + (+ x y)) + (hello [user] + (println "Hello" user))] + (dotimes [_ (add 6 8)] + (hello "John Doe"))) + ;; the myfn sexp should have a comment face (mysfn 101 foo From 5243649fb6b40d1c9c668fa7ec550063212231d5 Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Sat, 5 Apr 2025 11:01:26 +0300 Subject: [PATCH 295/379] Bump the development version --- clojure-ts-mode.el | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index 8862265..3cfe8fc 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -6,7 +6,7 @@ ;; Maintainer: Danny Freeman ;; URL: http://github.com/clojure-emacs/clojure-ts-mode ;; Keywords: languages clojure clojurescript lisp -;; Version: 0.2.4-snapshot +;; Version: 0.3.0-snapshot ;; Package-Requires: ((emacs "30.1")) ;; This file is not part of GNU Emacs. @@ -71,7 +71,7 @@ :link '(emacs-commentary-link :tag "Commentary" "clojure-mode")) (defconst clojure-ts-mode-version - "0.2.4-snapshot" + "0.3.0-snapshot" "The current version of `clojure-ts-mode'.") (defcustom clojure-ts-comment-macro-font-lock-body nil @@ -123,7 +123,7 @@ double quotes on the third column." "When non-nil, redirect all `clojure-mode' buffers to `clojure-ts-mode'." :safe #'booleanp :type 'boolean - :package-version '(clojure-ts-mode . "0.2.4")) + :package-version '(clojure-ts-mode . "0.3")) (defcustom clojure-ts-semantic-indent-rules nil "Custom rules to extend default indentation rules for `semantic' style. @@ -143,7 +143,7 @@ Default set of rules is defined in (list (const :tag "Inner indentation rule" :inner) integer integer)))) - :package-version '(clojure-ts-mode . "0.2.4")) + :package-version '(clojure-ts-mode . "0.3")) (defvar clojure-ts-mode-remappings '((clojure-mode . clojure-ts-mode) From 606740ac2987c8cc98314080e9f31e6cb8708ce3 Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Sat, 5 Apr 2025 11:04:28 +0300 Subject: [PATCH 296/379] Fix docstring --- clojure-ts-mode.el | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index 3cfe8fc..3e67f8d 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -619,7 +619,7 @@ This does not include the NODE's namespace." (treesit-node-child node (if (clojure-ts--metadata-node-p first-child) (1+ n) n) t))) (defun clojure-ts--node-with-metadata-parent (node) - "Return parent for NODE only if NODE has metadata, otherwise returns nil." + "Return parent for NODE only if NODE has metadata, otherwise return nil." (when-let* ((prev-sibling (treesit-node-prev-sibling node)) ((clojure-ts--metadata-node-p prev-sibling))) (treesit-node-parent (treesit-node-parent node)))) From 7792ef270436456cb18398109cdaf1a4aa0044a7 Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Sat, 5 Apr 2025 11:07:06 +0300 Subject: [PATCH 297/379] Tweak code style --- clojure-ts-mode.el | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index 3e67f8d..03a722b 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -614,9 +614,15 @@ This does not include the NODE's namespace." (string-equal expected-symbol-name (clojure-ts--named-node-text node)))) (defun clojure-ts--node-child-skip-metadata (node n) - "Return the Nth child of NODE like `treesit-node-child`, skipping the optional metadata node at pos 0 if present." + "Return the Nth child of NODE like `treesit-node-child', sans metadata. +Skip the optional metadata node at pos 0 if present." (let ((first-child (treesit-node-child node 0 t))) - (treesit-node-child node (if (clojure-ts--metadata-node-p first-child) (1+ n) n) t))) + (treesit-node-child + node + (if (clojure-ts--metadata-node-p first-child) + (1+ n) + n) + t))) (defun clojure-ts--node-with-metadata-parent (node) "Return parent for NODE only if NODE has metadata, otherwise return nil." From c21395023463a7d0adbe36c320ea54733c412849 Mon Sep 17 00:00:00 2001 From: Roman Rudakov Date: Sat, 5 Apr 2025 10:49:42 +0200 Subject: [PATCH 298/379] Pass fully qualified symbol to clojure-ts-get-indent-function --- CHANGELOG.md | 1 + clojure-ts-mode.el | 23 ++++++++++++++++++----- test/clojure-ts-mode-indentation-test.el | 18 +++++++++--------- 3 files changed, 28 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 69b63d3..2135331 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ - [#69](https://github.com/clojure-emacs/clojure-ts-mode/pull/69): Add basic support for dynamic indentation via `clojure-ts-get-indent-function`. - [#70](https://github.com/clojure-emacs/clojure-ts-mode/pull/70): Add support for nested indentation rules. - [#71](https://github.com/clojure-emacs/clojure-ts-mode/pull/71): Properly highlight function name in `letfn` form. +- [#72](https://github.com/clojure-emacs/clojure-ts-mode/pull/72): Pass fully qualified symbol to `clojure-ts-get-indent-function`. ## 0.2.3 (2025-03-04) diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index 03a722b..a8450bc 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -608,6 +608,12 @@ with the markdown_inline grammar." This does not include the NODE's namespace." (treesit-node-text (treesit-node-child-by-field-name node "name"))) +(defun clojure-ts--node-namespace-text (node) + "Gets the namespace of a symbol or keyword NODE. + +If there is no namespace, returns nil." + (treesit-node-text (treesit-node-child-by-field-name node "namespace"))) + (defun clojure-ts--symbol-named-p (expected-symbol-name node) "Return non-nil if NODE is a symbol with text matching EXPECTED-SYMBOL-NAME." (and (clojure-ts--symbol-node-p node) @@ -909,8 +915,8 @@ and (:defn) is converted to (:inner 1)." ((equal spec :defn) (list :inner current-depth)) (t nil)))) -(defun clojure-ts--dynamic-indent-for-symbol (symbol-name) - "Returns the dynamic indentation specification for SYMBOL-NAME, if found. +(defun clojure-ts--dynamic-indent-for-symbol (sym &optional ns) + "Returns the dynamic indentation specification for SYM, if found. If the function `clojure-ts-get-indent-function' is defined, call it and produce a valid indentation specification from its return value. @@ -919,9 +925,15 @@ The `clojure-ts-get-indent-function' should return an indentation specification compatible with `clojure-mode', which will then be converted to a suitable `clojure-ts-mode' specification. -For example, (1 ((:defn)) nil) is converted to ((:block 1) (:inner 2))." +For example, (1 ((:defn)) nil) is converted to ((:block 1) (:inner 2)). + +If NS is defined, then the fully qualified symbol is passed to +`clojure-ts-get-indent-function'." (when (functionp clojure-ts-get-indent-function) - (let ((spec (funcall clojure-ts-get-indent-function symbol-name))) + (let* ((full-symbol (if ns + (concat ns "/" sym) + sym)) + (spec (funcall clojure-ts-get-indent-function full-symbol))) (if (integerp spec) (list (list :block spec)) (when (sequencep spec) @@ -948,8 +960,9 @@ root of the syntax tree, it returns nil. A rule is considered a match only if the CURRENT-DEPTH matches the rule's required depth." (let* ((first-child (clojure-ts--node-child-skip-metadata parent 0)) (symbol-name (clojure-ts--named-node-text first-child)) + (symbol-namespace (clojure-ts--node-namespace-text first-child)) (idx (- (treesit-node-index node) 2))) - (if-let* ((rule-set (or (clojure-ts--dynamic-indent-for-symbol symbol-name) + (if-let* ((rule-set (or (clojure-ts--dynamic-indent-for-symbol symbol-name symbol-namespace) (alist-get symbol-name (seq-union clojure-ts-semantic-indent-rules clojure-ts--semantic-indent-rules-defaults diff --git a/test/clojure-ts-mode-indentation-test.el b/test/clojure-ts-mode-indentation-test.el index 738f2a0..e6bbd98 100644 --- a/test/clojure-ts-mode-indentation-test.el +++ b/test/clojure-ts-mode-indentation-test.el @@ -96,7 +96,7 @@ DESCRIPTION is a string with the description of the spec." (when (stringp symbol-name) (cond ((string-equal symbol-name "my-with-in-str") 1) - ((string-equal symbol-name "my-letfn") '(1 ((:defn)) :form))))) + ((string-equal symbol-name "my.alias/my-letfn") '(1 ((:defn)) :form))))) (describe "indentation" @@ -305,10 +305,10 @@ DESCRIPTION is a string with the description of the spec." [fnspecs & body] ~@body) -(my-letfn [(twice [x] - (* x 2)) - (six-times [y] - (* (twice y) 3))] +(my.alias/my-letfn [(twice [x] + (* x 2)) + (six-times [y] + (* (twice y) 3))] (println \"Twice 15 =\" (twice 15)) (println \"Six times 15 =\" (six-times 15)))" (setq-local clojure-ts-get-indent-function #'cider--get-symbol-indent-mock) @@ -320,9 +320,9 @@ DESCRIPTION is a string with the description of the spec." [fnspecs & body] ~@body) -(my-letfn [(twice [x] - (* x 2)) - (six-times [y] - (* (twice y) 3))] +(my.alias/my-letfn [(twice [x] + (* x 2)) + (six-times [y] + (* (twice y) 3))] (println \"Twice 15 =\" (twice 15)) (println \"Six times 15 =\" (six-times 15)))")))) From 95427929a743f2987daf083af3eeb08b850c2d61 Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Sun, 6 Apr 2025 18:41:28 +0300 Subject: [PATCH 299/379] Update the bug report template --- .github/{ISSUE_TEMPLATE.md => ISSUE_TEMPLATE/bug_report.md} | 6 ++++++ 1 file changed, 6 insertions(+) rename .github/{ISSUE_TEMPLATE.md => ISSUE_TEMPLATE/bug_report.md} (94%) diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE/bug_report.md similarity index 94% rename from .github/ISSUE_TEMPLATE.md rename to .github/ISSUE_TEMPLATE/bug_report.md index aa0669a..329a945 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -1,3 +1,9 @@ +--- +name: Bug Report +about: Report an issue you've discovered. +labels: [bug] +--- + *Use the template below when reporting bugs. Please, make sure that you're running the latest stable clojure-ts-mode and that the problem you're reporting hasn't been reported (and potentially fixed) already.* From 897659a3098c8e56585890f312f87f14598b9c28 Mon Sep 17 00:00:00 2001 From: Roman Rudakov Date: Wed, 9 Apr 2025 20:44:20 +0200 Subject: [PATCH 300/379] Improve performance of semantic indentation by caching rules --- CHANGELOG.md | 1 + README.md | 18 +++++++++ clojure-ts-mode.el | 94 ++++++++++++++++++++++++++++++++++------------ 3 files changed, 89 insertions(+), 24 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2135331..5f4517e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ - [#70](https://github.com/clojure-emacs/clojure-ts-mode/pull/70): Add support for nested indentation rules. - [#71](https://github.com/clojure-emacs/clojure-ts-mode/pull/71): Properly highlight function name in `letfn` form. - [#72](https://github.com/clojure-emacs/clojure-ts-mode/pull/72): Pass fully qualified symbol to `clojure-ts-get-indent-function`. +- Improve performance of semantic indentation by caching rules. ## 0.2.3 (2025-03-04) diff --git a/README.md b/README.md index cad93f3..c9374ac 100644 --- a/README.md +++ b/README.md @@ -210,6 +210,24 @@ For example: - `defn` and `fn` have a rule `((:inner 0))`. - `letfn` has a rule `((:block 1) (:inner 2 0))`. +Note that `clojure-ts-semantic-indent-rules` should be set using the +customization interface or `setopt`; otherwise, it will not be applied +correctly. + +#### Project local indentation + +Custom indentation rules can be set for individual projects. To achieve this, +you need to create a `.dir-locals.el` file in the project root. The content +should look like: + +```emacs-lisp +((clojure-ts-mode . ((clojure-ts-semantic-indent-rules . (("with-transaction" . ((:block 1))) + ("with-retry" . ((:block 1)))))))) +``` + +In order to apply directory-local variables to existing buffers, they must be +reverted. + ### Font Locking To highlight entire rich `comment` expression with the comment font face, set diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index a8450bc..cb71cb9 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -125,26 +125,6 @@ double quotes on the third column." :type 'boolean :package-version '(clojure-ts-mode . "0.3")) -(defcustom clojure-ts-semantic-indent-rules nil - "Custom rules to extend default indentation rules for `semantic' style. - -Each rule is an alist entry which looks like `(\"symbol-name\" -. (rule-type rule-value))', where rule-type is one either `:block' or -`:inner' and rule-value is an integer. The semantic is similar to -cljfmt indentation rules. - -Default set of rules is defined in -`clojure-ts--semantic-indent-rules-defaults'." - :safe #'listp - :type '(alist :key-type string - :value-type (repeat (choice (list (choice (const :tag "Block indentation rule" :block) - (const :tag "Inner indentation rule" :inner)) - integer) - (list (const :tag "Inner indentation rule" :inner) - integer - integer)))) - :package-version '(clojure-ts-mode . "0.3")) - (defvar clojure-ts-mode-remappings '((clojure-mode . clojure-ts-mode) (clojurescript-mode . clojure-ts-clojurescript-mode) @@ -864,6 +844,61 @@ The format reflects cljfmt indentation rules. All the default rules are aligned with https://github.com/weavejester/cljfmt/blob/0.13.0/cljfmt/resources/cljfmt/indents/clojure.clj") +(defvar-local clojure-ts--semantic-indent-rules-cache nil) + +(defun clojure-ts--compute-semantic-indentation-rules-cache (rules) + "Compute the combined semantic indentation rules cache. + +If RULES are not provided, this function computes the union of +`clojure-ts-semantic-indent-rules' and +`clojure-ts--semantic-indent-rules-defaults', prioritizing user-defined +rules. If RULES are provided, this function uses them instead of +`clojure-ts-semantic-indent-rules'. + +This function is called when the `clojure-ts-semantic-indent-rules' +variable is customized using setopt or the Emacs customization +interface. It is also called when file-local variables are updated. +This ensures that updated indentation rules are always precalculated." + (seq-union rules + clojure-ts--semantic-indent-rules-defaults + (lambda (e1 e2) (equal (car e1) (car e2))))) + +(defun clojure-ts--set-semantic-indent-rules (symbol value) + "Setter function for `clojure-ts-semantic-indent-rules' variable. + +Sets SYMBOL's top-level default value to VALUE and updates the +`clojure-ts--semantic-indent-rules-cache' in all `clojure-ts-mode' +buffers, if any exist. + +NOTE: This function is not meant to be called directly." + (set-default-toplevel-value symbol value) + ;; Update cache in every `clojure-ts-mode' buffer. + (let ((new-cache (clojure-ts--compute-semantic-indentation-rules-cache value))) + (dolist (buf (buffer-list)) + (when (buffer-local-boundp 'clojure-ts--semantic-indent-rules-cache buf) + (setq clojure-ts--semantic-indent-rules-cache new-cache))))) + +(defcustom clojure-ts-semantic-indent-rules nil + "Custom rules to extend default indentation rules for `semantic' style. + +Each rule is an alist entry which looks like `(\"symbol-name\" +. (rule-type rule-value))', where rule-type is one either `:block' or +`:inner' and rule-value is an integer. The semantic is similar to +cljfmt indentation rules. + +Default set of rules is defined in +`clojure-ts--semantic-indent-rules-defaults'." + :safe #'listp + :type '(alist :key-type string + :value-type (repeat (choice (list (choice (const :tag "Block indentation rule" :block) + (const :tag "Inner indentation rule" :inner)) + integer) + (list (const :tag "Inner indentation rule" :inner) + integer + integer)))) + :package-version '(clojure-ts-mode . "0.3") + :set #'clojure-ts--set-semantic-indent-rules) + (defun clojure-ts--match-block-0-body (bol first-child) "Match if expression body is not at the same line as FIRST-CHILD. @@ -929,7 +964,7 @@ For example, (1 ((:defn)) nil) is converted to ((:block 1) (:inner 2)). If NS is defined, then the fully qualified symbol is passed to `clojure-ts-get-indent-function'." - (when (functionp clojure-ts-get-indent-function) + (when (and sym (functionp clojure-ts-get-indent-function)) (let* ((full-symbol (if ns (concat ns "/" sym) sym)) @@ -964,9 +999,7 @@ only if the CURRENT-DEPTH matches the rule's required depth." (idx (- (treesit-node-index node) 2))) (if-let* ((rule-set (or (clojure-ts--dynamic-indent-for-symbol symbol-name symbol-namespace) (alist-get symbol-name - (seq-union clojure-ts-semantic-indent-rules - clojure-ts--semantic-indent-rules-defaults - (lambda (e1 e2) (equal (car e1) (car e2)))) + clojure-ts--semantic-indent-rules-cache nil nil #'equal)))) @@ -1311,6 +1344,19 @@ See `clojure-ts--font-lock-settings' for usage of MARKDOWN-AVAILABLE." (treesit-major-mode-setup) + ;; Initial indentation rules cache calculation. + (setq clojure-ts--semantic-indent-rules-cache + (clojure-ts--compute-semantic-indentation-rules-cache clojure-ts-semantic-indent-rules)) + + ;; If indentation rules are set in `.dir-locals.el', it is advisable to + ;; recalculate the buffer-local value whenever the value changes. + (add-hook 'hack-local-variables-hook + (lambda () + (setq clojure-ts--semantic-indent-rules-cache + (clojure-ts--compute-semantic-indentation-rules-cache clojure-ts-semantic-indent-rules))) + 0 + t) + ;; Workaround for treesit-transpose-sexps not correctly working with ;; treesit-thing-settings on Emacs 30. ;; Once treesit-transpose-sexps it working again this can be removed From a0b01b212723dea772dd8c06932be0f9f66ea000 Mon Sep 17 00:00:00 2001 From: Roman Rudakov Date: Tue, 8 Apr 2025 12:18:00 +0200 Subject: [PATCH 301/379] [#74] Add imenu support for keyword definitions --- CHANGELOG.md | 3 +- README.md | 13 ++++++++ clojure-ts-mode.el | 50 +++++++++++++++++++++++++----- test/clojure-ts-mode-imenu-test.el | 16 +++++++--- test/samples/spec.clj | 7 +++++ 5 files changed, 76 insertions(+), 13 deletions(-) create mode 100644 test/samples/spec.clj diff --git a/CHANGELOG.md b/CHANGELOG.md index 5f4517e..0060ede 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,7 +18,8 @@ - [#70](https://github.com/clojure-emacs/clojure-ts-mode/pull/70): Add support for nested indentation rules. - [#71](https://github.com/clojure-emacs/clojure-ts-mode/pull/71): Properly highlight function name in `letfn` form. - [#72](https://github.com/clojure-emacs/clojure-ts-mode/pull/72): Pass fully qualified symbol to `clojure-ts-get-indent-function`. -- Improve performance of semantic indentation by caching rules. +- [#76](https://github.com/clojure-emacs/clojure-ts-mode/pull/76): Improve performance of semantic indentation by caching rules. +- [#74](https://github.com/clojure-emacs/clojure-ts-mode/issues/74): Add imenu support for keywords definitions. ## 0.2.3 (2025-03-04) diff --git a/README.md b/README.md index c9374ac..af70b42 100644 --- a/README.md +++ b/README.md @@ -275,6 +275,19 @@ Every new line in the docstrings is indented by `clojure-ts-docstring-fill-prefix-width` number of spaces (set to 2 by default which matches the `clojure-mode` settings). +#### imenu + +`clojure-ts-mode` supports various types of definition that can be navigated +using `imenu`, such as: + +- namespace +- function +- macro +- var +- interface (forms such as `defprotocol`, `definterface` and `defmulti`) +- class (forms such as `deftype`, `defrecord` and `defstruct`) +- keyword (for example, spec definitions) + ## Migrating to clojure-ts-mode If you are migrating to `clojure-ts-mode` note that `clojure-mode` is still required for cider and clj-refactor packages to work properly. diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index cb71cb9..9658234 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -637,17 +637,33 @@ See `clojure-ts--definition-node-p' when an exact match is possible." (and (clojure-ts--list-node-p node) (let* ((child (clojure-ts--node-child-skip-metadata node 0)) - (child-txt (clojure-ts--named-node-text child))) + (child-txt (clojure-ts--named-node-text child)) + (name-sym (clojure-ts--node-child-skip-metadata node 1))) (and (clojure-ts--symbol-node-p child) + (clojure-ts--symbol-node-p name-sym) (string-match-p definition-type-regexp child-txt))))) +(defun clojure-ts--kwd-definition-node-match-p (node) + "Return non-nil if the NODE is a keyword definition." + (and (clojure-ts--list-node-p node) + (let* ((child (clojure-ts--node-child-skip-metadata node 0)) + (child-txt (clojure-ts--named-node-text child)) + (child-ns (clojure-ts--node-namespace-text child)) + (name-kwd (clojure-ts--node-child-skip-metadata node 1))) + (and child-ns + (clojure-ts--symbol-node-p child) + (clojure-ts--keyword-node-p name-kwd) + (string-equal child-txt "def"))))) + (defun clojure-ts--standard-definition-node-name (node) "Return the definition name for the given NODE. -Returns nil if NODE is not a list with symbols as the first two children. -For example the node representing the expression (def foo 1) would return foo. -The node representing (ns user) would return user. -Does not does any matching on the first symbol (def, defn, etc), so identifying -that a node is a definition is intended to be done elsewhere. + +Returns nil if NODE is not a list with symbols as the first two +children. For example the node representing the expression (def foo 1) +would return foo. The node representing (ns user) would return user. +Does not do any matching on the first symbol (def, defn, etc), so +identifying that a node is a definition is intended to be done +elsewhere. Can be called directly, but intended for use as `treesit-defun-name-function'." (when (and (clojure-ts--list-node-p node) @@ -663,6 +679,21 @@ Can be called directly, but intended for use as `treesit-defun-name-function'." (concat (treesit-node-text ns) "/" (treesit-node-text name)) (treesit-node-text name))))))) +(defun clojure-ts--kwd-definition-node-name (node) + "Return the keyword name for the given NODE. + +Returns nil if NODE is not a list where the first element is a symbol +and the second is a keyword. For example, a node representing the +expression (s/def ::foo int?) would return foo. + +Can be called directly, but intended for use as +`treesit-defun-name-function'." + (when (and (clojure-ts--list-node-p node) + (clojure-ts--symbol-node-p (clojure-ts--node-child-skip-metadata node 0))) + (let ((kwd (clojure-ts--node-child-skip-metadata node 1))) + (when (clojure-ts--keyword-node-p kwd) + (treesit-node-text (treesit-node-child-by-field-name kwd "name")))))) + (defvar clojure-ts--function-type-regexp (rx string-start (or (seq "defn" (opt "-")) "defmethod" "deftest") string-end) "Regular expression for matching definition nodes that resemble functions.") @@ -713,7 +744,6 @@ Includes a dispatch value when applicable (defmethods)." "Return non-nil if NODE represents a protocol or interface definition." (clojure-ts--definition-node-match-p clojure-ts--interface-type-regexp node)) - (defvar clojure-ts--imenu-settings `(("Namespace" "list_lit" clojure-ts--ns-node-p) ("Function" "list_lit" clojure-ts--function-node-p @@ -722,7 +752,11 @@ Includes a dispatch value when applicable (defmethods)." ("Macro" "list_lit" clojure-ts--defmacro-node-p) ("Variable" "list_lit" clojure-ts--variable-definition-node-p) ("Interface" "list_lit" clojure-ts--interface-node-p) - ("Class" "list_lit" clojure-ts--class-node-p)) + ("Class" "list_lit" clojure-ts--class-node-p) + ("Keyword" + "list_lit" + clojure-ts--kwd-definition-node-match-p + clojure-ts--kwd-definition-node-name)) "The value for `treesit-simple-imenu-settings'. By default `treesit-defun-name-function' is used to extract definition names. See `clojure-ts--standard-definition-node-name' for the implementation used.") diff --git a/test/clojure-ts-mode-imenu-test.el b/test/clojure-ts-mode-imenu-test.el index 4567596..1822231 100644 --- a/test/clojure-ts-mode-imenu-test.el +++ b/test/clojure-ts-mode-imenu-test.el @@ -29,10 +29,18 @@ (describe "clojure-ts-mode imenu integration" (it "should index def with meta data" (with-clojure-ts-buffer "^{:foo 1}(def a 1)" - (expect (imenu--in-alist "a" (imenu--make-index-alist)) - :not :to-be nil))) + (let ((flatten-index (imenu--flatten-index-alist (imenu--make-index-alist) t))) + (expect (imenu-find-default "a" flatten-index) + :to-equal "Variable:a")))) (it "should index defn with meta data" (with-clojure-ts-buffer "^{:foo 1}(defn a [])" - (expect (imenu--in-alist "a" (imenu--make-index-alist)) - :not :to-be nil)))) + (let ((flatten-index (imenu--flatten-index-alist (imenu--make-index-alist) t))) + (expect (imenu-find-default "a" flatten-index) + :to-equal "Function:a")))) + + (it "should index def with keywords as a first item" + (with-clojure-ts-buffer "(s/def ::username string?)" + (let ((flatten-index (imenu--flatten-index-alist (imenu--make-index-alist) t))) + (expect (imenu-find-default "username" flatten-index) + :to-equal "Keyword:username"))))) diff --git a/test/samples/spec.clj b/test/samples/spec.clj new file mode 100644 index 0000000..b0770cf --- /dev/null +++ b/test/samples/spec.clj @@ -0,0 +1,7 @@ +(ns spec + (:require + [clojure.spec.alpha :as s])) + +(s/def ::username string?) +(s/def ::age number?) +(s/def ::email string?) From dc66d67a1e8af979e3f687615ff7ea4bab62ce80 Mon Sep 17 00:00:00 2001 From: Roman Rudakov Date: Thu, 10 Apr 2025 20:47:50 +0200 Subject: [PATCH 302/379] [#77] Update grammars --- CHANGELOG.md | 1 + README.md | 13 ++++++++++++- clojure-ts-mode.el | 44 +++++++++++++++++++++++++++----------------- 3 files changed, 40 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0060ede..2ce075e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ - [#72](https://github.com/clojure-emacs/clojure-ts-mode/pull/72): Pass fully qualified symbol to `clojure-ts-get-indent-function`. - [#76](https://github.com/clojure-emacs/clojure-ts-mode/pull/76): Improve performance of semantic indentation by caching rules. - [#74](https://github.com/clojure-emacs/clojure-ts-mode/issues/74): Add imenu support for keywords definitions. +- [#77](https://github.com/clojure-emacs/clojure-ts-mode/issues/77): Update grammars to the latest versions. ## 0.2.3 (2025-03-04) diff --git a/README.md b/README.md index af70b42..cbbafb3 100644 --- a/README.md +++ b/README.md @@ -124,7 +124,7 @@ Once installed, evaluate clojure-ts-mode.el and you should be ready to go. `clojure-ts-mode` makes use of two TreeSitter grammars to work properly: - The Clojure grammar, mentioned earlier -- [markdown_inline](https://github.com/MDeiml/tree-sitter-markdown), which +- [markdown-inline](https://github.com/MDeiml/tree-sitter-markdown), which will be used for docstrings if available and if `clojure-ts-use-markdown-inline` is enabled. If you have `git` and a C compiler (`cc`) available on your system's `PATH`, @@ -137,6 +137,17 @@ option to install it manually, Please, refer to the installation instructions of each required grammar and make sure you're install the versions expected. (see `clojure-ts-grammar-recipes` for details) +### Upgrading tree-sitter grammars + +To reinstall or upgrade TreeSitter grammars, you can execute: + +```emacs-lisp +M-x clojure-ts-reinstall-grammars +``` + +This will install the latest compatible grammars, even if they are already +installed. + ## Configuration To see a list of available configuration options do `M-x customize-group clojure-ts`. diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index 9658234..b6bd75f 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -343,14 +343,14 @@ if a third argument (the value) is provided. (defvar clojure-ts--treesit-range-settings (treesit-range-rules - :embed 'markdown_inline + :embed 'markdown-inline :host 'clojure (clojure-ts--docstring-query '@capture))) (defun clojure-ts--font-lock-settings (markdown-available) "Return font lock settings suitable for use in `treesit-font-lock-settings'. When MARKDOWN-AVAILABLE is non-nil, includes rules for highlighting docstrings -with the markdown_inline grammar." +with the markdown-inline grammar." (append (treesit-font-lock-rules :feature 'string @@ -512,11 +512,9 @@ with the markdown_inline grammar." (when markdown-available (treesit-font-lock-rules :feature 'doc - :language 'markdown_inline + :language 'markdown-inline :override t - `((inline - (code_span (code_span_delimiter) :* @font-lock-delimiter-face) - @font-lock-constant-face)))) + `((code_span) @font-lock-constant-face))) (treesit-font-lock-rules :feature 'quote @@ -985,7 +983,7 @@ and (:defn) is converted to (:inner 1)." (t nil)))) (defun clojure-ts--dynamic-indent-for-symbol (sym &optional ns) - "Returns the dynamic indentation specification for SYM, if found. + "Return the dynamic indentation specification for SYM, if found. If the function `clojure-ts-get-indent-function' is defined, call it and produce a valid indentation specification from its return value. @@ -1019,7 +1017,7 @@ If NS is defined, then the fully qualified symbol is passed to (equal (car spec1) :block))))))))) (defun clojure-ts--find-semantic-rule (node parent current-depth) - "Returns a suitable indentation rule for NODE, considering the CURRENT-DEPTH. + "Return a suitable indentation rule for NODE, considering the CURRENT-DEPTH. Attempts to find an indentation rule by examining the symbol name of the PARENT's first child. If a rule is not found, it navigates up the @@ -1153,13 +1151,13 @@ according to the rule. If NODE is nil, use next node after BOL." (clojure-ts--metadata-node-p prev-sibling)))) (defun clojure-ts--anchor-parent-skip-metadata (_node parent _bol) - "Anchor function that returns position of PARENT start for NODE. + "Return position of PARENT start for NODE. If PARENT has optional metadata we skip it and return starting position of the first child's opening paren. -NOTE: This anchor is used to fix indentation issue for forms with type -hints." +NOTE: This serves as an anchor function to resolve an indentation issue +for forms with type hints." (let ((first-child (treesit-node-child parent 0 t))) (if (clojure-ts--metadata-node-p first-child) ;; We don't need named node here @@ -1167,7 +1165,7 @@ hints." (treesit-node-start parent)))) (defun clojure-ts--match-collection-item-with-metadata (node-type) - "Returns a matcher for a collection item with metadata by NODE-TYPE. + "Return a matcher for a collection item with metadata by NODE-TYPE. The returned matcher accepts NODE, PARENT and BOL and returns true only if NODE has metadata and its parent has type NODE-TYPE." @@ -1296,9 +1294,9 @@ If JUSTIFY is non-nil, justify as well as fill the paragraph." (defconst clojure-ts-grammar-recipes '((clojure "https://github.com/sogaiu/tree-sitter-clojure.git" - "v0.0.12") - (markdown_inline "https://github.com/MDeiml/tree-sitter-markdown" - "v0.1.6" + "v0.0.13") + (markdown-inline "https://github.com/MDeiml/tree-sitter-markdown" + "v0.4.1" "tree-sitter-markdown-inline/src")) "Intended to be used as the value for `treesit-language-source-alist'.") @@ -1316,6 +1314,18 @@ If JUSTIFY is non-nil, justify as well as fill the paragraph." (let ((treesit-language-source-alist clojure-ts-grammar-recipes)) (treesit-install-language-grammar grammar))))))) +(defun clojure-ts-reinstall-grammars () + "Install the required versions of language grammars. + +If the grammars are already installed, they will be reinstalled. This +function can also be used to upgrade the grammars if they are outdated." + (interactive) + (dolist (recipe clojure-ts-grammar-recipes) + (let ((grammar (car recipe))) + (message "Installing %s tree-sitter grammar" grammar) + (let ((treesit-language-source-alist clojure-ts-grammar-recipes)) + (treesit-install-language-grammar grammar))))) + (defun clojure-ts-mode-variables (&optional markdown-available) "Initialize buffer-local variables for `clojure-ts-mode'. See `clojure-ts--font-lock-settings' for usage of MARKDOWN-AVAILABLE." @@ -1361,9 +1371,9 @@ See `clojure-ts--font-lock-settings' for usage of MARKDOWN-AVAILABLE." :syntax-table clojure-ts-mode-syntax-table (clojure-ts--ensure-grammars) (let ((use-markdown-inline (and clojure-ts-use-markdown-inline - (treesit-ready-p 'markdown_inline t)))) + (treesit-ready-p 'markdown-inline t)))) (when use-markdown-inline - (treesit-parser-create 'markdown_inline) + (treesit-parser-create 'markdown-inline) (setq-local treesit-range-settings clojure-ts--treesit-range-settings)) (when (treesit-ready-p 'clojure) From 63a9ea47c8365532f67a4112eea5111b27d880b5 Mon Sep 17 00:00:00 2001 From: Roman Rudakov Date: Fri, 11 Apr 2025 10:19:34 +0200 Subject: [PATCH 303/379] Improve markdown highlighting in the docstrings --- CHANGELOG.md | 1 + Eldev | 2 +- clojure-ts-mode.el | 9 ++++++++- test/samples/docstrings.clj | 11 ++++++++++- 4 files changed, 20 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ce075e..627b567 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,6 +21,7 @@ - [#76](https://github.com/clojure-emacs/clojure-ts-mode/pull/76): Improve performance of semantic indentation by caching rules. - [#74](https://github.com/clojure-emacs/clojure-ts-mode/issues/74): Add imenu support for keywords definitions. - [#77](https://github.com/clojure-emacs/clojure-ts-mode/issues/77): Update grammars to the latest versions. +- [#79](https://github.com/clojure-emacs/clojure-ts-mode/pull/79): Improve markdown highlighting in the docstrings. ## 0.2.3 (2025-03-04) diff --git a/Eldev b/Eldev index 7a30881..b704571 100644 --- a/Eldev +++ b/Eldev @@ -17,7 +17,7 @@ (setq checkdoc-permit-comma-termination-flag t) (setq checkdoc--interactive-docstring-flag nil) -(setf eldev-lint-default '(elisp)) +(setq eldev-lint-default-excluded '(package)) (with-eval-after-load 'elisp-lint ;; We will byte-compile with Eldev. diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index b6bd75f..8f89f47 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -514,7 +514,14 @@ with the markdown-inline grammar." :feature 'doc :language 'markdown-inline :override t - `((code_span) @font-lock-constant-face))) + `([((image_description) @link) + ((link_destination) @font-lock-constant-face) + ((code_span) @font-lock-constant-face) + ((emphasis) @underline) + ((strong_emphasis) @bold) + (inline_link (link_text) @link) + (inline_link (link_destination) @font-lock-constant-face) + (shortcut_link (link_text) @link)]))) (treesit-font-lock-rules :feature 'quote diff --git a/test/samples/docstrings.clj b/test/samples/docstrings.clj index c3bb2a7..e8d5821 100644 --- a/test/samples/docstrings.clj +++ b/test/samples/docstrings.clj @@ -42,7 +42,16 @@ Don't format code this way." "fizz")) (defmacro fix-bug - "Fixes most known bugs." + "Fixes most known bugs. + + Check markdown: + - [[some-function]] + - _emphasize_ + - [link](https://github.com) + - __strong__ + - *emphasize* + + Looks good." [& body] `(try ~@body From c2aedeb48830e74b344c04a2fd2e2a1e4b8e49e9 Mon Sep 17 00:00:00 2001 From: Roman Rudakov Date: Mon, 14 Apr 2025 20:39:35 +0200 Subject: [PATCH 304/379] [#60] Fix issue with fontification when markdown-inline is enabled Many thanks to Juri Linkov for his assistance. --- CHANGELOG.md | 1 + clojure-ts-mode.el | 15 ++++++++++++--- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 627b567..ec08dda 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ - [#74](https://github.com/clojure-emacs/clojure-ts-mode/issues/74): Add imenu support for keywords definitions. - [#77](https://github.com/clojure-emacs/clojure-ts-mode/issues/77): Update grammars to the latest versions. - [#79](https://github.com/clojure-emacs/clojure-ts-mode/pull/79): Improve markdown highlighting in the docstrings. +- [#60](https://github.com/clojure-emacs/clojure-ts-mode/issues/60): Fix issue with incorrect fontification, when `markdown-inline` is enabled. ## 0.2.3 (2025-03-04) diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index 8f89f47..29fe1b2 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -345,6 +345,7 @@ if a third argument (the value) is provided. (treesit-range-rules :embed 'markdown-inline :host 'clojure + :local t (clojure-ts--docstring-query '@capture))) (defun clojure-ts--font-lock-settings (markdown-available) @@ -1230,7 +1231,7 @@ It is simply `clojure-ts-docstring-fill-prefix-width' number of spaces." (defun clojure-ts--fill-paragraph (&optional justify) "Like `fill-paragraph', but can handler Clojure docstrings. If JUSTIFY is non-nil, justify as well as fill the paragraph." - (let ((current-node (treesit-node-at (point)))) + (let ((current-node (treesit-node-at (point) 'clojure))) (if (clojure-ts--match-docstring nil current-node nil) (let ((fill-column (or clojure-ts-docstring-fill-column fill-column)) (fill-prefix (clojure-ts--docstring-fill-prefix)) @@ -1260,11 +1261,20 @@ If JUSTIFY is non-nil, justify as well as fill the paragraph." "map_lit" "ns_map_lit" "vec_lit" "set_lit") "A regular expression that matches nodes that can be treated as lists.") +(defconst clojure-ts--markdown-inline-sexp-nodes + '("inline_link" "full_reference_link" "collapsed_reference_link" + "uri_autolink" "email_autolink" "shortcut_link" "image" + "code_span") + "Nodes representing s-expressions in the `markdown-inline' parser.") + (defconst clojure-ts--thing-settings `((clojure (sexp ,(regexp-opt clojure-ts--sexp-nodes)) (list ,(regexp-opt clojure-ts--list-nodes)) - (text ,(regexp-opt '("comment")))))) + (text ,(regexp-opt '("comment")))) + (when clojure-ts-use-markdown-inline + (markdown-inline + (sexp ,(regexp-opt clojure-ts--markdown-inline-sexp-nodes)))))) (defvar clojure-ts-mode-map (let ((map (make-sparse-keymap))) @@ -1380,7 +1390,6 @@ See `clojure-ts--font-lock-settings' for usage of MARKDOWN-AVAILABLE." (let ((use-markdown-inline (and clojure-ts-use-markdown-inline (treesit-ready-p 'markdown-inline t)))) (when use-markdown-inline - (treesit-parser-create 'markdown-inline) (setq-local treesit-range-settings clojure-ts--treesit-range-settings)) (when (treesit-ready-p 'clojure) From add23c919664cdd6616fed2a9e9f9d559fe1e8d7 Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Tue, 15 Apr 2025 10:54:01 +0300 Subject: [PATCH 305/379] Minor readme tweaks --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index cbbafb3..197c38d 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ Those will be addressed over the time, as more and more people use `clojure-ts-m ### Requirements For `clojure-ts-mode` to work, you need Emacs 30+ built with TreeSitter support. -To check if your Emacs supports tree sitter run the following (e.g. by using `M-:`): +To check if your Emacs supports TreeSitter run the following (e.g. by using `M-:`): ``` emacs-lisp (treesit-available-p) @@ -84,7 +84,7 @@ grammars clojure-ts-mode is available on [MElPA](https://melpa.org/#/clojure-ts-mode) and [NonGNU ELPA](https://elpa.nongnu.org/nongnu/clojure-ts-mode.html). -It can be installed with +It can be installed with: ``` emacs-lisp (package-install 'clojure-ts-mode) @@ -92,7 +92,7 @@ It can be installed with #### package-vc -Emacs 29 also includes `package-vc-install`, so you can run +Emacs also includes `package-vc-install`, so you can run: ``` emacs-lisp (package-vc-install "https://github.com/clojure-emacs/clojure-ts-mode") @@ -112,7 +112,7 @@ git clone https://github.com/clojure-emacs/clojure-ts-mode.git (add-to-list 'load-path "~/path/to/clojure-ts-mode/") ``` -Once installed, evaluate clojure-ts-mode.el and you should be ready to go. +Once installed, evaluate `clojure-ts-mode.el` and you should be ready to go. ### Install tree-sitter grammars From a2ac36cee39eea1c1b87c3530710b1fe6a7409cb Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Tue, 15 Apr 2025 10:58:30 +0300 Subject: [PATCH 306/379] Add some notes about the compatibility with CIDER --- README.md | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 197c38d..f2f121b 100644 --- a/README.md +++ b/README.md @@ -350,9 +350,9 @@ refactoring commands in `clojure-mode`. ### Does `clojure-ts-mode` work with CIDER? Yes! Preliminary support for `clojure-ts-mode` was released in [CIDER -1.14](https://github.com/clojure-emacs/cider/releases/tag/v1.14.0). Make sure to -grab the latest CIDER from MELPA/GitHub. Note that `clojure-mode` is still -needed for some APIs that haven't yet been ported to `clojure-ts-mode`. +1.14](https://github.com/clojure-emacs/cider/releases/tag/v1.14.0). Note that +`clojure-mode` is still needed for some APIs that haven't yet been ported to +`clojure-ts-mode`. For now, when you take care of the keybindings for the CIDER commands you use and ensure `cider-mode` is enabled for `clojure-ts-mode` buffers in your config, @@ -364,6 +364,11 @@ most functionality should already work: Check out [this article](https://metaredux.com/posts/2024/02/19/cider-preliminary-support-for-clojure-ts-mode.html) for more details. +> [!NOTE] +> +> The dynamic indentation feature in CIDER requires clojure-ts-mode 0.3+. +> Dynamic font-locking currently doesn't work with clojure-ts-mode. + ### Does `clojure-ts-mode` work with `inf-clojure`? Currently, there is an [open PR](https://github.com/clojure-emacs/inf-clojure/pull/215) adding support for inf-clojure. From cae705d74affc5465f2b6536c80dd927c9e46f4c Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Tue, 15 Apr 2025 11:00:59 +0300 Subject: [PATCH 307/379] Update copyright --- README.md | 2 +- clojure-ts-mode.el | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index f2f121b..d5407bc 100644 --- a/README.md +++ b/README.md @@ -375,7 +375,7 @@ Currently, there is an [open PR](https://github.com/clojure-emacs/inf-clojure/pu ## License -Copyright © 2022-2025 Danny Freeman and [contributors][]. +Copyright © 2022-2025 Danny Freeman, Bozhidar Batsov and [contributors][]. Distributed under the GNU General Public License; type C-h C-c to view it. diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index 29fe1b2..b893a7b 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -1,9 +1,10 @@ ;;; clojure-ts-mode.el --- Major mode for Clojure code -*- lexical-binding: t; -*- -;; Copyright © 2022-2025 Danny Freeman +;; Copyright © 2022-2025 Danny Freeman, Bozhidar Batsov and contributors ;; ;; Authors: Danny Freeman -;; Maintainer: Danny Freeman +;; Bozhidar Batsov +;; Maintainer: Bozhidar Batsov ;; URL: http://github.com/clojure-emacs/clojure-ts-mode ;; Keywords: languages clojure clojurescript lisp ;; Version: 0.3.0-snapshot From 27da9a6a935b65a5a24d93cfd88c421e8867f6d0 Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Tue, 15 Apr 2025 11:02:54 +0300 Subject: [PATCH 308/379] Release 0.3 --- CHANGELOG.md | 2 ++ clojure-ts-mode.el | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ec08dda..3ba2af9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## main (unreleased) +## 0.3.0 (2025-04-15) + - [#62](https://github.com/clojure-emacs/clojure-ts-mode/issues/62): Define `list` "thing" to improve navigation in Emacs 31. - [#64](https://github.com/clojure-emacs/clojure-ts-mode/pull/64): Add defcustom `clojure-ts-auto-remap` to control remapping of `clojure-mode` buffers. - [#66](https://github.com/clojure-emacs/clojure-ts-mode/pull/66): Improve syntax highlighting: diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index b893a7b..971d23d 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -7,7 +7,7 @@ ;; Maintainer: Bozhidar Batsov ;; URL: http://github.com/clojure-emacs/clojure-ts-mode ;; Keywords: languages clojure clojurescript lisp -;; Version: 0.3.0-snapshot +;; Version: 0.3.0 ;; Package-Requires: ((emacs "30.1")) ;; This file is not part of GNU Emacs. @@ -72,7 +72,7 @@ :link '(emacs-commentary-link :tag "Commentary" "clojure-mode")) (defconst clojure-ts-mode-version - "0.3.0-snapshot" + "0.3.0" "The current version of `clojure-ts-mode'.") (defcustom clojure-ts-comment-macro-font-lock-body nil From 6efda6a94f4d17461f39c728070b1103f80fd277 Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Tue, 15 Apr 2025 11:04:23 +0300 Subject: [PATCH 309/379] Bump the development version --- clojure-ts-mode.el | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index 971d23d..94af72e 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -7,7 +7,7 @@ ;; Maintainer: Bozhidar Batsov ;; URL: http://github.com/clojure-emacs/clojure-ts-mode ;; Keywords: languages clojure clojurescript lisp -;; Version: 0.3.0 +;; Version: 0.4.0-snapshot ;; Package-Requires: ((emacs "30.1")) ;; This file is not part of GNU Emacs. @@ -72,7 +72,7 @@ :link '(emacs-commentary-link :tag "Commentary" "clojure-mode")) (defconst clojure-ts-mode-version - "0.3.0" + "0.4.0-snapshot" "The current version of `clojure-ts-mode'.") (defcustom clojure-ts-comment-macro-font-lock-body nil From cec1d322dee8571c98704bbc26bc6201711291c5 Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Tue, 15 Apr 2025 11:06:11 +0300 Subject: [PATCH 310/379] Automate the creation of GitHub releases --- .github/workflows/github_release.yml | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) create mode 100644 .github/workflows/github_release.yml diff --git a/.github/workflows/github_release.yml b/.github/workflows/github_release.yml new file mode 100644 index 0000000..390eacc --- /dev/null +++ b/.github/workflows/github_release.yml @@ -0,0 +1,27 @@ +name: Create GitHub Release + +on: + push: + tags: + - "v*" # Trigger when a version tag is pushed (e.g., v1.0.0) + +jobs: + create-release: + runs-on: ubuntu-latest + + permissions: + contents: write + + steps: + - name: Checkout Code + uses: actions/checkout@v4 + + - name: Create GitHub Release with Auto-Generated Notes + uses: ncipollo/release-action@v1 + with: + tag: ${{ github.ref_name }} + name: clojure-ts-mode ${{ github.ref_name }} + prerelease: ${{ contains(github.ref, '-rc') || contains(github.ref, '-alpha') || contains(github.ref, '-beta') }} + generateReleaseNotes: true # Auto-generate release notes based on PRs and commits + # TODO: Use bodyFile to get the contents from changelog + token: ${{ secrets.GITHUB_TOKEN }} From 61041c82be52faf7f931b62b7b300742b04f76a3 Mon Sep 17 00:00:00 2001 From: Roman Rudakov Date: Fri, 18 Apr 2025 16:53:41 +0200 Subject: [PATCH 311/379] [#16] Implement clojure-ts-align --- CHANGELOG.md | 2 + README.md | 32 +++ clojure-ts-mode.el | 279 +++++++++++++++++++++-- test/clojure-ts-mode-indentation-test.el | 68 ++++++ test/samples/align.clj | 32 +++ 5 files changed, 394 insertions(+), 19 deletions(-) create mode 100644 test/samples/align.clj diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ba2af9..41e2a14 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## main (unreleased) +- [#16](https://github.com/clojure-emacs/clojure-ts-mode/issues/16): Introduce `clojure-ts-align`. + ## 0.3.0 (2025-04-15) - [#62](https://github.com/clojure-emacs/clojure-ts-mode/issues/62): Define `list` "thing" to improve navigation in Emacs 31. diff --git a/README.md b/README.md index d5407bc..f44f583 100644 --- a/README.md +++ b/README.md @@ -239,6 +239,38 @@ should look like: In order to apply directory-local variables to existing buffers, they must be reverted. +### Vertical alignment + +You can vertically align sexps with `C-c SPC`. For instance, typing this combo +on the following form: + +```clojure +(def my-map + {:a-key 1 + :other-key 2}) +``` + +Leads to the following: + +```clojure +(def my-map + {:a-key 1 + :other-key 2}) +``` + +Forms that can be aligned vertically are configured via the following variables: + +- `clojure-ts-align-reader-conditionals` - align reader conditionals as if they + were maps. +- `clojure-ts-align-binding-forms` - a customizable list of forms with let-like + bindings that can be aligned vertically. +- `clojure-ts-align-cond-forms` - a customizable list of forms whose body + elements can be aligned vertically. These forms respect the block semantic + indentation rule (if configured) and align only the body forms, skipping N + special arguments. +- `clojure-ts-align-separator` - determines whether blank lines prevent vertical + alignment. + ### Font Locking To highlight entire rich `comment` expression with the comment font face, set diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index 94af72e..7c16c01 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -56,6 +56,7 @@ ;;; Code: (require 'treesit) +(require 'align) (declare-function treesit-parser-create "treesit.c") (declare-function treesit-node-eq "treesit.c") @@ -126,6 +127,70 @@ double quotes on the third column." :type 'boolean :package-version '(clojure-ts-mode . "0.3")) +(defcustom clojure-ts-align-reader-conditionals nil + "Whether to align reader conditionals, as if they were maps." + :package-version '(clojure-ts-mode . "0.4") + :safe #'booleanp + :type 'boolean) + +(defcustom clojure-ts-align-binding-forms + '("let" + "when-let" + "when-some" + "if-let" + "if-some" + "binding" + "loop" + "doseq" + "for" + "with-open" + "with-local-vars" + "with-redefs" + "clojure.core/let" + "clojure.core/when-let" + "clojure.core/when-some" + "clojure.core/if-let" + "clojure.core/if-some" + "clojure.core/binding" + "clojure.core/loop" + "clojure.core/doseq" + "clojure.core/for" + "clojure.core/with-open" + "clojure.core/with-local-vars" + "clojure.core/with-redefs") + "List of strings matching forms that have binding forms." + :package-version '(clojure-ts-mode . "0.4") + :safe #'listp + :type '(repeat string)) + +(defconst clojure-ts--align-separator-newline-regexp "^ *$") + +(defcustom clojure-ts-align-separator clojure-ts--align-separator-newline-regexp + "Separator passed to `align-region' when performing vertical alignment." + :package-version '(clojure-ts-mode . "0.4") + :type `(choice (const :tag "Make blank lines prevent vertical alignment from happening." + ,clojure-ts--align-separator-newline-regexp) + (other :tag "Allow blank lines to happen within a vertically-aligned expression." + entire))) + +(defcustom clojure-ts-align-cond-forms + '("condp" + "cond" + "cond->" + "cond->>" + "case" + "are" + "clojure.core/condp" + "clojure.core/cond" + "clojure.core/cond->" + "clojure.core/cond->>" + "clojure.core/case" + "clojure.core/are") + "List of strings identifying cond-like forms." + :package-version '(clojure-ts-mode . "0.4") + :safe #'listp + :type '(repeat string)) + (defvar clojure-ts-mode-remappings '((clojure-mode . clojure-ts-mode) (clojurescript-mode . clojure-ts-clojurescript-mode) @@ -1025,6 +1090,18 @@ If NS is defined, then the fully qualified symbol is passed to (seq-sort (lambda (spec1 _spec2) (equal (car spec1) :block))))))))) +(defun clojure-ts--find-semantic-rules-for-node (node) + "Return a list of semantic rules for NODE." + (let* ((first-child (clojure-ts--node-child-skip-metadata node 0)) + (symbol-name (clojure-ts--named-node-text first-child)) + (symbol-namespace (clojure-ts--node-namespace-text first-child))) + (or (clojure-ts--dynamic-indent-for-symbol symbol-name symbol-namespace) + (alist-get symbol-name + clojure-ts--semantic-indent-rules-cache + nil + nil + #'equal)))) + (defun clojure-ts--find-semantic-rule (node parent current-depth) "Return a suitable indentation rule for NODE, considering the CURRENT-DEPTH. @@ -1034,16 +1111,8 @@ syntax tree and recursively attempts to find a rule, incrementally increasing the CURRENT-DEPTH. If a rule is not found upon reaching the root of the syntax tree, it returns nil. A rule is considered a match only if the CURRENT-DEPTH matches the rule's required depth." - (let* ((first-child (clojure-ts--node-child-skip-metadata parent 0)) - (symbol-name (clojure-ts--named-node-text first-child)) - (symbol-namespace (clojure-ts--node-namespace-text first-child)) - (idx (- (treesit-node-index node) 2))) - (if-let* ((rule-set (or (clojure-ts--dynamic-indent-for-symbol symbol-name symbol-namespace) - (alist-get symbol-name - clojure-ts--semantic-indent-rules-cache - nil - nil - #'equal)))) + (let* ((idx (- (treesit-node-index node) 2))) + (if-let* ((rule-set (clojure-ts--find-semantic-rules-for-node parent))) (if (zerop current-depth) (let ((rule (car rule-set))) (if (equal (car rule) :block) @@ -1061,7 +1130,9 @@ only if the CURRENT-DEPTH matches the rule's required depth." (or (null rule-idx) (equal rule-idx idx)))))) (seq-first))) - (when-let* ((new-parent (treesit-node-parent parent))) + ;; Let's go no more than 3 levels up to avoid performance degradation. + (when-let* (((< current-depth 3)) + (new-parent (treesit-node-parent parent))) (clojure-ts--find-semantic-rule parent new-parent (1+ current-depth)))))) @@ -1188,12 +1259,6 @@ if NODE has metadata and its parent has type NODE-TYPE." `((clojure ((parent-is "source") parent-bol 0) (clojure-ts--match-docstring parent 0) - ;; https://guide.clojure.style/#body-indentation - (clojure-ts--match-form-body clojure-ts--anchor-parent-skip-metadata 2) - ;; https://guide.clojure.style/#threading-macros-alignment - (clojure-ts--match-threading-macro-arg prev-sibling 0) - ;; https://guide.clojure.style/#vertically-align-fn-args - (clojure-ts--match-function-call-arg (nth-sibling 2 nil) 0) ;; Collections items with metadata. ;; ;; This should be before `clojure-ts--match-with-metadata', otherwise they @@ -1208,10 +1273,17 @@ if NODE has metadata and its parent has type NODE-TYPE." ;; All other forms with metadata. (clojure-ts--match-with-metadata parent 0) ;; Literal Sequences - ((parent-is "list_lit") parent 1) ;; https://guide.clojure.style/#one-space-indent ((parent-is "vec_lit") parent 1) ;; https://guide.clojure.style/#bindings-alignment ((parent-is "map_lit") parent 1) ;; https://guide.clojure.style/#map-keys-alignment - ((parent-is "set_lit") parent 2)))) + ((parent-is "set_lit") parent 2) + ;; https://guide.clojure.style/#body-indentation + (clojure-ts--match-form-body clojure-ts--anchor-parent-skip-metadata 2) + ;; https://guide.clojure.style/#threading-macros-alignment + (clojure-ts--match-threading-macro-arg prev-sibling 0) + ;; https://guide.clojure.style/#vertically-align-fn-args + (clojure-ts--match-function-call-arg (nth-sibling 2 nil) 0) + ;; https://guide.clojure.style/#one-space-indent + ((parent-is "list_lit") parent 1)))) (defun clojure-ts--configured-indent-rules () "Gets the configured choice of indent rules." @@ -1277,9 +1349,177 @@ If JUSTIFY is non-nil, justify as well as fill the paragraph." (markdown-inline (sexp ,(regexp-opt clojure-ts--markdown-inline-sexp-nodes)))))) +;;; Vertical alignment + +(defun clojure-ts--beginning-of-defun-pos () + "Return the point that represents the beginning of the current defun." + (treesit-node-start (treesit-defun-at-point))) + +(defun clojure-ts--end-of-defun-pos () + "Return the point that represends the end of the current defun." + (treesit-node-end (treesit-defun-at-point))) + +(defun clojure-ts--search-whitespace-after-next-sexp (root-node bound) + "Move the point after all whitespace following the next s-expression. + +Set match data group 1 to this region of whitespace and return the +point. + +To move over the next s-expression, fetch the next node after the +current cursor position that is a direct child of ROOT-NODE and navigate +to its end. The most complex aspect here is handling nodes with +metadata. Some forms are represented in the syntax tree as a single +s-expression (for example, ^long my-var or ^String (str \"Hello\" +\"world\")), while other forms are two separate s-expressions (for +example, ^long 123 or ^String \"Hello\"). Expressions with two nodes +share some common features: + +- The top-level node type is usually sym_lit + +- They do not have value children, or they have an empty name. + +Regular expression and syntax analysis code is borrowed from +`clojure-mode.' + +BOUND bounds the whitespace search." + (unwind-protect + (when-let* ((cur-sexp (treesit-node-first-child-for-pos root-node (point) t))) + (goto-char (treesit-node-start cur-sexp)) + (if (and (string= "sym_lit" (treesit-node-type cur-sexp)) + (clojure-ts--metadata-node-p (treesit-node-child cur-sexp 0 t)) + (and (not (treesit-node-child-by-field-name cur-sexp "value")) + (string-empty-p (clojure-ts--named-node-text cur-sexp)))) + (treesit-end-of-thing 'sexp 2 'restricted) + (treesit-end-of-thing 'sexp 1 'restrict)) + (when (looking-at ",") + (forward-char)) + ;; Move past any whitespace or comment. + (search-forward-regexp "\\([,\s\t]*\\)\\(;+.*\\)?" bound) + (pcase (syntax-after (point)) + ;; End-of-line, try again on next line. + (`(12) (clojure-ts--search-whitespace-after-next-sexp root-node bound)) + ;; Closing paren, stop here. + (`(5 . ,_) nil) + ;; Anything else is something to align. + (_ (point)))) + (when (and bound (> (point) bound)) + (goto-char bound)))) + +(defun clojure-ts--get-nodes-to-align (region-node beg end) + "Return a plist of nodes data for alignment. + +The search is limited by BEG, END and REGION-NODE. + +Possible node types are: map, bindings-vec, cond or read-cond. + +The returned value is a list of property lists. Each property list +includes `:sexp-type', `:node', `:beg-marker', and `:end-marker'. +Markers are necessary to fetch the same nodes after their boundaries +have changed." + (let* ((query (treesit-query-compile 'clojure + (append + `(((map_lit) @map) + ((list_lit + ((sym_lit) @sym + (:match ,(clojure-ts-symbol-regexp clojure-ts-align-binding-forms) @sym)) + (vec_lit) @bindings-vec)) + ((list_lit + ((sym_lit) @sym + (:match ,(clojure-ts-symbol-regexp clojure-ts-align-cond-forms) @sym))) + @cond)) + (when clojure-ts-align-reader-conditionals + '(((read_cond_lit) @read-cond))))))) + (thread-last (treesit-query-capture region-node query beg end) + (seq-remove (lambda (elt) (eq (car elt) 'sym))) + ;; When first node is reindented, all other nodes become + ;; outdated. Executing the entire query everytime is very + ;; expensive, instead we use markers for every captured node to + ;; retrieve only a single node later. + (seq-map (lambda (elt) + (let* ((sexp-type (car elt)) + (node (cdr elt)) + (beg-marker (copy-marker (treesit-node-start node) t)) + (end-marker (copy-marker (treesit-node-end node)))) + (list :sexp-type sexp-type + :node node + :beg-marker beg-marker + :end-marker end-marker))))))) + +(defun clojure-ts--point-to-align-position (sexp-type node) + "Move point to the appropriate position to align NODE. + +For NODE with SEXP-TYPE map or bindings-vec, the appropriate +position is after the first opening brace. + +For NODE with SEXP-TYPE cond, we need to skip the first symbol and the +subsequent special arguments based on block indentation rules." + (goto-char (treesit-node-start node)) + (when-let* ((cur-sexp (treesit-node-first-child-for-pos node (point) t))) + (goto-char (treesit-node-start cur-sexp)) + ;; For cond forms we need to skip first n + 1 nodes according to block + ;; indentation rules. First node to skip is the symbol itself. + (when (equal sexp-type 'cond) + (if-let* ((rule-set (clojure-ts--find-semantic-rules-for-node node)) + (rule (car rule-set)) + ((equal (car rule) :block))) + (treesit-beginning-of-thing 'sexp (1- (- (cadr rule))) 'restrict) + (treesit-beginning-of-thing 'sexp -1))))) + +(defun clojure-ts-align (beg end) + "Vertically align the contents of the sexp around point. + +If region is active, align it. Otherwise, align everything in the +current \"top-level\" sexp. When called from lisp code align everything +between BEG and END." + (interactive (if (use-region-p) + (list (region-beginning) (region-end)) + (save-excursion + (let ((start (clojure-ts--beginning-of-defun-pos)) + (end (clojure-ts--end-of-defun-pos))) + (list start end))))) + (setq end (copy-marker end)) + (let* ((root-node (treesit-buffer-root-node 'clojure)) + ;; By default `treesit-query-capture' captures all nodes that cross the + ;; range. We need to restrict it to only nodes inside of the range. + (region-node (treesit-node-descendant-for-range root-node beg (marker-position end) t)) + (sexps-to-align (clojure-ts--get-nodes-to-align region-node beg (marker-position end)))) + (save-excursion + (indent-region beg (marker-position end)) + (dolist (sexp sexps-to-align) + ;; After reindenting a node, all other nodes in the `sexps-to-align' + ;; list become outdated, so we need to fetch updated nodes for every + ;; iteration. + (let* ((new-root-node (treesit-buffer-root-node 'clojure)) + (new-region-node (treesit-node-descendant-for-range new-root-node + beg + (marker-position end) + t)) + (sexp-beg (marker-position (plist-get sexp :beg-marker))) + (sexp-end (marker-position (plist-get sexp :end-marker))) + (node (treesit-node-descendant-for-range new-region-node + sexp-beg + sexp-end + t)) + (sexp-type (plist-get sexp :sexp-type)) + (node-end (treesit-node-end node))) + (clojure-ts--point-to-align-position sexp-type node) + (align-region (point) node-end nil + `((clojure-align (regexp . ,(lambda (&optional bound _noerror) + (clojure-ts--search-whitespace-after-next-sexp node bound))) + (group . 1) + (separate . ,clojure-ts-align-separator) + (repeat . t))) + nil) + ;; After every iteration we have to re-indent the s-expression, + ;; otherwise some can be indented inconsistently. + (indent-region (marker-position (plist-get sexp :beg-marker)) + (marker-position (plist-get sexp :end-marker)))))))) + + (defvar clojure-ts-mode-map (let ((map (make-sparse-keymap))) ;;(set-keymap-parent map clojure-mode-map) + (keymap-set map "C-c SPC" #'clojure-ts-align) map)) (defvar clojure-ts-clojurescript-mode-map @@ -1347,6 +1587,7 @@ function can also be used to upgrade the grammars if they are outdated." (defun clojure-ts-mode-variables (&optional markdown-available) "Initialize buffer-local variables for `clojure-ts-mode'. See `clojure-ts--font-lock-settings' for usage of MARKDOWN-AVAILABLE." + (setq-local indent-tabs-mode nil) (setq-local comment-add 1) (setq-local comment-start ";") diff --git a/test/clojure-ts-mode-indentation-test.el b/test/clojure-ts-mode-indentation-test.el index e6bbd98..75ceb6d 100644 --- a/test/clojure-ts-mode-indentation-test.el +++ b/test/clojure-ts-mode-indentation-test.el @@ -326,3 +326,71 @@ DESCRIPTION is a string with the description of the spec." (* (twice y) 3))] (println \"Twice 15 =\" (twice 15)) (println \"Six times 15 =\" (six-times 15)))")))) + +(describe "clojure-ts-align" + (it "should handle improperly indented content" + (with-clojure-ts-buffer-point " +(let [a-long-name 10 +b |20])" + (call-interactively #'clojure-ts-align) + (expect (buffer-string) :to-equal " +(let [a-long-name 10 + b 20])")) + + (with-clojure-ts-buffer-point " +(let [^long my-map {:hello \"World\" ;Hello + :foo + ^String (str \"Foo\" \"Bar\") + :number ^long 132 + :zz \"hello\"} + another| {:this ^{:hello \"world\"} \"is\" + :a #long \"1234\" + :b {:this \"is\" + :nested \"map\"}}])" + (call-interactively #'clojure-ts-align) + (expect (buffer-string) :to-equal " +(let [^long my-map {:hello \"World\" ;Hello + :foo + ^String (str \"Foo\" \"Bar\") + :number ^long 132 + :zz \"hello\"} + another {:this ^{:hello \"world\"} \"is\" + :a #long \"1234\" + :b {:this \"is\" + :nested \"map\"}}])")) + + (with-clojure-ts-buffer-point " +(condp = 2 +|123 \"Hello\" +99999 \"World\" +234 nil)" + (call-interactively #'clojure-ts-align) + (expect (buffer-string) :to-equal " +(condp = 2 + 123 \"Hello\" + 99999 \"World\" + 234 nil)"))) + + (it "should not align reader conditionals by defaul" + (with-clojure-ts-buffer-point " +#?(:clj 2 + |:cljs 2)" + (call-interactively #'clojure-ts-align) + (expect (buffer-string) :to-equal " +#?(:clj 2 + :cljs 2)"))) + + (it "should align reader conditionals when clojure-ts-align-reader-conditionals is true" + (with-clojure-ts-buffer-point " +#?(:clj 2 + |:cljs 2)" + (setq-local clojure-ts-align-reader-conditionals t) + (call-interactively #'clojure-ts-align) + (expect (buffer-string) :to-equal " +#?(:clj 2 + :cljs 2)"))) + + (it "should remove extra commas" + (with-clojure-ts-buffer-point "{|:a 2, ,:c 4}" + (call-interactively #'clojure-ts-align) + (expect (buffer-string) :to-equal "{:a 2, :c 4}")))) diff --git a/test/samples/align.clj b/test/samples/align.clj new file mode 100644 index 0000000..cf361cb --- /dev/null +++ b/test/samples/align.clj @@ -0,0 +1,32 @@ +(ns align) + +(let [^long my-map {:hello "World" ;Hello + :foo + ^String (str "Foo" "Bar") + :number ^long 132 + :zz "hello"} + another {:this ^{:hello "world"} "is" + :a #long "1234" + :b {:this "is" + :nested "map"}}]) + + +{:foo "bar", :baz "Hello" + :a "b" :c "d"} + + +(clojure.core/with-redefs [hello "world" + foo "bar"] + (println hello foo)) + +(condp = 2 + 123 "Hello" + 99999 "World" + 234 nil) + +(let [a-long-name 10 + b 20]) + + +#?(:clj 2 + :cljs 2) From 605adba8babdc82013cd9a9a9f15a8f3ae587e64 Mon Sep 17 00:00:00 2001 From: Roman Rudakov Date: Wed, 16 Apr 2025 22:20:18 +0200 Subject: [PATCH 312/379] [#11] Add regex syntax highlighting --- CHANGELOG.md | 1 + README.md | 19 +++- clojure-ts-mode.el | 107 +++++++++++++++++---- screenshots/markdown-syntax-dark-theme.png | Bin 0 -> 59689 bytes screenshots/regex-syntax-dark-theme.png | Bin 0 -> 50440 bytes test/samples/regex.clj | 7 ++ 6 files changed, 115 insertions(+), 19 deletions(-) create mode 100644 screenshots/markdown-syntax-dark-theme.png create mode 100644 screenshots/regex-syntax-dark-theme.png create mode 100644 test/samples/regex.clj diff --git a/CHANGELOG.md b/CHANGELOG.md index 41e2a14..8c11acd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## main (unreleased) - [#16](https://github.com/clojure-emacs/clojure-ts-mode/issues/16): Introduce `clojure-ts-align`. +- [#11](https://github.com/clojure-emacs/clojure-ts-mode/issues/11): Enable regex syntax highlighting. ## 0.3.0 (2025-04-15) diff --git a/README.md b/README.md index f44f583..0c96c55 100644 --- a/README.md +++ b/README.md @@ -293,12 +293,29 @@ highlighted like regular clojure code. ### Highlight markdown syntax in docstrings By default markdown syntax is highlighted in the docstrings using -`markdown_inline` grammar. To disable this feature set +`markdown-inline` grammar. To disable this feature set ``` emacs-lisp (setopt clojure-ts-use-markdown-inline nil) ``` +Example of syntax highlighting: + + + +### Highlight regex syntax + +By default syntax inside regex literals is highlighted using [regex](https://github.com/tree-sitter/tree-sitter-regex) grammar. To +disable this feature set + +```emacs-lisp +(setopt clojure-ts-use-regex-parser nil) +``` + +Example of syntax highlighting: + + + ### Navigation and Evaluation To make forms inside of `(comment ...)` forms appear as top-level forms for evaluation and navigation, set diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index 7c16c01..f88f342 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -121,6 +121,12 @@ double quotes on the third column." :safe #'booleanp :package-version '(clojure-ts-mode . "0.2.3")) +(defcustom clojure-ts-use-regex-parser t + "When non-nil, use separate grammar to highlight regex syntax." + :type 'boolean + :safe #'booleanp + :package-version '(clojure-ts-mode . "0.4")) + (defcustom clojure-ts-auto-remap t "When non-nil, redirect all `clojure-mode' buffers to `clojure-ts-mode'." :safe #'booleanp @@ -407,17 +413,37 @@ if a third argument (the value) is provided. :*) (:match ,clojure-ts--interface-def-symbol-regexp @_def_symbol)))) -(defvar clojure-ts--treesit-range-settings - (treesit-range-rules - :embed 'markdown-inline - :host 'clojure - :local t - (clojure-ts--docstring-query '@capture))) +(defun clojure-ts--treesit-range-settings (use-markdown-inline use-regex) + "Return value for `treesit-range-settings' for `clojure-ts-mode'. -(defun clojure-ts--font-lock-settings (markdown-available) +When USE-MARKDOWN-INLINE is non-nil, include range settings for +markdown-inline parser. + +When USE-REGEX is non-nil, include range settings for regex parser." + (append + (when use-markdown-inline + (treesit-range-rules + :embed 'markdown-inline + :host 'clojure + :offset '(1 . -1) + :local t + (clojure-ts--docstring-query '@capture))) + (when use-regex + (treesit-range-rules + :embed 'regex + :host 'clojure + :offset '(2 . -1) + :local t + '((regex_lit) @capture))))) + +(defun clojure-ts--font-lock-settings (markdown-available regex-available) "Return font lock settings suitable for use in `treesit-font-lock-settings'. + When MARKDOWN-AVAILABLE is non-nil, includes rules for highlighting docstrings -with the markdown-inline grammar." +with the markdown-inline grammar. + +When REGEX-AVAILABLE is non-nil, includes rules for highlighting regex +literals with regex grammar." (append (treesit-font-lock-rules :feature 'string @@ -590,6 +616,44 @@ with the markdown-inline grammar." (inline_link (link_destination) @font-lock-constant-face) (shortcut_link (link_text) @link)]))) + (when regex-available + ;; Queries are adapted from + ;; https://github.com/tree-sitter/tree-sitter-regex/blob/v0.24.3/queries/highlights.scm. + (treesit-font-lock-rules + :feature 'regex + :language 'regex + :override t + '((["(" + ")" + "(?" + "(?:" + "(?<" + "(?P<" + "(?P=" + ">" + "[" + "]" + "{" + "}" + "[:" + ":]"] @font-lock-regexp-grouping-construct) + (["*" + "+" + "?" + "|" + "=" + "!"] @font-lock-property-name-face) + ((group_name) @font-lock-variable-name-face) + ((count_quantifier + [(decimal_digits) @font-lock-number-face + "," @font-lock-delimiter-face])) + ((flags) @font-lock-constant-face) + ((character_class + ["^" @font-lock-escape-face + (class_range "-" @font-lock-escape-face)])) + ((identity_escape) @font-lock-builtin-face) + ([(start_assertion) (end_assertion)] @font-lock-constant-face)))) + (treesit-font-lock-rules :feature 'quote :language 'clojure @@ -1555,7 +1619,9 @@ between BEG and END." "v0.0.13") (markdown-inline "https://github.com/MDeiml/tree-sitter-markdown" "v0.4.1" - "tree-sitter-markdown-inline/src")) + "tree-sitter-markdown-inline/src") + (regex "https://github.com/tree-sitter/tree-sitter-regex" + "v0.24.3")) "Intended to be used as the value for `treesit-language-source-alist'.") (defun clojure-ts--ensure-grammars () @@ -1584,20 +1650,22 @@ function can also be used to upgrade the grammars if they are outdated." (let ((treesit-language-source-alist clojure-ts-grammar-recipes)) (treesit-install-language-grammar grammar))))) -(defun clojure-ts-mode-variables (&optional markdown-available) +(defun clojure-ts-mode-variables (&optional markdown-available regex-available) "Initialize buffer-local variables for `clojure-ts-mode'. -See `clojure-ts--font-lock-settings' for usage of MARKDOWN-AVAILABLE." + +See `clojure-ts--font-lock-settings' for usage of MARKDOWN-AVAILABLE and +REGEX-AVAILABLE." (setq-local indent-tabs-mode nil) (setq-local comment-add 1) (setq-local comment-start ";") (setq-local treesit-font-lock-settings - (clojure-ts--font-lock-settings markdown-available)) + (clojure-ts--font-lock-settings markdown-available regex-available)) (setq-local treesit-font-lock-feature-list '((comment definition variable) (keyword string char symbol builtin type) - (constant number quote metadata doc) - (bracket deref function regex tagged-literals))) + (constant number quote metadata doc regex) + (bracket deref function tagged-literals))) (setq-local treesit-defun-prefer-top-level t) (setq-local treesit-defun-tactic 'top-level) @@ -1630,13 +1698,16 @@ See `clojure-ts--font-lock-settings' for usage of MARKDOWN-AVAILABLE." :syntax-table clojure-ts-mode-syntax-table (clojure-ts--ensure-grammars) (let ((use-markdown-inline (and clojure-ts-use-markdown-inline - (treesit-ready-p 'markdown-inline t)))) - (when use-markdown-inline - (setq-local treesit-range-settings clojure-ts--treesit-range-settings)) + (treesit-ready-p 'markdown-inline t))) + (use-regex (and clojure-ts-use-regex-parser + (treesit-ready-p 'regex t)))) + (setq-local treesit-range-settings + (clojure-ts--treesit-range-settings use-markdown-inline + use-regex)) (when (treesit-ready-p 'clojure) (treesit-parser-create 'clojure) - (clojure-ts-mode-variables use-markdown-inline) + (clojure-ts-mode-variables use-markdown-inline use-regex) (when clojure-ts--debug (setq-local treesit--indent-verbose t) diff --git a/screenshots/markdown-syntax-dark-theme.png b/screenshots/markdown-syntax-dark-theme.png new file mode 100644 index 0000000000000000000000000000000000000000..7a908acb9058f09c069961534df494a184a42910 GIT binary patch literal 59689 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 literal 0 HcmV?d00001 diff --git a/screenshots/regex-syntax-dark-theme.png b/screenshots/regex-syntax-dark-theme.png new file mode 100644 index 0000000000000000000000000000000000000000..ad7ee45dd7963bd9b77e1403333fdc6cb7128704 GIT binary patch literal 50440 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 literal 0 HcmV?d00001 diff --git a/test/samples/regex.clj b/test/samples/regex.clj new file mode 100644 index 0000000..f37b50a --- /dev/null +++ b/test/samples/regex.clj @@ -0,0 +1,7 @@ +(ns regex) + +(def email-pattern + #"[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?") + +(def simple-regex + #"^(\\d+).*[a|b|c|d].*[a-z0-9!#]$") From 7d76cce5659cc07802f8853970eccd3228b95d70 Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Wed, 23 Apr 2025 14:49:17 +0300 Subject: [PATCH 313/379] Seems dynamic font-locking actually works --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 0c96c55..7571565 100644 --- a/README.md +++ b/README.md @@ -416,7 +416,6 @@ Check out [this article](https://metaredux.com/posts/2024/02/19/cider-preliminar > [!NOTE] > > The dynamic indentation feature in CIDER requires clojure-ts-mode 0.3+. -> Dynamic font-locking currently doesn't work with clojure-ts-mode. ### Does `clojure-ts-mode` work with `inf-clojure`? From 8aed0089520199706f18659bffacfa99e14bf62d Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Wed, 23 Apr 2025 14:50:39 +0300 Subject: [PATCH 314/379] Use the spelling Tree-sitter consistently Turns out that's how the project is named officially. --- README.md | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 7571565..dcca818 100644 --- a/README.md +++ b/README.md @@ -19,13 +19,13 @@ for a very long time, but it suffers from a few [long-standing problems](https://github.com/clojure-emacs/clojure-mode#caveats), related to Emacs limitations baked into its design. The introduction of built-in support for Tree-sitter in Emacs 29 presents a natural opportunity to address many of -them. Enter `clojure-ts-mode`, which makes use of TreeSitter to provide: +them. Enter `clojure-ts-mode`, which makes use of Tree-sitter to provide: - fast, accurate and more granular font-locking - fast indentation - common Emacs functionality like structured navigation, `imenu` (an outline of a source buffer), current form inference (used internally by various Emacs modes and utilities), etc -Working with TreeSitter is significantly easier than the legacy Emacs APIs for font-locking and +Working with Tree-sitter is significantly easier than the legacy Emacs APIs for font-locking and indentation, which makes it easier to contribute to `clojure-ts-mode`, and to improve it in general. Keep in mind that the transition to `clojure-ts-mode` won't happen overnight for several reasons: @@ -55,8 +55,8 @@ Those will be addressed over the time, as more and more people use `clojure-ts-m ### Requirements -For `clojure-ts-mode` to work, you need Emacs 30+ built with TreeSitter support. -To check if your Emacs supports TreeSitter run the following (e.g. by using `M-:`): +For `clojure-ts-mode` to work, you need Emacs 30+ built with Tree-sitter support. +To check if your Emacs supports Tree-sitter run the following (e.g. by using `M-:`): ``` emacs-lisp (treesit-available-p) @@ -64,11 +64,11 @@ To check if your Emacs supports TreeSitter run the following (e.g. by using `M-: Additionally, you'll need to have Git and some C compiler (`cc`) installed and available in your `$PATH` (or Emacs's `exec-path`), for `clojure-ts-mode` to be able to install the required -TreeSitter grammars automatically. +Tree-sitter grammars automatically. > [!TIP] > -> As the TreeSitter support in Emacs is still fairly new and under active development itself, for optimal +> As the Tree-sitter support in Emacs is still fairly new and under active development itself, for optimal > results you should use the latest stable Emacs release or even the development version of Emacs. > See the "Caveats" section for more on the subject. @@ -121,7 +121,7 @@ Once installed, evaluate `clojure-ts-mode.el` and you should be ready to go. > `clojure-ts-mode` install the required grammars automatically, so for most > people no manual actions will be required. -`clojure-ts-mode` makes use of two TreeSitter grammars to work properly: +`clojure-ts-mode` makes use of two Tree-sitter grammars to work properly: - The Clojure grammar, mentioned earlier - [markdown-inline](https://github.com/MDeiml/tree-sitter-markdown), which @@ -139,7 +139,7 @@ each required grammar and make sure you're install the versions expected. (see ### Upgrading tree-sitter grammars -To reinstall or upgrade TreeSitter grammars, you can execute: +To reinstall or upgrade Tree-sitter grammars, you can execute: ```emacs-lisp M-x clojure-ts-reinstall-grammars @@ -374,14 +374,14 @@ After installing the package do the following. ## Caveats -As the TreeSitter Emacs APIs are new and keep evolving there are some +As the Tree-sitter Emacs APIs are new and keep evolving there are some differences in the behavior of `clojure-ts-mode` on different Emacs versions. Here are some notable examples: - On Emacs 29 the parent mode is `prog-mode`, but on Emacs 30+ it's both `prog-mode` and `clojure-mode` (this is very helpful when dealing with `derived-mode-p` checks) - Navigation by sexp/lists might work differently on Emacs versions lower - than 31. Starting with version 31, Emacs uses TreeSitter 'things' settings, if + than 31. Starting with version 31, Emacs uses Tree-sitter 'things' settings, if available, to rebind some commands. - The indentation of list elements with metadata is inconsistent with other collections. This inconsistency stems from the grammar's interpretation of From 63d863d0482a0172fd5ba0d9140a861c3bdf7e3e Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Wed, 23 Apr 2025 15:01:09 +0300 Subject: [PATCH 315/379] Code style --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index dcca818..96651de 100644 --- a/README.md +++ b/README.md @@ -216,6 +216,7 @@ within the expression's body, nested `n` levels deep, is indented by two spaces. These rule definitions fully reflect the [cljfmt rules](https://github.com/weavejester/cljfmt/blob/0.13.0/docs/INDENTS.md). For example: + - `do` has a rule `((:block 0))`. - `when` has a rule `((:block 1))`. - `defn` and `fn` have a rule `((:inner 0))`. From 1f6be8f01127d5cac94e28fc9bcc7659a5b924e2 Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Wed, 23 Apr 2025 15:02:57 +0300 Subject: [PATCH 316/379] Use consistent naming --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 96651de..02e598a 100644 --- a/README.md +++ b/README.md @@ -281,7 +281,7 @@ To highlight entire rich `comment` expression with the comment font face, set ``` By default this is `nil`, so that anything within a `comment` expression is -highlighted like regular clojure code. +highlighted like regular Clojure code. > [!TIP] > From e960a905ab9ae6c77101ca1e65dd76e59c7f4009 Mon Sep 17 00:00:00 2001 From: Roman Rudakov Date: Wed, 23 Apr 2025 20:35:35 +0200 Subject: [PATCH 317/379] [#16] Add support for automatic aligning forms --- CHANGELOG.md | 1 + README.md | 4 + clojure-ts-mode.el | 145 +++++++++++------ test/clojure-ts-mode-indentation-test.el | 188 ++++++++++++++++++++++- test/samples/align.clj | 27 +++- 5 files changed, 318 insertions(+), 47 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c11acd..281c425 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ - [#16](https://github.com/clojure-emacs/clojure-ts-mode/issues/16): Introduce `clojure-ts-align`. - [#11](https://github.com/clojure-emacs/clojure-ts-mode/issues/11): Enable regex syntax highlighting. +- [#16](https://github.com/clojure-emacs/clojure-ts-mode/issues/16): Add support for automatic aligning forms. ## 0.3.0 (2025-04-15) diff --git a/README.md b/README.md index 02e598a..251effc 100644 --- a/README.md +++ b/README.md @@ -259,6 +259,10 @@ Leads to the following: :other-key 2}) ``` +This can also be done automatically (as part of indentation) by turning on +`clojure-ts-align-forms-automatically`. This way it will happen whenever you +select some code and hit `TAB`. + Forms that can be aligned vertically are configured via the following variables: - `clojure-ts-align-reader-conditionals` - align reader conditionals as if they diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index f88f342..f1de91d 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -197,6 +197,22 @@ double quotes on the third column." :safe #'listp :type '(repeat string)) +(defcustom clojure-ts-align-forms-automatically nil + "If non-nil, vertically align some forms automatically. + +Automatically means it is done as part of indenting code. This applies +to binding forms (`clojure-ts-align-binding-forms'), to cond +forms (`clojure-ts-align-cond-forms') and to map literals. For +instance, selecting a map a hitting +\\`\\[indent-for-tab-command]' will align the +values like this: + +{:some-key 10 + :key2 20}" + :package-version '(clojure-ts-mode . "0.4") + :safe #'booleanp + :type 'boolean) + (defvar clojure-ts-mode-remappings '((clojure-mode . clojure-ts-mode) (clojurescript-mode . clojure-ts-clojurescript-mode) @@ -1340,6 +1356,9 @@ if NODE has metadata and its parent has type NODE-TYPE." ((parent-is "vec_lit") parent 1) ;; https://guide.clojure.style/#bindings-alignment ((parent-is "map_lit") parent 1) ;; https://guide.clojure.style/#map-keys-alignment ((parent-is "set_lit") parent 2) + ((parent-is "splicing_read_cond_lit") parent 4) + ((parent-is "read_cond_lit") parent 3) + ((parent-is "tagged_or_ctor_lit") parent 0) ;; https://guide.clojure.style/#body-indentation (clojure-ts--match-form-body clojure-ts--anchor-parent-skip-metadata 2) ;; https://guide.clojure.style/#threading-macros-alignment @@ -1447,32 +1466,56 @@ Regular expression and syntax analysis code is borrowed from BOUND bounds the whitespace search." (unwind-protect - (when-let* ((cur-sexp (treesit-node-first-child-for-pos root-node (point) t))) - (goto-char (treesit-node-start cur-sexp)) - (if (and (string= "sym_lit" (treesit-node-type cur-sexp)) - (clojure-ts--metadata-node-p (treesit-node-child cur-sexp 0 t)) - (and (not (treesit-node-child-by-field-name cur-sexp "value")) - (string-empty-p (clojure-ts--named-node-text cur-sexp)))) - (treesit-end-of-thing 'sexp 2 'restricted) - (treesit-end-of-thing 'sexp 1 'restrict)) - (when (looking-at ",") - (forward-char)) - ;; Move past any whitespace or comment. - (search-forward-regexp "\\([,\s\t]*\\)\\(;+.*\\)?" bound) - (pcase (syntax-after (point)) - ;; End-of-line, try again on next line. - (`(12) (clojure-ts--search-whitespace-after-next-sexp root-node bound)) - ;; Closing paren, stop here. - (`(5 . ,_) nil) - ;; Anything else is something to align. - (_ (point)))) + (let ((regex "\\([,\s\t]*\\)\\(;+.*\\)?")) + ;; If we're on an empty line, we should return match, otherwise + ;; `clojure-ts-align-separator' setting won't work. + (if (and (bolp) (looking-at-p "[[:blank:]]*$")) + (progn + (search-forward-regexp regex bound) + (point)) + (when-let* ((cur-sexp (treesit-node-first-child-for-pos root-node (point) t))) + (goto-char (treesit-node-start cur-sexp)) + (if (and (string= "sym_lit" (treesit-node-type cur-sexp)) + (clojure-ts--metadata-node-p (treesit-node-child cur-sexp 0 t)) + (and (not (treesit-node-child-by-field-name cur-sexp "value")) + (string-empty-p (clojure-ts--named-node-text cur-sexp)))) + (treesit-end-of-thing 'sexp 2 'restricted) + (treesit-end-of-thing 'sexp 1 'restrict)) + (when (looking-at ",") + (forward-char)) + ;; Move past any whitespace or comment. + (search-forward-regexp regex bound) + (pcase (syntax-after (point)) + ;; End-of-line, try again on next line. + (`(12) (progn + (forward-char 1) + (clojure-ts--search-whitespace-after-next-sexp root-node bound))) + ;; Closing paren, stop here. + (`(5 . ,_) nil) + ;; Anything else is something to align. + (_ (point)))))) (when (and bound (> (point) bound)) (goto-char bound)))) -(defun clojure-ts--get-nodes-to-align (region-node beg end) +(defun clojure-ts--region-node (beg end) + "Return the smallest node that covers buffer positions BEG to END." + (let* ((root-node (treesit-buffer-root-node 'clojure))) + (treesit-node-descendant-for-range root-node beg end t))) + +(defun clojure-ts--node-from-sexp-data (beg end sexp) + "Return updated node using SEXP data in the region between BEG and END." + (let* ((new-region-node (clojure-ts--region-node beg end)) + (sexp-beg (marker-position (plist-get sexp :beg-marker))) + (sexp-end (marker-position (plist-get sexp :end-marker)))) + (treesit-node-descendant-for-range new-region-node + sexp-beg + sexp-end + t))) + +(defun clojure-ts--get-nodes-to-align (beg end) "Return a plist of nodes data for alignment. -The search is limited by BEG, END and REGION-NODE. +The search is limited by BEG, END. Possible node types are: map, bindings-vec, cond or read-cond. @@ -1480,7 +1523,10 @@ The returned value is a list of property lists. Each property list includes `:sexp-type', `:node', `:beg-marker', and `:end-marker'. Markers are necessary to fetch the same nodes after their boundaries have changed." - (let* ((query (treesit-query-compile 'clojure + ;; By default `treesit-query-capture' captures all nodes that cross the range. + ;; We need to restrict it to only nodes inside of the range. + (let* ((region-node (clojure-ts--region-node beg end)) + (query (treesit-query-compile 'clojure (append `(((map_lit) @map) ((list_lit @@ -1492,7 +1538,8 @@ have changed." (:match ,(clojure-ts-symbol-regexp clojure-ts-align-cond-forms) @sym))) @cond)) (when clojure-ts-align-reader-conditionals - '(((read_cond_lit) @read-cond))))))) + '(((read_cond_lit) @read-cond) + ((splicing_read_cond_lit) @read-cond))))))) (thread-last (treesit-query-capture region-node query beg end) (seq-remove (lambda (elt) (eq (car elt) 'sym))) ;; When first node is reindented, all other nodes become @@ -1538,38 +1585,29 @@ between BEG and END." (interactive (if (use-region-p) (list (region-beginning) (region-end)) (save-excursion - (let ((start (clojure-ts--beginning-of-defun-pos)) - (end (clojure-ts--end-of-defun-pos))) - (list start end))))) + (if (not (treesit-defun-at-point)) + (user-error "No defun at point") + (let ((start (clojure-ts--beginning-of-defun-pos)) + (end (clojure-ts--end-of-defun-pos))) + (list start end)))))) (setq end (copy-marker end)) - (let* ((root-node (treesit-buffer-root-node 'clojure)) - ;; By default `treesit-query-capture' captures all nodes that cross the - ;; range. We need to restrict it to only nodes inside of the range. - (region-node (treesit-node-descendant-for-range root-node beg (marker-position end) t)) - (sexps-to-align (clojure-ts--get-nodes-to-align region-node beg (marker-position end)))) + (let* ((sexps-to-align (clojure-ts--get-nodes-to-align beg (marker-position end))) + ;; We have to disable it here to avoid endless recursion. + (clojure-ts-align-forms-automatically nil)) (save-excursion - (indent-region beg (marker-position end)) + (indent-region beg end) (dolist (sexp sexps-to-align) ;; After reindenting a node, all other nodes in the `sexps-to-align' ;; list become outdated, so we need to fetch updated nodes for every ;; iteration. - (let* ((new-root-node (treesit-buffer-root-node 'clojure)) - (new-region-node (treesit-node-descendant-for-range new-root-node - beg - (marker-position end) - t)) - (sexp-beg (marker-position (plist-get sexp :beg-marker))) - (sexp-end (marker-position (plist-get sexp :end-marker))) - (node (treesit-node-descendant-for-range new-region-node - sexp-beg - sexp-end - t)) + (let* ((node (clojure-ts--node-from-sexp-data beg (marker-position end) sexp)) (sexp-type (plist-get sexp :sexp-type)) (node-end (treesit-node-end node))) (clojure-ts--point-to-align-position sexp-type node) (align-region (point) node-end nil `((clojure-align (regexp . ,(lambda (&optional bound _noerror) - (clojure-ts--search-whitespace-after-next-sexp node bound))) + (let ((updated-node (clojure-ts--node-from-sexp-data beg (marker-position end) sexp))) + (clojure-ts--search-whitespace-after-next-sexp updated-node bound)))) (group . 1) (separate . ,clojure-ts-align-separator) (repeat . t))) @@ -1577,8 +1615,20 @@ between BEG and END." ;; After every iteration we have to re-indent the s-expression, ;; otherwise some can be indented inconsistently. (indent-region (marker-position (plist-get sexp :beg-marker)) - (marker-position (plist-get sexp :end-marker)))))))) + (plist-get sexp :end-marker)))) + ;; If `clojure-ts-align-separator' is used, `align-region' leaves trailing + ;; whitespaces on empty lines. + (delete-trailing-whitespace beg (marker-position end))))) + +(defun clojure-ts-indent-region (beg end) + "Like `indent-region', but also maybe align forms. +Forms between BEG and END are aligned according to +`clojure-ts-align-forms-automatically'." + (prog1 (let ((indent-region-function #'treesit-indent-region)) + (indent-region beg end)) + (when clojure-ts-align-forms-automatically + (clojure-ts-align beg end)))) (defvar clojure-ts-mode-map (let ((map (make-sparse-keymap))) @@ -1717,6 +1767,11 @@ REGEX-AVAILABLE." (treesit-major-mode-setup) + ;; We should assign this after calling `treesit-major-mode-setup', + ;; otherwise it will be owerwritten. + (when clojure-ts-align-forms-automatically + (setq-local indent-region-function #'clojure-ts-indent-region)) + ;; Initial indentation rules cache calculation. (setq clojure-ts--semantic-indent-rules-cache (clojure-ts--compute-semantic-indentation-rules-cache clojure-ts-semantic-indent-rules)) diff --git a/test/clojure-ts-mode-indentation-test.el b/test/clojure-ts-mode-indentation-test.el index 75ceb6d..fe181f9 100644 --- a/test/clojure-ts-mode-indentation-test.el +++ b/test/clojure-ts-mode-indentation-test.el @@ -75,6 +75,38 @@ DESCRIPTION is a string with the description of the spec." forms)))) +(defmacro when-aligning-it (description &rest forms) + "Return a buttercup spec. + +Check that all FORMS correspond to properly indented sexps. + +DESCRIPTION is a string with the description of the spec." + (declare (indent defun)) + `(it ,description + (let ((clojure-ts-align-forms-automatically t) + (clojure-ts-align-reader-conditionals t)) + ,@(mapcar (lambda (form) + `(with-temp-buffer + (clojure-ts-mode) + (insert "\n" ,(replace-regexp-in-string " +" " " form)) + (indent-region (point-min) (point-max)) + (should (equal (buffer-substring-no-properties (point-min) (point-max)) + ,(concat "\n" form))))) + forms)) + (let ((clojure-ts-align-forms-automatically nil)) + ,@(mapcar (lambda (form) + `(with-temp-buffer + (clojure-ts-mode) + (insert "\n" ,(replace-regexp-in-string " +" " " form)) + ;; This is to check that we did NOT align anything. Run + ;; `indent-region' and then check that no extra spaces + ;; where inserted besides the start of the line. + (indent-region (point-min) (point-max)) + (goto-char (point-min)) + (should-not (search-forward-regexp "\\([^\s\n]\\) +" nil 'noerror)))) + forms)))) + + ;; Provide font locking for easier test editing. (font-lock-add-keywords @@ -393,4 +425,158 @@ b |20])" (it "should remove extra commas" (with-clojure-ts-buffer-point "{|:a 2, ,:c 4}" (call-interactively #'clojure-ts-align) - (expect (buffer-string) :to-equal "{:a 2, :c 4}")))) + (expect (buffer-string) :to-equal "{:a 2, :c 4}")))) + +(describe "clojure-ts-align-forms-automatically" + ;; Copied from `clojure-mode' + (when-aligning-it "should basic forms" + " +{:this-is-a-form b + c d}" + + " +{:this-is b + c d}" + + " +{:this b + c d}" + + " +{:a b + c d}" + + " +(let [this-is-a-form b + c d])" + + " +(let [this-is b + c d])" + + " +(let [this b + c d])" + + " +(let [a b + c d])") + + (when-aligning-it "should handle a blank line" + " +(let [this-is-a-form b + c d + + another form + k g])" + + " +{:this-is-a-form b + c d + + :another form + k g}") + + (when-aligning-it "should handle basic forms (reversed)" + " +{c d + :this-is-a-form b}" + " +{c d + :this-is b}" + " +{c d + :this b}" + " +{c d + :a b}" + + " +(let [c d + this-is-a-form b])" + + " +(let [c d + this-is b])" + + " +(let [c d + this b])" + + " +(let [c d + a b])") + + (when-aligning-it "should handle multiple words" + " +(cond this is just + a test of + how well + multiple words will work)") + + (when-aligning-it "should handle nested maps" + " +{:a {:a :a + :bbbb :b} + :bbbb :b}") + + (when-aligning-it "should regard end as a marker" + " +{:a {:a :a + :aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa :a} + :b {:a :a + :aa :a}}") + + (when-aligning-it "should handle trailing commas" + " +{:a {:a :a, + :aa :a}, + :b {:a :a, + :aa :a}}") + + (when-aligning-it "should handle standard reader conditionals" + " +#?(:clj 2 + :cljs 2)") + + (when-aligning-it "should handle splicing reader conditional" + " +#?@(:clj [2] + :cljs [2])") + + (when-aligning-it "should handle sexps broken up by line comments" + " +(let [x 1 + ;; comment + xx 1] + xx)" + + " +{:x 1 + ;; comment + :xxx 2}" + + " +(case x + :aa 1 + ;; comment + :a 2)") + + (when-aligning-it "should work correctly when margin comments appear after nested, multi-line, non-terminal sexps" + " +(let [x {:a 1 + :b 2} ; comment + xx 3] + x)" + + " +{:aa {:b 1 + :cc 2} ;; comment + :a 1}}" + + " +(case x + :a (let [a 1 + aa (+ a 1)] + aa); comment + :aa 2)")) diff --git a/test/samples/align.clj b/test/samples/align.clj index cf361cb..f70e767 100644 --- a/test/samples/align.clj +++ b/test/samples/align.clj @@ -27,6 +27,31 @@ (let [a-long-name 10 b 20]) - #?(:clj 2 :cljs 2) + +#?@(:clj [2] + :cljs [4]) + +(let [this-is-a-form b + c d + + another form + k g]) + +{:this-is-a-form b + c d + + :another form + k g} + +(let [x {:a 1 + :b 2} ; comment + xx 3] + x) + +(case x + :a (let [a 1 + aa (+ a 1)] + aa); comment + :aa 2) From 09f7da6c1eb779e112dc24f315186ceadc000fa1 Mon Sep 17 00:00:00 2001 From: Roman Rudakov Date: Fri, 25 Apr 2025 17:39:09 +0200 Subject: [PATCH 318/379] [#82] Support outline-minor-mode comments headings --- CHANGELOG.md | 1 + README.md | 15 ++++++++++++++- clojure-ts-mode.el | 32 ++++++++++++++++++++++++++++++++ test/samples/outline.clj | 19 +++++++++++++++++++ 4 files changed, 66 insertions(+), 1 deletion(-) create mode 100644 test/samples/outline.clj diff --git a/CHANGELOG.md b/CHANGELOG.md index 281c425..cecf8a2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ - [#16](https://github.com/clojure-emacs/clojure-ts-mode/issues/16): Introduce `clojure-ts-align`. - [#11](https://github.com/clojure-emacs/clojure-ts-mode/issues/11): Enable regex syntax highlighting. - [#16](https://github.com/clojure-emacs/clojure-ts-mode/issues/16): Add support for automatic aligning forms. +- [#82](https://github.com/clojure-emacs/clojure-ts-mode/issues/82): Introduce `clojure-ts-outline-variant`. ## 0.3.0 (2025-04-15) diff --git a/README.md b/README.md index 251effc..da73c1e 100644 --- a/README.md +++ b/README.md @@ -340,7 +340,7 @@ Every new line in the docstrings is indented by `clojure-ts-docstring-fill-prefix-width` number of spaces (set to 2 by default which matches the `clojure-mode` settings). -#### imenu +### imenu `clojure-ts-mode` supports various types of definition that can be navigated using `imenu`, such as: @@ -353,6 +353,19 @@ using `imenu`, such as: - class (forms such as `deftype`, `defrecord` and `defstruct`) - keyword (for example, spec definitions) +### Integration with `outline-minor-mode` + +`clojure-ts-mode` supports two integration variants with +`outline-minor-mode`. The default variant uses special top-level comments (level +1 heading starts with three semicolons, level 2 heading starts with four, +etc.). The other variant treats def-like forms (the same forms produced by the +`imenu` command) as outline headings. To use the second option, use the +following customization: + +```emacs-lisp +(setopt clojure-ts-outline-variant 'imenu) +``` + ## Migrating to clojure-ts-mode If you are migrating to `clojure-ts-mode` note that `clojure-mode` is still required for cider and clj-refactor packages to work properly. diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index f1de91d..51c7996 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -133,6 +133,17 @@ double quotes on the third column." :type 'boolean :package-version '(clojure-ts-mode . "0.3")) +(defcustom clojure-ts-outline-variant 'comments + "Determines how `clojure-ts-mode' integrates with `outline-minor-mode'. + +If set to the symbol `comments', then top-level comments starting with +three or more semicolons will be treated as outline headings. If set to +`imenu', then def-like forms are treated as outline headings." + :safe #'symbolp + :type '(choice (const :tag "Use special comments" comments) + (const :tag "Use imenu" imenu)) + :package-version '(clojure-ts-mode . "0.4")) + (defcustom clojure-ts-align-reader-conditionals nil "Whether to align reader conditionals, as if they were maps." :package-version '(clojure-ts-mode . "0.4") @@ -913,6 +924,20 @@ Includes a dispatch value when applicable (defmethods)." By default `treesit-defun-name-function' is used to extract definition names. See `clojure-ts--standard-definition-node-name' for the implementation used.") +;;; Outline settings + +(defun clojure-ts--outline-predicate (node) + "Return TRUE if NODE is an outline heading comment." + (and (string= (treesit-node-type node) "comment") + (string-match-p "^\\(?:;;;;* \\).*" (treesit-node-text node)))) + +(defun clojure-ts--outline-level () + "Return the current level of the outline heading at point." + (let* ((node (treesit-outline--at-point)) + (node-text (treesit-node-text node))) + (string-match ";;\\(;+\\) " node-text) + (- (match-end 1) (match-beginning 1)))) + (defcustom clojure-ts-indent-style 'semantic "Automatic indentation style to use when mode `clojure-ts-mode' is run. @@ -1708,6 +1733,13 @@ REGEX-AVAILABLE." (setq-local indent-tabs-mode nil) (setq-local comment-add 1) (setq-local comment-start ";") + (when (equal clojure-ts-outline-variant 'comments) + ;; NOTE: If `imenu' option is selected for `clojure-ts-outline-variant', all + ;; necessary variables will be set automatically by + ;; `treesit-major-mode-setup'. + (setq-local treesit-outline-predicate #'clojure-ts--outline-predicate + outline-search-function #'treesit-outline-search + outline-level #'clojure-ts--outline-level)) (setq-local treesit-font-lock-settings (clojure-ts--font-lock-settings markdown-available regex-available)) diff --git a/test/samples/outline.clj b/test/samples/outline.clj new file mode 100644 index 0000000..b6722d2 --- /dev/null +++ b/test/samples/outline.clj @@ -0,0 +1,19 @@ +(ns outline) + + +;;; First heading level 1 + +(defn foo + [bar] + (println bar)) + +;;;; Heading level 2 + +(def baz + {:hello "World"}) + +;;; Second heading level 1 + +(defn hello-world + [] + (println "Hello, world!")) From 91b243141a1828b9ee3a3b9e9125b57a90240a06 Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Sun, 27 Apr 2025 10:24:45 +0300 Subject: [PATCH 319/379] Small README tweaks --- README.md | 36 ++++++++++++++++++++++-------------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index da73c1e..68812ce 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,9 @@ them. Enter `clojure-ts-mode`, which makes use of Tree-sitter to provide: - fast, accurate and more granular font-locking - fast indentation -- common Emacs functionality like structured navigation, `imenu` (an outline of a source buffer), current form inference (used internally by various Emacs modes and utilities), etc +- common Emacs functionality like structured navigation, `imenu` (an outline of + a source buffer), current form inference (used internally by various Emacs + modes and utilities), etc Working with Tree-sitter is significantly easier than the legacy Emacs APIs for font-locking and indentation, which makes it easier to contribute to `clojure-ts-mode`, and to improve it in general. @@ -156,13 +158,16 @@ Most configuration changes will require reverting any active `clojure-ts-mode` b ### Remapping of `clojure-mode` buffers -By default, `clojure-ts-mode` assumes command over all buffers and file extensions previously associated with `clojure-mode` (and derived major modes like `clojurescript-mode`). To disable this remapping, set +By default, `clojure-ts-mode` assumes command over all buffers and file +extensions previously associated with `clojure-mode` (and derived major modes +like `clojurescript-mode`). To disable this remapping, set ``` emacs-lisp (setopt clojure-ts-auto-remap nil) ``` -You can also use the commands `clojure-ts-activate` / `clojure-ts-deactivate` to interactively change this behavior. +You can also use the commands `clojure-ts-activate` / `clojure-ts-deactivate` to +interactively change this behavior. ### Indentation @@ -297,27 +302,28 @@ highlighted like regular Clojure code. ### Highlight markdown syntax in docstrings -By default markdown syntax is highlighted in the docstrings using -`markdown-inline` grammar. To disable this feature set +By default Markdown syntax is highlighted in the docstrings using +`markdown-inline` grammar. To disable this feature use: ``` emacs-lisp (setopt clojure-ts-use-markdown-inline nil) ``` -Example of syntax highlighting: +Example of Markdown syntax highlighting: -### Highlight regex syntax +### Highlight regular expression syntax -By default syntax inside regex literals is highlighted using [regex](https://github.com/tree-sitter/tree-sitter-regex) grammar. To -disable this feature set +By default syntax inside regex literals is highlighted using +[regex](https://github.com/tree-sitter/tree-sitter-regex) grammar. To disable +this feature use: ```emacs-lisp (setopt clojure-ts-use-regex-parser nil) ``` -Example of syntax highlighting: +Example of regex syntax highlighting: @@ -368,9 +374,10 @@ following customization: ## Migrating to clojure-ts-mode -If you are migrating to `clojure-ts-mode` note that `clojure-mode` is still required for cider and clj-refactor packages to work properly. +If you are migrating to `clojure-ts-mode` note that `clojure-mode` is still +required for cider and clj-refactor packages to work properly. -After installing the package do the following. +After installing the package do the following: - Check the value of `clojure-mode-hook` and copy all relevant hooks to `clojure-ts-mode-hook`. @@ -381,7 +388,8 @@ After installing the package do the following. (add-hook 'clojure-ts-mode-hook #'clj-refactor-mode) ``` -- Update `.dir-locals.el` in all of your Clojure projects to activate directory local variables in `clojure-ts-mode`. +- Update `.dir-locals.el` in all of your Clojure projects to activate directory + local variables in `clojure-ts-mode`. ``` emacs-lisp ((clojure-mode @@ -411,7 +419,7 @@ and `clojure-mode` (this is very helpful when dealing with `derived-mode-p` chec ### What `clojure-mode` features are currently missing? -As of version 0.2.x, the most obvious missing feature are the various +As of version 0.4.x, the most obvious missing feature are the various refactoring commands in `clojure-mode`. ### Does `clojure-ts-mode` work with CIDER? From 43dbaddc506a174f97607599e6ab082db79462da Mon Sep 17 00:00:00 2001 From: Roman Rudakov Date: Fri, 25 Apr 2025 21:36:10 +0200 Subject: [PATCH 320/379] Fix some issues with short anonymous functions --- CHANGELOG.md | 4 ++ clojure-ts-mode.el | 58 +++++++++++++++++++----- test/clojure-ts-mode-font-lock-test.el | 4 ++ test/clojure-ts-mode-indentation-test.el | 17 +++++++ test/samples/test.clj | 13 ++++++ 5 files changed, 85 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cecf8a2..d40be97 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ - [#11](https://github.com/clojure-emacs/clojure-ts-mode/issues/11): Enable regex syntax highlighting. - [#16](https://github.com/clojure-emacs/clojure-ts-mode/issues/16): Add support for automatic aligning forms. - [#82](https://github.com/clojure-emacs/clojure-ts-mode/issues/82): Introduce `clojure-ts-outline-variant`. +- [#86](https://github.com/clojure-emacs/clojure-ts-mode/pull/86): Better handling of function literals: + - Syntax highlighting of built-in keywords. + - Consistent indentation with regular forms. + - Support for automatic aligning forms. ## 0.3.0 (2025-04-15) diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index 51c7996..340e016 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -514,6 +514,13 @@ literals with regex grammar." (:equal "clojure.core" @ns)) name: (sym_name) @font-lock-keyword-face)) (:match ,clojure-ts--builtin-symbol-regexp @font-lock-keyword-face)) + ((anon_fn_lit meta: _ :* :anchor (sym_lit !namespace name: (sym_name) @font-lock-keyword-face)) + (:match ,clojure-ts--builtin-symbol-regexp @font-lock-keyword-face)) + ((anon_fn_lit meta: _ :* :anchor + (sym_lit namespace: ((sym_ns) @ns + (:equal "clojure.core" @ns)) + name: (sym_name) @font-lock-keyword-face)) + (:match ,clojure-ts--builtin-symbol-regexp @font-lock-keyword-face)) ((sym_name) @font-lock-builtin-face (:match ,clojure-ts--builtin-dynamic-var-regexp @font-lock-builtin-face))) @@ -726,6 +733,14 @@ literals with regex grammar." "Return non-nil if NODE is a Clojure list." (string-equal "list_lit" (treesit-node-type node))) +(defun clojure-ts--anon-fn-node-p (node) + "Return non-nil if NODE is a Clojure function literal." + (string-equal "anon_fn_lit" (treesit-node-type node))) + +(defun clojure-ts--opening-paren-node-p (node) + "Return non-nil if NODE is an opening paren." + (string-equal "(" (treesit-node-text node))) + (defun clojure-ts--symbol-node-p (node) "Return non-nil if NODE is a Clojure symbol." (string-equal "sym_lit" (treesit-node-type node))) @@ -1249,7 +1264,8 @@ PARENT not should be a list. If first symbol in the expression has an indentation rule in `clojure-ts--semantic-indent-rules-defaults' or `clojure-ts-semantic-indent-rules' check if NODE should be indented according to the rule. If NODE is nil, use next node after BOL." - (and (clojure-ts--list-node-p parent) + (and (or (clojure-ts--list-node-p parent) + (clojure-ts--anon-fn-node-p parent)) (let* ((first-child (clojure-ts--node-child-skip-metadata parent 0))) (when-let* ((rule (clojure-ts--find-semantic-rule node parent 0))) (and (not (clojure-ts--match-with-metadata node)) @@ -1265,7 +1281,8 @@ according to the rule. If NODE is nil, use next node after BOL." (defun clojure-ts--match-function-call-arg (node parent _bol) "Match NODE if PARENT is a list expressing a function or macro call." - (and (clojure-ts--list-node-p parent) + (and (or (clojure-ts--list-node-p parent) + (clojure-ts--anon-fn-node-p parent)) ;; Can the following two clauses be replaced by checking indexes? ;; Does the second child exist, and is it not equal to the current node? (treesit-node-child parent 1 t) @@ -1284,7 +1301,8 @@ according to the rule. If NODE is nil, use next node after BOL." "Match NODE if it is an argument to a PARENT threading macro." ;; We want threading macros to indent 2 only if the ->> is on it's own line. ;; If not, then align function arg. - (and (clojure-ts--list-node-p parent) + (and (or (clojure-ts--list-node-p parent) + (clojure-ts--anon-fn-node-p parent)) (let ((first-child (treesit-node-child parent 0 t))) (clojure-ts--symbol-matches-p clojure-ts--threading-macro @@ -1335,7 +1353,7 @@ according to the rule. If NODE is nil, use next node after BOL." (and prev-sibling (clojure-ts--metadata-node-p prev-sibling)))) -(defun clojure-ts--anchor-parent-skip-metadata (_node parent _bol) +(defun clojure-ts--anchor-parent-opening-paren (_node parent _bol) "Return position of PARENT start for NODE. If PARENT has optional metadata we skip it and return starting position @@ -1343,11 +1361,9 @@ of the first child's opening paren. NOTE: This serves as an anchor function to resolve an indentation issue for forms with type hints." - (let ((first-child (treesit-node-child parent 0 t))) - (if (clojure-ts--metadata-node-p first-child) - ;; We don't need named node here - (treesit-node-start (treesit-node-child parent 1)) - (treesit-node-start parent)))) + (thread-first parent + (treesit-search-subtree #'clojure-ts--opening-paren-node-p nil t 1) + (treesit-node-start))) (defun clojure-ts--match-collection-item-with-metadata (node-type) "Return a matcher for a collection item with metadata by NODE-TYPE. @@ -1359,6 +1375,18 @@ if NODE has metadata and its parent has type NODE-TYPE." (treesit-node-type (clojure-ts--node-with-metadata-parent node))))) +(defun clojure-ts--anchor-nth-sibling (n &optional named) + "Return the start of the Nth child of PARENT. + +NAMED non-nil means count only named nodes. + +NOTE: This is a replacement for built-in `nth-sibling' anchor preset, +which doesn't work properly for named nodes (see the bug +https://debbugs.gnu.org/cgi/bugreport.cgi?bug=78065)" + (lambda (_n parent &rest _) + (treesit-node-start + (treesit-node-child parent n named)))) + (defun clojure-ts--semantic-indent-rules () "Return a list of indentation rules for `treesit-simple-indent-rules'." `((clojure @@ -1385,11 +1413,11 @@ if NODE has metadata and its parent has type NODE-TYPE." ((parent-is "read_cond_lit") parent 3) ((parent-is "tagged_or_ctor_lit") parent 0) ;; https://guide.clojure.style/#body-indentation - (clojure-ts--match-form-body clojure-ts--anchor-parent-skip-metadata 2) + (clojure-ts--match-form-body clojure-ts--anchor-parent-opening-paren 2) ;; https://guide.clojure.style/#threading-macros-alignment (clojure-ts--match-threading-macro-arg prev-sibling 0) ;; https://guide.clojure.style/#vertically-align-fn-args - (clojure-ts--match-function-call-arg (nth-sibling 2 nil) 0) + (clojure-ts--match-function-call-arg ,(clojure-ts--anchor-nth-sibling 1 t) 0) ;; https://guide.clojure.style/#one-space-indent ((parent-is "list_lit") parent 1)))) @@ -1561,6 +1589,14 @@ have changed." ((list_lit ((sym_lit) @sym (:match ,(clojure-ts-symbol-regexp clojure-ts-align-cond-forms) @sym))) + @cond) + ((anon_fn_lit + ((sym_lit) @sym + (:match ,(clojure-ts-symbol-regexp clojure-ts-align-binding-forms) @sym)) + (vec_lit) @bindings-vec)) + ((anon_fn_lit + ((sym_lit) @sym + (:match ,(clojure-ts-symbol-regexp clojure-ts-align-cond-forms) @sym))) @cond)) (when clojure-ts-align-reader-conditionals '(((read_cond_lit) @read-cond) diff --git a/test/clojure-ts-mode-font-lock-test.el b/test/clojure-ts-mode-font-lock-test.el index 02e0fa4..05eba9e 100644 --- a/test/clojure-ts-mode-font-lock-test.el +++ b/test/clojure-ts-mode-font-lock-test.el @@ -169,6 +169,10 @@ DESCRIPTION is the description of the spec." (2 5 font-lock-type-face) (8 9 font-lock-keyword-face))) + (when-fontifying-it "function literals" + ("#(or one two)" + (3 4 font-lock-keyword-face))) + (when-fontifying-it "should highlight function name in all known forms" ("(letfn [(add [x y] (+ x y)) diff --git a/test/clojure-ts-mode-indentation-test.el b/test/clojure-ts-mode-indentation-test.el index fe181f9..942175a 100644 --- a/test/clojure-ts-mode-indentation-test.el +++ b/test/clojure-ts-mode-indentation-test.el @@ -184,6 +184,12 @@ DESCRIPTION is a string with the description of the spec." (#'foo 5 6)") +(when-indenting-it "should support function literals" + " +#(or true + false + %)") + (when-indenting-it "should support block-0 expressions" " (do (aligned) @@ -462,6 +468,17 @@ b |20])" (let [a b c d])") + (when-aligning-it "should handle function literals" + " +#(let [hello 1 + foo \"hone\"] + (pringln hello))" + + " +^{:some :metadata} #(let [foo % + bar-zzz %] + foo)") + (when-aligning-it "should handle a blank line" " (let [this-is-a-form b diff --git a/test/samples/test.clj b/test/samples/test.clj index 842ff5a..18ead86 100644 --- a/test/samples/test.clj +++ b/test/samples/test.clj @@ -41,6 +41,19 @@ 0 0i) +;; Function literals + +^{:some "metadata"} #(let [foo % + bar-zzz %] + foo) + +#(or one + two) + +#(let [hello 1 + foo "hone"] + (pringln hello)) + ;; examples of valid namespace definitions (comment (ns .validns) From b5c1b0787d90851ef291c1b3a878202812d1fe76 Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Tue, 29 Apr 2025 08:05:15 +0300 Subject: [PATCH 321/379] Update a few references to Tree-sitter --- README.md | 8 ++++---- clojure-ts-mode.el | 12 ++++++------ 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 68812ce..cf83375 100644 --- a/README.md +++ b/README.md @@ -4,13 +4,13 @@ [![License GPL 3][badge-license]][copying] [![Lint Status](https://github.com/clojure-emacs/clojure-ts-mode/actions/workflows/lint-emacs.yml/badge.svg)](https://github.com/clojure-emacs/clojure-ts-mode/actions/workflows/lint-emacs.yml) -# Clojure Tree-Sitter Mode +# Clojure Tree-sitter Mode `clojure-ts-mode` is an Emacs major mode that provides font-lock (syntax highlighting), indentation, and navigation support for the [Clojure(Script) programming language](http://clojure.org), powered by the [tree-sitter-clojure](https://github.com/sogaiu/tree-sitter-clojure) -[tree-sitter](https://tree-sitter.github.io/tree-sitter/) grammar. +[Tree-sitter](https://tree-sitter.github.io/tree-sitter/) grammar. ## Rationale @@ -116,7 +116,7 @@ git clone https://github.com/clojure-emacs/clojure-ts-mode.git Once installed, evaluate `clojure-ts-mode.el` and you should be ready to go. -### Install tree-sitter grammars +### Install Tree-sitter grammars > [!NOTE] > @@ -139,7 +139,7 @@ option to install it manually, Please, refer to the installation instructions of each required grammar and make sure you're install the versions expected. (see `clojure-ts-grammar-recipes` for details) -### Upgrading tree-sitter grammars +### Upgrading Tree-sitter grammars To reinstall or upgrade Tree-sitter grammars, you can execute: diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index 340e016..e4ac5e1 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -17,7 +17,7 @@ ;; Provides font-lock, indentation, and navigation for the ;; Clojure programming language (http://clojure.org). -;; For the tree-sitter grammar this mode is based on, +;; For the Tree-sitter grammar this mode is based on, ;; see https://github.com/sogaiu/tree-sitter-clojure. ;; Using clojure-ts-mode with paredit or smartparens is highly recommended. @@ -66,7 +66,7 @@ (declare-function treesit-node-child-by-field-name "treesit.c") (defgroup clojure-ts nil - "Major mode for editing Clojure code with tree-sitter." + "Major mode for editing Clojure code with Tree-sitter." :prefix "clojure-ts-" :group 'languages :link '(url-link :tag "GitHub" "https://github.com/clojure-emacs/clojure-ts-mode") @@ -89,7 +89,7 @@ itself." :package-version '(clojure-ts-mode . "0.1.3")) (defcustom clojure-ts-ensure-grammars t - "When non-nil, ensure required tree-sitter grammars are installed." + "When non-nil, ensure required Tree-sitter grammars are installed." :safe #'booleanp :type 'boolean :package-version '(clojure-ts-mode . "0.2.0")) @@ -1741,7 +1741,7 @@ Forms between BEG and END are aligned according to (dolist (recipe clojure-ts-grammar-recipes) (let ((grammar (car recipe))) (unless (treesit-language-available-p grammar nil) - (message "Installing %s tree-sitter grammar" grammar) + (message "Installing %s Tree-sitter grammar" grammar) ;; `treesit-language-source-alist' is dynamically scoped. ;; Binding it in this let expression allows ;; `treesit-install-language-gramamr' to pick up the grammar recipes @@ -1757,7 +1757,7 @@ function can also be used to upgrade the grammars if they are outdated." (interactive) (dolist (recipe clojure-ts-grammar-recipes) (let ((grammar (car recipe))) - (message "Installing %s tree-sitter grammar" grammar) + (message "Installing %s Tree-sitter grammar" grammar) (let ((treesit-language-source-alist clojure-ts-grammar-recipes)) (treesit-install-language-grammar grammar))))) @@ -1932,7 +1932,7 @@ Useful if you want to switch to the `clojure-mode's mode mappings." ;; nbb scripts are ClojureScript source files (add-to-list 'interpreter-mode-alist '("nbb" . clojure-ts-clojurescript-mode)) (clojure-ts--register-novel-modes))) - (message "Clojure TS Mode will not be activated as tree-sitter support is missing.")) + (message "Clojure TS Mode will not be activated as Tree-sitter support is missing.")) (defvar clojure-ts--find-ns-query (treesit-query-compile From 2875629cbb4cfa1b289c69345d615b6c492ef6a6 Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Tue, 29 Apr 2025 08:19:15 +0300 Subject: [PATCH 322/379] Improve a bit the design doc --- doc/design.md | 78 +++++++++++++++++++++++++++++---------------------- 1 file changed, 45 insertions(+), 33 deletions(-) diff --git a/doc/design.md b/doc/design.md index 0d2df9c..8afeaff 100644 --- a/doc/design.md +++ b/doc/design.md @@ -4,47 +4,50 @@ This document is still a work in progress. Clojure-ts-mode is based on the tree-sitter-clojure grammar. -If you want to contribute to clojure-ts-mode, it is recommend that you familiarize yourself with how tree-sitter works. -The official documentation is a great place to start: https://tree-sitter.github.io/tree-sitter/ -These guides for Emacs tree-sitter development are also useful -- https://casouri.github.io/note/2023/tree-sitter-starter-guide/index.html +If you want to contribute to clojure-ts-mode, it is recommend that you familiarize yourself with how Tree-sitter works. +The official documentation is a great place to start: +These guides for Emacs Tree-sitter development are also useful + +- - `Developing major modes with tree-sitter` (From the Emacs 29+ Manual, `C-h i`, search for `tree-sitter`) In short: -Tree-sitter is a tool that generates parser libraries for programming languages, and provides an API for interacting with those parsers. -The generated parsers can create syntax trees from source code text. -The nodes of those trees are defined by the grammar. -Emacs can use these generated parsers to provide major modes with things like syntax highlighting, indentation, navigation, structural editing, and many other things. + +- Tree-sitter is a tool that generates parser libraries for programming languages, and provides an API for interacting with those parsers. +- The generated parsers can create syntax trees from source code text. +- The nodes of those trees are defined by the grammar. +- Emacs can use these generated parsers to provide major modes with things like syntax highlighting, indentation, navigation, structural editing, and many other things. ## Important Definitions -- Parser: A dynamic library compiled from C source code that is generated by the tree-sitter tool. A parser reads source code for a particular language and produces a syntax tree. -- Grammar: The rules that define how a parser will create the syntax tree for a language. The grammar is written in javascript. Tree-sitter tooling consumes the grammar as input and outputs C source (which can be compiled into a parser) +- Parser: A dynamic library compiled from C source code that is generated by the Tree-sitter tool. A parser reads source code for a particular language and produces a syntax tree. +- Grammar: The rules that define how a parser will create the syntax tree for a language. The grammar is written in JavaScript. Tree-sitter tooling consumes the grammar as input and outputs C source (which can be compiled into a parser) - Syntax Tree: a tree data structure comprised of syntax nodes that represents some source code text. - - Concrete Syntax Tree: Syntax trees that contain nodes for every token in the source code, including things likes brackets and parentheses. Tree-sitter creates Concrete Syntax Trees. - - Abstract Syntax Tree: A syntax tree with less important details removed. An AST may contain a node for a list, but not individual parentheses. Tree-sitter does not create Abstract Syntax Trees. + - Concrete Syntax Tree: Syntax trees that contain nodes for every token in the source code, including things likes brackets and parentheses. Tree-sitter creates Concrete Syntax Trees. + - Abstract Syntax Tree: A syntax tree with less important details removed. An AST may contain a node for a list, but not individual parentheses. Tree-sitter does not create Abstract Syntax Trees. - Syntax Node: A node in a syntax tree. It represents some subset of a source code text. Each node has a type, defined by the grammar used to produce it. Some common node types represent language constructs like strings, integers, operators. - - Named Syntax Node: A node that can be identified by a name given to it in the tree-sitter Grammar. In clojure-ts-mode, `list_lit` is a named node for lists. - - Anonymous Syntax Node: A node that cannot be identified by a name. In the Grammar these are identified by simple strings, not by complex Grammar rules. In clojure-ts-mode, `"("` and `")"` are anonymous nodes. + - Named Syntax Node: A node that can be identified by a name given to it in the Tree-sitter Grammar. In clojure-ts-mode, `list_lit` is a named node for lists. + - Anonymous Syntax Node: A node that cannot be identified by a name. In the Grammar these are identified by simple strings, not by complex Grammar rules. In clojure-ts-mode, `"("` and `")"` are anonymous nodes. - Font Locking: What Emacs calls "Syntax Highlighting". ## tree-sitter-clojure -Clojure-ts-mode uses the tree-sitter-clojure grammar, which can be found at https://github.com/sogaiu/tree-sitter-clojure -The clojure-ts-mode grammar provides very basic, low level nodes that try to match clojure's very light syntax. +Clojure-ts-mode uses the tree-sitter-clojure grammar, which can be found at +The clojure-ts-mode grammar provides very basic, low level nodes that try to match Clojure's very light syntax. There are nodes to represent: + - Symbols (sym_lit) - - Contain (sym_ns) and (sym_name) nodes + - Contain (sym_ns) and (sym_name) nodes - Keywords (kwd_lit) - - Contain (kwd_ns) and (kw_name) nodes + - Contain (kwd_ns) and (kw_name) nodes - Strings (str_lit) - Chars (char_lit) - Nil (nil_lit) - Booleans (bool_lit) - Numbers (num_lit) - Comments (comment, dis_expr) - - dis_expr is the `#_` discard expression + - dis_expr is the `#_` discard expression - Lists (list_list) - Vectors (vec_lit) - Maps (map_lit) @@ -61,7 +64,7 @@ will produce a parse tree like so ``` (vec_lit - meta: (meta_lit + meta: (meta_lit value: (kwd_lit name: (kwd_name))) value: (num_lit)) ``` @@ -70,12 +73,12 @@ The best place to learn more about the tree-sitter-clojure grammar is to read th ### Clojure Syntax, not Clojure Semantics -An important observation that anyone familiar with popular tree-sitter grammars may have picked up on is that there are no nodes representing things like functions, macros, types, and other semantic concepts. -Representing the semantics of Clojure in a tree-sitter grammar is much more difficult than traditional languages that do not use macros heavily like Clojure and other lisps. -To understand what an expression represents in Clojure source code requires macro-expansion of the source code. -Macro-expansion requires a runtime, and tree-sitter does not have access to a Clojure runtime and will never have access to a Clojure runtime. -Additionally tree-sitter never looks back on what it has parsed, only forward, considering what is directly ahead of it. So even if it could identify a macro like `myspecialdef` it would forget about it as soon as it moved passed the declaring `defmacro` node. -Another way to think about this: tree-sitter is designed to be fast and good-enough for tooling to implement syntax highlighting, indentation, and other editing conveniences. It is not meant for interpreting and execution. +An important observation that anyone familiar with popular Tree-sitter grammars may have picked up on is that there are no nodes representing things like functions, macros, types, and other semantic concepts. +Representing the semantics of Clojure in a Tree-sitter grammar is much more difficult than traditional languages that do not use macros heavily like Clojure and other lisps. +To understand what an expression represents in Clojure source code requires macro-expansion of the source code. +Macro-expansion requires a runtime, and Tree-sitter does not have access to a Clojure runtime and will never have access to a Clojure runtime. +Additionally Tree-sitter never looks back on what it has parsed, only forward, considering what is directly ahead of it. So even if it could identify a macro like `myspecialdef` it would forget about it as soon as it moved passed the declaring `defmacro` node. +Another way to think about this: Tree-sitter is designed to be fast and good-enough for tooling to implement syntax highlighting, indentation, and other editing conveniences. It is not meant for interpreting and execution. #### Example 1: False Negative Function Classification @@ -88,9 +91,8 @@ Consider the following macro (defn2 dog [] "bark") ``` - This macro lets the caller define a function, but a hypothetical tree-sitter-clojure semantic grammar might just see a function call where a variable dog is passed as an argument. -How should tree-sitter know that `dog` should be highlighted like function? It would have to evaluate the `defn2` macro to understand that. +How should Tree-sitter know that `dog` should be highlighted like function? It would have to evaluate the `defn2` macro to understand that. #### Example 2: False Positive Function Classification @@ -105,13 +107,13 @@ How should tree-sitter know that `dog` should be highlighted like function? It w evaluates to 1, and the following -``` +```clojure (foo) ``` evaluates to 1. -How is tree-sitter supposed to understand that `(defn foo [] 2)` of the expression `(no-defn (defn foo [] 2))` is not a function declaration? It would have to evaluate the `no-defn` macro. +How is Tree-sitter supposed to understand that `(defn foo [] 2)` of the expression `(no-defn (defn foo [] 2))` is not a function declaration? It would have to evaluate the `no-defn` macro. #### Syntax and Semantics: Conclusions @@ -122,17 +124,27 @@ Instead, it is up to the emacs-lisp code and other consumers of the tree-sitter- There are some pros and cons of this decision for tree-sitter-clojure to only consider syntax and not semantics. Some of the (non-exhaustive) upsides: + - No semantic false positives or negatives in the parse tree. - Simple grammar to maintain with less nodes and rules - Small, fast grammar (with a small set of grammar rules, tree-sitter-clojure has one of the smallest binaries and fastest grammars in widespread use) - Stability: the grammar changes infrequently and is very stable for downstream consumers -And the primary downside: Semantics must be (re)-implemented in tools that consume the grammar. While this results in more work for tooling authors, the tools that use the grammar are easier to change than the grammar itself. The inaccurate nature of statically interpreting Clojure semantics means that not every decision made for the grammar would meet the needs of the various grammar consumers. This would lead to bugs and feature requests. Nearly all changes to the grammar will result in some sort of breakages to its consumers, so changes are best avoided once the grammar has stabilized. Therefore avoiding these semantic interpretations in the grammar is one of the best ways to minimize changes in the grammar. +And the primary downside: Semantics must be (re)-implemented in tools that +consume the grammar. While this results in more work for tooling authors, the +tools that use the grammar are easier to change than the grammar itself. The +inaccurate nature of statically interpreting Clojure semantics means that not +every decision made for the grammar would meet the needs of the various grammar +consumers. This would lead to bugs and feature requests. Nearly all changes to +the grammar will result in some sort of breakages to its consumers, so changes +are best avoided once the grammar has stabilized. Therefore avoiding these +semantic interpretations in the grammar is one of the best ways to minimize +changes in the grammar. #### Further Reading -- https://github.com/sogaiu/tree-sitter-clojure/blob/master/doc/scope.md -- https://tree-sitter.github.io/tree-sitter/using-parsers#named-vs-anonymous-nodes +- +- ## Syntax Highlighting From ff3969c1efb9a8b5651a94f0a78c166a904372a3 Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Tue, 29 Apr 2025 08:23:26 +0300 Subject: [PATCH 323/379] Remove mentions of the mailing list It was never used, so it seems safe to say we don't really need it. --- CONTRIBUTING.md | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b72898e..9a6a3fe 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -35,21 +35,9 @@ clojure-ts-mode (version 2.1.1) * Open a [pull request][4] that relates to *only* one subject with a clear title and description in grammatically correct, complete sentences. -## I don't have a github account - -or maybe you would rather use email. That is okay. - -If you prefer you can also send a message to the [mailing list][7]. -This mailing list is not the [primary issue tracker][1]. -All the same etiquette rules above apply to the mailing list as well. -Submitted patches will be turned into pull requests. -Any issues reported on the mailing list will be copied to the issue tracker -where the primary work will take place. - [1]: https://github.com/clojure-emacs/clojure-ts-mode/issues [2]: http://gun.io/blog/how-to-github-fork-branch-and-pull-request [3]: http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html [4]: https://help.github.com/articles/using-pull-requests [5]: http://gitready.com/advanced/2009/02/10/squashing-commits-with-rebase.html [6]: https://github.com/clojure-emacs/clojure-ts-mode/blob/master/CHANGELOG.md -[7]: https://lists.sr.ht/~dannyfreeman/clojure-ts-mode From 4bdd7f2111fe8fd32cff1256c1e7dc54c17d8552 Mon Sep 17 00:00:00 2001 From: Roman Rudakov Date: Mon, 28 Apr 2025 09:08:44 +0200 Subject: [PATCH 324/379] Add unwind refactoring commands --- CHANGELOG.md | 1 + README.md | 22 ++ clojure-ts-mode.el | 196 +++++++++++++++++- test/clojure-ts-mode-font-lock-test.el | 6 +- ...clojure-ts-mode-refactor-threading-test.el | 166 +++++++++++++++ test/clojure-ts-mode-util-test.el | 64 +++--- test/samples/refactoring.clj | 37 ++++ test/test-helper.el | 9 +- 8 files changed, 461 insertions(+), 40 deletions(-) create mode 100644 test/clojure-ts-mode-refactor-threading-test.el create mode 100644 test/samples/refactoring.clj diff --git a/CHANGELOG.md b/CHANGELOG.md index d40be97..7dd9d56 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ - Syntax highlighting of built-in keywords. - Consistent indentation with regular forms. - Support for automatic aligning forms. +- [#88](https://github.com/clojure-emacs/clojure-ts-mode/pull/88): Introduce `clojure-ts-unwind` and `clojure-ts-unwind-all`. ## 0.3.0 (2025-04-15) diff --git a/README.md b/README.md index cf83375..f2d656c 100644 --- a/README.md +++ b/README.md @@ -372,6 +372,28 @@ following customization: (setopt clojure-ts-outline-variant 'imenu) ``` +## Refactoring support + +### Threading macros related features + +`clojure-unwind`: Unwind a threaded expression. Supports both `->>`/`some->>` +and `->`/`some->`. + +`clojure-unwind-all`: Fully unwind a threaded expression removing the threading +macro. + +### Default keybindings + +| Keybinding | Command | +|:------------|:--------------------| +| `C-c SPC` | `clojure-ts-align` | +| `C-c C-r u` | `clojure-ts-unwind` | + +### Customize refactoring commands prefix + +By default prefix for all refactoring commands is `C-c C-r`. It can be changed +by customizing `clojure-ts-refactor-map-prefix` variable. + ## Migrating to clojure-ts-mode If you are migrating to `clojure-ts-mode` note that `clojure-mode` is still diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index e4ac5e1..4559e60 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -57,6 +57,7 @@ (require 'treesit) (require 'align) +(require 'subr-x) (declare-function treesit-parser-create "treesit.c") (declare-function treesit-node-eq "treesit.c") @@ -144,6 +145,11 @@ three or more semicolons will be treated as outline headings. If set to (const :tag "Use imenu" imenu)) :package-version '(clojure-ts-mode . "0.4")) +(defcustom clojure-ts-refactor-map-prefix "C-c C-r" + "Clojure refactor keymap prefix." + :type 'string + :package-version '(clojure-ts-mode . "0.4")) + (defcustom clojure-ts-align-reader-conditionals nil "Whether to align reader conditionals, as if they were maps." :package-version '(clojure-ts-mode . "0.4") @@ -1691,11 +1697,199 @@ Forms between BEG and END are aligned according to (when clojure-ts-align-forms-automatically (clojure-ts-align beg end)))) +;;; Refactoring + +(defun clojure-ts--threading-sexp-node () + "Return list node at point which is a threading expression." + (when-let* ((node-at-point (treesit-node-at (point) 'clojure t))) + ;; We don't want to match `cond->' and `cond->>', so we should define a very + ;; specific regexp. + (let ((sym-regex (rx bol (* "some") "->" (* ">") eol))) + (treesit-parent-until node-at-point + (lambda (node) + (and (or (clojure-ts--list-node-p node) + (clojure-ts--anon-fn-node-p node)) + (let ((first-child (treesit-node-child node 0 t))) + (clojure-ts--symbol-matches-p sym-regex first-child)))) + t)))) + +(defun clojure-ts--delete-and-extract-sexp () + "Delete the surrounding sexp and return it." + (let* ((sexp-node (treesit-thing-at-point 'sexp 'nested)) + (result (treesit-node-text sexp-node))) + (delete-region (treesit-node-start sexp-node) + (treesit-node-end sexp-node)) + result)) + +(defun clojure-ts--ensure-parens-around-function-name () + "Insert parens around function name if necessary." + (unless (string= (treesit-node-text (treesit-node-at (point))) "(") + (insert-parentheses 1) + (backward-up-list))) + +(defun clojure-ts--multiline-sexp-p () + "Return TRUE if s-expression at point is multiline." + (let ((sexp (treesit-thing-at-point 'sexp 'nested))) + (not (= (line-number-at-pos (treesit-node-start sexp)) + (line-number-at-pos (treesit-node-end sexp)))))) + +(defun clojure-ts--unwind-thread-first () + "Unwind a thread first macro once." + (let* ((threading-sexp (clojure-ts--threading-sexp-node)) + (first-child-start (thread-first threading-sexp + (treesit-node-child 0 t) + (treesit-node-start) + (copy-marker)))) + (save-excursion + (goto-char first-child-start) + (treesit-beginning-of-thing 'sexp -1) + (let ((contents (clojure-ts--delete-and-extract-sexp))) + (when (looking-at " *\n") + (join-line 'following)) + (just-one-space) + (goto-char first-child-start) + (treesit-beginning-of-thing 'sexp -1) + (let ((multiline-p (clojure-ts--multiline-sexp-p))) + (clojure-ts--ensure-parens-around-function-name) + (down-list) + (forward-sexp) + (insert " " contents) + (when multiline-p + (insert "\n"))))))) + +(defun clojure-ts--unwind-thread-last () + "Unwind a thread last macro once." + (let* ((threading-sexp (clojure-ts--threading-sexp-node)) + (first-child-start (thread-first threading-sexp + (treesit-node-child 0 t) + (treesit-node-start) + (copy-marker)))) + (save-excursion + (goto-char first-child-start) + (treesit-beginning-of-thing 'sexp -1) + (let ((contents (clojure-ts--delete-and-extract-sexp))) + (when (looking-at " *\n") + (join-line 'following)) + (just-one-space) + (goto-char first-child-start) + (treesit-beginning-of-thing 'sexp -1) + (let ((multiline-p (clojure-ts--multiline-sexp-p))) + (clojure-ts--ensure-parens-around-function-name) + (forward-list) + (down-list -1) + (when multiline-p + (insert "\n")) + (insert " " contents)))))) + +(defun clojure-ts--node-threading-p (node) + "Return non-nil if NODE is a threading macro s-expression." + (and (or (clojure-ts--list-node-p node) + (clojure-ts--anon-fn-node-p node)) + (let ((first-child (treesit-node-child node 0 t))) + (clojure-ts--symbol-matches-p clojure-ts--threading-macro first-child)))) + +(defun clojure-ts--skip-first-child (parent) + "Move point to the beginning of the first child of the PARENT node." + (thread-first parent + (treesit-node-child 1 t) + (treesit-node-start) + (goto-char))) + +(defun clojure-ts--nothing-more-to-unwind () + "Return TRUE if threading expression at point has only one argument." + (let ((threading-sexp (clojure-ts--threading-sexp-node))) + (save-excursion + (clojure-ts--skip-first-child threading-sexp) + (not (treesit-end-of-thing 'sexp 2 'restricted))))) + +(defun clojure-ts--pop-out-of-threading () + "Raise a sexp up a level to unwind a threading form." + (let ((threading-sexp (clojure-ts--threading-sexp-node))) + (save-excursion + (clojure-ts--skip-first-child threading-sexp) + (raise-sexp)))) + +(defun clojure-ts--fix-sexp-whitespace () + "Fix whitespace after unwinding a threading form." + (save-excursion + (let ((beg (point))) + (treesit-end-of-thing 'sexp) + (indent-region beg (point)) + (delete-trailing-whitespace beg (point))))) + +(defun clojure-ts--unwind-sexps-counter () + "Return total number of s-expressions of a threading form at point." + (if-let* ((threading-sexp (clojure-ts--threading-sexp-node))) + (save-excursion + (clojure-ts--skip-first-child threading-sexp) + (let ((n 0)) + (while (treesit-end-of-thing 'sexp 1 'restricted) + (setq n (1+ n))) + n)) + (user-error "No threading form to unwind at point"))) + +(defun clojure-ts-unwind (&optional n) + "Unwind thread at point or above point by N levels. + +With universal argument \\[universal-argument], fully unwinds thread." + (interactive "P") + (setq n (cond + ((equal n '(4)) (clojure-ts--unwind-sexps-counter)) + (n) + (1))) + (if-let* ((threading-sexp (clojure-ts--threading-sexp-node)) + (sym (thread-first threading-sexp + (treesit-node-child 0 t) + (clojure-ts--named-node-text)))) + (save-excursion + (let ((beg (thread-first threading-sexp + (treesit-node-start) + (copy-marker))) + (end (thread-first threading-sexp + (treesit-node-end) + (copy-marker)))) + (while (> n 0) + (cond + ((string-match-p (rx bol (* "some") "->" eol) sym) + (clojure-ts--unwind-thread-first)) + ((string-match-p (rx bol (* "some") "->>" eol) sym) + (clojure-ts--unwind-thread-last))) + (setq n (1- n)) + ;; After unwinding we check if it is the last expression and maybe + ;; splice it. + (when (clojure-ts--nothing-more-to-unwind) + (clojure-ts--pop-out-of-threading) + (clojure-ts--fix-sexp-whitespace) + (setq n 0))) + (indent-region beg end) + (delete-trailing-whitespace beg end))) + (user-error "No threading form to unwind at point"))) + +(defun clojure-ts-unwind-all () + "Fully unwind thread at point or above point." + (interactive) + (clojure-ts-unwind '(4))) + +(defvar clojure-ts-refactor-map + (let ((map (make-sparse-keymap))) + (keymap-set map "C-u" #'clojure-ts-unwind) + (keymap-set map "u" #'clojure-ts-unwind) + map) + "Keymap for `clojure-ts-mode' refactoring commands.") + (defvar clojure-ts-mode-map (let ((map (make-sparse-keymap))) ;;(set-keymap-parent map clojure-mode-map) (keymap-set map "C-c SPC" #'clojure-ts-align) - map)) + (keymap-set map clojure-ts-refactor-map-prefix clojure-ts-refactor-map) + (easy-menu-define clojure-ts-mode-menu map "Clojure[TS] Mode Menu" + '("Clojure" + ["Align expression" clojure-ts-align] + ("Refactor -> and ->>" + ["Unwind once" clojure-ts-unwind] + ["Fully unwind a threading macro" clojure-ts-unwind-all]))) + map) + "Keymap for `clojure-ts-mode'.") (defvar clojure-ts-clojurescript-mode-map (let ((map (make-sparse-keymap))) diff --git a/test/clojure-ts-mode-font-lock-test.el b/test/clojure-ts-mode-font-lock-test.el index 05eba9e..8611211 100644 --- a/test/clojure-ts-mode-font-lock-test.el +++ b/test/clojure-ts-mode-font-lock-test.el @@ -34,9 +34,9 @@ (declare (debug t) (indent 1)) `(with-clojure-ts-buffer ,content - (font-lock-ensure) - (goto-char (point-min)) - ,@body)) + (font-lock-ensure) + (goto-char (point-min)) + ,@body)) (defun clojure-ts-get-face-at (start end content) "Get the face between START and END in CONTENT." diff --git a/test/clojure-ts-mode-refactor-threading-test.el b/test/clojure-ts-mode-refactor-threading-test.el new file mode 100644 index 0000000..45aaa17 --- /dev/null +++ b/test/clojure-ts-mode-refactor-threading-test.el @@ -0,0 +1,166 @@ +;;; clojure-ts-mode-refactor-threading-test.el --- clojure-ts-mode: refactor threading tests -*- lexical-binding: t; -*- + +;; Copyright (C) 2025 Roman Rudakov + +;; Author: Roman Rudakov +;; Keywords: + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;;; Commentary: + +;; The threading refactoring code is adapted from clojure-mode.el. + +;;; Code: + +(require 'clojure-ts-mode) +(require 'buttercup) +(require 'test-helper "test/test-helper") + +(describe "clojure-unwind" + + (when-refactoring-it "should unwind -> one step" + "(-> {} + (assoc :key \"value\") + (dissoc :lock))" + + "(-> (assoc {} :key \"value\") + (dissoc :lock))" + + (clojure-ts-unwind)) + + (when-refactoring-it "should unwind -> completely" + "(-> {} + (assoc :key \"value\") + (dissoc :lock))" + + "(dissoc (assoc {} :key \"value\") :lock)" + + (clojure-ts-unwind) + (clojure-ts-unwind)) + + (when-refactoring-it "should unwind ->> one step" + "(->> [1 2 3 4 5] + (filter even?) + (map square))" + + "(->> (filter even? [1 2 3 4 5]) + (map square))" + + (clojure-ts-unwind)) + + (when-refactoring-it "should unwind ->> completely" + "(->> [1 2 3 4 5] + (filter even?) + (map square))" + + "(map square (filter even? [1 2 3 4 5]))" + + (clojure-ts-unwind) + (clojure-ts-unwind)) + + (when-refactoring-it "should unwind N steps with numeric prefix arg" + "(->> [1 2 3 4 5] + (filter even?) + (map square) + sum)" + + "(->> (map square (filter even? [1 2 3 4 5])) + sum)" + + (clojure-ts-unwind 2)) + + (when-refactoring-it "should unwind completely with universal prefix arg" + "(->> [1 2 3 4 5] + (filter even?) + (map square) + sum)" + + "(sum (map square (filter even? [1 2 3 4 5])))" + + (clojure-ts-unwind '(4))) + + (when-refactoring-it "should unwind correctly when multiple ->> are present on same line" + "(->> 1 inc) (->> [1 2 3 4 5] + (filter even?) + (map square))" + + "(->> 1 inc) (map square (filter even? [1 2 3 4 5]))" + + (clojure-ts-unwind) + (clojure-ts-unwind)) + + (when-refactoring-it "should unwind with function name" + "(->> [1 2 3 4 5] + sum + square)" + + "(->> (sum [1 2 3 4 5]) + square)" + + (clojure-ts-unwind)) + + (when-refactoring-it "should unwind with function name twice" + "(-> [1 2 3 4 5] + sum + square)" + + "(square (sum [1 2 3 4 5]))" + + (clojure-ts-unwind) + (clojure-ts-unwind)) + + (when-refactoring-it "should thread-issue-6-1" + "(defn plus [a b] + (-> a (+ b)))" + + "(defn plus [a b] + (+ a b))" + + (clojure-ts-unwind)) + + (when-refactoring-it "should thread-issue-6-2" + "(defn plus [a b] + (->> a (+ b)))" + + "(defn plus [a b] + (+ b a))" + + (clojure-ts-unwind)) + + (when-refactoring-it "should unwind some->" + "(some-> {:a 1} + (find :b) + val + (+ 5))" + + "(some-> (val (find {:a 1} :b)) + (+ 5))" + + (clojure-ts-unwind) + (clojure-ts-unwind)) + + (when-refactoring-it "should unwind some->>" + "(some->> :b + (find {:a 1}) val + (+ 5))" + + "(some->> (val (find {:a 1} :b)) + (+ 5))" + + (clojure-ts-unwind) + (clojure-ts-unwind))) + +(provide 'clojure-ts-mode-refactor-threading-test) +;;; clojure-ts-mode-refactor-threading-test.el ends here diff --git a/test/clojure-ts-mode-util-test.el b/test/clojure-ts-mode-util-test.el index 8156c1a..05b0fcc 100644 --- a/test/clojure-ts-mode-util-test.el +++ b/test/clojure-ts-mode-util-test.el @@ -31,101 +31,101 @@ (describe "clojure-ts-find-ns" (it "should find common namespace declarations" (with-clojure-ts-buffer "(ns foo)" - (expect (clojure-ts-find-ns) :to-equal "foo")) + (expect (clojure-ts-find-ns) :to-equal "foo")) (with-clojure-ts-buffer "(ns foo)" - (expect (clojure-ts-find-ns) :to-equal "foo")) + (expect (clojure-ts-find-ns) :to-equal "foo")) (with-clojure-ts-buffer "(ns foo.baz)" - (expect (clojure-ts-find-ns) :to-equal "foo.baz")) + (expect (clojure-ts-find-ns) :to-equal "foo.baz")) (with-clojure-ts-buffer "(ns ^:bar foo)" - (expect (clojure-ts-find-ns) :to-equal "foo")) + (expect (clojure-ts-find-ns) :to-equal "foo")) (with-clojure-ts-buffer "(ns ^:bar ^:baz foo)" - (expect (clojure-ts-find-ns) :to-equal "foo"))) + (expect (clojure-ts-find-ns) :to-equal "foo"))) (it "should find namespaces with spaces before ns form" (with-clojure-ts-buffer " (ns foo)" - (expect (clojure-ts-find-ns) :to-equal "foo"))) + (expect (clojure-ts-find-ns) :to-equal "foo"))) (it "should skip namespaces within any comment forms" (with-clojure-ts-buffer "(comment (ns foo))" - (expect (clojure-ts-find-ns) :to-equal nil)) + (expect (clojure-ts-find-ns) :to-equal nil)) (with-clojure-ts-buffer " (ns foo) (comment (ns bar))" - (expect (clojure-ts-find-ns) :to-equal "foo")) + (expect (clojure-ts-find-ns) :to-equal "foo")) (with-clojure-ts-buffer " (comment (ns foo)) (ns bar) (comment (ns baz))" - (expect (clojure-ts-find-ns) :to-equal "bar"))) + (expect (clojure-ts-find-ns) :to-equal "bar"))) (it "should find namespace declarations with nested metadata and docstrings" (with-clojure-ts-buffer "(ns ^{:bar true} foo)" - (expect (clojure-ts-find-ns) :to-equal "foo")) + (expect (clojure-ts-find-ns) :to-equal "foo")) (with-clojure-ts-buffer "(ns #^{:bar true} foo)" - (expect (clojure-ts-find-ns) :to-equal "foo")) + (expect (clojure-ts-find-ns) :to-equal "foo")) (with-clojure-ts-buffer "(ns #^{:fail {}} foo)" - (expect (clojure-ts-find-ns) :to-equal "foo")) + (expect (clojure-ts-find-ns) :to-equal "foo")) (with-clojure-ts-buffer "(ns ^{:fail2 {}} foo.baz)" - (expect (clojure-ts-find-ns) :to-equal "foo.baz")) + (expect (clojure-ts-find-ns) :to-equal "foo.baz")) (with-clojure-ts-buffer "(ns ^{} foo)" - (expect (clojure-ts-find-ns) :to-equal "foo")) + (expect (clojure-ts-find-ns) :to-equal "foo")) (with-clojure-ts-buffer "(ns ^{:skip-wiki true} aleph.netty)" - (expect (clojure-ts-find-ns) :to-equal "aleph.netty")) + (expect (clojure-ts-find-ns) :to-equal "aleph.netty")) (with-clojure-ts-buffer "(ns ^{:foo {:bar :baz} :fake (ns in.meta)} foo \"docstring (ns misleading)\")" - (expect (clojure-ts-find-ns) :to-equal "foo"))) + (expect (clojure-ts-find-ns) :to-equal "foo"))) (it "should support non-alphanumeric characters" (with-clojure-ts-buffer "(ns foo+)" - (expect (clojure-ts-find-ns) :to-equal "foo+")) + (expect (clojure-ts-find-ns) :to-equal "foo+")) (with-clojure-ts-buffer "(ns bar**baz$-_quux)" - (expect (clojure-ts-find-ns) :to-equal "bar**baz$-_quux")) + (expect (clojure-ts-find-ns) :to-equal "bar**baz$-_quux")) (with-clojure-ts-buffer "(ns aoc-2019.puzzles.day14)" - (expect (clojure-ts-find-ns) :to-equal "aoc-2019.puzzles.day14"))) + (expect (clojure-ts-find-ns) :to-equal "aoc-2019.puzzles.day14"))) (it "should support in-ns forms" (with-clojure-ts-buffer "(in-ns 'bar.baz)" - (expect (clojure-ts-find-ns) :to-equal "bar.baz"))) + (expect (clojure-ts-find-ns) :to-equal "bar.baz"))) (it "should take the first ns instead of closest unlike clojure-mode" (with-clojure-ts-buffer " (ns foo1) (ns foo2)" - (expect (clojure-ts-find-ns) :to-equal "foo1")) + (expect (clojure-ts-find-ns) :to-equal "foo1")) (with-clojure-ts-buffer-point " (in-ns foo1) (ns 'foo2) (in-ns 'foo3) | (ns foo4)" - (expect (clojure-ts-find-ns) :to-equal "foo3")) + (expect (clojure-ts-find-ns) :to-equal "foo3")) (with-clojure-ts-buffer "(ns foo) (ns-unmap *ns* 'map) (ns.misleading 1 2 3)" - (expect (clojure-ts-find-ns) :to-equal "foo"))) + (expect (clojure-ts-find-ns) :to-equal "foo"))) (it "should skip leading garbage" (with-clojure-ts-buffer " (ns foo)" - (expect (clojure-ts-find-ns) :to-equal "foo")) + (expect (clojure-ts-find-ns) :to-equal "foo")) (with-clojure-ts-buffer "1(ns foo)" - (expect (clojure-ts-find-ns) :to-equal "foo")) + (expect (clojure-ts-find-ns) :to-equal "foo")) (with-clojure-ts-buffer "1 (ns foo)" - (expect (clojure-ts-find-ns) :to-equal "foo")) + (expect (clojure-ts-find-ns) :to-equal "foo")) (with-clojure-ts-buffer "1 (ns foo)" - (expect (clojure-ts-find-ns) :to-equal "foo")) + (expect (clojure-ts-find-ns) :to-equal "foo")) (with-clojure-ts-buffer "[1] (ns foo)" - (expect (clojure-ts-find-ns) :to-equal "foo")) + (expect (clojure-ts-find-ns) :to-equal "foo")) (with-clojure-ts-buffer "[1] (ns foo)" - (expect (clojure-ts-find-ns) :to-equal "foo")) + (expect (clojure-ts-find-ns) :to-equal "foo")) (with-clojure-ts-buffer "[1](ns foo)" - (expect (clojure-ts-find-ns) :to-equal "foo")) + (expect (clojure-ts-find-ns) :to-equal "foo")) (with-clojure-ts-buffer "(ns)(ns foo)" - (expect (clojure-ts-find-ns) :to-equal "foo")) + (expect (clojure-ts-find-ns) :to-equal "foo")) (with-clojure-ts-buffer "(ns 'foo)(ns bar)" - (expect (clojure-ts-find-ns) :to-equal "bar")))) + (expect (clojure-ts-find-ns) :to-equal "bar")))) diff --git a/test/samples/refactoring.clj b/test/samples/refactoring.clj new file mode 100644 index 0000000..7c3487f --- /dev/null +++ b/test/samples/refactoring.clj @@ -0,0 +1,37 @@ +(ns refactoring) + +;;; Threading + +(-> ;; This is comment + (foo) + ;; Another comment + (bar true + ;; Hello + false) + (baz)) + + +(let [some (->> yeah + (world foo + false) + hello)]) + +(->> coll + (filter identity) + (map :id) + (map :name)) + +(some->> coll + (filter identity) + (map :id) + (map :name)) + +(defn plus [a b] + (-> a (+ b))) + +(some->> :b + (find {:a 1}) val + (+ 5)) + +(some->> (val (find {:a 1} :b)) + (+ 5)) diff --git a/test/test-helper.el b/test/test-helper.el index f363644..a99ceec 100644 --- a/test/test-helper.el +++ b/test/test-helper.el @@ -42,10 +42,10 @@ and point left there." (declare (indent 2)) `(progn (with-clojure-ts-buffer ,text - (goto-char (point-min)) - (re-search-forward "|") - (delete-char -1) - ,@body))) + (goto-char (point-min)) + (re-search-forward "|") + (delete-char -1) + ,@body))) (defun clojure-ts--s-index-of (needle s &optional ignore-case) "Returns first index of NEEDLE in S, or nil. @@ -108,4 +108,5 @@ Removes the temp directory at the end of evaluation." ,@body) (delete-directory ,temp-dir t)))) +(provide 'test-helper) ;;; test-helper.el ends here From 3569c90c56dfd8bd61481699cdb4b1260ef30195 Mon Sep 17 00:00:00 2001 From: Roman Rudakov Date: Tue, 29 Apr 2025 11:18:51 +0200 Subject: [PATCH 325/379] Introduce threading refactoring commands --- CHANGELOG.md | 2 + README.md | 50 +++- clojure-ts-mode.el | 175 +++++++++-- ...clojure-ts-mode-refactor-threading-test.el | 279 +++++++++++++++++- test/samples/indentation.clj | 10 + test/samples/refactoring.clj | 35 +++ 6 files changed, 524 insertions(+), 27 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7dd9d56..3a45d82 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ - Consistent indentation with regular forms. - Support for automatic aligning forms. - [#88](https://github.com/clojure-emacs/clojure-ts-mode/pull/88): Introduce `clojure-ts-unwind` and `clojure-ts-unwind-all`. +- [#89](https://github.com/clojure-emacs/clojure-ts-mode/pull/89): Introduce `clojure-ts-thread`, `clojure-ts-thread-first-all` and + `clojure-ts-thread-last-all`. ## 0.3.0 (2025-04-15) diff --git a/README.md b/README.md index f2d656c..c7b8e40 100644 --- a/README.md +++ b/README.md @@ -376,24 +376,66 @@ following customization: ### Threading macros related features +`clojure-thread`: Thread another form into the surrounding thread. Both +`->>`/`some->>` and `->`/`some->` variants are supported. + `clojure-unwind`: Unwind a threaded expression. Supports both `->>`/`some->>` and `->`/`some->`. +`clojure-thread-first-all`: Introduce the thread first macro (`->`) and rewrite +the entire form. With a prefix argument do not thread the last form. + +`clojure-thread-last-all`: Introduce the thread last macro and rewrite the +entire form. With a prefix argument do not thread the last form. + `clojure-unwind-all`: Fully unwind a threaded expression removing the threading macro. ### Default keybindings -| Keybinding | Command | -|:------------|:--------------------| -| `C-c SPC` | `clojure-ts-align` | -| `C-c C-r u` | `clojure-ts-unwind` | +| Keybinding | Command | +|:----------------------------|:------------------------------| +| `C-c SPC` | `clojure-ts-align` | +| `C-c C-r t` / `C-c C-r C-t` | `clojure-ts-thread` | +| `C-c C-r u` / `C-c C-r C-u` | `clojure-ts-unwind` | +| `C-c C-r f` / `C-c C-r C-f` | `clojure-ts-thread-first-all` | +| `C-c C-r l` / `C-c C-r C-l` | `clojure-ts-thread-last-all` | ### Customize refactoring commands prefix By default prefix for all refactoring commands is `C-c C-r`. It can be changed by customizing `clojure-ts-refactor-map-prefix` variable. +### Customize threading refactoring behavior + +By default `clojure-ts-thread-first-all` and `clojure-ts-thread-last-all` will +thread all nested expressions. For example this expression: + +```clojure +(->map (assoc {} :key "value") :lock) +``` + +After executing `clojure-ts-thread-last-all` will be converted to: + +```clojure +(-> {} + (assoc :key "value") + (->map :lock)) +``` + +This behavior can be changed by setting: + +```emacs-lisp +(setopt clojure-ts-thread-all-but-last t) +``` + +Then the last expression will not be threaded and the result will be: + +```clojure +(-> (assoc {} :key "value") + (->map :lock)) +``` + ## Migrating to clojure-ts-mode If you are migrating to `clojure-ts-mode` note that `clojure-mode` is still diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index 4559e60..45dcc62 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -150,6 +150,16 @@ three or more semicolons will be treated as outline headings. If set to :type 'string :package-version '(clojure-ts-mode . "0.4")) +(defcustom clojure-ts-thread-all-but-last nil + "Non-nil means do not thread the last expression. + +This means that `clojure-ts-thread-first-all' and +`clojure-ts-thread-last-all' not thread the deepest sexp inside the +current sexp." + :package-version '(clojure-ts-mode . "0.4.0") + :safe #'booleanp + :type 'boolean) + (defcustom clojure-ts-align-reader-conditionals nil "Whether to align reader conditionals, as if they were maps." :package-version '(clojure-ts-mode . "0.4") @@ -1291,9 +1301,9 @@ according to the rule. If NODE is nil, use next node after BOL." (clojure-ts--anon-fn-node-p parent)) ;; Can the following two clauses be replaced by checking indexes? ;; Does the second child exist, and is it not equal to the current node? - (treesit-node-child parent 1 t) - (not (treesit-node-eq (treesit-node-child parent 1 t) node)) - (let ((first-child (treesit-node-child parent 0 t))) + (clojure-ts--node-child-skip-metadata parent 1) + (not (treesit-node-eq (clojure-ts--node-child-skip-metadata parent 1) node)) + (let ((first-child (clojure-ts--node-child-skip-metadata parent 0))) (or (clojure-ts--symbol-node-p first-child) (clojure-ts--keyword-node-p first-child) (clojure-ts--var-node-p first-child))))) @@ -1381,17 +1391,11 @@ if NODE has metadata and its parent has type NODE-TYPE." (treesit-node-type (clojure-ts--node-with-metadata-parent node))))) -(defun clojure-ts--anchor-nth-sibling (n &optional named) - "Return the start of the Nth child of PARENT. - -NAMED non-nil means count only named nodes. - -NOTE: This is a replacement for built-in `nth-sibling' anchor preset, -which doesn't work properly for named nodes (see the bug -https://debbugs.gnu.org/cgi/bugreport.cgi?bug=78065)" +(defun clojure-ts--anchor-nth-sibling (n) + "Return the start of the Nth child of PARENT skipping metadata." (lambda (_n parent &rest _) (treesit-node-start - (treesit-node-child parent n named)))) + (clojure-ts--node-child-skip-metadata parent n)))) (defun clojure-ts--semantic-indent-rules () "Return a list of indentation rules for `treesit-simple-indent-rules'." @@ -1423,7 +1427,7 @@ https://debbugs.gnu.org/cgi/bugreport.cgi?bug=78065)" ;; https://guide.clojure.style/#threading-macros-alignment (clojure-ts--match-threading-macro-arg prev-sibling 0) ;; https://guide.clojure.style/#vertically-align-fn-args - (clojure-ts--match-function-call-arg ,(clojure-ts--anchor-nth-sibling 1 t) 0) + (clojure-ts--match-function-call-arg ,(clojure-ts--anchor-nth-sibling 1) 0) ;; https://guide.clojure.style/#one-space-indent ((parent-is "list_lit") parent 1)))) @@ -1539,8 +1543,8 @@ BOUND bounds the whitespace search." (and (not (treesit-node-child-by-field-name cur-sexp "value")) (string-empty-p (clojure-ts--named-node-text cur-sexp)))) (treesit-end-of-thing 'sexp 2 'restricted) - (treesit-end-of-thing 'sexp 1 'restrict)) - (when (looking-at ",") + (treesit-end-of-thing 'sexp 1 'restricted)) + (when (looking-at-p ",") (forward-char)) ;; Move past any whitespace or comment. (search-forward-regexp regex bound) @@ -1744,7 +1748,7 @@ Forms between BEG and END are aligned according to (goto-char first-child-start) (treesit-beginning-of-thing 'sexp -1) (let ((contents (clojure-ts--delete-and-extract-sexp))) - (when (looking-at " *\n") + (when (looking-at-p " *\n") (join-line 'following)) (just-one-space) (goto-char first-child-start) @@ -1753,9 +1757,11 @@ Forms between BEG and END are aligned according to (clojure-ts--ensure-parens-around-function-name) (down-list) (forward-sexp) - (insert " " contents) - (when multiline-p - (insert "\n"))))))) + (cond + ((and multiline-p (looking-at-p " *\n")) + (insert "\n" contents)) + (multiline-p (insert " " contents "\n")) + (t (insert " " contents)))))))) (defun clojure-ts--unwind-thread-last () "Unwind a thread last macro once." @@ -1768,7 +1774,7 @@ Forms between BEG and END are aligned according to (goto-char first-child-start) (treesit-beginning-of-thing 'sexp -1) (let ((contents (clojure-ts--delete-and-extract-sexp))) - (when (looking-at " *\n") + (when (looking-at-p " *\n") (join-line 'following)) (just-one-space) (goto-char first-child-start) @@ -1804,10 +1810,16 @@ Forms between BEG and END are aligned according to (defun clojure-ts--pop-out-of-threading () "Raise a sexp up a level to unwind a threading form." - (let ((threading-sexp (clojure-ts--threading-sexp-node))) + (let* ((threading-sexp (clojure-ts--threading-sexp-node)) + (beg (thread-first threading-sexp + (treesit-node-child 0 t) + (treesit-node-start)))) (save-excursion (clojure-ts--skip-first-child threading-sexp) - (raise-sexp)))) + (delete-region beg (point)) + ;; `raise-sexp' doesn't work properly for function literals (it loses one + ;; of the parenthesis). Seems like an Emacs' bug. + (delete-pair)))) (defun clojure-ts--fix-sexp-whitespace () "Fix whitespace after unwinding a threading form." @@ -1870,10 +1882,125 @@ With universal argument \\[universal-argument], fully unwinds thread." (interactive) (clojure-ts-unwind '(4))) +(defun clojure-ts--remove-superfluous-parens () + "Remove extra parens from a form." + (when-let* ((node (treesit-thing-at-point 'sexp 'nested)) + ((clojure-ts--list-node-p node)) + ((= 1 (treesit-node-child-count node t)))) + (let ((delete-pair-blink-delay 0)) + (delete-pair)))) + +(defun clojure-ts--thread-first () + "Thread a sexp using ->." + (save-excursion + (clojure-ts--skip-first-child (clojure-ts--threading-sexp-node)) + (down-list) + (treesit-beginning-of-thing 'sexp -1) + (let ((contents (clojure-ts--delete-and-extract-sexp))) + (delete-char -1) + (when (looking-at-p " *\n") + (join-line 'following)) + (backward-up-list) + (insert contents "\n") + (clojure-ts--remove-superfluous-parens)))) + +(defun clojure-ts--thread-last () + "Thread a sexp using ->>." + (save-excursion + (clojure-ts--skip-first-child (clojure-ts--threading-sexp-node)) + (treesit-end-of-thing 'sexp) + (down-list -1) + (treesit-beginning-of-thing 'sexp) + (let ((contents (clojure-ts--delete-and-extract-sexp))) + (delete-char -1) + (treesit-end-of-thing 'sexp -1 'restricted) + (when (looking-at-p " *\n") + (join-line 'following)) + (backward-up-list) + (insert contents "\n") + (clojure-ts--remove-superfluous-parens)))) + +(defun clojure-ts--threadable-p (node) + "Return non-nil if expression NODE can be threaded. + +First argument after threading symbol itself should be a list and it +should have more than one named child." + (let ((second-child (treesit-node-child node 1 t))) + (and (clojure-ts--list-node-p second-child) + (> (treesit-node-child-count second-child t) 1)))) + +(defun clojure-ts-thread (&optional called-by-user-p) + "Thread by one more level an existing threading macro. + +If CALLED-BY-USER-P is non-nil (which is always TRUE when called +interactively), the function signals a `user-error' if threading form +cannot be found." + (interactive "p") + (if-let* ((threading-sexp (clojure-ts--threading-sexp-node)) + ((clojure-ts--threadable-p threading-sexp)) + (sym (thread-first threading-sexp + (treesit-node-child 0 t) + (clojure-ts--named-node-text)))) + (let ((beg (thread-first threading-sexp + (treesit-node-start) + (copy-marker))) + (end (thread-first threading-sexp + (treesit-node-end) + (copy-marker)))) + (cond + ((string-match-p (rx bol (* "some") "->" eol) sym) + (clojure-ts--thread-first)) + ((string-match-p (rx bol (* "some") "->>" eol) sym) + (clojure-ts--thread-last))) + (indent-region beg end) + (delete-trailing-whitespace beg end) + t) + (when called-by-user-p + (user-error "No threading form at point")))) + +(defun clojure-ts--thread-all (first-or-last-thread but-last) + "Fully thread the form at point. + +FIRST-OR-LAST-THREAD is either \"->\" or \"->>\". + +When BUT-LAST is non-nil, the last expression is not threaded. Default +value is `clojure-ts-thread-all-but-last.'" + (if-let* ((list-at-point (treesit-thing-at-point 'list 'nested))) + (save-excursion + (goto-char (treesit-node-start list-at-point)) + (insert-parentheses 1) + (insert first-or-last-thread) + (while (clojure-ts-thread)) + (when (or but-last clojure-ts-thread-all-but-last) + (clojure-ts-unwind))) + (user-error "No list to thread at point"))) + +(defun clojure-ts-thread-first-all (but-last) + "Fully thread the form at point using ->. + +When BUT-LAST is non-nil, the last expression is not threaded. Default +value is `clojure-ts-thread-all-but-last'." + (interactive "P") + (clojure-ts--thread-all "-> " but-last)) + +(defun clojure-ts-thread-last-all (but-last) + "Fully thread the form at point using ->>. + +When BUT-LAST is non-nil, the last expression is not threaded. Default +value is `clojure-ts-thread-all-but-last'." + (interactive "P") + (clojure-ts--thread-all "->> " but-last)) + (defvar clojure-ts-refactor-map (let ((map (make-sparse-keymap))) + (keymap-set map "C-t" #'clojure-ts-thread) + (keymap-set map "t" #'clojure-ts-thread) (keymap-set map "C-u" #'clojure-ts-unwind) (keymap-set map "u" #'clojure-ts-unwind) + (keymap-set map "C-f" #'clojure-ts-thread-first-all) + (keymap-set map "f" #'clojure-ts-thread-first-all) + (keymap-set map "C-l" #'clojure-ts-thread-last-all) + (keymap-set map "l" #'clojure-ts-thread-last-all) map) "Keymap for `clojure-ts-mode' refactoring commands.") @@ -1886,6 +2013,10 @@ With universal argument \\[universal-argument], fully unwinds thread." '("Clojure" ["Align expression" clojure-ts-align] ("Refactor -> and ->>" + ["Thread once more" clojure-ts-thread] + ["Fully thread a form with ->" clojure-ts-thread-first-all] + ["Fully thread a form with ->>" clojure-ts-thread-last-all] + "--" ["Unwind once" clojure-ts-unwind] ["Fully unwind a threading macro" clojure-ts-unwind-all]))) map) diff --git a/test/clojure-ts-mode-refactor-threading-test.el b/test/clojure-ts-mode-refactor-threading-test.el index 45aaa17..ce26d5d 100644 --- a/test/clojure-ts-mode-refactor-threading-test.el +++ b/test/clojure-ts-mode-refactor-threading-test.el @@ -28,7 +28,142 @@ (require 'buttercup) (require 'test-helper "test/test-helper") -(describe "clojure-unwind" +(describe "clojure-ts-thread" + + (when-refactoring-it "should work with -> when performed once" + "(-> (dissoc (assoc {} :key \"value\") :lock))" + + "(-> (assoc {} :key \"value\") + (dissoc :lock))" + + (clojure-ts-thread)) + + (when-refactoring-it "should work with -> when performed twice" + "(-> (dissoc (assoc {} :key \"value\") :lock))" + + "(-> {} + (assoc :key \"value\") + (dissoc :lock))" + + (clojure-ts-thread) + (clojure-ts-thread)) + + (when-refactoring-it "should not thread maps" + "(-> (dissoc (assoc {} :key \"value\") :lock))" + + "(-> {} + (assoc :key \"value\") + (dissoc :lock))" + + (clojure-ts-thread) + (clojure-ts-thread) + (clojure-ts-thread)) + + (when-refactoring-it "should not thread last sexp" + "(-> (dissoc (assoc (get-a-map) :key \"value\") :lock))" + + "(-> (get-a-map) + (assoc :key \"value\") + (dissoc :lock))" + + (clojure-ts-thread) + (clojure-ts-thread) + (clojure-ts-thread)) + + (when-refactoring-it "should thread-first-easy-on-whitespace" + "(-> + (dissoc (assoc {} :key \"value\") :lock))" + + "(-> + (assoc {} :key \"value\") + (dissoc :lock))" + + (clojure-ts-thread)) + + (when-refactoring-it "should remove superfluous parens" + "(-> (square (sum [1 2 3 4 5])))" + + "(-> [1 2 3 4 5] + sum + square)" + + (clojure-ts-thread) + (clojure-ts-thread)) + + (when-refactoring-it "should work with cursor before ->" + "(-> (not (s-acc/mobile? session)))" + + "(-> (s-acc/mobile? session) + not)" + + (beginning-of-buffer) + (clojure-ts-thread)) + + (when-refactoring-it "should work with one step with ->>" + "(->> (map square (filter even? [1 2 3 4 5])))" + + "(->> (filter even? [1 2 3 4 5]) + (map square))" + + (clojure-ts-thread)) + + (when-refactoring-it "should work with two steps with ->>" + "(->> (map square (filter even? [1 2 3 4 5])))" + + "(->> [1 2 3 4 5] + (filter even?) + (map square))" + + (clojure-ts-thread) + (clojure-ts-thread)) + + (when-refactoring-it "should not thread vectors with ->>" + "(->> (map square (filter even? [1 2 3 4 5])))" + + "(->> [1 2 3 4 5] + (filter even?) + (map square))" + + (clojure-ts-thread) + (clojure-ts-thread) + (clojure-ts-thread)) + + (when-refactoring-it "should not thread last sexp with ->>" + "(->> (map square (filter even? (get-a-list))))" + + "(->> (get-a-list) + (filter even?) + (map square))" + + (clojure-ts-thread) + (clojure-ts-thread) + (clojure-ts-thread)) + + (when-refactoring-it "should work with some->" + "(some-> (+ (val (find {:a 1} :b)) 5))" + + "(some-> {:a 1} + (find :b) + val + (+ 5))" + + (clojure-ts-thread) + (clojure-ts-thread) + (clojure-ts-thread)) + + (when-refactoring-it "should work with some->>" + "(some->> (+ 5 (val (find {:a 1} :b))))" + + "(some->> :b + (find {:a 1}) + val + (+ 5))" + + (clojure-ts-thread) + (clojure-ts-thread) + (clojure-ts-thread))) + +(describe "clojure-ts-unwind" (when-refactoring-it "should unwind -> one step" "(-> {} @@ -162,5 +297,147 @@ (clojure-ts-unwind) (clojure-ts-unwind))) +(describe "clojure-ts-thread-first-all" + + (when-refactoring-it "should thread first all sexps" + "(->map (assoc {} :key \"value\") :lock)" + + "(-> {} + (assoc :key \"value\") + (->map :lock))" + + (beginning-of-buffer) + (clojure-ts-thread-first-all nil)) + + (when-refactoring-it "should thread a form except the last expression" + "(->map (assoc {} :key \"value\") :lock)" + + "(-> (assoc {} :key \"value\") + (->map :lock))" + + (beginning-of-buffer) + (clojure-ts-thread-first-all t)) + + (when-refactoring-it "should thread with an empty first line" + "(map + inc + [1 2])" + + "(-> inc + (map + [1 2]))" + + (goto-char (point-min)) + (clojure-ts-thread-first-all nil)) + + (when-refactoring-it "should thread-first-maybe-unjoin-lines" + "(map + inc + [1 2])" + + "(map + inc + [1 2])" + + (goto-char (point-min)) + (clojure-ts-thread-first-all nil) + (clojure-ts-unwind-all))) + +(describe "clojure-ts-thread-last-all" + + (when-refactoring-it "should fully thread a form" + "(map square (filter even? (make-things)))" + + "(->> (make-things) + (filter even?) + (map square))" + + (beginning-of-buffer) + (clojure-ts-thread-last-all nil)) + + (when-refactoring-it "should thread a form except the last expression" + "(map square (filter even? (make-things)))" + + "(->> (filter even? (make-things)) + (map square))" + + (beginning-of-buffer) + (clojure-ts-thread-last-all t)) + + (when-refactoring-it "should handle dangling parens 1" + "(map inc + (range))" + + "(->> (range) + (map inc))" + + (beginning-of-buffer) + (clojure-ts-thread-last-all nil)) + + (when-refactoring-it "should handle dangling parens 2" + "(deftask dev [] + (comp (serve) + (cljs)))" + + "(->> (cljs) + (comp (serve)) + (deftask dev []))" + + (beginning-of-buffer) + (clojure-ts-thread-last-all nil))) + +(describe "clojure-ts-unwind-all" + + (when-refactoring-it "should unwind all in ->" + "(-> {} + (assoc :key \"value\") + (dissoc :lock))" + + "(dissoc (assoc {} :key \"value\") :lock)" + + (beginning-of-buffer) + (clojure-ts-unwind-all)) + + (when-refactoring-it "should unwind all in ->>" + "(->> (make-things) + (filter even?) + (map square))" + + "(map square (filter even? (make-things)))" + + (beginning-of-buffer) + (clojure-ts-unwind-all)) + + (when-refactoring-it "should leave multiline sexp alone" + "(->> [a b] + (some (fn [x] + (when x + 10))))" + + "(some (fn [x] + (when x + 10)) + [a b])" + + (clojure-ts-unwind-all)) + + ;; NOTE: This feature is implemented in `clojure-mode' via text properties and + ;; doesn't work for the same expression after restarting Emacs. For now it's + ;; not implemented in `clojure-ts-mode', although we respect multiline + ;; expressions in some cases. + ;; + ;; (when-refactoring-it "should thread-last-maybe-unjoin-lines" "(deftask dev + ;; [] (comp (serve) (cljs (lala) 10)))" + + ;; "(deftask dev [] + ;; (comp (serve) + ;; (cljs (lala) + ;; 10)))" + + ;; (goto-char (point-min)) + ;; (clojure-ts-thread-last-all nil) + ;; (clojure-ts-unwind-all)) + ) + (provide 'clojure-ts-mode-refactor-threading-test) ;;; clojure-ts-mode-refactor-threading-test.el ends here diff --git a/test/samples/indentation.clj b/test/samples/indentation.clj index 53e8269..132a5f2 100644 --- a/test/samples/indentation.clj +++ b/test/samples/indentation.clj @@ -281,3 +281,13 @@ user "John Doe"] (dotimes [_ (add x y)] (hello user)))) + +(with-open [input-stream + ^java.io.BufferedInputStream + (foo bar + baz + true) + + reader + (io/reader input-stream)] + (read-report (into [] (csv/read-csv reader)))) diff --git a/test/samples/refactoring.clj b/test/samples/refactoring.clj index 7c3487f..e6f24b8 100644 --- a/test/samples/refactoring.clj +++ b/test/samples/refactoring.clj @@ -2,6 +2,8 @@ ;;; Threading +;;;; Unwind + (-> ;; This is comment (foo) ;; Another comment @@ -35,3 +37,36 @@ (some->> (val (find {:a 1} :b)) (+ 5)) + +;;;; Thread + +(-> (foo (bar (baz)) "arg on a separate line")) + +(foo (bar (baz))) + +(-> (foo (bar)) + (baz)) + +(->> (filter :active? (map :status items))) + +(-> (dissoc (assoc {} :key "value") :lock)) + + +(-> inc + (map [1 2])) + +(map + inc + [1 2]) + +#(-> (.-value (.-target %))) + +(->> (range) + (map inc)) + +(->> (map square (filter even? [1 2 3 4 5]))) + +(deftask dev [] + (comp (serve) + (cljs (lala) + 10))) From ea1c1342450f45ac328da6d80c978b181ca48ce1 Mon Sep 17 00:00:00 2001 From: Roman Rudakov Date: Sun, 4 May 2025 20:38:24 +0200 Subject: [PATCH 326/379] Introduce cycle privacy refactoring command --- CHANGELOG.md | 1 + README.md | 21 ++-- clojure-ts-mode.el | 46 +++++++- test/clojure-ts-mode-cycling-test.el | 163 +++++++++++++++++++++++++++ test/samples/refactoring.clj | 12 ++ 5 files changed, 235 insertions(+), 8 deletions(-) create mode 100644 test/clojure-ts-mode-cycling-test.el diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a45d82..c8fc91b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ - [#88](https://github.com/clojure-emacs/clojure-ts-mode/pull/88): Introduce `clojure-ts-unwind` and `clojure-ts-unwind-all`. - [#89](https://github.com/clojure-emacs/clojure-ts-mode/pull/89): Introduce `clojure-ts-thread`, `clojure-ts-thread-first-all` and `clojure-ts-thread-last-all`. +- [#90](https://github.com/clojure-emacs/clojure-ts-mode/pull/90): Introduce `clojure-ts-cycle-privacy`. ## 0.3.0 (2025-04-15) diff --git a/README.md b/README.md index c7b8e40..bf14a33 100644 --- a/README.md +++ b/README.md @@ -376,20 +376,26 @@ following customization: ### Threading macros related features -`clojure-thread`: Thread another form into the surrounding thread. Both +`clojure-ts-thread`: Thread another form into the surrounding thread. Both `->>`/`some->>` and `->`/`some->` variants are supported. -`clojure-unwind`: Unwind a threaded expression. Supports both `->>`/`some->>` +`clojure-ts-unwind`: Unwind a threaded expression. Supports both `->>`/`some->>` and `->`/`some->`. -`clojure-thread-first-all`: Introduce the thread first macro (`->`) and rewrite -the entire form. With a prefix argument do not thread the last form. +`clojure-ts-thread-first-all`: Introduce the thread first macro (`->`) and +rewrite the entire form. With a prefix argument do not thread the last form. -`clojure-thread-last-all`: Introduce the thread last macro and rewrite the +`clojure-ts-thread-last-all`: Introduce the thread last macro and rewrite the entire form. With a prefix argument do not thread the last form. -`clojure-unwind-all`: Fully unwind a threaded expression removing the threading -macro. +`clojure-ts-unwind-all`: Fully unwind a threaded expression removing the +threading macro. + +### Cycling things + +`clojure-ts-cycle-privacy`: Cycle privacy of `def`s or `defn`s. Use metadata +explicitly with setting `clojure-ts-use-metadata-for-defn-privacy` to `t` for +`defn`s too. ### Default keybindings @@ -400,6 +406,7 @@ macro. | `C-c C-r u` / `C-c C-r C-u` | `clojure-ts-unwind` | | `C-c C-r f` / `C-c C-r C-f` | `clojure-ts-thread-first-all` | | `C-c C-r l` / `C-c C-r C-l` | `clojure-ts-thread-last-all` | +| `C-c C-r p` / `C-c C-r C-p` | `clojure-ts-cycle-privacy` | ### Customize refactoring commands prefix diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index 45dcc62..a110d2f 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -160,6 +160,14 @@ current sexp." :safe #'booleanp :type 'boolean) +(defcustom clojure-ts-use-metadata-for-defn-privacy nil + "If nil, `clojure-ts-cycle-privacy' will use (defn- f []). + +If t, it will use (defn ^:private f [])." + :package-version '(clojure-ts-mode . "0.4.0") + :safe #'booleanp + :type 'boolean) + (defcustom clojure-ts-align-reader-conditionals nil "Whether to align reader conditionals, as if they were maps." :package-version '(clojure-ts-mode . "0.4") @@ -1480,6 +1488,21 @@ If JUSTIFY is non-nil, justify as well as fill the paragraph." "map_lit" "ns_map_lit" "vec_lit" "set_lit") "A regular expression that matches nodes that can be treated as lists.") +(defun clojure-ts--defun-node-p (node) + "Return TRUE if NODE is a function or a var definition." + (and (clojure-ts--list-node-p node) + (let ((sym (clojure-ts--node-child-skip-metadata node 0))) + (string-match-p (rx bol + (or "def" + "defn" + "defn-" + "definline" + "defrecord" + "defmacro" + "defmulti") + eol) + (clojure-ts--named-node-text sym))))) + (defconst clojure-ts--markdown-inline-sexp-nodes '("inline_link" "full_reference_link" "collapsed_reference_link" "uri_autolink" "email_autolink" "shortcut_link" "image" @@ -1490,7 +1513,8 @@ If JUSTIFY is non-nil, justify as well as fill the paragraph." `((clojure (sexp ,(regexp-opt clojure-ts--sexp-nodes)) (list ,(regexp-opt clojure-ts--list-nodes)) - (text ,(regexp-opt '("comment")))) + (text ,(regexp-opt '("comment"))) + (defun ,#'clojure-ts--defun-node-p)) (when clojure-ts-use-markdown-inline (markdown-inline (sexp ,(regexp-opt clojure-ts--markdown-inline-sexp-nodes)))))) @@ -1991,6 +2015,23 @@ value is `clojure-ts-thread-all-but-last'." (interactive "P") (clojure-ts--thread-all "->> " but-last)) +(defun clojure-ts-cycle-privacy () + "Make a definition at point public or private." + (interactive) + (if-let* ((node-at-point (treesit-node-at (point) 'clojure t)) + (defun-node (treesit-parent-until node-at-point 'defun t))) + (save-excursion + (goto-char (treesit-node-start defun-node)) + (search-forward-regexp (rx "def" (* letter) (? (group (or "-" " ^:private"))))) + (if (match-string 1) + (replace-match "" nil nil nil 1) + (goto-char (match-end 0)) + (insert (if (or clojure-ts-use-metadata-for-defn-privacy + (not (string= (match-string 0) "defn"))) + " ^:private" + "-")))) + (user-error "No defun at point"))) + (defvar clojure-ts-refactor-map (let ((map (make-sparse-keymap))) (keymap-set map "C-t" #'clojure-ts-thread) @@ -2001,6 +2042,8 @@ value is `clojure-ts-thread-all-but-last'." (keymap-set map "f" #'clojure-ts-thread-first-all) (keymap-set map "C-l" #'clojure-ts-thread-last-all) (keymap-set map "l" #'clojure-ts-thread-last-all) + (keymap-set map "C-p" #'clojure-ts-cycle-privacy) + (keymap-set map "p" #'clojure-ts-cycle-privacy) map) "Keymap for `clojure-ts-mode' refactoring commands.") @@ -2012,6 +2055,7 @@ value is `clojure-ts-thread-all-but-last'." (easy-menu-define clojure-ts-mode-menu map "Clojure[TS] Mode Menu" '("Clojure" ["Align expression" clojure-ts-align] + ["Cycle privacy" clojure-ts-cycle-privacy] ("Refactor -> and ->>" ["Thread once more" clojure-ts-thread] ["Fully thread a form with ->" clojure-ts-thread-first-all] diff --git a/test/clojure-ts-mode-cycling-test.el b/test/clojure-ts-mode-cycling-test.el new file mode 100644 index 0000000..d0e8130 --- /dev/null +++ b/test/clojure-ts-mode-cycling-test.el @@ -0,0 +1,163 @@ +;;; clojure-ts-mode-cycling-test.el --- Clojure[TS] Mode: cycling things tests -*- lexical-binding: t; -*- + +;; Copyright (C) 2025 Roman Rudakov + +;; Author: Roman Rudakov + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;;; Commentary: + +;; The code is adapted from `clojure-mode'. + +;;; Code: + +(require 'clojure-ts-mode) +(require 'buttercup) +(require 'test-helper "test/test-helper") + +(describe "clojure-ts-cycle-privacy" + + (when-refactoring-it "should turn a public defn into a private defn" + "(defn add [a b] + (+ a b))" + + "(defn- add [a b] + (+ a b))" + + (clojure-ts-cycle-privacy)) + + (when-refactoring-it "should also work from the beginning of a sexp" + "(defn- add [a b] + (+ a b))" + + "(defn add [a b] + (+ a b))" + + (backward-sexp) + (clojure-ts-cycle-privacy)) + + (when-refactoring-it "should use metadata when clojure-use-metadata-for-privacy is set to true" + "(defn add [a b] + (+ a b))" + + "(defn ^:private add [a b] + (+ a b))" + + (let ((clojure-ts-use-metadata-for-defn-privacy t)) + (clojure-ts-cycle-privacy))) + + (when-refactoring-it "should turn a private defn into a public defn" + "(defn- add [a b] + (+ a b))" + + "(defn add [a b] + (+ a b))" + + (clojure-ts-cycle-privacy)) + + (when-refactoring-it "should turn a private defn with metadata into a public defn" + "(defn ^:private add [a b] + (+ a b))" + + "(defn add [a b] + (+ a b))" + + (let ((clojure-ts-use-metadata-for-defn-privacy t)) + (clojure-ts-cycle-privacy))) + + (when-refactoring-it "should also work with pre-existing metadata" + "(def ^:dynamic config + \"docs\" + {:env \"staging\"})" + + "(def ^:private ^:dynamic config + \"docs\" + {:env \"staging\"})" + + (clojure-ts-cycle-privacy)) + + (when-refactoring-it "should turn a private def with metadata into a public def" + "(def ^:private config + \"docs\" + {:env \"staging\"})" + + "(def config + \"docs\" + {:env \"staging\"})" + + (clojure-ts-cycle-privacy)) + + (when-refactoring-it "should turn a public defmulti into a private defmulti" + "(defmulti service-charge (juxt account-level :tag))" + + "(defmulti ^:private service-charge (juxt account-level :tag))" + + (clojure-ts-cycle-privacy)) + + (when-refactoring-it "should turn a private defmulti into a public defmulti" + "(defmulti ^:private service-charge (juxt account-level :tag))" + + "(defmulti service-charge (juxt account-level :tag))" + + (clojure-ts-cycle-privacy)) + + (when-refactoring-it "should turn a public defmacro into a private defmacro" + "(defmacro unless [pred a b] + `(if (not ~pred) ~a ~b))" + + "(defmacro ^:private unless [pred a b] + `(if (not ~pred) ~a ~b))" + + (clojure-ts-cycle-privacy)) + + (when-refactoring-it "should turn a private defmacro into a public defmacro" + "(defmacro ^:private unless [pred a b] + `(if (not ~pred) ~a ~b))" + + "(defmacro unless [pred a b] + `(if (not ~pred) ~a ~b))" + + (clojure-ts-cycle-privacy)) + + (when-refactoring-it "should turn a private definline into a public definline" + "(definline bad-sqr [x] `(* ~x ~x))" + + "(definline ^:private bad-sqr [x] `(* ~x ~x))" + + (clojure-ts-cycle-privacy)) + + (when-refactoring-it "should turn a public definline into a private definline" + "(definline ^:private bad-sqr [x] `(* ~x ~x))" + + "(definline bad-sqr [x] `(* ~x ~x))" + + (clojure-ts-cycle-privacy)) + + (when-refactoring-it "should turn a private defrecord into a public defrecord" + "(defrecord Person [fname lname address])" + + "(defrecord ^:private Person [fname lname address])" + + (clojure-ts-cycle-privacy)) + + (when-refactoring-it "should turn a public defrecord into a private defrecord" + "(defrecord ^:private Person [fname lname address])" + + "(defrecord Person [fname lname address])" + + (clojure-ts-cycle-privacy))) + +(provide 'clojure-ts-mode-cycling-test) +;;; clojure-ts-mode-cycling-test.el ends here diff --git a/test/samples/refactoring.clj b/test/samples/refactoring.clj index e6f24b8..109243d 100644 --- a/test/samples/refactoring.clj +++ b/test/samples/refactoring.clj @@ -66,7 +66,19 @@ (->> (map square (filter even? [1 2 3 4 5]))) +(-> (dissoc (assoc {} :key "value") :lock)) + (deftask dev [] (comp (serve) (cljs (lala) 10))) + +(def my-name "Roma") + +(defn say-hello + [] + (println "Hello" my-name)) + +(definline bad-sqr [x] `(* ~x ~x)) + +(defmulti service-charge (juxt account-level :tag)) From c7a355588755d35144f64b0a4a8061bdd47cae45 Mon Sep 17 00:00:00 2001 From: Roman Rudakov Date: Sun, 4 May 2025 21:43:58 +0200 Subject: [PATCH 327/379] Introduce clojure-ts-toggle-keyword-string --- CHANGELOG.md | 1 + README.md | 20 +++++++++++------- clojure-ts-mode.el | 18 ++++++++++++++++ test/clojure-ts-mode-cycling-test.el | 31 ++++++++++++++++++++++++++++ 4 files changed, 62 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c8fc91b..292cbe0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ - [#89](https://github.com/clojure-emacs/clojure-ts-mode/pull/89): Introduce `clojure-ts-thread`, `clojure-ts-thread-first-all` and `clojure-ts-thread-last-all`. - [#90](https://github.com/clojure-emacs/clojure-ts-mode/pull/90): Introduce `clojure-ts-cycle-privacy`. +- [#91](https://github.com/clojure-emacs/clojure-ts-mode/pull/91): Introduce `clojure-ts-cycle-keyword-string`. ## 0.3.0 (2025-04-15) diff --git a/README.md b/README.md index bf14a33..4ef3293 100644 --- a/README.md +++ b/README.md @@ -393,20 +393,24 @@ threading macro. ### Cycling things +`clojure-ts-cycle-keyword-string`: Convert the string at point to a keyword and +vice versa. + `clojure-ts-cycle-privacy`: Cycle privacy of `def`s or `defn`s. Use metadata explicitly with setting `clojure-ts-use-metadata-for-defn-privacy` to `t` for `defn`s too. ### Default keybindings -| Keybinding | Command | -|:----------------------------|:------------------------------| -| `C-c SPC` | `clojure-ts-align` | -| `C-c C-r t` / `C-c C-r C-t` | `clojure-ts-thread` | -| `C-c C-r u` / `C-c C-r C-u` | `clojure-ts-unwind` | -| `C-c C-r f` / `C-c C-r C-f` | `clojure-ts-thread-first-all` | -| `C-c C-r l` / `C-c C-r C-l` | `clojure-ts-thread-last-all` | -| `C-c C-r p` / `C-c C-r C-p` | `clojure-ts-cycle-privacy` | +| Keybinding | Command | +|:----------------------------|:----------------------------------| +| `C-:` | `clojure-ts-cycle-keyword-string` | +| `C-c SPC` | `clojure-ts-align` | +| `C-c C-r t` / `C-c C-r C-t` | `clojure-ts-thread` | +| `C-c C-r u` / `C-c C-r C-u` | `clojure-ts-unwind` | +| `C-c C-r f` / `C-c C-r C-f` | `clojure-ts-thread-first-all` | +| `C-c C-r l` / `C-c C-r C-l` | `clojure-ts-thread-last-all` | +| `C-c C-r p` / `C-c C-r C-p` | `clojure-ts-cycle-privacy` | ### Customize refactoring commands prefix diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index a110d2f..4ce9a29 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -2032,6 +2032,22 @@ value is `clojure-ts-thread-all-but-last'." "-")))) (user-error "No defun at point"))) +(defun clojure-ts-cycle-keyword-string () + "Convert the string at point to a keyword, or vice versa." + (interactive) + (let ((node (treesit-thing-at-point 'sexp 'nested)) + (pos (point))) + (cond + ((clojure-ts--string-node-p node) + (if (string-match-p " " (treesit-node-text node t)) + (user-error "Cannot convert a string containing spaces to keyword") + (insert ?: (substring (clojure-ts--delete-and-extract-sexp) 1 -1)))) + ((clojure-ts--keyword-node-p node) + (insert ?\" (substring (clojure-ts--delete-and-extract-sexp) 1) ?\")) + (t + (user-error "No string or keyword at point"))) + (goto-char pos))) + (defvar clojure-ts-refactor-map (let ((map (make-sparse-keymap))) (keymap-set map "C-t" #'clojure-ts-thread) @@ -2050,10 +2066,12 @@ value is `clojure-ts-thread-all-but-last'." (defvar clojure-ts-mode-map (let ((map (make-sparse-keymap))) ;;(set-keymap-parent map clojure-mode-map) + (keymap-set map "C-:" #'clojure-ts-cycle-keyword-string) (keymap-set map "C-c SPC" #'clojure-ts-align) (keymap-set map clojure-ts-refactor-map-prefix clojure-ts-refactor-map) (easy-menu-define clojure-ts-mode-menu map "Clojure[TS] Mode Menu" '("Clojure" + ["Toggle between string & keyword" clojure-ts-cycle-keyword-string] ["Align expression" clojure-ts-align] ["Cycle privacy" clojure-ts-cycle-privacy] ("Refactor -> and ->>" diff --git a/test/clojure-ts-mode-cycling-test.el b/test/clojure-ts-mode-cycling-test.el index d0e8130..b0d83cb 100644 --- a/test/clojure-ts-mode-cycling-test.el +++ b/test/clojure-ts-mode-cycling-test.el @@ -27,6 +27,37 @@ (require 'buttercup) (require 'test-helper "test/test-helper") +(describe "clojure-ts-cycle-keyword-string" + (when-refactoring-with-point-it "should convert string to keyword" + "\"hel|lo\"" + + ":hel|lo" + + (clojure-ts-cycle-keyword-string)) + + (when-refactoring-with-point-it "should convert keyword to string" + ":|hello" + + "\"|hello\"" + + (clojure-ts-cycle-keyword-string)) + + (it "should signal a user error when there is nothing to convert at point" + (with-clojure-ts-buffer "[true false]" + (goto-char 2) + (expect (clojure-ts-cycle-keyword-string) + :to-throw + 'user-error + '("No string or keyword at point")))) + + (it "should signal a user error when string at point contains spaces" + (with-clojure-ts-buffer "\"Hello world\"" + (goto-char 2) + (expect (clojure-ts-cycle-keyword-string) + :to-throw + 'user-error + '("Cannot convert a string containing spaces to keyword"))))) + (describe "clojure-ts-cycle-privacy" (when-refactoring-it "should turn a public defn into a private defn" From edf0d32b92ee36fad797d02e7960a0dfbd93b648 Mon Sep 17 00:00:00 2001 From: Roman Rudakov Date: Mon, 5 May 2025 21:27:54 +0200 Subject: [PATCH 328/379] Introduce commands to convert collection type --- CHANGELOG.md | 1 + README.md | 34 +++-- clojure-ts-mode.el | 87 +++++++++++++ ...clojure-ts-mode-convert-collection-test.el | 119 ++++++++++++++++++ test/samples/refactoring.clj | 10 ++ 5 files changed, 242 insertions(+), 9 deletions(-) create mode 100644 test/clojure-ts-mode-convert-collection-test.el diff --git a/CHANGELOG.md b/CHANGELOG.md index 292cbe0..f2413e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ `clojure-ts-thread-last-all`. - [#90](https://github.com/clojure-emacs/clojure-ts-mode/pull/90): Introduce `clojure-ts-cycle-privacy`. - [#91](https://github.com/clojure-emacs/clojure-ts-mode/pull/91): Introduce `clojure-ts-cycle-keyword-string`. +- [#92](https://github.com/clojure-emacs/clojure-ts-mode/pull/92): Add commands to convert between collections types. ## 0.3.0 (2025-04-15) diff --git a/README.md b/README.md index 4ef3293..0891515 100644 --- a/README.md +++ b/README.md @@ -400,17 +400,33 @@ vice versa. explicitly with setting `clojure-ts-use-metadata-for-defn-privacy` to `t` for `defn`s too. +### Convert collection + +Convert any given collection at point to list, quoted list, map, vector or +set. The following commands are available: + +- `clojure-ts-convert-collection-to-list` +- `clojure-ts-convert-collection-to-quoted-list` +- `clojure-ts-convert-collection-to-map` +- `clojure-ts-convert-collection-to-vector` +- `clojure-ts-convert-collection-to-set` + ### Default keybindings -| Keybinding | Command | -|:----------------------------|:----------------------------------| -| `C-:` | `clojure-ts-cycle-keyword-string` | -| `C-c SPC` | `clojure-ts-align` | -| `C-c C-r t` / `C-c C-r C-t` | `clojure-ts-thread` | -| `C-c C-r u` / `C-c C-r C-u` | `clojure-ts-unwind` | -| `C-c C-r f` / `C-c C-r C-f` | `clojure-ts-thread-first-all` | -| `C-c C-r l` / `C-c C-r C-l` | `clojure-ts-thread-last-all` | -| `C-c C-r p` / `C-c C-r C-p` | `clojure-ts-cycle-privacy` | +| Keybinding | Command | +|:----------------------------|:-----------------------------------------------| +| `C-:` | `clojure-ts-cycle-keyword-string` | +| `C-c SPC` | `clojure-ts-align` | +| `C-c C-r t` / `C-c C-r C-t` | `clojure-ts-thread` | +| `C-c C-r u` / `C-c C-r C-u` | `clojure-ts-unwind` | +| `C-c C-r f` / `C-c C-r C-f` | `clojure-ts-thread-first-all` | +| `C-c C-r l` / `C-c C-r C-l` | `clojure-ts-thread-last-all` | +| `C-c C-r p` / `C-c C-r C-p` | `clojure-ts-cycle-privacy` | +| `C-c C-r (` / `C-c C-r C-(` | `clojure-ts-convert-collection-to-list` | +| `C-c C-r '` / `C-c C-r C-'` | `clojure-ts-convert-collection-to-quoted-list` | +| `C-c C-r {` / `C-c C-r C-{` | `clojure-ts-convert-collection-to-map` | +| `C-c C-r [` / `C-c C-r C-[` | `clojure-ts-convert-collection-to-vector` | +| `C-c C-r #` / `C-c C-r C-#` | `clojure-ts-convert-collection-to-set` | ### Customize refactoring commands prefix diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index 4ce9a29..204126c 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -2048,6 +2048,77 @@ value is `clojure-ts-thread-all-but-last'." (user-error "No string or keyword at point"))) (goto-char pos))) +(defun clojure-ts--collection-node-at-point () + "Return node at point that represent a collection." + (when-let* ((node (thread-first (point) + (treesit-node-at 'clojure) + (treesit-parent-until (rx bol + (or "map_lit" + "vec_lit" + "set_lit" + "list_lit" + "quoting_lit") + eol))))) + (cond + ;; If node is a list, check if it's quoted. + ((string= (treesit-node-type node) "list_lit") + (if-let* ((parent (treesit-node-parent node)) + ((string= (treesit-node-type parent) "quoting_lit"))) + parent + node)) + ;; If the point is at the quote character, check if the child node is a + ;; list. + ((string= (treesit-node-type node) "quoting_lit") + (when-let* ((first-child (clojure-ts--node-child-skip-metadata node 0)) + ((string= (treesit-node-type first-child) "list_lit"))) + node)) + (t node)))) + +(defun clojure-ts--convert-collection (delim-open &optional prefix) + "Convert collection at point to another collection type. + +The original collection is being unwrapped and wrapped between +DELIM-OPEN and its matching paren. If PREFIX is non-nil it's inserted +before DELIM-OPEN." + (if-let* ((coll-node (clojure-ts--collection-node-at-point))) + (save-excursion + (goto-char (treesit-node-start coll-node)) + (when (string-match-p (rx (or "set_lit" "quoting_lit")) + (treesit-node-type coll-node)) + (delete-char 1)) + (let ((parens-require-spaces nil) + (delete-pair-blink-delay 0)) + (when prefix + (insert-char prefix)) + (insert-pair 1 delim-open (matching-paren delim-open)) + (delete-pair 1))) + (user-error "No collection at point to convert"))) + +(defun clojure-ts-convert-collection-to-list () + "Convert collection at point to list." + (interactive) + (clojure-ts--convert-collection ?\()) + +(defun clojure-ts-convert-collection-to-quoted-list () + "Convert collection at point to quoted list." + (interactive) + (clojure-ts--convert-collection ?\( ?')) + +(defun clojure-ts-convert-collection-to-map () + "Convert collection at point to map." + (interactive) + (clojure-ts--convert-collection ?{)) + +(defun clojure-ts-convert-collection-to-vector () + "Convert collection at point to vector." + (interactive) + (clojure-ts--convert-collection ?\[)) + +(defun clojure-ts-convert-collection-to-set () + "Convert collection at point to set." + (interactive) + (clojure-ts--convert-collection ?{ ?#)) + (defvar clojure-ts-refactor-map (let ((map (make-sparse-keymap))) (keymap-set map "C-t" #'clojure-ts-thread) @@ -2060,6 +2131,16 @@ value is `clojure-ts-thread-all-but-last'." (keymap-set map "l" #'clojure-ts-thread-last-all) (keymap-set map "C-p" #'clojure-ts-cycle-privacy) (keymap-set map "p" #'clojure-ts-cycle-privacy) + (keymap-set map "C-(" #'clojure-ts-convert-collection-to-list) + (keymap-set map "(" #'clojure-ts-convert-collection-to-list) + (keymap-set map "C-'" #'clojure-ts-convert-collection-to-quoted-list) + (keymap-set map "'" #'clojure-ts-convert-collection-to-quoted-list) + (keymap-set map "C-{" #'clojure-ts-convert-collection-to-map) + (keymap-set map "{" #'clojure-ts-convert-collection-to-map) + (keymap-set map "C-[" #'clojure-ts-convert-collection-to-vector) + (keymap-set map "[" #'clojure-ts-convert-collection-to-vector) + (keymap-set map "C-#" #'clojure-ts-convert-collection-to-set) + (keymap-set map "#" #'clojure-ts-convert-collection-to-set) map) "Keymap for `clojure-ts-mode' refactoring commands.") @@ -2074,6 +2155,12 @@ value is `clojure-ts-thread-all-but-last'." ["Toggle between string & keyword" clojure-ts-cycle-keyword-string] ["Align expression" clojure-ts-align] ["Cycle privacy" clojure-ts-cycle-privacy] + ("Convert collection" + ["Convert to list" clojure-ts-convert-collection-to-list] + ["Convert to quoted list" clojure-ts-convert-collection-to-quoted-list] + ["Convert to map" clojure-ts-convert-collection-to-map] + ["Convert to vector" clojure-ts-convert-collection-to-vector] + ["Convert to set" clojure-ts-convert-collection-to-set]) ("Refactor -> and ->>" ["Thread once more" clojure-ts-thread] ["Fully thread a form with ->" clojure-ts-thread-first-all] diff --git a/test/clojure-ts-mode-convert-collection-test.el b/test/clojure-ts-mode-convert-collection-test.el new file mode 100644 index 0000000..05e04f6 --- /dev/null +++ b/test/clojure-ts-mode-convert-collection-test.el @@ -0,0 +1,119 @@ +;;; clojure-ts-mode-convert-collection-test.el --- Clojure[TS] Mode convert collection type. -*- lexical-binding: t; -*- + +;; Copyright (C) 2025 Roman Rudakov + +;; Author: Roman Rudakov + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;;; Commentary: + +;; Adapted from `clojure-mode'. + +;;; Code: + +(require 'clojure-ts-mode) +(require 'buttercup) +(require 'test-helper "test/test-helper") + +(describe "clojure-ts-convert-collection-to-map" + (when-refactoring-it "should convert a list to a map" + "(:a 1 :b 2)" + "{:a 1 :b 2}" + (backward-sexp) + (down-list) + (clojure-ts-convert-collection-to-map)) + + (it "should signal a user error when there is no collection at point" + (with-clojure-ts-buffer "false" + (backward-sexp) + (expect (clojure-ts-convert-collection-to-map) + :to-throw + 'user-error + '("No collection at point to convert"))))) + +(describe "clojure-ts-convert-collection-to-vector" + (when-refactoring-it "should convert a map to a vector" + "{:a 1 :b 2}" + "[:a 1 :b 2]" + (backward-sexp) + (down-list) + (clojure-ts-convert-collection-to-vector)) + + (it "should signal a user error when there is no collection at point" + (with-clojure-ts-buffer "false" + (backward-sexp) + (expect (clojure-ts-convert-collection-to-vector) + :to-throw + 'user-error + '("No collection at point to convert"))))) + +(describe "clojure-ts-convert-collection-to-set" + (when-refactoring-it "should convert a vector to a set" + "[1 2 3]" + "#{1 2 3}" + (backward-sexp) + (down-list) + (clojure-ts-convert-collection-to-set)) + + (when-refactoring-it "should convert a quoted list to a set" + "'(1 2 3)" + "#{1 2 3}" + (backward-sexp) + (down-list) + (clojure-ts-convert-collection-to-set)) + + (it "should signal a user error when there is no collection at point" + (with-clojure-ts-buffer "false" + (backward-sexp) + (expect (clojure-ts-convert-collection-to-set) + :to-throw + 'user-error + '("No collection at point to convert"))))) + +(describe "clojure-ts-convert-collection-to-list" + (when-refactoring-it "should convert a set to a list" + "#{1 2 3}" + "(1 2 3)" + (backward-sexp) + (down-list) + (clojure-ts-convert-collection-to-list)) + + (it "should signal a user error when there is no collection at point" + (with-clojure-ts-buffer "false" + (backward-sexp) + (expect (clojure-ts-convert-collection-to-list) + :to-throw + 'user-error + '("No collection at point to convert"))))) + +(describe "clojure-ts-convert-collection-to-quoted-list" + (when-refactoring-it "should convert a set to a quoted list" + "#{1 2 3}" + "'(1 2 3)" + (backward-sexp) + (down-list) + (clojure-ts-convert-collection-to-quoted-list)) + + (it "should signal a user error when there is no collection at point" + (with-clojure-ts-buffer "false" + (backward-sexp) + (expect (clojure-ts-convert-collection-to-quoted-list) + :to-throw + 'user-error + '("No collection at point to convert"))))) + + +(provide 'clojure-ts-mode-convert-collection-test) +;;; clojure-ts-mode-convert-collection-test.el ends here diff --git a/test/samples/refactoring.clj b/test/samples/refactoring.clj index 109243d..d06a77d 100644 --- a/test/samples/refactoring.clj +++ b/test/samples/refactoring.clj @@ -82,3 +82,13 @@ (definline bad-sqr [x] `(* ~x ~x)) (defmulti service-charge (juxt account-level :tag)) + +;; Convert collections. + +#{1 2 3} + +[1 2 3] + +;; TODO: Define indentation rule for `ns_map_lit` +#:hello{:name "Roma" + :world true} From a1fdc69c5c21f25468896213e931b00ea656758f Mon Sep 17 00:00:00 2001 From: Roman Rudakov Date: Sat, 10 May 2025 18:22:42 +0200 Subject: [PATCH 329/379] Introduce clojure-ts-add-arity refactoring command --- CHANGELOG.md | 1 + README.md | 8 + clojure-ts-mode.el | 213 +++++++++-- ...clojure-ts-mode-refactor-add-arity-test.el | 350 ++++++++++++++++++ test/samples/refactoring.clj | 40 ++ test/test-helper.el | 2 +- 6 files changed, 584 insertions(+), 30 deletions(-) create mode 100644 test/clojure-ts-mode-refactor-add-arity-test.el diff --git a/CHANGELOG.md b/CHANGELOG.md index f2413e2..600eabd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ - [#90](https://github.com/clojure-emacs/clojure-ts-mode/pull/90): Introduce `clojure-ts-cycle-privacy`. - [#91](https://github.com/clojure-emacs/clojure-ts-mode/pull/91): Introduce `clojure-ts-cycle-keyword-string`. - [#92](https://github.com/clojure-emacs/clojure-ts-mode/pull/92): Add commands to convert between collections types. +- [#93](https://github.com/clojure-emacs/clojure-ts-mode/pull/93): Introduce `clojure-ts-add-arity`. ## 0.3.0 (2025-04-15) diff --git a/README.md b/README.md index 0891515..75972c7 100644 --- a/README.md +++ b/README.md @@ -411,6 +411,13 @@ set. The following commands are available: - `clojure-ts-convert-collection-to-vector` - `clojure-ts-convert-collection-to-set` +### Add arity to a function or macro + +`clojure-ts-add-arity`: Add a new arity to an existing single-arity or +multi-arity function or macro. Function can be defined using `defn`, `fn` or +`defmethod` form. This command also supports functions defined inside forms like +`letfn`, `defprotol`, `reify` or `proxy`. + ### Default keybindings | Keybinding | Command | @@ -427,6 +434,7 @@ set. The following commands are available: | `C-c C-r {` / `C-c C-r C-{` | `clojure-ts-convert-collection-to-map` | | `C-c C-r [` / `C-c C-r C-[` | `clojure-ts-convert-collection-to-vector` | | `C-c C-r #` / `C-c C-r C-#` | `clojure-ts-convert-collection-to-set` | +| `C-c C-r a` / `C-c C-r C-a` | `clojure-ts-add-arity` | ### Customize refactoring commands prefix diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index 204126c..56fcd07 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -757,6 +757,10 @@ literals with regex grammar." "Return non-nil if NODE is a Clojure list." (string-equal "list_lit" (treesit-node-type node))) +(defun clojure-ts--vec-node-p (node) + "Return non-nil if NODE is a Clojure vector." + (string-equal "vec_lit" (treesit-node-type node))) + (defun clojure-ts--anon-fn-node-p (node) "Return non-nil if NODE is a Clojure function literal." (string-equal "anon_fn_lit" (treesit-node-type node))) @@ -1471,6 +1475,27 @@ If JUSTIFY is non-nil, justify as well as fill the paragraph." (fill-paragraph justify))) t)) +(defun clojure-ts--list-node-sym-text (node &optional include-anon-fn-lit) + "Return text of the first child of the NODE if NODE is a list. + +Return nil if the NODE is not a list or if the first child is not a +symbol. Optionally if INCLUDE-ANON-FN-LIT is non-nil, return the text +of the first symbol of a functional literal NODE." + (when (or (clojure-ts--list-node-p node) + (and include-anon-fn-lit + (clojure-ts--anon-fn-node-p node))) + (when-let* ((first-child (clojure-ts--node-child-skip-metadata node 0)) + ((clojure-ts--symbol-node-p first-child))) + (clojure-ts--named-node-text first-child)))) + +(defun clojure-ts--list-node-sym-match-p (node regex &optional include-anon-fn-lit) + "Return TRUE if NODE is a list and its first symbol matches the REGEX. + +Optionally if INCLUDE-ANON-FN-LIT is TRUE, perform the same check for a +function literal." + (when-let* ((sym-text (clojure-ts--list-node-sym-text node include-anon-fn-lit))) + (string-match-p regex sym-text))) + (defconst clojure-ts--sexp-nodes '("#_" ;; transpose-sexp near a discard macro moves it around. "num_lit" "sym_lit" "kwd_lit" "nil_lit" "bool_lit" @@ -1490,18 +1515,16 @@ If JUSTIFY is non-nil, justify as well as fill the paragraph." (defun clojure-ts--defun-node-p (node) "Return TRUE if NODE is a function or a var definition." - (and (clojure-ts--list-node-p node) - (let ((sym (clojure-ts--node-child-skip-metadata node 0))) - (string-match-p (rx bol - (or "def" - "defn" - "defn-" - "definline" - "defrecord" - "defmacro" - "defmulti") - eol) - (clojure-ts--named-node-text sym))))) + (clojure-ts--list-node-sym-match-p node + (rx bol + (or "def" + "defn" + "defn-" + "definline" + "defrecord" + "defmacro" + "defmulti") + eol))) (defconst clojure-ts--markdown-inline-sexp-nodes '("inline_link" "full_reference_link" "collapsed_reference_link" @@ -1727,19 +1750,23 @@ Forms between BEG and END are aligned according to ;;; Refactoring +(defun clojure-ts--parent-until (pred) + "Return the closest parent of node at point that satisfies PRED." + (when-let* ((node-at-point (treesit-node-at (point) 'clojure t))) + (treesit-parent-until node-at-point pred t))) + +(defun clojure-ts--search-list-form-at-point (sym-regex &optional include-anon-fn-lit) + "Return the list node at point which first symbol matches SYM-REGEX. + +If INCLUDE-ANON-FN-LIT is non-nil, this function may also return a +functional literal node." + (clojure-ts--parent-until + (lambda (node) + (clojure-ts--list-node-sym-match-p node sym-regex include-anon-fn-lit)))) + (defun clojure-ts--threading-sexp-node () "Return list node at point which is a threading expression." - (when-let* ((node-at-point (treesit-node-at (point) 'clojure t))) - ;; We don't want to match `cond->' and `cond->>', so we should define a very - ;; specific regexp. - (let ((sym-regex (rx bol (* "some") "->" (* ">") eol))) - (treesit-parent-until node-at-point - (lambda (node) - (and (or (clojure-ts--list-node-p node) - (clojure-ts--anon-fn-node-p node)) - (let ((first-child (treesit-node-child node 0 t))) - (clojure-ts--symbol-matches-p sym-regex first-child)))) - t)))) + (clojure-ts--search-list-form-at-point (rx bol (* "some") "->" (* ">") eol) t)) (defun clojure-ts--delete-and-extract-sexp () "Delete the surrounding sexp and return it." @@ -1874,9 +1901,7 @@ With universal argument \\[universal-argument], fully unwinds thread." (n) (1))) (if-let* ((threading-sexp (clojure-ts--threading-sexp-node)) - (sym (thread-first threading-sexp - (treesit-node-child 0 t) - (clojure-ts--named-node-text)))) + (sym (clojure-ts--list-node-sym-text threading-sexp t))) (save-excursion (let ((beg (thread-first threading-sexp (treesit-node-start) @@ -1962,9 +1987,7 @@ cannot be found." (interactive "p") (if-let* ((threading-sexp (clojure-ts--threading-sexp-node)) ((clojure-ts--threadable-p threading-sexp)) - (sym (thread-first threading-sexp - (treesit-node-child 0 t) - (clojure-ts--named-node-text)))) + (sym (clojure-ts--list-node-sym-text threading-sexp t))) (let ((beg (thread-first threading-sexp (treesit-node-start) (copy-marker))) @@ -2032,6 +2055,135 @@ value is `clojure-ts-thread-all-but-last'." "-")))) (user-error "No defun at point"))) +(defun clojure-ts--node-child (node predicate) + "Return the first child of the NODE that matches the PREDICATE. + +PREDICATE can be a symbol representing a thing in +`treesit-thing-settings', or a predicate, like regexp matching node +type, etc. See `treesit-thing-settings' for more details." + (thread-last (treesit-node-children node t) + (seq-find (lambda (child) + (treesit-node-match-p child predicate t))))) + +(defun clojure-ts--node-start-skip-metadata (node) + "Return NODE start position optionally skipping metadata." + (if (clojure-ts--metadata-node-p (treesit-node-child node 0 t)) + (treesit-node-start (treesit-node-child node 1)) + (treesit-node-start node))) + +(defun clojure-ts--add-arity-internal (fn-node) + "Add an arity to a function defined by FN-NODE." + (let* ((first-coll (clojure-ts--node-child fn-node (rx bol (or "vec_lit" "list_lit") eol))) + (coll-start (clojure-ts--node-start-skip-metadata first-coll)) + (line-parent (thread-first fn-node + (clojure-ts--node-child-skip-metadata 0) + (treesit-node-start) + (line-number-at-pos))) + (line-args (line-number-at-pos coll-start)) + (same-line-p (= line-parent line-args)) + (single-arity-p (clojure-ts--vec-node-p first-coll))) + (goto-char coll-start) + (when same-line-p + (newline-and-indent)) + (when single-arity-p + (insert-pair 2 ?\( ?\)) + (backward-up-list)) + (insert "([])\n") + ;; Put the point between square brackets. + (down-list -2))) + +(defun clojure-ts--add-arity-defprotocol-internal (fn-node) + "Add an arity to a defprotocol function defined by FN-NODE." + (let* ((args-vec (clojure-ts--node-child fn-node (rx bol "vec_lit" eol))) + (args-vec-start (clojure-ts--node-start-skip-metadata args-vec)) + (line-parent (thread-first fn-node + (clojure-ts--node-child-skip-metadata 0) + (treesit-node-start) + (line-number-at-pos))) + (line-args-vec (line-number-at-pos args-vec-start)) + (same-line-p (= line-parent line-args-vec))) + (goto-char args-vec-start) + (insert "[]") + (if same-line-p + (insert " ") + ;; If args vector is not at the same line, respect this and place each new + ;; vector on a new line. + (newline-and-indent)) + ;; Put the point between square brackets. + (down-list -1))) + +(defun clojure-ts--add-arity-reify-internal (fn-node) + "Add an arity to a reify function defined by FN-NODE." + (let* ((fn-name (clojure-ts--list-node-sym-text fn-node))) + (goto-char (clojure-ts--node-start-skip-metadata fn-node)) + (insert "(" fn-name " [])") + (newline-and-indent) + ;; Put the point between sqare brackets. + (down-list -2))) + +(defun clojure-ts--letfn-defn-p (node) + "Return non-nil if NODE is a function definition in a letfn form." + (when-let* ((parent (treesit-node-parent node))) + (and (clojure-ts--list-node-p node) + (clojure-ts--vec-node-p parent) + (let ((grandparent (treesit-node-parent parent))) + (string= (clojure-ts--list-node-sym-text grandparent) + "letfn"))))) + +(defun clojure-ts--proxy-defn-p (node) + "Return non-nil if NODE is a function definition in a proxy form." + (when-let* ((parent (treesit-node-parent node))) + (and (clojure-ts--list-node-p node) + (string= (clojure-ts--list-node-sym-text parent) "proxy")))) + +(defun clojure-ts--defprotocol-defn-p (node) + "Return non-nil if NODE is a function definition in a defprotocol form." + (when-let* ((parent (treesit-node-parent node))) + (and (clojure-ts--list-node-p node) + (string= (clojure-ts--list-node-sym-text parent) "defprotocol")))) + +(defun clojure-ts--reify-defn-p (node) + "Return non-nil if NODE is a function definition in a reify form." + (when-let* ((parent (treesit-node-parent node))) + (and (clojure-ts--list-node-p node) + (string= (clojure-ts--list-node-sym-text parent) "reify")))) + +(defun clojure-ts-add-arity () + "Add an arity to a function or macro." + (interactive) + (if-let* ((sym-regex (rx bol + (or "defn" + "letfn" + "fn" + "defmacro" + "defmethod" + "defprotocol" + "reify" + "proxy") + eol)) + (parent-def-node (clojure-ts--search-list-form-at-point sym-regex)) + (parent-def-sym (clojure-ts--list-node-sym-text parent-def-node)) + (fn-node (cond + ((string= parent-def-sym "letfn") + (clojure-ts--parent-until #'clojure-ts--letfn-defn-p)) + ((string= parent-def-sym "proxy") + (clojure-ts--parent-until #'clojure-ts--proxy-defn-p)) + ((string= parent-def-sym "defprotocol") + (clojure-ts--parent-until #'clojure-ts--defprotocol-defn-p)) + ((string= parent-def-sym "reify") + (clojure-ts--parent-until #'clojure-ts--reify-defn-p)) + (t parent-def-node)))) + (let ((beg-marker (copy-marker (treesit-node-start parent-def-node))) + (end-marker (copy-marker (treesit-node-end parent-def-node)))) + (cond + ((string= parent-def-sym "defprotocol") + (clojure-ts--add-arity-defprotocol-internal fn-node)) + ((string= parent-def-sym "reify") + (clojure-ts--add-arity-reify-internal fn-node)) + (t (clojure-ts--add-arity-internal fn-node))) + (indent-region beg-marker end-marker)) + (user-error "No suitable form to add an arity at point"))) + (defun clojure-ts-cycle-keyword-string () "Convert the string at point to a keyword, or vice versa." (interactive) @@ -2141,6 +2293,8 @@ before DELIM-OPEN." (keymap-set map "[" #'clojure-ts-convert-collection-to-vector) (keymap-set map "C-#" #'clojure-ts-convert-collection-to-set) (keymap-set map "#" #'clojure-ts-convert-collection-to-set) + (keymap-set map "C-a" #'clojure-ts-add-arity) + (keymap-set map "a" #'clojure-ts-add-arity) map) "Keymap for `clojure-ts-mode' refactoring commands.") @@ -2155,6 +2309,7 @@ before DELIM-OPEN." ["Toggle between string & keyword" clojure-ts-cycle-keyword-string] ["Align expression" clojure-ts-align] ["Cycle privacy" clojure-ts-cycle-privacy] + ["Add function/macro arity" clojure-ts-add-arity] ("Convert collection" ["Convert to list" clojure-ts-convert-collection-to-list] ["Convert to quoted list" clojure-ts-convert-collection-to-quoted-list] diff --git a/test/clojure-ts-mode-refactor-add-arity-test.el b/test/clojure-ts-mode-refactor-add-arity-test.el new file mode 100644 index 0000000..9c31f27 --- /dev/null +++ b/test/clojure-ts-mode-refactor-add-arity-test.el @@ -0,0 +1,350 @@ +;;; clojure-ts-mode-refactor-add-arity-test.el --- Clojure[TS] Mode: refactor add arity test. -*- lexical-binding: t; -*- + +;; Copyright (C) 2025 Roman Rudakov + +;; Author: Roman Rudakov + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;;; Commentary: + +;; Test for `clojure-ts-add-arity' + +;;; Code: + +(require 'clojure-ts-mode) +(require 'buttercup) +(require 'test-helper "test/test-helper") + +(describe "clojure-ts-add-arity" + + (when-refactoring-with-point-it "should add an arity to a single-arity defn with args on same line" + "(defn foo [arg] + body|)" + + "(defn foo + ([|]) + ([arg] + body))" + + (clojure-ts-add-arity)) + + (when-refactoring-with-point-it "should add an arity to a single-arity defn with args on next line" + "(defn foo + [arg] + bo|dy)" + + "(defn foo + ([|]) + ([arg] + body))" + + (clojure-ts-add-arity)) + + (when-refactoring-with-point-it "should handle a single-arity defn with a docstring" + "(defn foo + \"some docst|ring\" + [arg] + body)" + + "(defn foo + \"some docstring\" + ([|]) + ([arg] + body))" + + (clojure-ts-add-arity)) + + (when-refactoring-with-point-it "should handle a single-arity defn with metadata" + "(defn fo|o + ^{:bla \"meta\"} + [arg] + body)" + + "(defn foo + ^{:bla \"meta\"} + ([|]) + ([arg] + body))" + + (clojure-ts-add-arity)) + + (when-refactoring-with-point-it "should add an arity to a multi-arity defn" + "(defn foo + ([arg1]) + ([ar|g1 arg2] + body))" + + "(defn foo + ([|]) + ([arg1]) + ([arg1 arg2] + body))" + + (clojure-ts-add-arity)) + + (when-refactoring-with-point-it "should handle a multi-arity defn with a docstring" + "(defn foo + \"some docstring\" + ([]) + ([arg|] + body))" + + "(defn foo + \"some docstring\" + ([|]) + ([]) + ([arg] + body))" + + (clojure-ts-add-arity)) + + (when-refactoring-with-point-it "should handle a multi-arity defn with metadata" + "(defn foo + \"some docstring\" + ^{:bla \"meta\"} + ([]) + |([arg] + body))" + + "(defn foo + \"some docstring\" + ^{:bla \"meta\"} + ([|]) + ([]) + ([arg] + body))" + + (clojure-ts-add-arity)) + + (when-refactoring-with-point-it "should handle a single-arity fn" + "(fn foo [arg] + body|)" + + "(fn foo + ([|]) + ([arg] + body))" + + (clojure-ts-add-arity)) + + (when-refactoring-with-point-it "should handle a multi-arity fn" + "(fn foo + ([x y] + body) + ([a|rg] + body))" + + "(fn foo + ([|]) + ([x y] + body) + ([arg] + body))" + + (clojure-ts-add-arity)) + + (when-refactoring-with-point-it "should handle a single-arity defmacro" + "(defmacro foo [arg] + body|)" + + "(defmacro foo + ([|]) + ([arg] + body))" + + (clojure-ts-add-arity)) + + (when-refactoring-with-point-it "should handle a multi-arity defmacro" + "(defmacro foo + ([x y] + body) + ([a|rg] + body))" + + "(defmacro foo + ([|]) + ([x y] + body) + ([arg] + body))" + + (clojure-ts-add-arity)) + + (when-refactoring-with-point-it "should handle a single-arity defmethod" + "(defmethod foo :bar [arg] + body|)" + + "(defmethod foo :bar + ([|]) + ([arg] + body))" + + (clojure-ts-add-arity)) + + (when-refactoring-with-point-it "should handle a multi-arity defmethod" + "(defmethod foo :bar + ([x y] + body) + ([a|rg] + body))" + + "(defmethod foo :bar + ([|]) + ([x y] + body) + ([arg] + body))" + + (clojure-ts-add-arity)) + + (when-refactoring-with-point-it "should handle a defn inside a reader conditional" + "#?(:clj + (defn foo + \"some docstring\" + ^{:bla \"meta\"} + |([arg] + body)))" + + "#?(:clj + (defn foo + \"some docstring\" + ^{:bla \"meta\"} + ([|]) + ([arg] + body)))" + + (clojure-ts-add-arity)) + + (when-refactoring-with-point-it "should handle a defn inside a reader conditional with 2 platform tags" + "#?(:clj + (defn foo + \"some docstring\" + ^{:bla \"meta\"} + |([arg] + body)) + :cljs + (defn foo + \"some docstring\" + ^{:bla \"meta\"} + ([arg] + body)))" + + "#?(:clj + (defn foo + \"some docstring\" + ^{:bla \"meta\"} + ([|]) + ([arg] + body)) + :cljs + (defn foo + \"some docstring\" + ^{:bla \"meta\"} + ([arg] + body)))" + + (clojure-ts-add-arity)) + + (when-refactoring-with-point-it "should handle a single-arity fn inside a letfn" + "(letfn [(foo [x] + bo|dy)] + (foo 3))" + + "(letfn [(foo + ([|]) + ([x] + body))] + (foo 3))" + + (clojure-ts-add-arity)) + + (when-refactoring-with-point-it "should handle a multi-arity fn inside a letfn" + "(letfn [(foo + ([x] + body) + |([x y] + body))] + (foo 3))" + + "(letfn [(foo + ([|]) + ([x] + body) + ([x y] + body))] + (foo 3))" + + (clojure-ts-add-arity)) + + (when-refactoring-with-point-it "should handle a proxy" + "(proxy [Foo] [] + (bar [arg] + body|))" + + "(proxy [Foo] [] + (bar + ([|]) + ([arg] + body)))" + + (clojure-ts-add-arity)) + + (when-refactoring-with-point-it "should handle a defprotocol" + "(defprotocol Foo + \"some docstring\" + (bar [arg] [x |y] \"some docstring\"))" + + "(defprotocol Foo + \"some docstring\" + (bar [|] [arg] [x y] \"some docstring\"))" + + (clojure-ts-add-arity)) + + (when-refactoring-with-point-it "should handle a reify" + "(reify Foo + (bar [arg] body) + (blahs [arg]| body))" + + "(reify Foo + (bar [arg] body) + (blahs [|]) + (blahs [arg] body))" + + (clojure-ts-add-arity)) + + (it "should signal a user error when point is not inside a function body" + (with-clojure-ts-buffer-point " +(letf|n [(foo + ([x] + body) + ([x y] + body))] + (foo 3))" + (expect (clojure-ts-add-arity) + :to-throw + 'user-error + '("No suitable form to add an arity at point"))) + + (with-clojure-ts-buffer-point " +(defprotocol Fo|o + \"some docstring\" + (bar [arg] [x y] \"some docstring\"))" + (expect (clojure-ts-add-arity) + :to-throw + 'user-error + '("No suitable form to add an arity at point"))))) + +(provide 'clojure-ts-mode-refactor-add-arity-test) +;;; clojure-ts-mode-refactor-add-arity-test.el ends here diff --git a/test/samples/refactoring.clj b/test/samples/refactoring.clj index d06a77d..641e3c5 100644 --- a/test/samples/refactoring.clj +++ b/test/samples/refactoring.clj @@ -92,3 +92,43 @@ ;; TODO: Define indentation rule for `ns_map_lit` #:hello{:name "Roma" :world true} + + +(reify + java.io.FileFilter + (accept [this f] + (.isDirectory f)) + + (hello [world] + false)) + +(defmulti which-color-mm (fn [m & args] (:color m))) +(defmethod which-color-mm :blue + ([m] (print m)) + ([m f] (f m))) + +(letfn [(twice [x] + (* x 2)) + (six-times [y] + (* (twice y) 3))] + (println "Twice 15 =" (twice 15)) + (println "Six times 15 =" (six-times 15))) + +(let [p (proxy [java.io.InputStream] [] + (read + ([] 1) + ([^bytes bytes] 2) + ([^bytes bytes off len] 3)))] + (println (.read p)) + (println (.read p (byte-array 3))) + (println (.read p (byte-array 3) 0 3))) + +(defprotocol Fly + "A simple protocol for flying" + (fly [this] + "Method to fly")) + +(defn foo + ^{:bla "meta"} + [arg] + body) diff --git a/test/test-helper.el b/test/test-helper.el index a99ceec..fa821e6 100644 --- a/test/test-helper.el +++ b/test/test-helper.el @@ -39,7 +39,7 @@ And evaluate BODY." TEXT is a string with a | indicating where point is. The | will be erased and point left there." - (declare (indent 2)) + (declare (indent 1)) `(progn (with-clojure-ts-buffer ,text (goto-char (point-min)) From 93253746c0a360532bad3bcd5ef850925b0f8b14 Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Mon, 12 May 2025 14:05:13 +0300 Subject: [PATCH 330/379] Improve the refactoring docs a bit --- README.md | 83 +++++++++++++++++++++++++++---------------------------- 1 file changed, 40 insertions(+), 43 deletions(-) diff --git a/README.md b/README.md index 75972c7..59a8fa2 100644 --- a/README.md +++ b/README.md @@ -376,27 +376,54 @@ following customization: ### Threading macros related features -`clojure-ts-thread`: Thread another form into the surrounding thread. Both -`->>`/`some->>` and `->`/`some->` variants are supported. +There are a bunch of commands for threading and unwinding threaded Clojure forms: -`clojure-ts-unwind`: Unwind a threaded expression. Supports both `->>`/`some->>` +- `clojure-ts-thread`: Thread another form into the surrounding thread. Both +`->>`/`some->>` and `->`/`some->` variants are supported. +- `clojure-ts-unwind`: Unwind a threaded expression. Supports both `->>`/`some->>` and `->`/`some->`. - -`clojure-ts-thread-first-all`: Introduce the thread first macro (`->`) and +- `clojure-ts-thread-first-all`: Introduce the thread first macro (`->`) and rewrite the entire form. With a prefix argument do not thread the last form. - -`clojure-ts-thread-last-all`: Introduce the thread last macro and rewrite the +- `clojure-ts-thread-last-all`: Introduce the thread last macro and rewrite the entire form. With a prefix argument do not thread the last form. - -`clojure-ts-unwind-all`: Fully unwind a threaded expression removing the +- `clojure-ts-unwind-all`: Fully unwind a threaded expression removing the threading macro. +#### Customize threading refactoring behavior + +By default `clojure-ts-thread-first-all` and `clojure-ts-thread-last-all` will +thread all nested expressions. For example this expression: + +```clojure +(->map (assoc {} :key "value") :lock) +``` + +After executing `clojure-ts-thread-last-all` will be converted to: + +```clojure +(-> {} + (assoc :key "value") + (->map :lock)) +``` + +This behavior can be changed by setting: + +```emacs-lisp +(setopt clojure-ts-thread-all-but-last t) +``` + +Then the last expression will not be threaded and the result will be: + +```clojure +(-> (assoc {} :key "value") + (->map :lock)) +``` + ### Cycling things -`clojure-ts-cycle-keyword-string`: Convert the string at point to a keyword and +- `clojure-ts-cycle-keyword-string`: Convert the string at point to a keyword and vice versa. - -`clojure-ts-cycle-privacy`: Cycle privacy of `def`s or `defn`s. Use metadata +- `clojure-ts-cycle-privacy`: Cycle privacy of `def`s or `defn`s. Use metadata explicitly with setting `clojure-ts-use-metadata-for-defn-privacy` to `t` for `defn`s too. @@ -441,40 +468,10 @@ multi-arity function or macro. Function can be defined using `defn`, `fn` or By default prefix for all refactoring commands is `C-c C-r`. It can be changed by customizing `clojure-ts-refactor-map-prefix` variable. -### Customize threading refactoring behavior - -By default `clojure-ts-thread-first-all` and `clojure-ts-thread-last-all` will -thread all nested expressions. For example this expression: - -```clojure -(->map (assoc {} :key "value") :lock) -``` - -After executing `clojure-ts-thread-last-all` will be converted to: - -```clojure -(-> {} - (assoc :key "value") - (->map :lock)) -``` - -This behavior can be changed by setting: - -```emacs-lisp -(setopt clojure-ts-thread-all-but-last t) -``` - -Then the last expression will not be threaded and the result will be: - -```clojure -(-> (assoc {} :key "value") - (->map :lock)) -``` - ## Migrating to clojure-ts-mode If you are migrating to `clojure-ts-mode` note that `clojure-mode` is still -required for cider and clj-refactor packages to work properly. +required for CIDER and `clj-refactor` packages to work properly. After installing the package do the following: From 6db68e63c595d037ea497782e797cc5eae32cfcc Mon Sep 17 00:00:00 2001 From: Roman Rudakov Date: Mon, 12 May 2025 19:40:43 +0200 Subject: [PATCH 331/379] Add dis_expr to the list of sexp things This fixes a bug in clojure-ts-align. Before this change #_ and the following ignored expression were treated as 2 separate s-expressions, so we were stuck in an endless loop. --- CHANGELOG.md | 2 ++ clojure-ts-mode.el | 3 ++- test/clojure-ts-mode-indentation-test.el | 10 +++++++++- test/samples/align.clj | 7 +++++++ 4 files changed, 20 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 600eabd..189cfb9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,8 @@ - [#91](https://github.com/clojure-emacs/clojure-ts-mode/pull/91): Introduce `clojure-ts-cycle-keyword-string`. - [#92](https://github.com/clojure-emacs/clojure-ts-mode/pull/92): Add commands to convert between collections types. - [#93](https://github.com/clojure-emacs/clojure-ts-mode/pull/93): Introduce `clojure-ts-add-arity`. +- Fix an issue where `clojure-ts-align` would hang when called within an + expression containing ignored forms. ## 0.3.0 (2025-04-15) diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index 56fcd07..cc10a65 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -1505,7 +1505,8 @@ function literal." "var_quoting_lit" "sym_val_lit" "evaling_lit" "tagged_or_ctor_lit" "splicing_read_cond_lit" "derefing_lit" "quoting_lit" "syn_quoting_lit" - "unquote_splicing_lit" "unquoting_lit") + "unquote_splicing_lit" "unquoting_lit" + "dis_expr") "A regular expression that matches nodes that can be treated as s-expressions.") (defconst clojure-ts--list-nodes diff --git a/test/clojure-ts-mode-indentation-test.el b/test/clojure-ts-mode-indentation-test.el index 942175a..2f6d4e3 100644 --- a/test/clojure-ts-mode-indentation-test.el +++ b/test/clojure-ts-mode-indentation-test.el @@ -596,4 +596,12 @@ b |20])" :a (let [a 1 aa (+ a 1)] aa); comment - :aa 2)")) + :aa 2)") + + (when-aligning-it "should work correctly when there are ignored forms" + "{:map \"with\" + :some #_\"ignored\" \"form\"}" + + "{:map \"with\" + :multiple \"ignored\" + #_#_:forms \"foo\"}")) diff --git a/test/samples/align.clj b/test/samples/align.clj index f70e767..b7933f3 100644 --- a/test/samples/align.clj +++ b/test/samples/align.clj @@ -55,3 +55,10 @@ aa (+ a 1)] aa); comment :aa 2) + +{:map "with" + :some #_"ignored" "form"} + +{:map "with" + :multiple "ignored" + #_#_:forms "foo"} From 83d1aed86b385d39b06fc5daf5c32d07960e23e9 Mon Sep 17 00:00:00 2001 From: Roman Rudakov Date: Mon, 12 May 2025 20:14:33 +0200 Subject: [PATCH 332/379] Better handling of namespaced maps --- CHANGELOG.md | 3 +- clojure-ts-mode.el | 36 ++++++++++++++++-------- test/clojure-ts-mode-indentation-test.el | 7 ++++- test/samples/refactoring.clj | 8 ++++-- 4 files changed, 36 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 189cfb9..5a6b385 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,8 +17,7 @@ - [#91](https://github.com/clojure-emacs/clojure-ts-mode/pull/91): Introduce `clojure-ts-cycle-keyword-string`. - [#92](https://github.com/clojure-emacs/clojure-ts-mode/pull/92): Add commands to convert between collections types. - [#93](https://github.com/clojure-emacs/clojure-ts-mode/pull/93): Introduce `clojure-ts-add-arity`. -- Fix an issue where `clojure-ts-align` would hang when called within an - expression containing ignored forms. +- [#94](https://github.com/clojure-emacs/clojure-ts-mode/pull/94): Add indentation rules and `clojure-ts-align` support for namespaced maps. ## 0.3.0 (2025-04-15) diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index cc10a65..f69082e 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -1410,17 +1410,23 @@ if NODE has metadata and its parent has type NODE-TYPE." (clojure-ts--node-child-skip-metadata parent n)))) (defun clojure-ts--semantic-indent-rules () - "Return a list of indentation rules for `treesit-simple-indent-rules'." + "Return a list of indentation rules for `treesit-simple-indent-rules'. + +NOTE: All built-in matchers (such as `parent-is' etc) expect a node type +regex. Therefore, if the string map_lit is used, it will incorrectly +match both map_lit and ns_map_lit. To prevent this, more precise +regexes with anchors matching the beginning and end of the line are +used." `((clojure - ((parent-is "source") parent-bol 0) + ((parent-is "^source$") parent-bol 0) (clojure-ts--match-docstring parent 0) ;; Collections items with metadata. ;; ;; This should be before `clojure-ts--match-with-metadata', otherwise they ;; will never be matched. - (,(clojure-ts--match-collection-item-with-metadata "vec_lit") grand-parent 1) - (,(clojure-ts--match-collection-item-with-metadata "map_lit") grand-parent 1) - (,(clojure-ts--match-collection-item-with-metadata "set_lit") grand-parent 2) + (,(clojure-ts--match-collection-item-with-metadata "^vec_lit$") grand-parent 1) + (,(clojure-ts--match-collection-item-with-metadata "^map_lit$") grand-parent 1) + (,(clojure-ts--match-collection-item-with-metadata "^set_lit$") grand-parent 2) ;; ;; If we enable this rule for lists, it will break many things. ;; (,(clojure-ts--match-collection-item-with-metadata "list_lit") grand-parent 1) @@ -1428,12 +1434,13 @@ if NODE has metadata and its parent has type NODE-TYPE." ;; All other forms with metadata. (clojure-ts--match-with-metadata parent 0) ;; Literal Sequences - ((parent-is "vec_lit") parent 1) ;; https://guide.clojure.style/#bindings-alignment - ((parent-is "map_lit") parent 1) ;; https://guide.clojure.style/#map-keys-alignment - ((parent-is "set_lit") parent 2) - ((parent-is "splicing_read_cond_lit") parent 4) - ((parent-is "read_cond_lit") parent 3) - ((parent-is "tagged_or_ctor_lit") parent 0) + ((parent-is "^vec_lit$") parent 1) ;; https://guide.clojure.style/#bindings-alignment + ((parent-is "^map_lit$") parent 1) ;; https://guide.clojure.style/#map-keys-alignment + ((parent-is "^set_lit$") parent 2) + ((parent-is "^splicing_read_cond_lit$") parent 4) + ((parent-is "^read_cond_lit$") parent 3) + ((parent-is "^tagged_or_ctor_lit$") parent 0) + ((parent-is "^ns_map_lit$") (nth-sibling 2) 1) ;; https://guide.clojure.style/#body-indentation (clojure-ts--match-form-body clojure-ts--anchor-parent-opening-paren 2) ;; https://guide.clojure.style/#threading-macros-alignment @@ -1441,7 +1448,7 @@ if NODE has metadata and its parent has type NODE-TYPE." ;; https://guide.clojure.style/#vertically-align-fn-args (clojure-ts--match-function-call-arg ,(clojure-ts--anchor-nth-sibling 1) 0) ;; https://guide.clojure.style/#one-space-indent - ((parent-is "list_lit") parent 1)))) + ((parent-is "^list_lit$") parent 1)))) (defun clojure-ts--configured-indent-rules () "Gets the configured choice of indent rules." @@ -1640,6 +1647,7 @@ have changed." (query (treesit-query-compile 'clojure (append `(((map_lit) @map) + ((ns_map_lit) @ns-map) ((list_lit ((sym_lit) @sym (:match ,(clojure-ts-symbol-regexp clojure-ts-align-binding-forms) @sym)) @@ -1686,6 +1694,10 @@ subsequent special arguments based on block indentation rules." (goto-char (treesit-node-start node)) (when-let* ((cur-sexp (treesit-node-first-child-for-pos node (point) t))) (goto-char (treesit-node-start cur-sexp)) + ;; For namespaced maps we need to skip the namespace, which is the first + ;; nested sexp. + (when (equal sexp-type 'ns-map) + (treesit-beginning-of-thing 'sexp -1 'nested)) ;; For cond forms we need to skip first n + 1 nodes according to block ;; indentation rules. First node to skip is the symbol itself. (when (equal sexp-type 'cond) diff --git a/test/clojure-ts-mode-indentation-test.el b/test/clojure-ts-mode-indentation-test.el index 2f6d4e3..bda3538 100644 --- a/test/clojure-ts-mode-indentation-test.el +++ b/test/clojure-ts-mode-indentation-test.el @@ -604,4 +604,9 @@ b |20])" "{:map \"with\" :multiple \"ignored\" - #_#_:forms \"foo\"}")) + #_#_:forms \"foo\"}") + + (when-aligning-it "should support namespaced maps" + "#:hello {:world true + :foo \"bar\" + :some-very-long \"value\"}")) diff --git a/test/samples/refactoring.clj b/test/samples/refactoring.clj index 641e3c5..c7547bf 100644 --- a/test/samples/refactoring.clj +++ b/test/samples/refactoring.clj @@ -89,10 +89,12 @@ [1 2 3] -;; TODO: Define indentation rule for `ns_map_lit` -#:hello{:name "Roma" - :world true} +#:hello {:world true + :foo "bar" + :some-very-long "value"} +{:name "Roma" + :foo true} (reify java.io.FileFilter From cca0e9f8a9f3736d503b2a87977c01f5d6b297ee Mon Sep 17 00:00:00 2001 From: Roman Rudakov Date: Mon, 12 May 2025 20:57:28 +0200 Subject: [PATCH 333/379] Introduce more cycling refactoring commands Added: - clojure-ts-cycle-conditional - clojure-ts-cycle-not --- CHANGELOG.md | 1 + README.md | 7 +++ clojure-ts-mode.el | 91 ++++++++++++++++++++++++++++ test/clojure-ts-mode-cycling-test.el | 78 ++++++++++++++++++++++++ test/samples/refactoring.clj | 7 +++ 5 files changed, 184 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a6b385..059aa14 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ - [#92](https://github.com/clojure-emacs/clojure-ts-mode/pull/92): Add commands to convert between collections types. - [#93](https://github.com/clojure-emacs/clojure-ts-mode/pull/93): Introduce `clojure-ts-add-arity`. - [#94](https://github.com/clojure-emacs/clojure-ts-mode/pull/94): Add indentation rules and `clojure-ts-align` support for namespaced maps. +- Introduce `clojure-ts-cycle-conditional` and `clojure-ts-cycle-not`. ## 0.3.0 (2025-04-15) diff --git a/README.md b/README.md index 59a8fa2..36aa137 100644 --- a/README.md +++ b/README.md @@ -426,6 +426,11 @@ vice versa. - `clojure-ts-cycle-privacy`: Cycle privacy of `def`s or `defn`s. Use metadata explicitly with setting `clojure-ts-use-metadata-for-defn-privacy` to `t` for `defn`s too. +- `clojure-ts-cycle-conditional`: Change a surrounding conditional form to its + negated counterpart, or vice versa (supports `if`/`if-not` and + `when`/`when-not`). For `if`/`if-not` also transposes the else and then + branches, keeping the semantics the same as before. +- `clojure-ts-cycle-not`: Add or remove a `not` form around the current form. ### Convert collection @@ -461,6 +466,8 @@ multi-arity function or macro. Function can be defined using `defn`, `fn` or | `C-c C-r {` / `C-c C-r C-{` | `clojure-ts-convert-collection-to-map` | | `C-c C-r [` / `C-c C-r C-[` | `clojure-ts-convert-collection-to-vector` | | `C-c C-r #` / `C-c C-r C-#` | `clojure-ts-convert-collection-to-set` | +| `C-c C-r c` / `C-c C-r C-c` | `clojure-ts-cycle-conditional` | +| `C-c C-r o` / `C-c C-r C-o` | `clojure-ts-cycle-not` | | `C-c C-r a` / `C-c C-r C-a` | `clojure-ts-add-arity` | ### Customize refactoring commands prefix diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index f69082e..84aad83 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -1872,6 +1872,31 @@ functional literal node." (clojure-ts--skip-first-child threading-sexp) (not (treesit-end-of-thing 'sexp 2 'restricted))))) +(defun clojure-ts--raise-sexp () + "Raise current sexp one level higher up the tree. + +The built-in `raise-sexp' function doesn't work well with a few Clojure +nodes (function literals, expressions with metadata etc.), it loses some +parenthesis." + (when-let* ((sexp-node (treesit-thing-at (point) 'sexp)) + (beg (thread-first sexp-node + (clojure-ts--node-start-skip-metadata) + (copy-marker))) + (end (thread-first sexp-node + (treesit-node-end) + (copy-marker)))) + (when-let* ((parent (treesit-node-parent sexp-node)) + ((not (string= (treesit-node-type parent) "source"))) + (parent-beg (thread-first parent + (clojure-ts--node-start-skip-metadata) + (copy-marker))) + (parent-end (thread-first parent + (treesit-node-end) + (copy-marker)))) + (save-excursion + (delete-region parent-beg beg) + (delete-region end parent-end))))) + (defun clojure-ts--pop-out-of-threading () "Raise a sexp up a level to unwind a threading form." (let* ((threading-sexp (clojure-ts--threading-sexp-node)) @@ -2284,6 +2309,66 @@ before DELIM-OPEN." (interactive) (clojure-ts--convert-collection ?{ ?#)) +(defun clojure-ts-cycle-conditional () + "Change a surrounding conditional form to its negated counterpart, or vice versa." + (interactive) + (if-let* ((sym-regex (rx bol + (or "if" "if-not" "when" "when-not") + eol)) + (cond-node (clojure-ts--search-list-form-at-point sym-regex t)) + (cond-sym (clojure-ts--list-node-sym-text cond-node))) + (let ((beg (treesit-node-start cond-node)) + (end-marker (copy-marker (treesit-node-end cond-node))) + (new-sym (pcase cond-sym + ("if" "if-not") + ("if-not" "if") + ("when" "when-not") + ("when-not" "when")))) + (save-excursion + (goto-char (clojure-ts--node-start-skip-metadata cond-node)) + (down-list 1) + (delete-char (length cond-sym)) + (insert new-sym) + (when (member cond-sym '("if" "if-not")) + (forward-sexp 2) + (transpose-sexps 1)) + (indent-region beg end-marker))) + (user-error "No conditional expression found"))) + +(defun clojure-ts--point-outside-node-p (node) + "Return non-nil if point is outside of the actual NODE start. + +Clojure grammar treats metadata as part of an expression, so for example +^boolean (not (= 2 2)) is a single list node, including metadata. This +causes issues for functions that navigate by s-expressions and lists. +This function returns non-nil if point is outside of the outermost +parenthesis." + (let* ((actual-node-start (clojure-ts--node-start-skip-metadata node)) + (node-end (treesit-node-end node)) + (pos (point))) + (or (< pos actual-node-start) + (> pos node-end)))) + +(defun clojure-ts-cycle-not () + "Add or remove a not form around the current form." + (interactive) + (if-let* ((list-node (clojure-ts--parent-until (rx bol "list_lit" eol))) + ((not (clojure-ts--point-outside-node-p list-node)))) + (let ((beg (treesit-node-start list-node)) + (end-marker (copy-marker (treesit-node-end list-node))) + (pos (copy-marker (point) t))) + (goto-char (clojure-ts--node-start-skip-metadata list-node)) + (if-let* ((list-parent (treesit-node-parent list-node)) + ((clojure-ts--list-node-sym-match-p list-parent (rx bol "not" eol)))) + (clojure-ts--raise-sexp) + (insert-pair 1 ?\( ?\)) + (insert "not ")) + (indent-region beg end-marker) + ;; `save-excursion' doesn't work well when point is at the opening + ;; paren. + (goto-char pos)) + (user-error "Must be invoked inside a list"))) + (defvar clojure-ts-refactor-map (let ((map (make-sparse-keymap))) (keymap-set map "C-t" #'clojure-ts-thread) @@ -2306,6 +2391,10 @@ before DELIM-OPEN." (keymap-set map "[" #'clojure-ts-convert-collection-to-vector) (keymap-set map "C-#" #'clojure-ts-convert-collection-to-set) (keymap-set map "#" #'clojure-ts-convert-collection-to-set) + (keymap-set map "C-c" #'clojure-ts-cycle-conditional) + (keymap-set map "c" #'clojure-ts-cycle-conditional) + (keymap-set map "C-o" #'clojure-ts-cycle-not) + (keymap-set map "o" #'clojure-ts-cycle-not) (keymap-set map "C-a" #'clojure-ts-add-arity) (keymap-set map "a" #'clojure-ts-add-arity) map) @@ -2322,6 +2411,8 @@ before DELIM-OPEN." ["Toggle between string & keyword" clojure-ts-cycle-keyword-string] ["Align expression" clojure-ts-align] ["Cycle privacy" clojure-ts-cycle-privacy] + ["Cycle conditional" clojure-ts-cycle-conditional] + ["Cycle not" clojure-ts-cycle-not] ["Add function/macro arity" clojure-ts-add-arity] ("Convert collection" ["Convert to list" clojure-ts-convert-collection-to-list] diff --git a/test/clojure-ts-mode-cycling-test.el b/test/clojure-ts-mode-cycling-test.el index b0d83cb..81eef67 100644 --- a/test/clojure-ts-mode-cycling-test.el +++ b/test/clojure-ts-mode-cycling-test.el @@ -190,5 +190,83 @@ (clojure-ts-cycle-privacy))) +(describe "clojure-cycle-if" + + (when-refactoring-with-point-it "should cycle inner if" + "(if this + (if |that + (then AAA) + (else BBB)) + (otherwise CCC))" + + "(if this + (if-not |that + (else BBB) + (then AAA)) + (otherwise CCC))" + + (clojure-ts-cycle-conditional)) + + (when-refactoring-with-point-it "should cycle outer if" + "(if-not |this + (if that + (then AAA) + (else BBB)) + (otherwise CCC))" + + "(if |this + (otherwise CCC) + (if that + (then AAA) + (else BBB)))" + + (clojure-ts-cycle-conditional))) + +(describe "clojure-cycle-when" + + (when-refactoring-with-point-it "should cycle inner when" + "(when this + (when |that + (aaa) + (bbb)) + (ccc))" + + "(when this + (when-not |that + (aaa) + (bbb)) + (ccc))" + + (clojure-ts-cycle-conditional)) + + (when-refactoring-with-point-it "should cycle outer when" + "(when-not |this + (when that + (aaa) + (bbb)) + (ccc))" + + "(when |this + (when that + (aaa) + (bbb)) + (ccc))" + + (clojure-ts-cycle-conditional))) + +(describe "clojure-cycle-not" + + (when-refactoring-with-point-it "should add a not when missing" + "(ala bala| portokala)" + "(not (ala bala| portokala))" + + (clojure-ts-cycle-not)) + + (when-refactoring-with-point-it "should remove a not when present" + "(not (ala bala| portokala))" + "(ala bala| portokala)" + + (clojure-ts-cycle-not))) + (provide 'clojure-ts-mode-cycling-test) ;;; clojure-ts-mode-cycling-test.el ends here diff --git a/test/samples/refactoring.clj b/test/samples/refactoring.clj index c7547bf..10f12b5 100644 --- a/test/samples/refactoring.clj +++ b/test/samples/refactoring.clj @@ -134,3 +134,10 @@ ^{:bla "meta"} [arg] body) + +(if ^boolean (= 2 2) + true + false) + +(when-not true + (println "Hello world")) From 547bdb1b254c3aef0b11d862b4682ff121419dd6 Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Thu, 15 May 2025 10:32:12 +0300 Subject: [PATCH 334/379] [Docs] Update the missing features section --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 36aa137..d10ca0c 100644 --- a/README.md +++ b/README.md @@ -522,8 +522,8 @@ and `clojure-mode` (this is very helpful when dealing with `derived-mode-p` chec ### What `clojure-mode` features are currently missing? -As of version 0.4.x, the most obvious missing feature are the various -refactoring commands in `clojure-mode`. +As of version 0.4.x, `clojure-ts-mode` provides almost all `clojure-mode` features. +Currently only a few refactoring commands are missing. ### Does `clojure-ts-mode` work with CIDER? From c2269ea10a9129113a98eb58fc8abd8888a07e94 Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Thu, 15 May 2025 10:32:52 +0300 Subject: [PATCH 335/379] Release 0.4 --- CHANGELOG.md | 2 ++ clojure-ts-mode.el | 8 ++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 059aa14..80a7f02 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## main (unreleased) +## 0.4.0 (2025-05-15) + - [#16](https://github.com/clojure-emacs/clojure-ts-mode/issues/16): Introduce `clojure-ts-align`. - [#11](https://github.com/clojure-emacs/clojure-ts-mode/issues/11): Enable regex syntax highlighting. - [#16](https://github.com/clojure-emacs/clojure-ts-mode/issues/16): Add support for automatic aligning forms. diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index 84aad83..a4263b4 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -7,7 +7,7 @@ ;; Maintainer: Bozhidar Batsov ;; URL: http://github.com/clojure-emacs/clojure-ts-mode ;; Keywords: languages clojure clojurescript lisp -;; Version: 0.4.0-snapshot +;; Version: 0.4.0 ;; Package-Requires: ((emacs "30.1")) ;; This file is not part of GNU Emacs. @@ -74,7 +74,7 @@ :link '(emacs-commentary-link :tag "Commentary" "clojure-mode")) (defconst clojure-ts-mode-version - "0.4.0-snapshot" + "0.4.0" "The current version of `clojure-ts-mode'.") (defcustom clojure-ts-comment-macro-font-lock-body nil @@ -156,7 +156,7 @@ three or more semicolons will be treated as outline headings. If set to This means that `clojure-ts-thread-first-all' and `clojure-ts-thread-last-all' not thread the deepest sexp inside the current sexp." - :package-version '(clojure-ts-mode . "0.4.0") + :package-version '(clojure-ts-mode . "0.4") :safe #'booleanp :type 'boolean) @@ -164,7 +164,7 @@ current sexp." "If nil, `clojure-ts-cycle-privacy' will use (defn- f []). If t, it will use (defn ^:private f [])." - :package-version '(clojure-ts-mode . "0.4.0") + :package-version '(clojure-ts-mode . "0.4") :safe #'booleanp :type 'boolean) From 0cec0aa84ebfb8b7ba84f52eb6f172cb58bbb92b Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Thu, 15 May 2025 12:07:03 +0300 Subject: [PATCH 336/379] Add missing changelog entry --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 80a7f02..e042df9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,7 +20,7 @@ - [#92](https://github.com/clojure-emacs/clojure-ts-mode/pull/92): Add commands to convert between collections types. - [#93](https://github.com/clojure-emacs/clojure-ts-mode/pull/93): Introduce `clojure-ts-add-arity`. - [#94](https://github.com/clojure-emacs/clojure-ts-mode/pull/94): Add indentation rules and `clojure-ts-align` support for namespaced maps. -- Introduce `clojure-ts-cycle-conditional` and `clojure-ts-cycle-not`. +- [#95](https://github.com/clojure-emacs/clojure-ts-mode/pull/95): Introduce `clojure-ts-cycle-conditional` and `clojure-ts-cycle-not`. ## 0.3.0 (2025-04-15) From c5b2d2315ace41a90e158d5ec268b57e633ef9bc Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Fri, 16 May 2025 08:51:10 +0300 Subject: [PATCH 337/379] Make nested bullet points indentation uniform --- CHANGELOG.md | 52 ++++++++++++++++++++++++++-------------------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e042df9..dc65b85 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -68,37 +68,37 @@ ## 0.2.1 (2024-02-14) - [#36]: Rename all derived mode vars to match the package prefix. - - `clojurescript-ts-mode` -> `clojure-ts-clojurescript-mode` - - `clojurec-ts-mode` -> `clojure-ts-clojurec-mode` - - `clojure-dart-ts-mode` -> `clojure-ts-clojuredart-mode` - - `clojure-jank-ts-mode` -> `clojure-ts-jank-mode` + - `clojurescript-ts-mode` -> `clojure-ts-clojurescript-mode` + - `clojurec-ts-mode` -> `clojure-ts-clojurec-mode` + - `clojure-dart-ts-mode` -> `clojure-ts-clojuredart-mode` + - `clojure-jank-ts-mode` -> `clojure-ts-jank-mode` - [#30]: Add custom option `clojure-ts-toplevel-inside-comment-form` as an equivalent to `clojure-toplevel-inside-comment-form` in `clojure-mode`. - [#32]: Change behavior of `beginning-of-defun` and `end-of-defun` to consider all Clojure sexps as defuns. ## 0.2.0 - Pin grammar revision in treesit-language-source-alist - - [bd61a7fb281b7b0b1d2e20d19ab5d46cbcdc6c1e](https://github.com/clojure-emacs/clojure-ts-mode/commit/bd61a7fb281b7b0b1d2e20d19ab5d46cbcdc6c1e) -- Make font lock feature list more conforming with recommendations - - (See treesit-font-lock-level documentation for more information.) - - [2225190ee57ef667d69f2cd740e0137810bc38e7](https://github.com/clojure-emacs/clojure-ts-mode/commit/2225190ee57ef667d69f2cd740e0137810bc38e7) -- Highlight docstrings in interface, protocol, and variable definitions - - [9af0a6b35c708309acdfeb4c0c79061b0fd4eb44](https://github.com/clojure-emacs/clojure-ts-mode/commit/9af0a6b35c708309acdfeb4c0c79061b0fd4eb44) -- Add support for semantic indentation (now the default) - - [ae2e2486010554cfeb12f06a1485b4d81609d964](https://github.com/clojure-emacs/clojure-ts-mode/commit/ae2e2486010554cfeb12f06a1485b4d81609d964) - - [ca3914aa7aa9645ab244658f8db781cc6f95111e](https://github.com/clojure-emacs/clojure-ts-mode/commit/ca3914aa7aa9645ab244658f8db781cc6f95111e) - - [85871fdbc831b3129dae5762e9c247d453c35e15](https://github.com/clojure-emacs/clojure-ts-mode/commit/85871fdbc831b3129dae5762e9c247d453c35e15) - - [ff5d7e13dc53cc5da0e8139b04e02d90f61d9065](https://github.com/clojure-emacs/clojure-ts-mode/commit/ff5d7e13dc53cc5da0e8139b04e02d90f61d9065) + - [bd61a7fb281b7b0b1d2e20d19ab5d46cbcdc6c1e](https://github.com/clojure-emacs/clojure-ts-mode/commit/bd61a7fb281b7b0b1d2e20d19ab5d46cbcdc6c1e) +Make font lock feature list more conforming with recommendations + - (See treesit-font-lock-level documentation for more information.) + - [2225190ee57ef667d69f2cd740e0137810bc38e7](https://github.com/clojure-emacs/clojure-ts-mode/commit/2225190ee57ef667d69f2cd740e0137810bc38e7) +Highlight docstrings in interface, protocol, and variable definitions + - [9af0a6b35c708309acdfeb4c0c79061b0fd4eb44](https://github.com/clojure-emacs/clojure-ts-mode/commit/9af0a6b35c708309acdfeb4c0c79061b0fd4eb44) +Add support for semantic indentation (now the default) + - [ae2e2486010554cfeb12f06a1485b4d81609d964](https://github.com/clojure-emacs/clojure-ts-mode/commit/ae2e2486010554cfeb12f06a1485b4d81609d964) + - [ca3914aa7aa9645ab244658f8db781cc6f95111e](https://github.com/clojure-emacs/clojure-ts-mode/commit/ca3914aa7aa9645ab244658f8db781cc6f95111e) + - [85871fdbc831b3129dae5762e9c247d453c35e15](https://github.com/clojure-emacs/clojure-ts-mode/commit/85871fdbc831b3129dae5762e9c247d453c35e15) + - [ff5d7e13dc53cc5da0e8139b04e02d90f61d9065](https://github.com/clojure-emacs/clojure-ts-mode/commit/ff5d7e13dc53cc5da0e8139b04e02d90f61d9065) - Highlight "\`quoted-symbols\` in docs strings like this." - - This feature uses a nested markdown parser. + - This feature uses a nested markdown parser. If the parser is not available this feature should be silently disabled. - [9af0a6b35c708309acdfeb4c0c79061b0fd4eb44](https://github.com/clojure-emacs/clojure-ts-mode/commit/9af0a6b35c708309acdfeb4c0c79061b0fd4eb44) - Highlight methods for `deftype`, `defrecord`, `defprotocol`, `reify` and `definterface` forms ([#20](https://github.com/clojure-emacs/clojure-ts-mode/issues/20)). - - [5231c348e509cff91edd1ec59d7a59645395da15](https://github.com/clojure-emacs/clojure-ts-mode/commit/5231c348e509cff91edd1ec59d7a59645395da15) - - Thank you rrudakov for this contribution. + - [5231c348e509cff91edd1ec59d7a59645395da15](https://github.com/clojure-emacs/clojure-ts-mode/commit/5231c348e509cff91edd1ec59d7a59645395da15) + - Thank you rrudakov for this contribution. - Add derived `clojure-jank-ts-mode` for the [Jank](https://github.com/jank-lang/jank) dialect of clojure - - [a7b9654488693cdc9057a91410f74de42a397d1b](https://github.com/clojure-emacs/clojure-ts-mode/commit/a7b9654488693cdc9057a91410f74de42a397d1b) + - [a7b9654488693cdc9057a91410f74de42a397d1b](https://github.com/clojure-emacs/clojure-ts-mode/commit/a7b9654488693cdc9057a91410f74de42a397d1b) ## 0.1.5 @@ -112,18 +112,18 @@ ## 0.1.3 - Add custom option for highlighting comment macro body forms as comments. [ae3790adc0fc40ad905b8c30b152122991592a4e](https://github.com/clojure-emacs/clojure-ts-mode/commit/ae3790adc0fc40ad905b8c30b152122991592a4e) - - Defaults to OFF, highlighting comment body forms like any other expressions. - - Additionally, does a better job of better detecting comment macros by reducing false positives from forms like (not.clojure.core/comment) + - Defaults to OFF, highlighting comment body forms like any other expressions. + - Additionally, does a better job of better detecting comment macros by reducing false positives from forms like (not.clojure.core/comment) ## 0.1.2 - Add a syntax table from clojure-mode. [712dc772fd38111c1e35fe60e4dbe7ac83032bd6](https://github.com/clojure-emacs/clojure-ts-mode/commit/712dc772fd38111c1e35fe60e4dbe7ac83032bd6). - - Better support for `thing-at-point` driven functionality. - - Thank you @jasonjckn for this contribution. + - Better support for `thing-at-point` driven functionality. + - Thank you @jasonjckn for this contribution. - Add 3 derived major modes [4dc853df16ba09d10dc3a648865e681679c17606](https://github.com/clojure-emacs/clojure-ts-mode/commit/4dc853df16ba09d10dc3a648865e681679c17606) - - clojurescript-ts-mode - - clojurec-ts-mode - - clojure-dart-ts-mode + - clojurescript-ts-mode + - clojurec-ts-mode + - clojure-dart-ts-mode ## 0.1.1 From 8ef3c7a779ed5d33b2fb6957d1b0de391eaed084 Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Fri, 16 May 2025 08:55:46 +0300 Subject: [PATCH 338/379] Add a note about Emacs 30 --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index d10ca0c..deadcaf 100644 --- a/README.md +++ b/README.md @@ -550,6 +550,14 @@ Check out [this article](https://metaredux.com/posts/2024/02/19/cider-preliminar Currently, there is an [open PR](https://github.com/clojure-emacs/inf-clojure/pull/215) adding support for inf-clojure. +### Why does `clojure-ts-mode` require Emacs 30? + +You might be wondering why does `clojure-ts-mode` require Emacs 30 instead of +Emacs 29, which introduced the built-in Tree-sitter support. The answer is +simple - the initial Tree-sitter support in Emacs 29 had quite a few issues and +we felt it's better to nudge most people interested in using it to Emacs 30, +which fixed a lot of the problems. + ## License Copyright © 2022-2025 Danny Freeman, Bozhidar Batsov and [contributors][]. From db7054b2e2b18369280480d28cb10c17dd7a23a3 Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Fri, 16 May 2025 10:12:56 +0300 Subject: [PATCH 339/379] Use setopt consistently --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index deadcaf..860a329 100644 --- a/README.md +++ b/README.md @@ -179,7 +179,7 @@ interactively change this behavior. Set the var `clojure-ts-indent-style` to change it. ``` emacs-lisp -(setq clojure-ts-indent-style 'fixed) +(setopt clojure-ts-indent-style 'fixed) ``` > [!TIP] @@ -286,7 +286,7 @@ Forms that can be aligned vertically are configured via the following variables: To highlight entire rich `comment` expression with the comment font face, set ``` emacs-lisp -(setq clojure-ts-comment-macro-font-lock-body t) +(setopt clojure-ts-comment-macro-font-lock-body t) ``` By default this is `nil`, so that anything within a `comment` expression is @@ -332,7 +332,7 @@ Example of regex syntax highlighting: To make forms inside of `(comment ...)` forms appear as top-level forms for evaluation and navigation, set ``` emacs-lisp -(setq clojure-ts-toplevel-inside-comment-form t) +(setopt clojure-ts-toplevel-inside-comment-form t) ``` ### Fill paragraph From 3522b57877b2b9397696ab2e97062a8f25b12e3e Mon Sep 17 00:00:00 2001 From: Roman Rudakov Date: Fri, 16 May 2025 16:07:26 +0200 Subject: [PATCH 340/379] Improve support of extend-protocol forms --- CHANGELOG.md | 4 ++++ README.md | 2 +- clojure-ts-mode.el | 19 +++++++++++++++++-- test/clojure-ts-mode-font-lock-test.el | 8 +++++++- ...clojure-ts-mode-refactor-add-arity-test.el | 14 ++++++++++++++ test/samples/refactoring.clj | 6 ++++++ 6 files changed, 49 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index dc65b85..4022bcb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## main (unreleased) +- [#96](https://github.com/clojure-emacs/clojure-ts-mode/pull/96): Highlight function name properly in `extend-protocol` form. +- [#96](https://github.com/clojure-emacs/clojure-ts-mode/pull/96): Add support for extend-protocol forms to `clojure-ts-add-arity` refactoring + command. + ## 0.4.0 (2025-05-15) - [#16](https://github.com/clojure-emacs/clojure-ts-mode/issues/16): Introduce `clojure-ts-align`. diff --git a/README.md b/README.md index 860a329..8f73908 100644 --- a/README.md +++ b/README.md @@ -448,7 +448,7 @@ set. The following commands are available: `clojure-ts-add-arity`: Add a new arity to an existing single-arity or multi-arity function or macro. Function can be defined using `defn`, `fn` or `defmethod` form. This command also supports functions defined inside forms like -`letfn`, `defprotol`, `reify` or `proxy`. +`letfn`, `defprotol`, `reify`, `extend-protocol` or `proxy`. ### Default keybindings diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index a4263b4..8b6bca7 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -602,7 +602,12 @@ literals with regex grammar." (sym_lit name: (sym_name) @font-lock-function-name-face)))) ((list_lit ((sym_lit name: (sym_name) @def) - ((:equal "reify" @def))) + ((:match ,(rx-to-string + `(seq bol + (or "reify" + "extend-protocol") + eol)) + @def))) (list_lit (sym_lit name: (sym_name) @font-lock-function-name-face)))) ;; letfn @@ -2186,6 +2191,12 @@ type, etc. See `treesit-thing-settings' for more details." (and (clojure-ts--list-node-p node) (string= (clojure-ts--list-node-sym-text parent) "reify")))) +(defun clojure-ts--extend-protocol-defn-p (node) + "Return non-nil if NODE is a function definition in an extend-protocol form." + (when-let* ((parent (treesit-node-parent node))) + (and (clojure-ts--list-node-p node) + (string= (clojure-ts--list-node-sym-text parent) "extend-protocol")))) + (defun clojure-ts-add-arity () "Add an arity to a function or macro." (interactive) @@ -2196,6 +2207,7 @@ type, etc. See `treesit-thing-settings' for more details." "defmacro" "defmethod" "defprotocol" + "extend-protocol" "reify" "proxy") eol)) @@ -2210,13 +2222,16 @@ type, etc. See `treesit-thing-settings' for more details." (clojure-ts--parent-until #'clojure-ts--defprotocol-defn-p)) ((string= parent-def-sym "reify") (clojure-ts--parent-until #'clojure-ts--reify-defn-p)) + ((string= parent-def-sym "extend-protocol") + (clojure-ts--parent-until #'clojure-ts--extend-protocol-defn-p)) (t parent-def-node)))) (let ((beg-marker (copy-marker (treesit-node-start parent-def-node))) (end-marker (copy-marker (treesit-node-end parent-def-node)))) (cond ((string= parent-def-sym "defprotocol") (clojure-ts--add-arity-defprotocol-internal fn-node)) - ((string= parent-def-sym "reify") + ((or (string= parent-def-sym "reify") + (string= parent-def-sym "extend-protocol")) (clojure-ts--add-arity-reify-internal fn-node)) (t (clojure-ts--add-arity-internal fn-node))) (indent-region beg-marker end-marker)) diff --git a/test/clojure-ts-mode-font-lock-test.el b/test/clojure-ts-mode-font-lock-test.el index 8611211..1fa9ed1 100644 --- a/test/clojure-ts-mode-font-lock-test.el +++ b/test/clojure-ts-mode-font-lock-test.el @@ -223,4 +223,10 @@ DESCRIPTION is the description of the spec." (2 12 font-lock-keyword-face) (14 14 font-lock-type-face) (19 21 font-lock-function-name-face) - (34 39 font-lock-function-name-face)))) + (34 39 font-lock-function-name-face)) + + ("(extend-protocol prepare/SettableParameter + clojure.lang.IPersistentMap + (set-parameter [m ^PreparedStatement s i] + (.setObject s i (->pgobject m))))" + (81 93 font-lock-function-name-face)))) diff --git a/test/clojure-ts-mode-refactor-add-arity-test.el b/test/clojure-ts-mode-refactor-add-arity-test.el index 9c31f27..f119607 100644 --- a/test/clojure-ts-mode-refactor-add-arity-test.el +++ b/test/clojure-ts-mode-refactor-add-arity-test.el @@ -324,6 +324,20 @@ (clojure-ts-add-arity)) + (when-refactoring-with-point-it "should handle an extend-protocol" + "(extend-protocol prepare/SettableParameter + clojure.lang.IPersistentMap + (set-parameter [m ^PreparedStatement s i] + (.setObject| s i (->pgobject m))))" + + "(extend-protocol prepare/SettableParameter + clojure.lang.IPersistentMap + (set-parameter [|]) + (set-parameter [m ^PreparedStatement s i] + (.setObject s i (->pgobject m))))" + + (clojure-ts-add-arity)) + (it "should signal a user error when point is not inside a function body" (with-clojure-ts-buffer-point " (letf|n [(foo diff --git a/test/samples/refactoring.clj b/test/samples/refactoring.clj index 10f12b5..5a87bf7 100644 --- a/test/samples/refactoring.clj +++ b/test/samples/refactoring.clj @@ -141,3 +141,9 @@ (when-not true (println "Hello world")) + +(extend-protocol prepare/SettableParameter + clojure.lang.IPersistentMap + (set-parameter []) + (set-parameter [m ^PreparedStatement s i] + (.setObject| s i (->pgobject m)))) From 457f33f8cd047efa7b844a57d52a4c71e6ecf4c8 Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Tue, 27 May 2025 10:24:20 +0300 Subject: [PATCH 341/379] Add the version to the mode's menu --- clojure-ts-mode.el | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index 8b6bca7..4380c92 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -2441,7 +2441,8 @@ parenthesis." ["Fully thread a form with ->>" clojure-ts-thread-last-all] "--" ["Unwind once" clojure-ts-unwind] - ["Fully unwind a threading macro" clojure-ts-unwind-all]))) + ["Fully unwind a threading macro" clojure-ts-unwind-all]) + ["Version" clojure-mode-display-version])) map) "Keymap for `clojure-ts-mode'.") From b7f99500fb7d884f87ce849620dcf18cee507c0e Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Tue, 27 May 2025 10:25:01 +0300 Subject: [PATCH 342/379] Bump the dev version --- clojure-ts-mode.el | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index 4380c92..a3e117c 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -7,7 +7,7 @@ ;; Maintainer: Bozhidar Batsov ;; URL: http://github.com/clojure-emacs/clojure-ts-mode ;; Keywords: languages clojure clojurescript lisp -;; Version: 0.4.0 +;; Version: 0.5.0-snapshot ;; Package-Requires: ((emacs "30.1")) ;; This file is not part of GNU Emacs. @@ -74,7 +74,7 @@ :link '(emacs-commentary-link :tag "Commentary" "clojure-mode")) (defconst clojure-ts-mode-version - "0.4.0" + "0.5.0-snapshot" "The current version of `clojure-ts-mode'.") (defcustom clojure-ts-comment-macro-font-lock-body nil From 456a7cafd0adb2f8122255d6fb01562989c246a6 Mon Sep 17 00:00:00 2001 From: Roman Rudakov Date: Mon, 26 May 2025 20:31:13 +0200 Subject: [PATCH 343/379] Switch to the experimental Clojure grammar --- README.md | 22 ++- clojure-ts-mode.el | 344 ++++++++++++++++------------------- test/samples/indentation.clj | 14 +- test/samples/navigation.clj | 14 ++ 4 files changed, 200 insertions(+), 194 deletions(-) create mode 100644 test/samples/navigation.clj diff --git a/README.md b/README.md index 8f73908..7bc21af 100644 --- a/README.md +++ b/README.md @@ -123,11 +123,15 @@ Once installed, evaluate `clojure-ts-mode.el` and you should be ready to go. > `clojure-ts-mode` install the required grammars automatically, so for most > people no manual actions will be required. -`clojure-ts-mode` makes use of two Tree-sitter grammars to work properly: +`clojure-ts-mode` makes use of the following Tree-sitter grammars: -- The Clojure grammar, mentioned earlier -- [markdown-inline](https://github.com/MDeiml/tree-sitter-markdown), which -will be used for docstrings if available and if `clojure-ts-use-markdown-inline` is enabled. +- The [experimental](https://github.com/sogaiu/tree-sitter-clojure/tree/unstable-20250526) version Clojure grammar. This version includes a few + improvements, which potentially will be promoted to a stable release (See [the + discussion](https://github.com/sogaiu/tree-sitter-clojure/issues/65)). This grammar is required for proper work of `clojure-ts-mode`. +- [markdown-inline](https://github.com/MDeiml/tree-sitter-markdown), which will be used for docstrings if available and if + `clojure-ts-use-markdown-inline` is enabled. +- [tree-sitter-regex](https://github.com/tree-sitter/tree-sitter-regex/releases/tag/v0.24.3), which will be used for regex literals if available and if + `clojure-ts-use-regex-parser` is not `nil`. If you have `git` and a C compiler (`cc`) available on your system's `PATH`, `clojure-ts-mode` will install the @@ -136,8 +140,14 @@ set to `t` (the default). If `clojure-ts-mode` fails to automatically install the grammar, you have the option to install it manually, Please, refer to the installation instructions of -each required grammar and make sure you're install the versions expected. (see -`clojure-ts-grammar-recipes` for details) +each required grammar and make sure you're install the versions expected (see +`clojure-ts-grammar-recipes` for details). + +If `clojure-ts-ensure-grammars` is enabled, `clojure-ts-mode` will try to upgrade +the Clojure grammar if it's outdated. This might happen, when you activate +`clojure-ts-mode` for the first time after package update. If grammar was +previously installed, you might need to restart Emacs, because it has to reload +the grammar binary. ### Upgrading Tree-sitter grammars diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index a3e117c..e8a2e50 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -341,7 +341,7 @@ Only intended for use at development time.") "defmulti" "defn" "defn-" "defonce" "defprotocol" "defrecord" "defstruct" "deftype" "delay" "doall" "dorun" "doseq" "dosync" "dotimes" "doto" - "extend-protocol" "extend-type" + "extend-protocol" "extend-type" "extend" "for" "future" "gen-class" "gen-interface" "if-let" "if-not" "if-some" "import" "in-ns""io!" @@ -419,24 +419,25 @@ if a third argument (the value) is provided. (defun clojure-ts--docstring-query (capture-symbol) "Return a query that captures docstrings with CAPTURE-SYMBOL." `(;; Captures docstrings in def - ((list_lit :anchor (meta_lit) :? + ((list_lit :anchor [(comment) (meta_lit) (old_meta_lit)] :* :anchor (sym_lit) @_def_symbol - :anchor (comment) :? - :anchor (sym_lit) ; variable name - :anchor (comment) :? - :anchor (str_lit) ,capture-symbol - :anchor (_)) ; the variable's value + :anchor [(comment) (meta_lit) (old_meta_lit)] :* + ;; Variable name + :anchor (sym_lit) + :anchor [(comment) (meta_lit) (old_meta_lit)] :* + :anchor (str_lit (str_content) ,capture-symbol) @font-lock-doc-face + ;; The variable's value + :anchor (_)) (:match ,(clojure-ts-symbol-regexp clojure-ts-definition-docstring-symbols) @_def_symbol)) ;; Captures docstrings in metadata of definitions - ((list_lit :anchor (sym_lit) @_def_symbol - :anchor (comment) :? - :anchor (sym_lit - (meta_lit - value: (map_lit - (kwd_lit) @_doc-keyword - :anchor - (str_lit) ,capture-symbol)))) + ((list_lit :anchor [(comment) (meta_lit) (old_meta_lit)] :* + :anchor (sym_lit) @_def_symbol + :anchor (comment) :* + :anchor (meta_lit + value: (map_lit + (kwd_lit) @_doc-keyword + :anchor (str_lit (str_content) ,capture-symbol) @font-lock-doc-face))) ;; We're only supporting this on a fixed set of defining symbols ;; Existing regexes don't encompass def and defn ;; Naming another regex is very cumbersome. @@ -448,22 +449,27 @@ if a third argument (the value) is provided. @_def_symbol) (:equal @_doc-keyword ":doc")) ;; Captures docstrings defn, defmacro, ns, and things like that - ((list_lit :anchor (meta_lit) :? + ((list_lit :anchor [(comment) (meta_lit) (old_meta_lit)] :* :anchor (sym_lit) @_def_symbol - :anchor (comment) :? - :anchor (sym_lit) ; function_name - :anchor (comment) :? - :anchor (str_lit) ,capture-symbol) + :anchor [(comment) (meta_lit) (old_meta_lit)] :* + ;; Function_name + :anchor (sym_lit) + :anchor [(comment) (meta_lit) (old_meta_lit)] :* + :anchor (str_lit (str_content) ,capture-symbol) @font-lock-doc-face) (:match ,(clojure-ts-symbol-regexp clojure-ts-function-docstring-symbols) @_def_symbol)) ;; Captures docstrings in defprotcol, definterface - ((list_lit :anchor (sym_lit) @_def_symbol - (list_lit - :anchor (sym_lit) (vec_lit) :* - (str_lit) ,capture-symbol :anchor) + ((list_lit :anchor [(comment) (meta_lit) (old_meta_lit)] :* + :anchor (sym_lit) @_def_symbol + (list_lit :anchor (sym_lit) (vec_lit) :* + (str_lit (str_content) ,capture-symbol) @font-lock-doc-face) :*) (:match ,clojure-ts--interface-def-symbol-regexp @_def_symbol)))) +(defconst clojure-ts--match-docstring-query-compiled + (treesit-query-compile 'clojure (clojure-ts--docstring-query '@font-lock-doc-face)) + "Precompiled query that matches a Clojure docstring.") + (defun clojure-ts--treesit-range-settings (use-markdown-inline use-regex) "Return value for `treesit-range-settings' for `clojure-ts-mode'. @@ -476,16 +482,14 @@ When USE-REGEX is non-nil, include range settings for regex parser." (treesit-range-rules :embed 'markdown-inline :host 'clojure - :offset '(1 . -1) :local t (clojure-ts--docstring-query '@capture))) (when use-regex (treesit-range-rules :embed 'regex :host 'clojure - :offset '(2 . -1) :local t - '((regex_lit) @capture))))) + '((regex_content) @capture))))) (defun clojure-ts--font-lock-settings (markdown-available regex-available) "Return font lock settings suitable for use in `treesit-font-lock-settings'. @@ -531,19 +535,21 @@ literals with regex grammar." ;; `clojure.core'. :feature 'builtin :language 'clojure - `(((list_lit meta: _ :* :anchor (sym_lit !namespace name: (sym_name) @font-lock-keyword-face)) + `(((list_lit :anchor [(comment) (meta_lit) (old_meta_lit)] :* + :anchor (sym_lit !namespace name: (sym_name) @font-lock-keyword-face)) (:match ,clojure-ts--builtin-symbol-regexp @font-lock-keyword-face)) - ((list_lit meta: _ :* :anchor - (sym_lit namespace: ((sym_ns) @ns - (:equal "clojure.core" @ns)) - name: (sym_name) @font-lock-keyword-face)) + ((list_lit :anchor [(comment) (meta_lit) (old_meta_lit)] :* + :anchor (sym_lit namespace: ((sym_ns) @ns + (:equal "clojure.core" @ns)) + name: (sym_name) @font-lock-keyword-face)) (:match ,clojure-ts--builtin-symbol-regexp @font-lock-keyword-face)) - ((anon_fn_lit meta: _ :* :anchor (sym_lit !namespace name: (sym_name) @font-lock-keyword-face)) + ((anon_fn_lit :anchor [(comment) (meta_lit) (old_meta_lit)] :* + :anchor (sym_lit !namespace name: (sym_name) @font-lock-keyword-face)) (:match ,clojure-ts--builtin-symbol-regexp @font-lock-keyword-face)) - ((anon_fn_lit meta: _ :* :anchor - (sym_lit namespace: ((sym_ns) @ns - (:equal "clojure.core" @ns)) - name: (sym_name) @font-lock-keyword-face)) + ((anon_fn_lit :anchor [(comment) (meta_lit) (old_meta_lit)] :* + :anchor (sym_lit namespace: ((sym_ns) @ns + (:equal "clojure.core" @ns)) + name: (sym_name) @font-lock-keyword-face)) (:match ,clojure-ts--builtin-symbol-regexp @font-lock-keyword-face)) ((sym_name) @font-lock-builtin-face (:match ,clojure-ts--builtin-dynamic-var-regexp @font-lock-builtin-face))) @@ -565,8 +571,9 @@ literals with regex grammar." ;; No wonder the tree-sitter-clojure grammar only touches syntax, and not semantics :feature 'definition ;; defn and defn like macros :language 'clojure - `(((list_lit :anchor meta: _ :* + `(((list_lit :anchor [(comment) (meta_lit) (old_meta_lit)] :* :anchor (sym_lit (sym_name) @font-lock-keyword-face) + :anchor [(comment) (meta_lit) (old_meta_lit)] :* :anchor (sym_lit (sym_name) @font-lock-function-name-face)) (:match ,(rx-to-string `(seq bol @@ -579,25 +586,27 @@ literals with regex grammar." "deftest" "deftest-" "defmacro" - "definline") + "definline" + "defonce") eol)) @font-lock-keyword-face)) ((anon_fn_lit marker: "#" @font-lock-property-face)) ;; Methods implementation ((list_lit - ((sym_lit name: (sym_name) @def) - ((:match ,(rx-to-string - `(seq bol - (or - "defrecord" - "definterface" - "deftype" - "defprotocol") - eol)) - @def))) - :anchor - (sym_lit (sym_name) @font-lock-type-face) + :anchor [(comment) (meta_lit) (old_meta_lit)] :* + :anchor ((sym_lit name: (sym_name) @def) + ((:match ,(rx-to-string + `(seq bol + (or + "defrecord" + "definterface" + "deftype" + "defprotocol") + eol)) + @def))) + :anchor [(comment) (meta_lit) (old_meta_lit)] :* + :anchor (sym_lit (sym_name) @font-lock-type-face) (list_lit (sym_lit name: (sym_name) @font-lock-function-name-face)))) ((list_lit @@ -605,7 +614,8 @@ literals with regex grammar." ((:match ,(rx-to-string `(seq bol (or "reify" - "extend-protocol") + "extend-protocol" + "extend-type") eol)) @def))) (list_lit @@ -620,8 +630,9 @@ literals with regex grammar." :feature 'variable ;; def, defonce :language 'clojure - `(((list_lit :anchor meta: _ :* + `(((list_lit :anchor [(comment) (meta_lit) (old_meta_lit)] :* :anchor (sym_lit (sym_name) @font-lock-keyword-face) + :anchor [(comment) (meta_lit) (old_meta_lit)] :* :anchor (sym_lit (sym_name) @font-lock-variable-name-face)) (:match ,clojure-ts--variable-definition-symbol-regexp @font-lock-keyword-face))) @@ -669,7 +680,7 @@ literals with regex grammar." (treesit-font-lock-rules :feature 'doc :language 'markdown-inline - :override t + :override 'prepend `([((image_description) @link) ((link_destination) @font-lock-constant-face) ((code_span) @font-lock-constant-face) @@ -744,6 +755,7 @@ literals with regex grammar." `((comment) @font-lock-comment-face (dis_expr marker: "#_" @font-lock-comment-delimiter-face + meta: (meta_lit) :* @font-lock-comment-face value: _ @font-lock-comment-face) (,(append '(list_lit :anchor (sym_lit) @font-lock-comment-delimiter-face) @@ -788,7 +800,8 @@ literals with regex grammar." (defun clojure-ts--metadata-node-p (node) "Return non-nil if NODE is a Clojure metadata node." - (string-equal "meta_lit" (treesit-node-type node))) + (or (string-equal "meta_lit" (treesit-node-type node)) + (string-equal "old_meta_lit" (treesit-node-type node)))) (defun clojure-ts--var-node-p (node) "Return non-nil if NODE is a var (eg. #\\'foo)." @@ -821,11 +834,11 @@ Skip the optional metadata node at pos 0 if present." n) t))) -(defun clojure-ts--node-with-metadata-parent (node) - "Return parent for NODE only if NODE has metadata, otherwise return nil." - (when-let* ((prev-sibling (treesit-node-prev-sibling node)) - ((clojure-ts--metadata-node-p prev-sibling))) - (treesit-node-parent (treesit-node-parent node)))) +(defun clojure-ts--first-value-child (node) + "Return the first value child of the NODE. + +This will skip metadata and comment nodes." + (treesit-node-child-by-field-name node "value")) (defun clojure-ts--symbol-matches-p (symbol-regexp node) "Return non-nil if NODE is a symbol that matches SYMBOL-REGEXP." @@ -1043,6 +1056,7 @@ The possible values for this variable are ("try" . ((:block 0))) ("with-out-str" . ((:block 0))) ("defprotocol" . ((:block 1) (:inner 1))) + ("definterface" . ((:block 1) (:inner 1))) ("binding" . ((:block 1))) ("case" . ((:block 1))) ("cond->" . ((:block 1))) @@ -1299,31 +1313,31 @@ indentation rule in `clojure-ts--semantic-indent-rules-defaults' or according to the rule. If NODE is nil, use next node after BOL." (and (or (clojure-ts--list-node-p parent) (clojure-ts--anon-fn-node-p parent)) - (let* ((first-child (clojure-ts--node-child-skip-metadata parent 0))) + (let* ((first-child (clojure-ts--first-value-child parent))) (when-let* ((rule (clojure-ts--find-semantic-rule node parent 0))) - (and (not (clojure-ts--match-with-metadata node)) - (let ((rule-type (car rule)) - (rule-value (cadr rule))) - (if (equal rule-type :block) - (if (zerop rule-value) - ;; Special treatment for block 0 rule. - (clojure-ts--match-block-0-body bol first-child) - (clojure-ts--node-pos-match-block node parent bol rule-value)) - ;; Return true for any inner rule. - t))))))) + (let ((rule-type (car rule)) + (rule-value (cadr rule))) + (if (equal rule-type :block) + (if (zerop rule-value) + ;; Special treatment for block 0 rule. + (clojure-ts--match-block-0-body bol first-child) + (clojure-ts--node-pos-match-block node parent bol rule-value)) + ;; Return true for any inner rule. + t)))))) (defun clojure-ts--match-function-call-arg (node parent _bol) "Match NODE if PARENT is a list expressing a function or macro call." (and (or (clojure-ts--list-node-p parent) (clojure-ts--anon-fn-node-p parent)) - ;; Can the following two clauses be replaced by checking indexes? - ;; Does the second child exist, and is it not equal to the current node? - (clojure-ts--node-child-skip-metadata parent 1) - (not (treesit-node-eq (clojure-ts--node-child-skip-metadata parent 1) node)) - (let ((first-child (clojure-ts--node-child-skip-metadata parent 0))) - (or (clojure-ts--symbol-node-p first-child) - (clojure-ts--keyword-node-p first-child) - (clojure-ts--var-node-p first-child))))) + (let ((first-child (clojure-ts--first-value-child parent)) + (second-child (clojure-ts--node-child-skip-metadata parent 1))) + (and first-child + ;; Does the second child exist, and is it not equal to the current node? + second-child + (not (treesit-node-eq second-child node)) + (or (clojure-ts--symbol-node-p first-child) + (clojure-ts--keyword-node-p first-child) + (clojure-ts--var-node-p first-child)))))) (defvar clojure-ts--threading-macro (eval-and-compile @@ -1336,55 +1350,25 @@ according to the rule. If NODE is nil, use next node after BOL." ;; If not, then align function arg. (and (or (clojure-ts--list-node-p parent) (clojure-ts--anon-fn-node-p parent)) - (let ((first-child (treesit-node-child parent 0 t))) + (let ((first-child (clojure-ts--first-value-child parent))) (clojure-ts--symbol-matches-p clojure-ts--threading-macro first-child)))) -(defun clojure-ts--match-fn-docstring (node) - "Match NODE when it is a docstring for PARENT function definition node." - ;; A string that is the third node in a function defn block - (let ((parent (treesit-node-parent node))) - (and (treesit-node-eq node (treesit-node-child parent 2 t)) - (let ((first-auncle (treesit-node-child parent 0 t))) - (clojure-ts--symbol-matches-p - (regexp-opt clojure-ts-function-docstring-symbols) - first-auncle))))) - -(defun clojure-ts--match-def-docstring (node) - "Match NODE when it is a docstring for PARENT variable definition node." - ;; A string that is the fourth node in a variable definition block. - (let ((parent (treesit-node-parent node))) - (and (treesit-node-eq node (treesit-node-child parent 2 t)) - ;; There needs to be a value after the string. - ;; If there is no 4th child, then this string is the value. - (treesit-node-child parent 3 t) - (let ((first-auncle (treesit-node-child parent 0 t))) - (clojure-ts--symbol-matches-p - (regexp-opt clojure-ts-definition-docstring-symbols) - first-auncle))))) - -(defun clojure-ts--match-method-docstring (node) - "Match NODE when it is a docstring in a method definition." - (let* ((grandparent (treesit-node-parent ;; the protocol/interface - (treesit-node-parent node))) ;; the method definition - (first-grandauncle (treesit-node-child grandparent 0 t))) - (clojure-ts--symbol-matches-p - clojure-ts--interface-def-symbol-regexp - first-grandauncle))) - (defun clojure-ts--match-docstring (_node parent _bol) "Match PARENT when it is a docstring node." - (and (clojure-ts--string-node-p parent) ;; We are IN a string - (or (clojure-ts--match-def-docstring parent) - (clojure-ts--match-fn-docstring parent) - (clojure-ts--match-method-docstring parent)))) + (when-let* ((top-level-node (treesit-parent-until parent 'defun t)) + (result (treesit-query-capture top-level-node + clojure-ts--match-docstring-query-compiled))) + (seq-find (lambda (elt) + (and (eq (car elt) 'font-lock-doc-face) + (treesit-node-eq (cdr elt) parent))) + result))) (defun clojure-ts--match-with-metadata (node &optional _parent _bol) "Match NODE when it has metadata." - (let ((prev-sibling (treesit-node-prev-sibling node))) - (and prev-sibling - (clojure-ts--metadata-node-p prev-sibling)))) + (when-let* ((prev-sibling (treesit-node-prev-sibling node))) + (clojure-ts--metadata-node-p prev-sibling))) (defun clojure-ts--anchor-parent-opening-paren (_node parent _bol) "Return position of PARENT start for NODE. @@ -1398,21 +1382,10 @@ for forms with type hints." (treesit-search-subtree #'clojure-ts--opening-paren-node-p nil t 1) (treesit-node-start))) -(defun clojure-ts--match-collection-item-with-metadata (node-type) - "Return a matcher for a collection item with metadata by NODE-TYPE. - -The returned matcher accepts NODE, PARENT and BOL and returns true only -if NODE has metadata and its parent has type NODE-TYPE." - (lambda (node _parent _bol) - (string-equal node-type - (treesit-node-type - (clojure-ts--node-with-metadata-parent node))))) - (defun clojure-ts--anchor-nth-sibling (n) "Return the start of the Nth child of PARENT skipping metadata." (lambda (_n parent &rest _) - (treesit-node-start - (clojure-ts--node-child-skip-metadata parent n)))) + (treesit-node-start (treesit-node-child parent n t)))) (defun clojure-ts--semantic-indent-rules () "Return a list of indentation rules for `treesit-simple-indent-rules'. @@ -1425,19 +1398,6 @@ used." `((clojure ((parent-is "^source$") parent-bol 0) (clojure-ts--match-docstring parent 0) - ;; Collections items with metadata. - ;; - ;; This should be before `clojure-ts--match-with-metadata', otherwise they - ;; will never be matched. - (,(clojure-ts--match-collection-item-with-metadata "^vec_lit$") grand-parent 1) - (,(clojure-ts--match-collection-item-with-metadata "^map_lit$") grand-parent 1) - (,(clojure-ts--match-collection-item-with-metadata "^set_lit$") grand-parent 2) - ;; - ;; If we enable this rule for lists, it will break many things. - ;; (,(clojure-ts--match-collection-item-with-metadata "list_lit") grand-parent 1) - ;; - ;; All other forms with metadata. - (clojure-ts--match-with-metadata parent 0) ;; Literal Sequences ((parent-is "^vec_lit$") parent 1) ;; https://guide.clojure.style/#bindings-alignment ((parent-is "^map_lit$") parent 1) ;; https://guide.clojure.style/#map-keys-alignment @@ -1453,7 +1413,9 @@ used." ;; https://guide.clojure.style/#vertically-align-fn-args (clojure-ts--match-function-call-arg ,(clojure-ts--anchor-nth-sibling 1) 0) ;; https://guide.clojure.style/#one-space-indent - ((parent-is "^list_lit$") parent 1)))) + ((parent-is "^list_lit$") parent 1) + ((parent-is "^anon_fn_lit$") parent 2) + (clojure-ts--match-with-metadata parent 0)))) (defun clojure-ts--configured-indent-rules () "Gets the configured choice of indent rules." @@ -1496,7 +1458,7 @@ of the first symbol of a functional literal NODE." (when (or (clojure-ts--list-node-p node) (and include-anon-fn-lit (clojure-ts--anon-fn-node-p node))) - (when-let* ((first-child (clojure-ts--node-child-skip-metadata node 0)) + (when-let* ((first-child (clojure-ts--first-value-child node)) ((clojure-ts--symbol-node-p first-child))) (clojure-ts--named-node-text first-child)))) @@ -1545,10 +1507,19 @@ function literal." "code_span") "Nodes representing s-expressions in the `markdown-inline' parser.") +(defun clojure-ts--default-sexp-node-p (node) + "Return TRUE if point is after the # marker of set or function literal NODE." + (and (eq (char-before (point)) ?\#) + (string-match-p (rx bol (or "anon_fn_lit" "set_lit") eol) + (treesit-node-type (treesit-node-parent node))))) + (defconst clojure-ts--thing-settings `((clojure (sexp ,(regexp-opt clojure-ts--sexp-nodes)) (list ,(regexp-opt clojure-ts--list-nodes)) + (sexp-default + ;; For `C-M-f' in "#|(a)" or "#|{1 2 3}" + (,(rx (or "(" "{")) . ,#'clojure-ts--default-sexp-node-p)) (text ,(regexp-opt '("comment"))) (defun ,#'clojure-ts--defun-node-p)) (when clojure-ts-use-markdown-inline @@ -1598,10 +1569,7 @@ BOUND bounds the whitespace search." (point)) (when-let* ((cur-sexp (treesit-node-first-child-for-pos root-node (point) t))) (goto-char (treesit-node-start cur-sexp)) - (if (and (string= "sym_lit" (treesit-node-type cur-sexp)) - (clojure-ts--metadata-node-p (treesit-node-child cur-sexp 0 t)) - (and (not (treesit-node-child-by-field-name cur-sexp "value")) - (string-empty-p (clojure-ts--named-node-text cur-sexp)))) + (if (clojure-ts--metadata-node-p cur-sexp) (treesit-end-of-thing 'sexp 2 'restricted) (treesit-end-of-thing 'sexp 1 'restricted)) (when (looking-at-p ",") @@ -1913,6 +1881,7 @@ parenthesis." (delete-region beg (point)) ;; `raise-sexp' doesn't work properly for function literals (it loses one ;; of the parenthesis). Seems like an Emacs' bug. + (backward-up-list) (delete-pair)))) (defun clojure-ts--fix-sexp-whitespace () @@ -1952,19 +1921,25 @@ With universal argument \\[universal-argument], fully unwinds thread." (end (thread-first threading-sexp (treesit-node-end) (copy-marker)))) - (while (> n 0) - (cond - ((string-match-p (rx bol (* "some") "->" eol) sym) - (clojure-ts--unwind-thread-first)) - ((string-match-p (rx bol (* "some") "->>" eol) sym) - (clojure-ts--unwind-thread-last))) - (setq n (1- n)) - ;; After unwinding we check if it is the last expression and maybe - ;; splice it. - (when (clojure-ts--nothing-more-to-unwind) - (clojure-ts--pop-out-of-threading) - (clojure-ts--fix-sexp-whitespace) - (setq n 0))) + ;; If it's the last expression, just raise it out of the threading + ;; macro. + (if (clojure-ts--nothing-more-to-unwind) + (progn + (clojure-ts--pop-out-of-threading) + (clojure-ts--fix-sexp-whitespace)) + (while (> n 0) + (cond + ((string-match-p (rx bol (* "some") "->" eol) sym) + (clojure-ts--unwind-thread-first)) + ((string-match-p (rx bol (* "some") "->>" eol) sym) + (clojure-ts--unwind-thread-last))) + (setq n (1- n)) + ;; After unwinding we check if it is the last expression and maybe + ;; splice it. + (when (clojure-ts--nothing-more-to-unwind) + (clojure-ts--pop-out-of-threading) + (clojure-ts--fix-sexp-whitespace) + (setq n 0)))) (indent-region beg end) (delete-trailing-whitespace beg end))) (user-error "No threading form to unwind at point"))) @@ -2117,9 +2092,9 @@ type, etc. See `treesit-thing-settings' for more details." (defun clojure-ts--add-arity-internal (fn-node) "Add an arity to a function defined by FN-NODE." (let* ((first-coll (clojure-ts--node-child fn-node (rx bol (or "vec_lit" "list_lit") eol))) - (coll-start (clojure-ts--node-start-skip-metadata first-coll)) + (coll-start (treesit-node-start first-coll)) (line-parent (thread-first fn-node - (clojure-ts--node-child-skip-metadata 0) + (clojure-ts--first-value-child) (treesit-node-start) (line-number-at-pos))) (line-args (line-number-at-pos coll-start)) @@ -2138,7 +2113,7 @@ type, etc. See `treesit-thing-settings' for more details." (defun clojure-ts--add-arity-defprotocol-internal (fn-node) "Add an arity to a defprotocol function defined by FN-NODE." (let* ((args-vec (clojure-ts--node-child fn-node (rx bol "vec_lit" eol))) - (args-vec-start (clojure-ts--node-start-skip-metadata args-vec)) + (args-vec-start (treesit-node-start args-vec)) (line-parent (thread-first fn-node (clojure-ts--node-child-skip-metadata 0) (treesit-node-start) @@ -2158,7 +2133,7 @@ type, etc. See `treesit-thing-settings' for more details." (defun clojure-ts--add-arity-reify-internal (fn-node) "Add an arity to a reify function defined by FN-NODE." (let* ((fn-name (clojure-ts--list-node-sym-text fn-node))) - (goto-char (clojure-ts--node-start-skip-metadata fn-node)) + (goto-char (treesit-node-start fn-node)) (insert "(" fn-name " [])") (newline-and-indent) ;; Put the point between sqare brackets. @@ -2340,7 +2315,7 @@ before DELIM-OPEN." ("when" "when-not") ("when-not" "when")))) (save-excursion - (goto-char (clojure-ts--node-start-skip-metadata cond-node)) + (goto-char (treesit-node-start cond-node)) (down-list 1) (delete-char (length cond-sym)) (insert new-sym) @@ -2350,25 +2325,10 @@ before DELIM-OPEN." (indent-region beg end-marker))) (user-error "No conditional expression found"))) -(defun clojure-ts--point-outside-node-p (node) - "Return non-nil if point is outside of the actual NODE start. - -Clojure grammar treats metadata as part of an expression, so for example -^boolean (not (= 2 2)) is a single list node, including metadata. This -causes issues for functions that navigate by s-expressions and lists. -This function returns non-nil if point is outside of the outermost -parenthesis." - (let* ((actual-node-start (clojure-ts--node-start-skip-metadata node)) - (node-end (treesit-node-end node)) - (pos (point))) - (or (< pos actual-node-start) - (> pos node-end)))) - (defun clojure-ts-cycle-not () "Add or remove a not form around the current form." (interactive) - (if-let* ((list-node (clojure-ts--parent-until (rx bol "list_lit" eol))) - ((not (clojure-ts--point-outside-node-p list-node)))) + (if-let* ((list-node (clojure-ts--parent-until (rx bol "list_lit" eol)))) (let ((beg (treesit-node-start list-node)) (end-marker (copy-marker (treesit-node-end list-node))) (pos (copy-marker (point) t))) @@ -2476,7 +2436,7 @@ parenthesis." (defconst clojure-ts-grammar-recipes '((clojure "https://github.com/sogaiu/tree-sitter-clojure.git" - "v0.0.13") + "unstable-20250526") (markdown-inline "https://github.com/MDeiml/tree-sitter-markdown" "v0.4.1" "tree-sitter-markdown-inline/src") @@ -2484,12 +2444,20 @@ parenthesis." "v0.24.3")) "Intended to be used as the value for `treesit-language-source-alist'.") +(defun clojure-ts--grammar-outdated-p () + "Return TRUE if currently installed grammar is outdated." + (treesit-query-valid-p 'clojure '((sym_lit (meta_lit))))) + (defun clojure-ts--ensure-grammars () "Install required language grammars if not already available." (when clojure-ts-ensure-grammars (dolist (recipe clojure-ts-grammar-recipes) (let ((grammar (car recipe))) - (unless (treesit-language-available-p grammar nil) + (when (or (not (treesit-language-available-p grammar nil)) + ;; If Clojure grammar is available, but outdated, re-install + ;; it. + (and (equal grammar 'clojure) + (clojure-ts--grammar-outdated-p))) (message "Installing %s Tree-sitter grammar" grammar) ;; `treesit-language-source-alist' is dynamically scoped. ;; Binding it in this let expression allows @@ -2687,11 +2655,15 @@ Useful if you want to switch to the `clojure-mode's mode mappings." (treesit-query-compile 'clojure '(((source (list_lit + :anchor [(comment) (meta_lit) (old_meta_lit)] :* :anchor (sym_lit name: (sym_name) @ns) + :anchor [(comment) (meta_lit) (old_meta_lit)] :* :anchor (sym_lit name: (sym_name) @ns-name))) (:equal @ns "ns")) ((source (list_lit + :anchor [(comment) (meta_lit) (old_meta_lit)] :* :anchor (sym_lit name: (sym_name) @in-ns) + :anchor [(comment) (meta_lit) (old_meta_lit)] :* :anchor (quoting_lit :anchor (sym_lit name: (sym_name) @ns-name)))) (:equal @in-ns "in-ns"))))) diff --git a/test/samples/indentation.clj b/test/samples/indentation.clj index 132a5f2..7d30162 100644 --- a/test/samples/indentation.clj +++ b/test/samples/indentation.clj @@ -228,15 +228,25 @@ :foo "bar"} -;; NOTE: List elements with metadata are not indented correctly. +;; NOTE: It works well now with the alternative grammar. '(one two ^:foo - three) + three) ^{:nextjournal.clerk/visibility {:code :hide}} (defn actual [args]) +(println "Hello" + "World") + +#(println + "hello" + %) + +#(println "hello" + %) + (def ^:private hello "World") diff --git a/test/samples/navigation.clj b/test/samples/navigation.clj new file mode 100644 index 0000000..26bdf44 --- /dev/null +++ b/test/samples/navigation.clj @@ -0,0 +1,14 @@ +(ns navigation) + +(let [my-var ^{:foo "bar"} (= "Hello" "Hello")]) + +(let [my-var ^boolean (= "Hello" "world")]) + +#(+ % %) + +^boolean (= 2 2) + +(defn- to-string + ^String + [arg] + (.toString arg)) From 65c2fd7457663b1c21d2def3ac06034b3f573945 Mon Sep 17 00:00:00 2001 From: Roman Rudakov Date: Mon, 26 May 2025 20:33:42 +0200 Subject: [PATCH 344/379] Slightly speed-up clojure-ts-align and fix a minor issue Before we were trying to align multiple nodes starting from the top-level node and moving to the most deeply nested node. This could produce misaligned forms if nested nodes have extra spaces that has to be cleaned up. Now we start from the most deeply nested node and gradually move to the top of the tree. --- clojure-ts-mode.el | 64 ++++++++++++++++++++++++++++------------------ 1 file changed, 39 insertions(+), 25 deletions(-) diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index e8a2e50..ba6380d 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -1570,7 +1570,10 @@ BOUND bounds the whitespace search." (when-let* ((cur-sexp (treesit-node-first-child-for-pos root-node (point) t))) (goto-char (treesit-node-start cur-sexp)) (if (clojure-ts--metadata-node-p cur-sexp) - (treesit-end-of-thing 'sexp 2 'restricted) + (progn + (treesit-end-of-thing 'sexp 1 'restricted) + (just-one-space) + (treesit-end-of-thing 'sexp 1 'restricted)) (treesit-end-of-thing 'sexp 1 'restricted)) (when (looking-at-p ",") (forward-char)) @@ -1603,6 +1606,33 @@ BOUND bounds the whitespace search." sexp-end t))) +(defvar clojure-ts--align-query + (treesit-query-compile 'clojure + (append + `(((map_lit) @map) + ((ns_map_lit) @ns-map) + ((list_lit + ((sym_lit) @sym + (:match ,(clojure-ts-symbol-regexp clojure-ts-align-binding-forms) @sym)) + (vec_lit) @bindings-vec)) + ((list_lit + ((sym_lit) @sym + (:match ,(clojure-ts-symbol-regexp clojure-ts-align-cond-forms) @sym))) + @cond) + ((anon_fn_lit + ((sym_lit) @sym + (:match ,(clojure-ts-symbol-regexp clojure-ts-align-binding-forms) @sym)) + (vec_lit) @bindings-vec)) + ((anon_fn_lit + ((sym_lit) @sym + (:match ,(clojure-ts-symbol-regexp clojure-ts-align-cond-forms) @sym))) + @cond))))) + +(defvar clojure-ts--align-reader-conditionals-query + (treesit-query-compile 'clojure + '(((read_cond_lit) @read-cond) + ((splicing_read_cond_lit) @read-cond)))) + (defun clojure-ts--get-nodes-to-align (beg end) "Return a plist of nodes data for alignment. @@ -1617,31 +1647,15 @@ have changed." ;; By default `treesit-query-capture' captures all nodes that cross the range. ;; We need to restrict it to only nodes inside of the range. (let* ((region-node (clojure-ts--region-node beg end)) - (query (treesit-query-compile 'clojure - (append - `(((map_lit) @map) - ((ns_map_lit) @ns-map) - ((list_lit - ((sym_lit) @sym - (:match ,(clojure-ts-symbol-regexp clojure-ts-align-binding-forms) @sym)) - (vec_lit) @bindings-vec)) - ((list_lit - ((sym_lit) @sym - (:match ,(clojure-ts-symbol-regexp clojure-ts-align-cond-forms) @sym))) - @cond) - ((anon_fn_lit - ((sym_lit) @sym - (:match ,(clojure-ts-symbol-regexp clojure-ts-align-binding-forms) @sym)) - (vec_lit) @bindings-vec)) - ((anon_fn_lit - ((sym_lit) @sym - (:match ,(clojure-ts-symbol-regexp clojure-ts-align-cond-forms) @sym))) - @cond)) - (when clojure-ts-align-reader-conditionals - '(((read_cond_lit) @read-cond) - ((splicing_read_cond_lit) @read-cond))))))) - (thread-last (treesit-query-capture region-node query beg end) + (nodes (append (treesit-query-capture region-node clojure-ts--align-query beg end) + (when clojure-ts-align-reader-conditionals + (treesit-query-capture region-node clojure-ts--align-reader-conditionals-query beg end))))) + (thread-last nodes (seq-remove (lambda (elt) (eq (car elt) 'sym))) + ;; Reverse the result to align the most deeply nested nodes + ;; first. This way we can prevent breaking alignment of outer + ;; nodes. + (seq-reverse) ;; When first node is reindented, all other nodes become ;; outdated. Executing the entire query everytime is very ;; expensive, instead we use markers for every captured node to From 832cc5c127f64162ca72cff4a2e943a6d3a259b6 Mon Sep 17 00:00:00 2001 From: Roman Rudakov Date: Mon, 26 May 2025 20:41:24 +0200 Subject: [PATCH 345/379] Speed-up docstrings matching by pre-compiling regexps --- CHANGELOG.md | 6 +++ clojure-ts-mode.el | 40 ++++++++++++------- ...clojure-ts-mode-refactor-threading-test.el | 7 ++++ 3 files changed, 39 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4022bcb..18eb45e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ - [#96](https://github.com/clojure-emacs/clojure-ts-mode/pull/96): Highlight function name properly in `extend-protocol` form. - [#96](https://github.com/clojure-emacs/clojure-ts-mode/pull/96): Add support for extend-protocol forms to `clojure-ts-add-arity` refactoring command. +- Improve navigation by s-expression by switching to an experimental Clojure + grammar. +- More consistent docstrings highlighting and `fill-paragraph` behavior. +- Fix bug in `clojure-ts-align` when nested form has extra spaces. +- Fix bug in `clojure-ts-unwind` when there is only one expression after threading + symbol. ## 0.4.0 (2025-05-15) diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index ba6380d..53e8ffb 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -371,18 +371,22 @@ Only intended for use at development time.") "Return a regular expression that matches one of SYMBOLS exactly." (concat "^" (regexp-opt symbols) "$")) -(defvar clojure-ts-function-docstring-symbols - '("definline" - "defmulti" - "defmacro" - "defn" - "defn-" - "defprotocol" - "ns") +(defconst clojure-ts-function-docstring-symbols + (eval-and-compile + (rx line-start + (or "definline" + "defmulti" + "defmacro" + "defn" + "defn-" + "defprotocol" + "ns") + line-end)) "Symbols that accept an optional docstring as their second argument.") -(defvar clojure-ts-definition-docstring-symbols - '("def") +(defconst clojure-ts-definition-docstring-symbols + (eval-and-compile + (rx line-start "def" line-end)) "Symbols that accept an optional docstring as their second argument. Any symbols added here should only treat their second argument as a docstring if a third argument (the value) is provided. @@ -428,7 +432,7 @@ if a third argument (the value) is provided. :anchor (str_lit (str_content) ,capture-symbol) @font-lock-doc-face ;; The variable's value :anchor (_)) - (:match ,(clojure-ts-symbol-regexp clojure-ts-definition-docstring-symbols) + (:match ,clojure-ts-definition-docstring-symbols @_def_symbol)) ;; Captures docstrings in metadata of definitions ((list_lit :anchor [(comment) (meta_lit) (old_meta_lit)] :* @@ -456,7 +460,7 @@ if a third argument (the value) is provided. :anchor (sym_lit) :anchor [(comment) (meta_lit) (old_meta_lit)] :* :anchor (str_lit (str_content) ,capture-symbol) @font-lock-doc-face) - (:match ,(clojure-ts-symbol-regexp clojure-ts-function-docstring-symbols) + (:match ,clojure-ts-function-docstring-symbols @_def_symbol)) ;; Captures docstrings in defprotcol, definterface ((list_lit :anchor [(comment) (meta_lit) (old_meta_lit)] :* @@ -1498,7 +1502,15 @@ function literal." "definline" "defrecord" "defmacro" - "defmulti") + "defmulti" + "defonce" + "defprotocol" + "deftest" + "deftest-" + "ns" + "definterface" + "deftype" + "defstruct") eol))) (defconst clojure-ts--markdown-inline-sexp-nodes @@ -1509,7 +1521,7 @@ function literal." (defun clojure-ts--default-sexp-node-p (node) "Return TRUE if point is after the # marker of set or function literal NODE." - (and (eq (char-before (point)) ?\#) + (and (eq (char-before) ?\#) (string-match-p (rx bol (or "anon_fn_lit" "set_lit") eol) (treesit-node-type (treesit-node-parent node))))) diff --git a/test/clojure-ts-mode-refactor-threading-test.el b/test/clojure-ts-mode-refactor-threading-test.el index ce26d5d..35e1ebb 100644 --- a/test/clojure-ts-mode-refactor-threading-test.el +++ b/test/clojure-ts-mode-refactor-threading-test.el @@ -205,6 +205,13 @@ (clojure-ts-unwind) (clojure-ts-unwind)) + (when-refactoring-it "should work correctly when there is only one expression" + "(->> (filter even? [1 2 3 4]))" + + "(filter even? [1 2 3 4])" + + (clojure-ts-unwind)) + (when-refactoring-it "should unwind N steps with numeric prefix arg" "(->> [1 2 3 4 5] (filter even?) From 7e359edd3e695a81eaa20bd7db5c451ad281a8ef Mon Sep 17 00:00:00 2001 From: Roman Rudakov Date: Tue, 27 May 2025 11:59:48 +0200 Subject: [PATCH 346/379] Extend docstrings and fix some small issues --- CHANGELOG.md | 12 ++++++------ clojure-ts-mode.el | 29 +++++++++++++++++++++-------- test/samples/indentation.clj | 1 - 3 files changed, 27 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 18eb45e..aca43c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,12 +5,12 @@ - [#96](https://github.com/clojure-emacs/clojure-ts-mode/pull/96): Highlight function name properly in `extend-protocol` form. - [#96](https://github.com/clojure-emacs/clojure-ts-mode/pull/96): Add support for extend-protocol forms to `clojure-ts-add-arity` refactoring command. -- Improve navigation by s-expression by switching to an experimental Clojure - grammar. -- More consistent docstrings highlighting and `fill-paragraph` behavior. -- Fix bug in `clojure-ts-align` when nested form has extra spaces. -- Fix bug in `clojure-ts-unwind` when there is only one expression after threading - symbol. +- [#99](https://github.com/clojure-emacs/clojure-ts-mode/pull/99): Improve navigation by s-expression by switching to an experimental + Clojure grammar. +- [#99](https://github.com/clojure-emacs/clojure-ts-mode/pull/99): More consistent docstrings highlighting and `fill-paragraph` behavior. +- [#99](https://github.com/clojure-emacs/clojure-ts-mode/pull/99): Fix bug in `clojure-ts-align` when nested form has extra spaces. +- [#99](https://github.com/clojure-emacs/clojure-ts-mode/pull/99): Fix bug in `clojure-ts-unwind` when there is only one expression after + threading symbol. ## 0.4.0 (2025-05-15) diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index 53e8ffb..03a93af 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -470,7 +470,7 @@ if a third argument (the value) is provided. :*) (:match ,clojure-ts--interface-def-symbol-regexp @_def_symbol)))) -(defconst clojure-ts--match-docstring-query-compiled +(defconst clojure-ts--match-docstring-query (treesit-query-compile 'clojure (clojure-ts--docstring-query '@font-lock-doc-face)) "Precompiled query that matches a Clojure docstring.") @@ -839,9 +839,14 @@ Skip the optional metadata node at pos 0 if present." t))) (defun clojure-ts--first-value-child (node) - "Return the first value child of the NODE. - -This will skip metadata and comment nodes." + "Returns the first value child of the given NODE. + +In the syntax tree, there are a few types of possible child nodes: +unnamed standalone nodes (e.g., comments), anonymous nodes (e.g., +opening or closing parentheses), and named nodes. Named nodes are +standalone nodes that are labeled by a specific name. The most common +names are meta and value. This function skips any unnamed, anonymous, +and metadata nodes and returns the first value node." (treesit-node-child-by-field-name node "value")) (defun clojure-ts--symbol-matches-p (symbol-regexp node) @@ -1363,7 +1368,7 @@ according to the rule. If NODE is nil, use next node after BOL." "Match PARENT when it is a docstring node." (when-let* ((top-level-node (treesit-parent-until parent 'defun t)) (result (treesit-query-capture top-level-node - clojure-ts--match-docstring-query-compiled))) + clojure-ts--match-docstring-query))) (seq-find (lambda (elt) (and (eq (car elt) 'font-lock-doc-face) (treesit-node-eq (cdr elt) parent))) @@ -1529,6 +1534,9 @@ function literal." `((clojure (sexp ,(regexp-opt clojure-ts--sexp-nodes)) (list ,(regexp-opt clojure-ts--list-nodes)) + ;; `sexp-default' thing allows to fallback to the default implementation of + ;; `forward-sexp' function where `treesit-forward-sexp' produces undesired + ;; results. (sexp-default ;; For `C-M-f' in "#|(a)" or "#|{1 2 3}" (,(rx (or "(" "{")) . ,#'clojure-ts--default-sexp-node-p)) @@ -2470,8 +2478,13 @@ before DELIM-OPEN." "v0.24.3")) "Intended to be used as the value for `treesit-language-source-alist'.") -(defun clojure-ts--grammar-outdated-p () - "Return TRUE if currently installed grammar is outdated." +(defun clojure-ts--clojure-grammar-outdated-p () + "Return TRUE if currently installed grammar is outdated. + +This function checks if `clojure-ts-mode' is compatible with the +currently installed grammar. The simplest way to do this is to validate +a query that is valid in a previous grammar version but invalid in the +required version." (treesit-query-valid-p 'clojure '((sym_lit (meta_lit))))) (defun clojure-ts--ensure-grammars () @@ -2483,7 +2496,7 @@ before DELIM-OPEN." ;; If Clojure grammar is available, but outdated, re-install ;; it. (and (equal grammar 'clojure) - (clojure-ts--grammar-outdated-p))) + (clojure-ts--clojure-grammar-outdated-p))) (message "Installing %s Tree-sitter grammar" grammar) ;; `treesit-language-source-alist' is dynamically scoped. ;; Binding it in this let expression allows diff --git a/test/samples/indentation.clj b/test/samples/indentation.clj index 7d30162..52b417e 100644 --- a/test/samples/indentation.clj +++ b/test/samples/indentation.clj @@ -228,7 +228,6 @@ :foo "bar"} -;; NOTE: It works well now with the alternative grammar. '(one two ^:foo three) From cb2cb18640d3f643ea5c87cda8e5caaaab1e4015 Mon Sep 17 00:00:00 2001 From: Roman Rudakov Date: Tue, 27 May 2025 12:40:39 +0200 Subject: [PATCH 347/379] Update the design documentation --- clojure-ts-mode.el | 54 +++++++++++++++----------------- doc/design.md | 76 +++++++++++++++++++++++++++++++++------------- 2 files changed, 79 insertions(+), 51 deletions(-) diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index 03a93af..a5b504b 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -306,7 +306,7 @@ Only intended for use at development time.") "Syntax table for `clojure-ts-mode'.") (defconst clojure-ts--builtin-dynamic-var-regexp - (eval-and-compile + (eval-when-compile (concat "^" (regexp-opt '("*1" "*2" "*3" "*agent*" @@ -323,7 +323,7 @@ Only intended for use at development time.") "$"))) (defconst clojure-ts--builtin-symbol-regexp - (eval-and-compile + (eval-when-compile (concat "^" (regexp-opt '("do" "if" "let*" "var" @@ -372,52 +372,46 @@ Only intended for use at development time.") (concat "^" (regexp-opt symbols) "$")) (defconst clojure-ts-function-docstring-symbols - (eval-and-compile - (rx line-start - (or "definline" - "defmulti" - "defmacro" - "defn" - "defn-" - "defprotocol" - "ns") - line-end)) + (rx line-start + (or "definline" + "defmulti" + "defmacro" + "defn" + "defn-" + "defprotocol" + "ns") + line-end) "Symbols that accept an optional docstring as their second argument.") (defconst clojure-ts-definition-docstring-symbols - (eval-and-compile - (rx line-start "def" line-end)) + (rx line-start "def" line-end) "Symbols that accept an optional docstring as their second argument. Any symbols added here should only treat their second argument as a docstring if a third argument (the value) is provided. \"def\" is the only builtin Clojure symbol that behaves like this.") (defconst clojure-ts--variable-definition-symbol-regexp - (eval-and-compile - (rx line-start (or "def" "defonce") line-end)) + (rx line-start (or "def" "defonce") line-end) "A regular expression matching a symbol used to define a variable.") (defconst clojure-ts--typedef-symbol-regexp - (eval-and-compile - (rx line-start - (or "defprotocol" "defmulti" "deftype" "defrecord" - "definterface" "defmethod" "defstruct") - line-end)) + (rx line-start + (or "defprotocol" "defmulti" "deftype" "defrecord" + "definterface" "defmethod" "defstruct") + line-end) "A regular expression matching a symbol used to define a type.") (defconst clojure-ts--type-symbol-regexp - (eval-and-compile - (rx line-start - (or "deftype" "defrecord" - ;; While not reifying, helps with doc strings - "defprotocol" "definterface" - "reify" "proxy" "extend-type" "extend-protocol") - line-end)) + (rx line-start + (or "deftype" "defrecord" + ;; While not reifying, helps with doc strings + "defprotocol" "definterface" + "reify" "proxy" "extend-type" "extend-protocol") + line-end) "A regular expression matching a symbol used to define or instantiate a type.") (defconst clojure-ts--interface-def-symbol-regexp - (eval-and-compile - (rx line-start (or "defprotocol" "definterface") line-end)) + (rx line-start (or "defprotocol" "definterface") line-end) "A regular expression matching a symbol used to define an interface.") (defun clojure-ts--docstring-query (capture-symbol) diff --git a/doc/design.md b/doc/design.md index 8afeaff..4c19319 100644 --- a/doc/design.md +++ b/doc/design.md @@ -32,29 +32,43 @@ In short: ## tree-sitter-clojure -Clojure-ts-mode uses the tree-sitter-clojure grammar, which can be found at -The clojure-ts-mode grammar provides very basic, low level nodes that try to match Clojure's very light syntax. +`clojure-ts-mode` uses the experimental version tree-sitter-clojure grammar, which +can be found at +. The +`clojure-ts-mode` grammar provides very basic, low level nodes that try to match +Clojure's very light syntax. There are nodes to represent: -- Symbols (sym_lit) - - Contain (sym_ns) and (sym_name) nodes -- Keywords (kwd_lit) - - Contain (kwd_ns) and (kw_name) nodes -- Strings (str_lit) -- Chars (char_lit) -- Nil (nil_lit) -- Booleans (bool_lit) -- Numbers (num_lit) -- Comments (comment, dis_expr) - - dis_expr is the `#_` discard expression -- Lists (list_list) -- Vectors (vec_lit) -- Maps (map_lit) -- Sets (set_lit) - -There are also nodes to represent metadata, which appear on `meta:` child fields of the nodes the metadata is defined on. -For example a simple vector with metadata defined on it like so +- Symbols `(sym_lit)` + - Contain `(sym_ns)` and `(sym_name)` nodes +- Keywords `(kwd_lit)` + - Contain `(kwd_ns)` and `(kw_name)` nodes +- Strings `(str_lit)` + - Contains `(str_content)` node +- Chars `(char_lit)` +- Nil `(nil_lit)` +- Booleans `(bool_lit)` +- Numbers `(num_lit)` +- Comments `(comment, dis_expr)` + - `dis_expr` is the `#_` discard expression +- Lists `(list_list)` +- Vectors `(vec_lit)` +- Maps `(map_lit)` +- Sets `(set_lit)` +- Metadata nodes `(meta_lit)` +- Regex content `(regex_content)` +- Function literals `(anon_fn_lit)` + +The best place to learn more about the tree-sitter-clojure grammar is to read +the [grammar.js file from the tree-sitter-clojure repository](https://github.com/sogaiu/tree-sitter-clojure/blob/master/grammar.js "grammar.js"). + +### Difference between stable grammar and experimental + +#### Standalone metadata nodes + +Metadata nodes in stable grammar appear as child nodes of the nodes the metadata +is defined on. For example a simple vector with metadata defined on it like so: ```clojure ^:has-metadata [1] @@ -69,7 +83,27 @@ will produce a parse tree like so value: (num_lit)) ``` -The best place to learn more about the tree-sitter-clojure grammar is to read the [grammar.js file from the tree-sitter-clojure repository](https://github.com/sogaiu/tree-sitter-clojure/blob/master/grammar.js "grammar.js"). +Although it's somewhat closer to hoe Clojure treats metadata itself, in the +context of a text editor it creates some problems, which were discussed [here](https://github.com/sogaiu/tree-sitter-clojure/issues/65). To +name a few: +- `forward-sexp` command would skip both, metadata and the node it's attached + to. Called from an opening paren it would signal an error "No more sexp to + move across". +- `kill-sexp` command would kill both, metadata and the node it's attached to. +- `backward-up-list` called from the inside of a list with metadata would move + point to the beginning of metadata node. +- Internally we had to introduce some workarounds to skip metadata nodes or + figure out where the actual node starts. + +#### Special nodes for string content and regex content + +To parse the content of certain strings with a separate grammar, it is necessary +to extract the string's content, excluding its opening and closing quotes. To +achieve this, Emacs 31 allows specifying offsets for `treesit-range-settings`. +However, in Emacs 30.1, this feature is broken due to bug [#77848](https://debbugs.gnu.org/cgi/bugreport.cgi?bug=77848) (a fix is +anticipated in Emacs 30.2). The presence of `str_content` and `regex_content` nodes +allows us to support this feature across all Emacs versions without relying on +offsets. ### Clojure Syntax, not Clojure Semantics From eca1a96e7e4728649cf4bee05bd4b8592195b639 Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Tue, 27 May 2025 13:48:41 +0300 Subject: [PATCH 348/379] Fix a typo --- doc/design.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/doc/design.md b/doc/design.md index 4c19319..b07c59b 100644 --- a/doc/design.md +++ b/doc/design.md @@ -83,9 +83,10 @@ will produce a parse tree like so value: (num_lit)) ``` -Although it's somewhat closer to hoe Clojure treats metadata itself, in the +Although it's somewhat closer to how Clojure treats metadata itself, in the context of a text editor it creates some problems, which were discussed [here](https://github.com/sogaiu/tree-sitter-clojure/issues/65). To name a few: + - `forward-sexp` command would skip both, metadata and the node it's attached to. Called from an opening paren it would signal an error "No more sexp to move across". From c7c7550f04d2191ec3371d90f1549e43dae1db2e Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Tue, 27 May 2025 13:54:56 +0300 Subject: [PATCH 349/379] Appease checkdoc --- clojure-ts-mode.el | 6 +++--- test/clojure-ts-mode-indentation-test.el | 2 +- test/test-helper.el | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index a5b504b..c2dd69b 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -833,7 +833,7 @@ Skip the optional metadata node at pos 0 if present." t))) (defun clojure-ts--first-value-child (node) - "Returns the first value child of the given NODE. + "Return the first value child of the given NODE. In the syntax tree, there are a few types of possible child nodes: unnamed standalone nodes (e.g., comments), anonymous nodes (e.g., @@ -885,7 +885,7 @@ See `clojure-ts--definition-node-p' when an exact match is possible." (defun clojure-ts--standard-definition-node-name (node) "Return the definition name for the given NODE. -Returns nil if NODE is not a list with symbols as the first two +Return nil if NODE is not a list with symbols as the first two children. For example the node representing the expression (def foo 1) would return foo. The node representing (ns user) would return user. Does not do any matching on the first symbol (def, defn, etc), so @@ -909,7 +909,7 @@ Can be called directly, but intended for use as `treesit-defun-name-function'." (defun clojure-ts--kwd-definition-node-name (node) "Return the keyword name for the given NODE. -Returns nil if NODE is not a list where the first element is a symbol +Return nil if NODE is not a list where the first element is a symbol and the second is a keyword. For example, a node representing the expression (s/def ::foo int?) would return foo. diff --git a/test/clojure-ts-mode-indentation-test.el b/test/clojure-ts-mode-indentation-test.el index bda3538..d158ed8 100644 --- a/test/clojure-ts-mode-indentation-test.el +++ b/test/clojure-ts-mode-indentation-test.el @@ -124,7 +124,7 @@ DESCRIPTION is a string with the description of the spec." ;; Mock `cider--get-symbol-indent' function (defun cider--get-symbol-indent-mock (symbol-name) - "Returns static mocked indentation specs for SYMBOL-NAME if available." + "Return static mocked indentation specs for SYMBOL-NAME if available." (when (stringp symbol-name) (cond ((string-equal symbol-name "my-with-in-str") 1) diff --git a/test/test-helper.el b/test/test-helper.el index fa821e6..f1515d9 100644 --- a/test/test-helper.el +++ b/test/test-helper.el @@ -48,7 +48,7 @@ and point left there." ,@body))) (defun clojure-ts--s-index-of (needle s &optional ignore-case) - "Returns first index of NEEDLE in S, or nil. + "Return first index of NEEDLE in S, or nil. If IGNORE-CASE is non-nil, the comparison is done without paying attention to case differences." From 034b26678b5cec195f04511b69a2101613219c3a Mon Sep 17 00:00:00 2001 From: Roman Rudakov Date: Tue, 27 May 2025 16:24:11 +0200 Subject: [PATCH 350/379] Add "Syntax highlighting" section to the design documentation --- doc/design.md | 68 ++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 67 insertions(+), 1 deletion(-) diff --git a/doc/design.md b/doc/design.md index b07c59b..95590b5 100644 --- a/doc/design.md +++ b/doc/design.md @@ -183,7 +183,73 @@ changes in the grammar. ## Syntax Highlighting -TODO +To set up Tree-sitter fontification, `clojure-ts-mode` sets the +`treesit-font-lock-settings` variable with the output of +`clojure-ts--font-lock-settings`, and then calls `treesit-major-mode-setup`. + +`clojure-ts--font-lock-settings` returns a list of compiled queries. Each query +must have at least one capture name (names that start with `@`). If a capture +name matches an existing face name (e.g., `font-lock-keyword-face`), the +captured node will be fontified with that face. + +A capture name can also be arbitrary and used to check the text of the captured +node. It can also be used for both fontification and text checking. For +example in the following query: + +```emacs-lisp +`((list_lit :anchor [(comment) (meta_lit) (old_meta_lit)] :* + :anchor (sym_lit !namespace name: (sym_name) @font-lock-keyword-face)) + (:match ,clojure-ts--builtin-symbol-regexp @font-lock-keyword-face)) +``` + +We match any list whose first symbol (skipping any number of comments and +metadata nodes) does not have a namespace and matches a regex stored in the +`clojure-ts--builtin-symbol-regexp` variable. The matched symbol is fontified +using `font-lock-keyword-face`. + +### Embedded parsers + +The Clojure grammar in `clojure-ts-mode` is a main or "host" grammar. Emacs +also supports the use of any number of "embedded" grammars. `clojure-ts-mode` +currently uses the `markdown-inline` grammar to highlight Markdown constructs in +docstrings and the `regex` grammar to highlight regular expression syntax. + +To use an embedded parser, `clojure-ts-mode` must set an appropriate value for +the `treesit-range-settings` variable. The Clojure grammar provides convenient +nodes to capture only the content of strings and regexes, which makes defining +range settings for regexes quite simple: + +```emacs-lisp +(treesit-range-rules + :embed 'regex + :host 'clojure + :local t + '((regex_content) @capture)) +``` + +For docstrings, the query is a bit more complex. Therefore, we have the +function `clojure-ts--docstring-query`, which is used for syntax highlighting, +indentation rules, and range settings for the embedded Markdown parser: + +```emacs-lisp +(treesit-range-rules + :embed 'markdown-inline + :host 'clojure + :local t + (clojure-ts--docstring-query '@capture)) + ``` + +It is important to use the `:local` option for embedded parsers; otherwise, the +range will not be restricted to the captured node, which will lead to broken +fontification (see bug [#77733](https://debbugs.gnu.org/cgi/bugreport.cgi?bug=77733)). + +### Additional information + +To find more details one can evaluate the following expression in Emacs: + +```emacs-lisp +(info "(elisp) Parser-based Font Lock") +``` ## Indentation From 2dd7ed20c1456760358881d646209f923be19b90 Mon Sep 17 00:00:00 2001 From: Roman Rudakov Date: Tue, 27 May 2025 17:17:05 +0200 Subject: [PATCH 351/379] Add "Indentation" section to the design documentation --- doc/design.md | 60 ++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 57 insertions(+), 3 deletions(-) diff --git a/doc/design.md b/doc/design.md index 95590b5..e1d6b05 100644 --- a/doc/design.md +++ b/doc/design.md @@ -253,8 +253,62 @@ To find more details one can evaluate the following expression in Emacs: ## Indentation -TODO +To enable the parser-based indentation engine, `clojure-ts-mode` sets the +`treesit-simple-indent-rules` with the output of +`clojure-ts--configured-indent-rules`, and then call `treesit-major-mode-setup`. -## Semantic Interpretation in clojure-ts-mode +According to the documentation of `treesit-simple-indnet-rules` variable, its +values is: -TODO: demonstrate how clojure-ts-mode creates semantic meaning from a given syntax tree. Show examples of how new semantic meaning can be added (with highlighting, indentation, etc). +> A list of indent rule settings. +> Each indent rule setting should be (LANGUAGE RULE...), where LANGUAGE is +> a language symbol, and each RULE is of the form +> +> (MATCHER ANCHOR OFFSET) +> +> MATCHER determines whether this rule applies, ANCHOR and +> OFFSET together determines which column to indent to. + +For example rule like this: + +```emacs-lisp +'((clojure + ((parent-is "^vec_lit$") parent 1) + ((parent-is "^map_lit$") parent 1) + ((parent-is "^set_lit$") parent 2))) +``` + +will indent any node whose parent node is a `vec_lit` or `map_lit` with 1 space, +starting from the beginning of the parent node. For `set_lit`, it will add two +spaces because sets have two opening characters: `#` and `{`. + +In the example above, the `parent-is` matcher and `parent` anchor are built-in +presets. There are many predefined presets provided by Emacs. The list of all +available presets can be found in the documentation for the +`treesit-simple-indent-presets` variable. + +Sometimes, more complex behavior than predefined built-in presets is required. +In such cases, you can write your own matchers and anchors. One good example is +the `clojure-ts--match-form-body` matcher. It attempts to match a node at point +using the combined value of `clojure-ts--semantic-indent-rules-defaults` and +`clojure-ts-semantic-indent-rules`. These rules have a similar format to cljfmt +indentation rules. `clojure-ts-semantic-indent-rules` is a customization option +that users can tweak. `clojure-ts--match-form-body` traverses the syntax tree, +starting from the node at point, towards the top of the tree in order to find a +match. In addition to `clojure-ts--semantic-indent-rules-defaults` and +`clojure-ts-semantic-indent-rules`, it may also use `clojure-ts-get-indent-function` +if it is not `nil`. This function provides an API for dynamic indentation and +must return a value compatible with `cider-nrepl`. Searching for an indentation +rule across all these variables is slow; therefore, +`clojure-ts--semantic-indent-rules-cache` was introduced. It is set when +`clojure-ts-mode` is activated in a Clojure source buffer and refreshed every time +`clojure-ts-semantic-indent-rules` is updated (using setopt or the customization +interface) or when a `.dir-locals.el` file is updated. + +### Additional information + +To find more details one can evaluate the following expression in Emacs: + +```emacs-lisp +(info "(elisp) Parser-based Indentation") +``` From 5df8343c1ee65b37e6085a14589318bf6583817f Mon Sep 17 00:00:00 2001 From: Roman Rudakov Date: Thu, 29 May 2025 11:38:47 +0200 Subject: [PATCH 352/379] Inline treesit-query-p to ensure compatibility with Emacs 30 --- clojure-ts-mode.el | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index c2dd69b..29944b9 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -2472,6 +2472,12 @@ before DELIM-OPEN." "v0.24.3")) "Intended to be used as the value for `treesit-language-source-alist'.") +(defun clojure-ts--query-valid-p (query) + "Return non-nil if QUERY is valid in Clojure, nil otherwise." + (ignore-errors + (treesit-query-compile 'clojure query t) + t)) + (defun clojure-ts--clojure-grammar-outdated-p () "Return TRUE if currently installed grammar is outdated. @@ -2479,7 +2485,7 @@ This function checks if `clojure-ts-mode' is compatible with the currently installed grammar. The simplest way to do this is to validate a query that is valid in a previous grammar version but invalid in the required version." - (treesit-query-valid-p 'clojure '((sym_lit (meta_lit))))) + (clojure-ts--query-valid-p '((sym_lit (meta_lit))))) (defun clojure-ts--ensure-grammars () "Install required language grammars if not already available." From 545991db1ecd6845c1ca2705131a521942a02a8d Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Thu, 29 May 2025 12:58:39 +0300 Subject: [PATCH 353/379] Add a todo --- clojure-ts-mode.el | 1 + 1 file changed, 1 insertion(+) diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index 29944b9..abf54f1 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -2472,6 +2472,7 @@ before DELIM-OPEN." "v0.24.3")) "Intended to be used as the value for `treesit-language-source-alist'.") +;; TODO: Eventually this should be replaced with `treesit-query-valid-p' (defun clojure-ts--query-valid-p (query) "Return non-nil if QUERY is valid in Clojure, nil otherwise." (ignore-errors From a16c6b46f7af693b19f4b9ac712134fcf738fbf3 Mon Sep 17 00:00:00 2001 From: Roman Rudakov Date: Tue, 27 May 2025 23:03:37 +0200 Subject: [PATCH 354/379] [#23] Support syntax highlighting for embedded JS and C++ --- CHANGELOG.md | 5 ++ README.md | 8 +++ clojure-ts-mode.el | 120 +++++++++++++++++++++++++++++++++++++-- test/samples/embed.cljs | 12 ++++ test/samples/native.jank | 6 +- 5 files changed, 145 insertions(+), 6 deletions(-) create mode 100644 test/samples/embed.cljs diff --git a/CHANGELOG.md b/CHANGELOG.md index aca43c6..8545599 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,11 @@ - [#99](https://github.com/clojure-emacs/clojure-ts-mode/pull/99): Fix bug in `clojure-ts-align` when nested form has extra spaces. - [#99](https://github.com/clojure-emacs/clojure-ts-mode/pull/99): Fix bug in `clojure-ts-unwind` when there is only one expression after threading symbol. +- Introduce `clojure-ts-jank-use-cpp-parser` customization which allows + highlighting C++ syntax in Jank `native/raw` forms. +- Introduce `clojure-ts-clojurescript-use-js-parser` customization which allows + highlighting JS syntax in ClojureScript `js*` forms. + ## 0.4.0 (2025-05-15) diff --git a/README.md b/README.md index 7bc21af..f165b7c 100644 --- a/README.md +++ b/README.md @@ -133,6 +133,14 @@ Once installed, evaluate `clojure-ts-mode.el` and you should be ready to go. - [tree-sitter-regex](https://github.com/tree-sitter/tree-sitter-regex/releases/tag/v0.24.3), which will be used for regex literals if available and if `clojure-ts-use-regex-parser` is not `nil`. +`clojure-ts-clojurescript-mode` can optionally use `tree-sitter-javascript` grammar +to highlight JS syntax in `js*` forms. This is enabled by default and can be +turned off by setting `clojure-ts-clojurescript-use-js-parser` to `nil`. + +`clojure-ts-jank-mode` can optionally use `tree-sitter-cpp` grammar to highlight C++ +syntax in `native/raw` forms. This is enabled by default and can be turned off by +setting `clojure-ts-jank-use-cpp-parser` to `nil`. + If you have `git` and a C compiler (`cc`) available on your system's `PATH`, `clojure-ts-mode` will install the grammars when you first open a Clojure file and `clojure-ts-ensure-grammars` is diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index abf54f1..746062f 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -128,6 +128,18 @@ double quotes on the third column." :safe #'booleanp :package-version '(clojure-ts-mode . "0.4")) +(defcustom clojure-ts-clojurescript-use-js-parser t + "When non-nil, use JS grammar to highlight syntax in js* forms." + :type 'boolean + :safe #'booleanp + :package-version '(clojure-ts-mode . "0.5")) + +(defcustom clojure-ts-jank-use-cpp-parser t + "When non-nil, use C++ grammar to highlight syntax in native/raw forms." + :type 'boolean + :safe #'booleanp + :package-version '(clojure-ts-mode . "0.5")) + (defcustom clojure-ts-auto-remap t "When non-nil, redirect all `clojure-mode' buffers to `clojure-ts-mode'." :safe #'booleanp @@ -489,6 +501,34 @@ When USE-REGEX is non-nil, include range settings for regex parser." :local t '((regex_content) @capture))))) +(defun clojure-ts--fontify-string (node override _start _end &optional _rest) + "Fontify string content NODE with `font-lock-string-face'. + +In order to support embedded syntax highlighting for JS in ClojureScript +and C++ in Jank we need to avoid fontifying string content in some +special forms, such as native/raw in Jank and js* in ClojureScript, +otherwise string face will interfere with embedded parser's faces. + +This function respects OVERRIDE argument by passing it to +`treesit-fontify-with-override'. + +START and END arguments that are passed to this function are not start +and end of the NODE, so we ignore them." + (let* ((prev (treesit-node-prev-sibling (treesit-node-parent node))) + (jank-native-p (and (derived-mode-p 'clojure-ts-jank-mode) + clojure-ts-jank-use-cpp-parser + (clojure-ts--symbol-node-p prev) + (string= (treesit-node-text prev) "native/raw"))) + (js-interop-p (and (derived-mode-p 'clojure-ts-clojurescript-mode) + clojure-ts-clojurescript-use-js-parser + (clojure-ts--symbol-node-p prev) + (string= (treesit-node-text prev) "js*")))) + (when (not (or jank-native-p js-interop-p)) + (treesit-fontify-with-override (treesit-node-start node) + (treesit-node-end node) + 'font-lock-string-face + override)))) + (defun clojure-ts--font-lock-settings (markdown-available regex-available) "Return font lock settings suitable for use in `treesit-font-lock-settings'. @@ -501,7 +541,9 @@ literals with regex grammar." (treesit-font-lock-rules :feature 'string :language 'clojure - '((str_lit) @font-lock-string-face + '((str_lit open: _ @font-lock-string-face + (str_content) @clojure-ts--fontify-string + close: _ @font-lock-string-face) (regex_lit) @font-lock-regexp-face) :feature 'regex @@ -1400,7 +1442,6 @@ regexes with anchors matching the beginning and end of the line are used." `((clojure ((parent-is "^source$") parent-bol 0) - (clojure-ts--match-docstring parent 0) ;; Literal Sequences ((parent-is "^vec_lit$") parent 1) ;; https://guide.clojure.style/#bindings-alignment ((parent-is "^map_lit$") parent 1) ;; https://guide.clojure.style/#map-keys-alignment @@ -1418,7 +1459,12 @@ used." ;; https://guide.clojure.style/#one-space-indent ((parent-is "^list_lit$") parent 1) ((parent-is "^anon_fn_lit$") parent 2) - (clojure-ts--match-with-metadata parent 0)))) + (clojure-ts--match-with-metadata parent 0) + ;; This is slow and only matches when point is inside of a docstring and + ;; only when Markdown grammar is disabled. `indent-region' tries to match + ;; all the rules from top to bottom, so order matters here (the slowest + ;; rules should be at the bottom). + (clojure-ts--match-docstring parent 0)))) (defun clojure-ts--configured-indent-rules () "Gets the configured choice of indent rules." @@ -2518,6 +2564,44 @@ function can also be used to upgrade the grammars if they are outdated." (let ((treesit-language-source-alist clojure-ts-grammar-recipes)) (treesit-install-language-grammar grammar))))) +(defsubst clojure-ts--font-lock-setting-update-override (setting) + "Return SETTING with override set to TRUE." + (let ((new-setting (copy-tree setting))) + (setf (nth 3 new-setting) t) + new-setting)) + +(defun clojure-ts--harvest-treesit-configs (mode) + "Harvest tree-sitter configs from MODE. +Return a plist with the following keys and value: + + :font-lock (from `treesit-font-lock-settings') + :simple-indent (from `treesit-simple-indent-rules')" + (with-temp-buffer + (funcall mode) + ;; We need to set :override t for all external queries, otherwise new faces + ;; won't be applied on top of the string face defined for `clojure-ts-mode'. + (list :font-lock (seq-map #'clojure-ts--font-lock-setting-update-override + treesit-font-lock-settings) + :simple-indent treesit-simple-indent-rules))) + +(defun clojure-ts--add-config-for-mode (mode) + "Add configurations for MODE to current buffer. + +Configuration includes font-lock and indent. For font-lock rules, use +the same features enabled in MODE." + (let ((configs (clojure-ts--harvest-treesit-configs mode))) + (setq treesit-font-lock-settings + (append treesit-font-lock-settings + (plist-get configs :font-lock))) + ;; FIXME: This works a bit aggressively. `indent-region' always tries to + ;; use rules for embedded parser. Without it users can format embedded code + ;; in an arbitrary way. + ;; + ;; (setq treesit-simple-indent-rules + ;; (append treesit-simple-indent-rules + ;; (plist-get configs :simple-indent))) + )) + (defun clojure-ts-mode-variables (&optional markdown-available regex-available) "Initialize buffer-local variables for `clojure-ts-mode'. @@ -2625,7 +2709,20 @@ REGEX-AVAILABLE." (define-derived-mode clojure-ts-clojurescript-mode clojure-ts-mode "ClojureScript[TS]" "Major mode for editing ClojureScript code. -\\{clojure-ts-clojurescript-mode-map}") +\\{clojure-ts-clojurescript-mode-map}" + (when (and clojure-ts-clojurescript-use-js-parser + (treesit-ready-p 'javascript t)) + (setq-local treesit-range-settings + (append treesit-range-settings + (treesit-range-rules + :embed 'javascript + :host 'clojure + :local t + '(((list_lit (sym_lit) @_sym-name + :anchor (str_lit (str_content) @capture)) + (:equal @_sym-name "js*")))))) + (clojure-ts--add-config-for-mode 'js-ts-mode) + (treesit-major-mode-setup))) ;;;###autoload (define-derived-mode clojure-ts-clojurec-mode clojure-ts-mode "ClojureC[TS]" @@ -2643,7 +2740,20 @@ REGEX-AVAILABLE." (define-derived-mode clojure-ts-jank-mode clojure-ts-mode "Jank[TS]" "Major mode for editing Jank code. -\\{clojure-ts-jank-mode-map}") +\\{clojure-ts-jank-mode-map}" + (when (and clojure-ts-jank-use-cpp-parser + (treesit-ready-p 'cpp t)) + (setq-local treesit-range-settings + (append treesit-range-settings + (treesit-range-rules + :embed 'cpp + :host 'clojure + :local t + '(((list_lit (sym_lit) @_sym-name + :anchor (str_lit (str_content) @capture)) + (:equal @_sym-name "native/raw")))))) + (clojure-ts--add-config-for-mode 'c++-ts-mode) + (treesit-major-mode-setup))) (defun clojure-ts--register-novel-modes () "Set up Clojure modes not present in progenitor clojure-mode.el." diff --git a/test/samples/embed.cljs b/test/samples/embed.cljs new file mode 100644 index 0000000..22000a7 --- /dev/null +++ b/test/samples/embed.cljs @@ -0,0 +1,12 @@ +(ns embed) + +(js* "var hello = console.log('hello'); const now = new Date();") + +(js* "const hello = new Date(); + const someOtherVar = 'Just a string';") + +(println "This is a normal string") + +"Standalone string" + +(js* "var hello = 'world';") diff --git a/test/samples/native.jank b/test/samples/native.jank index bf07596..1eb03c7 100644 --- a/test/samples/native.jank +++ b/test/samples/native.jank @@ -4,7 +4,11 @@ (defn set-shader-source! [shader source] (native/raw "auto const shader(detail::to_int(~{ shader })); auto const &source(detail::to_string(~{ source })); + __value = make_box(); __value = make_box(glShaderSource(shader, 1, &source.data, nullptr));")) (defn compile-shader! [shader] - (native/raw "__value = make_box(glCompileShader(detail::to_int(~{ shader })));")) + (native/raw "__value = make_box(glCompileShader(detail::to_int(~{ shader })));") + "Normal string") + +"Normal string" From 0b305850bcb849ddbdee47c786e4611e4e9a6a48 Mon Sep 17 00:00:00 2001 From: Roman Rudakov Date: Fri, 30 May 2025 22:01:10 +0200 Subject: [PATCH 355/379] Cleanup some code - Remove 'append' from clojure-ts--align-query and extend it to match :let vector in 'for' and 'doseq' forms. - Remove hack with setting ':override t' for embedded parsers; it was added before I figured out how to not fontify some strings. --- clojure-ts-mode.el | 57 +++++++++++++++++++++++----------------------- 1 file changed, 28 insertions(+), 29 deletions(-) diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index 746062f..4b64582 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -1668,25 +1668,33 @@ BOUND bounds the whitespace search." (defvar clojure-ts--align-query (treesit-query-compile 'clojure - (append - `(((map_lit) @map) - ((ns_map_lit) @ns-map) - ((list_lit - ((sym_lit) @sym - (:match ,(clojure-ts-symbol-regexp clojure-ts-align-binding-forms) @sym)) - (vec_lit) @bindings-vec)) - ((list_lit - ((sym_lit) @sym - (:match ,(clojure-ts-symbol-regexp clojure-ts-align-cond-forms) @sym))) - @cond) - ((anon_fn_lit - ((sym_lit) @sym - (:match ,(clojure-ts-symbol-regexp clojure-ts-align-binding-forms) @sym)) - (vec_lit) @bindings-vec)) - ((anon_fn_lit - ((sym_lit) @sym - (:match ,(clojure-ts-symbol-regexp clojure-ts-align-cond-forms) @sym))) - @cond))))) + `(((map_lit) @map) + ((ns_map_lit) @ns-map) + ((list_lit + ((sym_lit) @sym + (:match ,(clojure-ts-symbol-regexp clojure-ts-align-binding-forms) @sym)) + (vec_lit) @bindings-vec)) + ((list_lit + :anchor + ((sym_lit) @sym + (:match ,(rx bol (or "for" "doseq") eol) @sym)) + (vec_lit + ((kwd_lit) @kwd + (:equal ":let" @kwd)) + :anchor + (vec_lit) @bindings-vec))) + ((list_lit + ((sym_lit) @sym + (:match ,(clojure-ts-symbol-regexp clojure-ts-align-cond-forms) @sym))) + @cond) + ((anon_fn_lit + ((sym_lit) @sym + (:match ,(clojure-ts-symbol-regexp clojure-ts-align-binding-forms) @sym)) + (vec_lit) @bindings-vec)) + ((anon_fn_lit + ((sym_lit) @sym + (:match ,(clojure-ts-symbol-regexp clojure-ts-align-cond-forms) @sym))) + @cond)))) (defvar clojure-ts--align-reader-conditionals-query (treesit-query-compile 'clojure @@ -2564,12 +2572,6 @@ function can also be used to upgrade the grammars if they are outdated." (let ((treesit-language-source-alist clojure-ts-grammar-recipes)) (treesit-install-language-grammar grammar))))) -(defsubst clojure-ts--font-lock-setting-update-override (setting) - "Return SETTING with override set to TRUE." - (let ((new-setting (copy-tree setting))) - (setf (nth 3 new-setting) t) - new-setting)) - (defun clojure-ts--harvest-treesit-configs (mode) "Harvest tree-sitter configs from MODE. Return a plist with the following keys and value: @@ -2578,10 +2580,7 @@ Return a plist with the following keys and value: :simple-indent (from `treesit-simple-indent-rules')" (with-temp-buffer (funcall mode) - ;; We need to set :override t for all external queries, otherwise new faces - ;; won't be applied on top of the string face defined for `clojure-ts-mode'. - (list :font-lock (seq-map #'clojure-ts--font-lock-setting-update-override - treesit-font-lock-settings) + (list :font-lock treesit-font-lock-settings :simple-indent treesit-simple-indent-rules))) (defun clojure-ts--add-config-for-mode (mode) From 82c4b33ea7f40f3251b577bf83b0827a7314dba3 Mon Sep 17 00:00:00 2001 From: Roman Rudakov Date: Fri, 30 May 2025 22:40:42 +0200 Subject: [PATCH 356/379] [#15] Introduce clojure-ts-extra-def-forms customization --- CHANGELOG.md | 9 ++++---- README.md | 31 ++++++++++++++++++++++++++ clojure-ts-mode.el | 21 +++++++++++++++++ test/clojure-ts-mode-font-lock-test.el | 19 ++++++++++++++++ 4 files changed, 76 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8545599..2045f19 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,11 +11,12 @@ - [#99](https://github.com/clojure-emacs/clojure-ts-mode/pull/99): Fix bug in `clojure-ts-align` when nested form has extra spaces. - [#99](https://github.com/clojure-emacs/clojure-ts-mode/pull/99): Fix bug in `clojure-ts-unwind` when there is only one expression after threading symbol. -- Introduce `clojure-ts-jank-use-cpp-parser` customization which allows +- [#103](https://github.com/clojure-emacs/clojure-ts-mode/issues/103): Introduce `clojure-ts-jank-use-cpp-parser` customization which allows highlighting C++ syntax in Jank `native/raw` forms. -- Introduce `clojure-ts-clojurescript-use-js-parser` customization which allows - highlighting JS syntax in ClojureScript `js*` forms. - +- [#103](https://github.com/clojure-emacs/clojure-ts-mode/issues/103): Introduce `clojure-ts-clojurescript-use-js-parser` customization which + allows highlighting JS syntax in ClojureScript `js*` forms. +- Introduce the `clojure-ts-extra-def-forms` customization option to specify + additional `defn`-like forms that should be fontified. ## 0.4.0 (2025-05-15) diff --git a/README.md b/README.md index f165b7c..d52ba1c 100644 --- a/README.md +++ b/README.md @@ -318,6 +318,37 @@ highlighted like regular Clojure code. > section](https://www.gnu.org/software/emacs/manual/html_node/emacs/Parser_002dbased-Font-Lock.html) > of the Emacs manual for more details. +#### Extending font-lock rules + +In `clojure-ts-mode` it is possible to specify additional defn-like forms that +should be fontified. For example to highlight the following form from Hiccup +library as a function definition: + +```clojure +(defelem file-upload + "Creates a file upload input." + [name] + (input-field "file" name nil)) +``` + +You can add `defelem` to `clojure-ts-extra-def-forms` list like this: + +```emacs-lisp +(add-to-list 'clojure-ts-extra-def-forms "defelem") +``` + +or set this variable using `setopt`: + +```emacs-lisp +(setopt clojure-ts-extra-def-forms '("defelem")) +``` + +This setting will highlight `defelem` symbol, function name and the docstring. + +**NOTE**: Setting `clojure-ts-extra-def-forms` won't change the indentation rule for +these forms. For indentation rules you should use +`clojure-ts-semantic-indent-rules` variable (see [semantic indentation](#customizing-semantic-indentation) section). + ### Highlight markdown syntax in docstrings By default Markdown syntax is highlighted in the docstrings using diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index 4b64582..c41f7f3 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -260,6 +260,12 @@ values like this: :safe #'booleanp :type 'boolean) +(defcustom clojure-ts-extra-def-forms nil + "List of forms that should be fontified the same way as defn." + :package-version '(clojure-ts-mode . "0.5") + :safe #'listp + :type '(repeat string)) + (defvar clojure-ts-mode-remappings '((clojure-mode . clojure-ts-mode) (clojurescript-mode . clojure-ts-clojurescript-mode) @@ -468,6 +474,15 @@ if a third argument (the value) is provided. :anchor (str_lit (str_content) ,capture-symbol) @font-lock-doc-face) (:match ,clojure-ts-function-docstring-symbols @_def_symbol)) + ((list_lit :anchor [(comment) (meta_lit) (old_meta_lit)] :* + :anchor (sym_lit) @_def_symbol + :anchor [(comment) (meta_lit) (old_meta_lit)] :* + ;; Function_name + :anchor (sym_lit) + :anchor [(comment) (meta_lit) (old_meta_lit)] :* + :anchor (str_lit (str_content) ,capture-symbol) @font-lock-doc-face) + (:match ,(clojure-ts-symbol-regexp clojure-ts-extra-def-forms) + @_def_symbol)) ;; Captures docstrings in defprotcol, definterface ((list_lit :anchor [(comment) (meta_lit) (old_meta_lit)] :* :anchor (sym_lit) @_def_symbol @@ -630,6 +645,12 @@ literals with regex grammar." "defonce") eol)) @font-lock-keyword-face)) + ((list_lit :anchor [(comment) (meta_lit) (old_meta_lit)] :* + :anchor (sym_lit (sym_name) @font-lock-keyword-face) + :anchor [(comment) (meta_lit) (old_meta_lit)] :* + :anchor (sym_lit (sym_name) @font-lock-function-name-face)) + (:match ,(clojure-ts-symbol-regexp clojure-ts-extra-def-forms) + @font-lock-keyword-face)) ((anon_fn_lit marker: "#" @font-lock-property-face)) ;; Methods implementation diff --git a/test/clojure-ts-mode-font-lock-test.el b/test/clojure-ts-mode-font-lock-test.el index 1fa9ed1..4770ccf 100644 --- a/test/clojure-ts-mode-font-lock-test.el +++ b/test/clojure-ts-mode-font-lock-test.el @@ -230,3 +230,22 @@ DESCRIPTION is the description of the spec." (set-parameter [m ^PreparedStatement s i] (.setObject s i (->pgobject m))))" (81 93 font-lock-function-name-face)))) + +;;;; Extra def forms + +(describe "clojure-ts-extra-def-forms" + (it "should respect the value of clojure-ts-extra-def-forms" + (with-clojure-ts-buffer "(defelem file-upload + \"Creates a file upload input.\" + [name] + (input-field \"file\" name nil))" + (setopt clojure-ts-extra-def-forms '("defelem")) + (clojure-ts-mode) + (font-lock-ensure) + (goto-char (point-min)) + (expect (get-text-property 2 'face) + :to-equal 'font-lock-keyword-face) + (expect (get-text-property 10 'face) + :to-equal 'font-lock-function-name-face) + (expect (get-text-property 25 'face) + :to-equal 'font-lock-doc-face)))) From 9d56af9035299a1154f884776f7a698667bbc2ed Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Sat, 31 May 2025 09:35:00 +0300 Subject: [PATCH 357/379] Convert note to proper admonition --- README.md | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index d52ba1c..41977c4 100644 --- a/README.md +++ b/README.md @@ -345,9 +345,12 @@ or set this variable using `setopt`: This setting will highlight `defelem` symbol, function name and the docstring. -**NOTE**: Setting `clojure-ts-extra-def-forms` won't change the indentation rule for -these forms. For indentation rules you should use -`clojure-ts-semantic-indent-rules` variable (see [semantic indentation](#customizing-semantic-indentation) section). +> [!IMPORTANT] +> +> Setting `clojure-ts-extra-def-forms` won't change the indentation rule for +> these forms. For indentation rules you should use +> `clojure-ts-semantic-indent-rules` variable (see [semantic +> indentation](#customizing-semantic-indentation) section). ### Highlight markdown syntax in docstrings From e983470f9b6118ea52276416594632c94e526870 Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Sat, 31 May 2025 09:38:01 +0300 Subject: [PATCH 358/379] [#105] Add a note for C++ invocation syntax that got changed in Jank --- clojure-ts-mode.el | 3 +++ 1 file changed, 3 insertions(+) diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index c41f7f3..2576132 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -530,6 +530,9 @@ This function respects OVERRIDE argument by passing it to START and END arguments that are passed to this function are not start and end of the NODE, so we ignore them." (let* ((prev (treesit-node-prev-sibling (treesit-node-parent node))) + ;; TODO: Seems jank has removed this syntax, so we might drop this + ;; after jank 1.0 gets released + ;; See https://github.com/jank-lang/jank/issues/24#issuecomment-2924460595 (jank-native-p (and (derived-mode-p 'clojure-ts-jank-mode) clojure-ts-jank-use-cpp-parser (clojure-ts--symbol-node-p prev) From 39f93d4c79e786c78cb9148465f80f51665787cf Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Sat, 31 May 2025 10:04:57 +0300 Subject: [PATCH 359/379] Improve the setup docs --- README.md | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 41977c4..703ea3a 100644 --- a/README.md +++ b/README.md @@ -144,10 +144,22 @@ setting `clojure-ts-jank-use-cpp-parser` to `nil`. If you have `git` and a C compiler (`cc`) available on your system's `PATH`, `clojure-ts-mode` will install the grammars when you first open a Clojure file and `clojure-ts-ensure-grammars` is -set to `t` (the default). +set to `t` (the default). macOS users can install the required tools like this: + +```shell +xcode-select --install +``` + +Similarly, Debian/Ubuntu users can do something like: + +```shell +sudo apt install build-essential +``` + +This installs GCC, G++, `make`, and other essential development tools. If `clojure-ts-mode` fails to automatically install the grammar, you have the -option to install it manually, Please, refer to the installation instructions of +option to install it manually. Please, refer to the installation instructions of each required grammar and make sure you're install the versions expected (see `clojure-ts-grammar-recipes` for details). @@ -249,7 +261,7 @@ Note that `clojure-ts-semantic-indent-rules` should be set using the customization interface or `setopt`; otherwise, it will not be applied correctly. -#### Project local indentation +#### Project-specific indentation Custom indentation rules can be set for individual projects. To achieve this, you need to create a `.dir-locals.el` file in the project root. The content @@ -261,7 +273,7 @@ should look like: ``` In order to apply directory-local variables to existing buffers, they must be -reverted. +"reverted" (reloaded). ### Vertical alignment From 95aed261172c2d8133dd4c87c2d5cfcf2ea30476 Mon Sep 17 00:00:00 2001 From: Roman Rudakov Date: Tue, 3 Jun 2025 22:36:59 +0200 Subject: [PATCH 360/379] [#107] Introduce clojure-ts-completion-at-point-function --- CHANGELOG.md | 3 +- README.md | 20 +++- clojure-ts-mode.el | 169 +++++++++++++++++++++++++---- test/clojure-ts-mode-completion.el | 153 ++++++++++++++++++++++++++ test/samples/completion.clj | 56 ++++++++++ 5 files changed, 376 insertions(+), 25 deletions(-) create mode 100644 test/clojure-ts-mode-completion.el create mode 100644 test/samples/completion.clj diff --git a/CHANGELOG.md b/CHANGELOG.md index 2045f19..1547127 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,8 +15,9 @@ highlighting C++ syntax in Jank `native/raw` forms. - [#103](https://github.com/clojure-emacs/clojure-ts-mode/issues/103): Introduce `clojure-ts-clojurescript-use-js-parser` customization which allows highlighting JS syntax in ClojureScript `js*` forms. -- Introduce the `clojure-ts-extra-def-forms` customization option to specify +- [#104](https://github.com/clojure-emacs/clojure-ts-mode/pull/104): Introduce the `clojure-ts-extra-def-forms` customization option to specify additional `defn`-like forms that should be fontified. +- Introduce completion feature and `clojure-ts-completion-enabled` customization. ## 0.4.0 (2025-05-15) diff --git a/README.md b/README.md index 703ea3a..6a86762 100644 --- a/README.md +++ b/README.md @@ -539,6 +539,21 @@ multi-arity function or macro. Function can be defined using `defn`, `fn` or By default prefix for all refactoring commands is `C-c C-r`. It can be changed by customizing `clojure-ts-refactor-map-prefix` variable. +## Code completion + +`clojure-ts-mode` provides basic code completion functionality. Completion only +works for the current source buffer and includes completion of top-level +definitions and local bindings. This feature can be turned off by setting: + +```emacs-lisp +(setopt clojure-ts-completion-enabled nil) +``` + +Here's the short video illustrating the feature with built-in completion (it +should also work well with more advanced packages like company and corfu): + +https://github.com/user-attachments/assets/7c37179f-5a5d-424f-9bd6-9c8525f6b2f7 + ## Migrating to clojure-ts-mode If you are migrating to `clojure-ts-mode` note that `clojure-mode` is still @@ -576,11 +591,6 @@ and `clojure-mode` (this is very helpful when dealing with `derived-mode-p` chec - Navigation by sexp/lists might work differently on Emacs versions lower than 31. Starting with version 31, Emacs uses Tree-sitter 'things' settings, if available, to rebind some commands. -- The indentation of list elements with metadata is inconsistent with other - collections. This inconsistency stems from the grammar's interpretation of - nearly every definition or function call as a list. Therefore, modifying the - indentation for list elements would adversely affect the indentation of - numerous other forms. ## Frequently Asked Questions diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index 2576132..d828571 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -266,6 +266,12 @@ values like this: :safe #'listp :type '(repeat string)) +(defcustom clojure-ts-completion-enabled t + "Enable built-in completion feature." + :package-version '(clojure-ts-mode . "0.5") + :safe #'booleanp + :type 'boolean) + (defvar clojure-ts-mode-remappings '((clojure-mode . clojure-ts-mode) (clojurescript-mode . clojure-ts-clojurescript-mode) @@ -1561,26 +1567,28 @@ function literal." "map_lit" "ns_map_lit" "vec_lit" "set_lit") "A regular expression that matches nodes that can be treated as lists.") +(defconst clojure-ts--defun-symbols-regex + (rx bol + (or "def" + "defn" + "defn-" + "definline" + "defrecord" + "defmacro" + "defmulti" + "defonce" + "defprotocol" + "deftest" + "deftest-" + "ns" + "definterface" + "deftype" + "defstruct") + eol)) + (defun clojure-ts--defun-node-p (node) "Return TRUE if NODE is a function or a var definition." - (clojure-ts--list-node-sym-match-p node - (rx bol - (or "def" - "defn" - "defn-" - "definline" - "defrecord" - "defmacro" - "defmulti" - "defonce" - "defprotocol" - "deftest" - "deftest-" - "ns" - "definterface" - "deftype" - "defstruct") - eol))) + (clojure-ts--list-node-sym-match-p node clojure-ts--defun-symbols-regex)) (defconst clojure-ts--markdown-inline-sexp-nodes '("inline_link" "full_reference_link" "collapsed_reference_link" @@ -2512,6 +2520,126 @@ before DELIM-OPEN." map) "Keymap for `clojure-ts-mode'.") +;;; Completion + +(defconst clojure-ts--completion-query-defuns + (treesit-query-compile 'clojure + `((source + (list_lit + ((sym_lit) @sym + (:match ,clojure-ts--defun-symbols-regex @sym)) + :anchor [(comment) (meta_lit) (old_meta_lit)] :* + :anchor ((sym_lit) @defun-candidate))))) + "Query that matches top-level definitions.") + +(defconst clojure-ts--completion-defn-with-args-sym-regex + (rx bol + (or "defn" + "defn-" + "fn" + "fn*" + "defmacro" + "defmethod") + eol) + "Regexp that matches a symbol of definition with arguments vector.") + +(defconst clojure-ts--completion-let-like-sym-regex + (rx bol + (or "let" + "if-let" + "when-let" + "if-some" + "when-some" + "loop" + "with-open" + "dotimes" + "with-local-vars") + eol) + "Regexp that matches a symbol of let-like form.") + +(defconst clojure-ts--completion-locals-query + (treesit-query-compile 'clojure `((vec_lit (sym_lit) @local-candidate) + (map_lit (sym_lit) @local-candidate))) + "Query that matches a local binding symbol. + +Symbold must be a direct child of a vector or a map. This query covers +bindings vector as well as destructuring syntax.") + +(defconst clojure-ts--completion-annotations + (list 'defun-candidate " Definition" + 'local-candidate " Local variable") + "Property list of completion candidate type and annotation string.") + +(defun clojure-ts--completion-annotation-function (candidate) + "Return annotation for a completion CANDIDATE." + (thread-last minibuffer-completion-table + (alist-get candidate) + (plist-get clojure-ts--completion-annotations))) + +(defun clojure-ts--completion-defun-with-args-node-p (node) + "Return non-nil if NODE is a function definition with arguments." + (when-let* ((sym-name (clojure-ts--list-node-sym-text node))) + (string-match-p clojure-ts--completion-defn-with-args-sym-regex sym-name))) + +(defun clojure-ts--completion-fn-args-nodes () + "Return a list of captured nodes that represent function arguments. + +The function traverses the syntax tree upwards and returns nodes from +all functions along the way." + (let ((parent-defun (clojure-ts--parent-until #'clojure-ts--completion-defun-with-args-node-p)) + (captured-nodes)) + (while parent-defun + (when-let* ((args-vec (clojure-ts--node-child parent-defun "vec_lit"))) + (setq captured-nodes + (append captured-nodes + (treesit-query-capture args-vec clojure-ts--completion-locals-query)) + parent-defun (treesit-parent-until parent-defun + #'clojure-ts--completion-defun-with-args-node-p)))) + captured-nodes)) + +(defun clojure-ts--completion-let-like-node-p (node) + "Return non-nil if NODE is a let-like form." + (when-let* ((sym-name (clojure-ts--list-node-sym-text node))) + (string-match-p clojure-ts--completion-let-like-sym-regex sym-name))) + +(defun clojure-ts--completion-let-locals-nodes () + "Return a list of captured nodes that represent bindings in let forms. + +The function tranverses the syntax tree upwards and returns nodes from +all let bindings found along the way." + (let ((parent-let (clojure-ts--parent-until #'clojure-ts--completion-let-like-node-p)) + (captured-nodes)) + (while parent-let + (when-let* ((bindings-vec (clojure-ts--node-child parent-let "vec_lit"))) + (setq captured-nodes + (append captured-nodes + (treesit-query-capture bindings-vec clojure-ts--completion-locals-query)) + parent-let (treesit-parent-until parent-let + #'clojure-ts--completion-let-like-node-p)))) + captured-nodes)) + +(defun clojure-ts-completion-at-point-function () + "Return a completion table for the symbol around point." + (when-let* ((bounds (bounds-of-thing-at-point 'symbol)) + (source (treesit-buffer-root-node 'clojure)) + (nodes (append (treesit-query-capture source clojure-ts--completion-query-defuns) + (clojure-ts--completion-fn-args-nodes) + (clojure-ts--completion-let-locals-nodes)))) + (list (car bounds) + (cdr bounds) + (thread-last nodes + ;; Remove node at point + (seq-remove (lambda (item) (= (treesit-node-end (cdr item)) (point)))) + ;; Remove unwanted captured nodes + (seq-filter (lambda (item) + (not (member (car item) '(sym kwd))))) + ;; Produce alist of candidates + (seq-map (lambda (item) (cons (treesit-node-text (cdr item) t) (car item)))) + ;; Remove duplicated candidates + (seq-uniq)) + :exclusive 'no + :annotation-function #'clojure-ts--completion-annotation-function))) + (defvar clojure-ts-clojurescript-mode-map (let ((map (make-sparse-keymap))) (set-keymap-parent map clojure-ts-mode-map) @@ -2670,7 +2798,10 @@ REGEX-AVAILABLE." clojure-ts--imenu-settings) (when (boundp 'treesit-thing-settings) ;; Emacs 30+ - (setq-local treesit-thing-settings clojure-ts--thing-settings))) + (setq-local treesit-thing-settings clojure-ts--thing-settings)) + + (when clojure-ts-completion-enabled + (add-to-list 'completion-at-point-functions #'clojure-ts-completion-at-point-function))) ;;;###autoload (define-derived-mode clojure-ts-mode prog-mode "Clojure[TS]" diff --git a/test/clojure-ts-mode-completion.el b/test/clojure-ts-mode-completion.el new file mode 100644 index 0000000..1bc92ce --- /dev/null +++ b/test/clojure-ts-mode-completion.el @@ -0,0 +1,153 @@ +;;; clojure-ts-mode-completion.el --- clojure-ts-mode: completion tests -*- lexical-binding: t; -*- + +;; Copyright (C) 2025 Roman Rudakov + +;; Author: Roman Rudakov +;; Keywords: + +;; This program is free software; you can redistribute it and/or modify +;; it under the terms of the GNU General Public License as published by +;; the Free Software Foundation, either version 3 of the License, or +;; (at your option) any later version. + +;; This program is distributed in the hope that it will be useful, +;; but WITHOUT ANY WARRANTY; without even the implied warranty of +;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +;; GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public License +;; along with this program. If not, see . + +;;; Commentary: + +;; Completion is a unique `clojure-ts-mode' feature. + +;;; Code: + +(require 'clojure-ts-mode) +(require 'buttercup) +(require 'test-helper "test/test-helper") + +(describe "clojure-ts-complete-at-point-function" + ;; NOTE: This function returns unfiltered candidates, so prefix doesn't really + ;; matter here. + + (it "should complete global vars" + (with-clojure-ts-buffer-point " +(def foo :first) + +(def bar :second) + +(defn baz + [] + (println foo bar)) + +b|" + (expect (nth 2 (clojure-ts-completion-at-point-function)) + :to-equal '(("foo" . defun-candidate) + ("bar" . defun-candidate) + ("baz" . defun-candidate))))) + + (it "should complete function arguments" + (with-clojure-ts-buffer-point " +(def foo :first) + +(def bar :second) + +(defn baz + [username] + (println u|))" + (expect (nth 2 (clojure-ts-completion-at-point-function)) + :to-equal '(("foo" . defun-candidate) + ("bar" . defun-candidate) + ("baz" . defun-candidate) + ("username" . local-candidate))))) + + (it "should not complete function arguments outside of function" + (with-clojure-ts-buffer-point " +(def foo :first) + +(def bar :second) + +(defn baz + [username] + (println bar)) + +u|" + (expect (nth 2 (clojure-ts-completion-at-point-function)) + :to-equal '(("foo" . defun-candidate) + ("bar" . defun-candidate) + ("baz" . defun-candidate))))) + + (it "should complete destructured function arguments" + (with-clojure-ts-buffer-point " +(defn baz + [{:keys [username]}] + (println u|))" + (expect (nth 2 (clojure-ts-completion-at-point-function)) + :to-equal '(("baz" . defun-candidate) + ("username" . local-candidate)))) + + (with-clojure-ts-buffer-point " +(defn baz + [{:strs [username]}] + (println u|))" + (expect (nth 2 (clojure-ts-completion-at-point-function)) + :to-equal '(("baz" . defun-candidate) + ("username" . local-candidate)))) + + (with-clojure-ts-buffer-point " +(defn baz + [{:syms [username]}] + (println u|))" + (expect (nth 2 (clojure-ts-completion-at-point-function)) + :to-equal '(("baz" . defun-candidate) + ("username" . local-candidate)))) + + (with-clojure-ts-buffer-point " +(defn baz + [{username :name}] + (println u|))" + (expect (nth 2 (clojure-ts-completion-at-point-function)) + :to-equal '(("baz" . defun-candidate) + ("username" . local-candidate)))) + + (with-clojure-ts-buffer-point " +(defn baz + [[first-name last-name]] + (println f|))" + (expect (nth 2 (clojure-ts-completion-at-point-function)) + :to-equal '(("baz" . defun-candidate) + ("first-name" . local-candidate) + ("last-name" . local-candidate))))) + + (it "should complete vector bindings" + (with-clojure-ts-buffer-point " +(defn baz + [first-name] + (let [last-name \"Doe\" + address {:street \"Whatever\" :zip-code 2222} + {:keys [street zip-code]} address] + a|))" + (expect (nth 2 (clojure-ts-completion-at-point-function)) + :to-equal '(("baz" . defun-candidate) + ("first-name" . local-candidate) + ("last-name" . local-candidate) + ("address" . local-candidate) + ("street" . local-candidate) + ("zip-code" . local-candidate))))) + + (it "should not complete called function names" + (with-clojure-ts-buffer-point " +(defn baz + [first-name] + (let [full-name (str first-name \"Doe\")] + s|))" + ;; `str' should not be among the candidates. + (expect (nth 2 (clojure-ts-completion-at-point-function)) + :to-equal '(("baz" . defun-candidate) + ("first-name" . local-candidate) + ("full-name" . local-candidate)))))) + +(provide 'clojure-ts-mode-completion) +;;; clojure-ts-mode-completion.el ends here diff --git a/test/samples/completion.clj b/test/samples/completion.clj new file mode 100644 index 0000000..16b64de --- /dev/null +++ b/test/samples/completion.clj @@ -0,0 +1,56 @@ +(ns completion) + +(def my-var "Hello") +(def my-another-var "World") + +(defn- my-function + "This is a docstring." + [some-arg] + (let [to-print (str "Hello" some-arg)] + (println my-var my-another-var to-print))) + +(fn [anon-arg] + anon-arg) + +(def hello-string "Hello") + +(defn complete-example + "Docstring won't interfere with completion." + [arg1 arg2 & {:keys [destructured]}] + ;; Here only function args and globals should be completed. + (println arg1 arg2 destructured) + (let [foo "bar" ; comment + baz ^String hello + map-var {:users/usename "Roma"} + {:users/keys [username]} map-var + another-map {:address "Universe"} + {custom-address :address} another-map + bar :kwd] + ;; Here let bindings are available in addition to globals and function args. + (println arg1 foo map-var custom-address username) + (when-let [nested-var "Whatever"] + (with-open [output-stream (io/output-stream "some-file")] + (println foo + baz + hello + map-var + username + another-map + custom-address + bar) + ;; Here we should see everything + (output-stream nested-var output-stream another-map))) + ;; And here only let bindings, globals and function args again. + (println username))) + +(def vec-variable ["one" "two" "three"]) + +(let [[one two three] vec-variable] + (println one two three)) + +(defn nested-fn + [top-arg] + (filter (fn [item] + ;; Both arguments are available here. + (= item top-arg)) + [1 2 3 4 5])) From 226b3962f62af61dee38fc32325f48ab4cd9a803 Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Wed, 4 Jun 2025 07:25:57 +0300 Subject: [PATCH 361/379] Update the FAQ --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 6a86762..df4f7a1 100644 --- a/README.md +++ b/README.md @@ -596,7 +596,7 @@ and `clojure-mode` (this is very helpful when dealing with `derived-mode-p` chec ### What `clojure-mode` features are currently missing? -As of version 0.4.x, `clojure-ts-mode` provides almost all `clojure-mode` features. +As of version 0.5.x, `clojure-ts-mode` provides almost all `clojure-mode` features. Currently only a few refactoring commands are missing. ### Does `clojure-ts-mode` work with CIDER? @@ -622,7 +622,7 @@ Check out [this article](https://metaredux.com/posts/2024/02/19/cider-preliminar ### Does `clojure-ts-mode` work with `inf-clojure`? -Currently, there is an [open PR](https://github.com/clojure-emacs/inf-clojure/pull/215) adding support for inf-clojure. +Yes, it does. `inf-clojure` 3.3+ supports `clojure-ts-mode`. ### Why does `clojure-ts-mode` require Emacs 30? From 33e31bb3d5e639f48b7202ea80db96f646df5b78 Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Wed, 4 Jun 2025 07:28:20 +0300 Subject: [PATCH 362/379] Tweak wording --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index df4f7a1..727abb2 100644 --- a/README.md +++ b/README.md @@ -549,8 +549,8 @@ definitions and local bindings. This feature can be turned off by setting: (setopt clojure-ts-completion-enabled nil) ``` -Here's the short video illustrating the feature with built-in completion (it -should also work well with more advanced packages like company and corfu): +Here's the short video illustrating the feature with Emacs's built-in completion UI (it +should also work well with more advanced packages like `company` and `corfu`): https://github.com/user-attachments/assets/7c37179f-5a5d-424f-9bd6-9c8525f6b2f7 From bd08cd02612507c4012d078eb94ccd7aa9b567b1 Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Wed, 4 Jun 2025 08:02:30 +0300 Subject: [PATCH 363/379] Release 0.5 --- CHANGELOG.md | 2 ++ clojure-ts-mode.el | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1547127..09b696c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## main (unreleased) +## 0.5.0 (2025-06-04) + - [#96](https://github.com/clojure-emacs/clojure-ts-mode/pull/96): Highlight function name properly in `extend-protocol` form. - [#96](https://github.com/clojure-emacs/clojure-ts-mode/pull/96): Add support for extend-protocol forms to `clojure-ts-add-arity` refactoring command. diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index d828571..6930a54 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -7,7 +7,7 @@ ;; Maintainer: Bozhidar Batsov ;; URL: http://github.com/clojure-emacs/clojure-ts-mode ;; Keywords: languages clojure clojurescript lisp -;; Version: 0.5.0-snapshot +;; Version: 0.5.0 ;; Package-Requires: ((emacs "30.1")) ;; This file is not part of GNU Emacs. @@ -74,7 +74,7 @@ :link '(emacs-commentary-link :tag "Commentary" "clojure-mode")) (defconst clojure-ts-mode-version - "0.5.0-snapshot" + "0.5.0" "The current version of `clojure-ts-mode'.") (defcustom clojure-ts-comment-macro-font-lock-body nil From 8e57ba78e14c1b165c8cb2e619dfb3cbbf46d742 Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Wed, 4 Jun 2025 08:03:47 +0300 Subject: [PATCH 364/379] Bump the development version --- clojure-ts-mode.el | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index 6930a54..15dfef6 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -7,7 +7,7 @@ ;; Maintainer: Bozhidar Batsov ;; URL: http://github.com/clojure-emacs/clojure-ts-mode ;; Keywords: languages clojure clojurescript lisp -;; Version: 0.5.0 +;; Version: 0.6.0-snapshot ;; Package-Requires: ((emacs "30.1")) ;; This file is not part of GNU Emacs. @@ -74,7 +74,7 @@ :link '(emacs-commentary-link :tag "Commentary" "clojure-mode")) (defconst clojure-ts-mode-version - "0.5.0" + "0.6.0-snapshot" "The current version of `clojure-ts-mode'.") (defcustom clojure-ts-comment-macro-font-lock-body nil From 569ed6c9a3c6bb4f7dcc92090a6a8e582f63964e Mon Sep 17 00:00:00 2001 From: Roman Rudakov Date: Thu, 5 Jun 2025 16:50:09 +0200 Subject: [PATCH 365/379] [#109] Fix performance issue by pre-compiling Tree-sitter queries There is one trade-off: Markdown syntax won't be highlighted in docstrings of custom extra def forms. I think it could be solved, but it would make the code more complicated. --- CHANGELOG.md | 2 + README.md | 4 + clojure-ts-mode.el | 750 +++++++++++++++++-------------- doc/design.md | 6 + test/samples/extra_def_forms.clj | 6 + 5 files changed, 423 insertions(+), 345 deletions(-) create mode 100644 test/samples/extra_def_forms.clj diff --git a/CHANGELOG.md b/CHANGELOG.md index 09b696c..a6fd4b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## main (unreleased) +- [#109](https://github.com/clojure-emacs/clojure-ts-mode/issues/109): Improve performance by pre-compiling Tree-sitter queries. + ## 0.5.0 (2025-06-04) - [#96](https://github.com/clojure-emacs/clojure-ts-mode/pull/96): Highlight function name properly in `extend-protocol` form. diff --git a/README.md b/README.md index 727abb2..b742c76 100644 --- a/README.md +++ b/README.md @@ -591,6 +591,10 @@ and `clojure-mode` (this is very helpful when dealing with `derived-mode-p` chec - Navigation by sexp/lists might work differently on Emacs versions lower than 31. Starting with version 31, Emacs uses Tree-sitter 'things' settings, if available, to rebind some commands. +- If you set `clojure-ts-extra-def-forms`, `clojure-ts-mode` will highlight the + specified forms, including their docstrings, in a manner similar to Clojure's + `defn`. However, Markdown syntax will not be highlighted within these custom + docstrings. ## Frequently Asked Questions diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index 15dfef6..806723e 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -260,12 +260,6 @@ values like this: :safe #'booleanp :type 'boolean) -(defcustom clojure-ts-extra-def-forms nil - "List of forms that should be fontified the same way as defn." - :package-version '(clojure-ts-mode . "0.5") - :safe #'listp - :type '(repeat string)) - (defcustom clojure-ts-completion-enabled t "Enable built-in completion feature." :package-version '(clojure-ts-mode . "0.5") @@ -438,67 +432,65 @@ if a third argument (the value) is provided. (rx line-start (or "defprotocol" "definterface") line-end) "A regular expression matching a symbol used to define an interface.") -(defun clojure-ts--docstring-query (capture-symbol) - "Return a query that captures docstrings with CAPTURE-SYMBOL." - `(;; Captures docstrings in def - ((list_lit :anchor [(comment) (meta_lit) (old_meta_lit)] :* - :anchor (sym_lit) @_def_symbol - :anchor [(comment) (meta_lit) (old_meta_lit)] :* - ;; Variable name - :anchor (sym_lit) - :anchor [(comment) (meta_lit) (old_meta_lit)] :* - :anchor (str_lit (str_content) ,capture-symbol) @font-lock-doc-face - ;; The variable's value - :anchor (_)) - (:match ,clojure-ts-definition-docstring-symbols - @_def_symbol)) - ;; Captures docstrings in metadata of definitions - ((list_lit :anchor [(comment) (meta_lit) (old_meta_lit)] :* - :anchor (sym_lit) @_def_symbol - :anchor (comment) :* - :anchor (meta_lit - value: (map_lit - (kwd_lit) @_doc-keyword - :anchor (str_lit (str_content) ,capture-symbol) @font-lock-doc-face))) - ;; We're only supporting this on a fixed set of defining symbols - ;; Existing regexes don't encompass def and defn - ;; Naming another regex is very cumbersome. - (:match ,(clojure-ts-symbol-regexp - '("def" "defonce" "defn" "defn-" "defmacro" "ns" - "defmulti" "definterface" "defprotocol" - "deftest" "deftest-" - "deftype" "defrecord" "defstruct")) - @_def_symbol) - (:equal @_doc-keyword ":doc")) - ;; Captures docstrings defn, defmacro, ns, and things like that - ((list_lit :anchor [(comment) (meta_lit) (old_meta_lit)] :* - :anchor (sym_lit) @_def_symbol - :anchor [(comment) (meta_lit) (old_meta_lit)] :* - ;; Function_name - :anchor (sym_lit) - :anchor [(comment) (meta_lit) (old_meta_lit)] :* - :anchor (str_lit (str_content) ,capture-symbol) @font-lock-doc-face) - (:match ,clojure-ts-function-docstring-symbols - @_def_symbol)) - ((list_lit :anchor [(comment) (meta_lit) (old_meta_lit)] :* - :anchor (sym_lit) @_def_symbol - :anchor [(comment) (meta_lit) (old_meta_lit)] :* - ;; Function_name - :anchor (sym_lit) - :anchor [(comment) (meta_lit) (old_meta_lit)] :* - :anchor (str_lit (str_content) ,capture-symbol) @font-lock-doc-face) - (:match ,(clojure-ts-symbol-regexp clojure-ts-extra-def-forms) - @_def_symbol)) - ;; Captures docstrings in defprotcol, definterface - ((list_lit :anchor [(comment) (meta_lit) (old_meta_lit)] :* - :anchor (sym_lit) @_def_symbol - (list_lit :anchor (sym_lit) (vec_lit) :* - (str_lit (str_content) ,capture-symbol) @font-lock-doc-face) - :*) - (:match ,clojure-ts--interface-def-symbol-regexp @_def_symbol)))) +(defun clojure-ts--docstring-query (capture-symbol &optional capture-quotes) + "Return a query that captures docstrings with CAPTURE-SYMBOL. + +By default produced query captures only strings content, if optional +CAPTURE-QUOTES argument is non-nil, then the entire string literals are +captured including quotes." + (let ((quotes-symbol (if capture-quotes + capture-symbol + '@_ignore))) + `(;; Captures docstrings in def + ((list_lit :anchor [(comment) (meta_lit) (old_meta_lit)] :* + :anchor (sym_lit) @_def_symbol + :anchor [(comment) (meta_lit) (old_meta_lit)] :* + ;; Variable name + :anchor (sym_lit) + :anchor [(comment) (meta_lit) (old_meta_lit)] :* + :anchor (str_lit (str_content) ,capture-symbol) ,quotes-symbol + ;; The variable's value + :anchor (_)) + (:match ,clojure-ts-definition-docstring-symbols + @_def_symbol)) + ;; Captures docstrings in metadata of definitions + ((list_lit :anchor [(comment) (meta_lit) (old_meta_lit)] :* + :anchor (sym_lit) @_def_symbol + :anchor (comment) :* + :anchor (meta_lit + value: (map_lit + (kwd_lit) @_doc-keyword + :anchor (str_lit (str_content) ,capture-symbol) ,quotes-symbol))) + ;; We're only supporting this on a fixed set of defining symbols + ;; Existing regexes don't encompass def and defn + ;; Naming another regex is very cumbersome. + (:match ,(clojure-ts-symbol-regexp + '("def" "defonce" "defn" "defn-" "defmacro" "ns" + "defmulti" "definterface" "defprotocol" + "deftest" "deftest-" + "deftype" "defrecord" "defstruct")) + @_def_symbol) + (:equal @_doc-keyword ":doc")) + ;; Captures docstrings defn, defmacro, ns, and things like that + ((list_lit :anchor [(comment) (meta_lit) (old_meta_lit)] :* + :anchor (sym_lit) @_def_symbol + :anchor [(comment) (meta_lit) (old_meta_lit)] :* + ;; Function_name + :anchor (sym_lit) + :anchor [(comment) (meta_lit) (old_meta_lit)] :* + :anchor (str_lit (str_content) ,capture-symbol) ,quotes-symbol) + (:match ,clojure-ts-function-docstring-symbols + @_def_symbol)) + ;; Captures docstrings in defprotcol, definterface + ((list_lit :anchor [(comment) (meta_lit) (old_meta_lit)] :* + :anchor (sym_lit) @_def_symbol + (list_lit :anchor (sym_lit) (vec_lit) :* + (str_lit (str_content) ,capture-symbol) ,quotes-symbol) + :*) + (:match ,clojure-ts--interface-def-symbol-regexp @_def_symbol))))) (defconst clojure-ts--match-docstring-query - (treesit-query-compile 'clojure (clojure-ts--docstring-query '@font-lock-doc-face)) + (treesit-query-compile 'clojure (clojure-ts--docstring-query '@font-lock-doc-face t)) "Precompiled query that matches a Clojure docstring.") (defun clojure-ts--treesit-range-settings (use-markdown-inline use-regex) @@ -553,6 +545,341 @@ and end of the NODE, so we ignore them." 'font-lock-string-face override)))) +(defconst clojure-ts--clojure-font-lock-queries + (treesit-font-lock-rules + :feature 'string + :language 'clojure + '((str_lit open: _ @font-lock-string-face + (str_content) @clojure-ts--fontify-string + close: _ @font-lock-string-face) + (regex_lit) @font-lock-regexp-face) + + :feature 'regex + :language 'clojure + :override t + '((regex_lit marker: _ @font-lock-property-face)) + + :feature 'number + :language 'clojure + '((num_lit) @font-lock-number-face) + + :feature 'constant + :language 'clojure + '([(bool_lit) (nil_lit)] @font-lock-constant-face) + + :feature 'char + :language 'clojure + '((char_lit) @clojure-ts-character-face) + + :feature 'keyword + :language 'clojure + '((kwd_ns) @font-lock-type-face + (kwd_name) @clojure-ts-keyword-face + (kwd_lit + marker: _ @clojure-ts-keyword-face + delimiter: _ :? @default)) + + ;; Highlight as built-in only if there is no namespace or namespace is + ;; `clojure.core'. + :feature 'builtin + :language 'clojure + `(((list_lit :anchor [(comment) (meta_lit) (old_meta_lit)] :* + :anchor (sym_lit !namespace name: (sym_name) @font-lock-keyword-face)) + (:match ,clojure-ts--builtin-symbol-regexp @font-lock-keyword-face)) + ((list_lit :anchor [(comment) (meta_lit) (old_meta_lit)] :* + :anchor (sym_lit namespace: ((sym_ns) @ns + (:equal "clojure.core" @ns)) + name: (sym_name) @font-lock-keyword-face)) + (:match ,clojure-ts--builtin-symbol-regexp @font-lock-keyword-face)) + ((anon_fn_lit :anchor [(comment) (meta_lit) (old_meta_lit)] :* + :anchor (sym_lit !namespace name: (sym_name) @font-lock-keyword-face)) + (:match ,clojure-ts--builtin-symbol-regexp @font-lock-keyword-face)) + ((anon_fn_lit :anchor [(comment) (meta_lit) (old_meta_lit)] :* + :anchor (sym_lit namespace: ((sym_ns) @ns + (:equal "clojure.core" @ns)) + name: (sym_name) @font-lock-keyword-face)) + (:match ,clojure-ts--builtin-symbol-regexp @font-lock-keyword-face)) + ((sym_name) @font-lock-builtin-face + (:match ,clojure-ts--builtin-dynamic-var-regexp @font-lock-builtin-face))) + + ;; Any function calls, not built-ins. + ;; This can give false positives (macros, quoted lists, namespace imports) + ;; but is a level 4 feature and never enabled by default. + :feature 'function + :language 'clojure + '((list_lit :anchor (sym_lit (sym_name) @font-lock-function-call-face))) + + :feature 'symbol + :language 'clojure + '((sym_ns) @font-lock-type-face) + + ;; How does this work for defns nested in other forms, not at the top level? + ;; Should I match against the source node to only hit the top level? Can that be expressed? + ;; What about valid usages like `(let [closed 1] (defn +closed [n] (+ n closed)))'?? + ;; No wonder the tree-sitter-clojure grammar only touches syntax, and not semantics + :feature 'definition ;; defn and defn like macros + :language 'clojure + `(((list_lit :anchor [(comment) (meta_lit) (old_meta_lit)] :* + :anchor (sym_lit (sym_name) @font-lock-keyword-face) + :anchor [(comment) (meta_lit) (old_meta_lit)] :* + :anchor (sym_lit (sym_name) @font-lock-function-name-face)) + (:match ,(rx-to-string + `(seq bol + (or + "fn" + "defn" + "defn-" + "defmulti" + "defmethod" + "deftest" + "deftest-" + "defmacro" + "definline" + "defonce") + eol)) + @font-lock-keyword-face)) + ((anon_fn_lit + marker: "#" @font-lock-property-face)) + ;; Methods implementation + ((list_lit + :anchor [(comment) (meta_lit) (old_meta_lit)] :* + :anchor ((sym_lit name: (sym_name) @def) + ((:match ,(rx-to-string + `(seq bol + (or + "defrecord" + "definterface" + "deftype" + "defprotocol") + eol)) + @def))) + :anchor [(comment) (meta_lit) (old_meta_lit)] :* + :anchor (sym_lit (sym_name) @font-lock-type-face) + (list_lit + (sym_lit name: (sym_name) @font-lock-function-name-face)))) + ((list_lit + ((sym_lit name: (sym_name) @def) + ((:match ,(rx-to-string + `(seq bol + (or "reify" + "extend-protocol" + "extend-type") + eol)) + @def))) + (list_lit + (sym_lit name: (sym_name) @font-lock-function-name-face)))) + ;; letfn + ((list_lit + ((sym_lit name: (sym_name) @symbol) + ((:equal "letfn" @symbol))) + (vec_lit + (list_lit + (sym_lit name: (sym_name) @font-lock-function-name-face)))))) + + :feature 'variable ;; def, defonce + :language 'clojure + `(((list_lit :anchor [(comment) (meta_lit) (old_meta_lit)] :* + :anchor (sym_lit (sym_name) @font-lock-keyword-face) + :anchor [(comment) (meta_lit) (old_meta_lit)] :* + :anchor (sym_lit (sym_name) @font-lock-variable-name-face)) + (:match ,clojure-ts--variable-definition-symbol-regexp @font-lock-keyword-face))) + + ;; Can we support declarations in the namespace form? + :feature 'type + :language 'clojure + `(;; Type Declarations + ((list_lit :anchor (sym_lit (sym_name) @def) + :anchor (sym_lit (sym_name) @font-lock-type-face)) + (:match ,clojure-ts--typedef-symbol-regexp @def)) + ;; Type Hints + (meta_lit + marker: "^" @font-lock-operator-face + value: (sym_lit (sym_name) @font-lock-type-face)) + (old_meta_lit + marker: "#^" @font-lock-operator-face + value: (sym_lit (sym_name) @font-lock-type-face)) + ;; Highlight namespace + ((list_lit :anchor (sym_lit (sym_name) @def) + :anchor (sym_lit (sym_name) @font-lock-type-face)) + (:equal "ns" @def))) + + :feature 'metadata + :language 'clojure + :override t + `((meta_lit + marker: "^" @font-lock-operator-face + value: (kwd_lit (kwd_name) @clojure-ts-keyword-face)) + (old_meta_lit + marker: "#^" @font-lock-operator-face + value: (kwd_lit (kwd_name) @clojure-ts-keyword-face))) + + :feature 'tagged-literals + :language 'clojure + :override t + '((tagged_or_ctor_lit marker: "#" @font-lock-preprocessor-face + tag: (sym_lit) @font-lock-preprocessor-face)) + + :feature 'doc + :language 'clojure + :override t + (clojure-ts--docstring-query '@font-lock-doc-face t) + + :feature 'quote + :language 'clojure + '((quoting_lit + marker: _ @font-lock-delimiter-face) + (var_quoting_lit + marker: _ @font-lock-delimiter-face) + (syn_quoting_lit + marker: _ @font-lock-delimiter-face) + (unquoting_lit + marker: _ @font-lock-delimiter-face) + (unquote_splicing_lit + marker: _ @font-lock-delimiter-face) + (var_quoting_lit + marker: _ @font-lock-delimiter-face)) + + :feature 'bracket + :language 'clojure + '((["(" ")" "[" "]" "{" "}"]) @font-lock-bracket-face + (set_lit :anchor "#" @font-lock-bracket-face)) + + :feature 'comment + :language 'clojure + :override t + `((comment) @font-lock-comment-face + (dis_expr + marker: "#_" @font-lock-comment-delimiter-face + meta: (meta_lit) :* @font-lock-comment-face + value: _ @font-lock-comment-face) + (,(append + '(list_lit :anchor (sym_lit) @font-lock-comment-delimiter-face) + (when clojure-ts-comment-macro-font-lock-body + '(_ :* @font-lock-comment-face))) + (:match "^\\(\\(clojure.core/\\)?comment\\)$" @font-lock-comment-delimiter-face))) + + :feature 'deref ;; not part of clojure-mode, but a cool idea? + :language 'clojure + '((derefing_lit + marker: "@" @font-lock-warning-face)))) + +(defvar clojure-ts--clojure-extra-queries nil + "Pre-compiled Tree-sitter queries produced from `clojure-ts-extra-def-forms'.") + +(defun clojure-ts--compute-extra-def-queries (syms) + "Comute font lock rules for extra def forms. + +If SYMS are not provided, return nil. If SYMS are provided, this +function returns compiled font lock rules that should be assigned to +`clojure-ts--clojure-extra-queries' variable. + +This function is called when the `clojure-ts-extra-def-forms' variable +is customized using setopt or the Emacs customization interface. It is +also called when file-local variables are updated. This ensures that +updated indentation rules are always precalculated." + (when syms + (treesit-font-lock-rules + :feature 'definition + :language 'clojure + `(((list_lit :anchor [(comment) (meta_lit) (old_meta_lit)] :* + :anchor (sym_lit (sym_name) @font-lock-keyword-face) + :anchor [(comment) (meta_lit) (old_meta_lit)] :* + :anchor (sym_lit (sym_name) @font-lock-function-name-face)) + (:match ,(clojure-ts-symbol-regexp syms) + @font-lock-keyword-face))) + + ;; NOTE: Here we also define queries to fontify docstrings in custom extra + ;; defn forms, but Markdown syntax won't work here, because it's not a part + ;; of range settings. + :feature 'doc + :language 'clojure + :override t + `(((list_lit :anchor [(comment) (meta_lit) (old_meta_lit)] :* + :anchor (sym_lit) @_def_symbol + :anchor [(comment) (meta_lit) (old_meta_lit)] :* + ;; Function_name + :anchor (sym_lit) + :anchor [(comment) (meta_lit) (old_meta_lit)] :* + :anchor (str_lit) @font-lock-doc-face) + (:match ,(clojure-ts-symbol-regexp syms) + @_def_symbol)))))) + +(defun clojure-ts--set-extra-def-queries (symbol value) + "Setter function for `clojure-ts-extra-def-forms' variable. + +Sets SYMBOL's top-level default value to VALUE and updates the +`clojure-ts--clojure-extra-queries' in all `clojure-ts-mode' +buffers, if any exist. + +NOTE: This function is not meant to be called directly." + (set-default-toplevel-value symbol value) + ;; Update value in every `clojure-ts-mode' buffer. + (let ((new-value (clojure-ts--compute-extra-def-queries value))) + (dolist (buf (buffer-list)) + (when (buffer-local-boundp 'clojure-ts--clojure-extra-queries buf) + (setq clojure-ts--clojure-extra-queries new-value))))) + +(defcustom clojure-ts-extra-def-forms nil + "List of forms that should be fontified the same way as defn." + :package-version '(clojure-ts-mode . "0.5") + :safe #'listp + :type '(repeat string) + :set #'clojure-ts--set-extra-def-queries) + +(defconst clojure-ts--markdown-font-lock-queries + (treesit-font-lock-rules + :feature 'doc + :language 'markdown-inline + :override 'prepend + `([((image_description) @link) + ((link_destination) @font-lock-constant-face) + ((code_span) @font-lock-constant-face) + ((emphasis) @underline) + ((strong_emphasis) @bold) + (inline_link (link_text) @link) + (inline_link (link_destination) @font-lock-constant-face) + (shortcut_link (link_text) @link)]))) + +(defconst clojure-ts--regex-font-lock-queries + ;; Queries are adapted from + ;; https://github.com/tree-sitter/tree-sitter-regex/blob/v0.24.3/queries/highlights.scm. + (treesit-font-lock-rules + :feature 'regex + :language 'regex + :override t + '((["(" + ")" + "(?" + "(?:" + "(?<" + "(?P<" + "(?P=" + ">" + "[" + "]" + "{" + "}" + "[:" + ":]"] + @font-lock-regexp-grouping-construct) + (["*" + "+" + "?" + "|" + "=" + "!"] + @font-lock-property-name-face) + ((group_name) @font-lock-variable-name-face) + ((count_quantifier + [(decimal_digits) @font-lock-number-face + "," @font-lock-delimiter-face])) + ((flags) @font-lock-constant-face) + ((character_class + ["^" @font-lock-escape-face + (class_range "-" @font-lock-escape-face)])) + ((identity_escape) @font-lock-builtin-face) + ([(start_assertion) (end_assertion)] @font-lock-constant-face)))) + (defun clojure-ts--font-lock-settings (markdown-available regex-available) "Return font lock settings suitable for use in `treesit-font-lock-settings'. @@ -561,282 +888,12 @@ with the markdown-inline grammar. When REGEX-AVAILABLE is non-nil, includes rules for highlighting regex literals with regex grammar." - (append - (treesit-font-lock-rules - :feature 'string - :language 'clojure - '((str_lit open: _ @font-lock-string-face - (str_content) @clojure-ts--fontify-string - close: _ @font-lock-string-face) - (regex_lit) @font-lock-regexp-face) - - :feature 'regex - :language 'clojure - :override t - '((regex_lit marker: _ @font-lock-property-face)) - - :feature 'number - :language 'clojure - '((num_lit) @font-lock-number-face) - - :feature 'constant - :language 'clojure - '([(bool_lit) (nil_lit)] @font-lock-constant-face) - - :feature 'char - :language 'clojure - '((char_lit) @clojure-ts-character-face) - - :feature 'keyword - :language 'clojure - '((kwd_ns) @font-lock-type-face - (kwd_name) @clojure-ts-keyword-face - (kwd_lit - marker: _ @clojure-ts-keyword-face - delimiter: _ :? @default)) - - ;; Highlight as built-in only if there is no namespace or namespace is - ;; `clojure.core'. - :feature 'builtin - :language 'clojure - `(((list_lit :anchor [(comment) (meta_lit) (old_meta_lit)] :* - :anchor (sym_lit !namespace name: (sym_name) @font-lock-keyword-face)) - (:match ,clojure-ts--builtin-symbol-regexp @font-lock-keyword-face)) - ((list_lit :anchor [(comment) (meta_lit) (old_meta_lit)] :* - :anchor (sym_lit namespace: ((sym_ns) @ns - (:equal "clojure.core" @ns)) - name: (sym_name) @font-lock-keyword-face)) - (:match ,clojure-ts--builtin-symbol-regexp @font-lock-keyword-face)) - ((anon_fn_lit :anchor [(comment) (meta_lit) (old_meta_lit)] :* - :anchor (sym_lit !namespace name: (sym_name) @font-lock-keyword-face)) - (:match ,clojure-ts--builtin-symbol-regexp @font-lock-keyword-face)) - ((anon_fn_lit :anchor [(comment) (meta_lit) (old_meta_lit)] :* - :anchor (sym_lit namespace: ((sym_ns) @ns - (:equal "clojure.core" @ns)) - name: (sym_name) @font-lock-keyword-face)) - (:match ,clojure-ts--builtin-symbol-regexp @font-lock-keyword-face)) - ((sym_name) @font-lock-builtin-face - (:match ,clojure-ts--builtin-dynamic-var-regexp @font-lock-builtin-face))) - - ;; Any function calls, not built-ins. - ;; This can give false positives (macros, quoted lists, namespace imports) - ;; but is a level 4 feature and never enabled by default. - :feature 'function - :language 'clojure - '((list_lit :anchor (sym_lit (sym_name) @font-lock-function-call-face))) - - :feature 'symbol - :language 'clojure - '((sym_ns) @font-lock-type-face) - - ;; How does this work for defns nested in other forms, not at the top level? - ;; Should I match against the source node to only hit the top level? Can that be expressed? - ;; What about valid usages like `(let [closed 1] (defn +closed [n] (+ n closed)))'?? - ;; No wonder the tree-sitter-clojure grammar only touches syntax, and not semantics - :feature 'definition ;; defn and defn like macros - :language 'clojure - `(((list_lit :anchor [(comment) (meta_lit) (old_meta_lit)] :* - :anchor (sym_lit (sym_name) @font-lock-keyword-face) - :anchor [(comment) (meta_lit) (old_meta_lit)] :* - :anchor (sym_lit (sym_name) @font-lock-function-name-face)) - (:match ,(rx-to-string - `(seq bol - (or - "fn" - "defn" - "defn-" - "defmulti" - "defmethod" - "deftest" - "deftest-" - "defmacro" - "definline" - "defonce") - eol)) - @font-lock-keyword-face)) - ((list_lit :anchor [(comment) (meta_lit) (old_meta_lit)] :* - :anchor (sym_lit (sym_name) @font-lock-keyword-face) - :anchor [(comment) (meta_lit) (old_meta_lit)] :* - :anchor (sym_lit (sym_name) @font-lock-function-name-face)) - (:match ,(clojure-ts-symbol-regexp clojure-ts-extra-def-forms) - @font-lock-keyword-face)) - ((anon_fn_lit - marker: "#" @font-lock-property-face)) - ;; Methods implementation - ((list_lit - :anchor [(comment) (meta_lit) (old_meta_lit)] :* - :anchor ((sym_lit name: (sym_name) @def) - ((:match ,(rx-to-string - `(seq bol - (or - "defrecord" - "definterface" - "deftype" - "defprotocol") - eol)) - @def))) - :anchor [(comment) (meta_lit) (old_meta_lit)] :* - :anchor (sym_lit (sym_name) @font-lock-type-face) - (list_lit - (sym_lit name: (sym_name) @font-lock-function-name-face)))) - ((list_lit - ((sym_lit name: (sym_name) @def) - ((:match ,(rx-to-string - `(seq bol - (or "reify" - "extend-protocol" - "extend-type") - eol)) - @def))) - (list_lit - (sym_lit name: (sym_name) @font-lock-function-name-face)))) - ;; letfn - ((list_lit - ((sym_lit name: (sym_name) @symbol) - ((:equal "letfn" @symbol))) - (vec_lit - (list_lit - (sym_lit name: (sym_name) @font-lock-function-name-face)))))) - - :feature 'variable ;; def, defonce - :language 'clojure - `(((list_lit :anchor [(comment) (meta_lit) (old_meta_lit)] :* - :anchor (sym_lit (sym_name) @font-lock-keyword-face) - :anchor [(comment) (meta_lit) (old_meta_lit)] :* - :anchor (sym_lit (sym_name) @font-lock-variable-name-face)) - (:match ,clojure-ts--variable-definition-symbol-regexp @font-lock-keyword-face))) - - ;; Can we support declarations in the namespace form? - :feature 'type - :language 'clojure - `(;; Type Declarations - ((list_lit :anchor (sym_lit (sym_name) @def) - :anchor (sym_lit (sym_name) @font-lock-type-face)) - (:match ,clojure-ts--typedef-symbol-regexp @def)) - ;; Type Hints - (meta_lit - marker: "^" @font-lock-operator-face - value: (sym_lit (sym_name) @font-lock-type-face)) - (old_meta_lit - marker: "#^" @font-lock-operator-face - value: (sym_lit (sym_name) @font-lock-type-face)) - ;; Highlight namespace - ((list_lit :anchor (sym_lit (sym_name) @def) - :anchor (sym_lit (sym_name) @font-lock-type-face)) - (:equal "ns" @def))) - - :feature 'metadata - :language 'clojure - :override t - `((meta_lit - marker: "^" @font-lock-operator-face - value: (kwd_lit (kwd_name) @clojure-ts-keyword-face)) - (old_meta_lit - marker: "#^" @font-lock-operator-face - value: (kwd_lit (kwd_name) @clojure-ts-keyword-face))) - - :feature 'tagged-literals - :language 'clojure - :override t - '((tagged_or_ctor_lit marker: "#" @font-lock-preprocessor-face - tag: (sym_lit) @font-lock-preprocessor-face)) - - :feature 'doc - :language 'clojure - :override t - (clojure-ts--docstring-query '@font-lock-doc-face)) - - (when markdown-available - (treesit-font-lock-rules - :feature 'doc - :language 'markdown-inline - :override 'prepend - `([((image_description) @link) - ((link_destination) @font-lock-constant-face) - ((code_span) @font-lock-constant-face) - ((emphasis) @underline) - ((strong_emphasis) @bold) - (inline_link (link_text) @link) - (inline_link (link_destination) @font-lock-constant-face) - (shortcut_link (link_text) @link)]))) - - (when regex-available - ;; Queries are adapted from - ;; https://github.com/tree-sitter/tree-sitter-regex/blob/v0.24.3/queries/highlights.scm. - (treesit-font-lock-rules - :feature 'regex - :language 'regex - :override t - '((["(" - ")" - "(?" - "(?:" - "(?<" - "(?P<" - "(?P=" - ">" - "[" - "]" - "{" - "}" - "[:" - ":]"] @font-lock-regexp-grouping-construct) - (["*" - "+" - "?" - "|" - "=" - "!"] @font-lock-property-name-face) - ((group_name) @font-lock-variable-name-face) - ((count_quantifier - [(decimal_digits) @font-lock-number-face - "," @font-lock-delimiter-face])) - ((flags) @font-lock-constant-face) - ((character_class - ["^" @font-lock-escape-face - (class_range "-" @font-lock-escape-face)])) - ((identity_escape) @font-lock-builtin-face) - ([(start_assertion) (end_assertion)] @font-lock-constant-face)))) - - (treesit-font-lock-rules - :feature 'quote - :language 'clojure - '((quoting_lit - marker: _ @font-lock-delimiter-face) - (var_quoting_lit - marker: _ @font-lock-delimiter-face) - (syn_quoting_lit - marker: _ @font-lock-delimiter-face) - (unquoting_lit - marker: _ @font-lock-delimiter-face) - (unquote_splicing_lit - marker: _ @font-lock-delimiter-face) - (var_quoting_lit - marker: _ @font-lock-delimiter-face)) - - :feature 'bracket - :language 'clojure - '((["(" ")" "[" "]" "{" "}"]) @font-lock-bracket-face - (set_lit :anchor "#" @font-lock-bracket-face)) - - :feature 'comment - :language 'clojure - :override t - `((comment) @font-lock-comment-face - (dis_expr - marker: "#_" @font-lock-comment-delimiter-face - meta: (meta_lit) :* @font-lock-comment-face - value: _ @font-lock-comment-face) - (,(append - '(list_lit :anchor (sym_lit) @font-lock-comment-delimiter-face) - (when clojure-ts-comment-macro-font-lock-body - '(_ :* @font-lock-comment-face))) - (:match "^\\(\\(clojure.core/\\)?comment\\)$" @font-lock-comment-delimiter-face))) - - :feature 'deref ;; not part of clojure-mode, but a cool idea? - :language 'clojure - '((derefing_lit - marker: "@" @font-lock-warning-face))))) + (append clojure-ts--clojure-font-lock-queries + clojure-ts--clojure-extra-queries + (when markdown-available + clojure-ts--markdown-font-lock-queries) + (when regex-available + clojure-ts--regex-font-lock-queries))) ;; Node predicates @@ -1515,7 +1572,7 @@ It is simply `clojure-ts-docstring-fill-prefix-width' number of spaces." (defun clojure-ts--fill-paragraph (&optional justify) "Like `fill-paragraph', but can handler Clojure docstrings. If JUSTIFY is non-nil, justify as well as fill the paragraph." - (let ((current-node (treesit-node-at (point) 'clojure))) + (let ((current-node (treesit-node-at (point) 'clojure t))) (if (clojure-ts--match-docstring nil current-node nil) (let ((fill-column (or clojure-ts-docstring-fill-column fill-column)) (fill-prefix (clojure-ts--docstring-fill-prefix)) @@ -2839,12 +2896,15 @@ REGEX-AVAILABLE." (setq clojure-ts--semantic-indent-rules-cache (clojure-ts--compute-semantic-indentation-rules-cache clojure-ts-semantic-indent-rules)) - ;; If indentation rules are set in `.dir-locals.el', it is advisable to - ;; recalculate the buffer-local value whenever the value changes. + ;; If indentation rules or extra def forms are set in `.dir-locals.el', it + ;; is advisable to recalculate the buffer-local value whenever the value + ;; changes. (add-hook 'hack-local-variables-hook (lambda () (setq clojure-ts--semantic-indent-rules-cache - (clojure-ts--compute-semantic-indentation-rules-cache clojure-ts-semantic-indent-rules))) + (clojure-ts--compute-semantic-indentation-rules-cache clojure-ts-semantic-indent-rules) + clojure-ts--clojure-extra-queries + (clojure-ts--compute-extra-def-queries clojure-ts-extra-def-forms))) 0 t) diff --git a/doc/design.md b/doc/design.md index e1d6b05..3425619 100644 --- a/doc/design.md +++ b/doc/design.md @@ -207,6 +207,12 @@ metadata nodes) does not have a namespace and matches a regex stored in the `clojure-ts--builtin-symbol-regexp` variable. The matched symbol is fontified using `font-lock-keyword-face`. +> [!IMPORTANT] +> +> Compiling queries at runtime is very expensive; therefore, it should be +> avoided as much as possible. Ideally, all queries should be pre-compiled and +> stored as `defconst` constants. + ### Embedded parsers The Clojure grammar in `clojure-ts-mode` is a main or "host" grammar. Emacs diff --git a/test/samples/extra_def_forms.clj b/test/samples/extra_def_forms.clj new file mode 100644 index 0000000..6ecb3a3 --- /dev/null +++ b/test/samples/extra_def_forms.clj @@ -0,0 +1,6 @@ +(ns extra-def-forms) + +(defelem file-upload + "Creates a file upload input." + [name] + (input-field "file" name nil)) From 01e6a0ba3eff56b7da2330c99b1ef63536a17ad9 Mon Sep 17 00:00:00 2001 From: Roman Rudakov Date: Tue, 10 Jun 2025 15:45:56 +0200 Subject: [PATCH 366/379] Set clojure-ts-completion-at-point-function locally We should use add-hook instead of add-to-list in order to set it only for clojure-ts-mode buffers. --- CHANGELOG.md | 5 ++++- clojure-ts-mode.el | 3 ++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a6fd4b3..0dd9206 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ ## main (unreleased) - [#109](https://github.com/clojure-emacs/clojure-ts-mode/issues/109): Improve performance by pre-compiling Tree-sitter queries. +- [#111](https://github.com/clojure-emacs/clojure-ts-mode/pull/111): Set `clojure-ts-completion-at-point-function` only for `clojure-ts-mode` + buffers. ## 0.5.0 (2025-06-04) @@ -21,7 +23,8 @@ allows highlighting JS syntax in ClojureScript `js*` forms. - [#104](https://github.com/clojure-emacs/clojure-ts-mode/pull/104): Introduce the `clojure-ts-extra-def-forms` customization option to specify additional `defn`-like forms that should be fontified. -- Introduce completion feature and `clojure-ts-completion-enabled` customization. +- [#108](https://github.com/clojure-emacs/clojure-ts-mode/pull/108): Introduce completion feature and `clojure-ts-completion-enabled` + customization. ## 0.4.0 (2025-05-15) diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index 806723e..a513648 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -2858,7 +2858,8 @@ REGEX-AVAILABLE." (setq-local treesit-thing-settings clojure-ts--thing-settings)) (when clojure-ts-completion-enabled - (add-to-list 'completion-at-point-functions #'clojure-ts-completion-at-point-function))) + (add-hook 'completion-at-point-functions + #'clojure-ts-completion-at-point-function nil 'local))) ;;;###autoload (define-derived-mode clojure-ts-mode prog-mode "Clojure[TS]" From 66cdee12e950f147e523849bcaf74c403ce06bad Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Tue, 17 Jun 2025 09:07:18 +0300 Subject: [PATCH 367/379] Release 0.5.1 --- CHANGELOG.md | 5 +++-- clojure-ts-mode.el | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0dd9206..3e762aa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,9 +2,10 @@ ## main (unreleased) +## 0.5.1 (2025-06-17) + - [#109](https://github.com/clojure-emacs/clojure-ts-mode/issues/109): Improve performance by pre-compiling Tree-sitter queries. -- [#111](https://github.com/clojure-emacs/clojure-ts-mode/pull/111): Set `clojure-ts-completion-at-point-function` only for `clojure-ts-mode` - buffers. +- [#111](https://github.com/clojure-emacs/clojure-ts-mode/pull/111): Set `clojure-ts-completion-at-point-function` only for `clojure-ts-mode` buffers. ## 0.5.0 (2025-06-04) diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index a513648..5a90ce1 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -7,7 +7,7 @@ ;; Maintainer: Bozhidar Batsov ;; URL: http://github.com/clojure-emacs/clojure-ts-mode ;; Keywords: languages clojure clojurescript lisp -;; Version: 0.6.0-snapshot +;; Version: 0.5.1 ;; Package-Requires: ((emacs "30.1")) ;; This file is not part of GNU Emacs. @@ -74,7 +74,7 @@ :link '(emacs-commentary-link :tag "Commentary" "clojure-mode")) (defconst clojure-ts-mode-version - "0.6.0-snapshot" + "0.5.1" "The current version of `clojure-ts-mode'.") (defcustom clojure-ts-comment-macro-font-lock-body nil From 710d5ff2c8036e9f610433bda0c08baf96f2e1c2 Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Tue, 17 Jun 2025 09:08:13 +0300 Subject: [PATCH 368/379] Bump the development version --- clojure-ts-mode.el | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index 5a90ce1..a513648 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -7,7 +7,7 @@ ;; Maintainer: Bozhidar Batsov ;; URL: http://github.com/clojure-emacs/clojure-ts-mode ;; Keywords: languages clojure clojurescript lisp -;; Version: 0.5.1 +;; Version: 0.6.0-snapshot ;; Package-Requires: ((emacs "30.1")) ;; This file is not part of GNU Emacs. @@ -74,7 +74,7 @@ :link '(emacs-commentary-link :tag "Commentary" "clojure-mode")) (defconst clojure-ts-mode-version - "0.5.1" + "0.6.0-snapshot" "The current version of `clojure-ts-mode'.") (defcustom clojure-ts-comment-macro-font-lock-body nil From 1b5723de13041cd29c6875a905f36ce6392c8adb Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Tue, 17 Jun 2025 09:19:03 +0300 Subject: [PATCH 369/379] Add a "Contributing" section to the README --- README.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/README.md b/README.md index b742c76..42133d6 100644 --- a/README.md +++ b/README.md @@ -636,6 +636,27 @@ simple - the initial Tree-sitter support in Emacs 29 had quite a few issues and we felt it's better to nudge most people interested in using it to Emacs 30, which fixed a lot of the problems. +## Contributing + +We welcome contributions of any kind! + +If you're not familiar with Tree-sitter, a good place to start is our +[design documentation](doc/design.md), which explains how Tree-sitter +works in Emacs in broad strokes and covers some of the design +decisions we've made a long the way. + +We're using [Eldev](https://github.com/emacs-eldev/eldev) as our build tool, so you'll +have to install it. We also provide a simple [Makefile](Makefile) with targets invoking Eldev. You +only need to know a couple of them: + +```shell +make lint + +make test +``` + +The process of releasing a new version of `clojure-ts-mode` is documented [here](doc/release-process). + ## License Copyright © 2022-2025 Danny Freeman, Bozhidar Batsov and [contributors][]. From adb1c2559e62cec29ada0033b388856c98b48d66 Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Tue, 17 Jun 2025 10:47:52 +0300 Subject: [PATCH 370/379] Rename the CI GHA workflow --- .github/workflows/{lint-emacs.yml => ci.yml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename .github/workflows/{lint-emacs.yml => ci.yml} (100%) diff --git a/.github/workflows/lint-emacs.yml b/.github/workflows/ci.yml similarity index 100% rename from .github/workflows/lint-emacs.yml rename to .github/workflows/ci.yml From bda9baf4dcee14bf7892bcaf7856ef4aafeb5ab9 Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Tue, 17 Jun 2025 10:49:49 +0300 Subject: [PATCH 371/379] Add Emacs 30.1 to the CI matrix --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2483fb9..53af842 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,7 +13,7 @@ jobs: strategy: matrix: - emacs_version: ['snapshot'] + emacs_version: ['30.1', 'snapshot'] steps: - name: Set up Emacs @@ -59,7 +59,7 @@ jobs: strategy: matrix: - emacs_version: ['snapshot'] + emacs_version: ['30.1', 'snapshot'] steps: - name: Set up Emacs From 71ddb1d579d8feb02f4f501cd8176d2383f99a5d Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Tue, 17 Jun 2025 10:52:08 +0300 Subject: [PATCH 372/379] Update README CI badge --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 42133d6..767ca69 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![MELPA Stable][melpa-stable-badge]][melpa-stable-package] [![MELPA][melpa-badge]][melpa-package] [![License GPL 3][badge-license]][copying] -[![Lint Status](https://github.com/clojure-emacs/clojure-ts-mode/actions/workflows/lint-emacs.yml/badge.svg)](https://github.com/clojure-emacs/clojure-ts-mode/actions/workflows/lint-emacs.yml) +[![CI Status](https://github.com/clojure-emacs/clojure-ts-mode/actions/workflows/ci.yml/badge.svg)](https://github.com/clojure-emacs/clojure-ts-mode/actions/workflows/ci.yml) # Clojure Tree-sitter Mode From 05a4e1e44285ce89904afdd9f6cc64699b6b8db0 Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Tue, 17 Jun 2025 11:46:17 +0300 Subject: [PATCH 373/379] Remove special handling for jank and clojuredart modes This aligns clojure-ts-mode with some changes I did recently in clojure-mode. --- clojure-ts-mode.el | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index a513648..6c2c1e3 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -269,7 +269,9 @@ values like this: (defvar clojure-ts-mode-remappings '((clojure-mode . clojure-ts-mode) (clojurescript-mode . clojure-ts-clojurescript-mode) - (clojurec-mode . clojure-ts-clojurec-mode)) + (clojurec-mode . clojure-ts-clojurec-mode) + (clojuredart-mode . clojure-ts-clojuredart-mode) + (jank-mode . clojure-ts-jank-mode)) "Alist of entries to `major-mode-remap-defaults'. See also `clojure-ts-activate-mode-remappings' and @@ -2970,11 +2972,6 @@ REGEX-AVAILABLE." (clojure-ts--add-config-for-mode 'c++-ts-mode) (treesit-major-mode-setup))) -(defun clojure-ts--register-novel-modes () - "Set up Clojure modes not present in progenitor clojure-mode.el." - (add-to-list 'auto-mode-alist '("\\.cljd\\'" . clojure-ts-clojuredart-mode)) - (add-to-list 'auto-mode-alist '("\\.jank\\'" . clojure-ts-jank-mode))) - (defun clojure-ts-activate-mode-remappings () "Remap all `clojure-mode' file-specified modes to use `clojure-ts-mode'. @@ -2995,10 +2992,8 @@ Useful if you want to switch to the `clojure-mode's mode mappings." (if (treesit-available-p) ;; Redirect clojure-mode to clojure-ts-mode if clojure-mode is present (if (require 'clojure-mode nil 'noerror) - (progn - (when clojure-ts-auto-remap - (clojure-ts-activate-mode-remappings)) - (clojure-ts--register-novel-modes)) + (when clojure-ts-auto-remap + (clojure-ts-activate-mode-remappings)) ;; When Clojure-mode is not present, setup auto-modes ourselves (progn ;; Regular clojure/edn files @@ -3007,13 +3002,14 @@ Useful if you want to switch to the `clojure-mode's mode mappings." '("\\.\\(clj\\|dtm\\|edn\\)\\'" . clojure-ts-mode)) (add-to-list 'auto-mode-alist '("\\.cljs\\'" . clojure-ts-clojurescript-mode)) (add-to-list 'auto-mode-alist '("\\.cljc\\'" . clojure-ts-clojurec-mode)) + (add-to-list 'auto-mode-alist '("\\.cljd\\'" . clojure-ts-clojuredart-mode)) + (add-to-list 'auto-mode-alist '("\\.jank\\'" . clojure-ts-jank-mode)) ;; boot build scripts are Clojure source files (add-to-list 'auto-mode-alist '("\\(?:build\\|profile\\)\\.boot\\'" . clojure-ts-mode)) ;; babashka scripts are Clojure source files (add-to-list 'interpreter-mode-alist '("bb" . clojure-ts-mode)) ;; nbb scripts are ClojureScript source files - (add-to-list 'interpreter-mode-alist '("nbb" . clojure-ts-clojurescript-mode)) - (clojure-ts--register-novel-modes))) + (add-to-list 'interpreter-mode-alist '("nbb" . clojure-ts-clojurescript-mode)))) (message "Clojure TS Mode will not be activated as Tree-sitter support is missing.")) (defvar clojure-ts--find-ns-query From a9e2ca729e4f743698cc1125944126fa2d2170d5 Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Tue, 17 Jun 2025 11:51:38 +0300 Subject: [PATCH 374/379] Add clojure-ts-joker-mode --- CHANGELOG.md | 2 ++ clojure-ts-mode.el | 15 ++++++++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e762aa..369b28e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ ## main (unreleased) +- Add a dedicated mode for editing Joker code. (`clojure-ts-joker-mode`) + ## 0.5.1 (2025-06-17) - [#109](https://github.com/clojure-emacs/clojure-ts-mode/issues/109): Improve performance by pre-compiling Tree-sitter queries. diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index 6c2c1e3..ca3f9a7 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -271,7 +271,8 @@ values like this: (clojurescript-mode . clojure-ts-clojurescript-mode) (clojurec-mode . clojure-ts-clojurec-mode) (clojuredart-mode . clojure-ts-clojuredart-mode) - (jank-mode . clojure-ts-jank-mode)) + (jank-mode . clojure-ts-jank-mode) + (joker-mode . clojure-ts-joker-mode)) "Alist of entries to `major-mode-remap-defaults'. See also `clojure-ts-activate-mode-remappings' and @@ -2719,6 +2720,11 @@ all let bindings found along the way." (set-keymap-parent map clojure-ts-mode-map) map)) +(defvar clojure-ts-joker-mode-map + (let ((map (make-sparse-keymap))) + (set-keymap-parent map clojure-ts-mode-map) + map)) + (defun clojure-ts-mode-display-version () "Display the current `clojure-mode-version' in the minibuffer." (interactive) @@ -2972,6 +2978,12 @@ REGEX-AVAILABLE." (clojure-ts--add-config-for-mode 'c++-ts-mode) (treesit-major-mode-setup))) +;;;###autoload +(define-derived-mode clojure-ts-joker-mode clojure-ts-mode "Joker[TS]" + "Major mode for editing Joker code. + +\\{clojure-ts-joker-mode-map}") + (defun clojure-ts-activate-mode-remappings () "Remap all `clojure-mode' file-specified modes to use `clojure-ts-mode'. @@ -3004,6 +3016,7 @@ Useful if you want to switch to the `clojure-mode's mode mappings." (add-to-list 'auto-mode-alist '("\\.cljc\\'" . clojure-ts-clojurec-mode)) (add-to-list 'auto-mode-alist '("\\.cljd\\'" . clojure-ts-clojuredart-mode)) (add-to-list 'auto-mode-alist '("\\.jank\\'" . clojure-ts-jank-mode)) + (add-to-list 'auto-mode-alist '("\\.joke\\'" . clojure-ts-joker-mode)) ;; boot build scripts are Clojure source files (add-to-list 'auto-mode-alist '("\\(?:build\\|profile\\)\\.boot\\'" . clojure-ts-mode)) ;; babashka scripts are Clojure source files From 372ed8d795cc6adcb0ea162862f99f7d458cca2f Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Tue, 17 Jun 2025 11:56:15 +0300 Subject: [PATCH 375/379] [Docs] Add a couple of resources --- doc/design.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/doc/design.md b/doc/design.md index 3425619..7555a38 100644 --- a/doc/design.md +++ b/doc/design.md @@ -1,15 +1,17 @@ # Design of clojure-ts-mode -This document is still a work in progress. +**Note:** This document is still a work in progress. Clojure-ts-mode is based on the tree-sitter-clojure grammar. If you want to contribute to clojure-ts-mode, it is recommend that you familiarize yourself with how Tree-sitter works. The official documentation is a great place to start: -These guides for Emacs Tree-sitter development are also useful +These guides for Emacs Tree-sitter development are also useful: - - `Developing major modes with tree-sitter` (From the Emacs 29+ Manual, `C-h i`, search for `tree-sitter`) +- [How to Get Started with Tree-sitter](https://www.masteringemacs.org/article/how-to-get-started-tree-sitter) +- [Emacs 30 Tree-sitter changes](https://archive.casouri.cc/note/2024/emacs-30-tree-sitter/) In short: From 2ecde0c4255b3093d0f2fc6967835e2e34cf029f Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Tue, 17 Jun 2025 11:59:11 +0300 Subject: [PATCH 376/379] [Docs] Small tweaks to the design document --- doc/design.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/doc/design.md b/doc/design.md index 7555a38..fb5d50b 100644 --- a/doc/design.md +++ b/doc/design.md @@ -22,22 +22,22 @@ In short: ## Important Definitions -- Parser: A dynamic library compiled from C source code that is generated by the Tree-sitter tool. A parser reads source code for a particular language and produces a syntax tree. -- Grammar: The rules that define how a parser will create the syntax tree for a language. The grammar is written in JavaScript. Tree-sitter tooling consumes the grammar as input and outputs C source (which can be compiled into a parser) -- Syntax Tree: a tree data structure comprised of syntax nodes that represents some source code text. - - Concrete Syntax Tree: Syntax trees that contain nodes for every token in the source code, including things likes brackets and parentheses. Tree-sitter creates Concrete Syntax Trees. - - Abstract Syntax Tree: A syntax tree with less important details removed. An AST may contain a node for a list, but not individual parentheses. Tree-sitter does not create Abstract Syntax Trees. -- Syntax Node: A node in a syntax tree. It represents some subset of a source code text. Each node has a type, defined by the grammar used to produce it. Some common node types represent language constructs like strings, integers, operators. - - Named Syntax Node: A node that can be identified by a name given to it in the Tree-sitter Grammar. In clojure-ts-mode, `list_lit` is a named node for lists. - - Anonymous Syntax Node: A node that cannot be identified by a name. In the Grammar these are identified by simple strings, not by complex Grammar rules. In clojure-ts-mode, `"("` and `")"` are anonymous nodes. -- Font Locking: What Emacs calls "Syntax Highlighting". +- **Parser**: A dynamic library compiled from C source code that is generated by the Tree-sitter tool. A parser reads source code for a particular language and produces a syntax tree. +- **Grammar**: The rules that define how a parser will create the syntax tree for a language. The grammar is written in JavaScript. Tree-sitter tooling consumes the grammar as input and outputs C source (which can be compiled into a parser) +- **Syntax Tree**: a tree data structure comprised of syntax nodes that represents some source code text. + - **Concrete Syntax Tree**: Syntax trees that contain nodes for every token in the source code, including things likes brackets and parentheses. Tree-sitter creates Concrete Syntax Trees. + - **Abstract Syntax Tree**: A syntax tree with less important details removed. An AST may contain a node for a list, but not individual parentheses. Tree-sitter does not create Abstract Syntax Trees. +- **Syntax Node**: A node in a syntax tree. It represents some subset of a source code text. Each node has a type, defined by the grammar used to produce it. Some common node types represent language constructs like strings, integers, operators. + - **Named Syntax Node**: A node that can be identified by a name given to it in the Tree-sitter Grammar. In clojure-ts-mode, `list_lit` is a named node for lists. + - **Anonymous Syntax Node**: A node that cannot be identified by a name. In the Grammar these are identified by simple strings, not by complex Grammar rules. In clojure-ts-mode, `"("` and `")"` are anonymous nodes. +- **Font Locking**: The Emacs terminology for "syntax highlighting". ## tree-sitter-clojure `clojure-ts-mode` uses the experimental version tree-sitter-clojure grammar, which can be found at . The -`clojure-ts-mode` grammar provides very basic, low level nodes that try to match +grammar provides very basic, low level nodes that try to match Clojure's very light syntax. There are nodes to represent: @@ -86,8 +86,8 @@ will produce a parse tree like so ``` Although it's somewhat closer to how Clojure treats metadata itself, in the -context of a text editor it creates some problems, which were discussed [here](https://github.com/sogaiu/tree-sitter-clojure/issues/65). To -name a few: +context of a text editor it creates some problems, which were discussed +[here](https://github.com/sogaiu/tree-sitter-clojure/issues/65). To name a few: - `forward-sexp` command would skip both, metadata and the node it's attached to. Called from an opening paren it would signal an error "No more sexp to From ce9474874c7a74d4b029ad6942222bdfd990eaa2 Mon Sep 17 00:00:00 2001 From: Bozhidar Batsov Date: Tue, 17 Jun 2025 12:19:58 +0300 Subject: [PATCH 377/379] [Docs] Reflow a couple of paragraphs with long lines in them --- doc/design.md | 54 +++++++++++++++++++++++++++++++++++---------------- 1 file changed, 37 insertions(+), 17 deletions(-) diff --git a/doc/design.md b/doc/design.md index fb5d50b..c5616a8 100644 --- a/doc/design.md +++ b/doc/design.md @@ -4,8 +4,10 @@ Clojure-ts-mode is based on the tree-sitter-clojure grammar. -If you want to contribute to clojure-ts-mode, it is recommend that you familiarize yourself with how Tree-sitter works. -The official documentation is a great place to start: +If you want to contribute to clojure-ts-mode, it is recommend that you +familiarize yourself with how Tree-sitter works. The official documentation is +a great place to start: + These guides for Emacs Tree-sitter development are also useful: - @@ -110,12 +112,23 @@ offsets. ### Clojure Syntax, not Clojure Semantics -An important observation that anyone familiar with popular Tree-sitter grammars may have picked up on is that there are no nodes representing things like functions, macros, types, and other semantic concepts. -Representing the semantics of Clojure in a Tree-sitter grammar is much more difficult than traditional languages that do not use macros heavily like Clojure and other lisps. -To understand what an expression represents in Clojure source code requires macro-expansion of the source code. -Macro-expansion requires a runtime, and Tree-sitter does not have access to a Clojure runtime and will never have access to a Clojure runtime. -Additionally Tree-sitter never looks back on what it has parsed, only forward, considering what is directly ahead of it. So even if it could identify a macro like `myspecialdef` it would forget about it as soon as it moved passed the declaring `defmacro` node. -Another way to think about this: Tree-sitter is designed to be fast and good-enough for tooling to implement syntax highlighting, indentation, and other editing conveniences. It is not meant for interpreting and execution. +An important observation that anyone familiar with popular Tree-sitter grammars +may have picked up on is that there are no nodes representing things like +functions, macros, types, and other semantic concepts. Representing the +semantics of Clojure in a Tree-sitter grammar is much more difficult than +traditional languages that do not use macros heavily like Clojure and other +Lisps. + +To understand what an expression represents in Clojure source code +requires macro-expansion of the source code. Macro-expansion requires a +runtime, and Tree-sitter does not have access to a Clojure runtime and will +never have access to a Clojure runtime. Additionally Tree-sitter never looks +back on what it has parsed, only forward, considering what is directly ahead of +it. So even if it could identify a macro like `myspecialdef` it would forget +about it as soon as it moved passed the declaring `defmacro` node. Another way +to think about this: Tree-sitter is designed to be fast and good-enough for +tooling to implement syntax highlighting, indentation, and other editing +conveniences. _It is not meant for interpreting and execution._ #### Example 1: False Negative Function Classification @@ -128,8 +141,11 @@ Consider the following macro (defn2 dog [] "bark") ``` -This macro lets the caller define a function, but a hypothetical tree-sitter-clojure semantic grammar might just see a function call where a variable dog is passed as an argument. -How should Tree-sitter know that `dog` should be highlighted like function? It would have to evaluate the `defn2` macro to understand that. +This macro lets the caller define a function, but a hypothetical +tree-sitter-clojure semantic grammar might just see a function call where a +variable dog is passed as an argument. How should Tree-sitter know that `dog` +should be highlighted like function? It would have to evaluate the `defn2` macro +to understand that. #### Example 2: False Positive Function Classification @@ -154,13 +170,17 @@ How is Tree-sitter supposed to understand that `(defn foo [] 2)` of the expressi #### Syntax and Semantics: Conclusions -While these examples are silly, they illustrate the issue with encoding semantics into the tree-sitter-clojure grammar. -If we tried to make the grammar understand functions, macros, types, and other semantic elements it will end up giving false positives and negatives in the parse tree. -While this is an inevitability for simple static analysis of Clojure code, tree-sitter-clojure chooses to avoid making these kinds of mistakes all-together. -Instead, it is up to the emacs-lisp code and other consumers of the tree-sitter-clojure grammar to make decisions about the semantic meaning of clojure-code. - -There are some pros and cons of this decision for tree-sitter-clojure to only consider syntax and not semantics. -Some of the (non-exhaustive) upsides: +While these examples are silly, they illustrate the issue with encoding +semantics into the tree-sitter-clojure grammar. If we tried to make the grammar +understand functions, macros, types, and other semantic elements it will end up +giving false positives and negatives in the parse tree. While this is an +inevitability for simple static analysis of Clojure code, tree-sitter-clojure +chooses to avoid making these kinds of mistakes all-together. Instead, it is up +to the emacs-lisp code and other consumers of the tree-sitter-clojure grammar to +make decisions about the semantic meaning of clojure-code. + +There are some pros and cons of this decision for tree-sitter-clojure to only +consider syntax and not semantics. Some of the (non-exhaustive) upsides: - No semantic false positives or negatives in the parse tree. - Simple grammar to maintain with less nodes and rules From 43f7a675c246f99ad4ae896be61511a537e8b773 Mon Sep 17 00:00:00 2001 From: Roman Rudakov Date: Tue, 17 Jun 2025 11:57:18 +0200 Subject: [PATCH 378/379] [CI] Fix compilation for Emacs-30 --- CHANGELOG.md | 3 ++- clojure-ts-mode.el | 18 +++++++++--------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 369b28e..859ff73 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,8 @@ ## main (unreleased) -- Add a dedicated mode for editing Joker code. (`clojure-ts-joker-mode`) +- Add a dedicated mode for editing Joker code. (`clojure-ts-joker-mode`). +- [#113](https://github.com/clojure-emacs/clojure-ts-mode/pull/113): Fix non-working refactoring commands for Emacs-30. ## 0.5.1 (2025-06-17) diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index ca3f9a7..9677f47 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -1129,8 +1129,8 @@ See `clojure-ts--standard-definition-node-name' for the implementation used.") (defun clojure-ts--outline-level () "Return the current level of the outline heading at point." - (let* ((node (treesit-outline--at-point)) - (node-text (treesit-node-text node))) + (when-let* ((node (treesit-thing-at (point) #'clojure-ts--outline-predicate)) + (node-text (treesit-node-text node))) (string-match ";;\\(;+\\) " node-text) (- (match-end 1) (match-beginning 1)))) @@ -1873,7 +1873,7 @@ between BEG and END." ;; We have to disable it here to avoid endless recursion. (clojure-ts-align-forms-automatically nil)) (save-excursion - (indent-region beg end) + (indent-region beg (marker-position end)) (dolist (sexp sexps-to-align) ;; After reindenting a node, all other nodes in the `sexps-to-align' ;; list become outdated, so we need to fetch updated nodes for every @@ -1893,7 +1893,7 @@ between BEG and END." ;; After every iteration we have to re-indent the s-expression, ;; otherwise some can be indented inconsistently. (indent-region (marker-position (plist-get sexp :beg-marker)) - (plist-get sexp :end-marker)))) + (marker-position (plist-get sexp :end-marker))))) ;; If `clojure-ts-align-separator' is used, `align-region' leaves trailing ;; whitespaces on empty lines. (delete-trailing-whitespace beg (marker-position end))))) @@ -2114,7 +2114,7 @@ With universal argument \\[universal-argument], fully unwinds thread." (clojure-ts--pop-out-of-threading) (clojure-ts--fix-sexp-whitespace) (setq n 0)))) - (indent-region beg end) + (indent-region (marker-position beg) (marker-position end)) (delete-trailing-whitespace beg end))) (user-error "No threading form to unwind at point"))) @@ -2191,7 +2191,7 @@ cannot be found." (clojure-ts--thread-first)) ((string-match-p (rx bol (* "some") "->>" eol) sym) (clojure-ts--thread-last))) - (indent-region beg end) + (indent-region (marker-position beg) (marker-position end)) (delete-trailing-whitespace beg end) t) (when called-by-user-p @@ -2383,7 +2383,7 @@ type, etc. See `treesit-thing-settings' for more details." (string= parent-def-sym "extend-protocol")) (clojure-ts--add-arity-reify-internal fn-node)) (t (clojure-ts--add-arity-internal fn-node))) - (indent-region beg-marker end-marker)) + (indent-region (marker-position beg-marker) (marker-position end-marker))) (user-error "No suitable form to add an arity at point"))) (defun clojure-ts-cycle-keyword-string () @@ -2496,7 +2496,7 @@ before DELIM-OPEN." (when (member cond-sym '("if" "if-not")) (forward-sexp 2) (transpose-sexps 1)) - (indent-region beg end-marker))) + (indent-region beg (marker-position end-marker)))) (user-error "No conditional expression found"))) (defun clojure-ts-cycle-not () @@ -2512,7 +2512,7 @@ before DELIM-OPEN." (clojure-ts--raise-sexp) (insert-pair 1 ?\( ?\)) (insert "not ")) - (indent-region beg end-marker) + (indent-region beg (marker-position end-marker)) ;; `save-excursion' doesn't work well when point is at the opening ;; paren. (goto-char pos)) From 7c33f297788f21f150eb5a0a47fa62baf1e0e5ec Mon Sep 17 00:00:00 2001 From: Roman Rudakov Date: Fri, 20 Jun 2025 16:51:17 +0200 Subject: [PATCH 379/379] Extend built-in completion Complete keywords and local bindings in `for` and `doseq` forms. --- CHANGELOG.md | 2 ++ clojure-ts-mode.el | 26 +++++++++----- test/clojure-ts-mode-completion.el | 55 ++++++++++++++++++++++++++++-- test/samples/completion.clj | 15 ++++++++ 4 files changed, 86 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 859ff73..96251e7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ - Add a dedicated mode for editing Joker code. (`clojure-ts-joker-mode`). - [#113](https://github.com/clojure-emacs/clojure-ts-mode/pull/113): Fix non-working refactoring commands for Emacs-30. +- [#114](https://github.com/clojure-emacs/clojure-ts-mode/pull/114): Extend built-in completion to complete keywords and local bindings in + `for` and `doseq` forms. ## 0.5.1 (2025-06-17) diff --git a/clojure-ts-mode.el b/clojure-ts-mode.el index 9677f47..4802d9e 100644 --- a/clojure-ts-mode.el +++ b/clojure-ts-mode.el @@ -2592,6 +2592,10 @@ before DELIM-OPEN." :anchor ((sym_lit) @defun-candidate))))) "Query that matches top-level definitions.") +(defconst clojure-ts--completion-query-keywords + (treesit-query-compile 'clojure '((kwd_lit) @keyword-candidate)) + "Query that matches any Clojure keyword.") + (defconst clojure-ts--completion-defn-with-args-sym-regex (rx bol (or "defn" @@ -2613,7 +2617,9 @@ before DELIM-OPEN." "loop" "with-open" "dotimes" - "with-local-vars") + "with-local-vars" + "for" + "doseq") eol) "Regexp that matches a symbol of let-like form.") @@ -2627,7 +2633,8 @@ bindings vector as well as destructuring syntax.") (defconst clojure-ts--completion-annotations (list 'defun-candidate " Definition" - 'local-candidate " Local variable") + 'local-candidate " Local variable" + 'keyword-candidate " Keyword") "Property list of completion candidate type and annotation string.") (defun clojure-ts--completion-annotation-function (candidate) @@ -2652,9 +2659,9 @@ all functions along the way." (when-let* ((args-vec (clojure-ts--node-child parent-defun "vec_lit"))) (setq captured-nodes (append captured-nodes - (treesit-query-capture args-vec clojure-ts--completion-locals-query)) - parent-defun (treesit-parent-until parent-defun - #'clojure-ts--completion-defun-with-args-node-p)))) + (treesit-query-capture args-vec clojure-ts--completion-locals-query)))) + (setq parent-defun (treesit-parent-until parent-defun + #'clojure-ts--completion-defun-with-args-node-p))) captured-nodes)) (defun clojure-ts--completion-let-like-node-p (node) @@ -2673,9 +2680,9 @@ all let bindings found along the way." (when-let* ((bindings-vec (clojure-ts--node-child parent-let "vec_lit"))) (setq captured-nodes (append captured-nodes - (treesit-query-capture bindings-vec clojure-ts--completion-locals-query)) - parent-let (treesit-parent-until parent-let - #'clojure-ts--completion-let-like-node-p)))) + (treesit-query-capture bindings-vec clojure-ts--completion-locals-query)))) + (setq parent-let (treesit-parent-until parent-let + #'clojure-ts--completion-let-like-node-p))) captured-nodes)) (defun clojure-ts-completion-at-point-function () @@ -2683,6 +2690,7 @@ all let bindings found along the way." (when-let* ((bounds (bounds-of-thing-at-point 'symbol)) (source (treesit-buffer-root-node 'clojure)) (nodes (append (treesit-query-capture source clojure-ts--completion-query-defuns) + (treesit-query-capture source clojure-ts--completion-query-keywords) (clojure-ts--completion-fn-args-nodes) (clojure-ts--completion-let-locals-nodes)))) (list (car bounds) @@ -2692,7 +2700,7 @@ all let bindings found along the way." (seq-remove (lambda (item) (= (treesit-node-end (cdr item)) (point)))) ;; Remove unwanted captured nodes (seq-filter (lambda (item) - (not (member (car item) '(sym kwd))))) + (not (equal (car item) 'sym)))) ;; Produce alist of candidates (seq-map (lambda (item) (cons (treesit-node-text (cdr item) t) (car item)))) ;; Remove duplicated candidates diff --git a/test/clojure-ts-mode-completion.el b/test/clojure-ts-mode-completion.el index 1bc92ce..ffa30df 100644 --- a/test/clojure-ts-mode-completion.el +++ b/test/clojure-ts-mode-completion.el @@ -46,7 +46,9 @@ b|" (expect (nth 2 (clojure-ts-completion-at-point-function)) :to-equal '(("foo" . defun-candidate) ("bar" . defun-candidate) - ("baz" . defun-candidate))))) + ("baz" . defun-candidate) + (":first" . keyword-candidate) + (":second" . keyword-candidate))))) (it "should complete function arguments" (with-clojure-ts-buffer-point " @@ -61,6 +63,8 @@ b|" :to-equal '(("foo" . defun-candidate) ("bar" . defun-candidate) ("baz" . defun-candidate) + (":first" . keyword-candidate) + (":second" . keyword-candidate) ("username" . local-candidate))))) (it "should not complete function arguments outside of function" @@ -77,7 +81,9 @@ u|" (expect (nth 2 (clojure-ts-completion-at-point-function)) :to-equal '(("foo" . defun-candidate) ("bar" . defun-candidate) - ("baz" . defun-candidate))))) + ("baz" . defun-candidate) + (":first" . keyword-candidate) + (":second" . keyword-candidate))))) (it "should complete destructured function arguments" (with-clojure-ts-buffer-point " @@ -86,6 +92,7 @@ u|" (println u|))" (expect (nth 2 (clojure-ts-completion-at-point-function)) :to-equal '(("baz" . defun-candidate) + (":keys" . keyword-candidate) ("username" . local-candidate)))) (with-clojure-ts-buffer-point " @@ -94,6 +101,7 @@ u|" (println u|))" (expect (nth 2 (clojure-ts-completion-at-point-function)) :to-equal '(("baz" . defun-candidate) + (":strs" . keyword-candidate) ("username" . local-candidate)))) (with-clojure-ts-buffer-point " @@ -102,6 +110,7 @@ u|" (println u|))" (expect (nth 2 (clojure-ts-completion-at-point-function)) :to-equal '(("baz" . defun-candidate) + (":syms" . keyword-candidate) ("username" . local-candidate)))) (with-clojure-ts-buffer-point " @@ -110,6 +119,7 @@ u|" (println u|))" (expect (nth 2 (clojure-ts-completion-at-point-function)) :to-equal '(("baz" . defun-candidate) + (":name" . keyword-candidate) ("username" . local-candidate)))) (with-clojure-ts-buffer-point " @@ -131,6 +141,9 @@ u|" a|))" (expect (nth 2 (clojure-ts-completion-at-point-function)) :to-equal '(("baz" . defun-candidate) + (":street" . keyword-candidate) + (":zip-code" . keyword-candidate) + (":keys" . keyword-candidate) ("first-name" . local-candidate) ("last-name" . local-candidate) ("address" . local-candidate) @@ -147,7 +160,43 @@ u|" (expect (nth 2 (clojure-ts-completion-at-point-function)) :to-equal '(("baz" . defun-candidate) ("first-name" . local-candidate) - ("full-name" . local-candidate)))))) + ("full-name" . local-candidate))))) + + (it "should complete any keyword" + (with-clojure-ts-buffer-point " +(defn baz + [first-name] + (let [last-name \"Doe\" + address {:street \"Whatever\" :zip-code 2222} + {:keys [street zip-code]} address] + (println street zip-code))) + +:|" + (expect (nth 2 (clojure-ts-completion-at-point-function)) + :to-equal '(("baz" . defun-candidate) + (":street" . keyword-candidate) + (":zip-code" . keyword-candidate) + (":keys" . keyword-candidate))))) + + (it "should complete locals of for bindings" + (with-clojure-ts-buffer-point " +(for [digit [\"one\" \"two\" \"three\"] + :let [prefixed-digit (str \"hello-\" digit)]] + (println d|))" + (expect (nth 2 (clojure-ts-completion-at-point-function)) + :to-equal '((":let" . keyword-candidate) + ("digit" . local-candidate) + ("prefixed-digit" . local-candidate))))) + + (it "should complete locals of doseq bindings" + (with-clojure-ts-buffer-point " +(doseq [digit [\"one\" \"two\" \"three\"] + :let [prefixed-digit (str \"hello-\" digit)]] + (println d|))" + (expect (nth 2 (clojure-ts-completion-at-point-function)) + :to-equal '((":let" . keyword-candidate) + ("digit" . local-candidate) + ("prefixed-digit" . local-candidate)))))) (provide 'clojure-ts-mode-completion) ;;; clojure-ts-mode-completion.el ends here diff --git a/test/samples/completion.clj b/test/samples/completion.clj index 16b64de..7207d7f 100644 --- a/test/samples/completion.clj +++ b/test/samples/completion.clj @@ -54,3 +54,18 @@ ;; Both arguments are available here. (= item top-arg)) [1 2 3 4 5])) + +;; Works for top-level bindings and for nested `:let` bindings. +(for [digit vec-variable + :let [prefixed-digit (str "hello-" digit)]] + (println prefixed-digit digit)) + +;; Same for `doseq` +(doseq [word vec-variable + :let [suffixed-word (str "hello-" word)]] + (println suffixed-word word)) + +;; Can complete any keyword from the buffer +(do :users/usename + :address + :kwd) pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy