|
87 | 87 | ;; output in another buffer while `C-c C-c p` runs Markdown on the |
88 | 88 | ;; current buffer and previews the output in a browser. |
89 | 89 | ;; |
| 90 | +;; `C-c C-c c` will check for undefined references. If there are any, |
| 91 | +;; a small buffer will open with a list. Selecting a reference from |
| 92 | +;; this list and pressing `RET` will insert a reference template at |
| 93 | +;; the end of the buffer. |
| 94 | +;; |
90 | 95 | ;; * Images: `C-c C-i` |
91 | 96 | ;; |
92 | 97 | ;; `C-c C-i i` inserts an image, using the active region (if any) as |
|
151 | 156 | ;; * Greg Bognar <[email protected]> for menus and a patch. |
152 | 157 | ;; * Daniel Burrows <[email protected]> for filing Debian bug #456592. |
153 | 158 | ;; * Peter S. Galbraith <[email protected]> for maintaining emacs-goodies-el. |
| 159 | +;; * Dmitry Dzhus <[email protected]> for reference checking functions. |
154 | 160 |
|
155 | 161 | ;;; Bugs: |
156 | 162 |
|
@@ -642,6 +648,8 @@ as preformatted text." |
642 | 648 | ;; Markdown functions |
643 | 649 | (define-key markdown-mode-map "\C-c\C-cm" 'markdown) |
644 | 650 | (define-key markdown-mode-map "\C-c\C-cp" 'markdown-preview) |
| 651 | + ;; References |
| 652 | + (define-key markdown-mode-map "\C-c\C-cc" 'markdown-check-refs) |
645 | 653 | markdown-mode-map) |
646 | 654 | "Keymap for Markdown major mode") |
647 | 655 |
|
@@ -673,11 +681,145 @@ as preformatted text." |
673 | 681 | ["Insert image" markdown-insert-image] |
674 | 682 | ["Insert horizontal rule" markdown-insert-hr] |
675 | 683 | "---" |
| 684 | + ["Check references" markdown-check-refs] |
| 685 | + "---" |
676 | 686 | ["Version" markdown-show-version] |
677 | 687 | )) |
678 | 688 |
|
679 | 689 |
|
680 | 690 |
|
| 691 | +;;; References ================================================================ |
| 692 | + |
| 693 | +;;; Undefined reference checking code by Dmitry Dzhus <[email protected]>. |
| 694 | + |
| 695 | +(defconst markdown-refcheck-buffer |
| 696 | + "*Undefined references for %BUFFER%*" |
| 697 | + "Name of buffer which will contain a list of undefined |
| 698 | +references in `markdown-mode' buffer named %BUFFER%.") |
| 699 | + |
| 700 | +(defun markdown-has-reference-definition (reference) |
| 701 | + "Find out whether Markdown REFERENCE is defined. |
| 702 | +
|
| 703 | +REFERENCE should include the square brackets, like [this]." |
| 704 | + (let ((reference (downcase reference))) |
| 705 | + (save-excursion |
| 706 | + (goto-char (point-min)) |
| 707 | + (catch 'found |
| 708 | + (while (re-search-forward markdown-regex-reference-definition nil t) |
| 709 | + (when (string= reference (downcase (match-string-no-properties 1))) |
| 710 | + (throw 'found t))))))) |
| 711 | + |
| 712 | +(defun markdown-get-undefined-refs () |
| 713 | + "Return a list of undefined Markdown references. |
| 714 | +
|
| 715 | +Result is an alist of pairs (reference . occurencies), where |
| 716 | +occurencies is itself another alist of pairs (label . |
| 717 | +line-number). |
| 718 | +
|
| 719 | +For example, an alist corresponding to [Nice editor][Emacs] at line 12, |
| 720 | +\[GNU Emacs][Emacs] at line 45 and [manual][elisp] at line 127 is |
| 721 | +\((\"[emacs]\" (\"[Nice editor]\" . 12) (\"[GNU Emacs]\" . 45)) (\"[elisp]\" (\"[manual]\" . 127)))." |
| 722 | + (let ((missing)) |
| 723 | + (save-excursion |
| 724 | + (goto-char (point-min)) |
| 725 | + (while |
| 726 | + (re-search-forward markdown-regex-link-reference nil t) |
| 727 | + (let* ((label (match-string-no-properties 1)) |
| 728 | + (reference (match-string-no-properties 2)) |
| 729 | + (target (downcase (if (string= reference "[]") label reference)))) |
| 730 | + (unless (markdown-has-reference-definition target) |
| 731 | + (let ((entry (assoc target missing))) |
| 732 | + (if (not entry) |
| 733 | + (add-to-list 'missing (cons target |
| 734 | + (list (cons label (line-number-at-pos)))) t) |
| 735 | + (setcdr entry |
| 736 | + (append (cdr entry) (list (cons label (line-number-at-pos)))))))))) |
| 737 | + missing))) |
| 738 | + |
| 739 | +(defun markdown-add-missing-ref-definition (ref buffer &optional recheck) |
| 740 | + "Add blank REF definition to the end of BUFFER. |
| 741 | +
|
| 742 | +REF is a Markdown reference in square brackets, like \"[lisp-history]\". |
| 743 | +
|
| 744 | +When RECHECK is non-nil, BUFFER gets rechecked for undefined |
| 745 | +references so that REF disappears from the list of those links." |
| 746 | + (with-current-buffer buffer |
| 747 | + (when (not (eq major-mode 'markdown-mode)) |
| 748 | + (error "Not available in current mdoe")) |
| 749 | + (goto-char (point-max)) |
| 750 | + (indent-new-comment-line) |
| 751 | + (insert (concat ref ": "))) |
| 752 | + (switch-to-buffer-other-window buffer) |
| 753 | + (goto-char (point-max)) |
| 754 | + (when recheck |
| 755 | + (markdown-check-refs t))) |
| 756 | + |
| 757 | +;; Button which adds an empty Markdown reference definition to the end |
| 758 | +;; of buffer specified as its 'target-buffer property. Reference name |
| 759 | +;; is button's label |
| 760 | +(define-button-type 'markdown-ref-button |
| 761 | + 'help-echo "Push to create an empty reference definition" |
| 762 | + 'face 'bold |
| 763 | + 'action (lambda (b) |
| 764 | + (markdown-add-missing-ref-definition |
| 765 | + (button-label b) (button-get b 'target-buffer) t))) |
| 766 | + |
| 767 | +;; Button jumping to line in buffer specified as its 'target-buffer |
| 768 | +;; property. Line number is button's 'line property. |
| 769 | +(define-button-type 'goto-line-button |
| 770 | + 'help-echo "Push to go to this line" |
| 771 | + 'face 'italic |
| 772 | + 'action (lambda (b) |
| 773 | + (message (button-get b 'buffer)) |
| 774 | + (switch-to-buffer-other-window (button-get b 'target-buffer)) |
| 775 | + (goto-line (button-get b 'target-line)))) |
| 776 | + |
| 777 | +(defun markdown-check-refs (&optional silent) |
| 778 | + "Show all undefined Markdown references in current `markdown-mode' buffer. |
| 779 | +
|
| 780 | +If SILENT is non-nil, do not message anything when no undefined |
| 781 | +references found. |
| 782 | +
|
| 783 | +Links which have empty reference definitions are considered to be |
| 784 | +defined." |
| 785 | + (interactive "P") |
| 786 | + (when (not (eq major-mode 'markdown-mode)) |
| 787 | + (error "Not available in current mode")) |
| 788 | + (let ((oldbuf (current-buffer)) |
| 789 | + (refs (markdown-get-undefined-refs)) |
| 790 | + (refbuf (get-buffer-create (replace-regexp-in-string |
| 791 | + "%BUFFER%" (buffer-name) |
| 792 | + markdown-refcheck-buffer t)))) |
| 793 | + (if (null refs) |
| 794 | + (progn |
| 795 | + (when (not silent) |
| 796 | + (message "No undefined references found")) |
| 797 | + (kill-buffer refbuf)) |
| 798 | + (with-current-buffer refbuf |
| 799 | + (when view-mode |
| 800 | + (View-exit-and-edit)) |
| 801 | + (erase-buffer) |
| 802 | + (insert "Following references lack definitions:") |
| 803 | + (newline 2) |
| 804 | + (dolist (ref refs) |
| 805 | + (let ((button-label (format "%s" (car ref)))) |
| 806 | + (insert-text-button button-label |
| 807 | + :type 'markdown-ref-button |
| 808 | + 'target-buffer oldbuf) |
| 809 | + (insert " (") |
| 810 | + (dolist (occurency (cdr ref)) |
| 811 | + (let ((line (cdr occurency))) |
| 812 | + (insert-button (number-to-string line) |
| 813 | + :type 'goto-line-button |
| 814 | + 'target-buffer oldbuf |
| 815 | + 'target-line line) |
| 816 | + (insert " "))) (delete-backward-char 1) |
| 817 | + (insert ")") |
| 818 | + (newline)))) |
| 819 | + (view-buffer-other-window refbuf) |
| 820 | + (goto-line 4)))) |
| 821 | + |
| 822 | + |
681 | 823 | ;;; Commands ================================================================== |
682 | 824 |
|
683 | 825 | (defun markdown () |
|
0 commit comments