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