Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Inital version.

Mode can be loaded. Insert and syntax highlighting works for the first markups (h1 - h6, italic, bold).
  • Loading branch information...
commit bf43975e239dbe9832b8e4c5fd2d747fa145c624 1 parent 8ceb27f
Matthias Nüßler authored July 15, 2012
3  .gitignore
... ...
@@ -0,0 +1,3 @@
  1
+.#*
  2
+\#*#
  3
+
1,262  jira-markup-mode.el
... ...
@@ -0,0 +1,1262 @@
  1
+;;; jira-markup-mode.el --- Emacs Major mode for JIRA-markup-formatted text files
  2
+
  3
+;; Copyright (C) 2012 Matthias Nuessler <m.nuessler@web.de>
  4
+
  5
+;; Author: Matthias Nuessler <m.nuessler@web.de>>
  6
+;; Created: July 15, 2012
  7
+;; Version: 0.0.1
  8
+;; Keywords: jira, markup
  9
+;; URL: https://github.com/mnuessler/jira-markup-mode
  10
+
  11
+;; This file is not part of GNU Emacs.
  12
+
  13
+;; This program is free software; you can redistribute it and/or modify
  14
+;; it under the terms of the GNU General Public License as published by
  15
+;; the Free Software Foundation; either version 2, or (at your option)
  16
+;; any later version.
  17
+
  18
+;; This program is distributed in the hope that it will be useful,
  19
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  20
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  21
+;; GNU General Public License for more details.
  22
+
  23
+;; You should have received a copy of the GNU General Public License
  24
+;; along with this program; if not, write to the Free Software
  25
+;; Foundation, Inc., 51 Franklin Street, Fifth Floor,
  26
+;; Boston, MA 02110-1301, USA.
  27
+
  28
+;;; Commentary:
  29
+;; Jira-markup-mode provides a major mode for editing JIRA wiki markup
  30
+;; files. Unlike jira.el it does not interact with JIRA.
  31
+;;
  32
+;; The code is based on markdown-mode.el, a major for mode for editing
  33
+;; markdown files.
  34
+;; https://confluence.atlassian.com/display/DOC/Confluence+Wiki+Markup
  35
+
  36
+;;; Dependencies:
  37
+
  38
+;; jira-markup-mode requires easymenu, a standard package since GNU Emacs
  39
+;; 19 and XEmacs 19, which provides a uniform interface for creating
  40
+;; menus in GNU Emacs and XEmacs.
  41
+
  42
+;;; Installation:
  43
+
  44
+;; Make sure to place `jira-markup-mode.el` somewhere in the load-path and add
  45
+;; the following lines to your `.emacs` file to associate jira-markup-mode
  46
+;; with `.text` files:
  47
+;;
  48
+;;     (autoload 'jira-markup-mode "jira-markup-mode"
  49
+;;        "Major mode for editing JIRA markup files" t)
  50
