Skip to content

Commit 7c1bb1c

Browse files
joostkremersjrblevin
authored andcommitted
Font lock and insertion and navigation functions for footnotes
Note: the binding for following wiki links has changed from C-c C-f to C-c C-w. All footnote related commands are now prefixed by C-c C-f.
1 parent 6bd845e commit 7c1bb1c

File tree

1 file changed

+150
-2
lines changed

1 file changed

+150
-2
lines changed

markdown-mode.el

Lines changed: 150 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -526,6 +526,13 @@ This will not take effect until Emacs is restarted."
526526
:group 'markdown
527527
:type 'string)
528528

529+
(defcustom markdown-footnote-location 'end
530+
"Position where new footnotes are inserted in the document."
531+
:group 'markdown
532+
:type '(choice (const :tag "At the end of the document" end)
533+
(const :tag "Immediately after the paragraph" immediately)
534+
(const :tag "Before next header" header)))
535+
529536
;;; Font lock =================================================================
530537

531538
(require 'font-lock)
@@ -578,6 +585,9 @@ This will not take effect until Emacs is restarted."
578585
(defvar markdown-reference-face 'markdown-reference-face
579586
"Face name to use for reference.")
580587

588+
(defvar markdown-footnote-face 'markdown-footnote-face
589+
"Face name to use for footnote identifiers.")
590+
581591
(defvar markdown-url-face 'markdown-url-face
582592
"Face name to use for URLs.")
583593

@@ -675,6 +685,11 @@ This will not take effect until Emacs is restarted."
675685
"Face for link references."
676686
:group 'markdown-faces)
677687

688+
(defface markdown-footnote-face
689+
'((t (:inherit font-lock-keyword-face)))
690+
"Face for footnote markers."
691+
:group 'markdown-faces)
692+
678693
(defface markdown-url-face
679694
'((t (:inherit font-lock-string-face)))
680695
"Face for URLs."
@@ -704,9 +719,13 @@ This will not take effect until Emacs is restarted."
704719
"Regular expression for a reference link [text][id].")
705720

706721
(defconst markdown-regex-reference-definition
707-
"^ \\{0,3\\}\\(\\[.+?\\]\\):\\s *\\(.*?\\)\\s *\\( \"[^\"]*\"$\\|$\\)"
722+
"^ \\{0,3\\}\\(\\[[^^]+?\\]\\):\\s *\\(.*?\\)\\s *\\( \"[^\"]*\"$\\|$\\)"
708723
"Regular expression for a link definition [id]: ...")
709724

725+
(defconst markdown-regex-footnote
726+
"\\(\\[\\^.+?\\]\\)"
727+
"Regular expression for a footnote marker [^fn]")
728+
710729
(defconst markdown-regex-header-1-atx
711730
"^\\(# \\)\\(.*?\\)\\($\\| #+$\\)"
712731
"Regular expression for level 1 atx-style (hash mark) headers.")
@@ -838,6 +857,7 @@ text.")
838857
'((1 markdown-reference-face t)
839858
(2 markdown-url-face t)
840859
(3 markdown-link-title-face t)))
860+
(cons markdown-regex-footnote 'markdown-footnote-face)
841861
(cons markdown-regex-bold '(2 markdown-bold-face))
842862
(cons markdown-regex-italic '(2 markdown-italic-face))
843863
)
@@ -862,6 +882,19 @@ text.")
862882
markdown-mode-font-lock-keywords-basic)
863883
"Default highlighting expressions for Markdown mode.")
864884

885+
;; Footnotes
886+
(defvar markdown-footnote-counter 0
887+
"Counter for footnote numbers.")
888+
(make-variable-buffer-local 'markdown-footnote-counter)
889+
890+
(defconst markdown-footnote-chars
891+
"[[:alnum:]-]"
892+
"Regular expression maching any character that is allowed in a footnote identifier.")
893+
894+
(defconst markdown-header-regexp
895+
"#+\\|\\S-.*\n\\(?:\\(===+\\)\\|\\(---+\\)\\)$"
896+
"Regexp identifying markdown headers.")
897+
865898

866899

867900
;;; Compatibility =============================================================
@@ -892,6 +925,15 @@ If we are at the first line, then consider the previous line to be blank."
892925
(forward-line -1)
893926
(markdown-cur-line-blank-p))))
894927

