diff --git a/markdown-toc.el b/markdown-toc.el
index c70c2c3..ed3becd 100644
--- a/markdown-toc.el
+++ b/markdown-toc.el
@@ -217,7 +217,7 @@ Return the end position if it exists, nil otherwise."
markdown-toc-header-toc-end))
(defcustom markdown-toc-indentation-space 4
- "Let the user decide the indentation level")
+ "Let the user decide the indentation level.")
(defcustom markdown-toc-user-toc-structure-manipulation-fn
(lambda (toc-structure) toc-structure)
@@ -264,6 +264,8 @@ If called interactively with prefix arg REPLACE-TOC-P, replaces previous TOC."
markdown-toc--generate-toc
insert)))
+(defalias 'markdown-toc/generate-toc 'markdown-toc-generate-toc)
+
;;;###autoload
(defun markdown-toc-generate-or-refresh-toc ()
"Generate a TOC for markdown file at current point or refreshes an already generated TOC."
@@ -284,7 +286,44 @@ If called interactively with prefix arg REPLACE-TOC-P, replaces previous TOC."
(save-excursion
(markdown-toc--delete-toc t)))
-(defalias 'markdown-toc/generate-toc 'markdown-toc-generate-toc)
+(defun markdown-toc--read-title-out-of-link (link)
+ "Extract the link title out of a markdown LINK title.
+This assumes no funky stuff in the markdown link format ` - [
](...) ` "
+ (->> link
+ s-trim
+ (s-chop-prefix "- [")
+ (s-split "]")
+ car))
+
+(defun markdown-toc--title-level (link)
+ "Determine the markdown title LINK out of its indentation.
+If misindented or not prefixed by `-`, it's considered not a link
+and returns nil. Otherwise, returns the level number."
+ (when (s-prefix? "-" (-> link s-trim)) ;; if not, it's not a link title
+ (let ((indent (->> link
+ (s-split "-")
+ car ;; first string contains a string with empty spaces
+ ;; which should be a multiple of
+ ;; `markdown-toc-indentation-space`
+ length)))
+ (when (zerop (% indent markdown-toc-indentation-space))
+ (+ 1 (/ indent markdown-toc-indentation-space))))))
+
+;;;###autoload
+(defun markdown-toc-follow-toc-link-at-point ()
+ "On a given toc link, navigate to the current markdown header.
+If the toc is misindented (according to
+markdown-toc-indentation-space`) or if not on a toc link, this
+does nothing.
+"
+ (interactive)
+ (let* ((full-title (buffer-substring-no-properties (point-at-bol) (point-at-eol))))
+ (let ((level (markdown-toc--title-level full-title)))
+ (when level ;; nil if misindented or not on a title
+ (let ((title (markdown-toc--read-title-out-of-link full-title)))
+ (goto-char (point-min))
+ (search-forward-regexp (format "%s %s" (s-repeat level "#") title))))
+ (message "markdown-toc: Not on a link (or misindented), nothing to do"))))
(defun markdown-toc--bug-report ()
"Compute the bug report for the user to include."
diff --git a/test/markdown-toc-test.el b/test/markdown-toc-test.el
index 816c520..c93154b 100755
--- a/test/markdown-toc-test.el
+++ b/test/markdown-toc-test.el
@@ -716,5 +716,90 @@ System information:
((:input '((:message2)) :output :res2))))
(markdown-toc-bug-report)))))
+(ert-deftest markdown-toc--read-title-out-of-link ()
+ (should (string= "this is the title"
+ (markdown-toc--read-title-out-of-link " - [this is the title](#this-is-the-link) ")))
+ (should (string= "another title"
+ (markdown-toc--read-title-out-of-link " - [another title](#this-is-the-link)
+with multiple line
+should not matter "))))
+
+(ert-deftest markdown-toc--title-level ()
+ (should (eq 1
+ (markdown-toc--title-level "- [this is the title](#this-is-the-link)")))
+ (should (eq 4
+ (let ((markdown-toc-indentation-space 4))
+ (markdown-toc--title-level " - [this is the title](#this-is-the-link)"))))
+ (should (eq 2
+ (let ((markdown-toc-indentation-space 2))
+ (markdown-toc--title-level " - [another title](#this-is-the-link)
+with multiple line
+should not matter "))))
+ (should (eq 2
+ (let ((markdown-toc-indentation-space 3))
+ (markdown-toc--title-level " - [another title](#this-is-the-link)
+with multiple line
+should not matter "))))
+ ;; no - as prefix so considered not a title
+ (should-not (markdown-toc--title-level "[this is the title](#this-is-the-link)"))
+ ;; prefixed with a dash but misaligned, title should be indented with a
+ ;; multiple of `markdown-toc-indentation-space` blank spaces
+ (should-not (markdown-toc--title-level " - [title](#this-is-the-link)")))
+
+(ert-deftest markdown-toc-follow-toc-link-at-point()
+ "Follow a correct toc link should follow to the title"
+ (should (string= "## Sources"
+ (with-temp-buffer
+ (insert "- [some markdown page title](#some-markdown-page-title)
+- [main title](#main-title)
+ - [Sources](#sources)
+ - [Marmalade (recommended)](#marmalade-recommended)
+
+# main title
+## Sources
+### marmalade
+...
+")
+ (search-backward "- [Sources]")
+ (call-interactively 'markdown-toc-follow-toc-link-at-point)
+ (buffer-substring-no-properties (point-at-bol) (point-at-eol))))))
+
+(ert-deftest markdown-toc-follow-toc-link-at-point-failures()
+ "Follow a misindented toc link should do nothing"
+ (should
+ ;; not move
+ (string= " - [Sources](#sources) <- misindented 3 instead of 4 here"
+ (with-temp-buffer
+ (insert "- [some markdown page title](#some-markdown-page-title)
+- [main title](#main-title)
+ - [Sources](#sources) <- misindented 3 instead of 4 here
+
+# main title
+## Sources
+...
+")
+ (search-backward "- [Sources]")
+ (call-interactively 'markdown-toc-follow-toc-link-at-point)
+ (buffer-substring-no-properties (point-at-bol) (point-at-eol)))))
+
+ (should
+ ;; not move as well because
+ (string= "not a title"
+ (with-temp-buffer
+ (insert "- [some markdown page title](#some-markdown-page-title)
+- [main title](#main-title)
+ - [Sources](#sources)
+ - [Marmalade (recommended)](#marmalade-recommended)
+
+# main title
+## Sources
+### marmalade
+not a title
+...
+")
+ (search-backward "not a title")
+ (call-interactively 'markdown-toc-follow-toc-link-at-point)
+ (buffer-substring-no-properties (point-at-bol) (point-at-eol))))))
+
(provide 'markdown-toc-tests)
;;; markdown-toc-tests.el ends here