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