clojure-emacs / clojure-mode

Emacs support for the Clojure(Script) programming language
912 stars 246 forks source link

Add #_ reader form #411

Closed benedekfazekas closed 3 years ago

benedekfazekas commented 8 years ago

Migrated over from clojure-emacs/clj-refactor.el#334 originally created by @agzam Agreement was that this should rather come here. see more discussion on the original issue.

the original description is as follows: Clojure's #_ (Ignore next form reader macro) is very useful. Would be very cool to have a function and a dedicated key binding to toggle it.

My initial attempt looks like this:

(defun cljr-toggle-ignore-next-form ()
  "Ignore next form (#_)"
  (interactive)
  (if (search-backward "#_" 2 t 1)
  (delete-char 2)
(progn
  (let ((fc (following-char)))
    (cond ((-contains? '( ?\) ?\] ?\} ) fc) (paredit-backward-up))
      ((-contains? '( ?\( ?\[ ?\: ?\{ ) fc) nil)
      (t (beginning-of-thing 'sexp)))
    (insert "#_")))))
vspinu commented 7 years ago

Two notes to consider if someone ever implements this:

Without the second item, I don't see much use of this, it's straitforward to get one level up and type #_ anyways.

yuhan0 commented 5 years ago

Hi, I wrote these functions a while ago to toggle the ignore form for the surrounding list or symbol below the cursor:

(defun clojure--toggle-ignore-next-sexp ()
  "Insert or delete the `#_' characters at the current point.
Assumes point is directly before a sexp or the #_ characters"
  (if (or (looking-at "#_")
          (search-backward "#_" (- (point) 2) t 1)
          (and (looking-at-p "_") (search-backward "#" (- (point) 2) t 1)))
      (delete-char 2)
    (insert "#_")))

(defun clojure-toggle-ignore-form (&optional arg)
  "Toggle the #_ ignore reader form for the surrounding form at point.
With optional ARG, move up by ARG surrounding lists first.
When called with C-u, act on the current top level defun."
  (interactive "P")
  (save-excursion
    (if (consp arg) ;; called with C-u
        (beginning-of-defun)
      (unless (looking-at-p "[({[]")
        ;; guard against moving past top level
        (condition-case nil
            (backward-up-list arg t t)
          (scan-error nil))))
    (clojure--toggle-ignore-next-sexp)))

(defun clojure-toggle-ignore-symbol (&optional arg)
  "Toggle the #_ ignore reader form for current symbol.
With optional prefix ARG, toggle ARG symbols ahead of cursor position."
  (interactive "p")
  (save-excursion
    (dotimes (i arg)
      (beginning-of-thing 'symbol)
      (clojure--toggle-ignore-next-sexp)
      (forward-symbol 2))))

It doesn't have the "cycling" property mentioned above, but you can specify a numeric prefix for how many levels to escape before commenting, or C-u to operate on the top-level form. I've been using it regularly for a few months now, some slight idiosyncracies might be the save-excursion (lots of vanilla Emacs commands like to move the cursor to the operating point, which I find disruptive), and the smartparens inspired conventions for numeric and universal prefix args.

Let me know if it's acceptable and I can prepare a PR for it :)

j-cr commented 4 years ago

Another (simpler/barebones) implementation, based on other clojure-cycle- functions:

(defun clojure-cycle-comment ()
  "Add or remove #_ literal before the current form."
  ;; code adapted from clojure-mode's `clojure-cycle-not'
  (interactive)
  (save-excursion
    (condition-case nil
        (backward-up-list)
      (scan-error (user-error "`clojure-cycle-comment' must be invoked inside a form")))
    (if (looking-back "#_")
        (delete-char -2) 
      (insert "#_"))))
andreyorst commented 4 years ago

This version will ignore next sexp if used outside of expression instead of echoing error message:

(defun clojure-toggle-ignore ()
  "Add or remove #_ literal before the current form or at next form if used at top level."
  (interactive)
  (save-excursion
    (condition-case nil
        (progn
          (backward-up-list)
          (if (looking-back "#_")
              (delete-char -2)
            (insert "#_")))
      (scan-error
       (progn
         (forward-sexp)
         (if (looking-back "#_")
             (delete-char -2)
           (backward-sexp)
           (insert "#_")))))))

Although maybe finding next sexp should be improved

dotemacs commented 4 years ago

I do have this in the works: https://github.com/dotemacs/clojure-comment-dwim.el/ just in case that you might be interested

Bost commented 3 years ago

I hammered myself following code. I wrote it some time ago so:

;; TODO Implement using the `spacemacs/toggle'
(defun my=toggle-reader-comment-fst-sexp-on-line (cmtstr)
  "If line starts with a line comment, toggle the comment.
Otherwise toggle the reader comment."
  (if (and (current-line-empty-p) (my=end-of-file-p))
      (progn
        (message "Point at the end-of-file. Doing nothing."))
    (let* ((point-pos1 (point)))
      ;; Switch to insert state at beginning of current line.
      ;; 0 means: don't insert any line
      (evil-insert-line 0)
      (let* ((point-pos2 (point))
             (is-comment-only (comment-only-p point-pos2
                                              (save-excursion
                                                (move-end-of-line 1)
                                                (point)))))
        (if is-comment-only
            ;; (evilnc-comment-or-uncomment-lines 1)
            (spacemacs/comment-or-uncomment-lines 1)
          (let* ((cmtstr-len (length cmtstr))
                 (line-start (buffer-substring-no-properties
                              point-pos2 (+ point-pos2 cmtstr-len))))
            ;; (message "line-start %s" line-start)
            (if (string= cmtstr line-start)
                (progn
                  (delete-char cmtstr-len)
                  (goto-char (- point-pos1 cmtstr-len)))
              (progn
                (insert cmtstr)
                (goto-char (+ point-pos1 cmtstr-len))))))))))

(defun my=racket-toggle-reader-comment-fst-sexp-on-line ()
  (interactive)
  (my=toggle-reader-comment-fst-sexp-on-line "#;"))

(defun my=clj-toggle-reader-comment-fst-sexp-on-line (&optional arg)
  "When invoked with prefix <C u 2> it toggles two forms - for key-value pair"
  (interactive "p")
  (my=toggle-reader-comment-fst-sexp-on-line
   (if (eq 2 arg)
       "#_#_"
     "#_")))

(defun my=racket-toggle-reader-comment-current-sexp ()
  (interactive)
  (newline-and-indent)
  (my=racket-toggle-reader-comment-fst-sexp-on-line))

(defun my=clj-toggle-reader-comment-current-sexp ()
  (interactive)
  (newline-and-indent)
  (my=clj-toggle-reader-comment-fst-sexp-on-line))

With keybindings:

(dolist (state-map `(,clojure-mode-map ,cider-repl-mode-map))
    (bind-keys :map state-map
               ;; on the german keyboard the '#' is next to Enter
               ("C-s-\\" . my=clj-toggle-reader-comment-current-sexp)
               ("s-\\"   . my=clj-toggle-reader-comment-fst-sexp-on-line)
               ))

(add-hook
   'racket-mode-hook
   (lambda ()
     (bind-keys :map racket-mode-map
                ("C-s-\\" . my=racket-toggle-reader-comment-fst-sexp-on-line)
                ("s-\\"   . my=racket-toggle-reader-comment-fst-sexp-on-line))))
yuhan0 commented 3 years ago

This should be closed by #583