use cooper theme -- end of git, I am trying livemesh
[srid.dotfiles.git] / emacs / external / yaml-mode.el
blob85b8da79beb441e489d5aec1f7f02ccf9f2846ab
1 ;;; yaml-mode.el --- Major mode for editing YAML files
3 ;; Copyright (C) 2006 Yoshiki Kurihara
5 ;; Author: Yoshiki Kurihara <kurihara@cpan.org>
6 ;; Marshall T. Vandegrift <llasram@gmail.com>
7 ;; Keywords: data yaml
8 ;; Version: 0.0.3
10 ;; This file is not part of Emacs
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.
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.
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.
27 ;;; Commentary:
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.
36 ;;; Installation:
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:
42 ;; (require 'yaml-mode)
43 ;; (add-to-list 'auto-mode-alist '("\\.yml$" . yaml-mode))
45 ;; to your .emacs file.
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':
51 ;; (add-hook 'yaml-mode-hook
52 ;; '(lambda ()
53 ;; (define-key yaml-mode-map "\C-m" 'newline-and-indent)))
55 ;;; Known Bugs:
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.
61 ;;; Code:
64 ;; User definable variables
66 (defgroup yaml nil
67 "Support for the YAML serialization format"
68 :group 'languages
69 :prefix "yaml-")
71 (defcustom yaml-mode-hook nil
72 "*Hook run by `yaml-mode'."
73 :type 'hook
74 :group 'yaml)
76 (defcustom yaml-indent-offset 2
77 "*Amount of offset per level of indentation."
78 :type 'integer
79 :group 'yaml)
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)
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)
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)
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)
108 ;; Constants
110 (defconst yaml-mode-version "0.0.3" "Version of `yaml-mode.'")
112 (defconst yaml-blank-line-re "^ *$"
113 "Regexp matching a line containing only (valid) whitespace.")
115 (defconst yaml-comment-re "\\(#*.*\\)"
116 "Regexp matching a line containing a YAML comment or delimiter.")
118 (defconst yaml-directive-re "^\\(?:--- \\)? *%\\(\\w+\\)"
119 "Regexp matching a line contatining a YAML directive.")
121 (defconst yaml-document-delimiter-re "^ *\\(?:---\\|[.][.][.]\\)"
122 "Rexexp matching a YAML document delimiter line.")
124 (defconst yaml-node-anchor-alias-re "[&*]\\w+"
125 "Regexp matching a YAML node anchor or alias.")
127 (defconst yaml-tag-re "!!?[^ \n]+"
128 "Rexexp matching a YAML tag.")
130 (defconst yaml-bare-scalar-re
131 "\\(?:[^-:,#!\n{\\[ ]\\|[^#!\n{\\[ ]\\S-\\)[^#\n]*?"
132 "Rexexp matching a YAML bare scalar.")
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.")
141 (defconst yaml-scalar-context-re
142 (concat "\\(?:^\\(?:--- \\)?\\|{\\|\\(?:[-,] +\\)+\\) *"
143 "\\(?:" yaml-bare-scalar-re " *: \\)?")
144 "Regexp indicating the begininng of a scalar context.")
146 (defconst yaml-nested-map-re
147 (concat ".*: *\\(?:&.*\\|{ *\\|" yaml-tag-re " *\\)?$")
148 "Regexp matching a line beginning a YAML nested structure.")
150 (defconst yaml-block-literal-base-re " *[>|][-+0-9]* *\\(?:\n\\|\\'\\)"
151 "Regexp matching the substring start of a block literal.")
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")
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")
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")
178 ;; Mode setup
180 (defvar yaml-mode-map ()
181 "Keymap used in `yaml-mode' buffers.")
182 (if yaml-mode-map
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))
192 (defvar yaml-mode-syntax-table nil
193 "Syntax table in use in yaml-mode buffers.")
194 (if yaml-mode-syntax-table
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))
211 (define-derived-mode yaml-mode fundamental-mode "YAML"
212 "Simple mode to edit YAML.
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))))
224 ;; Font-lock support
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.")
239 (defvar yaml-font-lock-syntactic-keywords
240 (list '(yaml-syntactic-block-literals 0 "." t))
241 "Additional syntax features to highlight in YAML mode.")
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))))))
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))
304 ;; Indentation and electric keys
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)))))
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))))
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)))))
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)))))
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))))
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)
390 (provide 'yaml-mode)
392 ;;; yaml-mode.el ends here