company-coq icon indicating copy to clipboard operation
company-coq copied to clipboard

Enhancement: quick way to adjust bullet levels

Open andersk opened this issue 8 years ago • 4 comments

I often find that I want to refactor a giant bulleted proof tree by inserting or deleting a level of bullets, say to turn

  eexists.
  simpl.
  split.
  - destruct H.
    + destruct H1.
      econstructor.
    + econstructor.
      * econstructor.
        eassumption.
      * reflexivity.
  - eassumption.

into

  edestruct IHa.
  - reflexivity.
  - eexists.
    simpl.
    split.
    + destruct H.
      * destruct H1.
        econstructor.
      * econstructor.
        -- econstructor.
           eassumption.
        -- reflexivity.
    + eassumption.

I would find it useful to have a quick way to adjust all of the bullets automatically when doing so. This could perhaps take the form of keyboard shortcuts to increase/decrease all bullet levels within the selected region.

andersk avatar Jun 24 '16 01:06 andersk

Thanks, this is a cool idea.

Can you try pasting the following into *scratch* and running eval-buffer after opening a Coq file?

(defun company-coq-features/code-folding--cycle-bullet (bullet delta)
  (let* ((bullets '(?- ?+ ?*))
         (bullet-char (aref bullet 0))
         (char-idx (-elem-index bullet-char bullets))
         (new-idx (mod (+ char-idx delta) (length bullets)))
         (new-char (nth new-idx bullets))
         (new-length (+ (length bullet)
                        (if (eq (+ char-idx delta) new-idx) 0 delta))))
    (make-string (max 1 new-length) new-char)))

(defvar company-coq--features/code-folding--increment-bullets-map
  (let ((map (make-sparse-keymap)))
    (define-key map (kbd ">") #'company-coq-features/code-folding-increment)
    (define-key map (kbd "<") #'company-coq-features/code-folding-decrement)
    (define-key map (kbd "<right>") #'company-coq-features/code-folding-increment)
    (define-key map (kbd "<left>") #'company-coq-features/code-folding-decrement)
    map))

(defun company-coq-features/code-folding--increment-bullets (delta)
  (save-excursion
    (let ((beg (region-beginning))
          (end (region-end)))
      (goto-char beg)
      (set-transient-map company-coq--features/code-folding--increment-bullets-map t)
      (while (re-search-forward company-coq-features/code-folding--bullet-regexp end t)
        (goto-char (match-beginning 1))
        (when (company-coq-features/code-folding--really-on-bullet-p (point))
          (let ((bullet (and (looking-at "[-+*]+") (match-string 0))))
            (replace-match (company-coq-features/code-folding--cycle-bullet bullet delta) t t))))
      (indent-rigidly beg end (* 2 delta))
      (setq deactivate-mark nil))))

(defun company-coq-features/code-folding-increment ()
  (interactive)
  (company-coq-features/code-folding--increment-bullets 1))

(defun company-coq-features/code-folding-decrement ()
  (interactive)
  (company-coq-features/code-folding--increment-bullets -1))

(define-key coq-mode-map (kbd "C-c >"))
(define-key coq-mode-map (kbd "C-c <"))

With these definitions you should be able to press C-c > after selecting a block of lines to increment all bullets. Repeating > and < (no need for C-c for repeats) should indent (resp. dedent) further.

Let me know if that works for you; if it does I'll merge it :)

cpitclaudel avatar Jun 24 '16 03:06 cpitclaudel

Cool, that was fast! It almost works.

  • The last two define-keys are missing their third argument.
  • The <right> and <left> bindings surprised me; I just wanted to move the cursor.
  • You maybe want to disable the decrement command if there’s a - bullet, since the current behavior there is not reversible.
  • Sometimes the decrement command also causes the line following the region to be dedented by two spaces. I think end needs to be adjusted after the replace-match operations.

andersk avatar Jun 24 '16 04:06 andersk

Thanks!

The last two define-keys are missing their third argument.

Uh, you're right; I wonder where that argument went :/

The and bindings surprised me; I just wanted to move the cursor.

I'll remove them :) (I modeled them after C-x TAB)

You maybe want to disable the decrement command if there’s a - bullet, since the current behavior there is not reversible.

An alternative could be to drop the bullets once they pass '-'. The would allow you to remove a top-level bullet.

Sometimes the decrement command also causes the line following the region to be dedented by two spaces. I think end needs to be adjusted after the replace-match operations.

That's right.

Here's a new version:

(defun company-coq--cycle-bullet (bullet delta)
  (let* ((bullets '(?- ?+ ?*))
         (bullet-char (aref bullet 0))
         (char-idx (-elem-index bullet-char bullets))
         (new-idx (mod (+ char-idx delta) (length bullets)))
         (new-char (nth new-idx bullets))
         (new-length (+ (length bullet)
                        (if (eq (+ char-idx delta) new-idx) 0 delta))))
    (if (= new-length 0) " "
      (make-string new-length new-char))))

(defvar company-coq--increment-bullets-map
  (let ((map (make-sparse-keymap)))
    (define-key map (kbd ">") #'company-coq-increment-bullets)
    (define-key map (kbd "<") #'company-coq-decrement-bullets)
    map))

(defun company-coq--increment-bullets-1 (beg end delta)
  (save-excursion
    (goto-char beg)
    (setq beg (goto-char (point-at-bol)))
    (setq end (move-marker (make-marker) end))
    (while (re-search-forward company-coq-features/code-folding--bullet-regexp end t)
      (goto-char (match-beginning 1))
      (when (company-coq-features/code-folding--really-on-bullet-p (point))
        (let ((bullet (and (looking-at "[-+*]+") (match-string 0))))
          (replace-match (company-coq--cycle-bullet bullet delta) t t)))))
  (indent-rigidly beg end (* 2 delta))
  (setq deactivate-mark nil)
  (move-marker end nil))

(defun company-coq--increment-bullets (delta)
  (set-transient-map company-coq--increment-bullets-map t)
  (company-coq--increment-bullets-1 (region-beginning) (region-end) delta))

(defun company-coq-increment-bullets ()
  (interactive)
  (company-coq--increment-bullets 1))

(defun company-coq-decrement-bullets ()
  (interactive)
  (company-coq--increment-bullets -1))

(define-key coq-mode-map (kbd "C-c >") #'company-coq-increment-bullets)
(define-key coq-mode-map (kbd "C-c <") #'company-coq-decrement-bullets)

Thanks for your help!

cpitclaudel avatar Jun 24 '16 13:06 cpitclaudel

Thank you—this looks great!

andersk avatar Jun 24 '16 19:06 andersk