@@ -526,6 +526,13 @@ This will not take effect until Emacs is restarted."
526
526
:group 'markdown
527
527
:type 'string )
528
528
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
+
529
536
; ;; Font lock =================================================================
530
537
531
538
(require 'font-lock )
@@ -578,6 +585,9 @@ This will not take effect until Emacs is restarted."
578
585
(defvar markdown-reference-face 'markdown-reference-face
579
586
" Face name to use for reference." )
580
587
588
+ (defvar markdown-footnote-face 'markdown-footnote-face
589
+ " Face name to use for footnote identifiers." )
590
+
581
591
(defvar markdown-url-face 'markdown-url-face
582
592
" Face name to use for URLs." )
583
593
@@ -675,6 +685,11 @@ This will not take effect until Emacs is restarted."
675
685
" Face for link references."
676
686
:group 'markdown-faces )
677
687
688
+ (defface markdown-footnote-face
689
+ '((t (:inherit font-lock-keyword-face )))
690
+ " Face for footnote markers."
691
+ :group 'markdown-faces )
692
+
678
693
(defface markdown-url-face
679
694
'((t (:inherit font-lock-string-face )))
680
695
" Face for URLs."
@@ -704,9 +719,13 @@ This will not take effect until Emacs is restarted."
704
719
" Regular expression for a reference link [text][id]." )
705
720
706
721
(defconst markdown-regex-reference-definition
707
- " ^ \\ {0,3\\ }\\ (\\ [. +?\\ ]\\ ):\\ s *\\ (.*?\\ )\\ s *\\ ( \" [^\" ]*\" $\\ |$\\ )"
722
+ " ^ \\ {0,3\\ }\\ (\\ [[^^] +?\\ ]\\ ):\\ s *\\ (.*?\\ )\\ s *\\ ( \" [^\" ]*\" $\\ |$\\ )"
708
723
" Regular expression for a link definition [id]: ..." )
709
724
725
+ (defconst markdown-regex-footnote
726
+ " \\ (\\ [\\ ^.+?\\ ]\\ )"
727
+ " Regular expression for a footnote marker [^fn]" )
728
+
710
729
(defconst markdown-regex-header-1-atx
711
730
" ^\\ (# \\ )\\ (.*?\\ )\\ ($\\ | #+$\\ )"
712
731
" Regular expression for level 1 atx-style (hash mark) headers." )
@@ -838,6 +857,7 @@ text.")
838
857
'((1 markdown-reference-face t )
839
858
(2 markdown-url-face t )
840
859
(3 markdown-link-title-face t )))
860
+ (cons markdown-regex-footnote 'markdown-footnote-face )
841
861
(cons markdown-regex-bold '(2 markdown-bold-face))
842
862
(cons markdown-regex-italic '(2 markdown-italic-face))
843
863
)
@@ -862,6 +882,19 @@ text.")
862
882
markdown-mode-font-lock-keywords-basic)
863
883
" Default highlighting expressions for Markdown mode." )
864
884
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
+
865
898
866
899
867
900
; ;; Compatibility =============================================================
@@ -892,6 +925,15 @@ If we are at the first line, then consider the previous line to be blank."
892
925
(forward-line -1 )
893
926
(markdown-cur-line-blank-p))))
894
927
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
+
895
937
(defun markdown-prev-line-indent-p ()
896
938
" Return t if the previous line is indented and nil otherwise."
897
939
(save-excursion
@@ -912,6 +954,12 @@ If we are at the first line, then consider the previous line to be blank."
912
954
(forward-line -1 )
913
955
(markdown-cur-line-indent)))
914
956
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
+
915
963
(defun markdown-cur-non-list-indent ()
916
964
" Return the number of leading whitespace characters in the current line."
917
965
(save-excursion
@@ -1354,6 +1402,97 @@ Arguments BEG and END specify the beginning and end of the region."
1354
1402
(interactive " *r" )
1355
1403
(markdown-block-region beg end " " ))
1356
1404
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
+
1357
1496
; ;; Indentation ====================================================================
1358
1497
1359
1498
(defun markdown-indent-find-next-position (cur-pos positions )
@@ -1473,8 +1612,12 @@ it in the usual way."
1473
1612
(define-key map " \C -c-" 'markdown-insert-hr )
1474
1613
(define-key map " \C -c\C -tt" 'markdown-insert-title )
1475
1614
(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 )
1476
1619
; ; 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 )
1478
1621
(define-key map " \M -n" 'markdown-next-wiki-link )
1479
1622
(define-key map " \M -p" 'markdown-previous-wiki-link )
1480
1623
; ; Indentation
@@ -1534,6 +1677,11 @@ it in the usual way."
1534
1677
[" Insert image" markdown-insert-image]
1535
1678
[" Insert horizontal rule" markdown-insert-hr]
1536
1679
" ---"
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
+ " ---"
1537
1685
[" Check references" markdown-check-refs]
1538
1686
" ---"
1539
1687
[" Version" markdown-show-version]
0 commit comments