config

Personal configuration.
git clone git://code.dwrz.net/config
Log | Files | Refs

yaml-mode.el (18471B)


      1 ;;; yaml-mode.el --- Major mode for editing YAML files -*- lexical-binding: t -*-
      2 
      3 ;; Copyright (C) 2010-2014 Yoshiki Kurihara
      4 
      5 ;; Author: Yoshiki Kurihara <clouder@gmail.com>
      6 ;;         Marshall T. Vandegrift <llasram@gmail.com>
      7 ;; Maintainer: Vasilij Schneidermann <mail@vasilij.de>
      8 ;; URL: https://github.com/yoshiki/yaml-mode
      9 ;; Package-Requires: ((emacs "24.1"))
     10 ;; Keywords: data yaml
     11 ;; Version: 0.0.16
     12 
     13 ;; This file is not part of Emacs
     14 
     15 ;; This program is free software; you can redistribute it and/or modify
     16 ;; it under the terms of the GNU General Public License as published by
     17 ;; the Free Software Foundation, either version 3 of the License, or
     18 ;; (at your option) any later version.
     19 
     20 ;; This program is distributed in the hope that it will be useful,
     21 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
     22 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     23 ;; GNU General Public License for more details.
     24 
     25 ;; You should have received a copy of the GNU General Public License
     26 ;; along with this program.  If not, see <https://www.gnu.org/licenses/>.
     27 
     28 ;;; Commentary:
     29 
     30 ;; This is a major mode for editing files in the YAML data
     31 ;; serialization format.  It was initially developed by Yoshiki
     32 ;; Kurihara and many features were added by Marshall Vandegrift.  As
     33 ;; YAML and Python share the fact that indentation determines
     34 ;; structure, this mode provides indentation and indentation command
     35 ;; behavior very similar to that of python-mode.
     36 
     37 ;;; Installation:
     38 
     39 ;; To install, just drop this file into a directory in your
     40 ;; `load-path' and (optionally) byte-compile it.  To automatically
     41 ;; handle files ending in '.yml', add something like:
     42 ;;
     43 ;;    (require 'yaml-mode)
     44 ;;    (add-to-list 'auto-mode-alist '("\\.yml\\'" . yaml-mode))
     45 ;;
     46 ;; to your .emacs file.
     47 ;;
     48 ;; Unlike python-mode, this mode follows the Emacs convention of not
     49 ;; binding the ENTER key to `newline-and-indent'.  To get this
     50 ;; behavior, add the key definition to `yaml-mode-hook':
     51 ;;
     52 ;;    (add-hook 'yaml-mode-hook
     53 ;;     '(lambda ()
     54 ;;        (define-key yaml-mode-map "\C-m" 'newline-and-indent)))
     55 
     56 ;;; Known Bugs:
     57 
     58 ;; YAML is easy to write but complex to parse, and this mode doesn't
     59 ;; even really try.  Indentation and highlighting will break on
     60 ;; abnormally complicated structures.
     61 
     62 ;;; Code:
     63 
     64 
     65 ;; User definable variables
     66 
     67 ;;;###autoload
     68 (defgroup yaml nil
     69   "Support for the YAML serialization format"
     70   :group 'languages
     71   :prefix "yaml-")
     72 
     73 (defcustom yaml-mode-hook nil
     74   "*Hook run by `yaml-mode'."
     75   :type 'hook
     76   :group 'yaml)
     77 
     78 (defcustom yaml-indent-offset 2
     79   "*Amount of offset per level of indentation."
     80   :type 'integer
     81   :safe 'natnump
     82   :group 'yaml)
     83 
     84 (defcustom yaml-backspace-function 'backward-delete-char-untabify
     85   "*Function called by `yaml-electric-backspace' when deleting backwards.
     86 It will receive one argument, the numeric prefix value."
     87   :type 'function
     88   :group 'yaml)
     89 
     90 (defcustom yaml-block-literal-search-lines 100
     91   "*Maximum number of lines to search for start of block literals."
     92   :type 'integer
     93   :group 'yaml)
     94 
     95 (defcustom yaml-block-literal-electric-alist
     96   '((?| . "") (?> . "-"))
     97   "*Characters for which to provide electric behavior.
     98 The association list key should be a key code and the associated value
     99 should be a string containing additional characters to insert when
    100 that key is pressed to begin a block literal."
    101   :type 'alist
    102   :group 'yaml)
    103 
    104 (defface yaml-tab-face
    105   '((((class color)) (:background "red" :foreground "red" :bold t))
    106     (t (:reverse-video t)))
    107   "Face to use for highlighting tabs in YAML files."
    108   :group 'faces
    109   :group 'yaml)
    110 
    111 (defcustom yaml-imenu-generic-expression
    112   '((nil  "^\\(:?[a-zA-Z_-]+\\):"          1))
    113   "The imenu regex to parse an outline of the yaml file."
    114   :type 'string
    115   :group 'yaml)
    116 
    117 
    118 ;; Constants
    119 
    120 (defconst yaml-mode-version "0.0.15" "Version of `yaml-mode'.")
    121 
    122 (defconst yaml-blank-line-re "^ *$"
    123   "Regexp matching a line containing only (valid) whitespace.")
    124 
    125 (defconst yaml-directive-re "^\\(?:--- \\)? *%\\(\\w+\\)"
    126   "Regexp matching a line containing a YAML directive.")
    127 
    128 (defconst yaml-document-delimiter-re "^\\(?:---\\|[.][.][.]\\)"
    129   "Rexexp matching a YAML document delimiter line.")
    130 
    131 (defconst yaml-node-anchor-alias-re "[&*][a-zA-Z0-9_-]+"
    132   "Regexp matching a YAML node anchor or alias.")
    133 
    134 (defconst yaml-tag-re "!!?[^ \n]+"
    135   "Rexexp matching a YAML tag.")
    136 
    137 (defconst yaml-bare-scalar-re
    138   "\\(?:[^-:,#!\n{\\[ ]\\|[^#!\n{\\[ ]\\S-\\)[^#\n]*?"
    139   "Rexexp matching a YAML bare scalar.")
    140 
    141 (defconst yaml-hash-key-re
    142   (concat "\\(?:^\\(?:--- \\)?\\|{\\|\\(?:[-,] +\\)+\\) *"
    143           "\\(?:" yaml-tag-re " +\\)?"
    144           "\\(" yaml-bare-scalar-re "\\) *:"
    145           "\\(?: +\\|$\\)")
    146   "Regexp matching a single YAML hash key.")
    147 
    148 (defconst yaml-scalar-context-re
    149   (concat "\\(?:^\\(?:--- \\)?\\|{\\|\\(?: *[-,] +\\)+\\) *"
    150           "\\(?:" yaml-bare-scalar-re " *: \\)?")
    151   "Regexp indicating the beginning of a scalar context.")
    152 
    153 (defconst yaml-nested-map-re
    154   (concat "[^#\n]*: *\\(?:&.*\\|{ *\\|" yaml-tag-re " *\\)?$")
    155   "Regexp matching a line beginning a YAML nested structure.")
    156 
    157 (defconst yaml-block-literal-base-re " *[>|][-+0-9]* *\\(?:\n\\|\\'\\)"
    158   "Regexp matching the substring start of a block literal.")
    159 
    160 (defconst yaml-block-literal-re
    161   (concat yaml-scalar-context-re
    162           "\\(?:" yaml-tag-re "\\)?"
    163           yaml-block-literal-base-re)
    164   "Regexp matching a line beginning a YAML block literal.")
    165 
    166 (defconst yaml-nested-sequence-re
    167   (concat "^\\(?:\\(?: *- +\\)+\\|\\(:? *-$\\)\\)"
    168           "\\(?:" yaml-bare-scalar-re " *:\\(?: +.*\\)?\\)?$")
    169   "Regexp matching a line containing one or more nested YAML sequences.")
    170 
    171 (defconst yaml-constant-scalars-re
    172   (concat "\\(?:^\\|\\(?::\\|-\\|,\\|{\\|\\[\\) +\\) *"
    173           (regexp-opt
    174            '("~" "null" "Null" "NULL"
    175              ".nan" ".NaN" ".NAN"
    176              ".inf" ".Inf" ".INF"
    177              "-.inf" "-.Inf" "-.INF"
    178              "y" "Y" "yes" "Yes" "YES" "n" "N" "no" "No" "NO"
    179              "true" "True" "TRUE" "false" "False" "FALSE"
    180              "on" "On" "ON" "off" "Off" "OFF") t)
    181           "\\_>")
    182   "Regexp matching certain scalar constants in scalar context.")
    183 
    184 
    185 ;; Mode setup
    186 
    187 (defvar yaml-mode-map
    188   (let ((map (make-sparse-keymap)))
    189     (define-key map "|" 'yaml-electric-bar-and-angle)
    190     (define-key map ">" 'yaml-electric-bar-and-angle)
    191     (define-key map "-" 'yaml-electric-dash-and-dot)
    192     (define-key map "." 'yaml-electric-dash-and-dot)
    193     (define-key map (kbd "DEL") 'yaml-electric-backspace)
    194     map)
    195   "Keymap used in `yaml-mode' buffers.")
    196 
    197 (defvar yaml-mode-syntax-table
    198   (let ((syntax-table (make-syntax-table)))
    199     (modify-syntax-entry ?\' "\"" syntax-table)
    200     (modify-syntax-entry ?\" "\"" syntax-table)
    201     (modify-syntax-entry ?# "<" syntax-table)
    202     (modify-syntax-entry ?\n ">" syntax-table)
    203     (modify-syntax-entry ?\\ "\\" syntax-table)
    204     (modify-syntax-entry ?- "_" syntax-table)
    205     (modify-syntax-entry ?_ "_" syntax-table)
    206     (modify-syntax-entry ?& "." syntax-table)
    207     (modify-syntax-entry ?* "." syntax-table)
    208     (modify-syntax-entry ?\( "." syntax-table)
    209     (modify-syntax-entry ?\) "." syntax-table)
    210     (modify-syntax-entry ?\{ "(}" syntax-table)
    211     (modify-syntax-entry ?\} "){" syntax-table)
    212     (modify-syntax-entry ?\[ "(]" syntax-table)
    213     (modify-syntax-entry ?\] ")[" syntax-table)
    214     syntax-table)
    215   "Syntax table in use in `yaml-mode' buffers.")
    216 
    217 ;;;###autoload
    218 (define-derived-mode yaml-mode text-mode "YAML"
    219   "Simple mode to edit YAML.
    220 
    221 \\{yaml-mode-map}"
    222   :syntax-table yaml-mode-syntax-table
    223   (set (make-local-variable 'comment-start) "# ")
    224   (set (make-local-variable 'comment-start-skip) "#+ *")
    225   (set (make-local-variable 'comment-end) "")
    226   (set (make-local-variable 'indent-line-function) 'yaml-indent-line)
    227   (set (make-local-variable 'indent-tabs-mode) nil)
    228   (set (make-local-variable 'fill-paragraph-function) 'yaml-fill-paragraph)
    229   (set (make-local-variable 'page-delimiter) "^---\\([ \t].*\\)*\n")
    230 
    231   (set (make-local-variable 'syntax-propertize-function)
    232        'yaml-mode-syntax-propertize-function)
    233   (setq font-lock-defaults '(yaml-font-lock-keywords)))
    234 
    235 
    236 ;; Font-lock support
    237 
    238 (defvar yaml-font-lock-keywords
    239   `((yaml-font-lock-block-literals 0 font-lock-string-face)
    240     (,yaml-constant-scalars-re . (1 font-lock-constant-face))
    241     (,yaml-tag-re . (0 font-lock-type-face))
    242     (,yaml-node-anchor-alias-re . (0 font-lock-function-name-face))
    243     (,yaml-hash-key-re . (1 font-lock-variable-name-face))
    244     (,yaml-document-delimiter-re . (0 font-lock-comment-face))
    245     (,yaml-directive-re . (1 font-lock-builtin-face))
    246     ("^[\t]+" 0 'yaml-tab-face t))
    247   "Additional expressions to highlight in YAML mode.")
    248 
    249 (defun yaml-mode-syntax-propertize-function (beg end)
    250   "Override buffer's syntax table for special syntactic constructs."
    251   ;; Unhighlight foo#bar tokens between BEG and END.
    252   (save-excursion
    253     (goto-char beg)
    254     (while (search-forward "#" end t)
    255       (save-excursion
    256         (forward-char -1)
    257         ;; both ^# and [ \t]# are comments
    258         (when (and (not (bolp))
    259                    (not (memq (preceding-char) '(?\s ?\t))))
    260           (put-text-property (point) (1+ (point))
    261                              'syntax-table (string-to-syntax "_"))))))
    262 
    263   (save-excursion
    264     (goto-char beg)
    265     (while (and
    266             (> end (point))
    267             (re-search-forward "['\"]" end t))
    268       (when (get-text-property (point) 'yaml-block-literal)
    269         (put-text-property (1- (point)) (point)
    270                            'syntax-table (string-to-syntax "w")))
    271       (let* ((pt (point))
    272              (sps (save-excursion (syntax-ppss (1- pt)))))
    273         (when (not (nth 8 sps))
    274           (cond
    275            ((and (char-equal ?' (char-before (1- pt)))
    276                  (char-equal ?' (char-before pt)))
    277             (put-text-property (- pt 2) pt
    278                                'syntax-table (string-to-syntax "w"))
    279             ;; Workaround for https://debbugs.gnu.org/41195.
    280             (let ((syntax-propertize--done syntax-propertize--done))
    281               ;; Carefully invalidate the last cached ppss.
    282               (syntax-ppss-flush-cache (- pt 2))))
    283            ;; If quote is detected as a syntactic string start but appeared
    284            ;; after a non-whitespace character, then mark it as syntactic word.
    285            ((and (char-before (1- pt))
    286                  (char-equal ?w (char-syntax (char-before (1- pt)))))
    287             (put-text-property (1- pt) pt
    288                                'syntax-table (string-to-syntax "w")))
    289            (t
    290             ;; We're right after a quote that opens a string literal.
    291             ;; Skip over it (big speedup for long JSON strings).
    292             (goto-char (1- pt))
    293             (condition-case nil
    294                 (forward-sexp)
    295               (scan-error
    296                (goto-char end))))))))))
    297 
    298 (defun yaml-font-lock-block-literals (bound)
    299   "Find lines within block literals.
    300 Find the next line of the first (if any) block literal after point and
    301 prior to BOUND.  Returns the beginning and end of the block literal
    302 line in the match data, as consumed by `font-lock-keywords' matcher
    303 functions.  The function begins by searching backwards to determine
    304 whether or not the current line is within a block literal.  This could
    305 be time-consuming in large buffers, so the number of lines searched is
    306 artificially limited to the value of
    307 `yaml-block-literal-search-lines'."
    308   (if (eolp) (goto-char (1+ (point))))
    309   (unless (or (eobp) (>= (point) bound))
    310     (let ((begin (point))
    311           (end (min (1+ (line-end-position)) bound)))
    312       (goto-char (line-beginning-position))
    313       (while (and (looking-at yaml-blank-line-re)
    314                   (not (bobp)))
    315         (forward-line -1))
    316       (let ((nlines yaml-block-literal-search-lines)
    317             (min-level (current-indentation)))
    318         (forward-line -1)
    319         (while (and (/= nlines 0)
    320                     (/= min-level 0)
    321                     (not (looking-at yaml-block-literal-re))
    322                     (not (bobp)))
    323           (setq nlines (1- nlines))
    324           (unless (looking-at yaml-blank-line-re)
    325             (setq min-level (min min-level (current-indentation))))
    326           (forward-line -1))
    327         (when (looking-at-p " *- ")
    328           (setq min-level (- min-level 2)))
    329         (cond
    330          ((and (< (current-indentation) min-level)
    331                (looking-at yaml-block-literal-re))
    332           (goto-char end)
    333           (put-text-property begin end 'yaml-block-literal t)
    334           (set-match-data (list begin end))
    335           t)
    336          ((progn
    337             (goto-char begin)
    338             (re-search-forward (concat yaml-block-literal-re
    339                                        " *\\(.*\\)\n")
    340                                bound t))
    341           (let ((range (nthcdr 2 (match-data))))
    342             (put-text-property (car range) (cadr range) 'yaml-block-literal t)
    343             (set-match-data range))
    344           t))))))
    345 
    346 
    347 ;; Indentation and electric keys
    348 
    349 (defun yaml-compute-indentation ()
    350   "Calculate the maximum sensible indentation for the current line."
    351   (save-excursion
    352     (beginning-of-line)
    353     (if (looking-at yaml-document-delimiter-re) 0
    354       (forward-line -1)
    355       (while (and (looking-at yaml-blank-line-re)
    356                   (> (point) (point-min)))
    357         (forward-line -1))
    358       (+ (current-indentation)
    359          (if (looking-at yaml-nested-map-re) yaml-indent-offset 0)
    360          (if (looking-at yaml-nested-sequence-re) yaml-indent-offset 0)
    361          (if (looking-at yaml-block-literal-re) yaml-indent-offset 0)))))
    362 
    363 (defun yaml-indent-line ()
    364   "Indent the current line.
    365 The first time this command is used, the line will be indented to the
    366 maximum sensible indentation.  Each immediately subsequent usage will
    367 back-dent the line by `yaml-indent-offset' spaces.  On reaching column
    368 0, it will cycle back to the maximum sensible indentation."
    369   (interactive "*")
    370   (let ((ci (current-indentation))
    371         (need (yaml-compute-indentation)))
    372     (save-excursion
    373       (if (and (equal last-command this-command) (/= ci 0))
    374           (indent-line-to (* (/ (- ci 1) yaml-indent-offset) yaml-indent-offset))
    375         (indent-line-to need)))
    376     (if (< (current-column) (current-indentation))
    377         (forward-to-indentation 0))))
    378 
    379 (defun yaml-electric-backspace (arg)
    380   "Delete characters or back-dent the current line.
    381 If invoked following only whitespace on a line, will back-dent to the
    382 immediately previous multiple of `yaml-indent-offset' spaces."
    383   (interactive "*p")
    384   (if (or (/= (current-indentation) (current-column)) (bolp))
    385       (funcall yaml-backspace-function arg)
    386     (let ((ci (current-column)))
    387       (beginning-of-line)
    388       (delete-horizontal-space)
    389       (indent-to (* (/ (- ci (* arg yaml-indent-offset))
    390                        yaml-indent-offset)
    391                     yaml-indent-offset)))))
    392 
    393 (defun yaml-electric-bar-and-angle (arg)
    394   "Insert the bound key and possibly begin a block literal.
    395 Inserts the bound key.  If inserting the bound key causes the current
    396 line to match the initial line of a block literal, then inserts the
    397 matching string from `yaml-block-literal-electric-alist', a newline,
    398 and indents appropriately."
    399   (interactive "*P")
    400   (self-insert-command (prefix-numeric-value arg))
    401   (let ((extra-chars
    402          (assoc last-command-event
    403                 yaml-block-literal-electric-alist)))
    404     (cond
    405      ((and extra-chars (not arg) (eolp)
    406            (save-excursion
    407              (beginning-of-line)
    408              (looking-at yaml-block-literal-re)))
    409       (insert (cdr extra-chars))
    410       (newline-and-indent)))))
    411 
    412 (defun yaml-electric-dash-and-dot (arg)
    413   "Insert the bound key and possibly de-dent line.
    414 Inserts the bound key.  If inserting the bound key causes the current
    415 line to match a document delimiter, de-dent the line to the left
    416 margin."
    417   (interactive "*P")
    418   (self-insert-command (prefix-numeric-value arg))
    419   (save-excursion
    420     (beginning-of-line)
    421     (when (and (not arg) (looking-at yaml-document-delimiter-re))
    422       (delete-horizontal-space))))
    423 
    424 (defun yaml-narrow-to-block-literal ()
    425   "Narrow the buffer to block literal if the point is in it,
    426 otherwise do nothing."
    427   (interactive)
    428   (save-excursion
    429     (goto-char (line-beginning-position))
    430     (while (and (looking-at-p yaml-blank-line-re) (not (bobp)))
    431       (forward-line -1))
    432     (let ((nlines yaml-block-literal-search-lines)
    433           (min-level (current-indentation))
    434           beg)
    435       (forward-line -1)
    436       (while (and (/= nlines 0)
    437                   (/= min-level 0)
    438                   (not (looking-at-p yaml-block-literal-re))
    439                   (not (bobp)))
    440         (setq nlines (1- nlines))
    441         (unless (looking-at-p yaml-blank-line-re)
    442           (setq min-level (min min-level (current-indentation))))
    443         (forward-line -1))
    444       (when (and (< (current-indentation) min-level)
    445                  (looking-at-p yaml-block-literal-re))
    446         (setq min-level (current-indentation))
    447         (forward-line)
    448         (setq beg (point))
    449         (while (and (not (eobp))
    450                     (or (looking-at-p yaml-blank-line-re)
    451                         (> (current-indentation) min-level)))
    452           (forward-line))
    453         (narrow-to-region beg (point))))))
    454 
    455 (defun yaml-fill-paragraph (&optional justify region)
    456   "Fill paragraph.
    457 Outside of comments, this behaves as `fill-paragraph' except that
    458 filling does not cross boundaries of block literals.  Inside comments,
    459 this will do usual adaptive fill behaviors."
    460   (interactive "*P")
    461   (save-restriction
    462     (yaml-narrow-to-block-literal)
    463     (let ((fill-paragraph-function nil))
    464       (or (fill-comment-paragraph justify)
    465           (fill-paragraph justify region)))))
    466 
    467 (defun yaml-set-imenu-generic-expression ()
    468   (make-local-variable 'imenu-generic-expression)
    469   (make-local-variable 'imenu-create-index-function)
    470   (setq imenu-create-index-function 'imenu-default-create-index-function)
    471   (setq imenu-generic-expression yaml-imenu-generic-expression))
    472 
    473 (add-hook 'yaml-mode-hook 'yaml-set-imenu-generic-expression)
    474 
    475 
    476 (defun yaml-mode-version ()
    477   "Display version of `yaml-mode'."
    478   (interactive)
    479   (message "yaml-mode %s" yaml-mode-version)
    480   yaml-mode-version)
    481 
    482 ;;;###autoload
    483 (add-to-list 'auto-mode-alist '("\\.\\(e?ya?\\|ra\\)ml\\'" . yaml-mode))
    484 
    485 ;;;###autoload
    486 (add-to-list 'magic-mode-alist
    487              '("^%YAML\\s-+[0-9]+\\.[0-9]+\\(\\s-+#\\|\\s-*$\\)" . yaml-mode))
    488 
    489 (provide 'yaml-mode)
    490 
    491 ;;; yaml-mode.el ends here