Skip to content

Commit 0bd97a2

Browse files
author
Jason Blevins
committed
Reference checking
Using public domain code by Dmitry Dzhus <[email protected]> from markdown-goodies (with permission).
1 parent 18ae13a commit 0bd97a2

File tree

1 file changed

+142
-0
lines changed

1 file changed

+142
-0
lines changed

markdown-mode.el

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,11 @@
8787
;; output in another buffer while `C-c C-c p` runs Markdown on the
8888
;; current buffer and previews the output in a browser.
8989
;;
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+
;;
9095
;; * Images: `C-c C-i`
9196
;;
9297
;; `C-c C-i i` inserts an image, using the active region (if any) as
@@ -151,6 +156,7 @@
151156
;; * Greg Bognar <[email protected]> for menus and a patch.
152157
;; * Daniel Burrows <[email protected]> for filing Debian bug #456592.
153158
;; * Peter S. Galbraith <[email protected]> for maintaining emacs-goodies-el.
159+
;; * Dmitry Dzhus <[email protected]> for reference checking functions.
154160

155161
;;; Bugs:
156162

@@ -642,6 +648,8 @@ as preformatted text."
642648
;; Markdown functions
643649
(define-key markdown-mode-map "\C-c\C-cm" 'markdown)
644650
(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)
645653
markdown-mode-map)
646654
"Keymap for Markdown major mode")
647655

@@ -673,11 +681,145 @@ as preformatted text."
673681
["Insert image" markdown-insert-image]
674682
["Insert horizontal rule" markdown-insert-hr]
675683
"---"
684+
["Check references" markdown-check-refs]
685+
"---"
676686
["Version" markdown-show-version]
677687
))
678688

679689

680690

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+
681823
;;; Commands ==================================================================
682824

683825
(defun markdown ()

0 commit comments

Comments
 (0)