yaml-mode.el (18100B)
1 ;;; yaml-mode.el --- Major mode for editing YAML files 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 ;; Package-Requires: ((emacs "24.1")) 9 ;; Keywords: data yaml 10 ;; Version: 0.0.15 11 12 ;; This file is not part of Emacs 13 14 ;; This file is free software; you can redistribute it and/or modify 15 ;; it under the terms of the GNU General Public License as published by 16 ;; the Free Software Foundation; either version 2, or (at your option) 17 ;; any later version. 18 19 ;; This file is distributed in the hope that it will be useful, 20 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of 21 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 ;; GNU General Public License for more details. 23 24 ;; You should have received a copy of the GNU General Public License along 25 ;; with this program; if not, write to the Free Software Foundation, Inc., 26 ;; 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 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 contatining 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 'indent-line-function) 'yaml-indent-line) 226 (set (make-local-variable 'indent-tabs-mode) nil) 227 (set (make-local-variable 'fill-paragraph-function) 'yaml-fill-paragraph) 228 229 (set (make-local-variable 'syntax-propertize-function) 230 'yaml-mode-syntax-propertize-function) 231 (setq font-lock-defaults '(yaml-font-lock-keywords))) 232 233 234 ;; Font-lock support 235 236 (defvar yaml-font-lock-keywords 237 `((yaml-font-lock-block-literals 0 font-lock-string-face) 238 (,yaml-constant-scalars-re . (1 font-lock-constant-face)) 239 (,yaml-tag-re . (0 font-lock-type-face)) 240 (,yaml-node-anchor-alias-re . (0 font-lock-function-name-face)) 241 (,yaml-hash-key-re . (1 font-lock-variable-name-face)) 242 (,yaml-document-delimiter-re . (0 font-lock-comment-face)) 243 (,yaml-directive-re . (1 font-lock-builtin-face)) 244 ("^[\t]+" 0 'yaml-tab-face t)) 245 "Additional expressions to highlight in YAML mode.") 246 247 (defun yaml-mode-syntax-propertize-function (beg end) 248 "Override buffer's syntax table for special syntactic constructs." 249 ;; Unhighlight foo#bar tokens between BEG and END. 250 (save-excursion 251 (goto-char beg) 252 (while (search-forward "#" end t) 253 (save-excursion 254 (forward-char -1) 255 ;; both ^# and [ \t]# are comments 256 (when (and (not (bolp)) 257 (not (memq (preceding-char) '(?\s ?\t)))) 258 (put-text-property (point) (1+ (point)) 259 'syntax-table (string-to-syntax "_")))))) 260 261 (save-excursion 262 (goto-char beg) 263 (while (and 264 (> end (point)) 265 (re-search-forward "['\"]" end t)) 266 (when (get-text-property (point) 'yaml-block-literal) 267 (put-text-property (1- (point)) (point) 268 'syntax-table (string-to-syntax "w"))) 269 (let* ((pt (point)) 270 (sps (save-excursion (syntax-ppss (1- pt))))) 271 (when (not (nth 8 sps)) 272 (cond 273 ((and (char-equal ?' (char-before (1- pt))) 274 (char-equal ?' (char-before pt))) 275 (put-text-property (- pt 2) pt 276 'syntax-table (string-to-syntax "w")) 277 ;; Workaround for https://debbugs.gnu.org/41195. 278 (let ((syntax-propertize--done syntax-propertize--done)) 279 ;; Carefully invalidate the last cached ppss. 280 (syntax-ppss-flush-cache (- pt 2)))) 281 ;; If quote is detected as a syntactic string start but appeared 282 ;; after a non-whitespace character, then mark it as syntactic word. 283 ((and (char-before (1- pt)) 284 (char-equal ?w (char-syntax (char-before (1- pt))))) 285 (put-text-property (1- pt) pt 286 'syntax-table (string-to-syntax "w"))) 287 (t 288 ;; We're right after a quote that opens a string literal. 289 ;; Skip over it (big speedup for long JSON strings). 290 (goto-char (1- pt)) 291 (condition-case nil 292 (forward-sexp) 293 (scan-error 294 (goto-char end)))))))))) 295 296 (defun yaml-font-lock-block-literals (bound) 297 "Find lines within block literals. 298 Find the next line of the first (if any) block literal after point and 299 prior to BOUND. Returns the beginning and end of the block literal 300 line in the match data, as consumed by `font-lock-keywords' matcher 301 functions. The function begins by searching backwards to determine 302 whether or not the current line is within a block literal. This could 303 be time-consuming in large buffers, so the number of lines searched is 304 artificially limited to the value of 305 `yaml-block-literal-search-lines'." 306 (if (eolp) (goto-char (1+ (point)))) 307 (unless (or (eobp) (>= (point) bound)) 308 (let ((begin (point)) 309 (end (min (1+ (point-at-eol)) bound))) 310 (goto-char (point-at-bol)) 311 (while (and (looking-at yaml-blank-line-re) 312 (not (bobp))) 313 (forward-line -1)) 314 (let ((nlines yaml-block-literal-search-lines) 315 (min-level (current-indentation))) 316 (forward-line -1) 317 (while (and (/= nlines 0) 318 (/= min-level 0) 319 (not (looking-at yaml-block-literal-re)) 320 (not (bobp))) 321 (setq nlines (1- nlines)) 322 (unless (looking-at yaml-blank-line-re) 323 (setq min-level (min min-level (current-indentation)))) 324 (forward-line -1)) 325 (when (looking-at-p " *- ") 326 (setq min-level (- min-level 2))) 327 (cond 328 ((and (< (current-indentation) min-level) 329 (looking-at yaml-block-literal-re)) 330 (goto-char end) 331 (put-text-property begin end 'yaml-block-literal t) 332 (set-match-data (list begin end)) 333 t) 334 ((progn 335 (goto-char begin) 336 (re-search-forward (concat yaml-block-literal-re 337 " *\\(.*\\)\n") 338 bound t)) 339 (let ((range (nthcdr 2 (match-data)))) 340 (put-text-property (car range) (cadr range) 'yaml-block-literal t) 341 (set-match-data range)) 342 t)))))) 343 344 345 ;; Indentation and electric keys 346 347 (defun yaml-compute-indentation () 348 "Calculate the maximum sensible indentation for the current line." 349 (save-excursion 350 (beginning-of-line) 351 (if (looking-at yaml-document-delimiter-re) 0 352 (forward-line -1) 353 (while (and (looking-at yaml-blank-line-re) 354 (> (point) (point-min))) 355 (forward-line -1)) 356 (+ (current-indentation) 357 (if (looking-at yaml-nested-map-re) yaml-indent-offset 0) 358 (if (looking-at yaml-nested-sequence-re) yaml-indent-offset 0) 359 (if (looking-at yaml-block-literal-re) yaml-indent-offset 0))))) 360 361 (defun yaml-indent-line () 362 "Indent the current line. 363 The first time this command is used, the line will be indented to the 364 maximum sensible indentation. Each immediately subsequent usage will 365 back-dent the line by `yaml-indent-offset' spaces. On reaching column 366 0, it will cycle back to the maximum sensible indentation." 367 (interactive "*") 368 (let ((ci (current-indentation)) 369 (cc (current-column)) 370 (need (yaml-compute-indentation))) 371 (save-excursion 372 (beginning-of-line) 373 (delete-horizontal-space) 374 (if (and (equal last-command this-command) (/= ci 0)) 375 (indent-to (* (/ (- ci 1) yaml-indent-offset) yaml-indent-offset)) 376 (indent-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 (point-at-bol)) 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 (provide 'yaml-mode) 487 488 ;;; yaml-mode.el ends here