928+
(defun markdown-next-line-blank-p ()
929+
"Return t if the next line is blank and nil otherwise.
930+
If we are at the last line, then consider the next line to be blank."
931+
(save-excursion
932+
(if (= (point-at-bol) (point-max))
933+
t
934+
(forward-line 1)
935+
(markdown-cur-line-blank-p))))
936+
895937
(defun markdown-prev-line-indent-p ()
896938
"Return t if the previous line is indented and nil otherwise."
897939
(save-excursion
@@ -912,6 +954,12 @@ If we are at the first line, then consider the previous line to be blank."
912954
(forward-line -1)
913955
(markdown-cur-line-indent)))
914956

957+
(defun markdown-next-line-indent ()
958+
"Return the number of leading whitespace characters in the next line."
959+
(save-excursion
960+
(forward-line 1)
961+
(markdown-cur-line-indent)))
962+
915963
(defun markdown-cur-non-list-indent ()
916964
"Return the number of leading whitespace characters in the current line."
917965
(save-excursion
@@ -1354,6 +1402,97 @@ Arguments BEG and END specify the beginning and end of the region."
13541402
(interactive "*r")
13551403
(markdown-block-region beg end " "))
13561404

1405+
;;; Footnotes ======================================================================
1406+
1407+
(defun markdown-footnote-counter-inc ()
1408+
"Increment markdown-footnote-counter and return the new value."
1409+
(when (= markdown-footnote-counter 0) ; hasn't been updated in this buffer yet.
1410+
(save-excursion
1411+
(goto-char (point-min))
1412+
(while (re-search-forward (concat "^\\[\\^\\(" markdown-footnote-chars "*?\\)\\]:")
1413+
(point-max) t)
1414+
(let ((fn (string-to-number (match-string 1))))
1415+
(when (> fn markdown-footnote-counter)
1416+
(setq markdown-footnote-counter fn))))))
1417+
(incf markdown-footnote-counter))
1418+
1419+
(defun markdown-footnote-new ()
1420+
"Insert a footnote with a new number and jump to a position to enter the
1421+
footnote text."
1422+
(interactive)
1423+
(let ((fn (markdown-footnote-counter-inc)))
1424+
(insert (format "[^%d]" fn))
1425+
(markdown-footnote-text-find-new-location)
1426+
(insert (format "[^%d]: " fn))))
1427+
1428+
(defun markdown-footnote-text-find-new-location ()
1429+
"Position the cursor at the proper location for a new footnote text."
1430+
(cond
1431+
((eq markdown-footnote-location 'end) (goto-char (point-max)))
1432+
((eq markdown-footnote-location 'immediately) (forward-paragraph))
1433+
((eq markdown-footnote-location 'header)
1434+
;; search for a header. if none is found, go to the end of the document.
1435+
(catch 'eof
1436+
(while (progn
1437+
(forward-paragraph)
1438+
(unless (re-search-forward markdown-header-regexp nil t)
1439+
(throw 'eof nil))
1440+
(backward-paragraph)
1441+
(not (looking-at (concat "\n" markdown-header-regexp))))))))
1442+
;; make sure we're on an empty line:
1443+
(unless (markdown-cur-line-blank-p)
1444+
(insert "\n"))
1445+
;; and make sure the previous line is empty:
1446+
(unless (markdown-prev-line-blank-p)
1447+
(insert "\n"))
1448+
;; then make sure there's an empty line following the footnote:
1449+
(unless (markdown-next-line-blank-p)
1450+
(insert "\n")
1451+
(forward-line -1)))
1452+
1453+
(defun markdown-footnote-goto-text ()
1454+
"Jump to the text of the footnote under the cursor."
1455+
(interactive)
1456+
;; first make sure we're at a footnote marker
1457+
(unless (or (looking-back (concat "\\[\\^" markdown-footnote-chars "*\\]?") (point-at-bol))
1458+
(looking-at (concat "\\[?\\^" markdown-footnote-chars "*?\\]")))
1459+
(error "Not at a footnote"))
1460+
(let* ((fn nil)
1461+
(new-pos (save-excursion
1462+
;; move point between [ and ^:
1463+
(if (looking-at "\\[")
1464+
(forward-char 1)
1465+
(skip-chars-backward "^["))
1466+
(looking-at (concat "\\(\\^" markdown-footnote-chars "*?\\)\\]"))
1467+
(setq fn (match-string 1))
1468+
(goto-char (point-min))
1469+
(re-search-forward (concat "^\\[" fn "\\]:") nil t))))
1470+
(unless new-pos
1471+
(error "No definition found for footnote `%s'" fn))
1472+
(goto-char new-pos)
1473+
(skip-chars-forward "[:space:]")))
1474+
1475+
(defun markdown-footnote-return ()
1476+
"Return from a footnote to its footnote number in the main text."
1477+
(interactive)
1478+
(let ((fn (save-excursion
1479+
(backward-paragraph)
1480+
;; if we're in a multiparagraph footnote, we need to back up further
1481+
(while (>= (markdown-next-line-indent) 4)
1482+
(backward-paragraph))
1483+
(forward-line)
1484+
(if (looking-at (concat "^\\[\\(\\^" markdown-footnote-chars "*?\\)\\]:"))
1485+
(match-string 1)))))
1486+
(unless fn
1487+
(error "Not in a footnote"))
1488+
(let ((new-pos (save-excursion
1489+
(goto-char (point-min))
1490+
(re-search-forward (concat "\\[" fn "\\]\\([^:]\\|\\'\\)") nil t))))
1491+
(unless new-pos
1492+
(error "Footnote `%s' not found" fn))
1493+
(goto-char new-pos)
1494+
(skip-chars-backward "^]"))))
1495+
13571496
;;; Indentation ====================================================================
13581497

13591498
(defun markdown-indent-find-next-position (cur-pos positions)
@@ -1473,8 +1612,12 @@ it in the usual way."
14731612
(define-key map "\C-c-" 'markdown-insert-hr)
14741613
(define-key map "\C-c\C-tt" 'markdown-insert-title)
14751614
(define-key map "\C-c\C-ts" 'markdown-insert-section)
1615+
;; Footnotes
1616+
(define-key map "\C-c\C-fn" 'markdown-footnote-new)
1617+
(define-key map "\C-c\C-fg" 'markdown-footnote-goto-text)
1618+
(define-key map "\C-c\C-fb" 'markdown-footnote-return)
14761619
;; WikiLink Following
1477-
(define-key map "\C-c\C-f" 'markdown-follow-wiki-link-at-point)
1620+
(define-key map "\C-c\C-w" 'markdown-follow-wiki-link-at-point)
14781621
(define-key map "\M-n" 'markdown-next-wiki-link)
14791622
(define-key map "\M-p" 'markdown-previous-wiki-link)
14801623
;; Indentation
@@ -1534,6 +1677,11 @@ it in the usual way."
15341677
["Insert image" markdown-insert-image]
15351678
["Insert horizontal rule" markdown-insert-hr]
15361679
"---"
1680+
("Footnotes"
1681+
["Insert footnote" markdown-footnote-new]
1682+
["Jump to footnote text" markdown-footnote-goto-text]
1683+
["Return from footnote" markdown-footnote-return])
1684+
"---"
15371685
["Check references" markdown-check-refs]
15381686
"---"
15391687
["Version" markdown-show-version]

0 commit comments

Comments
 (0)