diff --git a/markdown-mode.el b/markdown-mode.el index fc1796b3..21b5caf2 100644 --- a/markdown-mode.el +++ b/markdown-mode.el @@ -8211,22 +8211,108 @@ returns nil." (or url (and ref (car (markdown-reference-definition (downcase (if (string= ref "") text ref)))))))) +(defun markdown--normalize-target (target) + (if-let* ((heading target) + (normalized (downcase heading)) + (normalized (string-replace "-" " " normalized)) + (normalized (replace-regexp-in-string "[[:punct:]]" "" + normalized)) + (normalized (string-replace " " "-" normalized)) + (url (format "%s" normalized))) + url + (warn "Failed to normalize url"))) + +(defun markdown--partial-heading-url-at-point () + "Return the heading link at point." + (if (markdown-heading-at-point) + (concat "#" (markdown--normalize-target (match-string 5))) + (warn "No heading found at point"))) + +(defun markdown--full-heading-url-at-point () + "Return the full file url at point, including the internal heading if +it exists." + (concat "file://" (buffer-file-name) + (markdown--partial-heading-url-at-point))) + +(defun markdown-copy-partial-heading-link-at-point-as-kill () + "Copy internal heading link at point to the `kill-ring'." + (interactive) + (if-let ((url (markdown--partial-heading-url-at-point))) + (progn + (message "Copied: %s" url) + (kill-new url)))) + +(defun markdown-copy-full-heading-link-at-point-as-kill () + "Copy the full url to heading at point to the `kill-ring'." + (interactive) + (if-let ((url (markdown--full-heading-url-at-point))) + (progn + (message "Copied: %s" url) + (kill-new url)))) + +(defun markdown--collect-headings (&optional buffer) + "Return all headings within BUFFER in a list as targets." + (with-current-buffer (or buffer (current-buffer)) + (let (targets) + (save-mark-and-excursion + (goto-char (point-min)) + (while (re-search-forward markdown-regex-header (buffer-end 1) t) + (setq targets + (append targets + (list (cons (match-string 5) + (match-beginning 5))))))) + targets))) + +(defun markdown-jump-to-heading (target) + "Push mark to current location and jump to TARGET." + (interactive (list (let ((targets (markdown--collect-headings))) + (assoc (completing-read "Jump to target heading: " targets) targets)))) + (let* ((pos (cdr target))) + (push-mark) + (goto-char pos))) + +(defun markdown--collect-targets (&optional buffer) + "Return all headings within BUFFER in a list as targets." + (with-current-buffer (or buffer (current-buffer)) + (let (targets) + (save-mark-and-excursion + (goto-char (point-min)) + (while (re-search-forward markdown-regex-header (buffer-end 1) t) + (setq targets + (append targets + (list (cons (markdown--normalize-target (match-string 5)) + (match-beginning 5))))))) + targets))) + +(defun markdown--jump-to-target (target) + "Push mark to current location and jump to TARGET." + (let* ((targets (markdown--collect-targets)) + (pos (cdr (assoc target targets)))) + (push-mark) + (goto-char pos))) + (defun markdown--browse-url (url) (let* ((struct (url-generic-parse-url url)) (full (url-fullness struct)) - (file url)) + (file url) + (target (url-target struct))) ;; Parse URL, determine fullness, strip query string (setq file (car (url-path-and-query struct))) ;; Open full URLs in browser, files in Emacs (if full - (browse-url url) + (let ((type (url-type struct))) + (browse-url url) + (when (and (equal type "file") target) + (markdown--jump-to-target target))) (when (and file (> (length file) 0)) (let ((link-file (funcall markdown-translate-filename-function file))) (if (and markdown-open-image-command (string-match-p (image-file-name-regexp) link-file)) (if (functionp markdown-open-image-command) (funcall markdown-open-image-command link-file) - (process-file markdown-open-image-command nil nil nil link-file)) - (find-file link-file))))))) + (process-file markdown-open-image-command nil nil nil link-file))) + (find-file link-file))) + (when target + (markdown--jump-to-target target))))) (defun markdown-follow-link-at-point (&optional event) "Open the non-wiki link at point or EVENT.