Klimi's new dotfiles with stow.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

472 line
17 KiB

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