jrblevin / markdown-mode

Emacs Markdown Mode
http://jblevins.org/projects/markdown-mode/
GNU General Public License v3.0
897 stars 163 forks source link

Footnote renumbering and sorting #189

Open unlaered opened 7 years ago

unlaered commented 7 years ago

Org has a useful setting and behavior that I am seeking to replicate when working with Markdown buffers. In org-footnote.el, org-footnote-auto-adjust is defined such that

When this is t, after each insertion or deletion of a footnote, simple fn:N footnotes will be renumbered, and all footnotes will be sorted.

There's more relevant functions beginning here in org-footnote.el.

What would be the simplest way to renumber footnotes in Markdown buffers? Markdown-mode doesn't seem to provide for this behavior. I don't write code. Aside from asking for a feature request to adapt this code from Org, are there global Emacs sorting functions that, with some find-and-replace ingenuity, can keep my footnotes in Markdown buffers correctly numbered? I know that some Markdown parsers will do sorting and renumbering automatically; for instance, say my Markdown buffer looks like this:

Lorem ipsum dolor sit amet, consectetur adipiscing elit,
sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.[^3]
Ut enim ad minim veniam, quis nostrud exercitation
ullamco laboris nisi ut aliquip ex ea commodo consequat.[^4]
Duis aute irure dolor in reprehenderit in voluptate
velit esse cillum dolore eu fugiat nulla pariatur.[^1]
Excepteur sint occaecat cupidatat non proident,
sunt in culpa qui officia deserunt mollit anim id est laborum.[^2]

[^1] footnote
[^2] footnote
[^3] footnote
[^4] footnote

Here I created footnotes 1 and 2 before 3 and 4, and markdown-mode numbers and sorts them (at the end of the buffer) in the order that they were created. Now if I open the file in a preview app like Marked, the footnotes are automatically renumbered and sorted so that they reflect their location in the buffer. And this is something Org can do with its own footnotes. How can I do it with markdown buffers?

lyndondrake commented 4 years ago

sorry to bump an old feature request but figure it's better than adding a new one. Is there anything I can do to help with this (I'm not competent at Lisp sadly)?

syohex commented 4 years ago

I'm not sure sort feature is like this ?

footnote-sort

diff --git a/markdown-mode.el b/markdown-mode.el
index a04f11d..54ec655 100644
--- a/markdown-mode.el
+++ b/markdown-mode.el
@@ -4538,6 +4538,42 @@ footnote text is found, NIL is returned."
       (skip-chars-forward "[ \t]")
       (point))))

+(defun markdown-footnote-find-all-marks ()
+  "Collect all footnote mark."
+  (save-excursion
+    (goto-char (point-min))
+    (cl-loop while (re-search-forward "\\[\\([^]]+\\)\\]\\(?:[^:]\\|\\'\\)" nil t)
+             collect (match-string-no-properties 1))))
+
+(defun markdown-footnote-find-all-texts ()
+  "Collect all footnote text."
+  (save-excursion
+    (goto-char (point-min))
+    (cl-loop while (re-search-forward "^ \\{0,3\\}\\[\\([^]]+\\)\\]:" nil t)
+             collect
+             (let ((id (match-string-no-properties 1))
+                   (line (line-number-at-pos))
+                   (content (buffer-substring-no-properties
+                             (line-beginning-position) (line-end-position))))
+               (cons id (list :line line :content content))))))
+
+(defun markdown-footnote-sort ()
+  "Sort footnote text order by appearance."
+  (interactive)
+  (let* ((marks (markdown-footnote-find-all-marks))
+         (texts (markdown-footnote-find-all-texts))
+         (new-lines (sort (mapcar (lambda (e) (plist-get (cdr e) :line)) texts) #'<)))
+    (save-excursion
+      (cl-loop for mark in marks
+               for new-line in new-lines
+               when (assoc-default mark texts)
+               do
+               (progn
+                 (goto-char (point-min))
+                 (forward-line (1- new-line))
+                 (delete-region (line-beginning-position) (line-end-position))
+                 (insert (plist-get it :content)))))))
+
 (defun markdown-footnote-marker-positions ()
   "Return the position and ID of the footnote marker point is on.
 The return value is a list (ID START END).  If point is not on a
lyndondrake commented 4 years ago

Not really, although that's helpful too. Something similar to Brett Terpstra's unique ids for footnotes tool: https://brettterpstra.com/projects/markdown-service-tools/ would be ideal…

ptitan commented 8 months ago

I think this function does the job :

Peek 19-02-2024 05-47

(defun markdown-footnote-find-all-marks ()
  "Collect all footnote mark."
  (save-excursion
    (goto-char (point-min))
    (cl-loop while (re-search-forward "\\[\\([^]]+\\)\\]\\(?:[^:]\\|\\'\\)" nil t)
             collect (match-string-no-properties 1))))

(defun markdown-footnote-find-all-texts ()
  "Collect all footnote text."
  (save-excursion
    (goto-char (point-min))
    (cl-loop while (re-search-forward "^ \\{0,3\\}\\[\\([^]]+\\)\\]:" nil t)
             collect
             (let ((id (match-string-no-properties 1))
                   (line (line-number-at-pos))
                   (content (buffer-substring-no-properties
                             (line-beginning-position) (line-end-position))))
               (cons id (list :line line :content content))))))

(defun markdown-footnote-sort ()
  "Sort footnote text order by appearance."
  (let* ((marks (markdown-footnote-find-all-marks))
         (texts (markdown-footnote-find-all-texts))
         (new-lines (sort (mapcar (lambda (e) (plist-get (cdr e) :line)) texts) #'<)))
    (save-excursion
      (cl-loop for mark in marks
               for new-line in new-lines
               when (assoc-default mark texts)
               do
               (progn
                 (goto-char (point-min))
                 (forward-line (1- new-line))
                 (delete-region (line-beginning-position) (line-end-position))
                 (insert (plist-get it :content)))))))

(defun renumber-footnotes ()
  "Renumber footnotes in a Markdown document."
  (save-excursion
    (goto-char (point-min))
    (let ((footnote-counter 1))
      (while (re-search-forward "\\[\\^\\([0-9]+\\)\\]" nil t)
        (replace-match (concat "[^" (number-to-string footnote-counter) "]"))
        (setq footnote-counter (1+ footnote-counter))))))

(defun renumber-footnote-references ()
  "Renumber footnote references in a Markdown document."
  (save-excursion
    (goto-char (point-min))
    (let ((footnote-counter 1))
      (while (re-search-forward "\\[\\^\\([0-9]+\\)\\]:" nil t)
        (replace-match (concat "[^" (number-to-string footnote-counter) "]:"))
        (setq footnote-counter (1+ footnote-counter))))))

(defun nb/md-normalize-footnotes ()
  "Align footnote references and definitions in a Markdown document."
  (interactive)
  (markdown-footnote-sort)
  (renumber-footnotes)
  (renumber-footnote-references))