config

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

yaml-mode.el (18519B)


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