diff --git a/CHANGELOG.md b/CHANGELOG.md index 33dd1b81..eb1cf822 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## master (unreleased) +### New features + +* [#567](https://github.com/clojure-emacs/clojure-mode/issues/567): Add new commands `clojure-toggle-ignore`, `clojure-toggle-ignore-surrounding-form`, and `clojure-toggle-defun` for inserting/deleting #_ ignore forms. + ### Changes * [#571](https://github.com/clojure-emacs/clojure-mode/issues/571): Remove `project.el` integration. diff --git a/clojure-mode.el b/clojure-mode.el index 53f0bb6c..96da810b 100644 --- a/clojure-mode.el +++ b/clojure-mode.el @@ -246,6 +246,10 @@ Out-of-the box `clojure-mode' understands lein, boot, gradle, (define-key map (kbd "s b") #'clojure-let-backward-slurp-sexp) (define-key map (kbd "C-a") #'clojure-add-arity) (define-key map (kbd "a") #'clojure-add-arity) + (define-key map (kbd "-") #'clojure-toggle-ignore) + (define-key map (kbd "C--") #'clojure-toggle-ignore) + (define-key map (kbd "_") #'clojure-toggle-ignore-surrounding-form) + (define-key map (kbd "C-_") #'clojure-toggle-ignore-surrounding-form) map) "Keymap for Clojure refactoring commands.") (fset 'clojure-refactor-map clojure-refactor-map) @@ -264,6 +268,8 @@ Out-of-the box `clojure-mode' understands lein, boot, gradle, ["Cycle if, if-not" clojure-cycle-if] ["Cycle when, when-not" clojure-cycle-when] ["Cycle not" clojure-cycle-not] + ["Toggle #_ ignore form" clojure-toggle-ignore] + ["Toggle #_ ignore of surrounding form" clojure-toggle-ignore-surrounding-form] ["Add function arity" clojure-add-arity] ("ns forms" ["Insert ns form at the top" clojure-insert-ns-form] @@ -2822,6 +2828,57 @@ Assumes cursor is at beginning of function." (clojure--add-arity-reify-internal))) (indent-region beg end-marker)))) + +;;; Toggle Ignore forms + +(defun clojure--toggle-ignore-next-sexp (&optional n) + "Insert or delete N `#_' ignore macros at the current point. +Point must be directly before a sexp or the #_ characters. +When acting on a top level form, insert #_ on a new line +preceding the form to prevent indentation changes." + (let ((rgx (rx-to-string `(repeat ,(or n 1) (seq "#_" (* (in "\r\n" blank))))))) + (backward-prefix-chars) + (skip-chars-backward "#_ \r\n") + (skip-chars-forward " \r\n") + (if (looking-at rgx) + (delete-region (point) (match-end 0)) + (dotimes (_ (or n 1)) (insert-before-markers "#_")) + (when (zerop (car (syntax-ppss))) + (insert-before-markers "\n"))))) + +(defun clojure-toggle-ignore (&optional n) + "Toggle the #_ ignore reader form for the sexp at point. +With numeric argument, toggle N number of #_ forms at the same point. + + e.g. with N = 2: + |a b c => #_#_a b c" + (interactive "p") + (save-excursion + (ignore-errors + (goto-char (or (nth 8 (syntax-ppss)) ;; beginning of string + (beginning-of-thing 'sexp)))) + (clojure--toggle-ignore-next-sexp n))) + +(defun clojure-toggle-ignore-surrounding-form (&optional arg) + "Toggle the #_ ignore reader form for the surrounding form at point. +With optional ARG, move up by ARG surrounding forms first. +With universal argument \\[universal-argument], act on the \"top-level\" form." + (interactive "P") + (save-excursion + (if (consp arg) + (clojure-toggle-ignore-defun) + (condition-case nil + (backward-up-list arg t t) + (scan-error nil))) + (clojure--toggle-ignore-next-sexp))) + +(defun clojure-toggle-ignore-defun () + "Toggle the #_ ignore reader form for the \"top-level\" form at point." + (interactive) + (save-excursion + (beginning-of-defun) + (clojure--toggle-ignore-next-sexp))) + ;;; ClojureScript (defconst clojurescript-font-lock-keywords diff --git a/test/clojure-mode-refactor-add-arity-test.el b/test/clojure-mode-refactor-add-arity-test.el index 2b3cbc55..e4deefcf 100644 --- a/test/clojure-mode-refactor-add-arity-test.el +++ b/test/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/test/clojure-mode-sexp-test.el b/test/clojure-mode-sexp-test.el index da0c08fe..f9c15a3b 100644 --- a/test/clojure-mode-sexp-test.el +++ b/test/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/test/clojure-mode-util-test.el b/test/clojure-mode-util-test.el index 27ce4c5d..4a0bbbbd 100644 --- a/test/clojure-mode-util-test.el +++ b/test/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 diff --git a/test/utils/test-helper.el b/test/utils/test-helper.el index 2cb20713..756def32 100644 --- a/test/utils/test-helper.el +++ b/test/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,30 @@ 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." + (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))))) + ;;; test-helper.el ends here