;;; markdown-mode.el --- Major mode for Markdown-formatted text -*- lexical-binding: t; -*- ;; Copyright (C) 2007-2020 Jason R. Blevins and markdown-mode ;; contributors (see the commit log for details). ;; Author: Jason R. Blevins ;; Maintainer: Jason R. Blevins ;; Created: May 24, 2007 ;; Version: 2.5 ;; Package-Requires: ((emacs "25.1")) ;; Keywords: Markdown, GitHub Flavored Markdown, itex ;; URL: https://jblevins.org/projects/markdown-mode/ ;; This file is not part of GNU Emacs. ;; This program is free software; you can redistribute it and/or modify ;; it under the terms of the GNU General Public License as published by ;; the Free Software Foundation, either version 3 of the License, or ;; (at your option) any later version. ;; This program is distributed in the hope that it will be useful, ;; but WITHOUT ANY WARRANTY; without even the implied warranty of ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ;; GNU General Public License for more details. ;; You should have received a copy of the GNU General Public License ;; along with this program. If not, see . ;;; Commentary: ;; See the README.md file for details. ;;; Code: (require 'easymenu) (require 'outline) (require 'thingatpt) (require 'cl-lib) (require 'url-parse) (require 'button) (require 'color) (require 'rx) (require 'subr-x) (defvar jit-lock-start) (defvar jit-lock-end) (defvar flyspell-generic-check-word-predicate) (defvar electric-pair-pairs) (defvar sh-ancestor-alist) (declare-function project-roots "project") (declare-function sh-set-shell "sh-script") ;;; Constants ================================================================= (defconst markdown-mode-version "2.5" "Markdown mode version number.") (defconst markdown-output-buffer-name "*markdown-output*" "Name of temporary buffer for markdown command output.") ;;; Global Variables ========================================================== (defvar markdown-reference-label-history nil "History of used reference labels.") (defvar markdown-live-preview-mode nil "Sentinel variable for command `markdown-live-preview-mode'.") (defvar markdown-gfm-language-history nil "History list of languages used in the current buffer in GFM code blocks.") ;;; Customizable Variables ==================================================== (defvar markdown-mode-hook nil "Hook run when entering Markdown mode.") (defvar markdown-before-export-hook nil "Hook run before running Markdown to export XHTML output. The hook may modify the buffer, which will be restored to it's original state after exporting is complete.") (defvar markdown-after-export-hook nil "Hook run after XHTML output has been saved. Any changes to the output buffer made by this hook will be saved.") (defgroup markdown nil "Major mode for editing text files in Markdown format." :prefix "markdown-" :group 'text :link '(url-link "https://jblevins.org/projects/markdown-mode/")) (defcustom markdown-command (let ((command (cl-loop for cmd in '("markdown" "pandoc" "markdown_py") when (executable-find cmd) return (file-name-nondirectory it)))) (or command "markdown")) "Command to run markdown." :group 'markdown :type '(choice (string :tag "Shell command") (repeat (string)) function)) (defcustom markdown-command-needs-filename nil "Set to non-nil if `markdown-command' does not accept input from stdin. Instead, it will be passed a filename as the final command line option. As a result, you will only be able to run Markdown from buffers which are visiting a file." :group 'markdown :type 'boolean) (defcustom markdown-open-command nil "Command used for opening Markdown files directly. For example, a standalone Markdown previewer. This command will be called with a single argument: the filename of the current buffer. It can also be a function, which will be called without arguments." :group 'markdown :type '(choice file function (const :tag "None" nil))) (defcustom markdown-open-image-command nil "Command used for opening image files directly. This is used at `markdown-follow-link-at-point'." :group 'markdown :type '(choice file function (const :tag "None" nil))) (defcustom markdown-hr-strings '("-------------------------------------------------------------------------------" "* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *" "---------------------------------------" "* * * * * * * * * * * * * * * * * * * *" "---------" "* * * * *") "Strings to use when inserting horizontal rules. The first string in the list will be the default when inserting a horizontal rule. Strings should be listed in decreasing order of prominence (as in headings from level one to six) for use with promotion and demotion functions." :group 'markdown :type '(repeat string)) (defcustom markdown-bold-underscore nil "Use two underscores when inserting bold text instead of two asterisks." :group 'markdown :type 'boolean) (defcustom markdown-italic-underscore nil "Use underscores when inserting italic text instead of asterisks." :group 'markdown :type 'boolean) (defcustom markdown-marginalize-headers nil "When non-nil, put opening atx header markup in a left margin. This setting goes well with `markdown-asymmetric-header'. But sadly it conflicts with `linum-mode' since they both use the same margin." :group 'markdown :type 'boolean :safe 'booleanp :package-version '(markdown-mode . "2.4")) (defcustom markdown-marginalize-headers-margin-width 6 "Character width of margin used for marginalized headers. The default value is based on there being six heading levels defined by Markdown and HTML. Increasing this produces extra whitespace on the left. Decreasing it may be preferred when fewer than six nested heading levels are used." :group 'markdown :type 'natnump :safe 'natnump :package-version '(markdown-mode . "2.4")) (defcustom markdown-asymmetric-header nil "Determines if atx header style will be asymmetric. Set to a non-nil value to use asymmetric header styling, placing header markup only at the beginning of the line. By default, balanced markup will be inserted at the beginning and end of the line around the header title." :group 'markdown :type 'boolean) (defcustom markdown-indent-function 'markdown-indent-line "Function to use to indent." :group 'markdown :type 'function) (defcustom markdown-indent-on-enter t "Determines indentation behavior when pressing \\[newline]. Possible settings are nil, t, and 'indent-and-new-item. When non-nil, pressing \\[newline] will call `newline-and-indent' to indent the following line according to the context using `markdown-indent-function'. In this case, note that \\[electric-newline-and-maybe-indent] can still be used to insert a newline without indentation. When set to 'indent-and-new-item and the point is in a list item when \\[newline] is pressed, the list will be continued on the next line, where a new item will be inserted. When set to nil, simply call `newline' as usual. In this case, you can still indent lines using \\[markdown-cycle] and continue lists with \\[markdown-insert-list-item]. Note that this assumes the variable `electric-indent-mode' is non-nil (enabled). When it is *disabled*, the behavior of \\[newline] and `\\[electric-newline-and-maybe-indent]' are reversed." :group 'markdown :type '(choice (const :tag "Don't automatically indent" nil) (const :tag "Automatically indent" t) (const :tag "Automatically indent and insert new list items" indent-and-new-item))) (defcustom markdown-enable-wiki-links nil "Syntax highlighting for wiki links. Set this to a non-nil value to turn on wiki link support by default. Support can be toggled later using the `markdown-toggle-wiki-links' function or \\[markdown-toggle-wiki-links]." :group 'markdown :type 'boolean :safe 'booleanp :package-version '(markdown-mode . "2.2")) (defcustom markdown-wiki-link-alias-first t "When non-nil, treat aliased wiki links like [[alias text|PageName]]. Otherwise, they will be treated as [[PageName|alias text]]." :group 'markdown :type 'boolean :safe 'booleanp) (defcustom markdown-wiki-link-search-subdirectories nil "When non-nil, search for wiki link targets in subdirectories. This is the default search behavior for GitHub and is automatically set to t in `gfm-mode'." :group 'markdown :type 'boolean :safe 'booleanp :package-version '(markdown-mode . "2.2")) (defcustom markdown-wiki-link-search-parent-directories nil "When non-nil, search for wiki link targets in parent directories. This is the default search behavior of Ikiwiki." :group 'markdown :type 'boolean :safe 'booleanp :package-version '(markdown-mode . "2.2")) (defcustom markdown-wiki-link-search-type nil "Searching type for markdown wiki link. sub-directories: search for wiki link targets in sub directories parent-directories: search for wiki link targets in parent directories project: search for wiki link targets under project root" :group 'markdown :type '(set (const :tag "search wiki link from subdirectories" sub-directories) (const :tag "search wiki link from parent directories" parent-directories) (const :tag "search wiki link under project root" project)) :package-version '(markdown-mode . "2.5")) (make-obsolete-variable 'markdown-wiki-link-search-subdirectories 'markdown-wiki-link-search-type "2.5") (make-obsolete-variable 'markdown-wiki-link-search-parent-directories 'markdown-wiki-link-search-type "2.5") (defcustom markdown-wiki-link-fontify-missing nil "When non-nil, change wiki link face according to existence of target files. This is expensive because it requires checking for the file each time the buffer changes or the user switches windows. It is disabled by default because it may cause lag when typing on slower machines." :group 'markdown :type 'boolean :safe 'booleanp :package-version '(markdown-mode . "2.2")) (defcustom markdown-uri-types '("acap" "cid" "data" "dav" "fax" "file" "ftp" "gopher" "http" "https" "imap" "ldap" "mailto" "mid" "message" "modem" "news" "nfs" "nntp" "pop" "prospero" "rtsp" "service" "sip" "tel" "telnet" "tip" "urn" "vemmi" "wais") "Link types for syntax highlighting of URIs." :group 'markdown :type '(repeat (string :tag "URI scheme"))) (defcustom markdown-url-compose-char '(?∞ ?… ?⋯ ?# ?★ ?⚓) "Placeholder character for hidden URLs. This may be a single character or a list of characters. In case of a list, the first one that satisfies `char-displayable-p' will be used." :type '(choice (character :tag "Single URL replacement character") (repeat :tag "List of possible URL replacement characters" character)) :package-version '(markdown-mode . "2.3")) (defcustom markdown-blockquote-display-char '("▌" "┃" ">") "String to display when hiding blockquote markup. This may be a single string or a list of string. In case of a list, the first one that satisfies `char-displayable-p' will be used." :type 'string :type '(choice (string :tag "Single blockquote display string") (repeat :tag "List of possible blockquote display strings" string)) :package-version '(markdown-mode . "2.3")) (defcustom markdown-hr-display-char '(?─ ?━ ?-) "Character for hiding horizontal rule markup. This may be a single character or a list of characters. In case of a list, the first one that satisfies `char-displayable-p' will be used." :group 'markdown :type '(choice (character :tag "Single HR display character") (repeat :tag "List of possible HR display characters" character)) :package-version '(markdown-mode . "2.3")) (defcustom markdown-definition-display-char '(?⁘ ?⁙ ?≡ ?⌑ ?◊ ?:) "Character for replacing definition list markup. This may be a single character or a list of characters. In case of a list, the first one that satisfies `char-displayable-p' will be used." :type '(choice (character :tag "Single definition list character") (repeat :tag "List of possible definition list characters" character)) :package-version '(markdown-mode . "2.3")) (defcustom markdown-enable-math nil "Syntax highlighting for inline LaTeX and itex expressions. Set this to a non-nil value to turn on math support by default. Math support can be enabled, disabled, or toggled later using `markdown-toggle-math' or \\[markdown-toggle-math]." :group 'markdown :type 'boolean :safe 'booleanp) (make-variable-buffer-local 'markdown-enable-math) (defcustom markdown-enable-html t "Enable font-lock support for HTML tags and attributes." :group 'markdown :type 'boolean :safe 'booleanp :package-version '(markdown-mode . "2.4")) (defcustom markdown-enable-highlighting-syntax nil "Enable highlighting syntax." :group 'markdown :type 'boolean :safe 'booleanp :package-version '(markdown-mode . "2.5")) (defcustom markdown-css-paths nil "List of URLs of CSS files to link to in the output XHTML." :group 'markdown :type '(repeat (string :tag "CSS File Path"))) (defcustom markdown-content-type "text/html" "Content type string for the http-equiv header in XHTML output. When set to an empty string, this attribute is omitted. Defaults to `text/html'." :group 'markdown :type 'string) (defcustom markdown-coding-system nil "Character set string for the http-equiv header in XHTML output. Defaults to `buffer-file-coding-system' (and falling back to `utf-8' when not available). Common settings are `iso-8859-1' and `iso-latin-1'. Use `list-coding-systems' for more choices." :group 'markdown :type 'coding-system) (defcustom markdown-export-kill-buffer t "Kill output buffer after HTML export. When non-nil, kill the HTML output buffer after exporting with `markdown-export'." :group 'markdown :type 'boolean :safe 'booleanp :package-version '(markdown-mode . "2.4")) (defcustom markdown-xhtml-header-content "" "Additional content to include in the XHTML block." :group 'markdown :type 'string) (defcustom markdown-xhtml-body-preamble "" "Content to include in the XHTML block, before the output." :group 'markdown :type 'string :safe 'stringp :package-version '(markdown-mode . "2.4")) (defcustom markdown-xhtml-body-epilogue "" "Content to include in the XHTML block, after the output." :group 'markdown :type 'string :safe 'stringp :package-version '(markdown-mode . "2.4")) (defcustom markdown-xhtml-standalone-regexp "^\\(<\\?xml\\| Links & Images menu." :group 'markdown :type 'boolean :safe 'booleanp :package-version '(markdown-mode . "2.3")) (make-variable-buffer-local 'markdown-hide-urls) (defcustom markdown-translate-filename-function #'identity "Function to use to translate filenames when following links. \\\\[markdown-follow-thing-at-point] and \\[markdown-follow-link-at-point] call this function with the filename as only argument whenever they encounter a filename (instead of a URL) to be visited and use its return value instead of the filename in the link. For example, if absolute filenames are actually relative to a server root directory, you can set `markdown-translate-filename-function' to a function that prepends the root directory to the given filename." :group 'markdown :type 'function :risky t :package-version '(markdown-mode . "2.4")) (defcustom markdown-max-image-size nil "Maximum width and height for displayed inline images. This variable may be nil or a cons cell (MAX-WIDTH . MAX-HEIGHT). When nil, use the actual size. Otherwise, use ImageMagick to resize larger images to be of the given maximum dimensions. This requires Emacs to be built with ImageMagick support." :group 'markdown :package-version '(markdown-mode . "2.4") :type '(choice (const :tag "Use actual image width" nil) (cons (choice (sexp :tag "Maximum width in pixels") (const :tag "No maximum width" nil)) (choice (sexp :tag "Maximum height in pixels") (const :tag "No maximum height" nil))))) (defcustom markdown-mouse-follow-link t "Non-nil means mouse on a link will follow the link. This variable must be set before loading markdown-mode." :group 'markdown :type 'boolean :safe 'booleanp :package-version '(markdown-mode . "2.5")) (defcustom markdown-table-align-p t "Non-nil means that table is aligned after table operation." :group 'markdown :type 'boolean :safe 'booleanp :package-version '(markdown-mode . "2.5")) ;;; Markdown-Specific `rx' Macro ============================================== ;; Based on python-rx from python.el. (eval-and-compile (defconst markdown-rx-constituents `((newline . ,(rx "\n")) ;; Note: #405 not consider markdown-list-indent-width however this is never used (indent . ,(rx (or (repeat 4 " ") "\t"))) (block-end . ,(rx (and (or (one-or-more (zero-or-more blank) "\n") line-end)))) (numeral . ,(rx (and (one-or-more (any "0-9#")) "."))) (bullet . ,(rx (any "*+:-"))) (list-marker . ,(rx (or (and (one-or-more (any "0-9#")) ".") (any "*+:-")))) (checkbox . ,(rx "[" (any " xX") "]"))) "Markdown-specific sexps for `markdown-rx'") (defun markdown-rx-to-string (form &optional no-group) "Markdown mode specialized `rx-to-string' function. This variant supports named Markdown expressions in FORM. NO-GROUP non-nil means don't put shy groups around the result." (let ((rx-constituents (append markdown-rx-constituents rx-constituents))) (rx-to-string form no-group))) (defmacro markdown-rx (&rest regexps) "Markdown mode specialized rx macro. This variant of `rx' supports common Markdown named REGEXPS." (cond ((null regexps) (error "No regexp")) ((cdr regexps) (markdown-rx-to-string `(and ,@regexps) t)) (t (markdown-rx-to-string (car regexps) t))))) ;;; Regular Expressions ======================================================= (defconst markdown-regex-comment-start "") (setq-local comment-start-skip "