+;;     (setq auto-mode-alist
  51
+;;        (cons '("\\.text" . jira-markup-mode) auto-mode-alist))
  52
+;;
  53
+
  54
+;;; Acknowledgments:
  55
+
  56
+;; Jira-markup-mode has benefited greatly from the efforts of the
  57
+;; developers of markdown-mode.  Markdown-mode was developed and is
  58
+;; maintained by Jason R. Blevins <jrblevin@sdf.org>.  For details
  59
+;; please refer to http://jblevins.org/projects/markdown-mode/
  60
+
  61
+;;; History:
  62
+
  63
+;; jira-markup-mode was written and is maintained by Matthias Nuessler.  The
  64
+;; first version was released on TODO, 2012.
  65
+;;
  66
+;;   * 2012-07-TODO: Version 0.1.0
  67
+
  68
+;;; Code:
  69
+
  70
+(require 'easymenu)
  71
+(require 'outline)
  72
+(eval-when-compile (require 'cl))
  73
+
  74
+;;; Constants =================================================================
  75
+
  76
+(defconst jira-markup-mode-version "0.1.0"
  77
+  "Jira markup mode version number.")
  78
+
  79
+;;; Customizable variables ====================================================
  80
+
  81
+(defvar jira-markup-mode-hook nil
  82
+  "Hook run when entering jira markup mode.")
  83
+
  84
+(defgroup jira-markup nil
  85
+  "Major mode for editing text files in Jira markup format."
  86
+  :prefix "jira-markup"
  87
+  :group 'wp
  88
+  :link '(url-link "https://github.com/mnuessler/jira-markup-mode"))
  89
+
  90
+(defcustom jira-markup-hr-string "----"
  91
+  "String to use for horizonal rules."
  92
+  :group 'jira-markup
  93
+  :type 'string)
  94
+
  95
+(defcustom jira-markup-indent-function 'jira-markup-indent-line
  96
+  "Function to use to indent."
  97
+  :group 'jira-markup
  98
+  :type 'function)
  99
+
  100
+(defcustom jira-markup-indent-on-enter t
  101
+  "Automatically indent new lines when enter key is pressed."
  102
+  :group 'jira-markup
  103
+  :type 'boolean)
  104
+
  105
+(defcustom jira-markup-follow-wiki-link-on-enter t
  106
+  "Follow wiki link at point (if any) when the enter key is pressed."
  107
+  :group 'jira-markup
  108
+  :type 'boolean)
  109
+
  110
+(defcustom jira-markup-wiki-link-alias-first t
  111
+  "When non-nil, treat aliased wiki links like [[alias text|PageName]].
  112
+Otherwise, they will be treated as [[PageName|alias text]]."
  113
+  :group 'jira-markup
  114
+  :type 'boolean)
  115
+
  116
+(defcustom jira-markup-uri-types
  117
+  '("acap" "cid" "data" "dav" "fax" "file" "ftp" "gopher" "http" "https"
  118
+    "imap" "ldap" "mailto" "mid" "modem" "news" "nfs" "nntp" "pop" "prospero"
  119
+    "rtsp" "service" "sip" "tel" "telnet" "tip" "urn" "vemmi" "wais")
  120
+  "Link types for syntax highlighting of URIs."
  121
+  :group 'jira-markup
  122
+  :type 'list)
  123
+
  124
+(defcustom jira-markup-link-space-sub-char
  125
+  "_"
  126
+  "Character to use instead of spaces when mapping wiki links to filenames."
  127
+  :group 'jira-markup
  128
+  :type 'string)
  129
+
  130
+
  131
+;;; Font lock =================================================================
  132
+
  133
+(require 'font-lock)
  134
+
  135
+(defvar jira-markup-italic-face 'jira-markup-italic-face
  136
+  "Face name to use for italic text.")
  137
+
  138
+(defvar jira-markup-bold-face 'jira-markup-bold-face
  139
+  "Face name to use for bold text.")
  140
+
  141
+(defvar jira-markup-header-face 'jira-markup-header-face
  142
+  "Face name to use as a base for headers.")
  143
+
  144
+(defvar jira-markup-header-face-1 'jira-markup-header-face-1
  145
+  "Face name to use for level-1 headers.")
  146
+
  147
+(defvar jira-markup-header-face-2 'jira-markup-header-face-2
  148
+  "Face name to use for level-2 headers.")
  149
+
  150
+(defvar jira-markup-header-face-3 'jira-markup-header-face-3
  151
+  "Face name to use for level-3 headers.")
  152
+
  153
+(defvar jira-markup-header-face-4 'jira-markup-header-face-4
  154
+  "Face name to use for level-4 headers.")
  155
+
  156
+(defvar jira-markup-header-face-5 'jira-markup-header-face-5
  157
+  "Face name to use for level-5 headers.")
  158
+
  159
+(defvar jira-markup-header-face-6 'jira-markup-header-face-6
  160
+  "Face name to use for level-6 headers.")
  161
+
  162
+(defvar jira-markup-inline-code-face 'jira-markup-inline-code-face
  163
+  "Face name to use for inline code.")
  164
+
  165
+(defvar jira-markup-list-face 'jira-markup-list-face
  166
+  "Face name to use for list markers.")
  167
+
  168
+(defvar jira-markup-blockquote-face 'jira-markup-blockquote-face
  169
+  "Face name to use for blockquote.")
  170
+
  171
+(defvar jira-markup-pre-face 'jira-markup-pre-face
  172
+  "Face name to use for preformatted text.")
  173
+
  174
+(defvar jira-markup-link-face 'jira-markup-link-face
  175
+  "Face name to use for links.")
  176
+
  177
+(defvar jira-markup-missing-link-face 'jira-markup-missing-link-face
  178
+  "Face name to use for links where the linked file does not exist.")
  179
+
  180
+(defvar jira-markup-reference-face 'jira-markup-reference-face
  181
+  "Face name to use for reference.")
  182
+
  183
+(defvar jira-markup-url-face 'jira-markup-url-face
  184
+  "Face name to use for URLs.")
  185
+
  186
+(defvar jira-markup-link-title-face 'jira-markup-link-title-face
  187
+  "Face name to use for reference link titles.")
  188
+
  189
+(defgroup jira-markup-faces nil
  190
+  "Faces used in Jira-Markup Mode"
  191
+  :group 'jira-markup
  192
+  :group 'faces)
  193
+
  194
+(defface jira-markup-italic-face
  195
+  '((t (:inherit font-lock-variable-name-face :slant italic)))
  196
+  "Face for italic text."
  197
+  :group 'jira-markup-faces)
  198
+
  199
+(defface jira-markup-bold-face
  200
+  '((t (:inherit font-lock-variable-name-face :weight bold)))
  201
+  "Face for bold text."
  202
+  :group 'jira-markup-faces)
  203
+
  204
+(defface jira-markup-header-face
  205
+  '((t (:inherit font-lock-function-name-face :weight bold)))
  206
+  "Base face for headers."
  207
+  :group 'jira-markup-faces)
  208
+
  209
+(defface jira-markup-header-face-1
  210
+  '((t (:inherit jira-markup-header-face)))
  211
+  "Face for level-1 headers."
  212
+  :group 'jira-markup-faces)
  213
+
  214
+(defface jira-markup-header-face-2
  215
+  '((t (:inherit jira-markup-header-face)))
  216
+  "Face for level-2 headers."
  217
+  :group 'jira-markup-faces)
  218
+
  219
+(defface jira-markup-header-face-3
  220
+  '((t (:inherit jira-markup-header-face)))
  221
+  "Face for level-3 headers."
  222
+  :group 'jira-markup-faces)
  223
+
  224
+(defface jira-markup-header-face-4
  225
+  '((t (:inherit jira-markup-header-face)))
  226
+  "Face for level-4 headers."
  227
+  :group 'jira-markup-faces)
  228
+
  229
+(defface jira-markup-header-face-5
  230
+  '((t (:inherit jira-markup-header-face)))
  231
+  "Face for level-5 headers."
  232
+  :group 'jira-markup-faces)
  233
+
  234
+(defface jira-markup-header-face-6
  235
+  '((t (:inherit jira-markup-header-face)))
  236
+  "Face for level-6 headers."
  237
+  :group 'jira-markup-faces)
  238
+
  239
+(defface jira-markup-inline-code-face
  240
+  '((t (:inherit font-lock-constant-face)))
  241
+  "Face for inline code."
  242
+  :group 'jira-markup-faces)
  243
+
  244
+(defface jira-markup-list-face
  245
+  '((t (:inherit font-lock-builtin-face)))
  246
+  "Face for list item markers."
  247
+  :group 'jira-markup-faces)
  248
+
  249
+(defface jira-markup-blockquote-face
  250
+  '((t (:inherit font-lock-doc-face)))
  251
+  "Face for blockquote sections."
  252
+  :group 'jira-markup-faces)
  253
+
  254
+(defface jira-markup-pre-face
  255
+  '((t (:inherit font-lock-constant-face)))
  256
+  "Face for preformatted text."
  257
+  :group 'jira-markup-faces)
  258
+
  259
+(defface jira-markup-link-face
  260
+  '((t (:inherit font-lock-keyword-face)))
  261
+  "Face for links."
  262
+  :group 'jira-markup-faces)
  263
+
  264
+(defface jira-markup-missing-link-face
  265
+  '((t (:inherit font-lock-warning-face)))
  266
+  "Face for missing links."
  267
+  :group 'jira-markup-faces)
  268
+
  269
+(defface jira-markup-reference-face
  270
+  '((t (:inherit font-lock-type-face)))
  271
+  "Face for link references."
  272
+  :group 'jira-markup-faces)
  273
+
  274
+(defface jira-markup-url-face
  275
+  '((t (:inherit font-lock-string-face)))
  276
+  "Face for URLs."
  277
+  :group 'jira-markup-faces)
  278
+
  279
+(defface jira-markup-link-title-face
  280
+  '((t (:inherit font-lock-comment-face)))
  281
+  "Face for reference link titles."
  282
+  :group 'jira-markup-faces)
  283
+
  284
+;; regular expressions
  285
+(defconst jira-markup-regex-link-inline
  286
+  "\\(!?\\[[^]]*?\\]\\)\\(([^\\)]*)\\)"
  287
+  "Regular expression for a [text](file) or an image link ![text](file).")
  288
+
  289
+(defconst jira-markup-regex-link-reference
  290
+  "\\(!?\\[[^]]+?\\]\\)[ ]?\\(\\[[^]]*?\\]\\)"
  291
+  "Regular expression for a reference link [text][id].")
  292
+
  293
+(defconst jira-markup-regex-reference-definition
  294
+  "^ \\{0,3\\}\\(\\[.*\\]\\):\\s *\\(.*?\\)\\s *\\( \"[^\"]*\"$\\|$\\)"
  295
+  "Regular expression for a link definition [id]: ...")
  296
+
  297
+(defconst jira-markup-regex-header
  298
+  "#+\\|\\S-.*\n\\(?:\\(===+\\)\\|\\(---+\\)\\)$"
  299
+  "Regexp identifying Jira-Markup headers.")
  300
+
  301
+(defconst jira-markup-regex-header-1
  302
+  "^\\(h1\\. \\)\\(.*?\\)$"
  303
+  "Regular expression for level 1 headers.")
  304
+
  305
+(defconst jira-markup-regex-header-2
  306
+  "^\\(h2\\. \\)\\(.*?\\)$"
  307
+  "Regular expression for level 2 headers.")
  308
+
  309
+(defconst jira-markup-regex-header-3
  310
+  "^\\(h3\\. \\)\\(.*?\\)$"
  311
+  "Regular expression for level 3 headers.")
  312
+
  313
+(defconst jira-markup-regex-header-4
  314
+  "^\\(h4\\. \\)\\(.*?\\)$"
  315
+  "Regular expression for level 4 headers.")
  316
+
  317
+(defconst jira-markup-regex-header-5
  318
+  "^\\(h5\\. \\)\\(.*?\\)$"
  319
+  "Regular expression for level 5 headers.")
  320
+
  321
+(defconst jira-markup-regex-header-6
  322
+  "^\\(h6\\. \\)\\(.*?\\)$"
  323
+  "Regular expression for level 6 headers.")
  324
+
  325
+(defconst jira-markup-regex-hr
  326
+  "^\\(\\*[ ]?\\*[ ]?\\*[ ]?[\\* ]*\\|-[ ]?-[ ]?-[--- ]*\\)$"
  327
+  "Regular expression for matching Jira-Markup horizontal rules.")
  328
+
  329
+(defconst jira-markup-regex-code
  330
+  "\\(^\\|[^\\]\\)\\(\\(`\\{1,2\\}\\)\\([^ \\]\\|[^ ]\\(.\\|\n[^\n]\\)*?[^ \\]\\)\\3\\)"
  331
+  "Regular expression for matching inline code fragments.")
  332
+
  333
+(defconst jira-markup-regex-pre
  334
+  "^\\(    \\|\t\\).*$"
  335
+  "Regular expression for matching preformatted text sections.")
  336
+
  337
+(defconst jira-markup-regex-list
  338
+  "^[ \t]*\\([0-9]+\\.\\|[\\*\\+-]\\) "
  339
+  "Regular expression for matching list markers.")
  340
+
  341
+(defconst jira-markup-regex-bold
  342
+  "\\(^\\|[^\\]\\)\\(\\(*\\)\\(.\\|\n[^\n]\\)*?[^\\ ]\\3\\)"
  343
+  "Regular expression for matching bold text.")
  344
+
  345
+(defconst jira-markup-regex-italic
  346
+  "\\(^\\|[^\\]\\)\\(\\(_\\)\\([^ \\]\\3\\|[^ ]\\(.\\|\n[^\n]\\)*?[^\\ ]\\3\\)\\)"
  347
+  "Regular expression for matching italic text.")
  348
+
  349
+(defconst jira-markup-regex-blockquote
  350
+  "^bq\\. \\(.*\n\\)+\n"
  351
+  "Regular expression for matching blockquote lines.")
  352
+
  353
+(defconst jira-markup-regex-line-break
  354
+  "  $"
  355
+  "Regular expression for matching line breaks.")
  356
+
  357
+(defconst jira-markup-regex-wiki-link
  358
+  "\\[\\[\\([^]|]+\\)\\(|\\([^]]+\\)\\)?\\]\\]"
  359
+  "Regular expression for matching wiki links.
  360
+This matches typical bracketed [[WikiLinks]] as well as 'aliased'
  361
+wiki links of the form [[PageName|link text]].  In this regular
  362
+expression, #1 matches the page name and #3 matches the link
  363
+text.")
  364
+
  365
+(defconst jira-markup-regex-uri
  366
+  (concat
  367
+   "\\(" (mapconcat 'identity jira-markup-uri-types "\\|")
  368
+   "\\):[^]\t\n\r<>,;() ]+")
  369
+  "Regular expression for matching inline URIs.")
  370
+
  371
+(defconst jira-markup-regex-angle-uri
  372
+  (concat
  373
+   "\\(<\\)\\("
  374
+   (mapconcat 'identity jira-markup-uri-types "\\|")
  375
+   "\\):[^]\t\n\r<>,;()]+\\(>\\)")
  376
+  "Regular expression for matching inline URIs in angle brackets.")
  377
+
  378
+(defconst jira-markup-regex-email
  379
+  "<\\(\\sw\\|\\s_\\|\\s.\\)+@\\(\\sw\\|\\s_\\|\\s.\\)+>"
  380
+  "Regular expression for matching inline email addresses.")
  381
+
  382
+(defconst jira-markup-regex-list-indent
  383
+  "^\\(\\s *\\)\\([0-9]+\\.\\|[\\*\\+-]\\)\\(\\s +\\)"
  384
+  "Regular expression for matching indentation of list items.")
  385
+
  386
+(defvar jira-markup-mode-font-lock-keywords-basic
  387
+  (list
  388
+   '(jira-markup-match-pre-blocks 0 jira-markup-pre-face t t)
  389
+   '(jira-markup-match-fenced-code-blocks 0 jira-markup-pre-face t t)
  390
+   (cons jira-markup-regex-blockquote 'jira-markup-blockquote-face)
  391
+   (cons jira-markup-regex-header-1 'jira-markup-header-face-1)
  392
+   (cons jira-markup-regex-header-2 'jira-markup-header-face-2)
  393
+   (cons jira-markup-regex-header-3 'jira-markup-header-face-3)
  394
+   (cons jira-markup-regex-header-4 'jira-markup-header-face-4)
  395
+   (cons jira-markup-regex-header-5 'jira-markup-header-face-5)
  396
+   (cons jira-markup-regex-header-6 'jira-markup-header-face-6)
  397
+   (cons jira-markup-regex-hr 'jira-markup-header-face)
  398
+   (cons jira-markup-regex-code '(2 jira-markup-inline-code-face))
  399
+   (cons jira-markup-regex-angle-uri 'jira-markup-link-face)
  400
+   (cons jira-markup-regex-uri 'jira-markup-link-face)
  401
+   (cons jira-markup-regex-email 'jira-markup-link-face)
  402
+   (cons jira-markup-regex-list 'jira-markup-list-face)
  403
+   (cons jira-markup-regex-link-inline
  404
+         '((1 jira-markup-link-face t)
  405
+           (2 jira-markup-url-face t)))
  406
+   (cons jira-markup-regex-link-reference
  407
+         '((1 jira-markup-link-face t)
  408
+           (2 jira-markup-reference-face t)))
  409
+   (cons jira-markup-regex-reference-definition
  410
+         '((1 jira-markup-reference-face t)
  411
+           (2 jira-markup-url-face t)
  412
+           (3 jira-markup-link-title-face t)))
  413
+   (cons jira-markup-regex-bold '(2 jira-markup-bold-face))
  414
+   (cons jira-markup-regex-italic '(2 jira-markup-italic-face))
  415
+   )
  416
+  "Syntax highlighting for Jira-Markup files.")
  417
+
  418
+(defvar jira-markup-mode-font-lock-keywords
  419
+  (append
  420
+   jira-markup-mode-font-lock-keywords-basic)
  421
+  "Default highlighting expressions for Jira-Markup mode.")
  422
+
  423
+;;; Jira-Markup parsing functions ================================================
  424
+
  425
+(defun jira-markup-cur-line-blank-p ()
  426
+  "Return t if the current line is blank and nil otherwise."
  427
+  (save-excursion
  428
+    (beginning-of-line)
  429
+    (re-search-forward "^\\s *$" (point-at-eol) t)))
  430
+
  431
+(defun jira-markup-prev-line-blank-p ()
  432
+  "Return t if the previous line is blank and nil otherwise.
  433
+If we are at the first line, then consider the previous line to be blank."
  434
+  (save-excursion
  435
+    (if (= (point-at-bol) (point-min))
  436
+        t
  437
+      (forward-line -1)
  438
+      (jira-markup-cur-line-blank-p))))
  439
+
  440
+(defun jira-markup-next-line-blank-p ()
  441
+  "Return t if the next line is blank and nil otherwise.
  442
+If we are at the last line, then consider the next line to be blank."
  443
+  (save-excursion
  444
+    (if (= (point-at-bol) (point-max))
  445
+        t
  446
+      (forward-line 1)
  447
+      (jira-markup-cur-line-blank-p))))
  448
+
  449
+(defun jira-markup-prev-line-indent-p ()
  450
+  "Return t if the previous line is indented and nil otherwise."
  451
+  (save-excursion
  452
+    (forward-line -1)
  453
+    (goto-char (point-at-bol))
  454
+    (if (re-search-forward "^\\s " (point-at-eol) t) t)))
  455
+
  456
+(defun jira-markup-cur-line-indent ()
  457
+  "Return the number of leading whitespace characters in the current line."
  458
+  (save-excursion
  459
+    (goto-char (point-at-bol))
  460
+    (re-search-forward "^\\s +" (point-at-eol) t)
  461
+    (current-column)))
  462
+
  463
+(defun jira-markup-prev-line-indent ()
  464
+  "Return the number of leading whitespace characters in the previous line."
  465
+  (save-excursion
  466
+    (forward-line -1)
  467
+    (jira-markup-cur-line-indent)))
  468
+
  469
+(defun jira-markup-next-line-indent ()
  470
+  "Return the number of leading whitespace characters in the next line."
  471
+  (save-excursion
  472
+    (forward-line 1)
  473
+    (jira-markup-cur-line-indent)))
  474
+
  475
+(defun jira-markup-cur-non-list-indent ()
  476
+  "Return the number of leading whitespace characters in the current line."
  477
+  (save-excursion
  478
+    (beginning-of-line)
  479
+    (when (re-search-forward jira-markup-regex-list-indent (point-at-eol) t)
  480
+      (current-column))))
  481
+
  482
+(defun jira-markup-prev-non-list-indent ()
  483
+  "Return position of the first non-list-marker on the previous line."
  484
+  (save-excursion
  485
+    (forward-line -1)
  486
+    (jira-markup-cur-non-list-indent)))
  487
+
  488
+(defun jira-markup--next-block ()
  489
+  "Move the point to the start of the next text block."
  490
+  (forward-line)
  491
+  (while (and (or (not (jira-markup-prev-line-blank-p))
  492
+                  (jira-markup-cur-line-blank-p))
  493
+              (not (eobp)))
  494
+    (forward-line)))
  495
+
  496
+(defun jira-markup--end-of-level (level)
  497
+  "Move the point to the end of region with indentation at least LEVEL."
  498
+  (let (indent)
  499
+    (while (and (not (< (setq indent (jira-markup-cur-line-indent)) level))
  500
+                (not (>= indent (+ level 4)))
  501
+                (not (eobp)))
  502
+      (jira-markup--next-block))
  503
+    (unless (eobp)
  504
+      ;; Move back before any trailing blank lines
  505
+      (while (and (jira-markup-prev-line-blank-p)
  506
+                  (not (bobp)))
  507
+        (forward-line -1))
  508
+      (forward-line -1)
  509
+      (end-of-line))))
  510
+
  511
+(defun jira-markup-match-pre-blocks (last)
  512
+  "Match Jira-Markup pre blocks from point to LAST.
  513
+A region matches as if it is indented at least four spaces
  514
+relative to the nearest previous block of lesser non-list-marker
  515
+indentation."
  516
+
  517
+  (let (cur-begin cur-end cur-indent prev-indent prev-list stop match found)
  518
+    ;; Don't start in the middle of a block
  519
+    (unless (and (bolp)
  520
+                 (jira-markup-prev-line-blank-p)
  521
+                 (not (jira-markup-cur-line-blank-p)))
  522
+      (jira-markup--next-block))
  523
+
  524
+    ;; Move to the first full block in the region with indent 4 or more
  525
+    (while (and (not (>= (setq cur-indent (jira-markup-cur-line-indent)) 4))
  526
+                (not (>= (point) last)))
  527
+      (jira-markup--next-block))
  528
+    (setq cur-begin (point))
  529
+    (jira-markup--end-of-level cur-indent)
  530
+    (setq cur-end (point))
  531
+    (setq match nil)
  532
+    (setq stop (> cur-begin cur-end))
  533
+
  534
+    (while (and (<= cur-end last) (not stop) (not match))
  535
+      ;; Move to the nearest preceding block of lesser (non-marker) indentation
  536
+      (setq prev-indent (+ cur-indent 1))
  537
+      (goto-char cur-begin)
  538
+      (setq found nil)
  539
+      (while (and (>= prev-indent cur-indent)
  540
+                  (not (and prev-list
  541
+                            (eq prev-indent cur-indent)))
  542
+                  (not (bobp)))
  543
+
  544
+        ;; Move point to the last line of the previous block.
  545
+        (forward-line -1)
  546
+        (while (and (jira-markup-cur-line-blank-p)
  547
+                    (not (bobp)))
  548
+          (forward-line -1))
  549
+
  550
+        ;; Update the indentation level using either the
  551
+        ;; non-list-marker indentation, if the previous line is the
  552
+        ;; start of a list, or the actual indentation.
  553
+        (setq prev-list (jira-markup-cur-non-list-indent))
  554
+        (setq prev-indent (or prev-list
  555
+                              (jira-markup-cur-line-indent)))
  556
+        (setq found t))
  557
+
  558
+      ;; If the loop didn't execute
  559
+      (unless found
  560
+        (setq prev-indent 0))
  561
+
  562
+      ;; Compare with prev-indent minus its remainder mod 4
  563
+      (setq prev-indent (- prev-indent (mod prev-indent 4)))
  564
+
  565
+      ;; Set match data and return t if we have a match
  566
+      (if (>= cur-indent (+ prev-indent 4))
  567
+          ;; Match
  568
+          (progn
  569
+            (setq match t)
  570
+            (set-match-data (list cur-begin cur-end))
  571
+            ;; Leave point at end of block
  572
+            (goto-char cur-end)
  573
+            (forward-line))
  574
+
  575
+        ;; Move to the next block (if possible)
  576
+        (goto-char cur-end)
  577
+        (jira-markup--next-block)
  578
+        (setq cur-begin (point))
  579
+        (setq cur-indent (jira-markup-cur-line-indent))
  580
+        (jira-markup--end-of-level cur-indent)
  581
+        (setq cur-end (point))
  582
+        (setq stop (equal cur-begin cur-end))))
  583
+    match))
  584
+
  585
+(defun jira-markup-match-fenced-code-blocks (last)
  586
+  "Match fenced code blocks from the point to LAST."
  587
+  (cond ((search-forward-regexp "^\\([~]\\{3,\\}\\)" last t)
  588
+         (beginning-of-line)
  589
+         (let ((beg (point)))
  590
+           (forward-line)
  591
+           (cond ((search-forward-regexp
  592
+                   (concat "^" (match-string 1) "~*") last t)
  593
+                  (set-match-data (list beg (point)))
  594
+                  t)
  595
+                 (t nil))))
  596
+        (t nil)))
  597
+
  598
+(defun jira-markup-font-lock-extend-region ()
  599
+  "Extend the search region to include an entire block of text.
  600
+This helps improve font locking for block constructs such as pre blocks."
  601
+  ;; Avoid compiler warnings about these global variables from font-lock.el.
  602
+  ;; See the documentation for variable `font-lock-extend-region-functions'.
  603
+  (eval-when-compile (defvar font-lock-beg) (defvar font-lock-end))
  604
+  (save-excursion
  605
+    (goto-char font-lock-beg)
  606
+    (let ((found (re-search-backward "\n\n" nil t)))
  607
+      (when found
  608
+        (goto-char font-lock-end)
  609
+        (when (re-search-forward "\n\n" nil t)
  610
+          (beginning-of-line)
  611
+          (setq font-lock-end (point)))
  612
+        (setq font-lock-beg found)))))
  613
+
  614
+
  615
+;;; Syntax Table ==============================================================
  616
+
  617
+(defvar jira-markup-mode-syntax-table
  618
+  (let ((jira-markup-mode-syntax-table (make-syntax-table)))
  619
+    (modify-syntax-entry ?\" "w" jira-markup-mode-syntax-table)
  620
+    jira-markup-mode-syntax-table)
  621
+  "Syntax table for `jira-markup-mode'.")
  622
+
  623
+
  624
+
  625
+;;; Element Insertion =========================================================
  626
+
  627
+(defun jira-markup-wrap-or-insert (s1 s2)
  628
+ "Insert the strings S1 and S2.
  629
+If Transient Mark mode is on and a region is active, wrap the strings S1
  630
+and S2 around the region."
  631
+ (if (and transient-mark-mode mark-active)
  632
+     (let ((a (region-beginning)) (b (region-end)))
  633
+       (goto-char a)
  634
+       (insert s1)
  635
+       (goto-char (+ b (length s1)))
  636
+       (insert s2))
  637
+   (insert s1 s2)))
  638
+
  639
+(defun jira-markup-insert-hr ()
  640
+  "Insert a horizonal rule using `jira-markup-hr-string'."
  641
+  (interactive)
  642
+  ;; Leading blank line
  643
+  (when (and (>= (point) (+ (point-min) 2))
  644
+             (not (looking-back "\n\n" 2)))
  645
+    (insert "\n"))
  646
+  ;; Insert custom HR string
  647
+  (insert (concat jira-markup-hr-string "\n"))
  648
+  ;; Following blank line
  649
+  (backward-char)
  650
+  (unless (looking-at "\n\n")
  651
+          (insert "\n")))
  652
+
  653
+(defun jira-markup-insert-bold ()
  654
+  "Insert markup for a bold word or phrase.
  655
+If Transient Mark mode is on and a region is active, it is made bold."
  656
+  (interactive)
  657
+  (jira-markup-wrap-or-insert "*" "*")
  658
+  (backward-char 1))
  659
+
  660
+(defun jira-markup-insert-italic ()
  661
+  "Insert markup for an italic word or phrase.
  662
+If Transient Mark mode is on and a region is active, it is made italic."
  663
+  (interactive)
  664
+  (jira-markup-wrap-or-insert "_" "_")
  665
+  (backward-char 1))
  666
+
  667
+(defun jira-markup-insert-code ()
  668
+  "Insert markup for an inline code fragment.
  669
+If Transient Mark mode is on and a region is active, it is marked
  670
+as inline code."
  671
+  (interactive)
  672
+  (jira-markup-wrap-or-insert "{{" "}}")
  673
+  (backward-char 2))
  674
+
  675
+(defun jira-markup-insert-link ()
  676
+  "Insert an inline link of the form []().
  677
+If Transient Mark mode is on and a region is active, it is used
  678
+as the link text."
  679
+  (interactive)
  680
+  (jira-markup-wrap-or-insert "[" "|")
  681
+  (insert "]")
  682
+  (backward-char 1))
  683
+
  684
+(defun jira-markup-insert-reference-link-region (url label title)
  685
+  "Insert a reference link at point using the region as the link text."
  686
+  (interactive "sLink URL: \nsLink Label (optional): \nsLink Title (optional): ")
  687
+  (let ((text (buffer-substring (region-beginning) (region-end))))
  688
+    (delete-region (region-beginning) (region-end))
  689
+    (jira-markup-insert-reference-link text url label title)))
  690
+
  691
+(defun jira-markup-insert-reference-link (text url label title)
  692
+  "Insert a reference link at point.
  693
+The link label definition is placed at the end of the current
  694
+paragraph."
  695
+  (interactive "sLink Text: \nsLink URL: \nsLink Label (optional): \nsLink Title (optional): ")
  696
+  (let (end)
  697
+    (insert (concat "[" text "][" label "]"))
  698
+    (setq end (point))
  699
+    (forward-paragraph)
  700
+    (insert "\n[")
  701
+    (if (> (length label) 0)
  702
+        (insert label)
  703
+      (insert text))
  704
+    (insert (concat "]: " url))
  705
+    (unless (> (length url) 0)
  706
+        (setq end (point)))
  707
+    (when (> (length title) 0)
  708
+      (insert (concat " \"" title "\"")))
  709
+    (insert "\n")
  710
+    (unless (looking-at "\n")
  711
+      (insert "\n"))
  712
+    (goto-char end)))
  713
+
  714
+(defun jira-markup-insert-wiki-link ()
  715
+  "Insert a wiki link of the form [[WikiLink]].
  716
+If Transient Mark mode is on and a region is active, it is used
  717
+as the link text."
  718
+  (interactive)
  719
+  (jira-markup-wrap-or-insert "[[" "]]")
  720
+  (backward-char 2))
  721
+
  722
+(defun jira-markup-insert-image ()
  723
+  "Insert an inline image tag of the form ![]().
  724
+If Transient Mark mode is on and a region is active, it is used
  725
+as the alt text of the image."
  726
+  (interactive)
  727
+  (jira-markup-wrap-or-insert "![" "]")
  728
+  (insert "()")
  729
+  (backward-char 1))
  730
+
  731
+(defun jira-markup-insert-header-1 ()
  732
+  "Insert a first level atx-style (hash mark) header.
  733
+If Transient Mark mode is on and a region is active, it is used
  734
+as the header text."
  735
+  (interactive)
  736
+  (jira-markup-insert-header 1))
  737
+
  738
+(defun jira-markup-insert-header-2 ()
  739
+  "Insert a second level atx-style (hash mark) header.
  740
+If Transient Mark mode is on and a region is active, it is used
  741
+as the header text."
  742
+  (interactive)
  743
+  (jira-markup-insert-header 2))
  744
+
  745
+(defun jira-markup-insert-header-3 ()
  746
+  "Insert a third level atx-style (hash mark) header.
  747
+If Transient Mark mode is on and a region is active, it is used
  748
+as the header text."
  749
+  (interactive)
  750
+  (jira-markup-insert-header 3))
  751
+
  752
+(defun jira-markup-insert-header-4 ()
  753
+  "Insert a fourth level atx-style (hash mark) header.
  754
+If Transient Mark mode is on and a region is active, it is used
  755
+as the header text."
  756
+  (interactive)
  757
+  (jira-markup-insert-header 4))
  758
+
  759
+(defun jira-markup-insert-header-5 ()
  760
+  "Insert a fifth level atx-style (hash mark) header.
  761
+If Transient Mark mode is on and a region is active, it is used
  762
+as the header text."
  763
+  (interactive)
  764
+  (jira-markup-insert-header 5))
  765
+
  766
+(defun jira-markup-insert-header-6 ()
  767
+  "Insert a sixth level atx-style (hash mark) header.
  768
+If Transient Mark mode is on and a region is active, it is used
  769
+as the header text."
  770
+  (interactive)
  771
+  (jira-markup-insert-header 6))
  772
+
  773
+(defun jira-markup-insert-header (n)
  774
+  "Insert a header.
  775
+With no prefix argument, insert a level-1 header.  With prefix N,
  776
+insert a level-N header.  If Transient Mark mode is on and the
  777
+region is active, it is used as the header text."
  778
+  (interactive "p")
  779
+  (unless n                             ; Test to see if n is defined
  780
+    (setq n 1))                         ; Default to level 1 header
  781
+  (jira-markup-wrap-or-insert (concat "h" (int-to-string n) ". ") ""))
  782
+
  783
+(defun jira-markup-insert-blockquote ()
  784
+  "Start a blockquote section (or blockquote the region).
  785
+If Transient Mark mode is on and a region is active, it is used as
  786
+the blockquote text."
  787
+  (interactive)
  788
+  (if (and (boundp 'transient-mark-mode) transient-mark-mode mark-active)
  789
+      (jira-markup-blockquote-region (region-beginning) (region-end))
  790
+    (insert "bq. ")))
  791
+
  792
+(defun jira-markup-block-region (beg end prefix)
  793
+  "Format the region using a block prefix.
  794
+Arguments BEG and END specify the beginning and end of the
  795
+region.  The characters PREFIX will appear at the beginning
  796
+of each line."
  797
+  (if mark-active
  798
+      (save-excursion
  799
+        ;; Ensure that there is a leading blank line
  800
+        (goto-char beg)
  801
+        (when (and (>= (point) (+ (point-min) 2))
  802
+                   (not (looking-back "\n\n" 2)))
  803
+          (insert "\n")
  804
+          (setq beg (1+ beg))
  805
+          (setq end (1+ end)))
  806
+        ;; Move back before any blank lines at the end
  807
+        (goto-char end)
  808
+        (while (and (looking-back "\n" 1)
  809
+                    (not (equal (point) (point-min))))
  810
+          (backward-char)
  811
+          (setq end (1- end)))
  812
+        ;; Ensure that there is a trailing blank line
  813
+        (goto-char end)
  814
+        (if (not (or (looking-at "\n\n")
  815
+                     (and (equal (1+ end) (point-max)) (looking-at "\n"))))
  816
+          (insert "\n"))
  817
+        ;; Insert PREFIX
  818
+        (goto-char beg)
  819
+        (beginning-of-line)
  820
+        (while (< (point-at-bol) end)
  821
+          (insert prefix)
  822
+          (setq end (+ (length prefix) end))
  823
+          (forward-line)))))
  824
+
  825
+(defun jira-markup-blockquote-region (beg end)
  826
+  "Blockquote the region.
  827
+Arguments BEG and END specify the beginning and end of the region."
  828
+  (interactive "*r")
  829
+  (jira-markup-block-region beg end "> "))
  830
+
  831
+(defun jira-markup-insert-pre ()
  832
+  "Start a preformatted section (or apply to the region).
  833
+If Transient Mark mode is on and a region is active, it is marked
  834
+as preformatted text."
  835
+  (interactive)
  836
+  (if (and (boundp 'transient-mark-mode) transient-mark-mode mark-active)
  837
+      (jira-markup-pre-region (region-beginning) (region-end))
  838
+    (insert "    ")))
  839
+
  840
+(defun jira-markup-pre-region (beg end)
  841
+  "Format the region as preformatted text.
  842
+Arguments BEG and END specify the beginning and end of the region."
  843
+  (interactive "*r")
  844
+  (jira-markup-block-region beg end "    "))
  845
+
  846
+
  847
+;;; Indentation ====================================================================
  848
+
  849
+(defun jira-markup-indent-find-next-position (cur-pos positions)
  850
+  "Return the position after the index of CUR-POS in POSITIONS."
  851
+  (while (and positions
  852
+              (not (equal cur-pos (car positions))))
  853
+    (setq positions (cdr positions)))
  854
+  (or (cadr positions) 0))
  855
+
  856
+(defun jira-markup-indent-line ()
  857
+  "Indent the current line using some heuristics.
  858
+If the _previous_ command was either `jira-markup-enter-key' or
  859
+`jira-markup-cycle', then we should cycle to the next
  860
+reasonable indentation position.  Otherwise, we could have been
  861
+called directly by `jira-markup-enter-key', by an initial call of
  862
+`jira-markup-cycle', or indirectly by `auto-fill-mode'.  In
  863
+these cases, indent to the default position."
  864
+  (interactive)
  865
+  (let ((positions (jira-markup-calc-indents))
  866
+        (cur-pos (current-column)))
  867
+    (if (not (equal this-command 'jira-markup-cycle))
  868
+        (indent-line-to (car positions))
  869
+      (setq positions (sort (delete-dups positions) '<))
  870
+      (indent-line-to
  871
+       (jira-markup-indent-find-next-position cur-pos positions)))))
  872
+
  873
+(defun jira-markup-calc-indents ()
  874
+  "Return a list of indentation columns to cycle through.
  875
+The first element in the returned list should be considered the
  876
+default indentation level."
  877
+  (let (pos prev-line-pos positions)
  878
+
  879
+    ;; Previous line indent
  880
+    (setq prev-line-pos (jira-markup-prev-line-indent))
  881
+    (setq positions (cons prev-line-pos positions))
  882
+
  883
+    ;; Previous non-list-marker indent
  884
+    (setq pos (jira-markup-prev-non-list-indent))
  885
+    (when pos
  886
+        (setq positions (cons pos positions))
  887
+        (setq positions (cons (+ pos tab-width) positions)))
  888
+
  889
+    ;; Indentation of the previous line + tab-width
  890
+    (cond
  891
+     (prev-line-pos
  892
+      (setq positions (cons (+ prev-line-pos tab-width) positions)))
  893
+     (t
  894
+      (setq positions (cons tab-width positions))))
  895
+
  896
+    ;; Indentation of the previous line - tab-width
  897
+    (if (and prev-line-pos
  898
+             (> prev-line-pos tab-width))
  899
+        (setq positions (cons (- prev-line-pos tab-width) positions)))
  900
+
  901
+    ;; Indentation of preceeding list item
  902
+    (setq pos
  903
+          (save-excursion
  904
+            (forward-line -1)
  905
+            (catch 'break
  906
+              (while (not (equal (point) (point-min)))
  907
+                (forward-line -1)
  908
+                (goto-char (point-at-bol))
  909
+                (when (re-search-forward jira-markup-regex-list-indent (point-at-eol) t)
  910
+                  (throw 'break (length (match-string 1)))))
  911
+              nil)))
  912
+    (if (and pos (not (eq pos prev-line-pos)))
  913
+        (setq positions (cons pos positions)))
  914
+
  915
+    ;; First column
  916
+    (setq positions (cons 0 positions))
  917
+
  918
+    (reverse positions)))
  919
+
  920
+(defun jira-markup-do-normal-return ()
  921
+  "Insert a newline and optionally indent the next line."
  922
+  (newline)
  923
+  (if jira-markup-indent-on-enter
  924
+      (funcall indent-line-function)))
  925
+
  926
+(defun jira-markup-enter-key ()
  927
+  "Handle RET according to context.
  928
+If there is a wiki link at the point, follow it unless
  929
+`jira-markup-follow-wiki-link-on-enter' is nil.  Otherwise, process
  930
+it in the usual way."
  931
+  (interactive)
  932
+  (if (and jira-markup-follow-wiki-link-on-enter (jira-markup-wiki-link-p))
  933
+      (jira-markup-follow-wiki-link-at-point)
  934
+    (jira-markup-do-normal-return)))
  935
+
  936
+
  937
+;;; Keymap ====================================================================
  938
+
  939
+(defvar jira-markup-mode-map
  940
+  (let ((map (make-keymap)))
  941
+    ;; Element insertion
  942
+    (define-key map "\C-c\C-al" 'jira-markup-insert-link)
  943
+    (define-key map "\C-c\C-ar" 'jira-markup-insert-reference-link-dwim)
  944
+    (define-key map "\C-c\C-aw" 'jira-markup-insert-wiki-link)
  945
+    (define-key map "\C-c\C-ii" 'jira-markup-insert-image)
  946
+    (define-key map "\C-c\C-t1" 'jira-markup-insert-header-1)
  947
+    (define-key map "\C-c\C-t2" 'jira-markup-insert-header-2)
  948
+    (define-key map "\C-c\C-t3" 'jira-markup-insert-header-3)
  949
+    (define-key map "\C-c\C-t4" 'jira-markup-insert-header-4)
  950
+    (define-key map "\C-c\C-t5" 'jira-markup-insert-header-5)
  951
+    (define-key map "\C-c\C-t6" 'jira-markup-insert-header-6)
  952
+    (define-key map "\C-c\C-pb" 'jira-markup-insert-bold)
  953
+    (define-key map "\C-c\C-ss" 'jira-markup-insert-bold)
  954
+    (define-key map "\C-c\C-pi" 'jira-markup-insert-italic)
  955
+    (define-key map "\C-c\C-se" 'jira-markup-insert-italic)
  956
+    (define-key map "\C-c\C-pf" 'jira-markup-insert-code)
  957
+    (define-key map "\C-c\C-sc" 'jira-markup-insert-code)
  958
+    (define-key map "\C-c\C-sb" 'jira-markup-insert-blockquote)
  959
+    (define-key map "\C-c\C-s\C-b" 'jira-markup-blockquote-region)
  960
+    (define-key map "\C-c\C-sp" 'jira-markup-insert-pre)
  961
+    (define-key map "\C-c\C-s\C-p" 'jira-markup-pre-region)
  962
+    (define-key map "\C-c-" 'jira-markup-insert-hr)
  963
+    (define-key map "\C-c\C-tt" 'jira-markup-insert-title)
  964
+    (define-key map "\C-c\C-ts" 'jira-markup-insert-section)
  965
+    ;; WikiLink Following
  966
+    (define-key map "\C-c\C-w" 'jira-markup-follow-wiki-link-at-point)
  967
+    (define-key map "\M-n" 'jira-markup-next-wiki-link)
  968
+    (define-key map "\M-p" 'jira-markup-previous-wiki-link)
  969
+    ;; Indentation
  970
+    (define-key map "\C-m" 'jira-markup-enter-key)
  971
+    ;; Header navigation
  972
+    (define-key map (kbd "C-M-n") 'outline-next-visible-heading)
  973
+    (define-key map (kbd "C-M-p") 'outline-previous-visible-heading)
  974
+    (define-key map (kbd "C-M-f") 'outline-forward-same-level)
  975
+    (define-key map (kbd "C-M-b") 'outline-backward-same-level)
  976
+    (define-key map (kbd "C-M-u") 'outline-up-heading)
  977
+    ;; Jira-Markup functions
  978
+    (define-key map "\C-c\C-cm" 'jira-markup)
  979
+    (define-key map "\C-c\C-cp" 'jira-markup-preview)
  980
+    (define-key map "\C-c\C-ce" 'jira-markup-export)
  981
+    (define-key map "\C-c\C-cv" 'jira-markup-export-and-view)
  982
+    ;; References
  983
+    (define-key map "\C-c\C-cc" 'jira-markup-check-refs)
  984
+    map)
  985
+  "Keymap for Jira-Markup major mode.")
  986
+
  987
+;;; Menu ==================================================================
  988
+
  989
+(easy-menu-define jira-markup-mode-menu jira-markup-mode-map
  990
+  "Menu for Jira-Markup mode"
  991
+  '("Jira"
  992
+    ("Headers"
  993
+     ["First level" jira-markup-insert-header-1]
  994
+     ["Second level" jira-markup-insert-header-2]
  995
+     ["Third level" jira-markup-insert-header-3]
  996
+     ["Fourth level" jira-markup-insert-header-4]
  997
+     ["Fifth level" jira-markup-insert-header-5]
  998
+     ["Sixth level" jira-markup-insert-header-6])
  999
+    "---"
  1000
+    ["Bold" jira-markup-insert-bold]
  1001
+    ["Italic" jira-markup-insert-italic]
  1002
+    ["Blockquote" jira-markup-insert-blockquote]
  1003
+    ["Preformatted" jira-markup-insert-pre]
  1004
+    ["Code" jira-markup-insert-code]
  1005
+    "---"
  1006
+    ["Insert inline link" jira-markup-insert-link]
  1007
+    ["Insert reference link" jira-markup-insert-reference-link-dwim]
  1008
+    ["Insert image" jira-markup-insert-image]
  1009
+    ["Insert horizontal rule" jira-markup-insert-hr]
  1010
+    ["Check references" jira-markup-check-refs]
  1011
+    "---"
  1012
+    ["Version" jira-markup-show-version]
  1013
+    ))
  1014
+
  1015
+
  1016
+;;; WikiLink Following/Markup =================================================
  1017
+
  1018
+(require 'thingatpt)
  1019
+
  1020
+(defun jira-markup-wiki-link-p ()
  1021
+  "Return non-nil when `point' is at a true wiki link.
  1022
+A true wiki link name matches `jira-markup-regex-wiki-link' but does not
  1023
+match the current file name after conversion.  This modifies the data
  1024
+returned by `match-data'.  Note that the potential wiki link name must
  1025
+be available via `match-string'."
  1026
+  (let ((case-fold-search nil))
  1027
+    (and (thing-at-point-looking-at jira-markup-regex-wiki-link)
  1028
+	 (or (not buffer-file-name)
  1029
+	     (not (string-equal (buffer-file-name)
  1030
+				(jira-markup-convert-wiki-link-to-filename
  1031
+                                 (jira-markup-wiki-link-link)))))
  1032
+	 (not (save-match-data
  1033
+		(save-excursion))))))
  1034
+
  1035
+(defun jira-markup-wiki-link-link ()
  1036
+  "Return the link part of the wiki link using current match data.
  1037
+The location of the link component depends on the value of
  1038
+`jira-markup-wiki-link-alias-first'."
  1039
+  (if jira-markup-wiki-link-alias-first
  1040
+      (or (match-string 3) (match-string 1))
  1041
+    (match-string 1)))
  1042
+
  1043
+(defun jira-markup-convert-wiki-link-to-filename (name)
  1044
+  "Generate a filename from the wiki link NAME.
  1045
+Spaces in NAME are replaced with `jira-markup-link-space-sub-char'.
  1046
+When in `gfm-mode', follow GitHub's conventions where [[Test Test]]
  1047
+and [[test test]] both map to Test-test.ext."
  1048
+  (let ((basename (jira-markup-replace-regexp-in-string
  1049
+                   "[[:space:]\n]" jira-markup-link-space-sub-char name)))
  1050
+    (when (eq major-mode 'gfm-mode)
  1051
+      (setq basename (concat (upcase (substring basename 0 1))
  1052
+                             (downcase (substring basename 1 nil)))))
  1053
+    (concat basename
  1054
+            (if (buffer-file-name)
  1055
+                (concat "."
  1056
+                        (file-name-extension (buffer-file-name)))))))
  1057
+
  1058
+(defun jira-markup-follow-wiki-link (name)
  1059
+  "Follow the wiki link NAME.
  1060
+Convert the name to a file name and call `find-file'.  Ensure that
  1061
+the new buffer remains in `jira-markup-mode'."
  1062
+  (let ((filename (jira-markup-convert-wiki-link-to-filename name)))
  1063
+    (find-file filename))
  1064
+  (jira-markup-mode))
  1065
+
  1066
+(defun jira-markup-follow-wiki-link-at-point ()
  1067
+  "Find Wiki Link at point.
  1068
+See `jira-markup-wiki-link-p' and `jira-markup-follow-wiki-link'."
  1069
+  (interactive)
  1070
+  (if (jira-markup-wiki-link-p)
  1071
+      (jira-markup-follow-wiki-link (jira-markup-wiki-link-link))
  1072
+    (error "Point is not at a Wiki Link")))
  1073
+
  1074
+(defun jira-markup-next-wiki-link ()
  1075
+  "Jump to next wiki link.
  1076
+See `jira-markup-wiki-link-p'."
  1077
+  (interactive)
  1078
+  (if (jira-markup-wiki-link-p)
  1079
+      ; At a wiki link already, move past it.
  1080
+      (goto-char (+ 1 (match-end 0))))
  1081
+  (save-match-data
  1082
+    ; Search for the next wiki link and move to the beginning.
  1083
+    (re-search-forward jira-markup-regex-wiki-link nil t)
  1084
+    (goto-char (match-beginning 0))))
  1085
+
  1086
+(defun jira-markup-previous-wiki-link ()
  1087
+  "Jump to previous wiki link.
  1088
+See `jira-markup-wiki-link-p'."
  1089
+  (interactive)
  1090
+  (re-search-backward jira-markup-regex-wiki-link nil t))
  1091
+
  1092
+(defun jira-markup-highlight-wiki-link (from to face)
  1093
+  "Highlight the wiki link in the region between FROM and TO using FACE."
  1094
+  (put-text-property from to 'font-lock-face face))
  1095
+
  1096
+(defun jira-markup-unfontify-region-wiki-links (from to)
  1097
+  "Remove wiki link faces from the region specified by FROM and TO."
  1098
+  (interactive "nfrom: \nnto: ")
  1099
+  (remove-text-properties from to '(font-lock-face jira-markup-link-face))
  1100
+  (remove-text-properties from to '(font-lock-face jira-markup-missing-link-face)))
  1101
+
  1102
+(defun jira-markup-fontify-region-wiki-links (from to)
  1103
+  "Search region given by FROM and TO for wiki links and fontify them.
  1104
+If a wiki link is found check to see if the backing file exists
  1105
+and highlight accordingly."
  1106
+  (goto-char from)
  1107
+  (while (re-search-forward jira-markup-regex-wiki-link to t)
  1108
+    (let ((highlight-beginning (match-beginning 0))
  1109
+	  (highlight-end (match-end 0))
  1110
+	  (file-name
  1111
+	   (jira-markup-convert-wiki-link-to-filename
  1112
+            (jira-markup-wiki-link-link))))
  1113
+      (if (file-exists-p file-name)
  1114
+	  (jira-markup-highlight-wiki-link
  1115
+	   highlight-beginning highlight-end jira-markup-link-face)
  1116
+	(jira-markup-highlight-wiki-link
  1117
+	 highlight-beginning highlight-end jira-markup-missing-link-face)))))
  1118
+
  1119
+(defun jira-markup-extend-changed-region (from to)
  1120
+  "Extend region given by FROM and TO so that we can fontify all links.
  1121
+The region is extended to the first newline before and the first
  1122
+newline after."
  1123
+  ;; start looking for the first new line before 'from
  1124
+  (goto-char from)
  1125
+  (re-search-backward "\n" nil t)
  1126
+  (let ((new-from (point-min))
  1127
+	(new-to (point-max)))
  1128
+    (if (not (= (point) from))
  1129
+	(setq new-from (point)))
  1130
+    ;; do the same thing for the first new line after 'to
  1131
+    (goto-char to)
  1132
+    (re-search-forward "\n" nil t)
  1133
+    (if (not (= (point) to))
  1134
+	(setq new-to (point)))
  1135
+    (list new-from new-to)))
  1136
+
  1137
+(defun jira-markup-check-change-for-wiki-link (from to change)
  1138
+  "Check region between FROM and TO for wiki links and re-fontfy as needed.
  1139
+Designed to be used with the `after-change-functions' hook.
  1140
+CHANGE is the number of bytes of pre-change text replaced by the
  1141
+given range."
  1142
+  (interactive "nfrom: \nnto: \nnchange: ")
  1143
+  (let* ((inhibit-quit t)
  1144
+	 (modified (buffer-modified-p))
  1145
+	 (buffer-undo-list t)
  1146
+	 (inhibit-read-only t)
  1147
+	 (inhibit-point-motion-hooks t)
  1148
+	 (inhibit-modification-hooks t)
  1149
+	 (current-point (point))
  1150
+	 deactivate-mark)
  1151
+    (unwind-protect
  1152
+        (save-match-data
  1153
+          (save-restriction
  1154
+            ;; Extend the region to fontify so that it starts
  1155
+            ;; and ends at safe places.
  1156
+            (multiple-value-bind (new-from new-to)
  1157
+                (jira-markup-extend-changed-region from to)
  1158
+              ;; Unfontify existing fontification (start from scratch)
  1159
+              (jira-markup-unfontify-region-wiki-links new-from new-to)
  1160
+              ;; Now do the fontification.
  1161
+              (jira-markup-fontify-region-wiki-links new-from new-to)))
  1162
+          (unless modified
  1163
+            (if (fboundp 'restore-buffer-modified-p)
  1164
+                (restore-buffer-modified-p nil)
  1165
+              (set-buffer-modified-p nil))))
  1166
+      (goto-char current-point))))
  1167
+
  1168
+(defun jira-markup-fontify-buffer-wiki-links ()
  1169
+  "Refontify all wiki links in the buffer."
  1170
+  (int