From b431f57ce1440b220ff5cd0a8c5f87508cb51de7 Mon Sep 17 00:00:00 2001 From: John Miller Date: Fri, 9 Sep 2016 09:47:48 -0500 Subject: [PATCH] Squashed 'lib/markdown-mode/' content from commit 3f11a4a git-subtree-dir: lib/markdown-mode git-subtree-split: 3f11a4a7609ba2a507ee10f491bdadab28e9b985 --- .dir-locals.el | 16 + .gitignore | 7 + .travis.yml | 14 + LICENSE | 339 ++ Makefile | 38 + README.md | 891 +++ markdown-mode.el | 6842 +++++++++++++++++++++++ scripts/get-recognized-gfm-languages.el | 14 + tests/.gitignore | 1 + tests/GFM.md | 143 + tests/Makefile | 40 + tests/Markdown.md | 120 + tests/checkdoc-batch.el | 10 + tests/cl-lib-0.5.el | 410 ++ tests/gfm.text | 78 + tests/ikiwiki/root | 3 + tests/ikiwiki/sub/foo | 3 + tests/inline.text | 30 + tests/itex.text | 19 + tests/lists.text | 160 + tests/markdown-test.el | 4032 +++++++++++++ tests/math.text | 25 + tests/more-lists.text | 57 + tests/nested-list.text | 25 + tests/outline-code.text | 17 + tests/outline.text | 44 + tests/r-markdown-test.text | 30 + tests/syntax.text | 897 +++ tests/test-cases.text | 454 ++ tests/wiki-links.text | 25 + webpage.sh | 49 + 31 files changed, 14833 insertions(+) create mode 100644 .dir-locals.el create mode 100644 .gitignore create mode 100644 .travis.yml create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 README.md create mode 100644 markdown-mode.el create mode 100644 scripts/get-recognized-gfm-languages.el create mode 100644 tests/.gitignore create mode 100644 tests/GFM.md create mode 100644 tests/Makefile create mode 100644 tests/Markdown.md create mode 100644 tests/checkdoc-batch.el create mode 100644 tests/cl-lib-0.5.el create mode 100644 tests/gfm.text create mode 100644 tests/ikiwiki/root create mode 100644 tests/ikiwiki/sub/foo create mode 100644 tests/inline.text create mode 100644 tests/itex.text create mode 100644 tests/lists.text create mode 100644 tests/markdown-test.el create mode 100644 tests/math.text create mode 100644 tests/more-lists.text create mode 100644 tests/nested-list.text create mode 100644 tests/outline-code.text create mode 100644 tests/outline.text create mode 100644 tests/r-markdown-test.text create mode 100644 tests/syntax.text create mode 100644 tests/test-cases.text create mode 100644 tests/wiki-links.text create mode 100755 webpage.sh diff --git a/.dir-locals.el b/.dir-locals.el new file mode 100644 index 0000000..cb39f4c --- /dev/null +++ b/.dir-locals.el @@ -0,0 +1,16 @@ +;;; Directory Local Variables +;;; See Info node `(emacs) Directory Variables' for more information. + +((nil + (require-final-newline . t) + ;; not tabs in code + (indent-tabs-mode) + ;; checkdoc, one space is enough + (sentence-end-double-space . nil) + ;; checkdoc, don't botch English grammar + (checkdoc-arguments-in-order-flag . nil) + ;; checkdoc, we don't want docs for internal vars + (checkdoc-force-docstrings-flag . nil)) + (emacs-lisp-mode + ;; remove trailing whitespace + (eval . (add-hook 'before-save-hook 'delete-trailing-whitespace nil t)))) diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..71b1c8d --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +*.elc +index.text +archive +reference +attic +patches +extra diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..561a4bc --- /dev/null +++ b/.travis.yml @@ -0,0 +1,14 @@ +language: generic +sudo: false +before_install: + - curl -fsSkL https://gist.github.com/rejeep/ebcd57c3af83b049833b/raw > x.sh && source ./x.sh + - evm install $EVM_EMACS --use --skip +env: + - EVM_EMACS=emacs-24.1-travis CL="-l cl-lib-0.5.el" + - EVM_EMACS=emacs-24.2-travis CL="-l cl-lib-0.5.el" + - EVM_EMACS=emacs-24.3-travis + - EVM_EMACS=emacs-24.4-travis + - EVM_EMACS=emacs-24.5-travis +script: + - emacs --version + - cd tests && make clean && make test diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d159169 --- /dev/null +++ b/LICENSE @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + 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 2 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, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..2f7d2d5 --- /dev/null +++ b/Makefile @@ -0,0 +1,38 @@ +EMACS=emacs + +PACKAGE=markdown-mode + +SOURCE=markdown-mode.el +COMPILED=markdown-mode.elc + +VERSION=$(shell cat $(SOURCE) | sed -n 's/^;; Version: \(.*\)/\1/p') + +TEST_FILES=tests/Makefile tests/*.el tests/*.text tests/*.md + +.el.elc: + $(EMACS) -q -no-site-file -no-init-file -batch -f batch-byte-compile $< + +all: $(COMPILED) + +.PHONY: dist test + +test: + make -C tests test + +clean: + rm -f $(COMPILED) + make -C tests clean + +dist: + DIR=$$(mktemp -d -t "$(PACKAGE)"); \ + DESTDIR="$$DIR/$(PACKAGE)-$(VERSION)"; \ + mkdir -p $$DESTDIR; \ + cp -a $(SOURCE) $$DESTDIR; \ + mkdir -p $$DESTDIR/tests; \ + cp -a $(TEST_FILES) $$DESTDIR/tests; \ + tar zcf $(CURDIR)/$(PACKAGE)-$(VERSION).tar.gz -C $$DIR .; \ + rm -r $$DIR; \ + echo "$(PACKAGE)-$(VERSION).tar.gz has been created" + +update: $(COMPILED) + cp -a $(SOURCE) $(COMPILED) $(HOME)/.emacs.d/site-lisp diff --git a/README.md b/README.md new file mode 100644 index 0000000..8604e10 --- /dev/null +++ b/README.md @@ -0,0 +1,891 @@ +# Emacs Markdown Mode [![MELPA badge][melpa-badge]][melpa-link] [![MELPA stable badge][melpa-stable-badge]][melpa-stable-link] [![Travis CI Build Status][travis-badge]][travis-link] + + [melpa-link]: https://melpa.org/#/markdown-mode + [melpa-stable-link]: https://stable.melpa.org/#/markdown-mode + [melpa-badge]: https://melpa.org/packages/markdown-mode-badge.svg + [melpa-stable-badge]: https://stable.melpa.org/packages/markdown-mode-badge.svg + [travis-link]: https://travis-ci.org/jrblevin/markdown-mode + [travis-badge]: https://travis-ci.org/jrblevin/markdown-mode.svg?branch=master + + + +markdown-mode is a major mode for editing [Markdown][]-formatted +text. The latest stable version is markdown-mode 2.1, released on +January 9, 2016. See the [release notes][] for details. +markdown-mode is free software, licensed under the GNU GPL. + +![Markdown Mode Screenshot](http://jblevins.org/projects/markdown-mode/screenshots/20160108-001.png) + +[Markdown]: http://daringfireball.net/projects/markdown/ +[release notes]: http://jblevins.org/projects/markdown-mode/rev-2-1 + +## Installation + +The recommended way to install markdown-mode is to install the package +from [MELPA Stable](https://stable.melpa.org/#/markdown-mode) +using `package.el`. First, configure `package.el` and the MELPA Stable +repository by adding the following to your `.emacs`, `init.el`, +or equivalent startup file: + + (require 'package) + (add-to-list 'package-archives + '("melpa-stable" . "https://stable.melpa.org/packages/")) + (package-initialize) + +Then, after restarting Emacs or evaluating the above statements, issue +the following command: M-x package-install RET markdown-mode RET. +When installed this way, the major modes `markdown-mode` and `gfm-mode` +will be autoloaded and `markdown-mode` will be used for file names +ending in either `.md` or `.markdown`. + +Alternatively, if you manage loading packages with [use-package][] +then you can automatically install and configure `markdown-mode` by +adding a declaration such as this one to your init file (as an +example; adjust settings as desired): + + (use-package markdown-mode + :ensure t + :commands (markdown-mode gfm-mode) + :mode (("README\\.md\\'" . gfm-mode) + ("\\.md\\'" . markdown-mode) + ("\\.markdown\\'" . markdown-mode)) + :init (setq markdown-command "multimarkdown")) + +[MELPA Stable]: http://stable.melpa.org/ +[use-package]: https://github.com/jwiegley/use-package + +**Direct Download** + +Alternatively you can manually download and install markdown-mode. +First, download the [latest stable version][markdown-mode.el] and +save the file where Emacs can find it (i.e., a directory in your +`load-path`). You can then configure `markdown-mode` and `gfm-mode` +to load automatically by adding the following to your init file: + + (autoload 'markdown-mode "markdown-mode" + "Major mode for editing Markdown files" t) + (add-to-list 'auto-mode-alist '("\\.markdown\\'" . markdown-mode)) + (add-to-list 'auto-mode-alist '("\\.md\\'" . markdown-mode)) + + (autoload 'gfm-mode "markdown-mode" + "Major mode for editing GitHub Flavored Markdown files" t) + (add-to-list 'auto-mode-alist '("README\\.md\\'" . gfm-mode)) + +[markdown-mode.el]: http://jblevins.org/projects/markdown-mode/markdown-mode.el + +**Development Version** + +To follow or contribute to markdown-mode development, you can +browse or clone the Git repository +[on GitHub](https://github.com/jrblevin/markdown-mode): + + git clone https://github.com/jrblevin/markdown-mode.git + +If you prefer to install and use the development version, which may +become unstable at some times, you can either clone the Git +repository as above or install markdown-mode from +[MELPA](https://melpa.org/#/markdown-mode). + +If you clone the repository directly, then make sure that Emacs can +find it by adding the following line to your startup file: + + (add-to-list 'load-path "/path/to/markdown-mode/repository") + +**Packaged Installation** + +markdown-mode is also available in several package managers. You +may want to confirm that the package you install contains the +latest stable version first (and please notify the package +maintainer if not). + + * Debian Linux: [elpa-markdown-mode][] and [emacs-goodies-el][] + * Ubuntu Linux: [elpa-markdown-mode][elpa-ubuntu] and [emacs-goodies-el][emacs-goodies-el-ubuntu] + * RedHat and Fedora Linux: [emacs-goodies][] + * NetBSD: [textproc/markdown-mode][] + * MacPorts: [markdown-mode.el][macports-package] ([pending][macports-ticket]) + * FreeBSD: [textproc/markdown-mode.el][freebsd-port] + + [elpa-markdown-mode]: https://packages.debian.org/sid/lisp/elpa-markdown-mode + [elpa-ubuntu]: http://packages.ubuntu.com/search?keywords=elpa-markdown-mode + [emacs-goodies-el]: http://packages.debian.org/emacs-goodies-el + [emacs-goodies-el-ubuntu]: http://packages.ubuntu.com/search?keywords=emacs-goodies-el + [emacs-goodies]: https://apps.fedoraproject.org/packages/emacs-goodies + [textproc/markdown-mode]: http://pkgsrc.se/textproc/markdown-mode + [macports-package]: https://trac.macports.org/browser/trunk/dports/editors/markdown-mode.el/Portfile + [macports-ticket]: http://trac.macports.org/ticket/35716 + [freebsd-port]: http://svnweb.freebsd.org/ports/head/textproc/markdown-mode.el + +**Dependencies** + +`markdown-mode` depends on `cl-lib`, which has been bundled with +GNU Emacs since 24.3. Users of GNU Emacs 24.1 and 24.2 can install +`cl-lib` with `package.el`. + +## Usage + +Keybindings are grouped by prefixes based on their function. For +example, the commands for inserting links are grouped under `C-c +C-a`, where the C-a is a mnemonic for the HTML `` tag. In +other cases, the connection to HTML is not direct. For example, +commands dealing with headings begin with C-c C-t (mnemonic: +titling). The primary commands in each group will are described +below. You can obtain a list of all keybindings by pressing `C-c +C-h`. Movement and shifting commands tend to be associated with +paired delimiters such as M-{ and M-} or C-c < and C-c >. +Outline navigation keybindings the same as in `org-mode`. Finally, +commands for running Markdown or doing maintenance on an open file +are grouped under the C-c C-c prefix. The most commonly used +commands are described below. You can obtain a list of all +keybindings by pressing C-c C-h. + + * Hyperlinks: C-c C-a + + In this group, C-c C-a l inserts an inline link of the form + `[text](url)`. The link text is determined as follows. First, + if there is an active region (i.e., when transient mark mode is + on and the mark is active), use it as the link text. Second, + if the point is at a word, use that word as the link text. In + these two cases, the original text will be replaced with the + link and point will be left at the position for inserting a + URL. Otherwise, insert empty link markup and place the point + for inserting the link text. + + C-c C-a L inserts a reference link of the form `[text][label]` + and, optionally, a corresponding reference label definition. + The link text is determined in the same way as with an inline + link (using the region, when active, or the word at the point), + but instead of inserting empty markup as a last resort, the + link text will be read from the minibuffer. The reference + label will be read from the minibuffer in both cases, with + completion from the set of currently defined references. To + create an implicit reference link, press RET to accept the + default, an empty label. If the entered referenced label is + not defined, additionally prompt for the URL and (optional) + title. If a URL is provided, a reference definition will be + inserted in accordance with `markdown-reference-location`. + If a title is given, it will be added to the end of the + reference definition and will be used to populate the title + attribute when converted to XHTML. + + C-c C-a u inserts a bare url, delimited by angle brackets. When + there is an active region, the text in the region is used as the + URL. If the point is at a URL, that url is used. Otherwise, + insert angle brackets and position the point in between them + for inserting the URL. + + C-c C-a f inserts a footnote marker at the point, inserts a + footnote definition below, and positions the point for + inserting the footnote text. Note that footnotes are an + extension to Markdown and are not supported by all processors. + + C-c C-a w behaves much like the inline link insertion command + and inserts a wiki link of the form `[[WikiLink]]`. If there + is an active region, use the region as the link text. If the + point is at a word, use the word as the link text. If there is + no active region and the point is not at word, simply insert + link markup. Note that wiki links are an extension to Markdown + and are not supported by all processors. + + * Images: C-c C-i + + C-c C-i i inserts markup for an inline image, using the + active region or the word at point, if any, as the alt text. + C-c C-i I behaves similarly and inserts a reference-style + image. + + Local images associated with image links may be displayed + inline in the buffer by pressing C-c C-i C-t + (`markdown-toggle-inline-images`). This is a toggle command, so + pressing this once again will remove inline images. + + * Styles: C-c C-s + + C-c C-s e inserts markup to make a region or word italic (e + for `` or emphasis). If there is an active region, make + the region italic. If the point is at a non-italic word, make + the word italic. If the point is at an italic word or phrase, + remove the italic markup. Otherwise, simply insert italic + delimiters and place the cursor in between them. Similarly, + use C-c C-s s for bold (``), C-c C-s c for + inline code (``), and C-c C-s k for inserting `` + tags. + + C-c C-s b inserts a blockquote using the active region, if any, + or starts a new blockquote. C-c C-s C-b is a variation which + always operates on the region, regardless of whether it is + active or not. The appropriate amount of indentation, if any, + is calculated automatically given the surrounding context, but + may be adjusted later using the region indentation commands. + + C-c C-s p behaves similarly for inserting preformatted code + blocks, with C-c C-s C-p being the region-only counterpart. + + * Headings: C-c C-t + + All heading insertion commands use the text in the active + region, if any, as the heading text. Otherwise, if the current + line is not blank, they use the text on the current line. + Finally, the setext commands will prompt for heading text if + there is no active region and the current line is blank. + + C-c C-t h inserts a heading with automatically chosen type and + level (both determined by the previous heading). C-c C-t H + behaves similarly, but uses setext (underlined) headings when + possible, still calculating the level automatically. + In cases where the automatically-determined level is not what + you intended, the level can be quickly promoted or demoted + (as described below). Alternatively, a C-u prefix can be + given to insert a heading promoted by one level or a C-u C-u + prefix can be given to insert a heading demoted by one level. + + To insert a heading of a specific level and type, use C-c C-t 1 + through C-c C-t 6 for atx (hash mark) headings and C-c C-t ! or + C-c C-t @ for setext headings of level one or two, respectively. + Note that ! is S-1 and @ is S-2. + + If the point is at a heading, these commands will replace the + existing markup in order to update the level and/or type of the + heading. To remove the markup of the heading at the point, + press C-c C-k to kill the heading and press C-y to yank the + heading text back into the buffer. + + * Horizontal Rules: C-c - + + C-c - inserts a horizontal rule. By default, insert the + first string in the list `markdown-hr-strings` (the most + prominent rule). With a C-u prefix, insert the last string. + With a numeric prefix N, insert the string in position N + (counting from 1). + + * Markdown and Maintenance Commands: C-c C-c + + *Compile:* C-c C-c m will run Markdown on the current buffer + and show the output in another buffer. *Preview*: C-c C-c p + runs Markdown on the current buffer and previews, stores the + output in a temporary file, and displays the file in a browser. + *Export:* C-c C-c e will run Markdown on the current buffer + and save the result in the file `basename.html`, where + `basename` is the name of the Markdown file with the extension + removed. *Export and View:* press C-c C-c v to export the + file and view it in a browser. *Open:* C-c C-c o will open + the Markdown source file directly using `markdown-open-command`. + *Live Export*: Press C-c C-c l to turn on + `markdown-live-preview-mode` to view the exported output + side-by-side with the source Markdown. **For all export commands, + the output file will be overwritten without notice.** + `markdown-live-preview-window-function` can be customized to open + in a browser other than `eww`. + + To summarize: + + - C-c C-c m: `markdown-command` > `*markdown-output*` buffer. + - C-c C-c p: `markdown-command` > temporary file > browser. + - C-c C-c e: `markdown-command` > `basename.html`. + - C-c C-c v: `markdown-command` > `basename.html` > browser. + - C-c C-c w: `markdown-command` > kill ring. + - C-c C-c o: `markdown-open-command`. + - C-c C-c l: `markdown-live-preview-mode` > `*eww*` buffer. + + C-c C-c c will check for undefined references. If there are + any, a small buffer will open with a list of undefined + references and the line numbers on which they appear. In Emacs + 22 and greater, selecting a reference from this list and + pressing RET will insert an empty reference definition at the + end of the buffer. Similarly, selecting the line number will + jump to the corresponding line. + + C-c C-c n renumbers any ordered lists in the buffer that are + out of sequence. + + C-c C-c ] completes all headings and normalizes all horizontal + rules in the buffer. + + * Following Links: C-c C-o + + Press C-c C-o when the point is on an inline or reference + link to open the URL in a browser. When the point is at a + wiki link, open it in another buffer (in the current window, + or in the other window with the C-u prefix). Use M-p and + M-n to quickly jump to the previous or next link of any type. + + * Jumping: C-c C-l + + Use C-c C-l to jump from the object at point to its counterpart + elsewhere in the text, when possible. Jumps between reference + links and definitions; between footnote markers and footnote + text. If more than one link uses the same reference name, a + new buffer will be created containing clickable buttons for jumping + to each link. You may press TAB or S-TAB to jump between + buttons in this window. + + * Promotion and Demotion: C-c C-- and C-c C-= + + Headings, horizontal rules, and list items can be promoted and + demoted, as well as bold and italic text. For headings, + "promotion" means *decreasing* the level (i.e., moving from + `

` to `

`) while "demotion" means *increasing* the + level. For horizontal rules, promotion and demotion means + moving backward or forward through the list of rule strings in + `markdown-hr-strings`. For bold and italic text, promotion and + demotion means changing the markup from underscores to asterisks. + Press C-c C-- or M-LEFT to promote the element at the point + if possible. + + To remember these commands, note that - is for decreasing the + level (promoting), and = (on the same key as +) is for + increasing the level (demoting). Similarly, the left and right + arrow keys indicate the direction that the atx heading markup + is moving in when promoting or demoting. + + * Completion: C-c C-] + + Complete markup is in normalized form, which means, for + example, that the underline portion of a setext header is the + same length as the heading text, or that the number of leading + and trailing hash marks of an atx header are equal and that + there is no extra whitespace in the header text. C-c C-] + completes the markup at the point, if it is determined to be + incomplete. + + * Editing Lists: M-RET, M-UP, M-DOWN, M-LEFT, and M-RIGHT + + New list items can be inserted with M-RET or C-c C-j. This + command determines the appropriate marker (one of the possible + unordered list markers or the next number in sequence for an + ordered list) and indentation level by examining nearby list + items. If there is no list before or after the point, start a + new list. Prefix this command by C-u to decrease the + indentation by one level. Prefix this command by C-u C-u to + increase the indentation by one level. + + Existing list items can be moved up or down with M-UP or + M-DOWN and indented or exdented with M-RIGHT or M-LEFT. + + * Editing Subtrees: M-S-UP, M-S-DOWN, M-S-LEFT, and M-S-RIGHT + + Entire subtrees of ATX headings can be promoted and demoted + with M-S-LEFT and M-S-RIGHT, which mirror the bindings + for promotion and demotion of list items. Similarly, subtrees + can be moved up and down with M-S-UP and M-S-DOWN. + + Please note the following "boundary" behavior for promotion and + demotion. Any level-six headings will not be demoted further + (i.e., they remain at level six, since Markdown and HTML define + only six levels) and any level-one headings will promoted away + entirely (i.e., heading markup will be removed, since a + level-zero heading is not defined). + + * Shifting the Region: C-c < and C-c > + + Text in the region can be indented or exdented as a group using + C-c > to indent to the next indentation point (calculated in + the current context), and C-c < to exdent to the previous + indentation point. These keybindings are the same as those for + similar commands in `python-mode`. + + * Killing Elements: C-c C-k + + Press C-c C-k to kill the thing at point and add important + text, without markup, to the kill ring. Possible things to + kill include (roughly in order of precedece): inline code, + headings, horizonal rules, links (add link text to kill ring), + images (add alt text to kill ring), angle URIs, email + addresses, bold, italics, reference definitions (add URI to + kill ring), footnote markers and text (kill both marker and + text, add text to kill ring), and list items. + + * Outline Navigation: C-c C-n, C-c C-p, C-c C-f, C-c C-b, and C-c C-u + + Navigation between headings is possible using `outline-mode`. + Use C-c C-n and C-c C-p to move between the next and previous + visible headings. Similarly, C-c C-f and C-c C-b move to the + next and previous visible headings at the same level as the one + at the point. Finally, C-c C-u will move up to a lower-level + (higher precedence) visible heading. + + * Movement by Paragraph or Block: M-{ and M-} + + The definition of a "paragraph" is slightly different in + markdown-mode than, say, text-mode, because markdown-mode + supports filling for list items and respects hard line breaks, + both of which break paragraphs. So, markdown-mode overrides + the usual paragraph navigation commands M-{ and M-} so that + with a C-u prefix, these commands jump to the beginning or + end of an entire block of text, respectively, where "blocks" + are separated by one or more lines. + + * Movement by Defun: C-M-a, C-M-e, and C-M-h + + The usual Emacs commands can be used to move by defuns + (top-level major definitions). In markdown-mode, a defun is a + section. As usual, C-M-a will move the point to the + beginning of the current or preceding defun, C-M-e will move + to the end of the current or following defun, and C-M-h will + put the region around the entire defun. + +As noted, many of the commands above behave differently depending +on whether Transient Mark mode is enabled or not. When it makes +sense, if Transient Mark mode is on and the region is active, the +command applies to the text in the region (e.g., C-c C-s s makes the +region bold). For users who prefer to work outside of Transient +Mark mode, since Emacs 22 it can be enabled temporarily by pressing +C-SPC C-SPC. When this is not the case, many commands then +proceed to look work with the word or line at the point. + +When applicable, commands that specifically act on the region even +outside of Transient Mark mode have the same keybinding as their +standard counterpart, but the letter is uppercase. For example, +`markdown-insert-blockquote` is bound to C-c C-s b and only acts on +the region in Transient Mark mode while `markdown-blockquote-region` +is bound to C-c C-s B and always applies to the region (when nonempty). + +Note that these region-specific functions are useful in many +cases where it may not be obvious. For example, yanking text from +the kill ring sets the mark at the beginning of the yanked text +and moves the point to the end. Therefore, the (inactive) region +contains the yanked text. So, C-y followed by C-c C-s C-b will +yank text and turn it into a blockquote. + +markdown-mode attempts to be flexible in how it handles +indentation. When you press TAB repeatedly, the point will cycle +through several possible indentation levels corresponding to things +you might have in mind when you press RET at the end of a line or +TAB. For example, you may want to start a new list item, +continue a list item with hanging indentation, indent for a nested +pre block, and so on. Exdention is handled similarly when backspace +is pressed at the beginning of the non-whitespace portion of a line. + +markdown-mode supports outline-minor-mode as well as org-mode-style +visibility cycling for atx- or hash-style headings. There are two +types of visibility cycling: Pressing S-TAB cycles globally between +the table of contents view (headings only), outline view (top-level +headings only), and the full document view. Pressing TAB while the +point is at a heading will cycle through levels of visibility for the +subtree: completely folded, visible children, and fully visible. +Note that mixing hash and underline style headings will give undesired +results. + +## Customization + +Although no configuration is *necessary* there are a few things +that can be customized. The M-x customize-mode command +provides an interface to all of the possible customizations: + + * `markdown-command` - the command used to run Markdown (default: + `markdown`). This variable may be customized to pass + command-line options to your Markdown processor of choice. + + * `markdown-command-needs-filename` - set to t if + `markdown-command` does not accept standard input (default: + `nil`). When `nil`, `markdown-mode` will pass the Markdown + content to `markdown-command` using standard input (`stdin`). + When set to t, `markdown-mode` will pass the name of the file + as the final command-line argument to `markdown-command`. Note + that in the latter case, you will only be able to run + `markdown-command` from buffers which are visiting a file. + + * `markdown-open-command` - the command used for calling a standalone + Markdown previewer which is capable of opening Markdown source files + directly (default: `nil`). This command will be called + with a single argument, the filename of the current buffer. + A representative program is the Mac app [Marked 2][], a + live-updating Markdown previewer which can be [called from a + simple shell script](http://jblevins.org/log/marked-2-command). + + * `markdown-hr-strings` - list of strings to use when inserting + horizontal rules. Different strings will not be distinguished + when converted to HTML--they will all be converted to + `
`--but they may add visual distinction and style to plain + text documents. To maintain some notion of promotion and + demotion, keep these sorted from largest to smallest. + + * `markdown-bold-underscore` - set to a non-nil value to use two + underscores when inserting bold text instead of two asterisks + (default: `nil`). + + * `markdown-italic-underscore` - set to a non-nil value to use + underscores when inserting italic text instead of asterisks + (default: `nil`). + + * `markdown-asymmetric-header` - set to a non-nil value to use + asymmetric header styling, placing header characters only on + the left of headers (default: `nil`). + + * `markdown-header-scaling` - set to a non-nil value to use + a variable-pitch font for headings where the size corresponds + to the level of the heading (default: `nil`). + + * `markdown-header-scaling-values` - list of scaling values, + relative to baseline, for headers of levels one through six, + used when `markdown-header-scaling` is non-nil + (default: `(list 1.8 1.4 1.2 1.0 1.0 1.0)`). + + * `markdown-list-indent-width` - depth of indentation for lists + when inserting, promoting, and demoting list items (default: 4). + + * `markdown-indent-function` - the function to use for automatic + indentation (default: `markdown-indent-line`). + + * `markdown-indent-on-enter` - set to a non-nil value to + automatically indent new lines when the enter key is pressed + (default: t) + + * `markdown-enable-wiki-links` - syntax highlighting for wiki + links (default: `nil`). Set this to a non-nil value to turn on + wiki link support by default. Wiki link support can be toggled + later using the function `markdown-toggle-wiki-links`." + + * `markdown-wiki-link-alias-first` - set to a non-nil value to + treat aliased wiki links like `[[link text|PageName]]` + (default: t). When set to nil, they will be treated as + `[[PageName|link text]]`. + + * `markdown-uri-types` - a list of protocol schemes (e.g., "http") + for URIs that `markdown-mode` should highlight. + + * `markdown-enable-math` - syntax highlighting for LaTeX + fragments (default: `nil`). Set this to t to turn on math + support by default. Math support can be enabled, disabled, or + toggled later using the function `markdown-toggle-math`." + + * `markdown-css-paths` - CSS files to link to in XHTML output + (default: `nil`). + + * `markdown-content-type` - when set to a nonempty string, an + `http-equiv` attribute will be included in the XHTML `` + block (default: `""`). If needed, the suggested values are + `application/xhtml+xml` or `text/html`. See also: + `markdown-coding-system`. + + * `markdown-coding-system` - used for specifying the character + set identifier in the `http-equiv` attribute when included + (default: `nil`). See `markdown-content-type`, which must + be set before this variable has any effect. When set to `nil`, + `buffer-file-coding-system` will be used to automatically + determine the coding system string (falling back to + `iso-8859-1` when unavailable). Common settings are `utf-8` + and `iso-latin-1`. + + * `markdown-xhtml-header-content` - additional content to include + in the XHTML `` block (default: `""`). + + * `markdown-xhtml-standalone-regexp` - a regular expression which + `markdown-mode` uses to determine whether the output of + `markdown-command` is a standalone XHTML document or an XHTML + fragment (default: `"^\\(<\\?xml\\|t). + + * `markdown-make-gfm-checkboxes-buttons` - Whether GitHub + Flavored Markdown style task lists (checkboxes) should be + turned into buttons that can be toggled with mouse-1 or RET. If + non-nil (default), then buttons are enabled. This works in + `markdown-mode` as well as `gfm-mode`. + +Additionally, the faces used for syntax highlighting can be modified to +your liking by issuing M-x customize-group RET markdown-faces +or by using the "Markdown Faces" link at the bottom of the mode +customization screen. + +[Marked 2]: https://itunes.apple.com/us/app/marked-2/id890031187?mt=12&uo=4&at=11l5Vs&ct=mm + +## Extensions + +Besides supporting the basic Markdown syntax, Markdown Mode also +includes syntax highlighting for `[[Wiki Links]]`. This can be +enabled by setting `markdown-enable-wiki-links` to a non-nil value. +Wiki links may be followed by pressing C-c C-o when the point +is at a wiki link. Use M-p and M-n to quickly jump to the +previous and next links (including links of other types). +Aliased or piped wiki links of the form `[[link text|PageName]]` +are also supported. Since some wikis reverse these components, set +`markdown-wiki-link-alias-first` to nil to treat them as +`[[PageName|link text]]`. If `markdown-wiki-link-fontify-missing` +is also non-nil, Markdown Mode will highlight wiki links with +missing target file in a different color. By default, Markdown +Mode only searches for target files in the current directory. +Sequential parent directory search (as in [Ikiwiki][]) can be +enabled by setting `markdown-wiki-link-search-parent-directories` +to a non-nil value. + +[Ikiwiki]: https://ikiwiki.info + +[SmartyPants][] support is possible by customizing `markdown-command`. +If you install `SmartyPants.pl` at, say, `/usr/local/bin/smartypants`, +then you can set `markdown-command` to `"markdown | smartypants"`. +You can do this either by using M-x customize-group markdown +or by placing the following in your `.emacs` file: + + (setq markdown-command "markdown | smartypants") + +[SmartyPants]: http://daringfireball.net/projects/smartypants/ + +Syntax highlighting for mathematical expressions written +in LaTeX (only expressions denoted by `$..$`, `$$..$$`, or `\[..\]`) +can be enabled by setting `markdown-enable-math` to a non-nil value, +either via customize or by placing `(setq markdown-enable-math t)` +in `.emacs`, and then restarting Emacs or calling +`markdown-reload-extensions`. + +## GitHub Flavored Markdown (GFM) + +A [GitHub Flavored Markdown][GFM] mode, `gfm-mode`, is also +available. The GitHub implementation differs slightly from +standard Markdown in that it supports things like different +behavior for underscores inside of words, automatic linking of +URLs, strikethrough text, and fenced code blocks with an optional +language keyword. + +The GFM-specific features above apply to `README.md` files, wiki +pages, and other Markdown-formatted files in repositories on +GitHub. GitHub also enables [additional features][GFM comments] for +writing on the site (for issues, pull requests, messages, etc.) +that are further extensions of GFM. These features include task +lists (checkboxes), newlines corresponding to hard line breaks, +auto-linked references to issues and commits, wiki links, and so +on. To make matters more confusing, although task lists are not +part of [GFM proper][GFM], [since 2014][] they are rendered (in a +read-only fashion) in all Markdown documents in repositories on the +site. These additional extensions are supported to varying degrees +by `markdown-mode` and `gfm-mode` as described below. + +* **URL autolinking:** Both `markdown-mode` and `gfm-mode` support + highlighting of URLs without angle brackets. + +* **Multiple underscores in words:** You must enable `gfm-mode` to + toggle support for underscores inside of words. In this mode + variable names such as `a_test_variable` will not trigger + emphasis (italics). + +* **Fenced code blocks:** Code blocks quoted with backticks, with + optional programming language keywords, are highlighted in + both `markdown-mode` and `gfm-mode`. They can be inserted with + C-c C-s P. If there is an active region, the text in the + region will be placed inside the code block. You will be + prompted for the name of the language, but may press enter to + continue without naming a language. + +* **Strikethrough:** Strikethrough text is only supported in + `gfm-mode` and can be inserted (and toggled) using C-c C-s d. + Following the mnemonics for the other style keybindings, the + letter d coincides with the HTML tag ``. + +* **Task lists:** GFM task lists will be rendered as checkboxes + (Emacs buttons) in both `markdown-mode` and `gfm-mode` when + `markdown-make-gfm-checkboxes-buttons` is set to a non-nil value + (and it is set to t by default). These checkboxes can be + toggled by clicking `mouse-1` or pressing RET over the button. + +* **Wiki links:** Generic wiki links are supported in + `markdown-mode`, but in `gfm-mode` specifically they will be + treated as they are on GitHub: spaces will be replaced by hyphens + in filenames and the first letter of the filename will be + capitalized. For example, `[[wiki link]]` will map to a file + named `Wiki-link` with the same extension as the current file. + +* **Newlines:** Neither `markdown-mode` nor `gfm-mode` do anything + specifically with respect to newline behavior. If you use + `gfm-mode` mostly to write text for comments or issues on the + GitHub site--where newlines are significant and correspond to + hard line breaks--then you may want to enable `visual-line-mode` + for line wrapping in buffers. You can do this with a + `gfm-mode-hook` as follows: + + ;; Use visual-line-mode in gfm-mode + (defun my-gfm-mode-hook () + (visual-line-mode 1)) + (add-hook 'gfm-mode-hook 'my-gfm-mode-hook) + +* **Preview:** GFM-specific preview can be powered by setting + `markdown-command` to use [Docter][]. This may also be + configured to work with [Marked 2][] for `markdown-open-command`. + +[GFM]: http://github.github.com/github-flavored-markdown/ +[GFM comments]: https://help.github.com/articles/writing-on-github/ +[since 2014]: https://github.com/blog/1825-task-lists-in-all-markdown-documents +[Docter]: https://github.com/alampros/Docter + +## Acknowledgments + +markdown-mode has benefited greatly from the efforts of the +following people: + + * Cyril Brulebois for Debian packaging. + * Conal Elliott for a font-lock regexp patch. + * Edward O'Connor for a font-lock regexp fix and + GitHub Flavored Markdown mode (`gfm-mode`). + * Greg Bognar for menus and running + `markdown` with an active region. + * Daniel Burrows for filing Debian bug #456592. + * Peter S. Galbraith for maintaining `emacs-goodies-el`. + * Dmitry Dzhus for undefined reference checking. + * Carsten Dominik for `org-mode`, from which the + visibility cycling functionality was derived, and for a bug fix + related to `orgtbl-mode`. + * Bryan Kyle for indentation code. + * Ben Voui for font-lock face customizations. + * Ankit Solanki for `longlines.el` + compatibility and custom CSS. + * Hilko Bengen for proper XHTML output. + * Jose A. Ortega Ruiz for Emacs 23 fixes. + * Nelson Minar for `html-helper-mode`, from which + comment matching functions were derived. + * Alec Resnick for bug reports. + * Joost Kremers for footnote-handling + functions, bug reports regarding indentation, and + fixes for byte-compilation warnings. + * Peter Williams for `fill-paragraph` + enhancements. + * George Ogata for fixing several + byte-compilation warnings. + * Eric Merritt for wiki link features. + * Philippe Ivaldi for XHTML preview + customizations and XHTML export. + * Jeremiah Dodds for supporting + Markdown processors which do not accept input from stdin. + * Werner Dittmann for bug reports + regarding the `cl` dependency and `auto-fill-mode` and indentation. + * Scott Pfister for generalizing the space + substitution character for mapping wiki links to filenames. + * Marcin Kasperski for a patch to + escape shell commands. + * Christopher J. Madsen for patches to fix a match + data bug and to prefer `visual-line-mode` in `gfm-mode`. + * Shigeru Fukaya for better adherence to + Emacs Lisp coding conventions. + * Donald Ephraim Curtis for fixing the `fill-paragraph` + regexp, refactoring the compilation and preview functions, + heading font-lock generalizations, list renumbering, + and kill ring save. + * Kevin Porter for wiki link handling in `gfm-mode`. + * Max Penet and Peter Eisentraut + for an autoload token for `gfm-mode`. + * Ian Yang for improving the reference definition regex. + * Akinori Musha for an imenu index function. + * Michael Sperber for XEmacs fixes. + * Francois Gannaz for suggesting charset + declaration in XHTML output. + * Zhenlei Jia for smart exdention function. + * Matus Goljer for improved wiki link following + and GFM code block insertion. + * Peter Jones for link following functions. + * Bryan Fink for a bug report regarding + externally modified files. + * Vegard Vesterheim for a bug fix + related to `orgtbl-mode`. + * Makoto Motohashi for before- and after- + export hooks, unit test improvements, and updates to support + wide characters. + * Michael Dwyer for `gfm-mode` underscore regexp. + * Chris Lott for suggesting reference label + completion. + * Gunnar Franke for a completion bug report. + * David Glasser for a `paragraph-separate` fix. + * Daniel Brotsky for better auto-fill defaults. + * Samuel Freilich for improved filling + behavior regarding list items, footnotes, and reference + definitions, improved killing of footnotes, and numerous other + tests and bug fixes. + * Antonis Kanouras for strikethrough support. + * Tim Visher for multiple CSS files and other + general improvements. + * Matt McClure for a patch to prevent + overwriting source files with .html extensions upon export. + * Roger Bolsius for ordered list improvements. + * Google's Open Source Programs Office for recognizing the project with + a monetary contribution in June 2015. + * Howard Melman for supporting GFM checkboxes + as buttons. + * Danny McClanahan for live preview + mode, completion of GFM programming language names, improved + font lock for fenced code blocks and metadata blocks, `cl-lib` + updates, and numerous other improvements. + * Syohei Yoshida for better heading detection + and movement functions, improved italic font lock, fixing adaptive + filling for hanging list items, more efficient fontification, + and numerous other improvements. + * Vitalie Spinu for improvements to font + lock and source code aesthetics. + * Kévin Le Gouguec for improvements + related to ATX headings and Pandoc fancy lists. + +## Bugs + +markdown-mode is developed and tested primarily for compatibility +with GNU Emacs 24.3 and later. If you find any bugs in +markdown-mode, please construct a test case or a patch and open a +ticket on the [GitHub issue tracker][issues]. + + [issues]: https://github.com/jrblevin/markdown-mode/issues + +## History + +markdown-mode was written and is maintained by Jason Blevins. The +first version was released on May 24, 2007. + + * 2007-05-24: Version 1.1 + * 2007-05-25: Version 1.2 + * 2007-06-05: [Version 1.3][] + * 2007-06-29: Version 1.4 + * 2007-10-11: [Version 1.5][] + * 2008-06-04: [Version 1.6][] + * 2009-10-01: [Version 1.7][] + * 2011-08-12: [Version 1.8][] + * 2011-08-15: [Version 1.8.1][] + * 2013-01-25: [Version 1.9][] + * 2013-03-24: [Version 2.0][] + * 2016-01-09: [Version 2.1][] + +[Version 1.3]: http://jblevins.org/projects/markdown-mode/rev-1-3 +[Version 1.5]: http://jblevins.org/projects/markdown-mode/rev-1-5 +[Version 1.6]: http://jblevins.org/projects/markdown-mode/rev-1-6 +[Version 1.7]: http://jblevins.org/projects/markdown-mode/rev-1-7 +[Version 1.8]: http://jblevins.org/projects/markdown-mode/rev-1-8 +[Version 1.8.1]: http://jblevins.org/projects/markdown-mode/rev-1-8-1 +[Version 1.9]: http://jblevins.org/projects/markdown-mode/rev-1-9 +[Version 2.0]: http://jblevins.org/projects/markdown-mode/rev-2-0 +[Version 2.1]: http://jblevins.org/projects/markdown-mode/rev-2-1 + diff --git a/markdown-mode.el b/markdown-mode.el new file mode 100644 index 0000000..b6fb64f --- /dev/null +++ b/markdown-mode.el @@ -0,0 +1,6842 @@ +;;; markdown-mode.el --- Major mode for Markdown-formatted text -*- lexical-binding: t; -*- + +;; Copyright (C) 2007-2016 Jason R. Blevins +;; Copyright (C) 2007, 2009 Edward O'Connor +;; Copyright (C) 2007 Conal Elliott +;; Copyright (C) 2008 Greg Bognar +;; Copyright (C) 2008 Dmitry Dzhus +;; Copyright (C) 2008 Bryan Kyle +;; Copyright (C) 2008 Ben Voui +;; Copyright (C) 2009 Ankit Solanki +;; Copyright (C) 2009 Hilko Bengen +;; Copyright (C) 2009 Peter Williams +;; Copyright (C) 2010 George Ogata +;; Copyright (C) 2011 Eric Merritt +;; Copyright (C) 2011 Philippe Ivaldi +;; Copyright (C) 2011 Jeremiah Dodds +;; Copyright (C) 2011 Christopher J. Madsen +;; Copyright (C) 2011 Shigeru Fukaya +;; Copyright (C) 2011 Joost Kremers +;; Copyright (C) 2011-2012 Donald Ephraim Curtis +;; Copyright (C) 2012 Akinori Musha +;; Copyright (C) 2012 Zhenlei Jia +;; Copyright (C) 2012 Peter Jones +;; Copyright (C) 2013 Matus Goljer +;; Copyright (C) 2015 Google, Inc. (Contributor: Samuel Freilich ) +;; Copyright (C) 2015 Antonis Kanouras +;; Copyright (C) 2015 Howard Melman +;; Copyright (C) 2015-2016 Danny McClanahan +;; Copyright (C) 2015-2016 Syohei Yoshida +;; Copyright (C) 2016 Vitalie Spinu + +;; Author: Jason R. Blevins +;; Maintainer: Jason R. Blevins +;; Created: May 24, 2007 +;; Version: 2.1 +;; Package-Requires: ((emacs "24") (cl-lib "0.5")) +;; Keywords: Markdown, GitHub Flavored Markdown, itex +;; URL: http://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 2, 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, write to the Free Software +;; Foundation, Inc., 51 Franklin Street, Fifth Floor, +;; Boston, MA 02110-1301, USA. + +;;; Commentary: + +;; markdown-mode is a major mode for editing [Markdown][]-formatted +;; text. The latest stable version is markdown-mode 2.1, released on +;; January 9, 2016. See the [release notes][] for details. +;; markdown-mode is free software, licensed under the GNU GPL. + +;; ![Markdown Mode Screenshot](http://jblevins.org/projects/markdown-mode/screenshots/20160108-001.png) + +;; [Markdown]: http://daringfireball.net/projects/markdown/ +;; [release notes]: http://jblevins.org/projects/markdown-mode/rev-2-1 + +;;; Installation: + +;; The recommended way to install markdown-mode is to install the package +;; from [MELPA Stable](https://stable.melpa.org/#/markdown-mode) +;; using `package.el'. First, configure `package.el' and the MELPA Stable +;; repository by adding the following to your `.emacs', `init.el', +;; or equivalent startup file: + +;; (require 'package) +;; (add-to-list 'package-archives +;; '("melpa-stable" . "https://stable.melpa.org/packages/")) +;; (package-initialize) + +;; Then, after restarting Emacs or evaluating the above statements, issue +;; the following command: `M-x package-install RET markdown-mode RET`. +;; When installed this way, the major modes `markdown-mode' and `gfm-mode' +;; will be autoloaded and `markdown-mode' will be used for file names +;; ending in either `.md` or `.markdown`. +;; +;; Alternatively, if you manage loading packages with [use-package][] +;; then you can automatically install and configure `markdown-mode' by +;; adding a declaration such as this one to your init file (as an +;; example; adjust settings as desired): +;; +;; (use-package markdown-mode +;; :ensure t +;; :commands (markdown-mode gfm-mode) +;; :mode (("README\\.md\\'" . gfm-mode) +;; ("\\.md\\'" . markdown-mode) +;; ("\\.markdown\\'" . markdown-mode)) +;; :init (setq markdown-command "multimarkdown")) + +;; [MELPA Stable]: http://stable.melpa.org/ +;; [use-package]: https://github.com/jwiegley/use-package + +;; **Direct Download** + +;; Alternatively you can manually download and install markdown-mode. +;; First, download the [latest stable version][markdown-mode.el] and +;; save the file where Emacs can find it (i.e., a directory in your +;; `load-path'). You can then configure `markdown-mode' and `gfm-mode' +;; to load automatically by adding the following to your init file: + +;; (autoload 'markdown-mode "markdown-mode" +;; "Major mode for editing Markdown files" t) +;; (add-to-list 'auto-mode-alist '("\\.markdown\\'" . markdown-mode)) +;; (add-to-list 'auto-mode-alist '("\\.md\\'" . markdown-mode)) +;; +;; (autoload 'gfm-mode "markdown-mode" +;; "Major mode for editing GitHub Flavored Markdown files" t) +;; (add-to-list 'auto-mode-alist '("README\\.md\\'" . gfm-mode)) + +;; [markdown-mode.el]: http://jblevins.org/projects/markdown-mode/markdown-mode.el + +;; **Development Version** + +;; To follow or contribute to markdown-mode development, you can +;; browse or clone the Git repository +;; [on GitHub](https://github.com/jrblevin/markdown-mode): + +;; git clone https://github.com/jrblevin/markdown-mode.git + +;; If you prefer to install and use the development version, which may +;; become unstable at some times, you can either clone the Git +;; repository as above or install markdown-mode from +;; [MELPA](https://melpa.org/#/markdown-mode). +;; +;; If you clone the repository directly, then make sure that Emacs can +;; find it by adding the following line to your startup file: +;; +;; (add-to-list 'load-path "/path/to/markdown-mode/repository") + +;; **Packaged Installation** + +;; markdown-mode is also available in several package managers. You +;; may want to confirm that the package you install contains the +;; latest stable version first (and please notify the package +;; maintainer if not). +;; +;; * Debian Linux: [elpa-markdown-mode][] and [emacs-goodies-el][] +;; * Ubuntu Linux: [elpa-markdown-mode][elpa-ubuntu] and [emacs-goodies-el][emacs-goodies-el-ubuntu] +;; * RedHat and Fedora Linux: [emacs-goodies][] +;; * NetBSD: [textproc/markdown-mode][] +;; * MacPorts: [markdown-mode.el][macports-package] ([pending][macports-ticket]) +;; * FreeBSD: [textproc/markdown-mode.el][freebsd-port] +;; +;; [elpa-markdown-mode]: https://packages.debian.org/sid/lisp/elpa-markdown-mode +;; [elpa-ubuntu]: http://packages.ubuntu.com/search?keywords=elpa-markdown-mode +;; [emacs-goodies-el]: http://packages.debian.org/emacs-goodies-el +;; [emacs-goodies-el-ubuntu]: http://packages.ubuntu.com/search?keywords=emacs-goodies-el +;; [emacs-goodies]: https://apps.fedoraproject.org/packages/emacs-goodies +;; [textproc/markdown-mode]: http://pkgsrc.se/textproc/markdown-mode +;; [macports-package]: https://trac.macports.org/browser/trunk/dports/editors/markdown-mode.el/Portfile +;; [macports-ticket]: http://trac.macports.org/ticket/35716 +;; [freebsd-port]: http://svnweb.freebsd.org/ports/head/textproc/markdown-mode.el + +;; **Dependencies** + +;; `markdown-mode' depends on `cl-lib', which has been bundled with +;; GNU Emacs since 24.3. Users of GNU Emacs 24.1 and 24.2 can install +;; `cl-lib' with `package.el'. + +;;; Usage: + +;; Keybindings are grouped by prefixes based on their function. For +;; example, the commands for inserting links are grouped under `C-c +;; C-a`, where the `C-a` is a mnemonic for the HTML `
` tag. In +;; other cases, the connection to HTML is not direct. For example, +;; commands dealing with headings begin with `C-c C-t` (mnemonic: +;; titling). The primary commands in each group will are described +;; below. You can obtain a list of all keybindings by pressing `C-c +;; C-h`. Movement and shifting commands tend to be associated with +;; paired delimiters such as `M-{` and `M-}` or `C-c <` and `C-c >`. +;; Outline navigation keybindings the same as in `org-mode'. Finally, +;; commands for running Markdown or doing maintenance on an open file +;; are grouped under the `C-c C-c` prefix. The most commonly used +;; commands are described below. You can obtain a list of all +;; keybindings by pressing `C-c C-h`. +;; +;; * Hyperlinks: `C-c C-a` +;; +;; In this group, `C-c C-a l` inserts an inline link of the form +;; `[text](url)`. The link text is determined as follows. First, +;; if there is an active region (i.e., when transient mark mode is +;; on and the mark is active), use it as the link text. Second, +;; if the point is at a word, use that word as the link text. In +;; these two cases, the original text will be replaced with the +;; link and point will be left at the position for inserting a +;; URL. Otherwise, insert empty link markup and place the point +;; for inserting the link text. +;; +;; `C-c C-a L` inserts a reference link of the form `[text][label]` +;; and, optionally, a corresponding reference label definition. +;; The link text is determined in the same way as with an inline +;; link (using the region, when active, or the word at the point), +;; but instead of inserting empty markup as a last resort, the +;; link text will be read from the minibuffer. The reference +;; label will be read from the minibuffer in both cases, with +;; completion from the set of currently defined references. To +;; create an implicit reference link, press `RET` to accept the +;; default, an empty label. If the entered referenced label is +;; not defined, additionally prompt for the URL and (optional) +;; title. If a URL is provided, a reference definition will be +;; inserted in accordance with `markdown-reference-location'. +;; If a title is given, it will be added to the end of the +;; reference definition and will be used to populate the title +;; attribute when converted to XHTML. +;; +;; `C-c C-a u` inserts a bare url, delimited by angle brackets. When +;; there is an active region, the text in the region is used as the +;; URL. If the point is at a URL, that url is used. Otherwise, +;; insert angle brackets and position the point in between them +;; for inserting the URL. +;; +;; `C-c C-a f` inserts a footnote marker at the point, inserts a +;; footnote definition below, and positions the point for +;; inserting the footnote text. Note that footnotes are an +;; extension to Markdown and are not supported by all processors. +;; +;; `C-c C-a w` behaves much like the inline link insertion command +;; and inserts a wiki link of the form `[[WikiLink]]`. If there +;; is an active region, use the region as the link text. If the +;; point is at a word, use the word as the link text. If there is +;; no active region and the point is not at word, simply insert +;; link markup. Note that wiki links are an extension to Markdown +;; and are not supported by all processors. +;; +;; * Images: `C-c C-i` +;; +;; `C-c C-i i` inserts markup for an inline image, using the +;; active region or the word at point, if any, as the alt text. +;; `C-c C-i I` behaves similarly and inserts a reference-style +;; image. +;; +;; Local images associated with image links may be displayed +;; inline in the buffer by pressing `C-c C-i C-t` +;; (`markdown-toggle-inline-images'). This is a toggle command, so +;; pressing this once again will remove inline images. +;; +;; * Styles: `C-c C-s` +;; +;; `C-c C-s e` inserts markup to make a region or word italic (`e` +;; for `` or emphasis). If there is an active region, make +;; the region italic. If the point is at a non-italic word, make +;; the word italic. If the point is at an italic word or phrase, +;; remove the italic markup. Otherwise, simply insert italic +;; delimiters and place the cursor in between them. Similarly, +;; use `C-c C-s s` for bold (``), `C-c C-s c` for +;; inline code (``), and `C-c C-s k` for inserting `` +;; tags. +;; +;; `C-c C-s b` inserts a blockquote using the active region, if any, +;; or starts a new blockquote. `C-c C-s C-b` is a variation which +;; always operates on the region, regardless of whether it is +;; active or not. The appropriate amount of indentation, if any, +;; is calculated automatically given the surrounding context, but +;; may be adjusted later using the region indentation commands. +;; +;; `C-c C-s p` behaves similarly for inserting preformatted code +;; blocks, with `C-c C-s C-p` being the region-only counterpart. +;; +;; * Headings: `C-c C-t` +;; +;; All heading insertion commands use the text in the active +;; region, if any, as the heading text. Otherwise, if the current +;; line is not blank, they use the text on the current line. +;; Finally, the setext commands will prompt for heading text if +;; there is no active region and the current line is blank. +;; +;; `C-c C-t h` inserts a heading with automatically chosen type and +;; level (both determined by the previous heading). `C-c C-t H` +;; behaves similarly, but uses setext (underlined) headings when +;; possible, still calculating the level automatically. +;; In cases where the automatically-determined level is not what +;; you intended, the level can be quickly promoted or demoted +;; (as described below). Alternatively, a `C-u` prefix can be +;; given to insert a heading promoted by one level or a `C-u C-u` +;; prefix can be given to insert a heading demoted by one level. +;; +;; To insert a heading of a specific level and type, use `C-c C-t 1` +;; through `C-c C-t 6` for atx (hash mark) headings and `C-c C-t !` or +;; `C-c C-t @` for setext headings of level one or two, respectively. +;; Note that `!` is `S-1` and `@` is `S-2`. +;; +;; If the point is at a heading, these commands will replace the +;; existing markup in order to update the level and/or type of the +;; heading. To remove the markup of the heading at the point, +;; press `C-c C-k` to kill the heading and press `C-y` to yank the +;; heading text back into the buffer. +;; +;; * Horizontal Rules: `C-c -` +;; +;; `C-c -` inserts a horizontal rule. By default, insert the +;; first string in the list `markdown-hr-strings' (the most +;; prominent rule). With a `C-u` prefix, insert the last string. +;; With a numeric prefix `N`, insert the string in position `N` +;; (counting from 1). +;; +;; * Markdown and Maintenance Commands: `C-c C-c` +;; +;; *Compile:* `C-c C-c m` will run Markdown on the current buffer +;; and show the output in another buffer. *Preview*: `C-c C-c p` +;; runs Markdown on the current buffer and previews, stores the +;; output in a temporary file, and displays the file in a browser. +;; *Export:* `C-c C-c e` will run Markdown on the current buffer +;; and save the result in the file `basename.html`, where +;; `basename` is the name of the Markdown file with the extension +;; removed. *Export and View:* press `C-c C-c v` to export the +;; file and view it in a browser. *Open:* `C-c C-c o` will open +;; the Markdown source file directly using `markdown-open-command'. +;; *Live Export*: Press `C-c C-c l` to turn on +;; `markdown-live-preview-mode' to view the exported output +;; side-by-side with the source Markdown. **For all export commands, +;; the output file will be overwritten without notice.** +;; `markdown-live-preview-window-function' can be customized to open +;; in a browser other than `eww'. +;; +;; To summarize: +;; +;; - `C-c C-c m`: `markdown-command' > `*markdown-output*` buffer. +;; - `C-c C-c p`: `markdown-command' > temporary file > browser. +;; - `C-c C-c e`: `markdown-command' > `basename.html`. +;; - `C-c C-c v`: `markdown-command' > `basename.html` > browser. +;; - `C-c C-c w`: `markdown-command' > kill ring. +;; - `C-c C-c o`: `markdown-open-command'. +;; - `C-c C-c l`: `markdown-live-preview-mode' > `*eww*` buffer. +;; +;; `C-c C-c c` will check for undefined references. If there are +;; any, a small buffer will open with a list of undefined +;; references and the line numbers on which they appear. In Emacs +;; 22 and greater, selecting a reference from this list and +;; pressing `RET` will insert an empty reference definition at the +;; end of the buffer. Similarly, selecting the line number will +;; jump to the corresponding line. +;; +;; `C-c C-c n` renumbers any ordered lists in the buffer that are +;; out of sequence. +;; +;; `C-c C-c ]` completes all headings and normalizes all horizontal +;; rules in the buffer. +;; +;; * Following Links: `C-c C-o` +;; +;; Press `C-c C-o` when the point is on an inline or reference +;; link to open the URL in a browser. When the point is at a +;; wiki link, open it in another buffer (in the current window, +;; or in the other window with the `C-u` prefix). Use `M-p` and +;; `M-n` to quickly jump to the previous or next link of any type. +;; +;; * Jumping: `C-c C-l` +;; +;; Use `C-c C-l` to jump from the object at point to its counterpart +;; elsewhere in the text, when possible. Jumps between reference +;; links and definitions; between footnote markers and footnote +;; text. If more than one link uses the same reference name, a +;; new buffer will be created containing clickable buttons for jumping +;; to each link. You may press `TAB` or `S-TAB` to jump between +;; buttons in this window. +;; +;; * Promotion and Demotion: `C-c C--` and `C-c C-=` +;; +;; Headings, horizontal rules, and list items can be promoted and +;; demoted, as well as bold and italic text. For headings, +;; "promotion" means *decreasing* the level (i.e., moving from +;; `

` to `

`) while "demotion" means *increasing* the +;; level. For horizontal rules, promotion and demotion means +;; moving backward or forward through the list of rule strings in +;; `markdown-hr-strings'. For bold and italic text, promotion and +;; demotion means changing the markup from underscores to asterisks. +;; Press `C-c C--` or `M-LEFT` to promote the element at the point +;; if possible. +;; +;; To remember these commands, note that `-` is for decreasing the +;; level (promoting), and `=` (on the same key as `+`) is for +;; increasing the level (demoting). Similarly, the left and right +;; arrow keys indicate the direction that the atx heading markup +;; is moving in when promoting or demoting. +;; +;; * Completion: `C-c C-]` +;; +;; Complete markup is in normalized form, which means, for +;; example, that the underline portion of a setext header is the +;; same length as the heading text, or that the number of leading +;; and trailing hash marks of an atx header are equal and that +;; there is no extra whitespace in the header text. `C-c C-]` +;; completes the markup at the point, if it is determined to be +;; incomplete. +;; +;; * Editing Lists: `M-RET`, `M-UP`, `M-DOWN`, `M-LEFT`, and `M-RIGHT` +;; +;; New list items can be inserted with `M-RET` or `C-c C-j`. This +;; command determines the appropriate marker (one of the possible +;; unordered list markers or the next number in sequence for an +;; ordered list) and indentation level by examining nearby list +;; items. If there is no list before or after the point, start a +;; new list. Prefix this command by `C-u` to decrease the +;; indentation by one level. Prefix this command by `C-u C-u` to +;; increase the indentation by one level. +;; +;; Existing list items can be moved up or down with `M-UP` or +;; `M-DOWN` and indented or exdented with `M-RIGHT` or `M-LEFT`. +;; +;; * Editing Subtrees: `M-S-UP`, `M-S-DOWN`, `M-S-LEFT`, and `M-S-RIGHT` +;; +;; Entire subtrees of ATX headings can be promoted and demoted +;; with `M-S-LEFT` and `M-S-RIGHT`, which mirror the bindings +;; for promotion and demotion of list items. Similarly, subtrees +;; can be moved up and down with `M-S-UP` and `M-S-DOWN`. +;; +;; Please note the following "boundary" behavior for promotion and +;; demotion. Any level-six headings will not be demoted further +;; (i.e., they remain at level six, since Markdown and HTML define +;; only six levels) and any level-one headings will promoted away +;; entirely (i.e., heading markup will be removed, since a +;; level-zero heading is not defined). +;; +;; * Shifting the Region: `C-c <` and `C-c >` +;; +;; Text in the region can be indented or exdented as a group using +;; `C-c >` to indent to the next indentation point (calculated in +;; the current context), and `C-c <` to exdent to the previous +;; indentation point. These keybindings are the same as those for +;; similar commands in `python-mode'. +;; +;; * Killing Elements: `C-c C-k` +;; +;; Press `C-c C-k` to kill the thing at point and add important +;; text, without markup, to the kill ring. Possible things to +;; kill include (roughly in order of precedece): inline code, +;; headings, horizonal rules, links (add link text to kill ring), +;; images (add alt text to kill ring), angle URIs, email +;; addresses, bold, italics, reference definitions (add URI to +;; kill ring), footnote markers and text (kill both marker and +;; text, add text to kill ring), and list items. +;; +;; * Outline Navigation: `C-c C-n`, `C-c C-p`, `C-c C-f`, `C-c C-b`, and `C-c C-u` +;; +;; Navigation between headings is possible using `outline-mode'. +;; Use `C-c C-n` and `C-c C-p` to move between the next and previous +;; visible headings. Similarly, `C-c C-f` and `C-c C-b` move to the +;; next and previous visible headings at the same level as the one +;; at the point. Finally, `C-c C-u` will move up to a lower-level +;; (higher precedence) visible heading. +;; +;; * Movement by Paragraph or Block: `M-{` and `M-}` +;; +;; The definition of a "paragraph" is slightly different in +;; markdown-mode than, say, text-mode, because markdown-mode +;; supports filling for list items and respects hard line breaks, +;; both of which break paragraphs. So, markdown-mode overrides +;; the usual paragraph navigation commands `M-{` and `M-}` so that +;; with a `C-u` prefix, these commands jump to the beginning or +;; end of an entire block of text, respectively, where "blocks" +;; are separated by one or more lines. +;; +;; * Movement by Defun: `C-M-a`, `C-M-e`, and `C-M-h` +;; +;; The usual Emacs commands can be used to move by defuns +;; (top-level major definitions). In markdown-mode, a defun is a +;; section. As usual, `C-M-a` will move the point to the +;; beginning of the current or preceding defun, `C-M-e` will move +;; to the end of the current or following defun, and `C-M-h` will +;; put the region around the entire defun. +;; +;; As noted, many of the commands above behave differently depending +;; on whether Transient Mark mode is enabled or not. When it makes +;; sense, if Transient Mark mode is on and the region is active, the +;; command applies to the text in the region (e.g., `C-c C-s s` makes the +;; region bold). For users who prefer to work outside of Transient +;; Mark mode, since Emacs 22 it can be enabled temporarily by pressing +;; `C-SPC C-SPC`. When this is not the case, many commands then +;; proceed to look work with the word or line at the point. +;; +;; When applicable, commands that specifically act on the region even +;; outside of Transient Mark mode have the same keybinding as their +;; standard counterpart, but the letter is uppercase. For example, +;; `markdown-insert-blockquote' is bound to `C-c C-s b` and only acts on +;; the region in Transient Mark mode while `markdown-blockquote-region' +;; is bound to `C-c C-s B` and always applies to the region (when nonempty). +;; +;; Note that these region-specific functions are useful in many +;; cases where it may not be obvious. For example, yanking text from +;; the kill ring sets the mark at the beginning of the yanked text +;; and moves the point to the end. Therefore, the (inactive) region +;; contains the yanked text. So, `C-y` followed by `C-c C-s C-b` will +;; yank text and turn it into a blockquote. +;; +;; markdown-mode attempts to be flexible in how it handles +;; indentation. When you press `TAB` repeatedly, the point will cycle +;; through several possible indentation levels corresponding to things +;; you might have in mind when you press `RET` at the end of a line or +;; `TAB`. For example, you may want to start a new list item, +;; continue a list item with hanging indentation, indent for a nested +;; pre block, and so on. Exdention is handled similarly when backspace +;; is pressed at the beginning of the non-whitespace portion of a line. +;; +;; markdown-mode supports outline-minor-mode as well as org-mode-style +;; visibility cycling for atx- or hash-style headings. There are two +;; types of visibility cycling: Pressing `S-TAB` cycles globally between +;; the table of contents view (headings only), outline view (top-level +;; headings only), and the full document view. Pressing `TAB` while the +;; point is at a heading will cycle through levels of visibility for the +;; subtree: completely folded, visible children, and fully visible. +;; Note that mixing hash and underline style headings will give undesired +;; results. + +;;; Customization: + +;; Although no configuration is *necessary* there are a few things +;; that can be customized. The `M-x customize-mode` command +;; provides an interface to all of the possible customizations: +;; +;; * `markdown-command' - the command used to run Markdown (default: +;; `markdown`). This variable may be customized to pass +;; command-line options to your Markdown processor of choice. +;; +;; * `markdown-command-needs-filename' - set to `t' if +;; `markdown-command' does not accept standard input (default: +;; `nil'). When `nil', `markdown-mode' will pass the Markdown +;; content to `markdown-command' using standard input (`stdin`). +;; When set to `t', `markdown-mode' will pass the name of the file +;; as the final command-line argument to `markdown-command'. Note +;; that in the latter case, you will only be able to run +;; `markdown-command' from buffers which are visiting a file. +;; +;; * `markdown-open-command' - the command used for calling a standalone +;; Markdown previewer which is capable of opening Markdown source files +;; directly (default: `nil'). This command will be called +;; with a single argument, the filename of the current buffer. +;; A representative program is the Mac app [Marked 2][], a +;; live-updating Markdown previewer which can be [called from a +;; simple shell script](http://jblevins.org/log/marked-2-command). +;; +;; * `markdown-hr-strings' - list of strings to use when inserting +;; horizontal rules. Different strings will not be distinguished +;; when converted to HTML--they will all be converted to +;; `
`--but they may add visual distinction and style to plain +;; text documents. To maintain some notion of promotion and +;; demotion, keep these sorted from largest to smallest. +;; +;; * `markdown-bold-underscore' - set to a non-nil value to use two +;; underscores when inserting bold text instead of two asterisks +;; (default: `nil'). +;; +;; * `markdown-italic-underscore' - set to a non-nil value to use +;; underscores when inserting italic text instead of asterisks +;; (default: `nil'). +;; +;; * `markdown-asymmetric-header' - set to a non-nil value to use +;; asymmetric header styling, placing header characters only on +;; the left of headers (default: `nil'). +;; +;; * `markdown-header-scaling' - set to a non-nil value to use +;; a variable-pitch font for headings where the size corresponds +;; to the level of the heading (default: `nil'). +;; +;; * `markdown-header-scaling-values' - list of scaling values, +;; relative to baseline, for headers of levels one through six, +;; used when `markdown-header-scaling' is non-nil +;; (default: `(list 1.8 1.4 1.2 1.0 1.0 1.0)`). +;; +;; * `markdown-list-indent-width' - depth of indentation for lists +;; when inserting, promoting, and demoting list items (default: 4). +;; +;; * `markdown-indent-function' - the function to use for automatic +;; indentation (default: `markdown-indent-line'). +;; +;; * `markdown-indent-on-enter' - set to a non-nil value to +;; automatically indent new lines when the enter key is pressed +;; (default: `t') +;; +;; * `markdown-enable-wiki-links' - syntax highlighting for wiki +;; links (default: `nil'). Set this to a non-nil value to turn on +;; wiki link support by default. Wiki link support can be toggled +;; later using the function `markdown-toggle-wiki-links'." +;; +;; * `markdown-wiki-link-alias-first' - set to a non-nil value to +;; treat aliased wiki links like `[[link text|PageName]]` +;; (default: `t'). When set to nil, they will be treated as +;; `[[PageName|link text]]'. +;; +;; * `markdown-uri-types' - a list of protocol schemes (e.g., "http") +;; for URIs that `markdown-mode' should highlight. +;; +;; * `markdown-enable-math' - syntax highlighting for LaTeX +;; fragments (default: `nil'). Set this to `t' to turn on math +;; support by default. Math support can be enabled, disabled, or +;; toggled later using the function `markdown-toggle-math'." +;; +;; * `markdown-css-paths' - CSS files to link to in XHTML output +;; (default: `nil`). +;; +;; * `markdown-content-type' - when set to a nonempty string, an +;; `http-equiv` attribute will be included in the XHTML `` +;; block (default: `""`). If needed, the suggested values are +;; `application/xhtml+xml` or `text/html`. See also: +;; `markdown-coding-system'. +;; +;; * `markdown-coding-system' - used for specifying the character +;; set identifier in the `http-equiv` attribute when included +;; (default: `nil'). See `markdown-content-type', which must +;; be set before this variable has any effect. When set to `nil', +;; `buffer-file-coding-system' will be used to automatically +;; determine the coding system string (falling back to +;; `iso-8859-1' when unavailable). Common settings are `utf-8' +;; and `iso-latin-1'. +;; +;; * `markdown-xhtml-header-content' - additional content to include +;; in the XHTML `` block (default: `""`). +;; +;; * `markdown-xhtml-standalone-regexp' - a regular expression which +;; `markdown-mode' uses to determine whether the output of +;; `markdown-command' is a standalone XHTML document or an XHTML +;; fragment (default: `"^\\(<\\?xml\\|`. +;; +;; * **Task lists:** GFM task lists will be rendered as checkboxes +;; (Emacs buttons) in both `markdown-mode' and `gfm-mode' when +;; `markdown-make-gfm-checkboxes-buttons' is set to a non-nil value +;; (and it is set to t by default). These checkboxes can be +;; toggled by clicking `mouse-1` or pressing `RET` over the button. +;; +;; * **Wiki links:** Generic wiki links are supported in +;; `markdown-mode', but in `gfm-mode' specifically they will be +;; treated as they are on GitHub: spaces will be replaced by hyphens +;; in filenames and the first letter of the filename will be +;; capitalized. For example, `[[wiki link]]' will map to a file +;; named `Wiki-link` with the same extension as the current file. +;; +;; * **Newlines:** Neither `markdown-mode' nor `gfm-mode' do anything +;; specifically with respect to newline behavior. If you use +;; `gfm-mode' mostly to write text for comments or issues on the +;; GitHub site--where newlines are significant and correspond to +;; hard line breaks--then you may want to enable `visual-line-mode' +;; for line wrapping in buffers. You can do this with a +;; `gfm-mode-hook' as follows: +;; +;; ;; Use visual-line-mode in gfm-mode +;; (defun my-gfm-mode-hook () +;; (visual-line-mode 1)) +;; (add-hook 'gfm-mode-hook 'my-gfm-mode-hook) +;; +;; * **Preview:** GFM-specific preview can be powered by setting +;; `markdown-command' to use [Docter][]. This may also be +;; configured to work with [Marked 2][] for `markdown-open-command'. +;; +;; [GFM]: http://github.github.com/github-flavored-markdown/ +;; [GFM comments]: https://help.github.com/articles/writing-on-github/ +;; [since 2014]: https://github.com/blog/1825-task-lists-in-all-markdown-documents +;; [Docter]: https://github.com/alampros/Docter + +;;; Acknowledgments: + +;; markdown-mode has benefited greatly from the efforts of the +;; following people: +;; +;; * Cyril Brulebois for Debian packaging. +;; * Conal Elliott for a font-lock regexp patch. +;; * Edward O'Connor for a font-lock regexp fix and +;; GitHub Flavored Markdown mode (`gfm-mode'). +;; * Greg Bognar for menus and running +;; `markdown' with an active region. +;; * Daniel Burrows for filing Debian bug #456592. +;; * Peter S. Galbraith for maintaining `emacs-goodies-el`. +;; * Dmitry Dzhus for undefined reference checking. +;; * Carsten Dominik for `org-mode', from which the +;; visibility cycling functionality was derived, and for a bug fix +;; related to `orgtbl-mode'. +;; * Bryan Kyle for indentation code. +;; * Ben Voui for font-lock face customizations. +;; * Ankit Solanki for `longlines.el` +;; compatibility and custom CSS. +;; * Hilko Bengen for proper XHTML output. +;; * Jose A. Ortega Ruiz for Emacs 23 fixes. +;; * Nelson Minar for `html-helper-mode', from which +;; comment matching functions were derived. +;; * Alec Resnick for bug reports. +;; * Joost Kremers for footnote-handling +;; functions, bug reports regarding indentation, and +;; fixes for byte-compilation warnings. +;; * Peter Williams for `fill-paragraph' +;; enhancements. +;; * George Ogata for fixing several +;; byte-compilation warnings. +;; * Eric Merritt for wiki link features. +;; * Philippe Ivaldi for XHTML preview +;; customizations and XHTML export. +;; * Jeremiah Dodds for supporting +;; Markdown processors which do not accept input from stdin. +;; * Werner Dittmann for bug reports +;; regarding the `cl` dependency and `auto-fill-mode' and indentation. +;; * Scott Pfister for generalizing the space +;; substitution character for mapping wiki links to filenames. +;; * Marcin Kasperski for a patch to +;; escape shell commands. +;; * Christopher J. Madsen for patches to fix a match +;; data bug and to prefer `visual-line-mode' in `gfm-mode'. +;; * Shigeru Fukaya for better adherence to +;; Emacs Lisp coding conventions. +;; * Donald Ephraim Curtis for fixing the `fill-paragraph' +;; regexp, refactoring the compilation and preview functions, +;; heading font-lock generalizations, list renumbering, +;; and kill ring save. +;; * Kevin Porter for wiki link handling in `gfm-mode'. +;; * Max Penet and Peter Eisentraut +;; for an autoload token for `gfm-mode'. +;; * Ian Yang for improving the reference definition regex. +;; * Akinori Musha for an imenu index function. +;; * Michael Sperber for XEmacs fixes. +;; * Francois Gannaz for suggesting charset +;; declaration in XHTML output. +;; * Zhenlei Jia for smart exdention function. +;; * Matus Goljer for improved wiki link following +;; and GFM code block insertion. +;; * Peter Jones for link following functions. +;; * Bryan Fink for a bug report regarding +;; externally modified files. +;; * Vegard Vesterheim for a bug fix +;; related to `orgtbl-mode'. +;; * Makoto Motohashi for before- and after- +;; export hooks, unit test improvements, and updates to support +;; wide characters. +;; * Michael Dwyer for `gfm-mode' underscore regexp. +;; * Chris Lott for suggesting reference label +;; completion. +;; * Gunnar Franke for a completion bug report. +;; * David Glasser for a `paragraph-separate' fix. +;; * Daniel Brotsky for better auto-fill defaults. +;; * Samuel Freilich for improved filling +;; behavior regarding list items, footnotes, and reference +;; definitions, improved killing of footnotes, and numerous other +;; tests and bug fixes. +;; * Antonis Kanouras for strikethrough support. +;; * Tim Visher for multiple CSS files and other +;; general improvements. +;; * Matt McClure for a patch to prevent +;; overwriting source files with .html extensions upon export. +;; * Roger Bolsius for ordered list improvements. +;; * Google's Open Source Programs Office for recognizing the project with +;; a monetary contribution in June 2015. +;; * Howard Melman for supporting GFM checkboxes +;; as buttons. +;; * Danny McClanahan for live preview +;; mode, completion of GFM programming language names, improved +;; font lock for fenced code blocks and metadata blocks, `cl-lib' +;; updates, and numerous other improvements. +;; * Syohei Yoshida for better heading detection +;; and movement functions, improved italic font lock, fixing adaptive +;; filling for hanging list items, more efficient fontification, +;; and numerous other improvements. +;; * Vitalie Spinu for improvements to font +;; lock and source code aesthetics. +;; * Kévin Le Gouguec for improvements +;; related to ATX headings and Pandoc fancy lists. + +;;; Bugs: + +;; markdown-mode is developed and tested primarily for compatibility +;; with GNU Emacs 24.3 and later. If you find any bugs in +;; markdown-mode, please construct a test case or a patch and open a +;; ticket on the [GitHub issue tracker][issues]. +;; +;; [issues]: https://github.com/jrblevin/markdown-mode/issues + +;;; History: + +;; markdown-mode was written and is maintained by Jason Blevins. The +;; first version was released on May 24, 2007. +;; +;; * 2007-05-24: Version 1.1 +;; * 2007-05-25: Version 1.2 +;; * 2007-06-05: [Version 1.3][] +;; * 2007-06-29: Version 1.4 +;; * 2007-10-11: [Version 1.5][] +;; * 2008-06-04: [Version 1.6][] +;; * 2009-10-01: [Version 1.7][] +;; * 2011-08-12: [Version 1.8][] +;; * 2011-08-15: [Version 1.8.1][] +;; * 2013-01-25: [Version 1.9][] +;; * 2013-03-24: [Version 2.0][] +;; * 2016-01-09: [Version 2.1][] +;; +;; [Version 1.3]: http://jblevins.org/projects/markdown-mode/rev-1-3 +;; [Version 1.5]: http://jblevins.org/projects/markdown-mode/rev-1-5 +;; [Version 1.6]: http://jblevins.org/projects/markdown-mode/rev-1-6 +;; [Version 1.7]: http://jblevins.org/projects/markdown-mode/rev-1-7 +;; [Version 1.8]: http://jblevins.org/projects/markdown-mode/rev-1-8 +;; [Version 1.8.1]: http://jblevins.org/projects/markdown-mode/rev-1-8-1 +;; [Version 1.9]: http://jblevins.org/projects/markdown-mode/rev-1-9 +;; [Version 2.0]: http://jblevins.org/projects/markdown-mode/rev-2-0 +;; [Version 2.1]: http://jblevins.org/projects/markdown-mode/rev-2-1 + + +;;; Code: + +(require 'easymenu) +(require 'outline) +(require 'thingatpt) +(require 'cl-lib) +(require 'url-parse) + +(defvar jit-lock-start) +(defvar jit-lock-end) + +(declare-function eww-open-file "eww") +(declare-function url-path-and-query "url-parse") + + +;;; Constants ================================================================= + +(defconst markdown-mode-version "2.1" + "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 'wp + :link '(url-link "http://jblevins.org/projects/markdown-mode/")) + +(defcustom markdown-command "markdown" + "Command to run markdown." + :group 'markdown + :type 'string) + +(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." + :group 'markdown + :type 'string) + +(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 'list) + +(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-asymmetric-header nil + "Determines if header style will be asymmetric. +Set to non-nil to only have header characters to the left of the title. +The default will ensure header characters are placed to the left and right +of the 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 + "Automatically indent new lines when enter key is pressed. +When this variable is set to t, pressing RET will call +`newline-and-indent'. When set to nil, define RET to call +`newline' as usual. In the latter case, you can still use +auto-indentation by pressing \\[newline-and-indent]." + :group 'markdown + :type 'boolean) + +(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) + +(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-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) + +(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) + +(defcustom markdown-uri-types + '("acap" "cid" "data" "dav" "fax" "file" "ftp" "gopher" "http" "https" + "imap" "ldap" "mailto" "mid" "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 'list) + +(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) + +(defcustom markdown-css-paths nil + "URL of CSS file to link to in the output XHTML." + :group 'markdown + :type 'list) + +(defcustom markdown-content-type "" + "Content type string for the http-equiv header in XHTML output. +When set to a non-empty string, insert the http-equiv attribute. +Otherwise, this attribute is omitted." + :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 +`iso-8859-1' when not available). Common settings are `utf-8' +and `iso-latin-1'. Use `list-coding-systems' for more choices." + :group 'markdown + :type 'coding-system) + +(defcustom markdown-xhtml-header-content "" + "Additional content to include in the XHTML block." + :group 'markdown + :type 'string) + +(defcustom markdown-xhtml-standalone-regexp + "^\\(<\\?xml\\|" + "Regular expression matches HTML comment closing.") + +(defconst markdown-regex-link-inline + "\\(!\\)?\\(\\[\\)\\([^]^][^]]*\\|\\)\\(\\]\\)\\((\\)\\([^)]*?\\)\\(?:\\s-+\\(\"[^\"]*\"\\)\\)?\\()\\)" + "Regular expression for a [text](file) or an image link ![text](file). +Group 1 matches the leading exclamation point (optional). +Group 2 matches the opening square bracket. +Group 3 matches the text inside the square brackets. +Group 4 matches the closing square bracket. +Group 5 matches the opening parenthesis. +Group 6 matches the URL. +Group 7 matches the title (optional). +Group 8 matches the closing parenthesis.") + +(defconst markdown-regex-link-reference + "\\(!\\)?\\(\\[\\)\\([^]^][^]]*\\|\\)\\(\\]\\)[ ]?\\(\\[\\)\\([^]]*?\\)\\(\\]\\)" + "Regular expression for a reference link [text][id]. +Group 1 matches the leading exclamation point (optional). +Group 2 matches the opening square bracket for the link text. +Group 3 matches the text inside the square brackets. +Group 4 matches the closing square bracket for the link text. +Group 5 matches the opening square bracket for the reference label. +Group 6 matches the reference label. +Group 7 matches the closing square bracket for the reference label.") + +(defconst markdown-regex-reference-definition + "^ \\{0,3\\}\\(\\[\\)\\([^]\n]+?\\)\\(\\]\\)\\(:\\)\\s *\\(.*?\\)\\s *\\( \"[^\"]*\"$\\|$\\)" + "Regular expression for a reference definition. +Group 1 matches the opening square bracket. +Group 2 matches the reference label. +Group 3 matches the closing square bracket. +Group 4 matches the colon. +Group 5 matches the URL. +Group 6 matches the title attribute (optional).") + +(defconst markdown-regex-footnote + "\\(\\[\\^\\)\\(.+?\\)\\(\\]\\)" + "Regular expression for a footnote marker [^fn]. +Group 1 matches the opening square bracket and carat. +Group 2 matches only the label, without the surrounding markup. +Group 3 matches the closing square bracket.") + +(defconst markdown-regex-header + "^\\(?:\\([^\r\n- ].*\\)\n\\(?:\\(=+\\)\\|\\(-+\\)\\)\\|\\(#+\\)[ \t]+\\(.*?\\)[ \t]*\\(#*\\)\\)$" + "Regexp identifying Markdown headings. +Group 1 matches the text of a setext heading. +Group 2 matches the underline of a level-1 setext heading. +Group 3 matches the underline of a level-2 setext heading. +Group 4 matches the opening hash marks of an atx heading. +Group 5 matches the text, without surrounding whitespace, of an atx heading. +Group 6 matches the closing hash marks of an atx heading.") + +(defconst markdown-regex-header-setext + "^\\([^\r\n- ].*\\)\n\\(=+\\|-+\\)$" + "Regular expression for generic setext-style (underline) headers.") + +(defconst markdown-regex-header-atx + "^\\(#+\\)[ \t]+\\(.*?\\)[ \t]*\\(#*\\)$" + "Regular expression for generic atx-style (hash mark) headers.") + +(defconst markdown-regex-hr + "^\\(\\*[ ]?\\*[ ]?\\*[ ]?[\\* ]*\\|-[ ]?-[ ]?-[--- ]*\\)$" + "Regular expression for matching Markdown horizontal rules.") + +(defconst markdown-regex-code + "\\(?:\\`\\|[^\\]\\)\\(\\(`+\\)\\(\\(?:.\\|\n[^\n]\\)*?[^`]\\)\\(\\2\\)\\)\\(?:[^`]\\|\\'\\)" + "Regular expression for matching inline code fragments. + +Group 1 matches the entire code fragment including the backticks. +Group 2 matches the opening backticks. +Group 3 matches the code fragment itself, without backticks. +Group 4 matches the closing backticks. + +The leading, unnumbered group ensures that the leading backquote +character is not escaped. +The last group, also unnumbered, requires that the character +following the code fragment is not a backquote. +Note that \\(?:.\\|\n[^\n]\\) matches any character, including newlines, +but not two newlines in a row.") + +(defconst markdown-regex-kbd + "\\(\\)\\(\\(?:.\\|\n[^\n]\\)*?\\)\\(\\)" + "Regular expression for matching tags. +Groups 1 and 3 match the opening and closing tags. +Group 2 matches the key sequence.") + +(defconst markdown-regex-gfm-code-block-open + "^[[:blank:]]*\\(```\\)[ ]?\\([^[:space:]]+\\|{[^}]*}\\)?\\([[:space:]]*?\\)$" + "Regular expression matching opening of GFM code blocks. +Group 1 matches the opening three backticks. +Group 2 matches the language identifier (optional).") + +(defconst markdown-regex-gfm-code-block-close + "^[[:blank:]]*\\(```\\)\\s *?$" + "Regular expression matching closing of GFM code blocks. +Group 1 matches the closing three backticks.") + +(defconst markdown-regex-pre + "^\\( \\|\t\\).*$" + "Regular expression for matching preformatted text sections.") + +(defconst markdown-regex-list + "^\\([ \t]*\\)\\([0-9#]+\\.\\|[\\*\\+-]\\)\\([ \t]+\\)" + "Regular expression for matching list items.") + +(defconst markdown-regex-bold + "\\(^\\|[^\\]\\)\\(\\([*_]\\{2\\}\\)\\([^ \n\t\\]\\|[^ \n\t]\\(?:.\\|\n[^\n]\\)*?[^\\ ]\\)\\(\\3\\)\\)" + "Regular expression for matching bold text. +Group 1 matches the character before the opening asterisk or +underscore, if any, ensuring that it is not a backslash escape. +Group 2 matches the entire expression, including delimiters. +Groups 3 and 5 matches the opening and closing delimiters. +Group 4 matches the text inside the delimiters.") + +(defconst markdown-regex-italic + "\\(?:^\\|[^\\]\\)\\(\\([*_]\\)\\([^ \n\t\\]\\|[^ \n\t]\\(?:.\\|\n[^\n]\\)*?[^\\ ]\\)\\(\\2\\)\\)" + "Regular expression for matching italic text. +The leading unnumbered matches the character before the opening +asterisk or underscore, if any, ensuring that it is not a +backslash escape. +Group 1 matches the entire expression, including delimiters. +Groups 2 and 4 matches the opening and closing delimiters. +Group 3 matches the text inside the delimiters.") + +(defconst markdown-regex-strike-through + "\\(^\\|[^\\]\\)\\(\\(~~\\)\\([^ \n\t\\]\\|[^ \n\t]\\(?:.\\|\n[^\n]\\)*?[^\\ ]\\)\\(~~\\)\\)" + "Regular expression for matching strike-through text. +Group 1 matches the character before the opening tilde, if any, +ensuring that it is not a backslash escape. +Group 2 matches the entire expression, including delimiters. +Groups 3 and 5 matches the opening and closing delimiters. +Group 4 matches the text inside the delimiters.") + +(defconst markdown-regex-gfm-italic + "\\(?:^\\|\\s-\\)\\(\\([*_]\\)\\([^ \\]\\2\\|[^ ]\\(?:.\\|\n[^\n]\\)*?[^\\ ]\\)\\(\\2\\)\\)" + "Regular expression for matching italic text in GitHub Flavored Markdown. +Underscores in words are not treated as special. +Group 1 matches the entire expression, including delimiters. +Groups 2 and 4 matches the opening and closing delimiters. +Group 3 matches the text inside the delimiters.") + +(defconst markdown-regex-blockquote + "^[ \t]*\\(>\\)\\(.*\\)$" + "Regular expression for matching blockquote lines. +Group 1 matches the leading angle bracket. +Group 2 matches the text.") + +(defconst markdown-regex-line-break + "[^ \n\t][ \t]*\\( \\)$" + "Regular expression for matching line breaks.") + +(defconst markdown-regex-wiki-link + "\\(?:^\\|[^\\]\\)\\(\\(\\[\\[\\)\\([^]|]+\\)\\(?:\\(|\\)\\([^]]+\\)\\)?\\(\\]\\]\\)\\)" + "Regular expression for matching wiki links. +This matches typical bracketed [[WikiLinks]] as well as 'aliased' +wiki links of the form [[PageName|link text]]. +The meanings of the first and second components depend +on the value of `markdown-wiki-link-alias-first'. + +Group 1 matches the entire link. +Group 2 matches the opening square brackets. +Group 3 matches the first component of the wiki link. +Group 4 matches the pipe separator, when present. +Group 5 matches the second component of the wiki link, when present. +Group 6 matches the closing square brackets.") + +(defconst markdown-regex-uri + (concat (regexp-opt markdown-uri-types) ":[^]\t\n\r<>,;() ]+") + "Regular expression for matching inline URIs.") + +(defconst markdown-regex-angle-uri + (concat "\\(<\\)\\(" (regexp-opt markdown-uri-types) ":[^]\t\n\r<>,;()]+\\)\\(>\\)") + "Regular expression for matching inline URIs in angle brackets.") + +(defconst markdown-regex-email + "<\\(\\(\\sw\\|\\s_\\|\\s.\\)+@\\(\\sw\\|\\s_\\|\\s.\\)+\\)>" + "Regular expression for matching inline email addresses.") + +(defsubst markdown-make-regex-link-generic () + "Make regular expression for matching any recognized link." + (concat "\\(?:" markdown-regex-link-inline + (when markdown-enable-wiki-links + (concat "\\|" markdown-regex-wiki-link)) + "\\|" markdown-regex-link-reference + "\\|" markdown-regex-angle-uri "\\)")) + +(defconst markdown-regex-gfm-checkbox + " \\(\\[[ xX]\\]\\) " + "Regular expression for matching GFM checkboxes. +Group 1 matches the text to become a button.") + +(defconst markdown-regex-block-separator + "\\(\\`\\|\\(\n[ \t]*\n\\)[^\n \t]\\)" + "Regular expression for matching block boundaries.") + +(defconst markdown-regex-math-inline-single + "\\(?:^\\|[^\\]\\)\\(\\$\\)\\(\\(?:[^\\$]\\|\\\\.\\)*\\)\\(\\$\\)" + "Regular expression for itex $..$ math mode expressions. +Groups 1 and 3 match the opening and closing dollar signs. +Group 3 matches the mathematical expression contained within.") + +(defconst markdown-regex-math-inline-double + "\\(?:^\\|[^\\]\\)\\(\\$\\$\\)\\(\\(?:[^\\$]\\|\\\\.\\)*\\)\\(\\$\\$\\)" + "Regular expression for itex $$..$$ math mode expressions. +Groups 1 and 3 match opening and closing dollar signs. +Group 3 matches the mathematical expression contained within.") + +(defconst markdown-regex-math-display + "^\\(\\\\\\[\\)\\(\\(?:.\\|\n\\)*\\)?\\(\\\\\\]\\)$" + "Regular expression for itex \[..\] display mode expressions. +Groups 1 and 3 match the opening and closing delimiters. +Group 2 matches the mathematical expression contained within.") + +(defsubst markdown-make-tilde-fence-regex (num-tildes &optional end-of-line) + "Return regexp matching a Pandoc code fence at least NUM-TILDES long. +END-OF-LINE is the regexp construct to indicate end of line; $ if +missing." + (format "%s%d%s%s" "^\\([~]\\{" num-tildes ",\\}\\)" (or end-of-line "$"))) + +(defconst markdown-regex-tilde-fence-begin + (markdown-make-tilde-fence-regex + 3 "[ ]?\\([^[:space:]]+\\|{[^}]*}\\)?\\([[:space:]]*?\\)$") + "Regular expression for matching Pandoc tildes.") + +(defconst markdown-regex-declarative-metadata + "^\\([[:alpha:]][[:alpha:] _-]*?\\)\\([:=][ \t]*\\)\\(.*\\)$" + "Regular expression for matching declarative metadata statements. +This matches MultiMarkdown metadata as well as YAML and TOML +assignments such as the following: + + variable: value + +or + + variable = value") + +(defconst markdown-regex-pandoc-metadata + "^\\(%\\)\\([ \t]*\\)\\(.*\\(?:\n[ \t]+.*\\)*\\)" + "Regular expression for matching Pandoc metadata.") + +(defconst markdown-regex-yaml-metadata-border + "\\(\\-\\{3\\}\\)$" + "Regular expression for matching yaml metadata.") + +(defconst markdown-regex-yaml-pandoc-metadata-end-border + "^\\(\\.\\{3\\}\\|\\-\\{3\\}\\)$" + "Regular expression for matching yaml metadata end borders.") + +(defsubst markdown-get-yaml-metadata-start-border () + "Return yaml metadata start border depending upon whether Pandoc is used." + (concat + (if markdown-use-pandoc-style-yaml-metadata "^" "\\`") + markdown-regex-yaml-metadata-border)) + +(defsubst markdown-get-yaml-metadata-end-border (_) + "Return yaml metadata end border depending upon whether Pandoc is used." + (if markdown-use-pandoc-style-yaml-metadata + markdown-regex-yaml-pandoc-metadata-end-border + markdown-regex-yaml-metadata-border)) + + +;;; Syntax ==================================================================== + +(defun markdown-syntax-propertize-extend-region (start end) + "Extend START to END region to include an entire block of text. +This helps improve syntax analysis for block constructs. +Returns a cons (NEW-START . NEW-END) or nil if no adjustment should be made. +Function is called repeatedly until it returns nil. For details, see +`syntax-propertize-extend-region-functions'." + (save-match-data + (save-excursion + (let* ((new-start (progn (goto-char start) + (skip-chars-forward "\n") + (if (re-search-backward "\n\n" nil t) + (min start (match-end 0)) + (point-min)))) + (new-end (progn (goto-char end) + (skip-chars-backward "\n") + (if (re-search-forward "\n\n" nil t) + (max end (match-beginning 0)) + (point-max)))) + (code-match (markdown-code-block-at-pos new-start)) + (new-start (or (and code-match (cl-first code-match)) new-start)) + (code-match (markdown-code-block-at-pos end)) + (new-end (or (and code-match (cl-second code-match)) new-end))) + (unless (and (eq new-start start) (eq new-end end)) + (cons new-start (min new-end (point-max)))))))) + +(defun markdown-font-lock-extend-region-function (start end _) + "Used in `jit-lock-after-change-extend-region-functions'. +Delegates to `markdown-syntax-propertize-extend-region'. START +and END are the previous region to refontify." + (let ((res (markdown-syntax-propertize-extend-region start end))) + (when res + (setq jit-lock-start (car res) + jit-lock-end (cdr res))))) + +(defun markdown-syntax-propertize-pre-blocks (start end) + "Match preformatted text blocks from START to END." + (save-excursion + (goto-char start) + (let ((levels (markdown-calculate-list-levels)) + indent pre-regexp close-regexp open close) + (while (and (< (point) end) (not close)) + ;; Search for a region with sufficient indentation + (if (null levels) + (setq indent 1) + (setq indent (1+ (length levels)))) + (setq pre-regexp (format "^\\( \\|\t\\)\\{%d\\}" indent)) + (setq close-regexp (format "^\\( \\|\t\\)\\{0,%d\\}\\([^ \t]\\)" (1- indent))) + + (cond + ;; If not at the beginning of a line, move forward + ((not (bolp)) (forward-line)) + ;; Move past blank lines + ((markdown-cur-line-blank-p) (forward-line)) + ;; At headers and horizontal rules, reset levels + ((markdown-new-baseline-p) (forward-line) (setq levels nil)) + ;; If the current line has sufficient indentation, mark out pre block + ;; The opening should be preceded by a blank line. + ((and (looking-at pre-regexp) + (save-match-data (markdown-prev-line-blank-p))) + (setq open (match-beginning 0)) + (while (and (or (looking-at-p pre-regexp) (markdown-cur-line-blank-p)) + (not (eobp))) + (forward-line)) + (skip-syntax-backward "-") + (setq close (point))) + ;; If current line has a list marker, update levels, move to end of block + ((looking-at markdown-regex-list) + (setq levels (markdown-update-list-levels + (match-string 2) (current-indentation) levels)) + (markdown-end-of-block-element)) + ;; If this is the end of the indentation level, adjust levels accordingly. + ;; Only match end of indentation level if levels is not the empty list. + ((and (car levels) (looking-at-p close-regexp)) + (setq levels (markdown-update-list-levels + nil (current-indentation) levels)) + (markdown-end-of-block-element)) + (t (markdown-end-of-block-element)))) + + (when (and open close) + ;; Set text property data + (put-text-property open close 'markdown-pre (list open close)) + ;; Recursively search again + (markdown-syntax-propertize-pre-blocks (point) end))))) + +(defconst markdown-fenced-block-pairs + `(((,markdown-regex-tilde-fence-begin markdown-tilde-fence-begin) + (markdown-make-tilde-fence-regex markdown-tilde-fence-end) + markdown-fenced-code) + ((markdown-get-yaml-metadata-start-border markdown-yaml-metadata-begin) + (markdown-get-yaml-metadata-end-border markdown-yaml-metadata-end) + markdown-yaml-metadata-section) + ((,markdown-regex-gfm-code-block-open markdown-gfm-block-begin) + (,markdown-regex-gfm-code-block-close markdown-gfm-block-end) + markdown-gfm-code)) + "Mapping of regular expressions to \"fenced-block\" constructs. +These constructs are distinguished by having a distinctive start +and end pattern, both of which take up an entire line of text, +but no special pattern to identify text within the fenced +blocks (unlike blockquotes and indented-code sections). + +Each element within this list takes the form: + + ((START-REGEX-OR-FUN START-PROPERTY) + (END-REGEX-OR-FUN END-PROPERTY) + MIDDLE-PROPERTY) + +Each *-REGEX-OR-FUN element can be a regular expression as a string, or a +function which evaluates to same. Functions for START-REGEX-OR-FUN accept no +arguments, but functions for END-REGEX-OR-FUN accept a single numerical argument +which is the length of the first group of the START-REGEX-OR-FUN match, which +can be ignored if unnecessary. `markdown-maybe-funcall-regexp' is used to +evaluate these into \"real\" regexps. + +The *-PROPERTY elements are the text properties applied to each part of the +block construct when it is matched using +`markdown-syntax-propertize-fenced-block-constructs'. START-PROPERTY is applied +to the text matching START-REGEX-OR-FUN, END-PROPERTY to END-REGEX-OR-FUN, and +MIDDLE-PROPERTY to the text in between the two. The value of *-PROPERTY is the +`match-data' when the regexp was matched to the text. In the case of +MIDDLE-PROPERTY, the value is a false match data of the form '(begin end), with +begin and end set to the edges of the \"middle\" text. This makes fontification +easier.") + +(defun markdown-text-property-at-point (prop) + (get-text-property (point) prop)) + +(defsubst markdown-maybe-funcall-regexp (object &optional arg) + (cond ((functionp object) + (if arg (funcall object arg) (funcall object))) + ((stringp object) object) + (t (error "Object cannot be turned into regex")))) + +(defsubst markdown-get-start-fence-regexp () + "Return regexp to find all \"start\" sections of fenced block constructs. +Which construct is actually contained in the match must be found separately." + (mapconcat + 'identity + (cl-mapcar (lambda (entry) (markdown-maybe-funcall-regexp (caar entry))) + markdown-fenced-block-pairs) + "\\|")) + +(defun markdown-get-fenced-block-begin-properties () + (cl-mapcar (lambda (entry) (cl-cadar entry)) markdown-fenced-block-pairs)) + +(defun markdown-get-fenced-block-end-properties () + (cl-mapcar (lambda (entry) (cl-cadadr entry)) markdown-fenced-block-pairs)) + +(defun markdown-get-fenced-block-middle-properties () + (cl-mapcar #'cl-third markdown-fenced-block-pairs)) + +(defun markdown-find-previous-prop (prop &optional lim) + "Find previous place where property PROP is non-nil, up to LIM. +Return a cons of (pos . property). pos is point if point contains +non-nil PROP." + (let ((res + (if (get-text-property (point) prop) (point) + (previous-single-property-change + (point) prop nil (or lim (point-min)))))) + (when (and (not (get-text-property res prop)) + (> res (point-min)) + (get-text-property (1- res) prop)) + (cl-decf res)) + (when (and res (get-text-property res prop)) (cons res prop)))) + +(defun markdown-find-next-prop (prop &optional lim) + "Find next place where property PROP is non-nil, up to LIM. +Return a cons of (POS . PROPERTY) where POS is point if point +contains non-nil PROP." + (let ((res + (if (get-text-property (point) prop) (point) + (next-single-property-change + (point) prop nil (or lim (point-max)))))) + (when (and res (get-text-property res prop)) (cons res prop)))) + +(defun markdown-min-of-seq (map-fn seq) + "Apply MAP-FN to SEQ and return element of SEQ with minimum value of MAP-FN." + (cl-loop for el in seq + with min = 1.0e+INF ; infinity + with min-el = nil + do (let ((res (funcall map-fn el))) + (when (< res min) + (setq min res) + (setq min-el el))) + finally return min-el)) + +(defun markdown-find-previous-block () + "Find previous block. +Detect whether `markdown-syntax-propertize-fenced-block-constructs' was +unable to propertize the entire block, but was able to propertize the beginning +of the block. If so, return a cons of (pos . property) where the beginning of +the block was propertized." + (let ((start-pt (point)) + (closest-open + (markdown-min-of-seq + #'car + (cl-remove-if + #'null + (cl-mapcar + #'markdown-find-previous-prop + (markdown-get-fenced-block-begin-properties)))))) + (when closest-open + (let* ((length-of-open-match + (let ((match-d + (get-text-property (car closest-open) (cdr closest-open)))) + (- (cl-fourth match-d) (cl-third match-d)))) + (end-regexp + (markdown-maybe-funcall-regexp + (cl-caadr + (cl-find-if + (lambda (entry) (eq (cl-cadar entry) (cdr closest-open))) + markdown-fenced-block-pairs)) + length-of-open-match)) + (end-prop-loc + (save-excursion + (save-match-data + (goto-char (car closest-open)) + (and (re-search-forward end-regexp start-pt t) + (match-beginning 0)))))) + (and (not end-prop-loc) closest-open))))) + +(defun markdown-get-fenced-block-from-start (prop) + "Return limits of an enclosing fenced block from its start, using PROP. +Return value is a list usable as `match-data'." + (catch 'no-rest-of-block + (let* ((correct-entry + (cl-find-if + (lambda (entry) (eq (cl-cadar entry) prop)) + markdown-fenced-block-pairs)) + (begin-of-begin (cl-first (markdown-text-property-at-point prop))) + (middle-prop (cl-third correct-entry)) + (end-prop (cl-cadadr correct-entry)) + (end-of-end + (save-excursion + (goto-char (match-end 0)) ; end of begin + (unless (eobp) (forward-char)) + (let ((mid-prop-v (markdown-text-property-at-point middle-prop))) + (if (not mid-prop-v) ; no middle + (progn + ;; try to find end by advancing one + (let ((end-prop-v + (markdown-text-property-at-point end-prop))) + (if end-prop-v (cl-second end-prop-v) + (throw 'no-rest-of-block nil)))) + (set-match-data mid-prop-v) + (goto-char (match-end 0)) ; end of middle + (beginning-of-line) ; into end + (cl-second (markdown-text-property-at-point end-prop))))))) + (list begin-of-begin end-of-end)))) + +(defun markdown-get-fenced-block-from-middle (prop) + "Return limits of an enclosing fenced block from its middle, using PROP. +Return value is a list usable as `match-data'." + (let* ((correct-entry + (cl-find-if + (lambda (entry) (eq (cl-third entry) prop)) + markdown-fenced-block-pairs)) + (begin-prop (cl-cadar correct-entry)) + (begin-of-begin + (save-excursion + (goto-char (match-beginning 0)) + (unless (bobp) (forward-line -1)) + (beginning-of-line) + (cl-first (markdown-text-property-at-point begin-prop)))) + (end-prop (cl-cadadr correct-entry)) + (end-of-end + (save-excursion + (goto-char (match-end 0)) + (beginning-of-line) + (cl-second (markdown-text-property-at-point end-prop))))) + (list begin-of-begin end-of-end))) + +(defun markdown-get-fenced-block-from-end (prop) + "Return limits of an enclosing fenced block from its end, using PROP. +Return value is a list usable as `match-data'." + (let* ((correct-entry + (cl-find-if + (lambda (entry) (eq (cl-cadadr entry) prop)) + markdown-fenced-block-pairs)) + (end-of-end (cl-second (markdown-text-property-at-point prop))) + (middle-prop (cl-third correct-entry)) + (begin-prop (cl-cadar correct-entry)) + (begin-of-begin + (save-excursion + (goto-char (match-beginning 0)) ; beginning of end + (unless (bobp) (backward-char)) ; into middle + (let ((mid-prop-v (markdown-text-property-at-point middle-prop))) + (if (not mid-prop-v) + (progn + (beginning-of-line) + (cl-first (markdown-text-property-at-point begin-prop))) + (set-match-data mid-prop-v) + (goto-char (match-beginning 0)) ; beginning of middle + (unless (bobp) (forward-line -1)) ; into beginning + (beginning-of-line) + (cl-first (markdown-text-property-at-point begin-prop))))))) + (list begin-of-begin end-of-end))) + +(defun markdown-get-enclosing-fenced-block-construct (&optional pos) + "Get \"fake\" match data for block enclosing POS. +Returns fake match data which encloses the start, middle, and end +of the block construct enclosing POS, if it exists. Used in +`markdown-code-block-at-pos'." + (save-excursion + (when pos (goto-char pos)) + (beginning-of-line) + (car + (cl-remove-if + #'null + (cl-mapcar + (lambda (fun-and-prop) + (cl-destructuring-bind (fun prop) fun-and-prop + (when prop + (save-match-data + (set-match-data (markdown-text-property-at-point prop)) + (funcall fun prop))))) + `((markdown-get-fenced-block-from-start + ,(cl-find-if + #'markdown-text-property-at-point + (markdown-get-fenced-block-begin-properties))) + (markdown-get-fenced-block-from-middle + ,(cl-find-if + #'markdown-text-property-at-point + (markdown-get-fenced-block-middle-properties))) + (markdown-get-fenced-block-from-end + ,(cl-find-if + #'markdown-text-property-at-point + (markdown-get-fenced-block-end-properties))))))))) + +(defun markdown-propertize-end-match (reg end correct-entry enclosed-text-start) + "Get match for REG up to END, if exists, and propertize appropriately. +CORRECT-ENTRY is an entry in `markdown-fenced-block-pairs' and +ENCLOSED-TEXT-START is the start of the \"middle\" section of the block." + (when (re-search-forward reg end t) + (put-text-property (match-beginning 0) (match-end 0) + (cl-cadadr correct-entry) (match-data t)) + (put-text-property + enclosed-text-start (match-beginning 0) (cl-third correct-entry) + (list enclosed-text-start (match-beginning 0))))) + +(defun markdown-syntax-propertize-fenced-block-constructs (start end) + "Propertize according to `markdown-fenced-block-pairs' from START to END. +If unable to propertize an entire block (if the start of a block is within START +and END, but the end of the block is not), propertize the start section of a +block, then in a subsequent call propertize both middle and end by finding the +start which was previously propertized." + (let ((start-reg (markdown-get-start-fence-regexp))) + (save-excursion + (goto-char start) + ;; start from previous unclosed block, if exists + (let ((prev-begin-block (markdown-find-previous-block))) + (when prev-begin-block + (let* ((correct-entry + (cl-find-if (lambda (entry) + (eq (cdr prev-begin-block) (cl-cadar entry))) + markdown-fenced-block-pairs)) + (enclosed-text-start (1+ (car prev-begin-block))) + (start-length + (save-excursion + (goto-char (car prev-begin-block)) + (string-match + (markdown-maybe-funcall-regexp + (caar correct-entry)) + (buffer-substring + (point-at-bol) (point-at-eol))) + (- (match-end 1) (match-beginning 1)))) + (end-reg (markdown-maybe-funcall-regexp + (cl-caadr correct-entry) start-length))) + (markdown-propertize-end-match + end-reg end correct-entry enclosed-text-start)))) + ;; find all new blocks within region + (while (re-search-forward start-reg end t) + ;; we assume the opening constructs take up (only) an entire line, + ;; so we re-check the current line + (let* ((cur-line (buffer-substring (point-at-bol) (point-at-eol))) + ;; find entry in `markdown-fenced-block-pairs' corresponding + ;; to regex which was matched + (correct-entry + (cl-find-if + (lambda (fenced-pair) + (string-match-p + (markdown-maybe-funcall-regexp (caar fenced-pair)) + cur-line)) + markdown-fenced-block-pairs)) + (enclosed-text-start + (save-excursion (1+ (point-at-eol)))) + (end-reg + (markdown-maybe-funcall-regexp + (cl-caadr correct-entry) + (if (and (match-beginning 1) (match-end 1)) + (- (match-end 1) (match-beginning 1)) + 0)))) + ;; get correct match data + (save-excursion + (beginning-of-line) + (re-search-forward + (markdown-maybe-funcall-regexp (caar correct-entry)) + (point-at-eol))) + ;; mark starting, even if ending is outside of region + (put-text-property (match-beginning 0) (match-end 0) + (cl-cadar correct-entry) (match-data t)) + (markdown-propertize-end-match + end-reg end correct-entry enclosed-text-start)))))) + +(defun markdown-syntax-propertize-blockquotes (start end) + "Match blockquotes from START to END." + (save-excursion + (goto-char start) + (while (and (re-search-forward markdown-regex-blockquote end t) + (not (markdown-code-block-at-pos (match-beginning 0)))) + (put-text-property (match-beginning 0) (match-end 0) + 'markdown-blockquote + (match-data t))))) + +(defun markdown-syntax-propertize-yaml-metadata (start end) + (save-excursion + (goto-char start) + (cl-loop + while (re-search-forward markdown-regex-declarative-metadata end t) + do (when (get-text-property (match-beginning 0) + 'markdown-yaml-metadata-section) + (put-text-property (match-beginning 1) (match-end 1) + 'markdown-metadata-key (match-data t)) + (put-text-property (match-beginning 2) (match-end 2) + 'markdown-metadata-markup (match-data t)) + (put-text-property (match-beginning 3) (match-end 3) + 'markdown-metadata-value (match-data t)))))) + +(defun markdown-syntax-propertize-headings (start end) + "Match headings of type SYMBOL with REGEX from START to END." + (goto-char start) + (while (re-search-forward markdown-regex-header end t) + (unless (markdown-code-block-at-pos (match-beginning 0)) + (put-text-property + (match-beginning 0) (match-end 0) 'markdown-heading t) + (put-text-property + (match-beginning 0) (match-end 0) + (cond ((match-string-no-properties 2) 'markdown-heading-1-setext) + ((match-string-no-properties 3) 'markdown-heading-2-setext) + (t (let ((atx-level (length (match-string-no-properties 4)))) + (intern (format "markdown-heading-%d-atx" atx-level))))) + (match-data t))))) + +(defun markdown-syntax-propertize-comments (start end) + "Match HTML comments from the START to END." + (let* ((state (syntax-ppss)) (in-comment (nth 4 state))) + (goto-char start) + (cond + ;; Comment start + ((and (not in-comment) + (re-search-forward markdown-regex-comment-start end t) + (save-match-data (not (markdown-code-at-point-p))) + (save-match-data (not (markdown-code-block-at-point)))) + (let ((open-beg (match-beginning 0))) + (put-text-property open-beg (1+ open-beg) + 'syntax-table (string-to-syntax "<")) + (markdown-syntax-propertize-comments + (min (1+ (match-end 0)) end (point-max)) end))) + ;; Comment end + ((and in-comment + (re-search-forward markdown-regex-comment-end end t)) + (put-text-property (1- (match-end 0)) (match-end 0) + 'syntax-table (string-to-syntax ">")) + (markdown-syntax-propertize-comments + (min (1+ (match-end 0)) end (point-max)) end)) + ;; Nothing found + (t nil)))) + +(defvar markdown--syntax-properties + (list 'markdown-tilde-fence-begin nil + 'markdown-tilde-fence-end nil + 'markdown-fenced-code nil + 'markdown-yaml-metadata-begin nil + 'markdown-yaml-metadata-end nil + 'markdown-yaml-metadata-section nil + 'markdown-gfm-block-begin nil + 'markdown-gfm-block-end nil + 'markdown-gfm-code nil + 'markdown-pre nil + 'markdown-blockquote nil + 'markdown-heading nil + 'markdown-heading-1-setext nil + 'markdown-heading-2-setext nil + 'markdown-heading-1-atx nil + 'markdown-heading-2-atx nil + 'markdown-heading-3-atx nil + 'markdown-heading-4-atx nil + 'markdown-heading-5-atx nil + 'markdown-heading-6-atx nil + 'markdown-metadata-key nil + 'markdown-metadata-value nil + 'markdown-metadata-markup nil) + "Property list of all known markdown syntactic properties.") + +(defun markdown-syntax-propertize (start end) + "Function used as `syntax-propertize-function'. +START and END delimit region to propertize." + (remove-text-properties start end markdown--syntax-properties) + (markdown-syntax-propertize-fenced-block-constructs start end) + (markdown-syntax-propertize-yaml-metadata start end) + (markdown-syntax-propertize-pre-blocks start end) + (markdown-syntax-propertize-blockquotes start end) + (markdown-syntax-propertize-headings start end) + (markdown-syntax-propertize-comments start end)) + + +;;; Font Lock ================================================================= + +(require 'font-lock) + +(defvar markdown-italic-face 'markdown-italic-face + "Face name to use for italic text.") + +(defvar markdown-bold-face 'markdown-bold-face + "Face name to use for bold text.") + +(defvar markdown-strike-through-face 'markdown-strike-through-face + "Face name to use for strike-through text.") + +(defvar markdown-header-delimiter-face 'markdown-header-delimiter-face + "Face name to use as a base for header delimiters.") + +(defvar markdown-header-rule-face 'markdown-header-rule-face + "Face name to use as a base for header rules.") + +(defvar markdown-header-face 'markdown-header-face + "Face name to use as a base for headers.") + +(defvar markdown-header-face-1 'markdown-header-face-1 + "Face name to use for level-1 headers.") + +(defvar markdown-header-face-2 'markdown-header-face-2 + "Face name to use for level-2 headers.") + +(defvar markdown-header-face-3 'markdown-header-face-3 + "Face name to use for level-3 headers.") + +(defvar markdown-header-face-4 'markdown-header-face-4 + "Face name to use for level-4 headers.") + +(defvar markdown-header-face-5 'markdown-header-face-5 + "Face name to use for level-5 headers.") + +(defvar markdown-header-face-6 'markdown-header-face-6 + "Face name to use for level-6 headers.") + +(defvar markdown-inline-code-face 'markdown-inline-code-face + "Face name to use for inline code.") + +(defvar markdown-list-face 'markdown-list-face + "Face name to use for list markers.") + +(defvar markdown-blockquote-face 'markdown-blockquote-face + "Face name to use for blockquote.") + +(defvar markdown-pre-face 'markdown-pre-face + "Face name to use for preformatted text.") + +(defvar markdown-language-keyword-face 'markdown-language-keyword-face + "Face name to use for programming language identifiers.") + +(defvar markdown-link-face 'markdown-link-face + "Face name to use for links.") + +(defvar markdown-missing-link-face 'markdown-missing-link-face + "Face name to use for links where the linked file does not exist.") + +(defvar markdown-reference-face 'markdown-reference-face + "Face name to use for reference.") + +(defvar markdown-footnote-face 'markdown-footnote-face + "Face name to use for footnote identifiers.") + +(defvar markdown-url-face 'markdown-url-face + "Face name to use for URLs.") + +(defvar markdown-link-title-face 'markdown-link-title-face + "Face name to use for reference link titles.") + +(defvar markdown-line-break-face 'markdown-line-break-face + "Face name to use for hard line breaks.") + +(defvar markdown-comment-face 'markdown-comment-face + "Face name to use for HTML comments.") + +(defvar markdown-math-face 'markdown-math-face + "Face name to use for LaTeX expressions.") + +(defvar markdown-metadata-key-face 'markdown-metadata-key-face + "Face name to use for metadata keys.") + +(defvar markdown-metadata-value-face 'markdown-metadata-value-face + "Face name to use for metadata values.") + +(defvar markdown-gfm-checkbox-face 'markdown-gfm-checkbox-face + "Face name to use for GFM checkboxes.") + +(defvar markdown-highlight-face 'markdown-highlight-face + "Face name to use for mouse highlighting.") + +(defvar markdown-markup-face 'markdown-markup-face + "Face name to use for markup elements.") + +(defgroup markdown-faces nil + "Faces used in Markdown Mode" + :group 'markdown + :group 'faces) + +(defface markdown-italic-face + '((t (:inherit font-lock-variable-name-face :slant italic :weight normal))) + "Face for italic text." + :group 'markdown-faces) + +(defface markdown-bold-face + '((t (:inherit font-lock-variable-name-face :weight bold :slant normal))) + "Face for bold text." + :group 'markdown-faces) + +(defface markdown-strike-through-face + '((t (:inherit font-lock-variable-name-face :strike-through t))) + "Face for strike-through text." + :group 'markdown-faces) + +(defface markdown-markup-face + '((t (:inherit shadow :slant normal :weight normal))) + "Face for markup elements." + :group 'markdown-faces) + +(defface markdown-header-rule-face + '((t (:inherit markdown-markup-face))) + "Base face for headers rules." + :group 'markdown-faces) + +(defface markdown-header-delimiter-face + '((t (:inherit markdown-markup-face))) + "Base face for headers hash delimiter." + :group 'markdown-faces) + +(defface markdown-inline-code-face + '((t (:inherit font-lock-constant-face))) + "Face for inline code." + :group 'markdown-faces) + +(defface markdown-list-face + '((t (:inherit markdown-markup-face))) + "Face for list item markers." + :group 'markdown-faces) + +(defface markdown-blockquote-face + '((t (:inherit font-lock-doc-face))) + "Face for blockquote sections." + :group 'markdown-faces) + +(defface markdown-pre-face + '((t (:inherit font-lock-constant-face))) + "Face for preformatted text." + :group 'markdown-faces) + +(defface markdown-language-keyword-face + '((t (:inherit font-lock-type-face))) + "Face for programming language identifiers." + :group 'markdown-faces) + +(defface markdown-link-face + '((t (:inherit font-lock-keyword-face))) + "Face for links." + :group 'markdown-faces) + +(defface markdown-missing-link-face + '((t (:inherit font-lock-warning-face))) + "Face for missing links." + :group 'markdown-faces) + +(defface markdown-reference-face + '((t (:inherit markdown-markup-face))) + "Face for link references." + :group 'markdown-faces) + +(defface markdown-footnote-face + '((t (:inherit markdown-markup-face))) + "Face for footnote markers." + :group 'markdown-faces) + +(defface markdown-url-face + '((t (:inherit font-lock-string-face))) + "Face for URLs." + :group 'markdown-faces) + +(defface markdown-link-title-face + '((t (:inherit font-lock-comment-face))) + "Face for reference link titles." + :group 'markdown-faces) + +(defface markdown-line-break-face + '((t (:inherit font-lock-constant-face :underline t))) + "Face for hard line breaks." + :group 'markdown-faces) + +(defface markdown-comment-face + '((t (:inherit font-lock-comment-face))) + "Face for HTML comments." + :group 'markdown-faces) + +(defface markdown-math-face + '((t (:inherit font-lock-string-face))) + "Face for LaTeX expressions." + :group 'markdown-faces) + +(defface markdown-metadata-key-face + '((t (:inherit font-lock-variable-name-face))) + "Face for metadata keys." + :group 'markdown-faces) + +(defface markdown-metadata-value-face + '((t (:inherit font-lock-string-face))) + "Face for metadata values." + :group 'markdown-faces) + +(defface markdown-gfm-checkbox-face + '((t (:inherit font-lock-builtin-face))) + "Face for GFM checkboxes." + :group 'markdown-faces) + +(defface markdown-highlight-face + '((t (:inherit highlight))) + "Face for mouse highlighting." + :group 'markdown-faces) + +(defcustom markdown-header-scaling nil + "Whether to use variable-height faces for headers. +When non-nil, `markdown-header-face' will inherit from +`variable-pitch' and the scaling values in +`markdown-header-scaling-values' will be applied to +headers of levels one through six respectively." + :type 'boolean + :initialize 'custom-initialize-default + :set (lambda (symbol value) + (set-default symbol value) + (markdown-update-header-faces value)) + :group 'markdown-faces) + +(defcustom markdown-header-scaling-values + '(1.8 1.4 1.2 1.0 1.0 1.0) + "List of scaling values for headers of level one through six. +Used when `markdown-header-scaling' is non-nil." + :type 'list + :initialize 'custom-initialize-default + :set (lambda (symbol value) + (set-default symbol value) + (markdown-update-header-faces markdown-header-scaling value)) + :group 'markdown-faces) + +(defun markdown-make-header-faces () + "Build the faces used for Markdown headers." + (defface markdown-header-face + `((t (:inherit (,(when markdown-header-scaling 'variable-pitch) + font-lock-function-name-face) + :weight bold))) + "Base face for headers." + :group 'markdown-faces) + (dotimes (num 6) + (let* ((num1 (1+ num)) + (face-name (intern (format "markdown-header-face-%s" num1))) + (scale (if markdown-header-scaling + (float (nth num markdown-header-scaling-values)) + 1.0))) + (eval + `(defface ,face-name + '((t (:inherit markdown-header-face :height ,scale))) + (format "Face for level %s headers. + +You probably don't want to customize this face directly. Instead +you can customize the base face `markdown-header-face' or the +variable-height variable `markdown-header-scaling'." ,num1) + :group 'markdown-faces))))) + +(markdown-make-header-faces) + +(defun markdown-update-header-faces (&optional scaling scaling-values) + "Update header faces, depending on if header SCALING is desired. +If so, use given list of SCALING-VALUES relative to the baseline +size of `markdown-header-face'." + (dotimes (num 6) + (let* ((face-name (intern (format "markdown-header-face-%s" (1+ num)))) + (scale (cond ((not scaling) 1.0) + (scaling-values (float (nth num scaling-values))) + (t (float (nth num markdown-header-scaling-values)))))) + (unless (get face-name 'saved-face) ; Don't update customized faces + (set-face-attribute face-name nil :height scale))))) + +(defun markdown-syntactic-face (state) + "Return font-lock face for characters with given STATE. +See `font-lock-syntactic-face-function' for details." + (let ((in-comment (nth 4 state))) + (cond + (in-comment 'markdown-comment-face) + (t nil)))) + +(defvar markdown-mode-font-lock-keywords-basic + `((markdown-match-yaml-metadata-begin . ((1 markdown-markup-face))) + (markdown-match-yaml-metadata-end . ((1 markdown-markup-face))) + (markdown-match-yaml-metadata-key . ((1 markdown-metadata-key-face) + (2 markdown-markup-face) + (3 markdown-metadata-value-face))) + (markdown-match-gfm-open-code-blocks . ((1 markdown-markup-face) + (2 markdown-language-keyword-face nil t))) + (markdown-match-gfm-close-code-blocks . ((1 markdown-markup-face))) + (markdown-match-gfm-code-blocks . ((0 markdown-pre-face))) + (markdown-match-fenced-start-code-block . ((1 markdown-markup-face) + (2 markdown-language-keyword-face nil t))) + (markdown-match-fenced-end-code-block . ((0 markdown-markup-face))) + (markdown-match-fenced-code-blocks . ((0 markdown-pre-face))) + (markdown-match-pre-blocks . ((0 markdown-pre-face))) + (markdown-match-blockquotes . ((1 markdown-markup-face) + (2 markdown-blockquote-face))) + (markdown-match-heading-1-setext . ((1 markdown-header-face-1) + (2 markdown-header-rule-face))) + (markdown-match-heading-2-setext . ((1 markdown-header-face-2) + (3 markdown-header-rule-face))) + (markdown-match-heading-6-atx . ((4 markdown-header-delimiter-face) + (5 markdown-header-face-6) + (6 markdown-header-delimiter-face))) + (markdown-match-heading-5-atx . ((4 markdown-header-delimiter-face) + (5 markdown-header-face-5) + (6 markdown-header-delimiter-face))) + (markdown-match-heading-4-atx . ((4 markdown-header-delimiter-face) + (5 markdown-header-face-4) + (6 markdown-header-delimiter-face))) + (markdown-match-heading-3-atx . ((4 markdown-header-delimiter-face) + (5 markdown-header-face-3) + (6 markdown-header-delimiter-face))) + (markdown-match-heading-2-atx . ((4 markdown-header-delimiter-face) + (5 markdown-header-face-2) + (6 markdown-header-delimiter-face))) + (markdown-match-heading-1-atx . ((4 markdown-header-delimiter-face) + (5 markdown-header-face-1) + (6 markdown-header-delimiter-face))) + (markdown-match-declarative-metadata . ((1 markdown-metadata-key-face) + (2 markdown-markup-face) + (3 markdown-metadata-value-face))) + (markdown-match-pandoc-metadata . ((1 markdown-markup-face) + (2 markdown-markup-face) + (3 markdown-metadata-value-face))) + (markdown-match-hr . markdown-header-delimiter-face) + (markdown-match-code . ((1 markdown-markup-face) + (2 markdown-inline-code-face) + (3 markdown-markup-face))) + (,markdown-regex-kbd . ((1 markdown-markup-face) + (2 markdown-inline-code-face) + (3 markdown-markup-face))) + (,markdown-regex-angle-uri . ((1 markdown-markup-face) + (2 markdown-link-face) + (3 markdown-markup-face))) + (,markdown-regex-list . (2 markdown-list-face)) + (,markdown-regex-footnote . ((1 markdown-markup-face) ; [^ + (2 markdown-footnote-face) ; label + (3 markdown-markup-face))) ; ] + (,markdown-regex-link-inline . ((1 markdown-markup-face nil t) ; ! (optional) + (2 markdown-markup-face) ; [ + (3 markdown-link-face) ; text + (4 markdown-markup-face) ; ] + (5 markdown-markup-face) ; ( + (6 markdown-url-face) ; url + (7 markdown-link-title-face nil t) ; "title" (optional) + (8 markdown-markup-face))) ; ) + (,markdown-regex-link-reference . ((1 markdown-markup-face nil t) ; ! (optional) + (2 markdown-markup-face) ; [ + (3 markdown-link-face) ; text + (4 markdown-markup-face) ; ] + (5 markdown-markup-face) ; [ + (6 markdown-reference-face) ; label + (7 markdown-markup-face))) ; ] + (,markdown-regex-reference-definition . ((1 markdown-markup-face) ; [ + (2 markdown-reference-face) ; label + (3 markdown-markup-face) ; ] + (4 markdown-markup-face) ; : + (5 markdown-url-face) ; url + (6 markdown-link-title-face))) ; "title" (optional) + ;; Math mode $..$ + (markdown-match-math-single . ((1 markdown-markup-face prepend) + (2 markdown-math-face append) + (3 markdown-markup-face prepend))) + ;; Math mode $$..$$ + (markdown-match-math-double . ((1 markdown-markup-face prepend) + (2 markdown-math-face append) + (3 markdown-markup-face prepend))) + (markdown-match-bold . ((1 markdown-markup-face prepend) + (2 markdown-bold-face append) + (3 markdown-markup-face prepend))) + (markdown-match-italic . ((1 markdown-markup-face prepend) + (2 markdown-italic-face append) + (3 markdown-markup-face prepend))) + (,markdown-regex-uri . markdown-link-face) + (,markdown-regex-email . markdown-link-face) + (,markdown-regex-line-break . (1 markdown-line-break-face prepend))) + + "Syntax highlighting for Markdown files.") + +(defvar markdown-mode-font-lock-keywords nil + "Default highlighting expressions for Markdown mode. +This variable is defined as a buffer-local variable for dynamic +extension support.") + +;; Footnotes +(defvar markdown-footnote-counter 0 + "Counter for footnote numbers.") +(make-variable-buffer-local 'markdown-footnote-counter) + +(defconst markdown-footnote-chars + "[[:alnum:]-]" + "Regular expression matching any character that is allowed in a footnote identifier.") + +(defconst markdown-regex-footnote-definition + (concat "^\\[\\(\\^" markdown-footnote-chars "*?\\)\\]:\\(?:[ \t]+\\|$\\)") + "Regular expression matching a footnote definition, capturing the label.") + + +;;; Compatibility ============================================================= + +(defun markdown-replace-regexp-in-string (regexp rep string) + "Replace ocurrences of REGEXP with REP in STRING. +This is a compatibility wrapper to provide `replace-regexp-in-string' +in XEmacs 21." + (if (featurep 'xemacs) + (replace-in-string string regexp rep) + (replace-regexp-in-string regexp rep string))) + +;; `markdown-use-region-p' is a compatibility function which checks +;; for an active region, with fallbacks for older Emacsen and XEmacs. +(eval-and-compile + (cond + ;; Emacs 24 and newer + ((fboundp 'use-region-p) + (defalias 'markdown-use-region-p 'use-region-p)) + ;; XEmacs + ((fboundp 'region-active-p) + (defalias 'markdown-use-region-p 'region-active-p)))) + +(defun markdown-use-buttons-p () + "Determine whether this Emacs supports buttons." + (or (featurep 'button) (locate-library "button"))) + +;; Use new names for outline-mode functions in Emacs 25 and later. +(eval-and-compile + (defalias 'markdown-hide-sublevels + (if (fboundp 'outline-hide-sublevels) + 'outline-hide-sublevels + 'hide-sublevels)) + (defalias 'markdown-show-all + (if (fboundp 'outline-show-all) + 'outline-show-all + 'show-all)) + (defalias 'markdown-hide-body + (if (fboundp 'outline-hide-body) + 'outline-hide-body + 'hide-body)) + (defalias 'markdown-show-children + (if (fboundp 'outline-show-children) + 'outline-show-children + 'show-children)) + (defalias 'markdown-show-subtree + (if (fboundp 'outline-show-subtree) + 'outline-show-subtree + 'show-subtree)) + (defalias 'markdown-hide-subtree + (if (fboundp 'outline-hide-subtree) + 'outline-hide-subtree + 'hide-subtree))) + + +;;; Markdown Parsing Functions ================================================ + +(defun markdown-cur-line-blank-p () + "Return t if the current line is blank and nil otherwise." + (save-excursion + (beginning-of-line) + (looking-at-p "^\\s *$"))) + +(defun markdown-prev-line-blank-p () + "Return t if the previous line is blank and nil otherwise. +If we are at the first line, then consider the previous line to be blank." + (or (= (line-beginning-position) (point-min)) + (save-excursion + (forward-line -1) + (markdown-cur-line-blank-p)))) + +(defun markdown-next-line-blank-p () + "Return t if the next line is blank and nil otherwise. +If we are at the last line, then consider the next line to be blank." + (or (= (line-end-position) (point-max)) + (save-excursion + (forward-line 1) + (markdown-cur-line-blank-p)))) + +(defun markdown-prev-line-indent () + "Return the number of leading whitespace characters in the previous line. +Return 0 if the current line is the first line in the buffer." + (save-excursion + (if (= (line-beginning-position) (point-min)) + 0 + (forward-line -1) + (current-indentation)))) + +(defun markdown-next-line-indent () + "Return the number of leading whitespace characters in the next line. +Return 0 if line is the last line in the buffer." + (save-excursion + (if (= (line-end-position) (point-max)) + 0 + (forward-line 1) + (current-indentation)))) + +(defun markdown-cur-non-list-indent () + "Return beginning position of list item text (not including the list marker). +Return nil if the current line is not the beginning of a list item." + (save-match-data + (save-excursion + (beginning-of-line) + (when (re-search-forward markdown-regex-list (line-end-position) t) + (current-column))))) + +(defun markdown-prev-non-list-indent () + "Return position of the first non-list-marker on the previous line." + (save-excursion + (forward-line -1) + (markdown-cur-non-list-indent))) + +(defun markdown-new-baseline-p () + "Determine if the current line begins a new baseline level." + (save-excursion + (beginning-of-line) + (or (looking-at-p markdown-regex-header) + (looking-at-p markdown-regex-hr) + (and (null (markdown-cur-non-list-indent)) + (= (current-indentation) 0) + (markdown-prev-line-blank-p))))) + +(defun markdown-search-backward-baseline () + "Search backward baseline point with no indentation and not a list item." + (end-of-line) + (let (stop) + (while (not (or stop (bobp))) + (re-search-backward markdown-regex-block-separator nil t) + (when (match-end 2) + (goto-char (match-end 2)) + (cond + ((markdown-new-baseline-p) + (setq stop t)) + ((looking-at-p markdown-regex-list) + (setq stop nil)) + (t (setq stop t))))))) + +(defun markdown-update-list-levels (marker indent levels) + "Update list levels given list MARKER, block INDENT, and current LEVELS. +Here, MARKER is a string representing the type of list, INDENT is an integer +giving the indentation, in spaces, of the current block, and LEVELS is a +list of the indentation levels of parent list items. When LEVELS is nil, +it means we are at baseline (not inside of a nested list)." + (cond + ;; New list item at baseline. + ((and marker (null levels)) + (setq levels (list indent))) + ;; List item with greater indentation (four or more spaces). + ;; Increase list level. + ((and marker (>= indent (+ (car levels) 4))) + (setq levels (cons indent levels))) + ;; List item with greater or equal indentation (less than four spaces). + ;; Do not increase list level. + ((and marker (>= indent (car levels))) + levels) + ;; Lesser indentation level. + ;; Pop appropriate number of elements off LEVELS list (e.g., lesser + ;; indentation could move back more than one list level). Note + ;; that this block need not be the beginning of list item. + ((< indent (car levels)) + (while (and (> (length levels) 1) + (< indent (+ (cadr levels) 4))) + (setq levels (cdr levels))) + levels) + ;; Otherwise, do nothing. + (t levels))) + +(defun markdown-calculate-list-levels () + "Calculate list levels at point. +Return a list of the form (n1 n2 n3 ...) where n1 is the +indentation of the deepest nested list item in the branch of +the list at the point, n2 is the indentation of the parent +list item, and so on. The depth of the list item is therefore +the length of the returned list. If the point is not at or +immediately after a list item, return nil." + (save-excursion + (let ((first (point)) levels indent pre-regexp) + ;; Find a baseline point with zero list indentation + (markdown-search-backward-baseline) + ;; Search for all list items between baseline and LOC + (while (and (< (point) first) + (re-search-forward markdown-regex-list first t)) + (setq pre-regexp (format "^\\( \\|\t\\)\\{%d\\}" (1+ (length levels)))) + (beginning-of-line) + (cond + ;; Make sure this is not a header or hr + ((markdown-new-baseline-p) (setq levels nil)) + ;; Make sure this is not a line from a pre block + ((looking-at-p pre-regexp)) + ;; If not, then update levels + (t + (setq indent (current-indentation)) + (setq levels (markdown-update-list-levels (match-string 2) + indent levels)))) + (end-of-line)) + levels))) + +(defun markdown-prev-list-item (level) + "Search backward from point for a list item with indentation LEVEL. +Set point to the beginning of the item, and return point, or nil +upon failure." + (let (bounds indent prev) + (setq prev (point)) + (forward-line -1) + (setq indent (current-indentation)) + (while + (cond + ;; List item + ((and (looking-at-p markdown-regex-list) + (setq bounds (markdown-cur-list-item-bounds))) + (cond + ;; Stop and return point at item of lesser or equal indentation + ((<= (nth 3 bounds) level) + (setq prev (point)) + nil) + ;; Stop at beginning of buffer + ((bobp) (setq prev nil)) + ;; Continue at item with greater indentation + ((> (nth 3 bounds) level) t))) + ;; Stop at beginning of buffer + ((bobp) (setq prev nil)) + ;; Continue if current line is blank + ((markdown-cur-line-blank-p) t) + ;; Continue while indentation is the same or greater + ((>= indent level) t) + ;; Stop if current indentation is less than list item + ;; and the next is blank + ((and (< indent level) + (markdown-next-line-blank-p)) + (setq prev nil)) + ;; Stop at a header + ((looking-at-p markdown-regex-header) (setq prev nil)) + ;; Stop at a horizontal rule + ((looking-at-p markdown-regex-hr) (setq prev nil)) + ;; Otherwise, continue. + (t t)) + (forward-line -1) + (setq indent (current-indentation))) + prev)) + +(defun markdown-next-list-item (level) + "Search forward from point for the next list item with indentation LEVEL. +Set point to the beginning of the item, and return point, or nil +upon failure." + (let (bounds indent next) + (setq next (point)) + (forward-line) + (setq indent (current-indentation)) + (while + (cond + ;; Stop at end of the buffer. + ((eobp) nil) + ;; Continue if the current line is blank + ((markdown-cur-line-blank-p) t) + ;; List item + ((and (looking-at-p markdown-regex-list) + (setq bounds (markdown-cur-list-item-bounds))) + (cond + ;; Continue at item with greater indentation + ((> (nth 3 bounds) level) t) + ;; Stop and return point at item of equal indentation + ((= (nth 3 bounds) level) + (setq next (point)) + nil) + ;; Stop and return nil at item with lesser indentation + ((< (nth 3 bounds) level) + (setq next nil) + nil))) + ;; Continue while indentation is the same or greater + ((>= indent level) t) + ;; Stop if current indentation is less than list item + ;; and the previous line was blank. + ((and (< indent level) + (markdown-prev-line-blank-p)) + (setq next nil)) + ;; Stop at a header + ((looking-at-p markdown-regex-header) (setq next nil)) + ;; Stop at a horizontal rule + ((looking-at-p markdown-regex-hr) (setq next nil)) + ;; Otherwise, continue. + (t t)) + (forward-line) + (setq indent (current-indentation))) + next)) + +(defun markdown-cur-list-item-end (level) + "Move to the end of the current list item with nonlist indentation LEVEL. +If the point is not in a list item, do nothing." + (let (indent) + (forward-line) + (setq indent (current-indentation)) + (while + (cond + ;; Stop at end of the buffer. + ((eobp) nil) + ;; Continue if the current line is blank + ((markdown-cur-line-blank-p) t) + ;; Continue while indentation is the same or greater + ((>= indent level) t) + ;; Stop if current indentation is less than list item + ;; and the previous line was blank. + ((and (< indent level) + (markdown-prev-line-blank-p)) + nil) + ;; Stop at a new list item of the same or lesser indentation + ((looking-at-p markdown-regex-list) nil) + ;; Stop at a header + ((looking-at-p markdown-regex-header) nil) + ;; Stop at a horizontal rule + ((looking-at-p markdown-regex-hr) nil) + ;; Otherwise, continue. + (t t)) + (forward-line) + (setq indent (current-indentation))) + ;; Don't skip over whitespace for empty list items (marker and + ;; whitespace only), just move to end of whitespace. + (if (looking-back (concat markdown-regex-list "\\s-*") nil) + (goto-char (match-end 3)) + (skip-syntax-backward "-")))) + +(defun markdown-cur-list-item-bounds () + "Return bounds and indentation of the current list item. +Return a list of the form (begin end indent nonlist-indent marker). +If the point is not inside a list item, return nil. +Leave match data intact for `markdown-regex-list'." + (let (cur prev-begin prev-end indent nonlist-indent marker) + ;; Store current location + (setq cur (point)) + ;; Verify that cur is between beginning and end of item + (save-excursion + (end-of-line) + (when (re-search-backward markdown-regex-list nil t) + (setq prev-begin (match-beginning 0)) + (setq indent (length (match-string 1))) + (setq nonlist-indent (length (match-string 0))) + (setq marker (concat (match-string 2) (match-string 3))) + (save-match-data + (markdown-cur-list-item-end nonlist-indent) + (setq prev-end (point))) + (when (and (>= cur prev-begin) + (<= cur prev-end) + nonlist-indent) + (list prev-begin prev-end indent nonlist-indent marker)))))) + +(defun markdown-bounds-of-thing-at-point (thing) + "Call `bounds-of-thing-at-point' for THING with slight modifications. +Does not include trailing newlines when THING is 'line. Handles the +end of buffer case by setting both endpoints equal to the value of +`point-max', since an empty region will trigger empty markup insertion. +Return bounds of form (beg . end) if THING is found, or nil otherwise." + (let* ((bounds (bounds-of-thing-at-point thing)) + (a (car bounds)) + (b (cdr bounds))) + (when bounds + (when (eq thing 'line) + (cond ((and (eobp) (markdown-cur-line-blank-p)) + (setq a b)) + ((char-equal (char-before b) ?\^J) + (setq b (1- b))))) + (cons a b)))) + +(defun markdown-reference-definition (reference) + "Find out whether Markdown REFERENCE is defined. +REFERENCE should not include the square brackets. +When REFERENCE is defined, return a list of the form (text start end) +containing the definition text itself followed by the start and end +locations of the text. Otherwise, return nil. +Leave match data for `markdown-regex-reference-definition' +intact additional processing." + (let ((reference (downcase reference))) + (save-excursion + (goto-char (point-min)) + (catch 'found + (while (re-search-forward markdown-regex-reference-definition nil t) + (when (string= reference (downcase (match-string-no-properties 2))) + (throw 'found + (list (match-string-no-properties 5) + (match-beginning 5) (match-end 5))))))))) + +(defun markdown-get-defined-references () + "Return a list of all defined reference labels (not including square brackets)." + (save-excursion + (goto-char (point-min)) + (let (refs) + (while (re-search-forward markdown-regex-reference-definition nil t) + (let ((target (match-string-no-properties 2))) + (cl-pushnew target refs :test #'equal))) + (reverse refs)))) + +(defun markdown-code-at-point-p () + "Return non-nil if the point is at an inline code fragment. +Return nil otherwise. Set match data according to +`markdown-match-code' upon success. +This function searches the block for a code fragment that +contains the point using `markdown-match-code'. We do this +because `thing-at-point-looking-at' does not work reliably with +`markdown-regex-code'. + +The match data is set as follows: +Group 1 matches the opening backticks. +Group 2 matches the code fragment itself, without backticks. +Group 3 matches the closing backticks." + (interactive) + (save-excursion + (let ((old-point (point)) + (end-of-block (progn (markdown-end-of-block) (point))) + found) + (markdown-beginning-of-block) + (while (and (markdown-match-code end-of-block) + (setq found t) + (< (match-end 0) old-point))) + (and found ; matched something + (<= (match-beginning 0) old-point) ; match contains old-point + (>= (match-end 0) old-point))))) + +(defun markdown-code-block-at-pos (pos) + "Return match data list if there is a code block at POS. +This includes pre blocks, tilde-fenced code blocks, and GFM +quoted code blocks. Return nil otherwise." + (or (get-text-property pos 'markdown-pre) + (markdown-get-enclosing-fenced-block-construct pos) + ;; polymode removes text properties set by markdown-mode, so + ;; check if `poly-markdown-mode' is active and whether the + ;; `chunkmode' property is non-nil at POS. + (and (bound-and-true-p poly-markdown-mode) + (get-text-property pos 'chunkmode)))) + +(defun markdown-code-block-at-point () + "Return match data if the point is inside a code block. +This includes pre blocks, tilde-fenced code blocks, and +GFM quoted code blocks. Calls `markdown-code-block-at-pos'." + (markdown-code-block-at-pos (point))) + + +;;; Markdown Font Lock Matching Functions ===================================== + +(defun markdown-range-property-any (begin end prop prop-values) + "Return t if PROP from BEGIN to END is equal to one of the given PROP-VALUES. +Also returns t if PROP is a list containing one of the PROP-VALUES. +Return nil otherwise." + (let (props) + (catch 'found + (dolist (loc (number-sequence begin end)) + (when (setq props (get-char-property loc prop)) + (cond ((listp props) + ;; props is a list, check for membership + (dolist (val prop-values) + (when (memq val props) (throw 'found loc)))) + (t + ;; props is a scalar, check for equality + (dolist (val prop-values) + (when (eq val props) (throw 'found loc)))))))))) + +(defun markdown-range-properties-exist (begin end props) + (cl-loop + for loc in (number-sequence begin end) + with result = nil + while (not + (setq result + (cl-some (lambda (prop) (get-char-property loc prop)) props))) + finally return result)) + +(defun markdown-match-inline-generic (regex last) + "Match inline REGEX from the point to LAST." + (when (re-search-forward regex last t) + (let ((bounds (markdown-code-block-at-pos (match-beginning 1)))) + (if (null bounds) + ;; Not in a code block: keep match data and return t when in bounds + (<= (match-end 0) last) + ;; In code block: move past it and recursively search again + (when (< (goto-char (nth 1 bounds)) last) + (markdown-match-inline-generic regex last)))))) + +(defun markdown-match-code (last) + "Match inline code fragments from point to LAST." + (unless (bobp) + (backward-char 1)) + (when (markdown-match-inline-generic markdown-regex-code last) + (set-match-data (list (match-beginning 1) (match-end 1) + (match-beginning 2) (match-end 2) + (match-beginning 3) (match-end 3) + (match-beginning 4) (match-end 4))) + (goto-char (1+ (match-end 0))))) + +(defun markdown-match-bold (last) + "Match inline bold from the point to LAST." + (when (markdown-match-inline-generic markdown-regex-bold last) + (let ((begin (match-beginning 2)) (end (match-end 2))) + (cond + ((markdown-range-property-any + begin end 'face (list markdown-inline-code-face + markdown-math-face)) + (goto-char (1+ (match-end 0))) + (markdown-match-bold last)) + (t + (set-match-data (list (match-beginning 2) (match-end 2) + (match-beginning 3) (match-end 3) + (match-beginning 4) (match-end 4) + (match-beginning 5) (match-end 5))) + (goto-char (1+ (match-end 0)))))))) + +(defun markdown-match-italic (last) + "Match inline italics from the point to LAST." + (let ((regex (if (eq major-mode 'gfm-mode) + markdown-regex-gfm-italic markdown-regex-italic))) + (when (markdown-match-inline-generic regex last) + (let ((begin (match-beginning 1)) (end (match-end 1))) + (cond + ((markdown-range-property-any + begin begin 'face (list markdown-url-face)) + ;; Italics shouldn't begin inside a URL due to an underscore + (goto-char (min (1+ (match-end 0)) last)) + (markdown-match-italic last)) + ((markdown-range-property-any + begin end 'face (list markdown-inline-code-face + markdown-bold-face + markdown-list-face + markdown-math-face)) + (goto-char (1+ (match-end 0))) + (markdown-match-italic last)) + (t + (set-match-data (list (match-beginning 1) (match-end 1) + (match-beginning 2) (match-end 2) + (match-beginning 3) (match-end 3) + (match-beginning 4) (match-end 4))) + (goto-char (1+ (match-end 0))))))))) + +(defun markdown-match-math-generic (regex last) + "Match REGEX from point to LAST. +REGEX is either `markdown-regex-math-inline-single' for matching +$..$ or `markdown-regex-math-inline-double' for matching $$..$$." + (when (and markdown-enable-math (markdown-match-inline-generic regex last)) + (let ((begin (match-beginning 1)) (end (match-end 1))) + (prog1 + (if (or (markdown-range-property-any + begin end 'face (list markdown-inline-code-face + markdown-bold-face)) + (markdown-range-properties-exist + begin end + (markdown-get-fenced-block-middle-properties))) + (markdown-match-math-generic regex last) + t) + (goto-char (1+ (match-end 0))))))) + +(defun markdown-match-math-single (last) + "Match single quoted $..$ math from point to LAST." + (markdown-match-math-generic markdown-regex-math-inline-single last)) + +(defun markdown-match-math-double (last) + "Match double quoted $$..$$ math from point to LAST." + (markdown-match-math-generic markdown-regex-math-inline-double last)) + +(defun markdown-match-propertized-text (property last) + "Match text with PROPERTY from point to LAST. +Restore match data previously stored in PROPERTY." + (let ((saved (get-text-property (point) property)) + pos) + (unless saved + (setq pos (next-single-char-property-change (point) property nil last)) + (setq saved (get-text-property pos property))) + (when saved + (set-match-data saved) + ;; Step at least one character beyond point. Otherwise + ;; `font-lock-fontify-keywords-region' infloops. + (goto-char (min (1+ (max (match-end 0) (point))) + (point-max))) + saved))) + +(defun markdown-match-pre-blocks (last) + "Match preformatted blocks from point to LAST. +Use data stored in 'markdown-pre text property during syntax +analysis." + (markdown-match-propertized-text 'markdown-pre last)) + +(defun markdown-match-gfm-code-blocks (last) + "Match GFM quoted code blocks from point to LAST. +Use data stored in 'markdown-gfm-code text property during syntax +analysis." + (markdown-match-propertized-text 'markdown-gfm-code last)) + +(defun markdown-match-gfm-open-code-blocks (last) + (markdown-match-propertized-text 'markdown-gfm-block-begin last)) + +(defun markdown-match-gfm-close-code-blocks (last) + (markdown-match-propertized-text 'markdown-gfm-block-end last)) + +(defun markdown-match-fenced-code-blocks (last) + "Match fenced code blocks from the point to LAST." + (markdown-match-propertized-text 'markdown-fenced-code last)) + +(defun markdown-match-fenced-start-code-block (last) + (markdown-match-propertized-text 'markdown-tilde-fence-begin last)) + +(defun markdown-match-fenced-end-code-block (last) + (markdown-match-propertized-text 'markdown-tilde-fence-end last)) + +(defun markdown-match-blockquotes (last) + "Match blockquotes from point to LAST. +Use data stored in 'markdown-blockquote text property during syntax +analysis." + (markdown-match-propertized-text 'markdown-blockquote last)) + +(defun markdown-match-heading-1-setext (last) + "Match level 1 setext headings from point to LAST." + (markdown-match-propertized-text 'markdown-heading-1-setext last)) + +(defun markdown-match-heading-2-setext (last) + "Match level 2 setext headings from point to LAST." + (markdown-match-propertized-text 'markdown-heading-2-setext last)) + +(defun markdown-match-heading-1-atx (last) + "Match level 1 ATX headings from point to LAST." + (markdown-match-propertized-text 'markdown-heading-1-atx last)) + +(defun markdown-match-heading-2-atx (last) + "Match level 2 ATX headings from point to LAST." + (markdown-match-propertized-text 'markdown-heading-2-atx last)) + +(defun markdown-match-heading-3-atx (last) + "Match level 3 ATX headings from point to LAST." + (markdown-match-propertized-text 'markdown-heading-3-atx last)) + +(defun markdown-match-heading-4-atx (last) + "Match level 4 ATX headings from point to LAST." + (markdown-match-propertized-text 'markdown-heading-4-atx last)) + +(defun markdown-match-heading-5-atx (last) + "Match level 5 ATX headings from point to LAST." + (markdown-match-propertized-text 'markdown-heading-5-atx last)) + +(defun markdown-match-heading-6-atx (last) + "Match level 6 ATX headings from point to LAST." + (markdown-match-propertized-text 'markdown-heading-6-atx last)) + +(defun markdown-match-hr (last) + "Match horizontal rules comments from the point to LAST." + (while (and (re-search-forward markdown-regex-hr last t) + (or (markdown-on-heading-p) + (markdown-code-block-at-point)) + (< (match-end 0) last)) + (forward-line)) + (beginning-of-line) + (cond ((looking-at-p markdown-regex-hr) + (forward-line) + t) + (t nil))) + +(defun markdown-match-comments (last) + "Match HTML comments from the point to LAST." + (when (and (skip-syntax-forward "^<" last)) + (let ((beg (point))) + (when (and (skip-syntax-forward "^>" last) (< (point) last)) + (forward-char) + (set-match-data (list beg (point))) + t)))) + +(defun markdown-get-match-boundaries (start-header end-header last &optional pos) + (save-excursion + (goto-char (or pos (point-min))) + (cl-loop + with cur-result = nil + and st-hdr = (or start-header "\\`") + and end-hdr = (or end-header "\n\n\\|\n\\'\\|\\'") + while (and (< (point) last) + (re-search-forward st-hdr last t) + (progn + (setq cur-result (match-data)) + (re-search-forward end-hdr nil t))) + collect (list cur-result (match-data))))) + +(defvar markdown-conditional-search-function #'re-search-forward + "Conditional search function used in `markdown-search-until-condition'. +Made into a variable to allow for dynamic let-binding.") + +(defun markdown-search-until-condition (condition &rest args) + (let (ret) + (while (and (not ret) (apply markdown-conditional-search-function args)) + (setq ret (funcall condition))) + ret)) + +(defun markdown-match-generic-metadata + (regexp last &optional start-header end-header) + "Match generic metadata specified by REGEXP from the point to LAST. +If START-HEADER is nil, we assume metadata can only occur at the +very top of a file (\"\\`\"). If END-HEADER is nil, we assume it +is \"\n\n\"" + (let* ((header-bounds + (markdown-get-match-boundaries start-header end-header last)) + (enclosing-header + (cl-find-if ; just take first if multiple + (lambda (match-bounds) + (cl-destructuring-bind (begin end) (cl-second match-bounds) + (and (< (point) begin) + (save-excursion (re-search-forward regexp end t))))) + header-bounds)) + (header-begin + (when enclosing-header (cl-second (cl-first enclosing-header)))) + (header-end + (when enclosing-header (cl-first (cl-second enclosing-header))))) + (cond ((null enclosing-header) + ;; Don't match anything outside of a header. + nil) + ((markdown-search-until-condition + (lambda () (> (point) header-begin)) regexp (min last header-end) t) + ;; If a metadata item is found, it may span several lines. + (let ((key-beginning (match-beginning 1)) + (key-end (match-end 1)) + (markup-begin (match-beginning 2)) + (markup-end (match-end 2)) + (value-beginning (match-beginning 3))) + (set-match-data (list key-beginning (point) ; complete metadata + key-beginning key-end ; key + markup-begin markup-end ; markup + value-beginning (point))) ; value + t)) + (t nil)))) + +(defun markdown-match-declarative-metadata (last) + "Match declarative metadata from the point to LAST." + (markdown-match-generic-metadata markdown-regex-declarative-metadata last)) + +(defun markdown-match-pandoc-metadata (last) + "Match Pandoc metadata from the point to LAST." + (markdown-match-generic-metadata markdown-regex-pandoc-metadata last)) + +(defun markdown-match-yaml-metadata-begin (last) + (markdown-match-propertized-text 'markdown-yaml-metadata-begin last)) + +(defun markdown-match-yaml-metadata-end (last) + (markdown-match-propertized-text 'markdown-yaml-metadata-end last)) + +(defun markdown-match-yaml-metadata-key (last) + (markdown-match-propertized-text 'markdown-metadata-key last)) + + +;;; Syntax Table ============================================================== + +(defvar markdown-mode-syntax-table + (let ((tab (make-syntax-table text-mode-syntax-table))) + (modify-syntax-entry ?\" "." tab) + tab) + "Syntax table for `markdown-mode'.") + + +;;; Element Insertion ========================================================= + +(defun markdown-ensure-blank-line-before () + "If previous line is not already blank, insert a blank line before point." + (unless (bolp) (insert "\n")) + (unless (or (bobp) (looking-back "\n\\s-*\n" nil)) (insert "\n"))) + +(defun markdown-ensure-blank-line-after () + "If following line is not already blank, insert a blank line after point. +Return the point where it was originally." + (save-excursion + (unless (eolp) (insert "\n")) + (unless (or (eobp) (looking-at-p "\n\\s-*\n")) (insert "\n")))) + +(defun markdown-wrap-or-insert (s1 s2 &optional thing beg end) + "Insert the strings S1 and S2, wrapping around region or THING. +If a region is specified by the optional BEG and END arguments, +wrap the strings S1 and S2 around that region. +If there is an active region, wrap the strings S1 and S2 around +the region. If there is not an active region but the point is at +THING, wrap that thing (which defaults to word). Otherwise, just +insert S1 and S2 and place the cursor in between. Return the +bounds of the entire wrapped string, or nil if nothing was wrapped +and S1 and S2 were only inserted." + (let (a b bounds new-point) + (cond + ;; Given region + ((and beg end) + (setq a beg + b end + new-point (+ (point) (length s1)))) + ;; Active region + ((markdown-use-region-p) + (setq a (region-beginning) + b (region-end) + new-point (+ (point) (length s1)))) + ;; Thing (word) at point + ((setq bounds (markdown-bounds-of-thing-at-point (or thing 'word))) + (setq a (car bounds) + b (cdr bounds) + new-point (+ (point) (length s1)))) + ;; No active region and no word + (t + (setq a (point) + b (point)))) + (goto-char b) + (insert s2) + (goto-char a) + (insert s1) + (when new-point (goto-char new-point)) + (if (= a b) + nil + (setq b (+ b (length s1) (length s2))) + (cons a b)))) + +(defun markdown-point-after-unwrap (cur prefix suffix) + "Return desired position of point after an unwrapping operation. +CUR gives the position of the point before the operation. +Additionally, two cons cells must be provided. PREFIX gives the +bounds of the prefix string and SUFFIX gives the bounds of the +suffix string." + (cond ((< cur (cdr prefix)) (car prefix)) + ((< cur (car suffix)) (- cur (- (cdr prefix) (car prefix)))) + ((<= cur (cdr suffix)) + (- cur (+ (- (cdr prefix) (car prefix)) + (- cur (car suffix))))) + (t cur))) + +(defun markdown-unwrap-thing-at-point (regexp all text) + "Remove prefix and suffix of thing at point and reposition the point. +When the thing at point matches REGEXP, replace the subexpression +ALL with the string in subexpression TEXT. Reposition the point +in an appropriate location accounting for the removal of prefix +and suffix strings. Return new bounds of string from group TEXT. +When REGEXP is nil, assumes match data is already set." + (when (or (null regexp) + (thing-at-point-looking-at regexp)) + (let ((cur (point)) + (prefix (cons (match-beginning all) (match-beginning text))) + (suffix (cons (match-end text) (match-end all))) + (bounds (cons (match-beginning text) (match-end text)))) + ;; Replace the thing at point + (replace-match (match-string text) t t nil all) + ;; Reposition the point + (goto-char (markdown-point-after-unwrap cur prefix suffix)) + ;; Adjust bounds + (setq bounds (cons (car prefix) + (- (cdr bounds) (- (cdr prefix) (car prefix)))))))) + +(defun markdown-unwrap-things-in-region (beg end regexp all text) + "Remove prefix and suffix of all things in region from BEG to END. +When a thing in the region matches REGEXP, replace the +subexpression ALL with the string in subexpression TEXT. +Return a cons cell containing updated bounds for the region." + (save-excursion + (goto-char beg) + (let ((removed 0) len-all len-text) + (while (re-search-forward regexp (- end removed) t) + (setq len-all (length (match-string-no-properties all))) + (setq len-text (length (match-string-no-properties text))) + (setq removed (+ removed (- len-all len-text))) + (replace-match (match-string text) t t nil all)) + (cons beg (- end removed))))) + +(defun markdown-insert-hr (arg) + "Insert or replace a horizonal rule. +By default, use the first element of `markdown-hr-strings'. When +ARG is non-nil, as when given a prefix, select a different +element as follows. When prefixed with \\[universal-argument], +use the last element of `markdown-hr-strings' instead. When +prefixed with an integer from 1 to the length of +`markdown-hr-strings', use the element in that position instead." + (interactive "*P") + (when (thing-at-point-looking-at markdown-regex-hr) + (delete-region (match-beginning 0) (match-end 0))) + (markdown-ensure-blank-line-before) + (cond ((equal arg '(4)) + (insert (car (reverse markdown-hr-strings)))) + ((and (integerp arg) (> arg 0) + (<= arg (length markdown-hr-strings))) + (insert (nth (1- arg) markdown-hr-strings))) + (t + (insert (car markdown-hr-strings)))) + (markdown-ensure-blank-line-after)) + +(defun markdown-insert-bold () + "Insert markup to make a region or word bold. +If there is an active region, make the region bold. If the point +is at a non-bold word, make the word bold. If the point is at a +bold word or phrase, remove the bold markup. Otherwise, simply +insert bold delimiters and place the cursor in between them." + (interactive) + (let ((delim (if markdown-bold-underscore "__" "**"))) + (if (markdown-use-region-p) + ;; Active region + (let ((bounds (markdown-unwrap-things-in-region + (region-beginning) (region-end) + markdown-regex-bold 2 4))) + (markdown-wrap-or-insert delim delim nil (car bounds) (cdr bounds))) + ;; Bold markup removal, bold word at point, or empty markup insertion + (if (thing-at-point-looking-at markdown-regex-bold) + (markdown-unwrap-thing-at-point nil 2 4) + (markdown-wrap-or-insert delim delim 'word nil nil))))) + +(defun markdown-insert-italic () + "Insert markup to make a region or word italic. +If there is an active region, make the region italic. If the point +is at a non-italic word, make the word italic. If the point is at an +italic word or phrase, remove the italic markup. Otherwise, simply +insert italic delimiters and place the cursor in between them." + (interactive) + (let ((delim (if markdown-italic-underscore "_" "*"))) + (if (markdown-use-region-p) + ;; Active region + (let ((bounds (markdown-unwrap-things-in-region + (region-beginning) (region-end) + markdown-regex-italic 1 3))) + (markdown-wrap-or-insert delim delim nil (car bounds) (cdr bounds))) + ;; Italic markup removal, italic word at point, or empty markup insertion + (if (thing-at-point-looking-at markdown-regex-italic) + (markdown-unwrap-thing-at-point nil 1 3) + (markdown-wrap-or-insert delim delim 'word nil nil))))) + +(defun markdown-insert-strike-through () + "Insert markup to make a region or word strikethrough. +If there is an active region, make the region strikethrough. If the point +is at a non-bold word, make the word strikethrough. If the point is at a +strikethrough word or phrase, remove the strikethrough markup. Otherwise, +simply insert bold delimiters and place the cursor in between them." + (interactive) + (let ((delim "~~")) + (if (markdown-use-region-p) + ;; Active region + (let ((bounds (markdown-unwrap-things-in-region + (region-beginning) (region-end) + markdown-regex-strike-through 2 4))) + (markdown-wrap-or-insert delim delim nil (car bounds) (cdr bounds))) + ;; Strikethrough markup removal, strikethrough word at point, or empty markup insertion + (if (thing-at-point-looking-at markdown-regex-strike-through) + (markdown-unwrap-thing-at-point nil 2 4) + (markdown-wrap-or-insert delim delim 'word nil nil))))) + +(defun markdown-insert-code () + "Insert markup to make a region or word an inline code fragment. +If there is an active region, make the region an inline code +fragment. If the point is at a word, make the word an inline +code fragment. Otherwise, simply insert code delimiters and +place the cursor in between them." + (interactive) + (if (markdown-use-region-p) + ;; Active region + (let ((bounds (markdown-unwrap-things-in-region + (region-beginning) (region-end) + markdown-regex-code 1 3))) + (markdown-wrap-or-insert "`" "`" nil (car bounds) (cdr bounds))) + ;; Code markup removal, code markup for word, or empty markup insertion + (if (markdown-code-at-point-p) + (markdown-unwrap-thing-at-point nil 0 2) + (markdown-wrap-or-insert "`" "`" 'word nil nil)))) + +(defun markdown-insert-kbd () + "Insert markup to wrap region or word in tags. +If there is an active region, use the region. If the point is at +a word, use the word. Otherwise, simply insert tags and +place the cursor in between them." + (interactive) + (if (markdown-use-region-p) + ;; Active region + (let ((bounds (markdown-unwrap-things-in-region + (region-beginning) (region-end) + markdown-regex-kbd 0 2))) + (markdown-wrap-or-insert "" "" nil (car bounds) (cdr bounds))) + ;; Markup removal, markup for word, or empty markup insertion + (if (thing-at-point-looking-at markdown-regex-kbd) + (markdown-unwrap-thing-at-point nil 0 2) + (markdown-wrap-or-insert "" "" 'word nil nil)))) + +(defun markdown-insert-link () + "Insert an inline link, using region or word as link text if possible. +If there is an active region, use the region as the link text. If the +point is at a word, use the word as the link text. In these cases, the +point will be left at the position for inserting a URL. If there is no +active region and the point is not at word, simply insert link markup and +place the point in the position to enter link text." + (interactive) + (let ((bounds (markdown-wrap-or-insert "[" "]()"))) + (when bounds + (goto-char (- (cdr bounds) 1))))) + +(defun markdown-insert-reference-link (text label &optional url title) + "Insert a reference link and, optionally, a reference definition. +The link TEXT will be inserted followed by the optional LABEL. +If a URL is given, also insert a definition for the reference +LABEL according to `markdown-reference-location'. If a TITLE is +given, it will be added to the end of the reference definition +and will be used to populate the title attribute when converted +to XHTML. If URL is nil, insert only the link portion (for +example, when a reference label is already defined)." + (insert (concat "[" text "][" label "]")) + (when url + (markdown-insert-reference-definition + (if (string-equal label "") text label) + url title))) + +(defun markdown-insert-reference-definition (label &optional url title) + "Add definition for reference LABEL with URL and TITLE. +LABEL is a Markdown reference label without square brackets. +URL and TITLE are optional. When given, the TITLE will +be used to populate the title attribute when converted to XHTML." + ;; END specifies where to leave the point upon return + (let ((end (point))) + (cl-case markdown-reference-location + (end (goto-char (point-max))) + (immediately (markdown-end-of-block)) + (header (markdown-end-of-defun))) + (unless (markdown-cur-line-blank-p) (insert "\n")) + (insert "\n[" label "]: ") + (if url + (insert url) + ;; When no URL is given, leave cursor at END following the colon + (setq end (point))) + (when (> (length title) 0) + (insert " \"" title "\"")) + (unless (looking-at-p "\n") + (insert "\n")) + (goto-char end) + (when url + (message + (substitute-command-keys + "Defined reference [%s], press \\[markdown-jump] to jump there") + label)))) + +(defun markdown-insert-reference-link-dwim () + "Insert a reference link of the form [text][label] at point. +If there is an active region, the text in the region will be used +as the link text. If the point is at an inline link, it will be +converted to a reference link. If the point is at a word, it will +be used as the link text. Otherwise, the link text will be read from +the minibuffer. The link label will be read from the minibuffer in +both cases, with completion from the set of currently defined +references. To create an implicit reference link, press RET to +accept the default, an empty label. If the entered referenced +label is not defined, additionally prompt for the URL +and (optional) title. The reference definition is placed at the +location determined by `markdown-reference-location'." + (interactive) + (let* ((defined-labels (markdown-get-defined-references)) + (switch (thing-at-point-looking-at markdown-regex-link-inline)) + (bounds (cond ((markdown-use-region-p) + (cons (region-beginning) (region-end))) + (switch + (cons (match-beginning 0) (match-end 0))) + (t + (markdown-bounds-of-thing-at-point 'word)))) + (text (cond (switch (match-string 3)) + (bounds (buffer-substring (car bounds) (cdr bounds))) + (t (read-string "Link Text: ")))) + (label (completing-read + "Link Label (default: none): " defined-labels + nil nil nil 'markdown-reference-label-history nil)) + (ref (save-match-data + (markdown-reference-definition + (if (> (length label) 0) label text)))) + (url (cond (ref nil) + (switch (match-string 6)) + (t (read-string "Link URL: ")))) + (title (cond + ((= (length url) 0) nil) + (switch (if (> (length (match-string 7)) 2) + (substring (match-string 7) 1 -1) + nil)) + (t (read-string "Link Title (optional): "))))) + (when bounds (delete-region (car bounds) (cdr bounds))) + (markdown-insert-reference-link text label url title))) + +(defun markdown-insert-uri () + "Insert markup for an inline URI. +If there is an active region, use it as the URI. If the point is +at a URI, wrap it with angle brackets. If the point is at an +inline URI, remove the angle brackets. Otherwise, simply insert +angle brackets place the point between them." + (interactive) + (if (markdown-use-region-p) + ;; Active region + (let ((bounds (markdown-unwrap-things-in-region + (region-beginning) (region-end) + markdown-regex-angle-uri 0 2))) + (markdown-wrap-or-insert "<" ">" nil (car bounds) (cdr bounds))) + ;; Markup removal, URI at point, or empty markup insertion + (if (thing-at-point-looking-at markdown-regex-angle-uri) + (markdown-unwrap-thing-at-point nil 0 2) + (markdown-wrap-or-insert "<" ">" 'url nil nil)))) + +(defun markdown-insert-wiki-link () + "Insert a wiki link of the form [[WikiLink]]. +If there is an active region, use the region as the link text. +If the point is at a word, use the word as the link text. If +there is no active region and the point is not at word, simply +insert link markup." + (interactive) + (if (markdown-use-region-p) + ;; Active region + (markdown-wrap-or-insert "[[" "]]" nil (region-beginning) (region-end)) + ;; Markup removal, wiki link at at point, or empty markup insertion + (if (thing-at-point-looking-at markdown-regex-wiki-link) + (if (or markdown-wiki-link-alias-first + (null (match-string 5))) + (markdown-unwrap-thing-at-point nil 1 3) + (markdown-unwrap-thing-at-point nil 1 5)) + (markdown-wrap-or-insert "[[" "]]")))) + +(defun markdown-insert-image (&optional arg) + "Insert image markup using region or word as alt text if possible. +If there is an active region, use the region as the alt text. If the +point is at a word, use the word as the alt text. In these cases, the +point will be left at the position for inserting a URL. If there is no +active region and the point is not at word, simply insert image markup and +place the point in the position to enter alt text. If ARG is nil, insert +inline image markup. Otherwise, insert reference image markup." + (interactive "*P") + (let ((bounds (if arg + (markdown-wrap-or-insert "![" "][]") + (markdown-wrap-or-insert "![" "]()")))) + (when bounds + (goto-char (- (cdr bounds) 1))))) + +(defun markdown-insert-reference-image () + "Insert reference-style image markup using region or word as alt text. +Calls `markdown-insert-image' with prefix argument." + (interactive) + (markdown-insert-image t)) + +(defun markdown-remove-header () + "Remove header markup if point is at a header. +Return bounds of remaining header text if a header was removed +and nil otherwise." + (interactive "*") + (or (markdown-unwrap-thing-at-point markdown-regex-header-atx 0 2) + (markdown-unwrap-thing-at-point markdown-regex-header-setext 0 1))) + +(defun markdown-insert-header (&optional level text setext) + "Insert or replace header markup. +The level of the header is specified by LEVEL and header text is +given by TEXT. LEVEL must be an integer from 1 and 6, and the +default value is 1. +When TEXT is nil, the header text is obtained as follows. +If there is an active region, it is used as the header text. +Otherwise, the current line will be used as the header text. +If there is not an active region and the point is at a header, +remove the header markup and replace with level N header. +Otherwise, insert empty header markup and place the cursor in +between. +The style of the header will be atx (hash marks) unless +SETEXT is non-nil, in which case a setext-style (underlined) +header will be inserted." + (interactive "p\nsHeader text: ") + (setq level (min (max (or level 1) 1) (if setext 2 6))) + ;; Determine header text if not given + (when (null text) + (if (markdown-use-region-p) + ;; Active region + (setq text (delete-and-extract-region (region-beginning) (region-end))) + ;; No active region + (markdown-remove-header) + (setq text (delete-and-extract-region + (line-beginning-position) (line-end-position))) + (when (and setext (string-match-p "^[ \t]*$" text)) + (setq text (read-string "Header text: ")))) + (setq text (markdown-compress-whitespace-string text))) + ;; Insertion with given text + (markdown-ensure-blank-line-before) + (let (hdr) + (cond (setext + (setq hdr (make-string (string-width text) (if (= level 2) ?- ?=))) + (insert text "\n" hdr)) + (t + (setq hdr (make-string level ?#)) + (insert hdr " " text) + (when (null markdown-asymmetric-header) (insert " " hdr))))) + (markdown-ensure-blank-line-after) + ;; Leave point at end of text + (cond (setext + (backward-char (1+ (string-width text)))) + ((null markdown-asymmetric-header) + (backward-char (1+ level))))) + +(defun markdown-insert-header-dwim (&optional arg setext) + "Insert or replace header markup. +The level and type of the header are determined automatically by +the type and level of the previous header, unless a prefix +argument is given via ARG. +With a numeric prefix valued 1 to 6, insert a header of the given +level, with the type being determined automatically (note that +only level 1 or 2 setext headers are possible). + +With a \\[universal-argument] prefix (i.e., when ARG is (4)), +promote the heading by one level. +With two \\[universal-argument] prefixes (i.e., when ARG is (16)), +demote the heading by one level. +When SETEXT is non-nil, prefer setext-style headers when +possible (levels one and two). + +When there is an active region, use it for the header text. When +the point is at an existing header, change the type and level +according to the rules above. +Otherwise, if the line is not empty, create a header using the +text on the current line as the header text. +Finally, if the point is on a blank line, insert empty header +markup (atx) or prompt for text (setext). +See `markdown-insert-header' for more details about how the +header text is determined." + (interactive "*P") + (let (level) + (save-excursion + (when (or (thing-at-point-looking-at markdown-regex-header) + (re-search-backward markdown-regex-header nil t)) + ;; level of current or previous header + (setq level (markdown-outline-level)) + ;; match group 1 indicates a setext header + (setq setext (match-end 1)))) + ;; check prefix argument + (cond + ((and (equal arg '(4)) level (> level 1)) ;; C-u + (cl-decf level)) + ((and (equal arg '(16)) level (< level 6)) ;; C-u C-u + (cl-incf level)) + (arg ;; numeric prefix + (setq level (prefix-numeric-value arg)))) + ;; setext headers must be level one or two + (and level (setq setext (and setext (<= level 2)))) + ;; insert the heading + (markdown-insert-header level nil setext))) + +(defun markdown-insert-header-setext-dwim (&optional arg) + "Insert or replace header markup, with preference for setext. +See `markdown-insert-header-dwim' for details, including how ARG is handled." + (interactive "*P") + (markdown-insert-header-dwim arg t)) + +(defun markdown-insert-header-atx-1 () + "Insert a first level atx-style (hash mark) header. +See `markdown-insert-header'." + (interactive "*") + (markdown-insert-header 1 nil nil)) + +(defun markdown-insert-header-atx-2 () + "Insert a level two atx-style (hash mark) header. +See `markdown-insert-header'." + (interactive "*") + (markdown-insert-header 2 nil nil)) + +(defun markdown-insert-header-atx-3 () + "Insert a level three atx-style (hash mark) header. +See `markdown-insert-header'." + (interactive "*") + (markdown-insert-header 3 nil nil)) + +(defun markdown-insert-header-atx-4 () + "Insert a level four atx-style (hash mark) header. +See `markdown-insert-header'." + (interactive "*") + (markdown-insert-header 4 nil nil)) + +(defun markdown-insert-header-atx-5 () + "Insert a level five atx-style (hash mark) header. +See `markdown-insert-header'." + (interactive "*") + (markdown-insert-header 5 nil nil)) + +(defun markdown-insert-header-atx-6 () + "Insert a sixth level atx-style (hash mark) header. +See `markdown-insert-header'." + (interactive "*") + (markdown-insert-header 6 nil nil)) + +(defun markdown-insert-header-setext-1 () + "Insert a setext-style (underlined) first-level header. +See `markdown-insert-header'." + (interactive "*") + (markdown-insert-header 1 nil t)) + +(defun markdown-insert-header-setext-2 () + "Insert a setext-style (underlined) second-level header. +See `markdown-insert-header'." + (interactive "*") + (markdown-insert-header 2 nil t)) + +(defun markdown-blockquote-indentation (loc) + "Return string containing necessary indentation for a blockquote at LOC. +Also see `markdown-pre-indentation'." + (save-excursion + (goto-char loc) + (let* ((list-level (length (markdown-calculate-list-levels))) + (indent "")) + (dotimes (_ list-level indent) + (setq indent (concat indent " ")))))) + +(defun markdown-insert-blockquote () + "Start a blockquote section (or blockquote the region). +If Transient Mark mode is on and a region is active, it is used as +the blockquote text." + (interactive) + (if (markdown-use-region-p) + (markdown-blockquote-region (region-beginning) (region-end)) + (markdown-ensure-blank-line-before) + (insert (markdown-blockquote-indentation (point)) "> ") + (markdown-ensure-blank-line-after))) + +(defun markdown-block-region (beg end prefix) + "Format the region using a block prefix. +Arguments BEG and END specify the beginning and end of the +region. The characters PREFIX will appear at the beginning +of each line." + (save-excursion + (let* ((end-marker (make-marker)) + (beg-marker (make-marker))) + ;; Ensure blank line after and remove extra whitespace + (goto-char end) + (skip-syntax-backward "-") + (set-marker end-marker (point)) + (delete-horizontal-space) + (markdown-ensure-blank-line-after) + ;; Ensure blank line before and remove extra whitespace + (goto-char beg) + (skip-syntax-forward "-") + (delete-horizontal-space) + (markdown-ensure-blank-line-before) + (set-marker beg-marker (point)) + ;; Insert PREFIX before each line + (goto-char beg-marker) + (while (and (< (line-beginning-position) end-marker) + (not (eobp))) + (insert prefix) + (forward-line))))) + +(defun markdown-blockquote-region (beg end) + "Blockquote the region. +Arguments BEG and END specify the beginning and end of the region." + (interactive "*r") + (markdown-block-region + beg end (concat (markdown-blockquote-indentation + (max (point-min) (1- beg))) "> "))) + +(defun markdown-pre-indentation (loc) + "Return string containing necessary whitespace for a pre block at LOC. +Also see `markdown-blockquote-indentation'." + (save-excursion + (goto-char loc) + (let* ((list-level (length (markdown-calculate-list-levels))) + indent) + (dotimes (_ (1+ list-level) indent) + (setq indent (concat indent " ")))))) + +(defun markdown-insert-pre () + "Start a preformatted section (or apply to the region). +If Transient Mark mode is on and a region is active, it is marked +as preformatted text." + (interactive) + (if (markdown-use-region-p) + (markdown-pre-region (region-beginning) (region-end)) + (markdown-ensure-blank-line-before) + (insert (markdown-pre-indentation (point))) + (markdown-ensure-blank-line-after))) + +(defun markdown-pre-region (beg end) + "Format the region as preformatted text. +Arguments BEG and END specify the beginning and end of the region." + (interactive "*r") + (let ((indent (markdown-pre-indentation (max (point-min) (1- beg))))) + (markdown-block-region beg end indent))) + +(defun markdown-electric-backquote (arg) + "Insert a backquote. +The numeric prefix argument ARG says how many times to repeat the insertion. +Call `markdown-insert-gfm-code-block' interactively +if three backquotes inserted at the beginning of line." + (interactive "*P") + (self-insert-command (prefix-numeric-value arg)) + (when (and markdown-gfm-use-electric-backquote (looking-back "^```" nil)) + (replace-match "") + (call-interactively #'markdown-insert-gfm-code-block))) + +(defconst markdown-gfm-recognized-languages + ;; to reproduce/update, evaluate the let-form in + ;; scripts/get-recognized-gfm-languages.el. that produces a single long sexp, + ;; but with appropriate use of a keyboard macro, indenting and filling it + ;; properly is pretty fast. + '("ABAP" "AGS-Script" "AMPL" "ANTLR" "API-Blueprint" "APL" "ASP" "ATS" + "ActionScript" "Ada" "Agda" "Alloy" "Ant-Build-System" "ApacheConf" "Apex" + "AppleScript" "Arc" "Arduino" "AsciiDoc" "AspectJ" "Assembly" "Augeas" + "AutoHotkey" "AutoIt" "Awk" "Batchfile" "Befunge" "Bison" "BitBake" + "BlitzBasic" "BlitzMax" "Bluespec" "Boo" "Brainfuck" "Brightscript" "Bro" "C#" + "C++" "C-ObjDump" "C2hs-Haskell" "CLIPS" "CMake" "COBOL" "CSS" "Cap'n-Proto" + "CartoCSS" "Ceylon" "Chapel" "Charity" "ChucK" "Cirru" "Clarion" "Clean" + "Click" "Clojure" "CoffeeScript" "ColdFusion" "ColdFusion-CFC" "Common-Lisp" + "Component-Pascal" "Cool" "Coq" "Cpp-ObjDump" "Creole" "Crystal" "Cucumber" + "Cuda" "Cycript" "Cython" "D-ObjDump" "DIGITAL-Command-Language" "DM" + "DNS-Zone" "DTrace" "Darcs-Patch" "Dart" "Diff" "Dockerfile" "Dogescript" + "Dylan" "ECL" "ECLiPSe" "Eagle" "Ecere-Projects" "Eiffel" "Elixir" "Elm" + "Emacs-Lisp" "EmberScript" "Erlang" "F#" "FLUX" "FORTRAN" "Factor" "Fancy" + "Fantom" "Filterscript" "Formatted" "Forth" "FreeMarker" "Frege" "G-code" + "GAMS" "GAP" "GAS" "GDScript" "GLSL" "Game-Maker-Language" "Genshi" + "Gentoo-Ebuild" "Gentoo-Eclass" "Gettext-Catalog" "Glyph" "Gnuplot" "Go" + "Golo" "Gosu" "Grace" "Gradle" "Grammatical-Framework" + "Graph-Modeling-Language" "Graphviz-(DOT)" "Groff" "Groovy" + "Groovy-Server-Pages" "HCL" "HTML" "HTML+Django" "HTML+EEX" "HTML+ERB" + "HTML+PHP" "HTTP" "Hack" "Haml" "Handlebars" "Harbour" "Haskell" "Haxe" "Hy" + "HyPhy" "IDL" "IGOR-Pro" "INI" "IRC-log" "Idris" "Inform-7" "Inno-Setup" "Io" + "Ioke" "Isabelle" "Isabelle-ROOT" "JFlex" "JSON" "JSON5" "JSONLD" "JSONiq" + "JSX" "Jade" "Jasmin" "Java" "Java-Server-Pages" "JavaScript" "Julia" + "Jupyter-Notebook" "KRL" "KiCad" "Kit" "Kotlin" "LFE" "LLVM" "LOLCODE" "LSL" + "LabVIEW" "Lasso" "Latte" "Lean" "Less" "Lex" "LilyPond" "Limbo" + "Linker-Script" "Linux-Kernel-Module" "Liquid" "Literate-Agda" + "Literate-CoffeeScript" "Literate-Haskell" "LiveScript" "Logos" "Logtalk" + "LookML" "LoomScript" "Lua" "MAXScript" "MTML" "MUF" "Makefile" "Mako" + "Markdown" "Mask" "Mathematica" "Matlab" "Maven-POM" "Max" "MediaWiki" + "Mercury" "Metal" "MiniD" "Mirah" "Modelica" "Modula-2" + "Module-Management-System" "Monkey" "Moocode" "MoonScript" "Myghty" "NCL" "NL" + "NSIS" "Nemerle" "NetLinx" "NetLinx+ERB" "NetLogo" "NewLisp" "Nginx" "Nimrod" + "Ninja" "Nit" "Nix" "Nu" "NumPy" "OCaml" "ObjDump" "Objective-C" + "Objective-C++" "Objective-J" "Omgrofl" "Opa" "Opal" "OpenCL" "OpenEdge-ABL" + "OpenSCAD" "Org" "Ox" "Oxygene" "Oz" "PAWN" "PHP" "PLSQL" "PLpgSQL" "Pan" + "Papyrus" "Parrot" "Parrot-Assembly" "Parrot-Internal-Representation" "Pascal" + "Perl" "Perl6" "Pickle" "PicoLisp" "PigLatin" "Pike" "Pod" "PogoScript" "Pony" + "PostScript" "PowerShell" "Processing" "Prolog" "Propeller-Spin" + "Protocol-Buffer" "Public-Key" "Puppet" "Pure-Data" "PureBasic" "PureScript" + "Python" "Python-traceback" "QML" "QMake" "RAML" "RDoc" "REALbasic" "RHTML" + "RMarkdown" "Racket" "Ragel-in-Ruby-Host" "Raw-token-data" "Rebol" "Red" + "Redcode" "Ren'Py" "RenderScript" "RobotFramework" "Rouge" "Ruby" "Rust" "SAS" + "SCSS" "SMT" "SPARQL" "SQF" "SQL" "SQLPL" "STON" "SVG" "Sage" "SaltStack" + "Sass" "Scala" "Scaml" "Scheme" "Scilab" "Self" "Shell" "ShellSession" "Shen" + "Slash" "Slim" "Smali" "Smalltalk" "Smarty" "SourcePawn" "Squirrel" "Stan" + "Standard-ML" "Stata" "Stylus" "SuperCollider" "Swift" "SystemVerilog" "TOML" + "TXL" "Tcl" "Tcsh" "TeX" "Tea" "Text" "Textile" "Thrift" "Turing" "Turtle" + "Twig" "TypeScript" "Unified-Parallel-C" "Unity3D-Asset" "UnrealScript" + "UrWeb" "VCL" "VHDL" "Vala" "Verilog" "VimL" "Visual-Basic" "Volt" "Vue" + "Web-Ontology-Language" "WebIDL" "X10" "XC" "XML" "XPages" "XProc" "XQuery" + "XS" "XSLT" "Xojo" "Xtend" "YAML" "Yacc" "Zephir" "Zimpl" "desktop" "eC" "edn" + "fish" "mupad" "nesC" "ooc" "reStructuredText" "wisp" "xBase") + "Language specifiers recognized by GitHub's syntax highlighting features.") + +(defvar markdown-gfm-used-languages nil + "Language names used in GFM code blocks.") +(make-variable-buffer-local 'markdown-gfm-used-languages) + +(defun markdown-trim-whitespace (str) + (markdown-replace-regexp-in-string + "\\(?:[[:space:]\r\n]+\\'\\|\\`[[:space:]\r\n]+\\)" "" str)) + +(defun markdown-clean-language-string (str) + (markdown-replace-regexp-in-string + "{\\.?\\|}" "" (markdown-trim-whitespace str))) + +(defun markdown-validate-language-string (widget) + (let ((str (widget-value widget))) + (unless (string= str (markdown-clean-language-string str)) + (widget-put widget :error (format "Invalid language spec: '%s'" str)) + widget))) + +(defun markdown-gfm-get-corpus () + "Create corpus of recognized GFM code block languages for the given buffer." + (let ((given-corpus (append markdown-gfm-additional-languages + markdown-gfm-recognized-languages))) + (append + markdown-gfm-used-languages + (if markdown-gfm-downcase-languages (cl-mapcar #'downcase given-corpus) + given-corpus)))) + +(defun markdown-gfm-add-used-language (lang) + "Clean LANG and add to list of used languages." + (add-to-list 'markdown-gfm-used-languages + (markdown-clean-language-string lang))) + +(defun markdown-insert-gfm-code-block (&optional lang) + "Insert GFM code block for language LANG. +If LANG is nil, the language will be queried from user. If a +region is active, wrap this region with the markup instead. If +the region boundaries are not on empty lines, these are added +automatically in order to have the correct markup." + (interactive + (list (let ((completion-ignore-case nil)) + (condition-case nil + (markdown-clean-language-string + (completing-read + (format "Programming language [%s]: " + (or (car markdown-gfm-used-languages) "none")) + (markdown-gfm-get-corpus) + nil 'confirm nil + 'markdown-gfm-language-history)) + (quit ""))))) + (unless (string= lang "") (markdown-gfm-add-used-language lang)) + (when (> (length lang) 0) (setq lang (concat " " lang))) + (if (markdown-use-region-p) + (let ((b (region-beginning)) (e (region-end))) + (goto-char e) + ;; if we're on a blank line, don't newline, otherwise the ``` + ;; should go on its own line + (unless (looking-back "\n" nil) + (newline)) + (insert "```") + (markdown-ensure-blank-line-after) + (goto-char b) + ;; if we're on a blank line, insert the quotes here, otherwise + ;; add a new line first + (unless (looking-at-p "\n") + (newline) + (forward-line -1)) + (markdown-ensure-blank-line-before) + (insert "```" lang)) + (markdown-ensure-blank-line-before) + (insert "```" lang "\n\n```") + (markdown-ensure-blank-line-after) + (forward-line -1))) + +(defun markdown-gfm-parse-buffer-for-languages (&optional buffer) + (with-current-buffer (or buffer (current-buffer)) + (save-excursion + (goto-char (point-min)) + (cl-loop + with prop = 'markdown-gfm-block-begin + for pos-prop = (markdown-find-next-prop prop) + while pos-prop + for lang = (progn + (goto-char (car pos-prop)) + (save-match-data + (set-match-data (get-text-property (point) prop)) + (when (and (match-beginning 2) (match-end 2)) + (buffer-substring-no-properties + (match-beginning 2) (match-end 2))))) + do (progn (when lang (markdown-gfm-add-used-language lang)) + (goto-char (next-single-property-change (point) prop))))))) + + +;;; Footnotes ================================================================== + +(defun markdown-footnote-counter-inc () + "Increment `markdown-footnote-counter' and return the new value." + (when (= markdown-footnote-counter 0) ; hasn't been updated in this buffer yet. + (save-excursion + (goto-char (point-min)) + (while (re-search-forward (concat "^\\[\\^\\(" markdown-footnote-chars "*?\\)\\]:") + (point-max) t) + (let ((fn (string-to-number (match-string 1)))) + (when (> fn markdown-footnote-counter) + (setq markdown-footnote-counter fn)))))) + (cl-incf markdown-footnote-counter)) + +(defun markdown-insert-footnote () + "Insert footnote with a new number and move point to footnote definition." + (interactive) + (let ((fn (markdown-footnote-counter-inc))) + (insert (format "[^%d]" fn)) + (markdown-footnote-text-find-new-location) + (markdown-ensure-blank-line-before) + (unless (markdown-cur-line-blank-p) + (insert "\n")) + (insert (format "[^%d]: " fn)) + (markdown-ensure-blank-line-after))) + +(defun markdown-footnote-text-find-new-location () + "Position the cursor at the proper location for a new footnote text." + (cond + ((eq markdown-footnote-location 'end) (goto-char (point-max))) + ((eq markdown-footnote-location 'immediately) (markdown-end-of-block)) + ((eq markdown-footnote-location 'header) (markdown-end-of-defun)))) + +(defun markdown-footnote-kill () + "Kill the footnote at point. +The footnote text is killed (and added to the kill ring), the +footnote marker is deleted. Point has to be either at the +footnote marker or in the footnote text." + (interactive) + (let ((marker-pos nil) + (skip-deleting-marker nil) + (starting-footnote-text-positions + (markdown-footnote-text-positions))) + (when starting-footnote-text-positions + ;; We're starting in footnote text, so mark our return position and jump + ;; to the marker if possible. + (let ((marker-pos (markdown-footnote-find-marker + (cl-first starting-footnote-text-positions)))) + (if marker-pos + (goto-char (1- marker-pos)) + ;; If there isn't a marker, we still want to kill the text. + (setq skip-deleting-marker t)))) + ;; Either we didn't start in the text, or we started in the text and jumped + ;; to the marker. We want to assume we're at the marker now and error if + ;; we're not. + (unless skip-deleting-marker + (let ((marker (markdown-footnote-delete-marker))) + (unless marker + (error "Not at a footnote")) + ;; Even if we knew the text position before, it changed when we deleted + ;; the label. + (setq marker-pos (cl-second marker)) + (let ((new-text-pos (markdown-footnote-find-text (cl-first marker)))) + (unless new-text-pos + (error "No text for footnote `%s'" (cl-first marker))) + (goto-char new-text-pos)))) + (let ((pos (markdown-footnote-kill-text))) + (goto-char (if starting-footnote-text-positions + pos + marker-pos))))) + +(defun markdown-footnote-delete-marker () + "Delete a footnote marker at point. +Returns a list (ID START) containing the footnote ID and the +start position of the marker before deletion. If no footnote +marker was deleted, this function returns NIL." + (let ((marker (markdown-footnote-marker-positions))) + (when marker + (delete-region (cl-second marker) (cl-third marker)) + (butlast marker)))) + +(defun markdown-footnote-kill-text () + "Kill footnote text at point. +Returns the start position of the footnote text before deletion, +or NIL if point was not inside a footnote text. + +The killed text is placed in the kill ring (without the footnote +number)." + (let ((fn (markdown-footnote-text-positions))) + (when fn + (let ((text (delete-and-extract-region (cl-second fn) (cl-third fn)))) + (string-match (concat "\\[\\" (cl-first fn) "\\]:[[:space:]]*\\(\\(.*\n?\\)*\\)") text) + (kill-new (match-string 1 text)) + (when (and (markdown-cur-line-blank-p) + (markdown-prev-line-blank-p) + (not (bobp))) + (delete-region (1- (point)) (point))) + (cl-second fn))))) + +(defun markdown-footnote-goto-text () + "Jump to the text of the footnote at point." + (interactive) + (let ((fn (car (markdown-footnote-marker-positions)))) + (unless fn + (error "Not at a footnote marker")) + (let ((new-pos (markdown-footnote-find-text fn))) + (unless new-pos + (error "No definition found for footnote `%s'" fn)) + (goto-char new-pos)))) + +(defun markdown-footnote-return () + "Return from a footnote to its footnote number in the main text." + (interactive) + (let ((fn (save-excursion + (car (markdown-footnote-text-positions))))) + (unless fn + (error "Not in a footnote")) + (let ((new-pos (markdown-footnote-find-marker fn))) + (unless new-pos + (error "Footnote marker `%s' not found" fn)) + (goto-char new-pos)))) + +(defun markdown-footnote-find-marker (id) + "Find the location of the footnote marker with ID. +The actual buffer position returned is the position directly +following the marker's closing bracket. If no marker is found, +NIL is returned." + (save-excursion + (goto-char (point-min)) + (when (re-search-forward (concat "\\[" id "\\]\\([^:]\\|\\'\\)") nil t) + (skip-chars-backward "^]") + (point)))) + +(defun markdown-footnote-find-text (id) + "Find the location of the text of footnote ID. +The actual buffer position returned is the position of the first +character of the text, after the footnote's identifier. If no +footnote text is found, NIL is returned." + (save-excursion + (goto-char (point-min)) + (when (re-search-forward (concat "^\\[" id "\\]:") nil t) + (skip-chars-forward "[ \t]") + (point)))) + +(defun markdown-footnote-marker-positions () + "Return the position and ID of the footnote marker point is on. +The return value is a list (ID START END). If point is not on a +footnote, NIL is returned." + ;; first make sure we're at a footnote marker + (if (or (looking-back (concat "\\[\\^" markdown-footnote-chars "*\\]?") (line-beginning-position)) + (looking-at-p (concat "\\[?\\^" markdown-footnote-chars "*?\\]"))) + (save-excursion + ;; move point between [ and ^: + (if (looking-at-p "\\[") + (forward-char 1) + (skip-chars-backward "^[")) + (looking-at (concat "\\(\\^" markdown-footnote-chars "*?\\)\\]")) + (list (match-string 1) (1- (match-beginning 1)) (1+ (match-end 1)))))) + +(defun markdown-footnote-text-positions () + "Return the start and end positions of the footnote text point is in. +The exact return value is a list of three elements: (ID START END). +The start position is the position of the opening bracket +of the footnote id. The end position is directly after the +newline that ends the footnote. If point is not in a footnote, +NIL is returned instead." + (save-excursion + (let (result) + (move-beginning-of-line 1) + ;; Try to find the label. If we haven't found the label and we're at a blank + ;; or indented line, back up if possible. + (while (and + (not (and (looking-at markdown-regex-footnote-definition) + (setq result (list (match-string 1) (point))))) + (and (not (bobp)) + (or (markdown-cur-line-blank-p) + (>= (current-indentation) 4)))) + (forward-line -1)) + (when result + ; Advance if there is a next line that is either blank or indented. + ; (Need to check if we're on the last line, because + ; markdown-next-line-blank-p returns true for last line in buffer.) + (while (and (/= (line-end-position) (point-max)) + (or (markdown-next-line-blank-p) + (>= (markdown-next-line-indent) 4))) + (forward-line)) + ; Move back while the current line is blank. + (while (markdown-cur-line-blank-p) + (forward-line -1)) + ; Advance to capture this line and a single trailing newline (if there + ; is one). + (forward-line) + (append result (list (point))))))) + + +;;; Element Removal =========================================================== + +(defun markdown-kill-thing-at-point () + "Kill thing at point and add important text, without markup, to kill ring. +Possible things to kill include (roughly in order of precedence): +inline code, headers, horizonal rules, links (add link text to +kill ring), images (add alt text to kill ring), angle uri, email +addresses, bold, italics, reference definition (add URI to kill +ring), footnote markers and text (kill both marker and text, add +text to kill ring), and list items." + (interactive "*") + (let (val) + (cond + ;; Inline code + ((markdown-code-at-point-p) + (kill-new (match-string 2)) + (delete-region (match-beginning 0) (match-end 0))) + ;; ATX header + ((thing-at-point-looking-at markdown-regex-header-atx) + (kill-new (match-string 2)) + (delete-region (match-beginning 0) (match-end 0))) + ;; Setext header + ((thing-at-point-looking-at markdown-regex-header-setext) + (kill-new (match-string 1)) + (delete-region (match-beginning 0) (match-end 0))) + ;; Horizonal rule + ((thing-at-point-looking-at markdown-regex-hr) + (kill-new (match-string 0)) + (delete-region (match-beginning 0) (match-end 0))) + ;; Inline link or image (add link or alt text to kill ring) + ((thing-at-point-looking-at markdown-regex-link-inline) + (kill-new (match-string 3)) + (delete-region (match-beginning 0) (match-end 0))) + ;; Reference link or image (add link or alt text to kill ring) + ((thing-at-point-looking-at markdown-regex-link-reference) + (kill-new (match-string 3)) + (delete-region (match-beginning 0) (match-end 0))) + ;; Angle URI (add URL to kill ring) + ((thing-at-point-looking-at markdown-regex-angle-uri) + (kill-new (match-string 2)) + (delete-region (match-beginning 0) (match-end 0))) + ;; Email address in angle brackets (add email address to kill ring) + ((thing-at-point-looking-at markdown-regex-email) + (kill-new (match-string 1)) + (delete-region (match-beginning 0) (match-end 0))) + ;; Wiki link (add alias text to kill ring) + ((and markdown-enable-wiki-links + (thing-at-point-looking-at markdown-regex-wiki-link)) + (kill-new (markdown-wiki-link-alias)) + (delete-region (match-beginning 1) (match-end 1))) + ;; Bold + ((thing-at-point-looking-at markdown-regex-bold) + (kill-new (match-string 4)) + (delete-region (match-beginning 2) (match-end 2))) + ;; Italics + ((thing-at-point-looking-at markdown-regex-italic) + (kill-new (match-string 3)) + (delete-region (match-beginning 1) (match-end 1))) + ;; Strikethrough + ((thing-at-point-looking-at markdown-regex-strike-through) + (kill-new (match-string 4)) + (delete-region (match-beginning 2) (match-end 2))) + ;; Footnote marker (add footnote text to kill ring) + ((thing-at-point-looking-at markdown-regex-footnote) + (markdown-footnote-kill)) + ;; Footnote text (add footnote text to kill ring) + ((setq val (markdown-footnote-text-positions)) + (markdown-footnote-kill)) + ;; Reference definition (add URL to kill ring) + ((thing-at-point-looking-at markdown-regex-reference-definition) + (kill-new (match-string 5)) + (delete-region (match-beginning 0) (match-end 0))) + ;; List item + ((setq val (markdown-cur-list-item-bounds)) + (kill-new (delete-and-extract-region (cl-first val) (cl-second val)))) + (t + (error "Nothing found at point to kill"))))) + + +;;; Indentation ==================================================================== + +(defun markdown-indent-find-next-position (cur-pos positions) + "Return the position after the index of CUR-POS in POSITIONS. +Positions are calculated by `markdown-calc-indents'." + (while (and positions + (not (equal cur-pos (car positions)))) + (setq positions (cdr positions))) + (or (cadr positions) 0)) + +(defun markdown-exdent-find-next-position (cur-pos positions) + "Return the maximal element that precedes CUR-POS from POSITIONS. +Positions are calculated by `markdown-calc-indents'." + (let ((result 0)) + (dolist (i positions) + (when (< i cur-pos) + (setq result (max result i)))) + result)) + +(defun markdown-indent-line () + "Indent the current line using some heuristics. +If the _previous_ command was either `markdown-enter-key' or +`markdown-cycle', then we should cycle to the next +reasonable indentation position. Otherwise, we could have been +called directly by `markdown-enter-key', by an initial call of +`markdown-cycle', or indirectly by `auto-fill-mode'. In +these cases, indent to the default position. +Positions are calculated by `markdown-calc-indents'." + (interactive) + (let ((positions (markdown-calc-indents)) + (cursor-pos (current-column)) + (_ (back-to-indentation)) + (cur-pos (current-column))) + (if (not (equal this-command 'markdown-cycle)) + (indent-line-to (car positions)) + (setq positions (sort (delete-dups positions) '<)) + (let* ((next-pos (markdown-indent-find-next-position cur-pos positions)) + (new-cursor-pos + (if (< cur-pos next-pos) + (+ cursor-pos (- next-pos cur-pos)) + (- cursor-pos cur-pos)))) + (indent-line-to next-pos) + (move-to-column new-cursor-pos))))) + +(defun markdown-calc-indents () + "Return a list of indentation columns to cycle through. +The first element in the returned list should be considered the +default indentation level. This function does not worry about +duplicate positions, which are handled up by calling functions." + (let (pos prev-line-pos positions) + + ;; Indentation of previous line + (setq prev-line-pos (markdown-prev-line-indent)) + (setq positions (cons prev-line-pos positions)) + + ;; Indentation of previous non-list-marker text + (when (setq pos (markdown-prev-non-list-indent)) + (setq positions (cons pos positions))) + + ;; Indentation required for a pre block in current context + (setq pos (length (markdown-pre-indentation (point)))) + (setq positions (cons pos positions)) + + ;; Indentation of the previous line + tab-width + (if prev-line-pos + (setq positions (cons (+ prev-line-pos tab-width) positions)) + (setq positions (cons tab-width positions))) + + ;; Indentation of the previous line - tab-width + (if (and prev-line-pos (> prev-line-pos tab-width)) + (setq positions (cons (- prev-line-pos tab-width) positions))) + + ;; Indentation of all preceeding list markers (when in a list) + (when (setq pos (markdown-calculate-list-levels)) + (setq positions (append pos positions))) + + ;; First column + (setq positions (cons 0 positions)) + + ;; Return reversed list + (reverse positions))) + +(defun markdown-enter-key () + "Handle RET according to to the value of `markdown-indent-on-enter'." + (interactive) + (newline) + (when markdown-indent-on-enter + (markdown-indent-line))) + +(defun markdown-exdent-or-delete (arg) + "Handle BACKSPACE by cycling through indentation points. +When BACKSPACE is pressed, if there is only whitespace +before the current point, then exdent the line one level. +Otherwise, do normal delete by repeating +`backward-delete-char-untabify' ARG times." + (interactive "*p") + (let ((cur-pos (current-column)) + (start-of-indention (save-excursion + (back-to-indentation) + (current-column))) + (positions (markdown-calc-indents))) + (if (and (> cur-pos 0) (= cur-pos start-of-indention)) + (indent-line-to (markdown-exdent-find-next-position cur-pos positions)) + (backward-delete-char-untabify arg)))) + +(defun markdown-find-leftmost-column (beg end) + "Find the leftmost column in the region from BEG to END." + (let ((mincol 1000)) + (save-excursion + (goto-char beg) + (while (< (point) end) + (back-to-indentation) + (unless (looking-at-p "[ \t]*$") + (setq mincol (min mincol (current-column)))) + (forward-line 1) + )) + mincol)) + +(defun markdown-indent-region (beg end arg) + "Indent the region from BEG to END using some heuristics. +When ARG is non-nil, exdent the region instead. +See `markdown-indent-line' and `markdown-indent-line'." + (interactive "*r\nP") + (let* ((positions (sort (delete-dups (markdown-calc-indents)) '<)) + (leftmostcol (markdown-find-leftmost-column beg end)) + (next-pos (if arg + (markdown-exdent-find-next-position leftmostcol positions) + (markdown-indent-find-next-position leftmostcol positions)))) + (indent-rigidly beg end (- next-pos leftmostcol)) + (setq deactivate-mark nil))) + +(defun markdown-exdent-region (beg end) + "Call `markdown-indent-region' on region from BEG to END with prefix." + (interactive "*r") + (markdown-indent-region beg end t)) + + +;;; Markup Completion ========================================================= + +(defconst markdown-complete-alist + '((markdown-regex-header-atx . markdown-complete-atx) + (markdown-regex-header-setext . markdown-complete-setext) + (markdown-regex-hr . markdown-complete-hr)) + "Association list of form (regexp . function) for markup completion.") + +(defun markdown-incomplete-atx-p () + "Return t if ATX header markup is incomplete and nil otherwise. +Assumes match data is available for `markdown-regex-header-atx'. +Checks that the number of trailing hash marks equals the number of leading +hash marks, that there is only a single space before and after the text, +and that there is no extraneous whitespace in the text." + (or + ;; Number of starting and ending hash marks differs + (not (= (length (match-string 1)) (length (match-string 3)))) + ;; When the header text is not empty... + (and (> (length (match-string 2)) 0) + ;; ...if there are extra leading, trailing, or interior spaces + (or (not (= (match-beginning 2) (1+ (match-end 1)))) + (not (= (match-beginning 3) (1+ (match-end 2)))) + (string-match-p "[ \t\n]\\{2\\}" (match-string 2)))) + ;; When the header text is empty... + (and (= (length (match-string 2)) 0) + ;; ...if there are too many or too few spaces + (not (= (match-beginning 3) (+ (match-end 1) 2)))))) + +(defun markdown-complete-atx () + "Complete and normalize ATX headers. +Add or remove hash marks to the end of the header to match the +beginning. Ensure that there is only a single space between hash +marks and header text. Removes extraneous whitespace from header text. +Assumes match data is available for `markdown-regex-header-atx'. +Return nil if markup was complete and non-nil if markup was completed." + (when (markdown-incomplete-atx-p) + (let* ((new-marker (make-marker)) + (new-marker (set-marker new-marker (match-end 2)))) + ;; Hash marks and spacing at end + (goto-char (match-end 2)) + (delete-region (match-end 2) (match-end 3)) + (insert " " (match-string 1)) + ;; Remove extraneous whitespace from title + (replace-match (markdown-compress-whitespace-string (match-string 2)) + t t nil 2) + ;; Spacing at beginning + (goto-char (match-end 1)) + (delete-region (match-end 1) (match-beginning 2)) + (insert " ") + ;; Leave point at end of text + (goto-char new-marker)))) + +(defun markdown-incomplete-setext-p () + "Return t if setext header markup is incomplete and nil otherwise. +Assumes match data is available for `markdown-regex-header-setext'. +Checks that length of underline matches text and that there is no +extraneous whitespace in the text." + (or (not (= (length (match-string 1)) (length (match-string 2)))) + (string-match-p "[ \t\n]\\{2\\}" (match-string 1)))) + +(defun markdown-complete-setext () + "Complete and normalize setext headers. +Add or remove underline characters to match length of header +text. Removes extraneous whitespace from header text. Assumes +match data is available for `markdown-regex-header-setext'. +Return nil if markup was complete and non-nil if markup was completed." + (when (markdown-incomplete-setext-p) + (let* ((text (markdown-compress-whitespace-string (match-string 1))) + (char (char-after (match-beginning 2))) + (level (if (char-equal char ?-) 2 1))) + (goto-char (match-beginning 0)) + (delete-region (match-beginning 0) (match-end 0)) + (markdown-insert-header level text t) + t))) + +(defun markdown-incomplete-hr-p () + "Return non-nil if hr is not in `markdown-hr-strings' and nil otherwise. +Assumes match data is available for `markdown-regex-hr'." + (not (member (match-string 0) markdown-hr-strings))) + +(defun markdown-complete-hr () + "Complete horizontal rules. +If horizontal rule string is a member of `markdown-hr-strings', +do nothing. Otherwise, replace with the car of +`markdown-hr-strings'. +Assumes match data is available for `markdown-regex-hr'. +Return nil if markup was complete and non-nil if markup was completed." + (when (markdown-incomplete-hr-p) + (replace-match (car markdown-hr-strings)) + t)) + +(defun markdown-complete () + "Complete markup of object near point or in region when active. +Handle all objects in `markdown-complete-alist', in order. +See `markdown-complete-at-point' and `markdown-complete-region'." + (interactive "*") + (if (markdown-use-region-p) + (markdown-complete-region (region-beginning) (region-end)) + (markdown-complete-at-point))) + +(defun markdown-complete-at-point () + "Complete markup of object near point. +Handle all elements of `markdown-complete-alist' in order." + (interactive "*") + (let ((list markdown-complete-alist) found changed) + (while list + (let ((regexp (eval (caar list))) + (function (cdar list))) + (setq list (cdr list)) + (when (thing-at-point-looking-at regexp) + (setq found t) + (setq changed (funcall function)) + (setq list nil)))) + (if found + (or changed (error "Markup at point is complete")) + (error "Nothing to complete at point")))) + +(defun markdown-complete-region (beg end) + "Complete markup of objects in region from BEG to END. +Handle all objects in `markdown-complete-alist', in order. Each +match is checked to ensure that a previous regexp does not also +match." + (interactive "*r") + (let ((end-marker (set-marker (make-marker) end)) + previous) + (dolist (element markdown-complete-alist) + (let ((regexp (eval (car element))) + (function (cdr element))) + (goto-char beg) + (while (re-search-forward regexp end-marker 'limit) + (when (match-string 0) + ;; Make sure this is not a match for any of the preceding regexps. + ;; This prevents mistaking an HR for a Setext subheading. + (let (match) + (save-match-data + (dolist (prev-regexp previous) + (or match (setq match (looking-back prev-regexp nil))))) + (unless match + (save-excursion (funcall function)))))) + (cl-pushnew regexp previous :test #'equal))) + previous)) + +(defun markdown-complete-buffer () + "Complete markup for all objects in the current buffer." + (interactive "*") + (markdown-complete-region (point-min) (point-max))) + + +;;; Markup Cycling ============================================================ + +(defun markdown-cycle-atx (arg &optional remove) + "Cycle ATX header markup. +Promote header (decrease level) when ARG is 1 and demote +header (increase level) if arg is -1. When REMOVE is non-nil, +remove the header when the level reaches zero and stop cycling +when it reaches six. Otherwise, perform a proper cycling through +levels one through six. Assumes match data is available for +`markdown-regex-header-atx'." + (let* ((old-level (length (match-string 1))) + (new-level (+ old-level arg)) + (text (match-string 2))) + (when (not remove) + (setq new-level (% new-level 6)) + (setq new-level (cond ((= new-level 0) 6) + ((< new-level 0) (+ new-level 6)) + (t new-level)))) + (cond + ((= new-level 0) + (markdown-unwrap-thing-at-point nil 0 2)) + ((<= new-level 6) + (goto-char (match-beginning 0)) + (delete-region (match-beginning 0) (match-end 0)) + (markdown-insert-header new-level text nil))))) + +(defun markdown-cycle-setext (arg &optional remove) + "Cycle setext header markup. +Promote header (increase level) when ARG is 1 and demote +header (decrease level or remove) if arg is -1. When demoting a +level-two setext header, replace with a level-three atx header. +When REMOVE is non-nil, remove the header when the level reaches +zero. Otherwise, cycle back to a level six atx header. Assumes +match data is available for `markdown-regex-header-setext'." + (let* ((char (char-after (match-beginning 2))) + (old-level (if (char-equal char ?=) 1 2)) + (new-level (+ old-level arg))) + (when (and (not remove) (= new-level 0)) + (setq new-level 6)) + (cond + ((= new-level 0) + (markdown-unwrap-thing-at-point nil 0 1)) + ((<= new-level 2) + (markdown-insert-header new-level nil t)) + ((<= new-level 6) + (markdown-insert-header new-level nil nil))))) + +(defun markdown-cycle-hr (arg &optional remove) + "Cycle string used for horizontal rule from `markdown-hr-strings'. +When ARG is 1, cycle forward (demote), and when ARG is -1, cycle +backwards (promote). When REMOVE is non-nil, remove the hr instead +of cycling when the end of the list is reached. +Assumes match data is available for `markdown-regex-hr'." + (let* ((strings (if (= arg -1) + (reverse markdown-hr-strings) + markdown-hr-strings)) + (tail (member (match-string 0) strings)) + (new (or (cadr tail) + (if remove + (if (= arg 1) + "" + (car tail)) + (car strings))))) + (replace-match new))) + +(defun markdown-cycle-bold () + "Cycle bold markup between underscores and asterisks. +Assumes match data is available for `markdown-regex-bold'." + (save-excursion + (let* ((old-delim (match-string 3)) + (new-delim (if (string-equal old-delim "**") "__" "**"))) + (replace-match new-delim t t nil 3) + (replace-match new-delim t t nil 5)))) + +(defun markdown-cycle-italic () + "Cycle italic markup between underscores and asterisks. +Assumes match data is available for `markdown-regex-italic'." + (save-excursion + (let* ((old-delim (match-string 2)) + (new-delim (if (string-equal old-delim "*") "_" "*"))) + (replace-match new-delim t t nil 2) + (replace-match new-delim t t nil 4)))) + + +;;; Keymap ==================================================================== + +(defvar markdown-mode-map + (let ((map (make-keymap))) + ;; Element insertion + (define-key map "\C-c\C-al" 'markdown-insert-link) + (define-key map "\C-c\C-aL" 'markdown-insert-reference-link-dwim) + (define-key map "\C-c\C-au" 'markdown-insert-uri) + (define-key map "\C-c\C-af" 'markdown-insert-footnote) + (define-key map "\C-c\C-aw" 'markdown-insert-wiki-link) + (define-key map "\C-c\C-ii" 'markdown-insert-image) + (define-key map "\C-c\C-iI" 'markdown-insert-reference-image) + (define-key map "\C-c\C-i\C-t" 'markdown-toggle-inline-images) + (define-key map "\C-c\C-th" 'markdown-insert-header-dwim) + (define-key map "\C-c\C-tH" 'markdown-insert-header-setext-dwim) + (define-key map "\C-c\C-t1" 'markdown-insert-header-atx-1) + (define-key map "\C-c\C-t2" 'markdown-insert-header-atx-2) + (define-key map "\C-c\C-t3" 'markdown-insert-header-atx-3) + (define-key map "\C-c\C-t4" 'markdown-insert-header-atx-4) + (define-key map "\C-c\C-t5" 'markdown-insert-header-atx-5) + (define-key map "\C-c\C-t6" 'markdown-insert-header-atx-6) + (define-key map "\C-c\C-t!" 'markdown-insert-header-setext-1) + (define-key map "\C-c\C-t@" 'markdown-insert-header-setext-2) + (define-key map "\C-c\C-ss" 'markdown-insert-bold) + (define-key map "\C-c\C-se" 'markdown-insert-italic) + (define-key map "\C-c\C-sc" 'markdown-insert-code) + (define-key map "\C-c\C-sb" 'markdown-insert-blockquote) + (define-key map "\C-c\C-sk" 'markdown-insert-kbd) + (define-key map "\C-c\C-s\C-b" 'markdown-blockquote-region) + (define-key map "\C-c\C-sp" 'markdown-insert-pre) + (define-key map "\C-c\C-s\C-p" 'markdown-pre-region) + (define-key map "\C-c\C-sP" 'markdown-insert-gfm-code-block) + (define-key map "\C-c-" 'markdown-insert-hr) + ;; Element insertion (deprecated) + (define-key map "\C-c\C-ar" 'markdown-insert-reference-link-dwim) + (define-key map "\C-c\C-tt" 'markdown-insert-header-setext-1) + (define-key map "\C-c\C-ts" 'markdown-insert-header-setext-2) + ;; Element removal + (define-key map (kbd "C-c C-k") 'markdown-kill-thing-at-point) + ;; Promotion, Demotion, Completion, and Cycling + (define-key map (kbd "C-c C--") 'markdown-promote) + (define-key map (kbd "C-c C-=") 'markdown-demote) + (define-key map (kbd "C-c C-]") 'markdown-complete) + ;; Following and Jumping + (define-key map (kbd "C-c C-o") 'markdown-follow-thing-at-point) + (define-key map (kbd "C-c C-l") 'markdown-jump) + ;; Indentation + (define-key map (kbd "C-m") 'markdown-enter-key) + (define-key map (kbd "DEL") 'markdown-exdent-or-delete) + (define-key map (kbd "C-c >") 'markdown-indent-region) + (define-key map (kbd "C-c <") 'markdown-exdent-region) + ;; Visibility cycling + (define-key map (kbd "TAB") 'markdown-cycle) + (define-key map (kbd "") 'markdown-shifttab) + (define-key map (kbd "") 'markdown-shifttab) + (define-key map (kbd "") 'markdown-shifttab) + ;; Header navigation + (define-key map (kbd "C-c C-n") 'markdown-next-visible-heading) + (define-key map (kbd "C-c C-p") 'markdown-previous-visible-heading) + (define-key map (kbd "C-c C-f") 'markdown-forward-same-level) + (define-key map (kbd "C-c C-b") 'markdown-backward-same-level) + (define-key map (kbd "C-c C-u") 'markdown-up-heading) + ;; Buffer-wide commands + (define-key map (kbd "C-c C-c m") 'markdown-other-window) + (define-key map (kbd "C-c C-c p") 'markdown-preview) + (define-key map (kbd "C-c C-c e") 'markdown-export) + (define-key map (kbd "C-c C-c v") 'markdown-export-and-preview) + (define-key map (kbd "C-c C-c o") 'markdown-open) + (define-key map (kbd "C-c C-c l") 'markdown-live-preview-mode) + (define-key map (kbd "C-c C-c w") 'markdown-kill-ring-save) + (define-key map (kbd "C-c C-c c") 'markdown-check-refs) + (define-key map (kbd "C-c C-c n") 'markdown-cleanup-list-numbers) + (define-key map (kbd "C-c C-c ]") 'markdown-complete-buffer) + ;; List editing + (define-key map (kbd "M-") 'markdown-move-up) + (define-key map (kbd "M-") 'markdown-move-down) + (define-key map (kbd "M-") 'markdown-promote) + (define-key map (kbd "M-") 'markdown-demote) + (define-key map (kbd "M-") 'markdown-insert-list-item) + (define-key map (kbd "C-c C-j") 'markdown-insert-list-item) + ;; Subtree editing + (define-key map (kbd "M-S-") 'markdown-move-subtree-up) + (define-key map (kbd "M-S-") 'markdown-move-subtree-down) + (define-key map (kbd "M-S-") 'markdown-promote-subtree) + (define-key map (kbd "M-S-") 'markdown-demote-subtree) + ;; Movement + (define-key map (kbd "M-{") 'markdown-backward-paragraph) + (define-key map (kbd "M-}") 'markdown-forward-paragraph) + (define-key map (kbd "M-n") 'markdown-next-link) + (define-key map (kbd "M-p") 'markdown-previous-link) + ;; Alternative keys (in case of problems with the arrow keys) + (define-key map (kbd "C-c C-x u") 'markdown-move-up) + (define-key map (kbd "C-c C-x d") 'markdown-move-down) + (define-key map (kbd "C-c C-x l") 'markdown-promote) + (define-key map (kbd "C-c C-x r") 'markdown-demote) + (define-key map (kbd "C-c C-x m") 'markdown-insert-list-item) + map) + "Keymap for Markdown major mode.") + +(defvar gfm-mode-map + (let ((map (make-sparse-keymap))) + (set-keymap-parent map markdown-mode-map) + (define-key map (kbd "C-c C-s d") 'markdown-insert-strike-through) + (define-key map "`" 'markdown-electric-backquote) + map) + "Keymap for `gfm-mode'. +See also `markdown-mode-map'.") + + +;;; Menu ================================================================== + +(easy-menu-define markdown-mode-menu markdown-mode-map + "Menu for Markdown mode" + '("Markdown" + ("Show/Hide" + ["Cycle visibility" markdown-cycle (markdown-on-heading-p)] + ["Cycle global visibility" markdown-shifttab]) + "---" + ["Compile" markdown-other-window] + ["Preview" markdown-preview] + ["Export" markdown-export] + ["Export & View" markdown-export-and-preview] + ["Open" markdown-open] + ["Live Export" markdown-live-preview-mode + :style toggle :selected markdown-live-preview-mode] + ["Kill ring save" markdown-kill-ring-save] + "---" + ("Headings" + ["Automatic" markdown-insert-header-dwim] + ["Automatic (prefer setext)" markdown-insert-header-setext-dwim] + "---" + ["First level setext" markdown-insert-header-setext-1] + ["Second level setext" markdown-insert-header-setext-2] + "---" + ["First level atx" markdown-insert-header-atx-1] + ["Second level atx" markdown-insert-header-atx-2] + ["Third level atx" markdown-insert-header-atx-3] + ["Fourth level atx" markdown-insert-header-atx-4] + ["Fifth level atx" markdown-insert-header-atx-5] + ["Sixth level atx" markdown-insert-header-atx-6]) + "---" + ["Bold" markdown-insert-bold] + ["Italic" markdown-insert-italic] + ["Strikethrough" markdown-insert-strike-through] + ["Blockquote" markdown-insert-blockquote] + ["Preformatted" markdown-insert-pre] + ["Code" markdown-insert-code] + "---" + ["Insert inline link" markdown-insert-link] + ["Insert reference link" markdown-insert-reference-link-dwim] + ["Insert URL" markdown-insert-uri] + ["Insert inline image" markdown-insert-image] + ["Insert reference image" markdown-insert-reference-image] + ["Insert list item" markdown-insert-list-item] + ["Insert horizontal rule" markdown-insert-hr] + ["Insert footnote" markdown-insert-footnote] + ["Kill element" markdown-kill-thing-at-point] + "---" + ["Jump" markdown-jump] + ["Follow link" markdown-follow-thing-at-point] + ("Outline" + ["Next visible heading" markdown-next-visible-heading] + ["Previous visible heading" markdown-previous-visible-heading] + ["Forward same level" markdown-forward-same-level] + ["Backward same level" markdown-backward-same-level] + ["Up to parent heading" markdown-up-heading]) + "---" + ("Completion and Cycling" + ["Complete" markdown-complete] + ["Promote" markdown-promote] + ["Demote" markdown-demote]) + ("List editing" + ["Indent list item" markdown-demote] + ["Exdent list item" markdown-promote]) + ("Region shifting" + ["Indent region" markdown-indent-region] + ["Exdent region" markdown-exdent-region]) + "---" + ["Toggle inline images" markdown-toggle-inline-images] + ["Check references" markdown-check-refs] + ["Clean up list numbering" markdown-cleanup-list-numbers] + ["Complete markup" markdown-complete-buffer] + "---" + ["Version" markdown-show-version] + )) + + +;;; imenu ===================================================================== + +(defun markdown-imenu-create-nested-index () + "Create and return a nested imenu index alist for the current buffer. +See `imenu-create-index-function' and `imenu--index-alist' for details." + (let* ((root '(nil . nil)) + cur-alist + (cur-level 0) + (empty-heading "-") + (self-heading ".") + hashes pos level heading) + (save-excursion + (goto-char (point-min)) + (while (re-search-forward markdown-regex-header (point-max) t) + (unless (markdown-code-block-at-point) + (cond + ((match-string-no-properties 2) ;; level 1 setext + (setq heading (match-string-no-properties 1)) + (setq pos (match-beginning 1) + level 1)) + ((match-string-no-properties 3) ;; level 2 setext + (setq heading (match-string-no-properties 1)) + (setq pos (match-beginning 1) + level 2)) + ((setq hashes (match-string-no-properties 4)) + (setq heading (match-string-no-properties 5) + pos (match-beginning 4) + level (length hashes)))) + (let ((alist (list (cons heading pos)))) + (cond + ((= cur-level level) ; new sibling + (setcdr cur-alist alist) + (setq cur-alist alist)) + ((< cur-level level) ; first child + (dotimes (_ (- level cur-level 1)) + (setq alist (list (cons empty-heading alist)))) + (if cur-alist + (let* ((parent (car cur-alist)) + (self-pos (cdr parent))) + (setcdr parent (cons (cons self-heading self-pos) alist))) + (setcdr root alist)) ; primogenitor + (setq cur-alist alist) + (setq cur-level level)) + (t ; new sibling of an ancestor + (let ((sibling-alist (last (cdr root)))) + (dotimes (_ (1- level)) + (setq sibling-alist (last (cdar sibling-alist)))) + (setcdr sibling-alist alist) + (setq cur-alist alist)) + (setq cur-level level)))))) + (cdr root)))) + +(defun markdown-imenu-create-flat-index () + "Create and return a flat imenu index alist for the current buffer. +See `imenu-create-index-function' and `imenu--index-alist' for details." + (let* ((empty-heading "-") index heading pos) + (save-excursion + (goto-char (point-min)) + (while (re-search-forward markdown-regex-header (point-max) t) + (when (and (not (markdown-code-block-at-point)) + (not (markdown-text-property-at-point 'markdown-yaml-metadata-begin))) + (cond + ((setq heading (match-string-no-properties 1)) + (setq pos (match-beginning 1))) + ((setq heading (match-string-no-properties 5)) + (setq pos (match-beginning 4)))) + (or (> (length heading) 0) + (setq heading empty-heading)) + (setq index (append index (list (cons heading pos)))))) + index))) + + +;;; References ================================================================ + +(defun markdown-reference-goto-definition () + "Jump to the definition of the reference at point or create it." + (interactive) + (when (thing-at-point-looking-at markdown-regex-link-reference) + (let* ((text (match-string-no-properties 3)) + (reference (match-string-no-properties 6)) + (target (downcase (if (string= reference "") text reference))) + (loc (cadr (markdown-reference-definition target)))) + (if loc + (goto-char loc) + (goto-char (match-beginning 0)) + (markdown-insert-reference-definition target))))) + +(defun markdown-reference-find-links (reference) + "Return a list of all links for REFERENCE. +REFERENCE should not include the surrounding square brackets. +Elements of the list have the form (text start line), where +text is the link text, start is the location at the beginning of +the link, and line is the line number on which the link appears." + (let* ((ref-quote (regexp-quote reference)) + (regexp (format "!?\\(?:\\[\\(%s\\)\\][ ]?\\[\\]\\|\\[\\([^]]+?\\)\\][ ]?\\[%s\\]\\)" + ref-quote ref-quote)) + links) + (save-excursion + (goto-char (point-min)) + (while (re-search-forward regexp nil t) + (let* ((text (or (match-string-no-properties 1) + (match-string-no-properties 2))) + (start (match-beginning 0)) + (line (markdown-line-number-at-pos))) + (cl-pushnew (list text start line) links :test #'equal)))) + links)) + +(defun markdown-get-undefined-refs () + "Return a list of undefined Markdown references. +Result is an alist of pairs (reference . occurrences), where +occurrences is itself another alist of pairs (label . line-number). +For example, an alist corresponding to [Nice editor][Emacs] at line 12, +\[GNU Emacs][Emacs] at line 45 and [manual][elisp] at line 127 is +\((\"emacs\" (\"Nice editor\" . 12) (\"GNU Emacs\" . 45)) (\"elisp\" (\"manual\" . 127)))." + (let ((missing)) + (save-excursion + (goto-char (point-min)) + (while + (re-search-forward markdown-regex-link-reference nil t) + (let* ((text (match-string-no-properties 3)) + (reference (match-string-no-properties 6)) + (target (downcase (if (string= reference "") text reference)))) + (unless (markdown-reference-definition target) + (let ((entry (assoc target missing))) + (if (not entry) + (cl-pushnew + (cons target (list (cons text (markdown-line-number-at-pos)))) + missing :test #'equal) + (setcdr entry + (append (cdr entry) (list (cons text (markdown-line-number-at-pos)))))))))) + (reverse missing)))) + +(defconst markdown-reference-check-buffer + "*Undefined references for %buffer%*" + "Pattern for name of buffer for listing undefined references. +The string %buffer% will be replaced by the corresponding +`markdown-mode' buffer name.") + +(defun markdown-reference-check-buffer (&optional buffer-name) + "Name and return buffer for reference checking. +BUFFER-NAME is the name of the main buffer being visited." + (or buffer-name (setq buffer-name (buffer-name))) + (let ((refbuf (get-buffer-create (markdown-replace-regexp-in-string + "%buffer%" buffer-name + markdown-reference-check-buffer)))) + (with-current-buffer refbuf + (when view-mode + (View-exit-and-edit)) + (use-local-map button-buffer-map) + (erase-buffer)) + refbuf)) + +(defconst markdown-reference-links-buffer + "*Reference links for %buffer%*" + "Pattern for name of buffer for listing references. +The string %buffer% will be replaced by the corresponding buffer name.") + +(defun markdown-reference-links-buffer (&optional buffer-name) + "Name, setup, and return a buffer for listing links. +BUFFER-NAME is the name of the main buffer being visited." + (or buffer-name (setq buffer-name (buffer-name))) + (let ((linkbuf (get-buffer-create (markdown-replace-regexp-in-string + "%buffer%" buffer-name + markdown-reference-links-buffer)))) + (with-current-buffer linkbuf + (when view-mode + (View-exit-and-edit)) + (use-local-map button-buffer-map) + (erase-buffer)) + linkbuf)) + +(when (markdown-use-buttons-p) + ;; Add an empty Markdown reference definition to buffer + ;; specified in the 'target-buffer property. The reference name is + ;; the button's label. + (define-button-type 'markdown-undefined-reference-button + 'help-echo "mouse-1, RET: create definition for undefined reference" + 'follow-link t + 'face 'bold + 'action (lambda (b) + (let ((buffer (button-get b 'target-buffer)) + (line (button-get b 'target-line)) + (label (button-label b))) + (switch-to-buffer-other-window buffer) + (goto-char (point-min)) + (forward-line line) + (markdown-insert-reference-definition label) + (markdown-check-refs t)))) + + ;; Jump to line in buffer specified by 'target-buffer property. + ;; Line number is button's 'line property. + (define-button-type 'markdown-goto-line-button + 'help-echo "mouse-1, RET: go to line" + 'follow-link t + 'face 'italic + 'action (lambda (b) + (message (button-get b 'buffer)) + (switch-to-buffer-other-window (button-get b 'target-buffer)) + ;; use call-interactively to silence compiler + (let ((current-prefix-arg (button-get b 'target-line))) + (call-interactively 'goto-line)))) + + ;; Jumps to a particular link at location given by 'target-char + ;; property in buffer given by 'target-buffer property. + (define-button-type 'markdown-link-button + 'help-echo "mouse-1, RET: jump to location of link" + 'follow-link t + 'face 'bold + 'action (lambda (b) + (let ((target (button-get b 'target-buffer)) + (loc (button-get b 'target-char))) + (kill-buffer-and-window) + (switch-to-buffer target) + (goto-char loc))))) + +(defun markdown-insert-undefined-reference-button (reference oldbuf) + "Insert a button for creating REFERENCE in buffer OLDBUF. +REFERENCE should be a list of the form (reference . occurrences), +as by `markdown-get-undefined-refs'." + (let ((label (car reference))) + (if (markdown-use-buttons-p) + ;; Create a reference button in Emacs 22 + (insert-button label + :type 'markdown-undefined-reference-button + 'target-buffer oldbuf + 'target-line (cdr (car (cdr reference)))) + ;; Insert reference as text in Emacs < 22 + (insert label)) + (insert " (") + (dolist (occurrence (cdr reference)) + (let ((line (cdr occurrence))) + (if (markdown-use-buttons-p) + ;; Create a line number button in Emacs 22 + (insert-button (number-to-string line) + :type 'markdown-goto-line-button + 'target-buffer oldbuf + 'target-line line) + ;; Insert line number as text in Emacs < 22 + (insert (number-to-string line))) + (insert " "))) + (delete-char -1) + (insert ")") + (newline))) + +(defun markdown-insert-link-button (link oldbuf) + "Insert a button for jumping to LINK in buffer OLDBUF. +LINK should be a list of the form (text char line) containing +the link text, location, and line number." + (let ((label (cl-first link)) + (char (cl-second link)) + (line (cl-third link))) + (if (markdown-use-buttons-p) + ;; Create a reference button in Emacs 22 + (insert-button label + :type 'markdown-link-button + 'target-buffer oldbuf + 'target-char char) + ;; Insert reference as text in Emacs < 22 + (insert label)) + (insert (format " (line %d)\n" line)))) + +(defun markdown-reference-goto-link (&optional reference) + "Jump to the location of the first use of REFERENCE." + (interactive) + (unless reference + (if (thing-at-point-looking-at markdown-regex-reference-definition) + (setq reference (match-string-no-properties 2)) + (error "No reference definition at point"))) + (let ((links (markdown-reference-find-links reference))) + (cond ((= (length links) 1) + (goto-char (cadr (car links)))) + ((> (length links) 1) + (let ((oldbuf (current-buffer)) + (linkbuf (markdown-reference-links-buffer))) + (with-current-buffer linkbuf + (insert "Links using reference " reference ":\n\n") + (dolist (link (reverse links)) + (markdown-insert-link-button link oldbuf))) + (view-buffer-other-window linkbuf) + (goto-char (point-min)) + (forward-line 2))) + (t + (error "No links for reference %s" reference))))) + +(defun markdown-check-refs (&optional silent) + "Show all undefined Markdown references in current `markdown-mode' buffer. +If SILENT is non-nil, do not message anything when no undefined +references found. +Links which have empty reference definitions are considered to be +defined." + (interactive "P") + (when (not (eq major-mode 'markdown-mode)) + (error "Not available in current mode")) + (let ((oldbuf (current-buffer)) + (refs (markdown-get-undefined-refs)) + (refbuf (markdown-reference-check-buffer))) + (if (null refs) + (progn + (when (not silent) + (message "No undefined references found")) + (kill-buffer refbuf)) + (with-current-buffer refbuf + (insert "The following references are undefined:\n\n") + (dolist (ref refs) + (markdown-insert-undefined-reference-button ref oldbuf)) + (view-buffer-other-window refbuf) + (goto-char (point-min)) + (forward-line 2))))) + + +;;; Lists ===================================================================== + +(defun markdown-insert-list-item (&optional arg) + "Insert a new list item. +If the point is inside unordered list, insert a bullet mark. If +the point is inside ordered list, insert the next number followed +by a period. Use the previous list item to determine the amount +of whitespace to place before and after list markers. + +With a \\[universal-argument] prefix (i.e., when ARG is (4)), +decrease the indentation by one level. + +With two \\[universal-argument] prefixes (i.e., when ARG is (16)), +increase the indentation by one level." + (interactive "p") + (let (bounds cur-indent marker indent new-indent new-loc) + (save-match-data + ;; Look for a list item on current or previous non-blank line + (save-excursion + (while (and (not (setq bounds (markdown-cur-list-item-bounds))) + (not (bobp)) + (markdown-cur-line-blank-p)) + (forward-line -1))) + (when bounds + (cond ((save-excursion + (skip-chars-backward " \t") + (looking-at-p markdown-regex-list)) + (beginning-of-line) + (insert "\n") + (forward-line -1)) + ((not (markdown-cur-line-blank-p)) + (newline))) + (setq new-loc (point))) + ;; Look ahead for a list item on next non-blank line + (unless bounds + (save-excursion + (while (and (null bounds) + (not (eobp)) + (markdown-cur-line-blank-p)) + (forward-line) + (setq bounds (markdown-cur-list-item-bounds)))) + (when bounds + (setq new-loc (point)) + (unless (markdown-cur-line-blank-p) + (newline)))) + (if (not bounds) + ;; When not in a list, start a new unordered one + (progn + (unless (markdown-cur-line-blank-p) + (insert "\n")) + (insert markdown-unordered-list-item-prefix)) + ;; Compute indentation and marker for new list item + (setq cur-indent (nth 2 bounds)) + (setq marker (nth 4 bounds)) + (cond + ;; Dedent: decrement indentation, find previous marker. + ((= arg 4) + (setq indent (max (- cur-indent 4) 0)) + (let ((prev-bounds + (save-excursion + (when (markdown-prev-list-item (- (nth 3 bounds) 1)) + (markdown-cur-list-item-bounds))))) + (when prev-bounds + (setq marker (nth 4 prev-bounds))))) + ;; Indent: increment indentation by 4, use same marker. + ((= arg 16) (setq indent (+ cur-indent 4))) + ;; Same level: keep current indentation and marker. + (t (setq indent cur-indent))) + (setq new-indent (make-string indent 32)) + (goto-char new-loc) + (cond + ;; Ordered list + ((string-match-p "[0-9]" marker) + (if (= arg 16) ;; starting a new column indented one more level + (insert (concat new-indent "1. ")) + ;; Don't use previous match-data + (set-match-data nil) + ;; travel up to the last item and pick the correct number. If + ;; the argument was nil, "new-indent = cur-indent" is the same, + ;; so we don't need special treatment. Neat. + (save-excursion + (while (and (not (looking-at (concat new-indent "\\([0-9]+\\)\\(\\.[ \t]*\\)"))) + (>= (forward-line -1) 0)))) + (let* ((old-prefix (match-string 1)) + (old-spacing (match-string 2)) + (new-prefix (if old-prefix + (int-to-string (1+ (string-to-number old-prefix))) + "1")) + (space-adjust (- (length old-prefix) (length new-prefix))) + (new-spacing (if (and (match-string 2) + (not (string-match-p "\t" old-spacing)) + (< space-adjust 0) + (> space-adjust (- 1 (length (match-string 2))))) + (substring (match-string 2) 0 space-adjust) + (or old-spacing ". ")))) + (insert (concat new-indent new-prefix new-spacing))))) + ;; Unordered list, or ordered list with hash mark + ((string-match-p "[\\*\\+-]\\|#\\." marker) + (insert new-indent marker))))))) + +(defun markdown-move-list-item-up () + "Move the current list item up in the list when possible." + (interactive) + (let (cur prev old) + (when (setq cur (markdown-cur-list-item-bounds)) + (setq old (point)) + (goto-char (nth 0 cur)) + (if (markdown-prev-list-item (nth 3 cur)) + (progn + (setq prev (markdown-cur-list-item-bounds)) + (condition-case nil + (progn + (transpose-regions (nth 0 prev) (nth 1 prev) + (nth 0 cur) (nth 1 cur) t) + (goto-char (+ (nth 0 prev) (- old (nth 0 cur))))) + ;; Catch error in case regions overlap. + (error (goto-char old)))) + (goto-char old))))) + +(defun markdown-move-list-item-down () + "Move the current list item down in the list when possible." + (interactive) + (let (cur next old) + (when (setq cur (markdown-cur-list-item-bounds)) + (setq old (point)) + (if (markdown-next-list-item (nth 3 cur)) + (progn + (setq next (markdown-cur-list-item-bounds)) + (condition-case nil + (progn + (transpose-regions (nth 0 cur) (nth 1 cur) + (nth 0 next) (nth 1 next) nil) + (goto-char (+ old (- (nth 1 next) (nth 1 cur))))) + ;; Catch error in case regions overlap. + (error (goto-char old)))) + (goto-char old))))) + +(defun markdown-demote-list-item (&optional bounds) + "Indent (or demote) the current list item. +Optionally, BOUNDS of the current list item may be provided if available." + (interactive) + (when (or bounds (setq bounds (markdown-cur-list-item-bounds))) + (save-excursion + (let ((end-marker (set-marker (make-marker) (nth 1 bounds)))) + (goto-char (nth 0 bounds)) + (while (< (point) end-marker) + (unless (markdown-cur-line-blank-p) + (insert (make-string markdown-list-indent-width ? ))) + (forward-line)))))) + +(defun markdown-promote-list-item (&optional bounds) + "Unindent (or promote) the current list item. +Optionally, BOUNDS of the current list item may be provided if available." + (interactive) + (when (or bounds (setq bounds (markdown-cur-list-item-bounds))) + (save-excursion + (save-match-data + (let ((end-marker (set-marker (make-marker) (nth 1 bounds))) + num regexp) + (goto-char (nth 0 bounds)) + (when (looking-at (format "^[ ]\\{1,%d\\}" + markdown-list-indent-width)) + (setq num (- (match-end 0) (match-beginning 0))) + (setq regexp (format "^[ ]\\{1,%d\\}" num)) + (while (and (< (point) end-marker) + (re-search-forward regexp end-marker t)) + (replace-match "" nil nil) + (forward-line)))))))) + +(defun markdown-cleanup-list-numbers-level (&optional pfx) + "Update the numbering for level PFX (as a string of spaces). + +Assume that the previously found match was for a numbered item in +a list." + (let ((cpfx pfx) + (idx 0) + (continue t) + (step t) + (sep nil)) + (while (and continue (not (eobp))) + (setq step t) + (cond + ((looking-at "^\\([\s-]*\\)[0-9]+\\. ") + (setq cpfx (match-string-no-properties 1)) + (cond + ((string= cpfx pfx) + (save-excursion + (replace-match + (concat pfx (number-to-string (setq idx (1+ idx))) ". "))) + (setq sep nil)) + ;; indented a level + ((string< pfx cpfx) + (setq sep (markdown-cleanup-list-numbers-level cpfx)) + (setq step nil)) + ;; exit the loop + (t + (setq step nil) + (setq continue nil)))) + + ((looking-at "^\\([\s-]*\\)[^ \t\n\r].*$") + (setq cpfx (match-string-no-properties 1)) + (cond + ;; reset if separated before + ((string= cpfx pfx) (when sep (setq idx 0))) + ((string< cpfx pfx) + (setq step nil) + (setq continue nil)))) + (t (setq sep t))) + + (when step + (beginning-of-line) + (setq continue (= (forward-line) 0)))) + sep)) + +(defun markdown-cleanup-list-numbers () + "Update the numbering of ordered lists." + (interactive) + (save-excursion + (goto-char (point-min)) + (markdown-cleanup-list-numbers-level ""))) + + +;;; Movement ================================================================== + +(defun markdown-beginning-of-defun (&optional arg) + "`beginning-of-defun-function' for Markdown. +Move backward to the beginning of the current or previous section. +When ARG is non-nil, repeat that many times. When ARG is negative, +move forward to the ARG-th following section." + (interactive "P") + (or arg (setq arg 1)) + (forward-char 1) + (or (re-search-backward markdown-regex-header nil t arg) + (goto-char (point-min)))) + +(defun markdown-end-of-defun (&optional arg) + "`end-of-defun-function' for Markdown. +Move forward to the end of the current or following section. +When ARG is non-nil, repeat that many times. When ARG is negative, +move back to the ARG-th preceding section." + (interactive "P") + (or arg (setq arg 1)) + (when (looking-at markdown-regex-header) + (goto-char (match-beginning 0)) + (forward-char 1)) + (if (re-search-forward markdown-regex-header nil t arg) + (goto-char (match-beginning 0)) + (goto-char (point-max))) + (skip-syntax-backward "-")) + +(defun markdown-beginning-of-block () + "Move the point to the start of the previous text block." + (interactive) + (if (re-search-backward markdown-regex-block-separator nil t) + (goto-char (or (match-end 2) (match-end 0))) + (goto-char (point-min)))) + +(defun markdown-end-of-block () + "Move the point to the start of the next text block." + (interactive) + (beginning-of-line) + (skip-syntax-forward "-") + (when (= (point) (point-min)) + (forward-char)) + (if (re-search-forward markdown-regex-block-separator nil t) + (goto-char (or (match-end 2) (match-end 0))) + (goto-char (point-max))) + (skip-syntax-backward "-") + (forward-line)) + +(defun markdown-forward-paragraph (arg) + "Move forward one or more paragraphs or by one block. +When ARG is nil or a numeric prefix, call `forward-paragraph' +with ARG. When called with \\[universal-argument], call +`markdown-end-of-block' instead." + (interactive "P") + (or arg (setq arg 1)) + (cond ((integerp arg) + (forward-paragraph arg)) + ((equal arg '(4)) + (markdown-end-of-block)))) + +(defun markdown-backward-paragraph (arg) + "Move backward one or more paragraphs or by one block. +When ARG is nil or a numeric prefix, call `backward-paragraph' +with ARG. When called with \\[universal-argument], call +`markdown-beginning-of-block' instead." + (interactive "P") + (or arg (setq arg 1)) + (cond ((integerp arg) + (backward-paragraph arg)) + ((equal arg '(4)) + (markdown-beginning-of-block)))) + +(defun markdown-end-of-block-element () + "Move the point to the start of the next block unit. +Stops at blank lines, list items, headers, and horizontal rules." + (interactive) + (forward-line) + (while (and (or (not (markdown-prev-line-blank-p)) + (markdown-cur-line-blank-p)) + (not (or (looking-at-p markdown-regex-list) + (looking-at-p markdown-regex-header) + (looking-at-p markdown-regex-hr))) + (not (eobp))) + (forward-line))) + +(defun markdown-next-link () + "Jump to next inline, reference, or wiki link. +If successful, return point. Otherwise, return nil. +See `markdown-wiki-link-p' and `markdown-previous-wiki-link'." + (interactive) + (let ((opoint (point))) + (when (or (markdown-link-p) (markdown-wiki-link-p)) + ;; At a link already, move past it. + (goto-char (+ (match-end 0) 1))) + ;; Search for the next wiki link and move to the beginning. + (while (and (re-search-forward (markdown-make-regex-link-generic) nil t) + (markdown-code-block-at-point) + (< (point) (point-max)))) + (if (and (not (eq (point) opoint)) + (or (markdown-link-p) (markdown-wiki-link-p))) + ;; Group 1 will move past non-escape character in wiki link regexp. + ;; Go to beginning of group zero for all other link types. + (goto-char (or (match-beginning 1) (match-beginning 0))) + (goto-char opoint) + nil))) + +(defun markdown-previous-link () + "Jump to previous wiki link. +If successful, return point. Otherwise, return nil. +See `markdown-wiki-link-p' and `markdown-next-wiki-link'." + (interactive) + (let ((opoint (point))) + (while (and (re-search-backward (markdown-make-regex-link-generic) nil t) + (markdown-code-block-at-point) + (> (point) (point-min)))) + (if (and (not (eq (point) opoint)) + (or (markdown-link-p) (markdown-wiki-link-p))) + (goto-char (or (match-beginning 1) (match-beginning 0))) + (goto-char opoint) + nil))) + +(defun markdown-next-heading () + "Move to the next heading line of any level. +With argument, repeats or can move backward if negative." + (let ((pos (outline-next-heading))) + (while (markdown-code-block-at-point) + (setq pos (outline-next-heading))) + pos)) + +(defun markdown-previous-heading () + "Move to the previous heading line of any level. +With argument, repeats or can move backward if negative." + (let ((pos (outline-previous-heading))) + (while (markdown-code-block-at-point) + (setq pos (outline-previous-heading))) + pos)) + + +;;; Outline =================================================================== + +(defun markdown-move-heading-common (move-fn &optional arg) + "Wrapper for `outline-mode' functions to skip false positives. +MOVE-FN is a function and ARG is its argument. For example, +headings inside preformatted code blocks may match +`outline-regexp' but should not be considered as headings." + (funcall move-fn arg) + (let ((prev -1)) + (while (and (/= prev (point)) (markdown-code-block-at-point)) + (setq prev (point)) + (funcall move-fn arg)))) + +(defun markdown-next-visible-heading (arg) + "Move to the next visible heading line of any level. +With argument, repeats or can move backward if negative. ARG is +passed to `outline-next-visible-heading'." + (interactive "p") + (markdown-move-heading-common 'outline-next-visible-heading arg)) + +(defun markdown-previous-visible-heading (arg) + "Move to the previous visible heading line of any level. +With argument, repeats or can move backward if negative. ARG is +passed to `outline-previous-visible-heading'." + (interactive "p") + (markdown-move-heading-common 'outline-previous-visible-heading arg)) + +(defun markdown-forward-same-level (arg) + "Move forward to the ARG'th heading at same level as this one. +Stop at the first and last headings of a superior heading." + (interactive "p") + (markdown-move-heading-common 'outline-forward-same-level arg)) + +(defun markdown-back-to-heading-over-code-block (&optional invisible-ok) + (beginning-of-line) + (or (and (outline-on-heading-p invisible-ok) + (not (markdown-code-block-at-point))) + (let ((header-re (concat "^\\(?:" outline-regexp "\\)")) + found) + (save-excursion + (while (not found) + (let (finish) + (while (and (not finish) (re-search-backward header-re nil t)) + (when (and (or invisible-ok (not (outline-invisible-p))) + (not (markdown-code-block-at-point))) + (setq finish t))) + (if (not finish) + (error "Before first heading") + (setq found (point)))))) + (goto-char found) + found))) + +(defun markdown-backward-same-level (arg) + "Move backward to the ARG'th heading at same level as this one. +Stop at the first and last headings of a superior heading." + (interactive "p") + (markdown-back-to-heading-over-code-block) + (while (> arg 0) + (let ((point-to-move-to (save-excursion + (outline-get-last-sibling)))) + (if point-to-move-to + (progn + (goto-char point-to-move-to) + (setq arg (1- arg))) + (error "No previous same-level heading"))))) + +(defun markdown-up-heading (arg) + "Move to the visible heading line of which the present line is a subheading. +With argument, move up ARG levels." + (interactive "p") + (markdown-move-heading-common 'outline-up-heading arg)) + +(defun markdown-back-to-heading (&optional invisible-ok) + "Move to previous heading line, or beg of this line if it's a heading. +Only visible heading lines are considered, unless INVISIBLE-OK is non-nil." + (markdown-move-heading-common 'outline-back-to-heading invisible-ok)) + +(defalias 'markdown-end-of-heading 'outline-end-of-heading) + +(defun markdown-on-heading-p () + "Return t if point is on a (visible) heading line." + (get-text-property (point) 'markdown-heading)) + +(defun markdown-end-of-subtree (&optional invisible-OK) + "Move to the end of the current subtree. +Only visible heading lines are considered, unless INVISIBLE-OK is +non-nil. +Derived from `org-end-of-subtree'." + (markdown-back-to-heading invisible-OK) + (let ((first t) + (level (funcall outline-level))) + (while (and (not (eobp)) + (or first (> (funcall outline-level) level))) + (setq first nil) + (markdown-next-heading)) + (if (memq (preceding-char) '(?\n ?\^M)) + (progn + ;; Go to end of line before heading + (forward-char -1) + (if (memq (preceding-char) '(?\n ?\^M)) + ;; leave blank line before heading + (forward-char -1))))) + (point)) + +(defun markdown-outline-fix-visibility () + "Hide any false positive headings that should not be shown. +For example, headings inside preformatted code blocks may match +`outline-regexp' but should not be shown as headings when cycling. +Also, the ending --- line in metadata blocks appears to be a +setext header, but should not be folded." + (save-excursion + (goto-char (point-min)) + ;; Unhide any false positives in metadata blocks + (when (markdown-text-property-at-point 'markdown-yaml-metadata-begin) + (let ((body (progn (forward-line) + (markdown-text-property-at-point + 'markdown-yaml-metadata-section)))) + (when body + (let ((end (progn (goto-char (cl-second body)) + (markdown-text-property-at-point + 'markdown-yaml-metadata-end)))) + (outline-flag-region (point-min) (1+ (cl-second end)) nil))))) + ;; Hide any false positives in code blocks + (unless (outline-on-heading-p) + (outline-next-visible-heading 1)) + (while (< (point) (point-max)) + (when (markdown-code-block-at-point) + (outline-flag-region (1- (point-at-bol)) (point-at-eol) t)) + (outline-next-visible-heading 1)))) + +(defvar markdown-cycle-global-status 1) +(defvar markdown-cycle-subtree-status nil) + +(defun markdown-next-preface () + (let (finish) + (while (and (not finish) (re-search-forward (concat "\n\\(?:" outline-regexp "\\)") + nil 'move)) + (unless (markdown-code-block-at-point) + (goto-char (match-beginning 0)) + (setq finish t)))) + (when (and (bolp) (or outline-blank-line (eobp)) (not (bobp))) + (forward-char -1))) + +(defun markdown-show-entry () + (save-excursion + (outline-back-to-heading t) + (outline-flag-region (1- (point)) + (progn + (markdown-next-preface) + (if (= 1 (- (point-max) (point))) + (point-max) + (point))) + nil))) + +(defun markdown-cycle (&optional arg) + "Visibility cycling for Markdown mode. +If ARG is t, perform global visibility cycling. If the point is +at an atx-style header, cycle visibility of the corresponding +subtree. Otherwise, insert a tab using `indent-relative'. +Derived from `org-cycle'." + (interactive "P") + (cond + ((eq arg t) ;; Global cycling + (cond + ((and (eq last-command this-command) + (eq markdown-cycle-global-status 2)) + ;; Move from overview to contents + (markdown-hide-sublevels 1) + (message "CONTENTS") + (setq markdown-cycle-global-status 3) + (markdown-outline-fix-visibility)) + + ((and (eq last-command this-command) + (eq markdown-cycle-global-status 3)) + ;; Move from contents to all + (markdown-show-all) + (message "SHOW ALL") + (setq markdown-cycle-global-status 1)) + + (t + ;; Defaults to overview + (markdown-hide-body) + (message "OVERVIEW") + (setq markdown-cycle-global-status 2) + (markdown-outline-fix-visibility)))) + + ((save-excursion (beginning-of-line 1) (markdown-on-heading-p)) + ;; At a heading: rotate between three different views + (markdown-back-to-heading) + (let ((goal-column 0) eoh eol eos) + ;; Determine boundaries + (save-excursion + (markdown-back-to-heading) + (save-excursion + (beginning-of-line 2) + (while (and (not (eobp)) ;; this is like `next-line' + (get-char-property (1- (point)) 'invisible)) + (beginning-of-line 2)) (setq eol (point))) + (markdown-end-of-heading) (setq eoh (point)) + (markdown-end-of-subtree t) + (skip-chars-forward " \t\n") + (beginning-of-line 1) ; in case this is an item + (setq eos (1- (point)))) + ;; Find out what to do next and set `this-command' + (cond + ((= eos eoh) + ;; Nothing is hidden behind this heading + (message "EMPTY ENTRY") + (setq markdown-cycle-subtree-status nil)) + ((>= eol eos) + ;; Entire subtree is hidden in one line: open it + (markdown-show-entry) + (markdown-show-children) + (message "CHILDREN") + (setq markdown-cycle-subtree-status 'children)) + ((and (eq last-command this-command) + (eq markdown-cycle-subtree-status 'children)) + ;; We just showed the children, now show everything. + (markdown-show-subtree) + (message "SUBTREE") + (setq markdown-cycle-subtree-status 'subtree)) + (t + ;; Default action: hide the subtree. + (markdown-hide-subtree) + (message "FOLDED") + (setq markdown-cycle-subtree-status 'folded))))) + + (t + (indent-for-tab-command)))) + +(defun markdown-shifttab () + "Global visibility cycling. +Calls `markdown-cycle' with argument t." + (interactive) + (markdown-cycle t)) + +(defun markdown-outline-level () + "Return the depth to which a statement is nested in the outline." + (cond + ((markdown-code-block-at-point) 7) + ((match-end 2) 1) + ((match-end 3) 2) + ((- (match-end 4) (match-beginning 4))))) + +(defun markdown-promote-subtree (&optional arg) + "Promote the current subtree of ATX headings. +Note that Markdown does not support heading levels higher than +six and therefore level-six headings will not be promoted +further. If ARG is non-nil promote the heading, otherwise +demote." + (interactive "*P") + (save-excursion + (when (and (or (thing-at-point-looking-at markdown-regex-header-atx) + (re-search-backward markdown-regex-header-atx nil t)) + (not (markdown-code-block-at-point))) + (let ((level (length (match-string 1))) + (promote-or-demote (if arg 1 -1)) + (remove 't)) + (markdown-cycle-atx promote-or-demote remove) + (catch 'end-of-subtree + (while (markdown-next-heading) + ;; Exit if this not a higher level heading; promote otherwise. + (if (and (looking-at markdown-regex-header-atx) + (<= (length (match-string-no-properties 1)) level)) + (throw 'end-of-subtree nil) + (markdown-cycle-atx promote-or-demote remove)))))))) + +(defun markdown-demote-subtree () + "Demote the current subtree of ATX headings." + (interactive) + (markdown-promote-subtree t)) + +(defun markdown-move-subtree-up () + "Move the current subtree of ATX headings up." + (interactive) + (outline-move-subtree-up 1)) + +(defun markdown-move-subtree-down () + "Move the current subtree of ATX headings down." + (interactive) + (outline-move-subtree-down 1)) + + +;;; Generic Structure Editing, Completion, and Cycling Commands =============== + +(defun markdown-move-up () + "Move list item up. +Calls `markdown-move-list-item-up'." + (interactive) + (markdown-move-list-item-up)) + +(defun markdown-move-down () + "Move list item down. +Calls `markdown-move-list-item-down'." + (interactive) + (markdown-move-list-item-down)) + +(defun markdown-promote () + "Either promote header or list item at point or cycle markup. +See `markdown-cycle-atx', `markdown-cycle-setext', and +`markdown-promote-list-item'." + (interactive) + (let (bounds) + (cond + ;; Promote atx header + ((thing-at-point-looking-at markdown-regex-header-atx) + (markdown-cycle-atx -1)) + ;; Promote setext header + ((thing-at-point-looking-at markdown-regex-header-setext) + (markdown-cycle-setext -1)) + ;; Promote horizonal rule + ((thing-at-point-looking-at markdown-regex-hr) + (markdown-cycle-hr -1)) + ;; Promote list item + ((setq bounds (markdown-cur-list-item-bounds)) + (markdown-promote-list-item bounds)) + ;; Promote bold + ((thing-at-point-looking-at markdown-regex-bold) + (markdown-cycle-bold)) + ;; Promote italic + ((thing-at-point-looking-at markdown-regex-italic) + (markdown-cycle-italic)) + (t + (error "Nothing to promote at point"))))) + +(defun markdown-demote () + "Either demote header or list item at point or cycle or remove markup. +See `markdown-cycle-atx', `markdown-cycle-setext', and +`markdown-demote-list-item'." + (interactive) + (let (bounds) + (cond + ;; Demote atx header + ((thing-at-point-looking-at markdown-regex-header-atx) + (markdown-cycle-atx 1)) + ;; Demote setext header + ((thing-at-point-looking-at markdown-regex-header-setext) + (markdown-cycle-setext 1)) + ;; Demote horizonal rule + ((thing-at-point-looking-at markdown-regex-hr) + (markdown-cycle-hr 1)) + ;; Demote list item + ((setq bounds (markdown-cur-list-item-bounds)) + (markdown-demote-list-item bounds)) + ;; Demote bold + ((thing-at-point-looking-at markdown-regex-bold) + (markdown-cycle-bold)) + ;; Demote italic + ((thing-at-point-looking-at markdown-regex-italic) + (markdown-cycle-italic)) + (t + (error "Nothing to demote at point"))))) + + +;;; Commands ================================================================== + +(defun markdown (&optional output-buffer-name) + "Run `markdown-command' on buffer, sending output to OUTPUT-BUFFER-NAME. +The output buffer name defaults to `markdown-output-buffer-name'. +Return the name of the output buffer used." + (interactive) + (save-window-excursion + (let ((begin-region) + (end-region)) + (if (markdown-use-region-p) + (setq begin-region (region-beginning) + end-region (region-end)) + (setq begin-region (point-min) + end-region (point-max))) + + (unless output-buffer-name + (setq output-buffer-name markdown-output-buffer-name)) + (cond + ;; Handle case when `markdown-command' does not read from stdin + (markdown-command-needs-filename + (if (not buffer-file-name) + (error "Must be visiting a file") + (shell-command (concat markdown-command " " + (shell-quote-argument buffer-file-name)) + output-buffer-name))) + ;; Pass region to `markdown-command' via stdin + (t + (let ((buf (get-buffer-create output-buffer-name))) + (with-current-buffer buf + (setq buffer-read-only nil) + (erase-buffer)) + (call-process-region begin-region end-region + shell-file-name nil buf nil + shell-command-switch markdown-command))))) + output-buffer-name)) + +(defun markdown-standalone (&optional output-buffer-name) + "Special function to provide standalone HTML output. +Insert the output in the buffer named OUTPUT-BUFFER-NAME." + (interactive) + (setq output-buffer-name (markdown output-buffer-name)) + (with-current-buffer output-buffer-name + (set-buffer output-buffer-name) + (unless (markdown-output-standalone-p) + (markdown-add-xhtml-header-and-footer output-buffer-name)) + (goto-char (point-min)) + (html-mode)) + output-buffer-name) + +(defun markdown-other-window (&optional output-buffer-name) + "Run `markdown-command' on current buffer and display in other window. +When OUTPUT-BUFFER-NAME is given, insert the output in the buffer with +that name." + (interactive) + (markdown-display-buffer-other-window + (markdown-standalone output-buffer-name))) + +(defun markdown-output-standalone-p () + "Determine whether `markdown-command' output is standalone XHTML. +Standalone XHTML output is identified by an occurrence of +`markdown-xhtml-standalone-regexp' in the first five lines of output." + (save-excursion + (goto-char (point-min)) + (re-search-forward + markdown-xhtml-standalone-regexp + (save-excursion (goto-char (point-min)) (forward-line 4) (point)) + t))) + +(defun markdown-stylesheet-link-string (stylesheet-path) + (concat "")) + +(defun markdown-add-xhtml-header-and-footer (title) + "Wrap XHTML header and footer with given TITLE around current buffer." + (goto-char (point-min)) + (insert "\n" + "\n\n" + "\n\n" + "\n") + (insert title) + (insert "\n") + (when (> (length markdown-content-type) 0) + (insert + (format + "\n" + markdown-content-type + (or (and markdown-coding-system + (fboundp 'coding-system-get) + (coding-system-get markdown-coding-system + 'mime-charset)) + (and (fboundp 'coding-system-get) + (coding-system-get buffer-file-coding-system + 'mime-charset)) + "iso-8859-1")))) + (if (> (length markdown-css-paths) 0) + (insert (mapconcat 'markdown-stylesheet-link-string markdown-css-paths "\n"))) + (when (> (length markdown-xhtml-header-content) 0) + (insert markdown-xhtml-header-content)) + (insert "\n\n\n" + "\n\n") + (goto-char (point-max)) + (insert "\n" + "\n" + "\n")) + +(defun markdown-preview (&optional output-buffer-name) + "Run `markdown-command' on the current buffer and view output in browser. +When OUTPUT-BUFFER-NAME is given, insert the output in the buffer with +that name." + (interactive) + (browse-url-of-buffer + (markdown-standalone (or output-buffer-name markdown-output-buffer-name)))) + +(defun markdown-export-file-name (&optional extension) + "Attempt to generate a filename for Markdown output. +The file extension will be EXTENSION if given, or .html by default. +If the current buffer is visiting a file, we construct a new +output filename based on that filename. Otherwise, return nil." + (when (buffer-file-name) + (unless extension + (setq extension ".html")) + (let ((candidate + (concat + (cond + ((buffer-file-name) + (file-name-sans-extension (buffer-file-name))) + (t (buffer-name))) + extension))) + (cond + ((equal candidate (buffer-file-name)) + (concat candidate extension)) + (t + candidate))))) + +(defun markdown-export (&optional output-file) + "Run Markdown on the current buffer, save to file, and return the filename. +If OUTPUT-FILE is given, use that as the filename. Otherwise, use the filename +generated by `markdown-export-file-name', which will be constructed using the +current filename, but with the extension removed and replaced with .html." + (interactive) + (unless output-file + (setq output-file (markdown-export-file-name ".html"))) + (when output-file + (let* ((init-buf (current-buffer)) + (init-point (point)) + (init-buf-string (buffer-string)) + (output-buffer (find-file-noselect output-file)) + (output-buffer-name (buffer-name output-buffer))) + (run-hooks 'markdown-before-export-hook) + (markdown-standalone output-buffer-name) + (with-current-buffer output-buffer + (run-hooks 'markdown-after-export-hook) + (save-buffer)) + ;; if modified, restore initial buffer + (when (buffer-modified-p init-buf) + (erase-buffer) + (insert init-buf-string) + (save-buffer) + (goto-char init-point)) + output-file))) + +(defun markdown-export-and-preview () + "Export to XHTML using `markdown-export' and browse the resulting file." + (interactive) + (browse-url-of-file (markdown-export))) + +(defvar markdown-live-preview-buffer nil + "Buffer used to preview markdown output in `markdown-live-preview-export'.") +(make-variable-buffer-local 'markdown-live-preview-buffer) + +(defvar markdown-live-preview-source-buffer nil + "Source buffer from which current buffer was generated. +This is the inverse of `markdown-live-preview-buffer'.") +(make-variable-buffer-local 'markdown-live-preview-source-buffer) + +(defvar markdown-live-preview-currently-exporting nil) + +(defun markdown-live-preview-get-filename () + "Standardize the filename exported by `markdown-live-preview-export'." + (markdown-export-file-name ".html")) + +(defun markdown-live-preview-window-eww (file) + "Preview FILE with eww. +To be used with `markdown-live-preview-window-function'." + (if (require 'eww nil t) + (progn + (eww-open-file file) + (get-buffer "*eww*")) + (error "EWW is not present or not loaded on this version of Emacs"))) + +(defun markdown-visual-lines-between-points (beg end) + (save-excursion + (goto-char beg) + (cl-loop with count = 0 + while (progn (end-of-visual-line) + (and (< (point) end) (line-move-visual 1 t))) + do (cl-incf count) + finally return count))) + +(defun markdown-live-preview-window-serialize (buf) + "Get window point and scroll data for all windows displaying BUF." + (when (buffer-live-p buf) + (with-current-buffer buf + (mapcar + (lambda (win) + (with-selected-window win + (let* ((start (window-start)) + (pt (window-point)) + (pt-or-sym (cond ((= pt (point-min)) 'min) + ((= pt (point-max)) 'max) + (t pt))) + (diff (markdown-visual-lines-between-points + start pt))) + (list win pt-or-sym diff)))) + (get-buffer-window-list buf))))) + +(defun markdown-get-point-back-lines (pt num-lines) + (save-excursion + (goto-char pt) + (line-move-visual (- num-lines) t) + ;; in testing, can occasionally overshoot the number of lines to traverse + (let ((actual-num-lines (markdown-visual-lines-between-points (point) pt))) + (when (> actual-num-lines num-lines) + (line-move-visual (- actual-num-lines num-lines) t))) + (point))) + +(defun markdown-live-preview-window-deserialize (window-posns) + "Apply window point and scroll data from WINDOW-POSNS. +WINDOW-POSNS is provided by `markdown-live-preview-window-serialize'." + (cl-destructuring-bind (win pt-or-sym diff) window-posns + (when (window-live-p win) + (with-current-buffer markdown-live-preview-buffer + (set-window-buffer win (current-buffer)) + (cl-destructuring-bind (actual-pt actual-diff) + (cl-case pt-or-sym + (min (list (point-min) 0)) + (max (list (point-max) diff)) + (t (list pt-or-sym diff))) + (set-window-start + win (markdown-get-point-back-lines actual-pt actual-diff)) + (set-window-point win actual-pt)))))) + +(defun markdown-live-preview-export () + "Export to XHTML using `markdown-export'. +Browse the resulting file within Emacs using +`markdown-live-preview-window-function' Return the buffer +displaying the rendered output." + (interactive) + (let* ((markdown-live-preview-currently-exporting t) + (cur-buf (current-buffer)) + (export-file (markdown-export (markdown-live-preview-get-filename))) + ;; get positions in all windows currently displaying output buffer + (window-data + (markdown-live-preview-window-serialize + markdown-live-preview-buffer))) + (save-window-excursion + (let ((output-buffer + (funcall markdown-live-preview-window-function export-file))) + (with-current-buffer output-buffer + (setq markdown-live-preview-source-buffer cur-buf) + (add-hook 'kill-buffer-hook + #'markdown-live-preview-remove-on-kill t t)) + (with-current-buffer cur-buf + (setq markdown-live-preview-buffer output-buffer)))) + (with-current-buffer cur-buf + ;; reset all windows displaying output buffer to where they were, + ;; now with the new output + (mapc #'markdown-live-preview-window-deserialize window-data) + ;; delete html editing buffer + (let ((buf (get-file-buffer export-file))) (when buf (kill-buffer buf))) + (when (and export-file (file-exists-p export-file) + (eq markdown-live-preview-delete-export + 'delete-on-export)) + (delete-file export-file)) + markdown-live-preview-buffer))) + +(defun markdown-live-preview-remove () + (when (buffer-live-p markdown-live-preview-buffer) + (kill-buffer markdown-live-preview-buffer)) + (setq markdown-live-preview-buffer nil) + ;; if set to 'delete-on-export, the output has already been deleted + (when (eq markdown-live-preview-delete-export 'delete-on-destroy) + (let ((outfile-name (markdown-live-preview-get-filename))) + (when (file-exists-p outfile-name) + (delete-file outfile-name))))) + +(defun markdown-display-buffer-other-window (buf) + (let ((cur-buf (current-buffer))) + (switch-to-buffer-other-window buf) + (set-buffer cur-buf))) + +(defun markdown-live-preview-if-markdown () + (when (and (derived-mode-p 'markdown-mode) + markdown-live-preview-mode) + (unless markdown-live-preview-currently-exporting + (if (buffer-live-p markdown-live-preview-buffer) + (markdown-live-preview-export) + (markdown-display-buffer-other-window + (markdown-live-preview-export)))))) + +(defun markdown-live-preview-remove-on-kill () + (cond ((and (derived-mode-p 'markdown-mode) + markdown-live-preview-mode) + (markdown-live-preview-remove)) + (markdown-live-preview-source-buffer + (with-current-buffer markdown-live-preview-source-buffer + (setq markdown-live-preview-buffer nil)) + (setq markdown-live-preview-source-buffer nil)))) + +(defun markdown-live-preview-switch-to-output () + "Switch to output buffer." + (interactive) + "Turn on `markdown-live-preview-mode' if not already on, and switch to its +output buffer in another window." + (if markdown-live-preview-mode + (markdown-display-buffer-other-window (markdown-live-preview-export))) + (markdown-live-preview-mode)) + +(defun markdown-live-preview-re-export () + "Re export source buffer." + (interactive) + "If the current buffer is a buffer displaying the exported version of a +`markdown-live-preview-mode' buffer, call `markdown-live-preview-export' and +update this buffer's contents." + (when markdown-live-preview-source-buffer + (with-current-buffer markdown-live-preview-source-buffer + (markdown-live-preview-export)))) + +(defun markdown-open () + "Open file for the current buffer with `markdown-open-command'." + (interactive) + (if (not markdown-open-command) + (error "Variable `markdown-open-command' must be set") + (if (not buffer-file-name) + (error "Must be visiting a file") + (call-process markdown-open-command + nil nil nil buffer-file-name)))) + +(defun markdown-kill-ring-save () + "Run Markdown on file and store output in the kill ring." + (interactive) + (save-window-excursion + (markdown) + (with-current-buffer markdown-output-buffer-name + (kill-ring-save (point-min) (point-max))))) + + +;;; Links ===================================================================== + +(defun markdown-link-p () + "Return non-nil when `point' is at a non-wiki link. +See `markdown-wiki-link-p' for more information." + (let ((case-fold-search nil)) + (and (not (markdown-wiki-link-p)) + (not (markdown-code-block-at-point)) + (or (thing-at-point-looking-at markdown-regex-link-inline) + (thing-at-point-looking-at markdown-regex-link-reference) + (thing-at-point-looking-at markdown-regex-uri) + (thing-at-point-looking-at markdown-regex-angle-uri))))) + +(defun markdown-link-link () + "Return the link part of the regular (non-wiki) link at point. +Works with both inline and reference style links. If point is +not at a link or the link reference is not defined returns nil." + (cond + ((thing-at-point-looking-at markdown-regex-link-inline) + (match-string-no-properties 6)) + ((thing-at-point-looking-at markdown-regex-link-reference) + (let* ((text (match-string-no-properties 3)) + (reference (match-string-no-properties 6)) + (target (downcase (if (string= reference "") text reference)))) + (car (markdown-reference-definition target)))) + ((thing-at-point-looking-at markdown-regex-uri) + (match-string-no-properties 0)) + ((thing-at-point-looking-at markdown-regex-angle-uri) + (match-string-no-properties 2)) + (t nil))) + +(defun markdown-follow-link-at-point () + "Open the current non-wiki link. +If the link is a complete URL, open in browser with `browse-url'. +Otherwise, open with `find-file' after stripping anchor and/or query string." + (interactive) + (if (markdown-link-p) + (let* ((link (markdown-link-link)) + (struct (url-generic-parse-url link)) + (full (url-fullness struct)) + (file link)) + ;; Parse URL, determine fullness, strip query string + (if (fboundp 'url-path-and-query) + (setq file (car (url-path-and-query struct))) + (when (and (setq file (url-filename struct)) + (string-match "\\?" file)) + (setq file (substring file 0 (match-beginning 0))))) + ;; Open full URLs in browser, files in Emacs + (if full + (browse-url link) + (when (and file (> (length file) 0)) (find-file file)))) + (error "Point is not at a Markdown link or URI"))) + + +;;; WikiLink Following/Markup ================================================= + +(defun markdown-wiki-link-p () + "Return non-nil if wiki links are enabled and `point' is at a true wiki link. +A true wiki link name matches `markdown-regex-wiki-link' but does +not match the current file name after conversion. This modifies +the data returned by `match-data'. Note that the potential wiki +link name must be available via `match-string'." + (when markdown-enable-wiki-links + (let ((case-fold-search nil)) + (and (thing-at-point-looking-at markdown-regex-wiki-link) + (not (markdown-code-block-at-point)) + (or (not buffer-file-name) + (not (string-equal (buffer-file-name) + (markdown-convert-wiki-link-to-filename + (markdown-wiki-link-link))))))))) + +(defun markdown-wiki-link-link () + "Return the link part of the wiki link using current match data. +The location of the link component depends on the value of +`markdown-wiki-link-alias-first'." + (if markdown-wiki-link-alias-first + (or (match-string-no-properties 5) (match-string-no-properties 3)) + (match-string-no-properties 3))) + +(defun markdown-wiki-link-alias () + "Return the alias or text part of the wiki link using current match data. +The location of the alias component depends on the value of +`markdown-wiki-link-alias-first'." + (if markdown-wiki-link-alias-first + (match-string-no-properties 3) + (or (match-string-no-properties 5) (match-string-no-properties 3)))) + +(defun markdown-convert-wiki-link-to-filename (name) + "Generate a filename from the wiki link NAME. +Spaces in NAME are replaced with `markdown-link-space-sub-char'. +When in `gfm-mode', follow GitHub's conventions where [[Test Test]] +and [[test test]] both map to Test-test.ext." + (let ((basename (markdown-replace-regexp-in-string + "[[:space:]\n]" markdown-link-space-sub-char name))) + (when (eq major-mode 'gfm-mode) + (setq basename (concat (upcase (substring basename 0 1)) + (downcase (substring basename 1 nil))))) + (let* ((default + (concat basename + (if (and (buffer-file-name) + (file-name-extension (buffer-file-name))) + (concat "." + (file-name-extension (buffer-file-name)))))) + (current default)) + (catch 'done + (condition-case nil + (cl-loop + (if (or (not markdown-wiki-link-search-parent-directories) + (file-exists-p current)) + (throw 'done current)) + (if (string-equal (expand-file-name current) + (concat "/" default)) + (throw 'done default)) + (setq current (concat "../" current))) + (error default)))))) + +(defun markdown-follow-wiki-link (name &optional other) + "Follow the wiki link NAME. +Convert the name to a file name and call `find-file'. Ensure that +the new buffer remains in `markdown-mode'. Open the link in another +window when OTHER is non-nil." + (let ((filename (markdown-convert-wiki-link-to-filename name)) + (wp (file-name-directory buffer-file-name))) + (when other (other-window 1)) + (let ((default-directory wp)) + (find-file filename))) + (when (not (eq major-mode 'markdown-mode)) + (markdown-mode))) + +(defun markdown-follow-wiki-link-at-point (&optional arg) + "Find Wiki Link at point. +With prefix argument ARG, open the file in other window. +See `markdown-wiki-link-p' and `markdown-follow-wiki-link'." + (interactive "P") + (if (markdown-wiki-link-p) + (markdown-follow-wiki-link (markdown-wiki-link-link) arg) + (error "Point is not at a Wiki Link"))) + +(defun markdown-highlight-wiki-link (from to face) + "Highlight the wiki link in the region between FROM and TO using FACE." + (put-text-property from to 'font-lock-face face)) + +(defun markdown-unfontify-region-wiki-links (from to) + "Remove wiki link faces from the region specified by FROM and TO." + (interactive "*r") + (let ((modified (buffer-modified-p))) + (remove-text-properties from to '(font-lock-face markdown-link-face)) + (remove-text-properties from to '(font-lock-face markdown-missing-link-face)) + ;; remove-text-properties marks the buffer modified in emacs 24.3, + ;; undo that if it wasn't originally marked modified + (set-buffer-modified-p modified))) + +(defun markdown-fontify-region-wiki-links (from to) + "Search region given by FROM and TO for wiki links and fontify them. +If a wiki link is found check to see if the backing file exists +and highlight accordingly." + (goto-char from) + (save-match-data + (while (re-search-forward markdown-regex-wiki-link to t) + (when (not (markdown-code-block-at-point)) + (let ((highlight-beginning (match-beginning 1)) + (highlight-end (match-end 1)) + (file-name + (markdown-convert-wiki-link-to-filename + (markdown-wiki-link-link)))) + (if (condition-case nil (file-exists-p file-name) (error nil)) + (markdown-highlight-wiki-link + highlight-beginning highlight-end markdown-link-face) + (markdown-highlight-wiki-link + highlight-beginning highlight-end markdown-missing-link-face))))))) + +(defun markdown-extend-changed-region (from to) + "Extend region given by FROM and TO so that we can fontify all links. +The region is extended to the first newline before and the first +newline after." + ;; start looking for the first new line before 'from + (goto-char from) + (re-search-backward "\n" nil t) + (let ((new-from (point-min)) + (new-to (point-max))) + (if (not (= (point) from)) + (setq new-from (point))) + ;; do the same thing for the first new line after 'to + (goto-char to) + (re-search-forward "\n" nil t) + (if (not (= (point) to)) + (setq new-to (point))) + (cl-values new-from new-to))) + +(defun markdown-check-change-for-wiki-link (from to) + "Check region between FROM and TO for wiki links and re-fontify as needed." + (interactive "*r") + (let* ((modified (buffer-modified-p)) + (buffer-undo-list t) + (inhibit-read-only t) + (inhibit-point-motion-hooks t) + deactivate-mark + buffer-file-truename) + (unwind-protect + (save-excursion + (save-match-data + (save-restriction + ;; Extend the region to fontify so that it starts + ;; and ends at safe places. + (cl-multiple-value-bind (new-from new-to) + (markdown-extend-changed-region from to) + (goto-char new-from) + ;; Only refontify when the range contains text with a + ;; wiki link face or if the wiki link regexp matches. + (when (or (markdown-range-property-any + new-from new-to 'font-lock-face + (list markdown-link-face + markdown-missing-link-face)) + (re-search-forward + markdown-regex-wiki-link new-to t)) + ;; Unfontify existing fontification (start from scratch) + (markdown-unfontify-region-wiki-links new-from new-to) + ;; Now do the fontification. + (markdown-fontify-region-wiki-links new-from new-to)))))) + (and (not modified) + (buffer-modified-p) + (set-buffer-modified-p nil))))) + +(defun markdown-check-change-for-wiki-link-after-change (from to _) + "Check region between FROM and TO for wiki links and re-fontify as needed. +Designed to be used with the `after-change-functions' hook." + (markdown-check-change-for-wiki-link from to)) + +(defun markdown-fontify-buffer-wiki-links () + "Refontify all wiki links in the buffer." + (interactive) + (markdown-check-change-for-wiki-link (point-min) (point-max))) + + +;;; Following and Jumping ===================================================== + +(defun markdown-follow-thing-at-point (arg) + "Follow thing at point if possible, such as a reference link or wiki link. +Opens inline and reference links in a browser. Opens wiki links +to other files in the current window, or the another window if +ARG is non-nil. +See `markdown-follow-link-at-point' and +`markdown-follow-wiki-link-at-point'." + (interactive "P") + (cond ((markdown-link-p) + (markdown-follow-link-at-point)) + ((markdown-wiki-link-p) + (markdown-follow-wiki-link-at-point arg)) + (t + (error "Nothing to follow at point")))) + +(defun markdown-jump () + "Jump to another location based on context at point. +Jumps between reference links and definitions; between footnote +markers and footnote text." + (interactive) + (cond ((markdown-footnote-text-positions) + (markdown-footnote-return)) + ((markdown-footnote-marker-positions) + (markdown-footnote-goto-text)) + ((thing-at-point-looking-at markdown-regex-link-reference) + (markdown-reference-goto-definition)) + ((thing-at-point-looking-at markdown-regex-reference-definition) + (markdown-reference-goto-link (match-string-no-properties 2))) + (t + (error "Nothing to jump to from context at point")))) + + +;;; Miscellaneous ============================================================= + +(defun markdown-compress-whitespace-string (str) + "Compress whitespace in STR and return result. +Leading and trailing whitespace is removed. Sequences of multiple +spaces, tabs, and newlines are replaced with single spaces." + (markdown-replace-regexp-in-string "\\(^[ \t\n]+\\|[ \t\n]+$\\)" "" + (markdown-replace-regexp-in-string "[ \t\n]+" " " str))) + +(defun markdown-line-number-at-pos (&optional pos) + "Return (narrowed) buffer line number at position POS. +If POS is nil, use current buffer location. +This is an exact copy of `line-number-at-pos' for use in emacs21." + (let ((opoint (or pos (point))) start) + (save-excursion + (goto-char (point-min)) + (setq start (point)) + (goto-char opoint) + (forward-line 0) + (1+ (count-lines start (point)))))) + +(defun markdown-inside-link-p () + "Return t if point is within a link." + (thing-at-point-looking-at (markdown-make-regex-link-generic))) + +(defun markdown-line-is-reference-definition-p () + "Return whether the current line is a (non-footnote) reference defition." + (save-excursion + (move-beginning-of-line 1) + (and (looking-at-p markdown-regex-reference-definition) + (not (looking-at-p "[ \t]*\\[^"))))) + +(defun markdown-adaptive-fill-function () + "Return prefix for filling paragraph or nil if not determined." + (cond + ;; List item inside blockquote + ((looking-at "^[ \t]*>[ \t]*\\(\\(?:[0-9]+\\|#\\)\\.\\|[*+-]\\)[ \t]+") + (markdown-replace-regexp-in-string + "[0-9\\.*+-]" " " (match-string-no-properties 0))) + ;; Blockquote + ((looking-at "^[ \t]*>[ \t]*") + (match-string-no-properties 0)) + ;; List items + ((looking-at markdown-regex-list) + (match-string-no-properties 0)) + ((looking-at-p markdown-regex-footnote-definition) + " ") ; four spaces + ;; No match + (t nil))) + +(defun markdown-fill-forward-paragraph-function (&optional arg) + (let* ((arg (or arg 1)) + (paragraphs-remaining (forward-paragraph arg)) + (start (point))) + (when (< arg 0) + (while (and (not (eobp)) + (progn (move-to-left-margin) (not (eobp))) + (looking-at-p paragraph-separate)) + (forward-line 1)) + (if (looking-at markdown-regex-list) + (forward-char (length (match-string 0))) + (goto-char start))) + paragraphs-remaining)) + + +;;; Extension Framework ======================================================= + +(defun markdown-reload-extensions () + "Check settings, update font-lock keywords and hooks, and re-fontify buffer." + (interactive) + (when (eq major-mode 'markdown-mode) + ;; Update font lock keywords with extensions + (setq markdown-mode-font-lock-keywords + (append + markdown-mode-font-lock-keywords-basic + (markdown-mode-font-lock-keywords-wiki-links) + (markdown-mode-font-lock-keywords-math))) + ;; Update font lock defaults + (setq font-lock-defaults + '(markdown-mode-font-lock-keywords + nil nil nil nil + (font-lock-syntactic-face-function . markdown-syntactic-face))) + ;; Refontify buffer + (when (fboundp 'font-lock-refresh-defaults) (font-lock-refresh-defaults)) + ;; Add or remove hooks related to extensions + (markdown-setup-wiki-link-hooks))) + +(defun markdown-handle-local-variables () + "Run in `hack-local-variables-hook' to update font lock rules. +Checks to see if there is actually a ‘markdown-mode’ file local variable +before regenerating font-lock rules for extensions." + (when (and (boundp 'file-local-variables-alist) + (assoc 'markdown-enable-wiki-links file-local-variables-alist) + (assoc 'markdown-enable-math file-local-variables-alist)) + (markdown-reload-extensions))) + +;;; Wiki Links ================================================================ + +(defun markdown-toggle-wiki-links (&optional arg) + "Toggle support for wiki links. +With a prefix argument ARG, enable wiki link support if ARG is positive, +and disable it otherwise." + (interactive (list (or current-prefix-arg 'toggle))) + (setq markdown-enable-wiki-links + (if (eq arg 'toggle) + (not markdown-enable-wiki-links) + (> (prefix-numeric-value arg) 0))) + (if markdown-enable-wiki-links + (message "markdown-mode wiki link support enabled") + (message "markdown-mode wiki link support disabled")) + (markdown-reload-extensions)) + +(defun markdown-setup-wiki-link-hooks () + "Add or remove hooks for fontifying wiki links. +These are only enabled when `markdown-wiki-link-fontify-missing' is non-nil." + ;; Anytime text changes make sure it gets fontified correctly + (if (and markdown-enable-wiki-links + markdown-wiki-link-fontify-missing) + (add-hook 'after-change-functions + 'markdown-check-change-for-wiki-link-after-change t t) + (remove-hook 'after-change-functions + 'markdown-check-change-for-wiki-link-after-change t)) + ;; If we left the buffer there is a really good chance we were + ;; creating one of the wiki link documents. Make sure we get + ;; refontified when we come back. + (if (and markdown-enable-wiki-links + markdown-wiki-link-fontify-missing) + (progn + (add-hook 'window-configuration-change-hook + 'markdown-fontify-buffer-wiki-links t t) + (markdown-fontify-buffer-wiki-links)) + (remove-hook 'window-configuration-change-hook + 'markdown-fontify-buffer-wiki-links t) + (markdown-unfontify-region-wiki-links (point-min) (point-max)))) + +(defun markdown-mode-font-lock-keywords-wiki-links () + "Return wiki-link lock keywords if support is enabled. +If `markdown-wiki-link-fontify-missing' is also enabled, we use +hooks in `markdown-setup-wiki-link-hooks' for fontification instead." + (when (and markdown-enable-wiki-links + (not markdown-wiki-link-fontify-missing)) + (list + (cons markdown-regex-wiki-link '((1 markdown-link-face prepend)))))) + +;;; Math Support ============================================================== + +(make-obsolete 'markdown-enable-math 'markdown-toggle-math "v2.1") + +(defun markdown-toggle-math (&optional arg) + "Toggle support for inline and display LaTeX math expressions. +With a prefix argument ARG, enable math mode if ARG is positive, +and disable it otherwise. If called from Lisp, enable the mode +if ARG is omitted or nil." + (interactive (list (or current-prefix-arg 'toggle))) + (setq markdown-enable-math + (if (eq arg 'toggle) + (not markdown-enable-math) + (> (prefix-numeric-value arg) 0))) + (if markdown-enable-math + (message "markdown-mode math support enabled") + (message "markdown-mode math support disabled")) + (markdown-reload-extensions)) + +(defun markdown-mode-font-lock-keywords-math () + "Return math font lock keywords if support is enabled." + (when markdown-enable-math + (list + ;; Display mode equations with brackets: \[ \] + (cons markdown-regex-math-display '((1 markdown-markup-face prepend) + (2 markdown-math-face append) + (3 markdown-markup-face prepend))) + ;; Equation reference (eq:foo) + (cons "\\((eq:\\)\\([[:alnum:]:_]+\\)\\()\\)" '((1 markdown-markup-face) + (2 markdown-reference-face) + (3 markdown-markup-face))) + ;; Equation reference \eqref{foo} + (cons "\\(\\\\eqref{\\)\\([[:alnum:]:_]+\\)\\(}\\)" '((1 markdown-markup-face) + (2 markdown-reference-face) + (3 markdown-markup-face)))))) + + +;;; GFM Checkboxes as Buttons ================================================= + +(require 'button) + +(define-button-type 'markdown-gfm-checkbox-button + 'follow-link t + 'face 'markdown-gfm-checkbox-face + 'mouse-face 'markdown-highlight-face + 'action #'markdown-toggle-gfm-checkbox) + +(defun markdown-toggle-gfm-checkbox (button) + "Toggle GFM checkbox BUTTON on click." + (save-match-data + (save-excursion + (goto-char (button-start button)) + (cond ((looking-at "\\[ \\]") + (replace-match "[x]" nil t)) + ((looking-at "\\[[xX]\\]") + (replace-match "[ ]" nil t)))))) + +(defun markdown-make-gfm-checkboxes-buttons (start end) + "Make GFM checkboxes buttons in region between START and END." + (save-excursion + (goto-char start) + (let ((case-fold-search t)) + (save-excursion + (while (re-search-forward markdown-regex-gfm-checkbox end t) + (make-button (match-beginning 1) (match-end 1) + :type 'markdown-gfm-checkbox-button)))))) + +;; Called when any modification is made to buffer text. +(defun markdown-gfm-checkbox-after-change-function (beg end _) + "Add to `after-change-functions' to setup GFM checkboxes as buttons. +BEG and END are the limits of scanned region." + (save-excursion + (save-match-data + ;; Rescan between start of line from `beg' and start of line after `end'. + (markdown-make-gfm-checkboxes-buttons + (progn (goto-char beg) (beginning-of-line) (point)) + (progn (goto-char end) (forward-line 1) (point)))))) + + +;;; Display inline image ================================================= + +(defvar markdown-inline-image-overlays nil) +(make-variable-buffer-local 'markdown-inline-image-overlays) + +(defun markdown-remove-inline-images () + "Remove inline image overlays from image links in the buffer. +This can be toggled with `markdown-toggle-inline-images' +or \\[markdown-toggle-inline-images]." + (interactive) + (mapc 'delete-overlay markdown-inline-image-overlays) + (setq markdown-inline-image-overlays nil)) + +(defun markdown-display-inline-images () + "Add inline image overlays to image links in the buffer. +This can be toggled with `markdown-toggle-inline-images' +or \\[markdown-toggle-inline-images]." + (interactive) + (unless (display-graphic-p) + (error "Cannot show images")) + (save-excursion + (save-restriction + (widen) + (goto-char (point-min)) + (while (re-search-forward markdown-regex-link-inline nil t) + (let ((start (match-beginning 0)) + (end (match-end 0)) + (file (match-string-no-properties 6))) + (when (file-exists-p file) + (let* ((abspath (if (file-name-absolute-p file) + file + (concat default-directory file))) + (image (create-image abspath))) + (when image + (let ((ov (make-overlay start end))) + (overlay-put ov 'display image) + (overlay-put ov 'face 'default) + (push ov markdown-inline-image-overlays)))))))))) + +(defun markdown-toggle-inline-images () + "Toggle inline image overlays in the buffer." + (interactive) + (if markdown-inline-image-overlays + (markdown-remove-inline-images) + (markdown-display-inline-images))) + + +;;; Mode Definition ========================================================== + +(defun markdown-show-version () + "Show the version number in the minibuffer." + (interactive) + (message "markdown-mode, version %s" markdown-mode-version)) + +;;;###autoload +(define-derived-mode markdown-mode text-mode "Markdown" + "Major mode for editing Markdown files." + ;; Natural Markdown tab width + (setq tab-width 4) + ;; Comments + (make-local-variable 'comment-start) + (setq comment-start "") + (make-local-variable 'comment-start-skip) + (setq comment-start-skip "" + (markdown-test-range-has-face (point-min) (1- (point-max)) markdown-pre-face))) + +(ert-deftest test-markdown-font-lock/inline-code-comment () + "Test comments inside of a pre block." + (markdown-test-string "`

`" + (markdown-test-range-has-face (1+ (point-min)) (- (point-max) 2) markdown-inline-code-face))) + +(ert-deftest test-markdown-font-lock/comment-hanging-indent () + "Test comments with hanging indentation." + (markdown-test-string "" + (markdown-test-range-has-face (point-min) (1- (point-max)) markdown-comment-face))) + +(ert-deftest test-markdown-font-lock/comment-multiple () + "Test multiple single-line comments in arow." + (markdown-test-string "\n" + (markdown-test-range-has-face + (point-at-bol) (1- (point-at-eol)) markdown-comment-face) + (forward-line) + (markdown-test-range-has-face + (point-at-bol) (1- (point-at-eol)) markdown-comment-face))) + +(ert-deftest test-markdown-font-lock/comment-list-items () + "Test comment with list inside." + (markdown-test-string + "" + (markdown-test-range-face-equals (point-min) (1- (point-max)) + markdown-comment-face))) + +(ert-deftest test-markdown-font-lock/comment-angle-bracket () + "Regression test for GH-117." + (markdown-test-string "" + (markdown-test-range-face-equals (point-min) (1- (point-max)) + markdown-comment-face))) + +(ert-deftest test-markdown-font-lock/footnote-markers-links () + "Test an edge case involving footnote markers and inline reference links." + (markdown-test-string "Harvard[^1] [tuition][]" + (markdown-test-range-has-face 1 7 nil) + (markdown-test-range-has-face 8 8 markdown-markup-face) + (markdown-test-range-has-face 10 10 markdown-footnote-face) + (markdown-test-range-has-face 11 11 markdown-markup-face) + (markdown-test-range-has-face 12 12 nil) + (markdown-test-range-has-face 13 13 markdown-markup-face) + (markdown-test-range-has-face 14 20 markdown-link-face) + (markdown-test-range-has-face 21 21 markdown-markup-face) + (markdown-test-range-has-face 22 23 markdown-markup-face))) + +(ert-deftest test-markdown-font-lock/mmd-metadata () + "Basic MultMarkdown metadata tests." + (markdown-test-string "Title: peg-multimarkdown User's Guide +Author: Fletcher T. Penney +Base Header Level: 2" + (markdown-test-range-has-face 1 5 markdown-metadata-key-face) + (markdown-test-range-has-face 6 6 markdown-markup-face) + (markdown-test-range-has-face 8 37 markdown-metadata-value-face) + (markdown-test-range-has-face 39 44 markdown-metadata-key-face) + (markdown-test-range-has-face 46 46 markdown-markup-face) + (markdown-test-range-has-face 47 64 markdown-metadata-value-face) + (markdown-test-range-has-face 66 82 markdown-metadata-key-face) + (markdown-test-range-has-face 83 83 markdown-markup-face) + (markdown-test-range-has-face 85 85 markdown-metadata-value-face)) + ;; Avoid triggering when a title contains a colon (e.g., Markdown: Syntax) + (markdown-test-file "syntax.text" + (markdown-test-range-has-face 1 16 markdown-header-face-1))) + +(ert-deftest test-markdown-font-lock/mmd-metadata-after-header () + "Ensure that similar lines are not matched after the header." + (markdown-test-string "Title: peg-multimarkdown User's Guide + +Author: Fletcher T. Penney +Base Header Level: 2" + (markdown-test-range-has-face 1 5 markdown-metadata-key-face) + (markdown-test-range-has-face 6 6 markdown-markup-face) + (markdown-test-range-has-face 8 37 markdown-metadata-value-face) + (markdown-test-range-has-face 40 65 nil) + (markdown-test-range-has-face 67 86 nil))) + +(ert-deftest test-markdown-font-lock/pandoc-metadata () + "Basic Pandoc metadata tests." + (markdown-test-string "% title + two-line title +% first author; + second author +% date + +body" + (markdown-test-range-has-face 1 1 markdown-markup-face) + (markdown-test-range-has-face 3 24 markdown-metadata-value-face) + (markdown-test-range-has-face 26 26 markdown-markup-face) + (markdown-test-range-has-face 28 56 markdown-metadata-value-face) + (markdown-test-range-has-face 58 58 markdown-markup-face) + (markdown-test-range-has-face 60 63 markdown-metadata-value-face) + (markdown-test-range-has-face 64 69 nil))) + +(ert-deftest test-markdown-font-lock/yaml-metadata () + "Basic YAML metadata tests." + (markdown-test-string + "--- +layout: post +date: 2015-08-13 11:35:25 EST +--- +" + (markdown-test-range-has-face 1 3 markdown-markup-face) + (markdown-test-range-has-face 5 10 markdown-metadata-key-face) + (markdown-test-range-has-face 11 11 markdown-markup-face) + (markdown-test-range-has-face 13 16 markdown-metadata-value-face) + (markdown-test-range-has-face 18 21 markdown-metadata-key-face) + (markdown-test-range-has-face 22 22 markdown-markup-face) + (markdown-test-range-has-face 24 46 markdown-metadata-value-face) + (markdown-test-range-has-face 48 50 markdown-markup-face))) + +(ert-deftest test-markdown-font-lock/toml-metadata () + "Basic TOML metadata tests." + (markdown-test-string + "--- +layout = post +date = 2015-08-13 11:35:25 EST +--- +" + (markdown-test-range-has-face 1 3 markdown-markup-face) + (markdown-test-range-has-face 5 10 markdown-metadata-key-face) + (markdown-test-range-has-face 12 12 markdown-markup-face) + (markdown-test-range-has-face 14 17 markdown-metadata-value-face) + (markdown-test-range-has-face 19 22 markdown-metadata-key-face) + (markdown-test-range-has-face 24 24 markdown-markup-face) + (markdown-test-range-has-face 26 48 markdown-metadata-value-face) + (markdown-test-range-has-face 50 52 markdown-markup-face))) + +(ert-deftest test-markdown-font-lock/pandoc-yaml-metadata () + "Basic yaml metadata tests, with pandoc syntax." + (let ((markdown-use-pandoc-style-yaml-metadata t)) + (markdown-test-string + "some text + +--- +layout: post +date: 2015-08-13 11:35:25 EST +... + +more text + +--- +layout: post +date: 2015-08-13 11:35:25 EST +--- + +But this is merely a code block + +``` +--- +layout: post +date: 2015-08-13 11:35:25 EST +--- +``` +" + ;; first section + (markdown-test-range-has-face 12 14 markdown-markup-face) + (markdown-test-range-has-face 16 21 markdown-metadata-key-face) + (markdown-test-range-has-face 22 22 markdown-markup-face) + (markdown-test-range-has-face 24 27 markdown-metadata-value-face) + (markdown-test-range-has-face 29 32 markdown-metadata-key-face) + (markdown-test-range-has-face 33 33 markdown-markup-face) + (markdown-test-range-has-face 35 57 markdown-metadata-value-face) + (markdown-test-range-has-face 59 61 markdown-markup-face) + ;; second section + (markdown-test-range-has-face 75 77 markdown-markup-face) + (markdown-test-range-has-face 79 84 markdown-metadata-key-face) + (markdown-test-range-has-face 85 85 markdown-markup-face) + (markdown-test-range-has-face 87 90 markdown-metadata-value-face) + (markdown-test-range-has-face 92 95 markdown-metadata-key-face) + (markdown-test-range-has-face 96 96 markdown-markup-face) + (markdown-test-range-has-face 98 120 markdown-metadata-value-face) + (markdown-test-range-has-face 122 124 markdown-markup-face) + ;; third section + (markdown-test-range-has-face 160 162 markdown-markup-face) + (markdown-test-range-has-face 164 213 markdown-pre-face) + (markdown-test-range-has-face 215 217 markdown-markup-face)))) + +(ert-deftest test-markdown-font-lock/line-break () + "Basic line break tests." + (markdown-test-string " \nasdf \n" + (markdown-test-range-has-face 1 9 nil) + (markdown-test-range-has-face 10 11 markdown-line-break-face))) + +(ert-deftest test-markdown-font-lock/blockquote-bold () + "Test font lock for bold inside of a blockquote." + (markdown-test-string + "> **bold**" + (markdown-test-range-has-face 2 10 markdown-blockquote-face) + (markdown-test-range-has-face 5 8 markdown-bold-face))) + +(ert-deftest test-markdown-font-lock/blockquote-italic () + "Test font lock for italic inside of a blockquote." + (markdown-test-string + "> *italic*" + (markdown-test-range-has-face 2 10 markdown-blockquote-face) + (markdown-test-range-has-face 4 9 markdown-italic-face))) + +(ert-deftest test-markdown-font-lock/blockquote-link () + "Test font lock for links inside of a blockquote. +This test will fail until font lock for inline links inside +blockquotes is implemented (at present, the blockquote face +takes precedence)." + :expected-result :failed + (markdown-test-string + "> [link](url)" + (markdown-test-range-has-face 1 13 markdown-blockquote-face) + (markdown-test-range-has-face 3 8 markdown-link-face) + (markdown-test-range-has-face 9 13 markdown-url-face))) + +(ert-deftest test-markdown-font-lock/blockquote-comment () + "Test font lock for comments inside of a blockquote." + (markdown-test-string + "> " + (markdown-test-range-has-face 1 1 markdown-markup-face) + (markdown-test-range-has-face 3 18 markdown-comment-face))) + +(ert-deftest test-markdown-font-lock/pre-override () + "Test that font lock for pre blocks overrides everything else." + (markdown-test-string + " **bold** + _italic_ + + [link](url) + * list" + (markdown-test-range-has-face 1 73 markdown-pre-face))) + +(ert-deftest test-markdown-font-lock/gfm-code-block-font-lock () + "GFM code block font lock test. Now in base markdown-mode as well!" + (markdown-test-file "gfm.text" + (markdown-test-range-has-face 2639 2641 markdown-markup-face) ; ``` + (markdown-test-range-has-face 2642 2645 markdown-language-keyword-face) ; lang + (markdown-test-range-has-face 2647 2728 markdown-pre-face) ; code + (markdown-test-range-has-face 2730 2732 markdown-markup-face))) ; ``` + +(ert-deftest test-markdown-font-lock/reference-definition () + "Reference definitions should not include ]." + (markdown-test-string "[1]: http://daringfireball.net/ \"title\"" + (markdown-test-range-has-face 2 2 markdown-reference-face) ; 1 + (markdown-test-range-has-face 6 31 markdown-url-face) ; URL + (markdown-test-range-has-face 34 38 markdown-link-title-face)) ; title + (markdown-test-string "[foo][1] and [bar][2]: not a reference definition" + (markdown-test-range-has-face 2 4 markdown-link-face) ; foo + (markdown-test-range-has-face 7 7 markdown-reference-face) ; 1 + (markdown-test-range-has-face 9 13 nil) ; [ ]and[ ] + (markdown-test-range-has-face 15 17 markdown-link-face) ; bar + (markdown-test-range-has-face 20 20 markdown-reference-face) ; 2 + (markdown-test-range-has-face 22 49 nil))) ; [ ]and[ ] + +;;; Markdown Parsing Functions: + +(ert-deftest test-markdown-parsing/extend-region-function () + "Test `markdown-syntax-propertize-extend-region'. +Should return a cons (NEW-START . NEW-END) or nil if no +adjustment should be made. Function is called repeatedly until it +returns nil." + (markdown-test-file + "inline.text" + (should (equal (markdown-syntax-propertize-extend-region 1 17) + (cons 1 91))) + (should (equal (markdown-syntax-propertize-extend-region 2 17) + (cons 1 91))) + (should (equal (markdown-syntax-propertize-extend-region 1 91) + nil)) + (should (equal (markdown-syntax-propertize-extend-region 93 157) + nil)) + (should (equal (markdown-syntax-propertize-extend-region 496 502) + (cons 486 510))) + (should (equal (markdown-syntax-propertize-extend-region 486 510) + nil)) + ;; Region that begins and ends with \n\n should not be extended + (should (equal (markdown-syntax-propertize-extend-region 157 355) + nil)))) + +(defun markdown-test-check-match-limits (prop num begin end &optional pos) + (let* ((posn (or pos (point))) + (props (get-text-property posn prop))) + (save-match-data + (set-match-data props) + (and (match-beginning num) (match-end num) + (= (match-beginning num) begin) + (= (match-end num) end))))) + +(ert-deftest test-markdown-parsing/syntax-with-adjacent-code-blocks () + "Test `markdown-syntax-propertize-fenced-code-blocks' with adjacent blocks." + (markdown-test-string + "~~~ shell +#!/bin/sh + +echo \"Hello, world!\" +~~~ + +~~~ shell +#!/bin/sh + +echo \"Hello, world v2!\" +~~~ +" + (let ((start-top-1 (make-marker)) (end-top-1 (make-marker)) + (start-lang-1 (make-marker)) (end-lang-1 (make-marker)) + (start-mid-1 (make-marker)) (end-mid-1 (make-marker)) + (start-bottom-1 (make-marker)) (end-bottom-1 (make-marker)) + (between (make-marker)) + (start-top-2 (make-marker)) (end-top-2 (make-marker)) + (start-lang-2 (make-marker)) (end-lang-2 (make-marker)) + (start-mid-2 (make-marker)) (end-mid-2 (make-marker)) + (start-bottom-2 (make-marker)) (end-bottom-2 (make-marker))) + ;; First code block + (set-marker start-top-1 1) + (set-marker end-top-1 4) + (set-marker start-lang-1 5) + (set-marker end-lang-1 10) + (set-marker start-mid-1 11) + (set-marker end-mid-1 43) + (set-marker start-bottom-1 43) + (set-marker end-bottom-1 46) + ;; check top tildes + (should (markdown-test-check-match-limits + 'markdown-tilde-fence-begin 1 (marker-position start-top-1) + (marker-position end-top-1) (marker-position start-top-1))) + ;; check top language specifier + (should (markdown-test-check-match-limits + 'markdown-tilde-fence-begin 2 (marker-position start-lang-1) + (marker-position end-lang-1) (marker-position start-lang-1))) + ;; check text in between + (should (markdown-test-check-match-limits + 'markdown-fenced-code 0 (marker-position start-mid-1) + (marker-position end-mid-1) (marker-position start-mid-1))) + ;; check bottom tildes + (should (markdown-test-check-match-limits + 'markdown-tilde-fence-end 1 (marker-position start-bottom-1) + (marker-position end-bottom-1) (marker-position start-bottom-1))) + ;; Point between code blocks + (set-marker between 47) + (should (equal (get-text-property between 'markdown-fenced-code) + nil)) + ;; Second code block + (set-marker start-top-2 48) + (set-marker end-top-2 51) + (set-marker start-lang-2 52) + (set-marker end-lang-2 57) + (set-marker start-mid-2 58) + (set-marker end-mid-2 93) + (set-marker start-bottom-2 93) + (set-marker end-bottom-2 96) + (should (markdown-test-check-match-limits + 'markdown-tilde-fence-begin 1 (marker-position start-top-2) + (marker-position end-top-2) (marker-position start-top-2))) + (should (markdown-test-check-match-limits + 'markdown-tilde-fence-begin 2 (marker-position start-lang-2) + (marker-position end-lang-2) (marker-position start-lang-2))) + (should (markdown-test-check-match-limits + 'markdown-fenced-code 0 (marker-position start-mid-2) + (marker-position end-mid-2) (marker-position start-mid-2))) + (should (markdown-test-check-match-limits + 'markdown-tilde-fence-end 1 (marker-position start-bottom-2) + (marker-position end-bottom-2) (marker-position start-bottom-2))) + ;; ;; Move point between code blocks and insert a character + (goto-char between) + (insert "x") + ;; Re-propertize region after change + (let ((range (markdown-syntax-propertize-extend-region (1- between) (point-max)))) + (markdown-syntax-propertize (car range) (cdr range))) + ;; Re-check first code block + (should (markdown-test-check-match-limits + 'markdown-tilde-fence-begin 1 (marker-position start-top-1) + (marker-position end-top-1) (marker-position start-top-1))) + (should (markdown-test-check-match-limits + 'markdown-tilde-fence-begin 2 (marker-position start-lang-1) + (marker-position end-lang-1) (marker-position start-lang-1))) + (should (markdown-test-check-match-limits + 'markdown-fenced-code 0 (marker-position start-mid-1) + (marker-position end-mid-1) (marker-position start-mid-1))) + (should (markdown-test-check-match-limits + 'markdown-tilde-fence-end 1 (marker-position start-bottom-1) + (marker-position end-bottom-1) (marker-position start-bottom-1))) + ;; Re-check point between code blocks + (should (equal (get-text-property between 'markdown-fenced-code) + nil)) + ;; Re-check second code block + (should (markdown-test-check-match-limits + 'markdown-tilde-fence-begin 1 (marker-position start-top-2) + (marker-position end-top-2) (marker-position start-top-2))) + (should (markdown-test-check-match-limits + 'markdown-tilde-fence-begin 2 (marker-position start-lang-2) + (marker-position end-lang-2) (marker-position start-lang-2))) + (should (markdown-test-check-match-limits + 'markdown-fenced-code 0 (marker-position start-mid-2) + (marker-position end-mid-2) (marker-position start-mid-2))) + (should (markdown-test-check-match-limits + 'markdown-tilde-fence-end 1 (marker-position start-bottom-2) + (marker-position end-bottom-2) + (marker-position start-bottom-2)))))) + +(ert-deftest test-markdown-parsing/propertize-fenced-in-between () + "Test whether `markdown-syntax-propertize-fenced-block-constructs' handles the +case when it can't propertize both the start and end of a fenced block within a +single pass (the end of the block is past the END argument)." + (markdown-test-string + "~~~ shell +#!/bin/sh + +echo \"Hello, world!\" +~~~ +" + (set-text-properties (point-min) (point-max) nil) + ;; syntax-propertize up to right after hashbang + (markdown-syntax-propertize-fenced-block-constructs (point-min) 21) + ;; ~~~ shell should be propertized, but nothing else + ;; check tildes + (should (markdown-test-check-match-limits + 'markdown-tilde-fence-begin 1 1 4 1)) + ;; check language + (should (markdown-test-check-match-limits + 'markdown-tilde-fence-begin 2 5 10 5)) + ;; middle should not be propertized + (should-not (get-text-property 11 'markdown-fenced-code)) + ;; neither should end + (should-not (get-text-property 43 'markdown-tilde-fence-end)) + (markdown-syntax-propertize-fenced-block-constructs 21 (point-max)) + ;; everything should be propertized now + ;; re-check top + (should (markdown-test-check-match-limits + 'markdown-tilde-fence-begin 1 1 4 1)) + (should (markdown-test-check-match-limits + 'markdown-tilde-fence-begin 2 5 10 5)) + ;; check middle + (should (markdown-test-check-match-limits 'markdown-fenced-code 0 10 43 10)) + ;; check ending tildes + (should (markdown-test-check-match-limits + 'markdown-tilde-fence-end 1 43 46 43)))) + +(ert-deftest test-markdown-parsing/get-code-block-at-pos () + "Test whether `markdown-code-block-at-pos' works in all situations. All + situations are: +1. pre block +2. tilde block +3. gfm block +4. yaml metadata block" + (let ((markdown-use-pandoc-style-yaml-metadata t)) + (markdown-test-string + " +~~~ ruby +some_ruby_fun() +~~~ + +--- +a: b +--- + +``` {.bash} +#!/bin/sh +echo hey +``` + + pre code + random stuff + more preformatted code + +--- +data: pandoc +... +" + ;; start/mid/end at tilde block + (should (equal (markdown-code-block-at-pos 2) (list 2 30))) + (should (equal (markdown-code-block-at-pos 11) (list 2 30))) + (should (equal (markdown-code-block-at-pos 27) (list 2 30))) + ;; yaml metadata block + (should (equal (markdown-code-block-at-pos 32) (list 32 44))) + (should (equal (markdown-code-block-at-pos 36) (list 32 44))) + (should (equal (markdown-code-block-at-pos 41) (list 32 44))) + ;; gfm block + (should (equal (markdown-code-block-at-pos 46) (list 46 80))) + (should (equal (markdown-code-block-at-pos 58) (list 46 80))) + (should (equal (markdown-code-block-at-pos 77) (list 46 80))) + ;; pre block + (should (equal (markdown-code-block-at-pos 82) (list 82 138))) + (should (equal (markdown-code-block-at-pos 99) (list 82 138))) + (should (equal (markdown-code-block-at-pos 137) (list 82 138))) + ;; pandoc yaml metadata block (should work if yaml above works) + (should (equal (markdown-code-block-at-pos 140) (list 140 160))) + (should (equal (markdown-code-block-at-pos 142) (list 140 160))) + (should (equal (markdown-code-block-at-pos 144) (list 140 160))) + (should (equal (markdown-code-block-at-pos 157) (list 140 160))) + (should (equal (markdown-code-block-at-pos 159) (list 140 160)))))) + +(ert-deftest test-markdown-parsing/syntax-get-fenced-blocks () + "Test whether *-get-fenced-block-* functions work in the case where a block is +only partially propertized." + (save-match-data + (markdown-test-string + "~~~ +" + (should (equal (markdown-syntax-propertize-extend-region + (point-min) (point-max)) + nil)) + (goto-char 1) + (set-match-data (markdown-text-property-at-point + 'markdown-tilde-fence-begin)) + (should (equal (markdown-get-fenced-block-from-start + 'markdown-tilde-fence-begin) + nil))) + (markdown-test-string + "~~~ +~~~" + (goto-char 1) + (set-match-data (markdown-text-property-at-point + 'markdown-tilde-fence-begin)) + (should (equal (markdown-get-fenced-block-from-start + 'markdown-tilde-fence-begin) + (list 1 8))) + (should (equal (markdown-code-block-at-point) (list 1 8))) + (goto-char 5) + (set-match-data (markdown-text-property-at-point + 'markdown-tilde-fence-end)) + (should (equal (markdown-get-fenced-block-from-end + 'markdown-tilde-fence-end) + (list 1 8))) + (should (equal (markdown-code-block-at-point) (list 1 8)))) + (markdown-test-string + "~~~ + +~~~" + (goto-char 1) + (set-match-data (markdown-text-property-at-point + 'markdown-tilde-fence-begin)) + (should (equal (markdown-get-fenced-block-from-start + 'markdown-tilde-fence-begin) + (list 1 9))) + (should (equal (markdown-code-block-at-point) (list 1 9))) + (goto-char 5) + (set-match-data (markdown-text-property-at-point 'markdown-fenced-code)) + (should (equal (markdown-get-fenced-block-from-middle + 'markdown-fenced-code) + (list 1 9))) + (should (equal (markdown-code-block-at-point) (list 1 9))) + (goto-char 6) + (set-match-data (markdown-text-property-at-point + 'markdown-tilde-fence-end)) + (should (equal (markdown-get-fenced-block-from-end + 'markdown-tilde-fence-end) + (list 1 9))) + (should (equal (markdown-code-block-at-point) (list 1 9)))))) + +(ert-deftest test-markdown-parsing/reference-definition-basic () + "Test reference definition function." + (markdown-test-file "syntax.text" + ;; Test accuracy of returned text and bounds + (should (equal (markdown-reference-definition "1") + (list "http://docutils.sourceforge.net/mirror/setext.html" 1942 1992))) + (should (equal (markdown-reference-definition "2") + (list "http://www.aaronsw.com/2002/atx/" 2000 2032))) + ;; Test that match data remains intact + (should (string-equal (match-string 5) "http://www.aaronsw.com/2002/atx/")) + ;; Test anchor-only relative URL + (should (equal (markdown-reference-definition "bq") + (list "#blockquote" 7536 7547))) + ;; Example references that appear in pre blocks in the text + (should (not (markdown-reference-definition ""))) + (should (not (markdown-reference-definition "id"))) + (should (not (markdown-reference-definition "foo"))) + (should (not (markdown-reference-definition "A"))) + (should (not (markdown-reference-definition "Google"))) + ;; Test that we don't pick up other text in square brackets + (should (not (markdown-reference-definition "blockquoting"))) + (should (not (markdown-reference-definition "square brackets"))) + ;; Test case insensitivity + (should (equal (markdown-reference-definition "SRC") + (list "/projects/markdown/syntax.text" 1245 1275))))) + +(ert-deftest test-markdown-parsing/get-defined-references () + "Test `markdown-get-defined-references'." + (markdown-test-file "syntax.text" + (should (equal (markdown-get-defined-references) + '("src" "1" "2" "3" "4" "5" "6" "bq" "l")))) + (markdown-test-file "outline.text" + (should (equal (markdown-get-defined-references) nil))) + (markdown-test-file "wiki-links.text" + (should (equal (markdown-get-defined-references) nil)))) + +(defun markdown-test-test-region (beg end) + (goto-char (1- beg)) + (should-not (markdown-code-at-point-p)) + (goto-char (1+ end)) + (should-not (markdown-code-at-point-p)) + (dolist (loc (number-sequence beg end)) + (goto-char loc) + (should (markdown-code-at-point-p)) + (should (equal (match-beginning 0) beg)) + (should (equal (match-end 0) end)))) + +(ert-deftest test-markdown-parsing/code-at-point-inline () + "Test `markdown-code-at-point-p'." + (markdown-test-file "inline.text" + (markdown-test-test-region 45 51) ; Regular code span + (markdown-test-test-region 61 90) ; Code containing backticks + (markdown-test-test-region 228 240) ; Backquotes at beginning + (markdown-test-test-region 341 352) ; Backquotes at end + (markdown-test-test-region 460 469) ; Backslash as final character + (markdown-test-test-region 657 667) ; A code span crossing lines + (markdown-test-test-region 749 758) ; Three backquotes on same line + (markdown-test-test-region 806 815) ; Three backquotes across lines + )) + +(ert-deftest test-markdown-parsing/code-at-point-one-space () + "Test `markdown-code-at-point-p' with multiple code spans in a row." + (markdown-test-string "`foo` `bar` `baz`" + (dolist (loc (number-sequence 1 6)) + (goto-char loc) + (should (markdown-code-at-point-p)) + (should (equal (match-data) (list 1 6 1 2 2 5 5 6)))) + (dolist (loc (number-sequence 7 12)) + (goto-char loc) + (should (markdown-code-at-point-p)) + (should (equal (match-data) (list 7 12 7 8 8 11 11 12)))) + (dolist (loc (number-sequence 13 18)) + (goto-char loc) + (should (markdown-code-at-point-p)) + (should (equal (match-data) (list 13 18 13 14 14 17 17 18)))))) + +(ert-deftest test-markdown-parsing/code-at-point-no-space () + "Test `markdown-code-at-point-p' with multiple code spans in a row." + (markdown-test-string "a`foo`b`bar`c`baz`d" + (goto-char 1) ; "a" + (should-not (markdown-code-at-point-p)) + (dolist (loc (number-sequence 2 7)) ; "`foo`b" + (goto-char loc) + (should (markdown-code-at-point-p)) + (should (equal (match-data) (list 2 7 2 3 3 6 6 7)))) + (dolist (loc (number-sequence 8 13)) ; "`bar`c" + (goto-char loc) + (should (markdown-code-at-point-p)) + (should (equal (match-data) (list 8 13 8 9 9 12 12 13)))) + (dolist (loc (number-sequence 14 19)) ; "`baz`d" + (goto-char loc) + (should (markdown-code-at-point-p)) + (should (equal (match-data) (list 14 19 14 15 15 18 18 19)))))) + +(ert-deftest test-markdown-parsing/code-at-point-blank-line () + "Test `markdown-code-at-point-p' at beginning of block." + (markdown-test-string "----------\n\n## foo\n" + (should-not (markdown-code-at-point-p)) + (forward-line) + (should-not (markdown-code-at-point-p)) + (forward-line) + (should-not (markdown-code-at-point-p)))) + +(ert-deftest test-markdown-parsing/match-comments () + "Test `markdown-match-comments'." + (markdown-test-string + "HTML comment" + (should (markdown-match-comments (point-max))) + (should (eq (point) 18)) + (should (equal (match-data) (list 6 18))) + (should-not (markdown-match-comments (point-max))))) + +(ert-deftest test-markdown-parsing/range-property-any () + "Test behavior of `markdown-range-property-any'." + (markdown-test-file + "inline.text" + (should (markdown-range-property-any + (point-min) (point-at-eol) + 'face (list markdown-markup-face + markdown-italic-face))) + (should-not (markdown-range-property-any + (point-min) (point-at-eol) + 'face (list markdown-bold-face))))) + +(ert-deftest test-markdown-parsing/inline-code () + "Don't cause infinite loop for inline code just after metadata block +Detail: https://github.com/jrblevin/markdown-mode/issues/115" + (markdown-test-string "--- +x: x +--- +`x` +" + (should (= (markdown-match-code (point-max)) (point-max))))) + +;;; Reference Checking: + +(ert-deftest test-markdown-references/goto-line-button () + "Create and test a goto line button." + (markdown-test-string "line 1\nline 2\n" + ;; Store the temporary buffer with the text + (let ((target (current-buffer))) + ;; Create a new buffer for inserting + (with-temp-buffer + ;; Verify that point is in a different buffer + (should (not (equal (current-buffer) target))) + ;; Insert and press the button + (insert-button "goto line 2" + :type 'markdown-goto-line-button + 'target-buffer target + 'target-line 2) + (should (string-equal (buffer-string) "goto line 2")) + (backward-button 1) + (call-interactively 'push-button) + ;; Verify that point is on line 2 of target buffer + (should (= (line-number-at-pos) 2)) + (should (looking-at "line 2")) + (should (equal (current-buffer) target)))))) + +(ert-deftest test-markdown-references/button-map () + "Verify that button-buffer-map is used for check references buffer." + (markdown-test-string "[undefined][ref]\n" + (let* ((target (buffer-name)) + (check (format "*Undefined references for %s*" target))) + (markdown-check-refs) + (with-current-buffer (get-buffer check) + (should (equal (local-key-binding (kbd "TAB")) 'forward-button)) + (should (equal (local-key-binding (kbd "")) 'backward-button)))))) + +;;; Lists: + +(ert-deftest test-markdown-lists/levels-1 () + "Test list levels function `markdown-calculate-list-levels'." + (markdown-test-file "nested-list.text" + (let ((values '(((1 . 1) . nil) ((2 . 13) . (3)) ((14 . 23) . (7 3)) + ((24 . 26) . (11 7 3))))) + (cl-loop for (range . value) in values + do (goto-char (point-min)) + (forward-line (1- (car range))) + (dotimes (n (- (cdr range) (car range))) + (should (equal (markdown-calculate-list-levels) value)) + (forward-line)))))) + +(ert-deftest test-markdown-lists/levels-2 () + "Test list levels function `markdown-calculate-list-levels'." + (markdown-test-file "syntax.text" + (let ((values '(((1 . 13) . nil) ((14 . 14) . (0)) ((15 . 17) . (4 0)) + ((18 . 18) . (0)) ((19 . 24) . (4 0)) ((25 . 25) . (0)) + ((26 . 29) . (4 0)) ((30 . 30) . (0)) ((31 . 33) . (4 0)) + ((34 . 588) . nil) ((589 . 595) . (0)) ((596 . 814) . nil) + ((815 . 820) . (0)) ((821 . 898) . nil)))) + (cl-loop for (range . value) in values + do (goto-char (point-min)) + (forward-line (1- (car range))) + (dotimes (n (- (cdr range) (car range))) + (should (equal (markdown-calculate-list-levels) value)) + (forward-line)))))) + +(ert-deftest test-markdown-lists/levels-interior () + "Test `markdown-calculate-list-levels' from inside a list item." + (markdown-test-file "nested-list.text" + (goto-char 36) + (should (equal (markdown-calculate-list-levels) (list 3))) + (goto-char 267) + (should (equal (markdown-calculate-list-levels) (list 7 3))) + (goto-char 540) + (should (equal (markdown-calculate-list-levels) (list 11 7 3))))) + +(ert-deftest test-markdown-lists/bounds-1 () + "Test list item bounds function `markdown-cur-list-item-bounds'." + (markdown-test-file "lists.text" + (markdown-test-goto-heading "Case 9") + (forward-line) + (should (eq (point) 3699)) + (markdown-next-list-item 4) + (should (eq (point) 3700)) + (should (equal (markdown-cur-list-item-bounds) + (list 3700 3901 0 4 "- "))) + (markdown-next-list-item 4) + (should (eq (point) 3903)) + (should (equal (markdown-cur-list-item-bounds) + (list 3903 3937 0 4 "* "))))) + +(ert-deftest test-markdown-lists/bounds-2 () + "Function `markdown-cur-list-item-bounds' should return nil outside of list items." + (markdown-test-string "line one\n\n* item\n" + (should (null (markdown-cur-list-item-bounds))) + (forward-line) + (should (null (markdown-cur-list-item-bounds))) + (forward-line) + (should (markdown-cur-list-item-bounds)))) + +(ert-deftest test-markdown-lists/promotion-and-demotion () + "Test function `markdown-promote-list-item'." + (markdown-test-file "nested-list.text" + (forward-line) + (should (looking-at " - List level 1 item 2 + + Second paragraph of item 2 + + Nested pre block in item 2 + Four spaces past the marker + + Another paragraph of item 2")) + (markdown-demote-list-item) + (should (looking-at " - List level 1 item 2 + + Second paragraph of item 2 + + Nested pre block in item 2 + Four spaces past the marker + + Another paragraph of item 2")) + (markdown-promote-list-item) + (should (looking-at " - List level 1 item 2 + + Second paragraph of item 2 + + Nested pre block in item 2 + Four spaces past the marker + + Another paragraph of item 2")) + (goto-char (point-min)) + (forward-line 22) + (should (looking-at " - List level 3 item 1 + + Nested pre block")) + (markdown-demote-list-item) + (should (looking-at " - List level 3 item 1 + + Nested pre block")) + (markdown-promote-list-item) + (should (looking-at " - List level 3 item 1 + + Nested pre block")))) + +(ert-deftest test-markdown-lists/promotion-and-demotion-custom () + "Test custom variable `markdown-list-indent-width'." + (markdown-test-file "nested-list.text" + (forward-line) + (should (looking-at " - List level 1 item 2 + + Second paragraph of item 2 + + Nested pre block in item 2 + Four spaces past the marker + + Another paragraph of item 2")) + (let ((markdown-list-indent-width 2)) + (markdown-demote-list-item)) + (should (looking-at " - List level 1 item 2 + + Second paragraph of item 2 + + Nested pre block in item 2 + Four spaces past the marker + + Another paragraph of item 2")))) + +;;; Outline minor mode tests: + +(ert-deftest test-markdown-outline/navigation () + "Test outline navigation functions." + (markdown-test-file "outline.text" + ;; Navigate to the first visible heading + (markdown-next-visible-heading 1) + (should (eq (point) 19)) + (should (looking-at "^# A top-level header")) + ;; Navigate forward at the same level + (markdown-forward-same-level 1) + (should (eq (point) 377)) + (should (looking-at "^=+$")) + ;; Navigate backward by four visible headings + (markdown-previous-visible-heading 4) + (should (eq (point) 69)) + (should (looking-at "^## A second-level header$")))) + +(ert-deftest test-markdown-outline/navigation-with-code () + "Test outline navigation functions with code blocks." + (markdown-test-file "outline-code.text" + ;; Navigate forward at the same level + (markdown-forward-same-level 1) + (should (eq (point) 159)) + (should (looking-at "^# Level one again")))) + +(ert-deftest test-markdown-outline/visibility-atx () + "Test outline visibility cycling for ATX-style headers." + (markdown-test-file "outline.text" + (let (last-command this-command) + ;; Navigate to the second visible heading + (markdown-next-visible-heading 2) + (should (eq (point) 69)) + (should (looking-at "^## A second-level header$")) + ;; Cycle visibility of this subtree + (setq this-command 'markdown-cycle) + (markdown-cycle) + (setq last-command 'markdown-cycle) + (should (eq (point) 69)) + (should (looking-at "^## A second-level header$")) + ;; Test that the entire subtree is invisible + (markdown-test-range-has-property 93 349 'invisible 'outline) + ;; Cycle visibility of this subtree again + (markdown-cycle) + (should (eq (point) 69)) + (should (looking-at "^## A second-level header$")) + ;; Test that text is visible + (markdown-test-range-has-property 95 121 'invisible nil) + ;; Test that subheadings are visible + (markdown-test-range-has-property 123 141 'invisible nil) + ;; Cycle visibility of this subtree again + (markdown-cycle) + (should (eq (point) 69)) + (should (looking-at "^## A second-level header$")) + ;; Verify that entire subtree is visible + (markdown-test-range-has-property 93 349 'invisible nil)))) + +(ert-deftest test-markdown-outline/visibility-setext () + "Test outline visibility cycling for setext-style headers." + (markdown-test-file "outline.text" + ;; Navigate to the sixth visible heading + (markdown-next-visible-heading 7) + (markdown-previous-visible-heading 1) + (should (looking-at markdown-regex-header)) + (should (string-equal (match-string-no-properties 1) "An underline-style header")) + (should (string-equal (match-string-no-properties 2) "=========================")) + ;; Cycle visibility subtree, test that it's invisible + (markdown-cycle) + (markdown-test-range-has-property 404 515 'invisible 'outline) + ;; Cycle visibility subtree, test that text and headers are visible + (markdown-cycle) + (markdown-test-range-has-property 404 417 'invisible nil) + (markdown-test-range-has-property 420 451 'invisible nil))) + +(ert-deftest test-markdown-outline/visibility-with-code () + "Test outline visibility cycling with code blocks." + (markdown-test-file "outline-code.text" + (let (last-command this-command) + ;; Cycle global visibility to "overview" mode + (setq this-command 'markdown-cycle) + (markdown-cycle t) + (setq last-command 'markdown-cycle) + (should (eq (point) (point-min))) + (should (looking-at "^# Level one")) + ;; Test that the code block is invisible + (markdown-test-range-has-property 83 157 'invisible 'outline) + ;; Check subsequent headings + (outline-next-visible-heading 1) + (should (eq (point) 69)) + (should (looking-at "^## Level two")) + (outline-next-visible-heading 1) + (should (eq (point) 159)) + (should (looking-at "^# Level one again"))))) + +(ert-deftest test-markdown-outline/visibility-with-metadata () + "Test outline visibility cycling with metadata blocks." + (markdown-test-string + "--- +layout = post +date = 2015-08-13 11:35:25 EST +--- +" + (let (last-command this-command) + ;; Cycle global visibility to "overview" mode + (setq this-command 'markdown-cycle) + (markdown-cycle t) + ;; Check that text is visible + (markdown-test-range-has-property (point-min) (point-max) 'invisible nil)))) + +;;; Movement tests: + +(ert-deftest test-markdown-movement/defun () + "Test defun navigation." + (markdown-test-file "outline.text" + ;; end-of-defun should go to point-max + (end-of-defun 10) + (should (= (point) (point-max))) + ;; end-of-defun should stop just before the next header + (goto-char (point-min)) + (end-of-defun) + (should (looking-at "\n# A top-level header")) + (end-of-defun) + (should (looking-at "\n## A second-level header")) + (end-of-defun) + (should (looking-at "\n### Third level ###")) + (end-of-defun) + (should (looking-at "\n### Third level number two ###")) + ;; beginning-of-defun should move to the start of the previous header + (beginning-of-defun) + (should (looking-at "### Third level ###")) + (beginning-of-defun) + (should (looking-at "## A second-level header")) + (beginning-of-defun) + (should (looking-at "# A top-level header")) + (beginning-of-defun) + ;; beginning-of-defun should move up to point-min + (should (= (point) (point-min))) + ;; (beginning-of-defun -1) should move to the start of the next header + (forward-line 2) + (beginning-of-defun -1) + (should (looking-at "## A second-level header")) + (beginning-of-defun -1) + (should (looking-at "### Third level ###")) + (beginning-of-defun -1) + (should (looking-at "### Third level number two ###")))) + +(ert-deftest test-markdown-movement/block () + "Test block movement." + (markdown-test-file "outline.text" + (markdown-end-of-block) + (should (looking-at "\n# A top-level header")) + (markdown-end-of-block) + (should (looking-at "\nfollowed by some body text")) + (markdown-end-of-block) + (should (looking-at "\n## A second-level header")) + (markdown-end-of-block) + (should (looking-at "\nfollowed by some body text")) + (markdown-end-of-block) + (should (looking-at "\n### Third level ###")) + (markdown-end-of-block) + (should (looking-at "\n\\* A list item")) + (markdown-end-of-block) + (should (looking-at "\n### Third level number two ###")) + (markdown-end-of-block) + (should (looking-at "\n### Level two again")) + (markdown-end-of-block) + (should (looking-at "\nfollowed by some body text")) + + (markdown-test-goto-heading "Level two") + (markdown-end-of-block) + (should (looking-at "\nbar")) + (markdown-end-of-block) + (should (= (point) (point-max))) + (markdown-beginning-of-block) + (should (looking-at "bar")) + (markdown-beginning-of-block) + (should (looking-at "## Level two")) + (markdown-beginning-of-block) + (should (looking-at "foo")) + (markdown-beginning-of-block) + (should (looking-at "# Level one")) + (markdown-beginning-of-block) + (should (looking-at "* With")) + (markdown-beginning-of-block) + (should (looking-at "And a level two underline header")) + + (goto-char (point-min)) + (markdown-test-goto-heading "A top-level header") + (beginning-of-line) + (markdown-beginning-of-block) + (should (= (point) (point-min))))) + +(ert-deftest test-markdown-movement/reference-definition () + "Test jumping to reference definitions." + ;; Jumping to explicit reference definition + (markdown-test-string "[a][ref]\n\n[ref]: gopher://localhost/\n" + (markdown-reference-goto-definition) + (should (= (point) 18))) + ;; Jumping to implicit reference definition + (markdown-test-string "[a][]\n\n[a]: ftp://localhost/\n" + (markdown-reference-goto-definition) + (should (= (point) 13))) + ;; Creating non-existent reference definition + (markdown-test-string "[a][]\n" + (markdown-reference-goto-definition) + (should (= (point) 13)) + (should (string-equal (buffer-string) "[a][]\n\n[a]: \n")))) + +(ert-deftest test-markdown-movement/back-to-same-level-over-code-block () + "`markdown-backward-same-level' over code block which contains header +like statement. Detail: https://github.com/jrblevin/markdown-mode/issues/75" + (markdown-test-string " +## Header 2-1 + +## Header 2-2 + +```R +# Header Like Statement +``` + +## Header 2-3 +" + (search-forward "## Header 2-3") + (let ((last-header-pos (point))) + (forward-line -1) + (call-interactively #'markdown-backward-same-level) + (should (looking-at-p "## Header 2-1")) + + (goto-char last-header-pos) + (call-interactively #'markdown-backward-same-level) + (should (looking-at-p "## Header 2-2")) + + (goto-char last-header-pos) + (markdown-backward-same-level 2) + (should (looking-at-p "## Header 2-1")) + + (search-forward "# Header Like Statement") + (call-interactively #'markdown-backward-same-level) + (should (looking-at-p "## Header 2-1"))))) + +;;; Link tests: + +(ert-deftest test-markdown-link/follow () + "Test link following in a browser and in Emacs." + (markdown-test-string "[text](http://path?query=foo#id)" + (let* ((opened-url nil) + (browse-url-browser-function + (lambda (url &rest args) (setq opened-url url)))) + (markdown-follow-thing-at-point nil) + (should (equal opened-url "http://path?query=foo#id")))) + (when (featurep 'url-parse) + (markdown-test-string "[text](path?query=foo#id)" + (markdown-follow-thing-at-point nil) + (should (equal (file-name-nondirectory (buffer-file-name)) "path")) + (kill-buffer)))) + +;;; Wiki link tests: + +(ert-deftest test-markdown-wiki-link/file-local-variables () + "Test enabling wiki links via file-local variables." + (markdown-test-file "wiki-links.text" + (should-not markdown-enable-wiki-links) + (hack-local-variables) + (should markdown-enable-wiki-links))) + +(ert-deftest test-markdown-wiki-link/aliasing () + "Test filename extraction for aliased wiki links." + (let ((markdown-enable-wiki-links t)) + (markdown-test-file "wiki-links.text" + ;; Confirm location of first wiki link + (should (eq (markdown-next-link) 8)) + ;; Confirm location of second wiki link + (should (eq (markdown-next-link) 73)) + ;; Test predicate function + (should (markdown-wiki-link-p)) + ;; Test alias-first filename extraction + (setq markdown-wiki-link-alias-first t) + (should (string-equal (markdown-wiki-link-link) "second")) + ;; Test alias-second filename extraction + (setq markdown-wiki-link-alias-first nil) + (should (string-equal (markdown-wiki-link-link) "first"))))) + +(ert-deftest test-markdown-wiki-link/navigation () + "Test wiki link navigation." + (let ((markdown-enable-wiki-links t)) + (markdown-test-file "wiki-links.text" + ;; Advance to first link + (should (eq (markdown-next-link) 8)) + ;; Advance to second link + (should (eq (markdown-next-link) 73)) + ;; Avance to final link + (should (eq (markdown-next-link) 155)) + ;; Return nil and don't advance point + (should (eq (markdown-next-link) nil)) + (should (eq (point) 155)) + ;; Move back to second link + (should (eq (markdown-previous-link) 73)) + ;; Move back to first link + (should (eq (markdown-previous-link) 8)) + ;; Return nil and don't move point + (should (eq (markdown-previous-link) nil)) + (should (eq (point) 8))))) + +(ert-deftest test-markdown-wiki-link/font-lock () + "Test font lock faces for wiki links." + (markdown-test-temp-file "wiki-links.text" + (let* ((fn (concat (file-name-directory buffer-file-name) + "inline.text")) + (markdown-enable-wiki-links t)) + ;; Create inline.text in the same temp directory, refontify + (write-region "" nil fn nil 1) + (markdown-fontify-buffer-wiki-links) + ;; Confirm location of first wiki link + (should (eq (markdown-next-link) 8)) + ;; First wiki link doesn't have a corresponding file + (markdown-test-range-has-property 8 20 'font-lock-face markdown-missing-link-face) + ;; Second wiki link doesn't have a corresponding file + (should (eq (markdown-next-link) 73)) + (markdown-test-range-has-property 73 88 'font-lock-face markdown-missing-link-face) + ;; Move to third wiki link, and create the missing file + (should (eq (markdown-next-link) 155)) + (should (string-equal (markdown-wiki-link-link) "inline")) + (markdown-test-range-has-property 155 164 'font-lock-face markdown-link-face) + ;; Check wiki links in code blocks + (markdown-test-range-has-face 360 395 markdown-pre-face) + ;; Remove temporary files + (delete-file fn) + ))) + +(ert-deftest test-markdown-wiki-link/kill () + "Simple tests for `markdown-kill-thing-at-point' for wiki links." + (let ((kill-ring nil) + (markdown-enable-wiki-links t) + (tests (list '("[[foo]]" . "foo") + '("[[foo|bar]]" . "bar")))) + (dolist (test tests) + ;; Load test string (the car), move to end of first line, kill + ;; thing at point, and then verify that the kill ring contains cdr. + (markdown-test-string (car test) + (end-of-line) + (call-interactively 'markdown-kill-thing-at-point) + (should (string-equal (current-kill 0) (cdr test))))))) + +;;; Filling tests: + +(ert-deftest test-markdown-filling/blockquote () + "Test filling of blockquotes. +See `adaptive-fill-first-line-regexp'." + (markdown-test-string "> Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua." + (fill-paragraph) + (should (string-equal (buffer-string) "> Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do\n> eiusmod tempor incididunt ut labore et dolore magna aliqua.")))) + +(ert-deftest test-markdown-filling/space-after-list-marker () + "`fill-paragraph' should preserve more than one space after a list marker, +since users may wish to indent their lists more than one space more than the +width of the marker. The examples on the Markdown Syntax page have three +spaces after the list marker for a total indentation of four." + (let ((str "\n\n* List item indented four spaces.\n* Also four spaces.")) + (markdown-test-string str + (forward-line 2) + (fill-paragraph) + (should (string-equal (buffer-string) str))))) + +(ert-deftest test-markdown-filling/multi-line-list-with-more-space () + "`fill-paragraph' should preserve more than one space after a list marker +(see `test-preserve-space-after-list-marker')." + (let ((str "* This list item is continued on\n the next line")) + (markdown-test-string str + ;; The first line is exactly 35 columns + (let ((fill-column 35)) + (fill-paragraph) + (should (string-equal (buffer-string) str)))))) + +(ert-deftest test-markdown-filling/list-item-plus () + "Test filling of list items with plus sign markers. +See `adaptive-fill-regexp'." + (markdown-test-string " + Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua." + (fill-paragraph) + (should (string-equal (buffer-string) " + Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do\n eiusmod tempor incididunt ut labore et dolore magna aliqua.")))) + +(ert-deftest test-markdown-filling/list-item-plus-in-blockquote () + "Test filling of list items with plus sign markers inside blockquote. +See `adaptive-fill-regexp'." + (markdown-test-string "> + Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua." + (fill-paragraph) + (should (string-equal (buffer-string) "> + Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do\n> eiusmod tempor incididunt ut labore et dolore magna aliqua.")))) + +(ert-deftest test-markdown-filling/line-break () + "Test filling of paragraphs with hard line breaks. +See `paragraph-separate'." + (markdown-test-string "Lorem ipsum dolor sit amet, \nconsectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua." + (let ((fill-column 70)) + (fill-paragraph) + (should (string-equal (buffer-string) "Lorem ipsum dolor sit amet, \nconsectetur adipisicing elit, sed do eiusmod tempor incididunt ut\nlabore et dolore magna aliqua."))))) + +(ert-deftest test-markdown-filling/decimal-number-at-beginning () + "Test filling when a number with a decimal appears at the beginning of a line." + (markdown-test-string "The circumference of a circle divided by it's radius is around\n3.14." + (fill-paragraph) + (should (string-equal (buffer-string) "The circumference of a circle divided by it's radius is around 3.14.")))) + +(ert-deftest test-markdown-filling/avoid-unintended-list-item () + "Avoid breaking lines where it would result in an unintended list item." + (markdown-test-string "Lorem ipsum dolor sit 4. amet" + (let ((fill-column 22)) + (fill-paragraph) + (should (string-equal (buffer-string) "Lorem ipsum dolor\nsit 4. amet"))))) + +(ert-deftest test-markdown-filling/no-break-link-reference () + "Shouldn't break line between label and url, or combine two link references." + (let ((str "[label1]: http://long-url.example.com\n[label2]: http://another-long-url.example.com/")) + (markdown-test-string str + (let ((fill-column 15)) ; after end of label, before end of URL + (fill-paragraph) + (should (string-equal (buffer-string) str)))))) + +(ert-deftest test-markdown-filling/no-break-before-list-item () + "There's no point in putting the first item of a list on the next line, +indented the same amount." + :expected-result :failed + (let ((str "* [Link](http://way-too-long.example.com)\n")) + (markdown-test-string str + (auto-fill-mode 1) + (let ((fill-column 10)) + (end-of-line) + (funcall auto-fill-function) + (should (string-equal (buffer-string) str)))))) + +(ert-deftest test-markdown-filling/break-within-list-item () + "This doesn't suppress auto-fill within a multi-word list item." + :expected-result :failed + (markdown-test-string "* [Link](http://example.com/) more text" + (auto-fill-mode 1) + (let ((fill-column 10)) + (end-of-line) + (funcall auto-fill-function) + (should (string-equal + (buffer-string) + "* [Link](http://example.com/)\n more text"))))) + +(ert-deftest test-markdown-filling/preserve-next-line-footnote () + "Footnote block can be after label" + (let ((str "[^label1]:\n Footnote block\n more footnote")) ; six spaces + (markdown-test-string str + (let ((fill-column 20)) ; could fit "footnote" after label, but shouldn't + (fill-paragraph) + (should (string-equal (buffer-string) str)))))) + +(ert-deftest test-markdown-filling/wrap-same-line-footnote () + "Additional lines must be indented one level (four spaces) when wrapped." + (markdown-test-string "[^label]: Long line should be wrapped" + (let ((fill-column 25)) ; wrap before end of "should" + (fill-paragraph) + (should (string-equal (buffer-string) "[^label]: Long line\n should be wrapped"))))) + +(ert-deftest test-markdown-filling/wrap-extra-hanging-indentation () + "Additional lines must be indented one level (four spaces) when wrapped." + (markdown-test-string "[^label]: Long line\n should be wrapped" + (let ((fill-column 25)) ; wrap before end of "should" + (fill-paragraph) + (should (string-equal (buffer-string) "[^label]: Long line\n should be wrapped"))))) + +(ert-deftest test-markdown-filling/full-justification () + "Test paragraph detection with lines with lots of whitespace." + (markdown-test-string "Lorem Ipsum Lorem Ipsum Lorem Ipsum Lorem Ipsum Lorem Ipsum Lorem Dolor Sit Amet Consectetur http://very-long-url.lorem.ipsum.sic.dolor.sit.amet.com" + (setq default-justification 'full) + (fill-paragraph) + (should (string-equal (buffer-string) "Lorem Ipsum Lorem Ipsum Lorem Ipsum Lorem Ipsum Lorem Ipsum Lorem\nDolor Sit Amet Consectetur\nhttp://very-long-url.lorem.ipsum.sic.dolor.sit.amet.com")) + (backward-paragraph) + (forward-paragraph) + (should (= (point) 198)))) + +(ert-deftest test-markdown-filling/list-line () + "Test fill-paragraph for list line. Don't insert bullet automatically. +Detail: https://github.com/jrblevin/markdown-mode/issues/79" + (markdown-test-string "* foo foo *foo* foo foo foo foo foo foo" + (let ((fill-column 10)) + (fill-paragraph) + (fill-paragraph) + (forward-line 2) + (back-to-indentation) + (should-not (looking-at-p "\\*foo")) + (forward-line 1) + (back-to-indentation) + (should-not (looking-at-p "\\*foo"))))) + +(ert-deftest test-markdown-filling/ignore-header () + "# Test fill-paragraph for containing header line paragraph. +https://github.com/jrblevin/markdown-mode/issues/159" + (markdown-test-string "# this is header line +this is not header line +" + (let ((fill-column 10)) + (fill-paragraph) + (should (string= (buffer-substring (point) (line-end-position)) "# this is header line"))))) + +(ert-deftest test-markdown-filling/unclosed-square-bracket () + "Test fill-paragraph following an unclosed square bracket." + (markdown-test-string "```\n[3\n```\n\naaaaaaaaaaaaaaaa bbbbbbbbbbbbbbbb" + (let ((fill-column 20)) + (forward-line 4) + (fill-paragraph) + (should (looking-at "aaaaaaaaaaaaaaaa\nbbbbbbbbbbbbbbbb"))))) + +;;; Export tests: + +(ert-deftest test-markdown-hook/xhtml-standalone () + "Test `markdown-xhtml-standalone-regexp' and `markdown-output-standalone-p'." + (should (string-match markdown-xhtml-standalone-regexp + "")) + (should (string-match markdown-xhtml-standalone-regexp + "")) + (should (string-match markdown-xhtml-standalone-regexp + "")) + (should-not (string-match markdown-xhtml-standalone-regexp + "

title

")) + (should-not (string-match markdown-xhtml-standalone-regexp + "
"))) + +;;; Hook tests: + +(ert-deftest test-markdown-hook/before-export () + "Test hook run before export XHTML." + (markdown-test-temp-file "lists.text" + (let* ((before-hook-run nil) + (orig-point (point)) + (func (lambda () + ;; Change value of a variable + (setq before-hook-run t) + ;; Insert some text + (goto-char (point-min)) + (insert "#") + ;; Deliberately move the point + (end-of-line) + ;; Verify changes + (should (looking-back "^## List Cases" nil)) + (should-not (= (point) orig-point)))) + (ofile (progn + ;; Register hook + (add-hook 'markdown-before-export-hook func) + ;; Export XHTML and return filename + (markdown-export))) + (obuffer (get-file-buffer ofile))) + ;; Test side effects of hook + (should (eq before-hook-run t)) + ;; Test position of point + (should (= (point) orig-point)) + ;; Test that buffer was restored to original state + (goto-char (point-min)) + (should (looking-at "^# List Cases")) + ;; Clean + (remove-hook 'markdown-before-export-hook func) + (kill-buffer obuffer) + (delete-file ofile)))) + +(ert-deftest test-markdown-hook/after-export () + "Test hook run after export XHTML." + (markdown-test-temp-file "lists.text" + (let* ((after-hook-run nil) + (func (lambda () + ;; Change variable value + (setq after-hook-run t) + ;; Add comment to output buffer + (goto-char (point-min)) + (insert "\n"))) + (ofile (progn + ;; Register hook + (add-hook 'markdown-after-export-hook func) + ;; Export XHTML and return filename + (markdown-export))) + (obuffer (get-file-buffer ofile))) + (message "obuffer = %S" obuffer) + ;; Test that variable was changed + (should (eq after-hook-run t)) + ;; Test that output buffer remains open + (should (get-buffer obuffer)) + ;; Test that output buffer modification remains + (with-current-buffer obuffer + (goto-char (point-min)) + (should (looking-at "\n"))) + ;; Test that buffer modification was saved + (should-not (buffer-modified-p obuffer)) + ;; Clean up + (remove-hook 'markdown-after-export-hook func) + (kill-buffer obuffer) + (delete-file ofile)))) + +;;; Extension: math support + +(ert-deftest test-markdown-math/file-local-variable () + "Test enabling math mode via `hack-local-variables-hook'." + (markdown-test-file "math.text" + (should-not markdown-enable-math) + (hack-local-variables) + (should markdown-enable-math))) + +(ert-deftest test-markdown-math/reload () + "Test enabling math mode via function `markdown-enable-math'." + (markdown-test-file "math.text" + (markdown-toggle-math t) + ;; Flag should be set to t + (should markdown-enable-math) + ;; Font-lock keywords should be updated + (should (member (cons markdown-regex-math-display '((1 markdown-markup-face prepend) + (2 markdown-math-face append) + (3 markdown-markup-face prepend))) + markdown-mode-font-lock-keywords)))) + +(ert-deftest test-markdown-math/font-lock () + "Test markdown math mode." + (markdown-test-file "math.text" + (markdown-toggle-math t) + (funcall markdown-test-font-lock-function) + (markdown-test-range-has-face 1 32 nil) + (markdown-test-range-has-face 33 33 markdown-markup-face) + (markdown-test-range-has-face 34 45 markdown-math-face) + (markdown-test-range-has-face 46 46 markdown-markup-face) + (markdown-test-range-has-face 47 49 nil) + (markdown-test-range-has-face 50 51 markdown-markup-face) + (markdown-test-range-has-face 52 63 markdown-math-face) + (markdown-test-range-has-face 64 65 markdown-markup-face) + (markdown-test-range-has-face 66 98 nil) + (markdown-test-range-has-face 99 100 markdown-markup-face) + (markdown-test-range-has-face 101 112 markdown-math-face) + (markdown-test-range-has-face 113 114 markdown-markup-face) + (markdown-test-range-has-face 113 114 markdown-markup-face) + (markdown-test-range-has-face 117 117 markdown-header-delimiter-face) + (markdown-test-range-has-face 119 152 markdown-header-face-1) + (markdown-test-range-has-face 129 129 markdown-markup-face) + (markdown-test-range-has-face 136 136 markdown-markup-face) + (markdown-test-range-has-face 174 177 markdown-markup-face) + (markdown-test-range-has-face 179 188 markdown-language-keyword-face) + (markdown-test-range-has-face 190 211 markdown-pre-face) + (markdown-test-range-has-face 212 215 markdown-markup-face) + (markdown-test-range-has-face 218 218 markdown-markup-face) + (markdown-test-range-has-face 219 223 markdown-math-face) + (markdown-test-range-has-face 224 224 markdown-markup-face))) + +(ert-deftest test-markdown-math/font-lock-italics () + "Test markdown math mode with underscores." + (markdown-test-file "math.text" + (markdown-toggle-math t) + (funcall markdown-test-font-lock-function) + (markdown-test-range-has-face 227 227 markdown-markup-face) + (markdown-test-range-has-face 228 233 markdown-math-face) + (markdown-test-range-has-face 234 234 markdown-markup-face) + (markdown-test-range-has-face 235 270 nil) + (markdown-test-range-has-face 271 271 markdown-markup-face) + (markdown-test-range-has-face 272 274 markdown-math-face) + (markdown-test-range-has-face 275 275 markdown-markup-face))) + +;;; gfm-mode tests: + +(ert-deftest test-markdown-gfm/pre-1 () + "GFM pre block font lock test." + (markdown-test-file-gfm "gfm.text" + (markdown-test-range-has-face 2626 2637 nil) + (markdown-test-range-has-face 2639 2641 markdown-markup-face) + (markdown-test-range-has-face 2642 2645 markdown-language-keyword-face) + (markdown-test-range-has-face 2647 2728 markdown-pre-face) + (markdown-test-range-has-face 2730 2732 markdown-markup-face))) + +(ert-deftest test-markdown-gfm/italic-1 () + "GFM italic font lock test." + (markdown-test-file-gfm "gfm.text" + (markdown-test-range-has-face 1483 1483 markdown-markup-face) + (markdown-test-range-has-face 1484 1487 markdown-italic-face) + (markdown-test-range-has-face 1488 1488 markdown-markup-face) + (markdown-test-range-has-face 1729 1790 nil))) + +(ert-deftest test-markdown-gfm/strike-through-1 () + "GFM strike through font lock test." + (markdown-test-string-gfm "one ~~two~~ three" + (markdown-test-range-has-face 1 4 nil) + (markdown-test-range-has-face 5 6 markdown-markup-face) + (markdown-test-range-has-face 7 9 markdown-strike-through-face) + (markdown-test-range-has-face 10 11 markdown-markup-face) + (markdown-test-range-has-face 12 17 nil))) + +(ert-deftest test-markdown-gfm/toggle-strike-through () + "Test toggling functionality of `markdown-insert-strike-through'." + (markdown-test-string-gfm "one ~~two~~ three" + (forward-word 2) + (markdown-insert-strike-through) + (should (string-equal (buffer-string) "one two three")) + (should (= (point) 8)) + (forward-word) + (markdown-insert-strike-through) + (should (= (point) 16)) + (should (string-equal (buffer-string) "one two ~~three~~")))) + +(ert-deftest test-markdown-gfm/insert-code-block () + "GFM code block insertion test." + ;; Test empty markup insertion + (markdown-test-string-gfm "line 1\nline 2\n" + (end-of-line) + (markdown-insert-gfm-code-block "elisp") + (should (equal (car markdown-gfm-used-languages) "elisp")) + (should (equal (car (markdown-gfm-get-corpus)) "elisp")) + (should (string-equal (buffer-string) + "line 1\n\n``` elisp\n\n```\n\nline 2\n"))) + ;; Test with active region + (markdown-test-string-gfm "line 1\nline 2\nline 3\n" + (forward-line) + (transient-mark-mode) + (push-mark (point) t t) + (end-of-line) + (should (markdown-use-region-p)) + (markdown-insert-gfm-code-block "elisp") + (should (string-equal (buffer-string) + "line 1\n\n``` elisp\nline 2\n```\n\nline 3\n")))) + +(ert-deftest test-markdown-gfm/gfm-parse-buffer-for-languages () + "Parse buffer for existing languages for `markdown-gfm-used-languages' test." + (markdown-test-string-gfm "``` MADEUP\n\n```\n``` LANGUAGES\n\n```\n```MaDeUp\n\n```\n```\n\n```\n``` \n\n```\n" + (markdown-gfm-parse-buffer-for-languages) + (should (equal markdown-gfm-used-languages + (list "MaDeUp" "LANGUAGES" "MADEUP"))) + (should (equal (car markdown-gfm-used-languages) "MaDeUp")) + (should (equal (car (markdown-gfm-get-corpus)) "MaDeUp")) + (goto-char (point-max)) + (markdown-insert-gfm-code-block "newlang") + (should (equal markdown-gfm-used-languages + (list "newlang" "MaDeUp" "LANGUAGES" "MADEUP"))) + (should (equal (car markdown-gfm-used-languages) "newlang")) + (should (equal (car (markdown-gfm-get-corpus)) "newlang")) + (let ((markdown-gfm-downcase-languages nil)) + (should + (equal (markdown-gfm-get-corpus) + (append markdown-gfm-used-languages + markdown-gfm-additional-languages + markdown-gfm-recognized-languages)))) + (let ((markdown-gfm-downcase-languages t)) + (should + (equal + (markdown-gfm-get-corpus) + (append markdown-gfm-used-languages + (cl-mapcar #'downcase + (append markdown-gfm-additional-languages + markdown-gfm-recognized-languages)))))))) + +(ert-deftest test-markdown-gfm/code-block-font-lock () + "GFM code block font lock test." + (markdown-test-file-gfm "gfm.text" + (markdown-test-range-has-face 2639 2641 markdown-markup-face) ; ``` + (markdown-test-range-has-face 2642 2645 markdown-language-keyword-face) ; lang + (markdown-test-range-has-face 2647 2728 markdown-pre-face) ; code + (markdown-test-range-has-face 2730 2732 markdown-markup-face))) ; ``` + +(ert-deftest test-markdown-gfm/code-block-font-lock-2 () + "GFM code block font lock test without language identifier." + (markdown-test-string-gfm "Plain code block:\n\n```\nfoo\n```\n" + (markdown-test-range-has-face 20 22 markdown-markup-face) + (markdown-test-range-has-face 24 26 markdown-pre-face) + (markdown-test-range-has-face 28 30 markdown-markup-face))) + +;;; Tests for other extensions: + +(ert-deftest test-markdown-ext/pandoc-fancy-lists () + "Test basic support for font lock and filling of Pandoc 'fancy lists'." + (markdown-test-string " #. abc\ndef\n" + ;; font lock + (markdown-test-range-has-face 1 1 nil) + (markdown-test-range-has-face 2 3 markdown-list-face) + (markdown-test-range-has-face 4 11 nil) + ;; filling + (forward-line) + (markdown-indent-region (line-beginning-position) (line-end-position) nil) + (should (string-equal (buffer-string) " #. abc\n def\n")) + (markdown-indent-region (line-beginning-position) (line-end-position) nil) + (should (string-equal (buffer-string) " #. abc\n def\n")))) + +(ert-deftest test-markdown-ext/ikiwiki () + (let ((markdown-enable-wiki-links t) + (markdown-wiki-link-fontify-missing t) + (markdown-wiki-link-search-parent-directories t)) + (progn + (find-file "ikiwiki/root") + (unwind-protect + (progn + (markdown-mode) + ;; font lock + (markdown-test-range-has-property 1 11 'font-lock-face markdown-link-face) + (markdown-test-range-has-property 14 33 'font-lock-face markdown-missing-link-face)) + (kill-buffer))) + (progn + (find-file "ikiwiki/sub/foo") + (unwind-protect + (progn + (markdown-mode) + ;; font lock + (markdown-test-range-has-property 1 16 'font-lock-face markdown-missing-link-face) + (markdown-test-range-has-property 19 26 'font-lock-face markdown-link-face)) + (kill-buffer))))) + +(defadvice markdown-live-preview-window-eww + (around markdown-test-create-fake-eww disable) + (setq ad-return-value (get-buffer-create "*eww*"))) + +(defmacro markdown-test-fake-eww (&rest body) + `(progn + ,@(if (and (fboundp 'libxml-parse-html-region) (require 'eww nil t)) body + `((ad-enable-advice #'markdown-live-preview-window-eww + 'around 'markdown-test-create-fake-eww) + (ad-activate #'markdown-live-preview-window-eww) + ,@body + (ad-disable-advice #'markdown-live-preview-window-eww + 'around 'markdown-test-create-fake-eww) + (ad-activate #'markdown-live-preview-window-eww))))) + +(defmacro markdown-test-eww-or-nothing (test &rest body) + (if (and (fboundp 'libxml-parse-html-region) (require 'eww nil t) + (executable-find markdown-command)) + `(progn ,@body) + (message "no eww, no libxml2, or no %s found: skipping %s" markdown-command test) + nil)) + +(ert-deftest test-markdown-ext/live-preview-exports () + (markdown-test-temp-file "inline.text" + (unless (and (fboundp 'libxml-parse-html-region) (require 'eww nil t)) + (should-error (markdown-live-preview-mode))) + (markdown-test-fake-eww + (markdown-live-preview-mode) + (should (buffer-live-p markdown-live-preview-buffer)) + (should (eq (current-buffer) + (with-current-buffer markdown-live-preview-buffer + markdown-live-preview-source-buffer))) + (kill-buffer markdown-live-preview-buffer) + (should (null markdown-live-preview-buffer)) + (set-buffer-modified-p t) + (save-buffer) ; should create new export + (should (buffer-live-p markdown-live-preview-buffer))))) + +(ert-deftest test-markdown-ext/live-preview-delete-exports () + (markdown-test-fake-eww + (let ((markdown-live-preview-delete-export 'delete-on-destroy) + file-output) + (markdown-test-temp-file "inline.text" + (markdown-live-preview-mode) + (setq file-output (markdown-export-file-name))) + (should-not (file-exists-p file-output))) + (let ((markdown-live-preview-delete-export 'delete-on-export) + file-output) + (markdown-test-temp-file "inline.text" + (markdown-live-preview-mode) + (setq file-output (markdown-export-file-name)) + (should-not (file-exists-p file-output)))) + (let ((markdown-live-preview-delete-export nil) + file-output) + (unwind-protect + (markdown-test-temp-file "inline.text" + (markdown-live-preview-mode) + (setq file-output (markdown-export-file-name)) + (should (file-exists-p file-output))) + (delete-file file-output))))) + +(ert-deftest test-markdown-ext/live-preview-follow-min-max () + (markdown-test-eww-or-nothing "live-preview-follow-min-max" + (markdown-test-temp-file "inline.text" + (markdown-live-preview-mode) + (should (buffer-live-p markdown-live-preview-buffer)) + (should (window-live-p (get-buffer-window markdown-live-preview-buffer))) + (with-selected-window (get-buffer-window markdown-live-preview-buffer) + (goto-char (point-min))) + (goto-char (point-min)) + (insert "a test ") + (markdown-live-preview-export) + (let (final-pt final-win-st-diff) + ;; test that still starts at point-min + (with-selected-window (get-buffer-window markdown-live-preview-buffer) + (should (= (window-point) 1)) + (should (= (markdown-visual-lines-between-points + (window-start) (window-point)) + 0)) + (set-window-point (selected-window) (point-max)) + (setq final-pt (window-point) + final-win-st-diff (markdown-visual-lines-between-points + (window-start) (window-point)))) + (goto-char (point-min)) + (insert "this is ") + (markdown-live-preview-export) + (with-selected-window (get-buffer-window markdown-live-preview-buffer) + (should (= (window-point) (+ final-pt (length "this is ")))) + (should (= (markdown-visual-lines-between-points + (window-start) (window-point)) + final-win-st-diff)) + ;; test that still starts at point-max, with correct line difference + (goto-char (floor (/ (float (- (point-max) (point-min))) 2))) + (setq final-pt (window-point) + final-win-st-diff (markdown-visual-lines-between-points + (window-start) final-pt))) + (markdown-live-preview-export) + ;; test that still starts at same point, with correct line difference + (with-selected-window (get-buffer-window markdown-live-preview-buffer) + (should (= (window-point) final-pt)) + (should (= (markdown-visual-lines-between-points + (window-start) (window-point)) + final-win-st-diff))))))) + +;; Tests for imenu + +(ert-deftest test-markdown-imenu/metadata () + "Don't correct header like statement in metadata. +https://github.com/jrblevin/markdown-mode/issues/145" + (markdown-test-string "--- +title = \"Blah\" +comments = false +--- + +# Header1 + +## Header2 +" + (let ((headers (mapcar #'car (markdown-imenu-create-flat-index)))) + (should (member "Header1" headers)) + (should (member "Header2" headers)) + (should-not (member "comments = false" headers))))) + +(provide 'markdown-test) + +;;; markdown-test.el ends here diff --git a/tests/math.text b/tests/math.text new file mode 100644 index 0000000..287cea0 --- /dev/null +++ b/tests/math.text @@ -0,0 +1,25 @@ +A simple equation for a line in $\mathbb{R}^2$: + +\[ y = mx + b \] + +Again, but with dollar signs: + +$$ y = mx + b $$ + +# Regarding $\vec g$ and its effects + +Math in a heading + +~~~~ {.haskell} +f x = length $ tail x +~~~~ + +$x + 1$ + +$e_{ik}$ in the statement the theorem about $V_k$ + +$**η = (-1)^{k(n-k)}sη$, where $**η$, is the Hodge star applied twice. + + + + diff --git a/tests/more-lists.text b/tests/more-lists.text new file mode 100644 index 0000000..77e1b2c --- /dev/null +++ b/tests/more-lists.text @@ -0,0 +1,57 @@ +Ordered, without paragraphs: + +1. Foo +2. Bar + +Unordered, with paragraphs: + +* A list item. + + With multiple paragraphs. + +* Bar + +Nested lists: + +* Bubbles + 1. bunk + 2. bupkis + * BELITTLER + 3. burper +* Abacus + * answer +* Cunning + +A complex, multiply-nested list: + + * Unordered stuff + with overhang + + 4. fourth nested + + 2. second nested + + 0. zeroth nested + + * More unordered stuff + + 0. zeroth nested + 2. second nested + 4. fourth nested + +Nested ordered lists: + + 1. first + + 0. zeroth nested + 2. second nested + 4. fourth nested + + 2. second + + 1. first nested + 3. third nested + + 3. third + + 4. fourth diff --git a/tests/nested-list.text b/tests/nested-list.text new file mode 100644 index 0000000..0442049 --- /dev/null +++ b/tests/nested-list.text @@ -0,0 +1,25 @@ + - List level 1 item 1 + - List level 1 item 2 + + Second paragraph of item 2 + + Nested pre block in item 2 + Four spaces past the marker + + Another paragraph of item 2 + + - List level 1 item 3 + + - List level 2 item 1 + Four spaces from higher-level the marker. + More indentation in the same block + does not trigger a pre block! + + Nested pre block + Four spaces past the previous marker + + - List level 2 item 2 + + - List level 3 item 1 + + Nested pre block diff --git a/tests/outline-code.text b/tests/outline-code.text new file mode 100644 index 0000000..188f462 --- /dev/null +++ b/tests/outline-code.text @@ -0,0 +1,17 @@ +# Level one + +* A list item + with some overhang + + and a sublist + +## Level two + +~~~ {.bash} +#!/bin/bash + +# In a code block, this shouldn't be a header +~~~ + +# Level one again + +some body text diff --git a/tests/outline.text b/tests/outline.text new file mode 100644 index 0000000..09108db --- /dev/null +++ b/tests/outline.text @@ -0,0 +1,44 @@ +Pre-header text. + +# A top-level header + +followed by some body text + +## A second-level header + +followed by some body text + +### Third level ### + +* A list item +* and another + with some overhang + + and a sublist + with overhang +- And a third with a different marker + +### Third level number two ### + +### Level two again + +followed by some body text + +An underline-style header +========================= + +With some text + +And a level two underline header +-------------------------------- + +* With +* an unordered +* list + +# Level one + +foo + +## Level two + +bar diff --git a/tests/r-markdown-test.text b/tests/r-markdown-test.text new file mode 100644 index 0000000..6f70ca6 --- /dev/null +++ b/tests/r-markdown-test.text @@ -0,0 +1,30 @@ +% Test example +% Mathieu Basille +% September 11, 2013 + +# First section + +## And a sub-section + +```{r sum} +2+2 +``` + +Some text... + + +## Second sub-section + +Some more text, and the faulty chunk with comments: + +```{r comments} +2+2 +## Wait, still 4? +``` + +And text you will never see again! + + +# Second section + +More text... diff --git a/tests/syntax.text b/tests/syntax.text new file mode 100644 index 0000000..0a5d63d --- /dev/null +++ b/tests/syntax.text @@ -0,0 +1,897 @@ +Markdown: Syntax +================ + + + + +* [Overview](#overview) + * [Philosophy](#philosophy) + * [Inline HTML](#html) + * [Automatic Escaping for Special Characters](#autoescape) +* [Block Elements](#block) + * [Paragraphs and Line Breaks](#p) + * [Headers](#header) + * [Blockquotes](#blockquote) + * [Lists](#list) + * [Code Blocks](#precode) + * [Horizontal Rules](#hr) +* [Span Elements](#span) + * [Links](#link) + * [Emphasis](#em) + * [Code](#code) + * [Images](#img) +* [Miscellaneous](#misc) + * [Backslash Escapes](#backslash) + * [Automatic Links](#autolink) + + +**Note:** This document is itself written using Markdown; you +can [see the source for it by adding '.text' to the URL][src]. + + [src]: /projects/markdown/syntax.text + +* * * + +

Overview

+ +

Philosophy

+ +Markdown is intended to be as easy-to-read and easy-to-write as is feasible. + +Readability, however, is emphasized above all else. A Markdown-formatted +document should be publishable as-is, as plain text, without looking +like it's been marked up with tags or formatting instructions. While +Markdown's syntax has been influenced by several existing text-to-HTML +filters -- including [Setext] [1], [atx] [2], [Textile] [3], [reStructuredText] [4], +[Grutatext] [5], and [EtText] [6] -- the single biggest source of +inspiration for Markdown's syntax is the format of plain text email. + + [1]: http://docutils.sourceforge.net/mirror/setext.html + [2]: http://www.aaronsw.com/2002/atx/ + [3]: http://textism.com/tools/textile/ + [4]: http://docutils.sourceforge.net/rst.html + [5]: http://www.triptico.com/software/grutatxt.html + [6]: http://ettext.taint.org/doc/ + +To this end, Markdown's syntax is comprised entirely of punctuation +characters, which punctuation characters have been carefully chosen so +as to look like what they mean. E.g., asterisks around a word actually +look like \*emphasis\*. Markdown lists look like, well, lists. Even +blockquotes look like quoted passages of text, assuming you've ever +used email. + + + +

Inline HTML

+ +Markdown's syntax is intended for one purpose: to be used as a +format for *writing* for the web. + +Markdown is not a replacement for HTML, or even close to it. Its +syntax is very small, corresponding only to a very small subset of +HTML tags. The idea is *not* to create a syntax that makes it easier +to insert HTML tags. In my opinion, HTML tags are already easy to +insert. The idea for Markdown is to make it easy to read, write, and +edit prose. HTML is a *publishing* format; Markdown is a *writing* +format. Thus, Markdown's formatting syntax only addresses issues that +can be conveyed in plain text. + +For any markup that is not covered by Markdown's syntax, you simply +use HTML itself. There's no need to preface it or delimit it to +indicate that you're switching from Markdown to HTML; you just use +the tags. + +The only restrictions are that block-level HTML elements -- e.g. `
`, +``, `
`, `

`, etc. -- must be separated from surrounding +content by blank lines, and the start and end tags of the block should +not be indented with tabs or spaces. Markdown is smart enough not +to add extra (unwanted) `

` tags around HTML block-level tags. + +For example, to add an HTML table to a Markdown article: + + This is a regular paragraph. + +

+ + + +
Foo
+ + This is another regular paragraph. + +Note that Markdown formatting syntax is not processed within block-level +HTML tags. E.g., you can't use Markdown-style `*emphasis*` inside an +HTML block. + +Span-level HTML tags -- e.g. ``, ``, or `` -- can be +used anywhere in a Markdown paragraph, list item, or header. If you +want, you can even use HTML tags instead of Markdown formatting; e.g. if +you'd prefer to use HTML `` or `` tags instead of Markdown's +link or image syntax, go right ahead. + +Unlike block-level HTML tags, Markdown syntax *is* processed within +span-level tags. + + +

Automatic Escaping for Special Characters

+ +In HTML, there are two characters that demand special treatment: `<` +and `&`. Left angle brackets are used to start tags; ampersands are +used to denote HTML entities. If you want to use them as literal +characters, you must escape them as entities, e.g. `<`, and +`&`. + +Ampersands in particular are bedeviling for web writers. If you want to +write about 'AT&T', you need to write '`AT&T`'. You even need to +escape ampersands within URLs. Thus, if you want to link to: + + http://images.google.com/images?num=30&q=larry+bird + +you need to encode the URL as: + + http://images.google.com/images?num=30&q=larry+bird + +in your anchor tag `href` attribute. Needless to say, this is easy to +forget, and is probably the single most common source of HTML validation +errors in otherwise well-marked-up web sites. + +Markdown allows you to use these characters naturally, taking care of +all the necessary escaping for you. If you use an ampersand as part of +an HTML entity, it remains unchanged; otherwise it will be translated +into `&`. + +So, if you want to include a copyright symbol in your article, you can write: + + © + +and Markdown will leave it alone. But if you write: + + AT&T + +Markdown will translate it to: + + AT&T + +Similarly, because Markdown supports [inline HTML](#html), if you use +angle brackets as delimiters for HTML tags, Markdown will treat them as +such. But if you write: + + 4 < 5 + +Markdown will translate it to: + + 4 < 5 + +However, inside Markdown code spans and blocks, angle brackets and +ampersands are *always* encoded automatically. This makes it easy to use +Markdown to write about HTML code. (As opposed to raw HTML, which is a +terrible format for writing about HTML syntax, because every single `<` +and `&` in your example code needs to be escaped.) + + +* * * + + +

Block Elements

+ + +

Paragraphs and Line Breaks

+ +A paragraph is simply one or more consecutive lines of text, separated +by one or more blank lines. (A blank line is any line that looks like a +blank line -- a line containing nothing but spaces or tabs is considered +blank.) Normal paragraphs should not be indented with spaces or tabs. + +The implication of the "one or more consecutive lines of text" rule is +that Markdown supports "hard-wrapped" text paragraphs. This differs +significantly from most other text-to-HTML formatters (including Movable +Type's "Convert Line Breaks" option) which translate every line break +character in a paragraph into a `
` tag. + +When you *do* want to insert a `
` break tag using Markdown, you +end a line with two or more spaces, then type return. + +Yes, this takes a tad more effort to create a `
`, but a simplistic +"every line break is a `
`" rule wouldn't work for Markdown. +Markdown's email-style [blockquoting][bq] and multi-paragraph [list items][l] +work best -- and look better -- when you format them with hard breaks. + + [bq]: #blockquote + [l]: #list + + + + + +Markdown supports two styles of headers, [Setext] [1] and [atx] [2]. + +Setext-style headers are "underlined" using equal signs (for first-level +headers) and dashes (for second-level headers). For example: + + This is an H1 + ============= + + This is an H2 + ------------- + +Any number of underlining `=`'s or `-`'s will work. + +Atx-style headers use 1-6 hash characters at the start of the line, +corresponding to header levels 1-6. For example: + + # This is an H1 + + ## This is an H2 + + ###### This is an H6 + +Optionally, you may "close" atx-style headers. This is purely +cosmetic -- you can use this if you think it looks better. The +closing hashes don't even need to match the number of hashes +used to open the header. (The number of opening hashes +determines the header level.) : + + # This is an H1 # + + ## This is an H2 ## + + ### This is an H3 ###### + + +

Blockquotes

+ +Markdown uses email-style `>` characters for blockquoting. If you're +familiar with quoting passages of text in an email message, then you +know how to create a blockquote in Markdown. It looks best if you hard +wrap the text and put a `>` before every line: + + > This is a blockquote with two paragraphs. Lorem ipsum dolor sit amet, + > consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus. + > Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus. + > + > Donec sit amet nisl. Aliquam semper ipsum sit amet velit. Suspendisse + > id sem consectetuer libero luctus adipiscing. + +Markdown allows you to be lazy and only put the `>` before the first +line of a hard-wrapped paragraph: + + > This is a blockquote with two paragraphs. Lorem ipsum dolor sit amet, + consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus. + Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus. + + > Donec sit amet nisl. Aliquam semper ipsum sit amet velit. Suspendisse + id sem consectetuer libero luctus adipiscing. + +Blockquotes can be nested (i.e. a blockquote-in-a-blockquote) by +adding additional levels of `>`: + + > This is the first level of quoting. + > + > > This is nested blockquote. + > + > Back to the first level. + +Blockquotes can contain other Markdown elements, including headers, lists, +and code blocks: + + > ## This is a header. + > + > 1. This is the first list item. + > 2. This is the second list item. + > + > Here's some example code: + > + > return shell_exec("echo $input | $markdown_script"); + +Any decent text editor should make email-style quoting easy. For +example, with BBEdit, you can make a selection and choose Increase +Quote Level from the Text menu. + + +

Lists

+ +Markdown supports ordered (numbered) and unordered (bulleted) lists. + +Unordered lists use asterisks, pluses, and hyphens -- interchangably +-- as list markers: + + * Red + * Green + * Blue + +is equivalent to: + + + Red + + Green + + Blue + +and: + + - Red + - Green + - Blue + +Ordered lists use numbers followed by periods: + + 1. Bird + 2. McHale + 3. Parish + +It's important to note that the actual numbers you use to mark the +list have no effect on the HTML output Markdown produces. The HTML +Markdown produces from the above list is: + +
    +
  1. Bird
  2. +
  3. McHale
  4. +
  5. Parish
  6. +
+ +If you instead wrote the list in Markdown like this: + + 1. Bird + 1. McHale + 1. Parish + +or even: + + 3. Bird + 1. McHale + 8. Parish + +you'd get the exact same HTML output. The point is, if you want to, +you can use ordinal numbers in your ordered Markdown lists, so that +the numbers in your source match the numbers in your published HTML. +But if you want to be lazy, you don't have to. + +If you do use lazy list numbering, however, you should still start the +list with the number 1. At some point in the future, Markdown may support +starting ordered lists at an arbitrary number. + +List markers typically start at the left margin, but may be indented by +up to three spaces. List markers must be followed by one or more spaces +or a tab. + +To make lists look nice, you can wrap items with hanging indents: + + * Lorem ipsum dolor sit amet, consectetuer adipiscing elit. + Aliquam hendrerit mi posuere lectus. Vestibulum enim wisi, + viverra nec, fringilla in, laoreet vitae, risus. + * Donec sit amet nisl. Aliquam semper ipsum sit amet velit. + Suspendisse id sem consectetuer libero luctus adipiscing. + +But if you want to be lazy, you don't have to: + + * Lorem ipsum dolor sit amet, consectetuer adipiscing elit. + Aliquam hendrerit mi posuere lectus. Vestibulum enim wisi, + viverra nec, fringilla in, laoreet vitae, risus. + * Donec sit amet nisl. Aliquam semper ipsum sit amet velit. + Suspendisse id sem consectetuer libero luctus adipiscing. + +If list items are separated by blank lines, Markdown will wrap the +items in `

` tags in the HTML output. For example, this input: + + * Bird + * Magic + +will turn into: + +

    +
  • Bird
  • +
  • Magic
  • +
+ +But this: + + * Bird + + * Magic + +will turn into: + +
    +
  • Bird

  • +
  • Magic

  • +
+ +List items may consist of multiple paragraphs. Each subsequent +paragraph in a list item must be indented by either 4 spaces +or one tab: + + 1. This is a list item with two paragraphs. Lorem ipsum dolor + sit amet, consectetuer adipiscing elit. Aliquam hendrerit + mi posuere lectus. + + Vestibulum enim wisi, viverra nec, fringilla in, laoreet + vitae, risus. Donec sit amet nisl. Aliquam semper ipsum + sit amet velit. + + 2. Suspendisse id sem consectetuer libero luctus adipiscing. + +It looks nice if you indent every line of the subsequent +paragraphs, but here again, Markdown will allow you to be +lazy: + + * This is a list item with two paragraphs. + + This is the second paragraph in the list item. You're + only required to indent the first line. Lorem ipsum dolor + sit amet, consectetuer adipiscing elit. + + * Another item in the same list. + +To put a blockquote within a list item, the blockquote's `>` +delimiters need to be indented: + + * A list item with a blockquote: + + > This is a blockquote + > inside a list item. + +To put a code block within a list item, the code block needs +to be indented *twice* -- 8 spaces or two tabs: + + * A list item with a code block: + + + + +It's worth noting that it's possible to trigger an ordered list by +accident, by writing something like this: + + 1986. What a great season. + +In other words, a *number-period-space* sequence at the beginning of a +line. To avoid this, you can backslash-escape the period: + + 1986\. What a great season. + + + +

Code Blocks

+ +Pre-formatted code blocks are used for writing about programming or +markup source code. Rather than forming normal paragraphs, the lines +of a code block are interpreted literally. Markdown wraps a code block +in both `
` and `` tags.
+
+To produce a code block in Markdown, simply indent every line of the
+block by at least 4 spaces or 1 tab. For example, given this input:
+
+    This is a normal paragraph:
+
+        This is a code block.
+
+Markdown will generate:
+
+    

This is a normal paragraph:

+ +
This is a code block.
+    
+ +One level of indentation -- 4 spaces or 1 tab -- is removed from each +line of the code block. For example, this: + + Here is an example of AppleScript: + + tell application "Foo" + beep + end tell + +will turn into: + +

Here is an example of AppleScript:

+ +
tell application "Foo"
+        beep
+    end tell
+    
+ +A code block continues until it reaches a line that is not indented +(or the end of the article). + +Within a code block, ampersands (`&`) and angle brackets (`<` and `>`) +are automatically converted into HTML entities. This makes it very +easy to include example HTML source code using Markdown -- just paste +it and indent it, and Markdown will handle the hassle of encoding the +ampersands and angle brackets. For example, this: + + + +will turn into: + +
<div class="footer">
+        &copy; 2004 Foo Corporation
+    </div>
+    
+ +Regular Markdown syntax is not processed within code blocks. E.g., +asterisks are just literal asterisks within a code block. This means +it's also easy to use Markdown to write about Markdown's own syntax. + + + +

Horizontal Rules

+ +You can produce a horizontal rule tag (`
`) by placing three or +more hyphens, asterisks, or underscores on a line by themselves. If you +wish, you may use spaces between the hyphens or asterisks. Each of the +following lines will produce a horizontal rule: + + * * * + + *** + + ***** + + - - - + + --------------------------------------- + + +* * * + +

Span Elements

+ + + +Markdown supports two style of links: *inline* and *reference*. + +In both styles, the link text is delimited by [square brackets]. + +To create an inline link, use a set of regular parentheses immediately +after the link text's closing square bracket. Inside the parentheses, +put the URL where you want the link to point, along with an *optional* +title for the link, surrounded in quotes. For example: + + This is [an example](http://example.com/ "Title") inline link. + + [This link](http://example.net/) has no title attribute. + +Will produce: + +

This is + an example inline link.

+ +

This link has no + title attribute.

+ +If you're referring to a local resource on the same server, you can +use relative paths: + + See my [About](/about/) page for details. + +Reference-style links use a second set of square brackets, inside +which you place a label of your choosing to identify the link: + + This is [an example][id] reference-style link. + +You can optionally use a space to separate the sets of brackets: + + This is [an example] [id] reference-style link. + +Then, anywhere in the document, you define your link label like this, +on a line by itself: + + [id]: http://example.com/ "Optional Title Here" + +That is: + +* Square brackets containing the link identifier (optionally + indented from the left margin using up to three spaces); +* followed by a colon; +* followed by one or more spaces (or tabs); +* followed by the URL for the link; +* optionally followed by a title attribute for the link, enclosed + in double or single quotes, or enclosed in parentheses. + +The following three link definitions are equivalent: + + [foo]: http://example.com/ "Optional Title Here" + [foo]: http://example.com/ 'Optional Title Here' + [foo]: http://example.com/ (Optional Title Here) + +**Note:** There is a known bug in Markdown.pl 1.0.1 which prevents +single quotes from being used to delimit link titles. + +The link URL may, optionally, be surrounded by angle brackets: + + [id]: "Optional Title Here" + +You can put the title attribute on the next line and use extra spaces +or tabs for padding, which tends to look better with longer URLs: + + [id]: http://example.com/longish/path/to/resource/here + "Optional Title Here" + +Link definitions are only used for creating links during Markdown +processing, and are stripped from your document in the HTML output. + +Link definition names may consist of letters, numbers, spaces, and +punctuation -- but they are *not* case sensitive. E.g. these two +links: + + [link text][a] + [link text][A] + +are equivalent. + +The *implicit link name* shortcut allows you to omit the name of the +link, in which case the link text itself is used as the name. +Just use an empty set of square brackets -- e.g., to link the word +"Google" to the google.com web site, you could simply write: + + [Google][] + +And then define the link: + + [Google]: http://google.com/ + +Because link names may contain spaces, this shortcut even works for +multiple words in the link text: + + Visit [Daring Fireball][] for more information. + +And then define the link: + + [Daring Fireball]: http://daringfireball.net/ + +Link definitions can be placed anywhere in your Markdown document. I +tend to put them immediately after each paragraph in which they're +used, but if you want, you can put them all at the end of your +document, sort of like footnotes. + +Here's an example of reference links in action: + + I get 10 times more traffic from [Google] [1] than from + [Yahoo] [2] or [MSN] [3]. + + [1]: http://google.com/ "Google" + [2]: http://search.yahoo.com/ "Yahoo Search" + [3]: http://search.msn.com/ "MSN Search" + +Using the implicit link name shortcut, you could instead write: + + I get 10 times more traffic from [Google][] than from + [Yahoo][] or [MSN][]. + + [google]: http://google.com/ "Google" + [yahoo]: http://search.yahoo.com/ "Yahoo Search" + [msn]: http://search.msn.com/ "MSN Search" + +Both of the above examples will produce the following HTML output: + +

I get 10 times more traffic from Google than from + Yahoo + or MSN.

+ +For comparison, here is the same paragraph written using +Markdown's inline link style: + + I get 10 times more traffic from [Google](http://google.com/ "Google") + than from [Yahoo](http://search.yahoo.com/ "Yahoo Search") or + [MSN](http://search.msn.com/ "MSN Search"). + +The point of reference-style links is not that they're easier to +write. The point is that with reference-style links, your document +source is vastly more readable. Compare the above examples: using +reference-style links, the paragraph itself is only 81 characters +long; with inline-style links, it's 176 characters; and as raw HTML, +it's 234 characters. In the raw HTML, there's more markup than there +is text. + +With Markdown's reference-style links, a source document much more +closely resembles the final output, as rendered in a browser. By +allowing you to move the markup-related metadata out of the paragraph, +you can add links without interrupting the narrative flow of your +prose. + + +

Emphasis

+ +Markdown treats asterisks (`*`) and underscores (`_`) as indicators of +emphasis. Text wrapped with one `*` or `_` will be wrapped with an +HTML `` tag; double `*`'s or `_`'s will be wrapped with an HTML +`` tag. E.g., this input: + + *single asterisks* + + _single underscores_ + + **double asterisks** + + __double underscores__ + +will produce: + + single asterisks + + single underscores + + double asterisks + + double underscores + +You can use whichever style you prefer; the lone restriction is that +the same character must be used to open and close an emphasis span. + +Emphasis can be used in the middle of a word: + + un*frigging*believable + +But if you surround an `*` or `_` with spaces, it'll be treated as a +literal asterisk or underscore. + +To produce a literal asterisk or underscore at a position where it +would otherwise be used as an emphasis delimiter, you can backslash +escape it: + + \*this text is surrounded by literal asterisks\* + + + +

Code

+ +To indicate a span of code, wrap it with backtick quotes (`` ` ``). +Unlike a pre-formatted code block, a code span indicates code within a +normal paragraph. For example: + + Use the `printf()` function. + +will produce: + +

Use the printf() function.

+ +To include a literal backtick character within a code span, you can use +multiple backticks as the opening and closing delimiters: + + ``There is a literal backtick (`) here.`` + +which will produce this: + +

There is a literal backtick (`) here.

+ +The backtick delimiters surrounding a code span may include spaces -- +one after the opening, one before the closing. This allows you to place +literal backtick characters at the beginning or end of a code span: + + A single backtick in a code span: `` ` `` + + A backtick-delimited string in a code span: `` `foo` `` + +will produce: + +

A single backtick in a code span: `

+ +

A backtick-delimited string in a code span: `foo`

+ +With a code span, ampersands and angle brackets are encoded as HTML +entities automatically, which makes it easy to include example HTML +tags. Markdown will turn this: + + Please don't use any `` tags. + +into: + +

Please don't use any <blink> tags.

+ +You can write this: + + `—` is the decimal-encoded equivalent of `—`. + +to produce: + +

&#8212; is the decimal-encoded + equivalent of &mdash;.

+ + + +

Images

+ +Admittedly, it's fairly difficult to devise a "natural" syntax for +placing images into a plain text document format. + +Markdown uses an image syntax that is intended to resemble the syntax +for links, allowing for two styles: *inline* and *reference*. + +Inline image syntax looks like this: + + ![Alt text](/path/to/img.jpg) + + ![Alt text](/path/to/img.jpg "Optional title") + +That is: + +* An exclamation mark: `!`; +* followed by a set of square brackets, containing the `alt` + attribute text for the image; +* followed by a set of parentheses, containing the URL or path to + the image, and an optional `title` attribute enclosed in double + or single quotes. + +Reference-style image syntax looks like this: + + ![Alt text][id] + +Where "id" is the name of a defined image reference. Image references +are defined using syntax identical to link references: + + [id]: url/to/image "Optional title attribute" + +As of this writing, Markdown has no syntax for specifying the +dimensions of an image; if this is important to you, you can simply +use regular HTML `` tags. + + +* * * + + +

Miscellaneous

+ + + +Markdown supports a shortcut style for creating "automatic" links for URLs and email addresses: simply surround the URL or email address with angle brackets. What this means is that if you want to show the actual text of a URL or email address, and also have it be a clickable link, you can do this: + + + +Markdown will turn this into: + + http://example.com/ + +Automatic links for email addresses work similarly, except that +Markdown will also perform a bit of randomized decimal and hex +entity-encoding to help obscure your address from address-harvesting +spambots. For example, Markdown will turn this: + + + +into something like this: + + address@exa + mple.com + +which will render in a browser as a clickable link to "address@example.com". + +(This sort of entity-encoding trick will indeed fool many, if not +most, address-harvesting bots, but it definitely won't fool all of +them. It's better than nothing, but an address published in this way +will probably eventually start receiving spam.) + + + +

Backslash Escapes

+ +Markdown allows you to use backslash escapes to generate literal +characters which would otherwise have special meaning in Markdown's +formatting syntax. For example, if you wanted to surround a word +with literal asterisks (instead of an HTML `` tag), you can use +backslashes before the asterisks, like this: + + \*literal asterisks\* + +Markdown provides backslash escapes for the following characters: + + \ backslash + ` backtick + * asterisk + _ underscore + {} curly braces + [] square brackets + () parentheses + # hash mark + + plus sign + - minus sign (hyphen) + . dot + ! exclamation mark + diff --git a/tests/test-cases.text b/tests/test-cases.text new file mode 100644 index 0000000..b5d3093 --- /dev/null +++ b/tests/test-cases.text @@ -0,0 +1,454 @@ +markdown-mode Test Cases +======================== + +1. Lists +-------- + +Unordered lists: + +- This is a bullet point. + - This is a sub bullet point. +- This is another bullet point. + +Ordered lists: + +1. This is an ordered list +2. With a second element. +44. And a forty-fourth element. +3. Remember, Markdown doesn't care which number you use. + +List items with bold and italic: + +> * This is a list item *in italics*, just a test. +> * *List item in italics.* +> * This is a list item **in bold**, just a test. +> * **List item in bold.** + +Bold and italic phrases at the beginning of lines: + +*not a list* +**also not a list** + + +2. Blockquotes +-------------- + +> this is a test +> of the blockquote mechanism + + +3. Two Inline Links on One Line +------------------------------- + +*Contributed by Ryan Barrett on 2007-07-02* + +thanks! the new version 1.4 works great. very cool. + +i did notice a minor bug. if there are two inline links in the same line, e.g. +[foo](bar) baz [foo](bar), it colors the text between the links (baz) as well. + +i'm guessing this is because the inline link regexp is greedy. if you switch +it to non-greedy - if elisp can do that - or to something like '\[[^]]+\]' if +not, that might do the trick. + + +4. Empty Inline Links +--------------------- + +[]() +[](asdf) +[asdf]() + + +5. Bold and Italics on the Same Line +------------------------------------ + +*Contributed by Conal Elliott on 2007-08-27* + +By the way, here are two syntax-highlighting glitches I've seen so far: + +**foo and doo** or *ziddle zop* +Oh -- what's happening here? + + +6. Reverse Order Italic then Bold +--------------------------------- + +Why doesn't the bold part get picked up? +*ziddle zop* or **foo and doo** + + +7. Two Inline Links in One Line +------------------------------- + +*Contributed by Alastair Rankine on 2007-09-10* + +Thanks for this, I have been waiting for an emacs markdown mode for ages! + +One minor quibble is in the highlighting of inline links. As currently +(1.4) implemented the presence of a close paren on the same line as an +inline link will highlight the link and the subsequent text: + + [blah](this bit will be highlighted) and so (will this bit) + +I think this is because of greedy regexes. Here is a simple patch to address. + + +8. Reference-Style Link with a Space +------------------------------------ + +Here's a [link] [1] with a space between the components. + + [1]: http://daringfireball.net/projects/markdown/syntax + + +9. Inline Code +-------------- + +Single `backtick code` fragments as well as ``double`backtick`` fragments are +supported. + + +10. Single Underscore and Asterisk +---------------------------------- + +This is a _ single underscore and a * single asterisk. +They should not trigger font locking. + + +11. Double Underscores and Asterisks +------------------------------------ + +Neither should a double **asterisk and double__ underscore! + +12. List-Like Strings in Preformatted Text +------------------------------------------ + +To make lists look nice, you can wrap items with hanging indents: + + * Lorem ipsum dolor sit amet, consectetuer adipiscing elit. + Aliquam hendrerit mi posuere lectus. Vestibulum enim wisi, + viverra nec, fringilla in, laoreet vitae, risus. + * Donec sit amet nisl. Aliquam semper ipsum sit amet velit. + Suspendisse id sem consectetuer libero luctus adipiscing. + +13. Multi-Line Italic and Bold +------------------------------ + +bold **phrase spanning +two lines** asdf **same line** test + +italic *phrase spanning +two lines* italic *same line* test + + +14. Reference Links +------------------- + +This document was created in GNU Emacs using [markdown-mode][]. It provides +syntax highlighting for [Markdown][] documents which allows reference-style +links like these. The reference definitions can even contain alternate +text such as [this one][id]. + + [markdown-mode]: http://jrblevin.freeshell.org/software/markdown-mode + [Markdown]: http://daringfireball.net/projects/markdown "Markdown Homepage" + [id]: http://reference-link.com/with/alt "Alternate Text" + + +15. Escaping +------------ + +ab \*literal asterisks\* asdf *inside \* literal asterisks* asd lkj + +ab \**this should be italic\** yz +ab \**this should be italic*\* yz +ab *\*this should be italic\** yz +ab *\*this should be italic*\* yz + + +16. Single Letter and Word on Same Line +--------------------------------------- + +a single **a** bold letter and bold word **test** asdf +a single _a_ italic letter and italic word _test_ asdf +a single __a__ bold letter and bold word __test__ asdf +a single *a* italic letter italic word *test* asdf + + +17. Hanging Indents +------------------- + +* An exclamation mark: `!`; +* followed by a set of square brackets, containing the `alt` + attribute text for the image; + + +18. Links in Preformatted Text +------------------------------ + +Here's an example of reference links in action: + + This document was created in GNU Emacs using [markdown-mode][]. It + provides syntax highlighting for [Markdown][] documents which allows + reference-style links like these. The reference definitions can even + contain alternate text such as [this one][id]. + + [markdown-mode]: http://jrblevin.freeshell.org/software/markdown-mode + [Markdown]: http://daringfireball.net/projects/markdown + [id]: http://reference-link.com/with/alt "Alternate Text" + + +19. Slash-Star in Preformatted Text +----------------------------------- + + svn commit -m "Imported RCS project" + svn add image.png binaries/* + svn commit -m "Imported additional project files" + +This text gets counted as part of the block quote too. But it can be ended +by a star-slash: */ It is as if `markdown-mode` is using C++ style comments. + +Fixed by commit d81138d. + + +20. Underscores in Code Blocks +------------------------------ + +*Contributed by shindo on 2008-01-20* + + get_something_by_name() + + +21. Escaped Characters +---------------------- + +Escaped backticks: \`not code\` + +Escaped underscores: don't\_italicize\_this + +Escaped asterisks: \*also not italic\* + +Escaped hash marks: +\# This is not a heading + + +22. Adjacent Wiki Links +----------------------- + +[[Two]] [[WikiLinks]] [[InARow]] + + +23. SmartyPants +--------------- + +This is a test of "SmartyPants," a progrm written by John Gruber for +generating typographically correct HTML entities--ones such as +the em-dash and elipsis... + + +24. Horizontal Rules +-------------------- + +* * * * * + +- - - --- - - - + +* *** * * *** * * * + + +25. Asterisks and Underscores Across Blocks +------------------------------------------- + +Asterisks *should + +not match across* block boundaries. + +Underscores _should + +not match across_ block boundaries. + +Double Asterisks **should + +not match across** block boundaries. + +Double underscores __should + +not match across__ block boundaries. + +But, *this should still match*. + +So should _this_. + +Addressed by commit d81138d. + + +26. Underscores Within Words +---------------------------- + +Code fragments `can_have_underscores_inside_like` this. + + +27. Code Blocks Spanning Lines +------------------------------ + +Markdown allows `code +fragments` to span across lines. + +Let's make sure that ``double` +backtick`code fragments`` work +this way too. + +However, they should `not + +match` across blocks. + +Here's another `valid` one. + + +28. Pre Blocks and Nested Lists +------------------------------- + + - List level 1 item 1 + - List level 1 item 2 + + This is a second paragraph, part of item 2. + + This nested pre block should match + + So should this + + Now we move back to the list. + + - List level 1 item 3 + + - List level 2 item 1 + + Nested pre block + + - List level 2 item 2 + + - List level 3 item 1 + + Nested pre block + + +29. Colon After Wiki Link +------------------------- + +[[AnotherLink]]:note + + +30. Incorrect Parsing of List Paragraph +--------------------------------------- + +*Contributed by Luciano Gerber on April 4, 2012.* + +It is interesting to see what happens when one queries +`social upheaval` and `protopalatial era`. + +* `social upheaval`: the follwing queries have been tried: + + social upheaval subClassOf + +The preceding text should not be matched as a preformatted block. + + +31. Footnotes +------------- + +This is a footnote marker,[^1] but this is not.[^] + +[^1]: And the definition of the footnote is here. + + [^2]: but this is preformatted text. + + +32. Carat as Implicit Reference Link +------------------------------------ + +This is a valid markdown link: [^][] + +[^]: http://jblevins.org/ "And this is a valid reference defintion!" + + +33. Email Address Inside Bold and Italic Spans +---------------------------------------------- + +*Lorem amet.* + +**Lorem amet.** + + +34. URL Inside Bold and Italic Spans +------------------------------------ + +*markdown-mode homepage http://jblevins.org/projects/markdown-mode/* + +**markdown-mode homepage http://jblevins.org/projects/markdown-mode/** + +35. Complex Preformatted Code Block +----------------------------------- + + class Employee < ActiveRecord::Base + attr_accessible :github_username, :name + + def self.syncify! + begin + employee_sync = GitOrganized::GithubEmployeeSync.new( + GitOrganized::Organization.new( + GitOrganized.config.default_org, + ["employees-pull", "employees-push"] + ), + Sources.new + ) + + employee_sync.sync + rescue Github::Error::ServiceError => e + retry + end + end + + after_save do |employee| Employee.syncify! end + after_destroy do |employee| Employee.syncify! end + end + +36. Fenced Code Blocks +---------------------- + +Fenced code blocks begin with three or more tildes and end with a line +with at least as many tildes. + +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +program hello + implicit none + print '(a)', 'Hello, world!' +end program hello +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +They may also have a `lang` attribute. + +~~~~~~~~~~~~~{: lang=fortran } +program hello + implicit none + print '(a)', 'Hello, world!' +end program hello +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +In Kramdown, one specifies the language identifier as follows: + +~~~ fortran +program hello + implicit none + print '(a)', 'Hello, world!' +end program hello +~~~ + +or with a Maruku-style inline attribute list + +~~~ +program hello + implicit none + print '(a)', 'Hello, world!' +end program hello +~~~ +{: .language-fortran} diff --git a/tests/wiki-links.text b/tests/wiki-links.text new file mode 100644 index 0000000..6c33349 --- /dev/null +++ b/tests/wiki-links.text @@ -0,0 +1,25 @@ +Line 1 +[[Wiki Link]] at point 8. + +Here's a second link, an aliased link [[first|second]], at point 73. +The first two links are invalid. This third link, [[inline]], is a +valid link since the file `inline.text` exists. The last link is at +point 155. + +Bracketed expressions in code blocks should not be matched as wiki +links: + +``` +{ + :user { + :repositories [["clojars" {:sign-releases false}]] + :plugins [ + [lein-cloverage "1.0.6"] + ] + } +} +``` + + + + diff --git a/webpage.sh b/webpage.sh new file mode 100755 index 0000000..d3f7483 --- /dev/null +++ b/webpage.sh @@ -0,0 +1,49 @@ +#!/bin/sh + +STARTRE='^;;; Commentary:$' +STOPRE='^ $' +DATE=$(date +"%B %e, %Y %H:%M %Z") + +echo "title: Emacs Markdown Mode +description: A major mode for GNU Emacs for editing Markdown-formatted text files. +markup: markdown +icon: emacs +city: Columbus +created: May 24, 2007 23:47 GMT +modified: $DATE" > index.text + +echo "# Emacs Markdown Mode [![MELPA badge][melpa-badge]][melpa-link] [![MELPA stable badge][melpa-stable-badge]][melpa-stable-link] [![Travis CI Build Status][travis-badge]][travis-link] + + [melpa-link]: https://melpa.org/#/markdown-mode + [melpa-stable-link]: https://stable.melpa.org/#/markdown-mode + [melpa-badge]: https://melpa.org/packages/markdown-mode-badge.svg + [melpa-stable-badge]: https://stable.melpa.org/packages/markdown-mode-badge.svg + [travis-link]: https://travis-ci.org/jrblevin/markdown-mode + [travis-badge]: https://travis-ci.org/jrblevin/markdown-mode.svg?branch=master + +" > README.md + +cat markdown-mode.el |\ + # Keep only the Commentary section + awk "/$STARTRE/,/$STOPRE/" |\ + # Remove the start and step regexps + grep -v "$STARTRE" | grep -v "$STOPRE" |\ + # Convert headers + sed -e 's/^;;; \(.*\):$/## \1/' |\ + # Remove leading spaces (but don't disturb pre blocks) + sed -e 's/^;;[ ]\{0,1\}//' |\ + # Escape wiki links + #sed -e 's/\(\[\[[^]\n]*\]\]\)/\\\1/g' |\ + # Use Markdown-style backticks for single-quoted lisp code + sed -e 's/`\([^'\'']*\)'\''/`\1`/g' |\ + # Use tags for single character, unprefixed keybindings + sed -e 's/`\([^`]\)`/\1<\/kbd>/g' |\ + # Use tags for TAB and RET keys + sed -e 's/`TAB`/TAB<\/kbd>/g' |\ + sed -e 's/`RET`/RET<\/kbd>/g' |\ + # Use tags for keybindings prefixed by C, M, or S + sed -e 's/`\([CMS]-[^`]*\)`/\1<\/kbd>/g' |\ + # Remove email addresses + sed -e 's/ <[^>]*@[^<]*> / /g' \ + | tee -a README.md >> index.text