1 ;; wesnoth-mode.el - A major mode for editing WML.
2 ;; Copyright (C) 2006, 2007 Chris Mann
4 ;; This program is free software; you can redistribute it and/or
5 ;; modify it under the terms of the GNU General Public License as
6 ;; published by the Free Software Foundation; either version 2 of the
7 ;; License, or (at your option) any later version.
9 ;; This program is distributed in the hope that it will be useful, but
10 ;; WITHOUT ANY WARRANTY; without even the implied warranty of
11 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 ;; General Public License for more details.
14 ;; You should have received a copy of the GNU General Public License
15 ;; along with this program; see the file COPYING. If not, write to the
16 ;; Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston,
20 ;; wesnoth-mode is a major mode for Emacs which assists in the editing
21 ;; of Wesnoth Markup Language (WML) files. Currently, this mode
22 ;; features syntax highlighting support, automatic indentation,
23 ;; tag-completion and preliminary support for syntax checking.
26 ;; Add the following to your .emacs:
27 ;; (add-to-list 'load-path "path/to/wesnoth-mode")
28 ;; (require 'wesnoth-mode)
30 ;; (add-to-list 'auto-mode-alist '("\\.cfg\\'" . wesnoth-mode))
31 ;; to automatically load wesnoth-mode for all files ending in '.cfg'.
33 (defconst wesnoth-mode-version
"1.1.2.1"
34 "The current version of wesnoth-mode.")
36 (defgroup wesnoth-mode nil
"Wesnoth-mode access"
40 (defcustom wesnoth-indent
2
41 "The number of columns to indent WML."
45 (defcustom wesnoth-indentation-function
'wesnoth-indent-line-default
46 "Use the specified function when indenting WML.
47 You can specify either `wesnoth-indent-line-default' or
48 `wesnoth-indent-line-savefile' as the indentation style or a
49 customised function for indentation."
53 (defcustom wesnoth-auto-indent-flag t
54 "Whether to attempt tag indentation when a newline is created.
55 If nil, no indentation will be attempted. Otherwise, attempt to
60 (defconst wesnoth-preprocessor-regexp
61 "#\\(?:define \\|e\\(?:lse\\|nd\\(?:\\(?:de\\|i\\)f\\)\\)\\|\\(?:if\\|un\\)def \\)"
62 "Regular expression to match all preprocessor statements.")
64 (defconst wesnoth-preprocessor-opening-regexp
65 "#\\(?:define \\|else\\|ifdef \\)"
66 "Regular expression to match \"opening\" preprocessor statements.")
68 (defconst wesnoth-preprocessor-closing-regexp
69 "#e\\(?:lse\\|nd\\(?:\\(?:de\\|i\\)f\\)\\)"
70 "Regular expression to match \"closing\" preprocessor statements.")
72 (defvar wesnoth-mode-hook nil
)
74 (defvar wesnoth-mode-map
()
75 "Keymap used in wesnoth mode.")
76 (unless wesnoth-mode-map
77 (setq wesnoth-mode-map
(make-sparse-keymap))
78 (define-key wesnoth-mode-map
"\C-\M-a" 'wesnoth-jump-backward
)
79 (define-key wesnoth-mode-map
"\C-\M-e" 'wesnoth-jump-forward
)
80 (define-key wesnoth-mode-map
"\C-c\C-m" 'wesnoth-jump-to-matching
)
81 (define-key wesnoth-mode-map
"\C-cm" 'wesnoth-jump-to-matching
)
82 (define-key wesnoth-mode-map
"\C-m" 'wesnoth-newline
)
83 (define-key wesnoth-mode-map
"\C-j" 'wesnoth-newline-and-indent
)
84 (define-key wesnoth-mode-map
"\C-c\C-c" 'wesnoth-check-structure
)
85 (define-key wesnoth-mode-map
"\C-cc" 'wesnoth-check-structure
)
86 (define-key wesnoth-mode-map
"\C-c\C-n" 'wesnoth-check-tag-names
)
87 (define-key wesnoth-mode-map
"\C-cf" 'wesnoth-fix-structure
)
88 (define-key wesnoth-mode-map
"\C-c\C-f" 'wesnoth-fix-structure
)
89 (define-key wesnoth-mode-map
"\C-cn" 'wesnoth-check-tag-names
)
90 (define-key wesnoth-mode-map
"\C-c\C-e" 'wesnoth-insert-tag
)
91 (define-key wesnoth-mode-map
"\C-ce" 'wesnoth-insert-tag
))
93 (defvar wesnoth-syntax-table
94 (let ((wesnoth-syntax-table (make-syntax-table)))
95 (modify-syntax-entry ?
= "." wesnoth-syntax-table
)
96 (modify-syntax-entry ?\_
"w" wesnoth-syntax-table
)
97 (modify-syntax-entry ?-
"_" wesnoth-syntax-table
)
98 (modify-syntax-entry ?.
"_" wesnoth-syntax-table
)
99 (modify-syntax-entry ?
\n ">" wesnoth-syntax-table
)
100 (modify-syntax-entry ?
\r ">" wesnoth-syntax-table
)
101 wesnoth-syntax-table
)
102 "Syntax table for wesnoth-mode.")
104 ;; Prevents automatic syntax-highlighting of elements which might be
105 ;; pre-processor statements.
106 (defvar wesnoth-syntactic-keywords
108 '("^[\t ]*\\(#\\(?:define \\|e\\(?:lse\\|nd\\(?:\\(?:de\\|i\\)f\\)\\)\\|\\(?:if\\|un\\)def \\)\\)" 1 "w")
109 '("\\(#[\t ]*.*$\\)" 1 "<"))
110 "Highlighting syntactic keywords within wesnoth-mode.")
112 (defvar wesnoth-font-lock-keywords
114 '("\\(#\\(?:define\\|\\(?:if\\|un\\)def\\)\\)"
115 1 font-lock-preprocessor-face
)
116 '("\\(#\\(?:define\\|\\(?:if\\|un\\)def\\)\\)[\t ]+\\(\\w+\\)"
117 2 font-lock-function-name-face
)
118 '("\\(#e\\(?:lse\\|nd\\(?:\\(?:de\\|i\\)f\\)\\)\\)" . font-lock-preprocessor-face
)
119 '("[\t ]*\\({[[:word:]/\.]+\\|{[@~][[:word:]/\.]+\\).*\\(}\\)"
120 (1 font-lock-function-name-face
)
121 (2 font-lock-function-name-face
))
122 '("\\[[^]]+\\]" . font-lock-type-face
)
123 '("\\$\\w+" . font-lock-variable-name-face
)
124 '("\\(\\w+\\(\\,[\t ]*\\w+\\)*\\)="
125 1 font-lock-variable-name-face
))
126 "Syntax highlighting for wesnoth-mode.")
128 (defvar wesnoth-tags-list
130 "abilities" "about" "advances" "advancefrom" "ai" "allow_recruit"
131 "and" "animation" "array" "attack" "attacks" "avoid" "binary_path"
132 "bold" "campaign" "capture_village" "choose""clear_variable"
133 "colour_adjust" "command" "deaths" "defend" "defends" "defense"
134 "delay" "destination" "disallow_recruit" "do" "effect" "else"
135 "end_turn" "endlevel" "era" "event" "expenses" "filter"
136 "filter_radius" "filter_second" "format" "frame" "game_config"
137 "generator" "gold" "have_unit" "header" "hide_unit" "if"
138 "illuminated_time" "image" "img" "income" "italic" "item" "jump"
139 "kill" "killed" "label" "language" "leader_goal" "main_map" "menu"
140 "message" "mini_map" "missile_frame" "modifications" "modify_side"
141 "modify_turns" "move" "move_unit_fake" "movement_costs" "movetype"
142 "multiplayer" "multiplayer_side" "music" "not" "num_units" "object"
143 "objectives" "objective" "observers" "option" "or" "panel" "part"
144 "place_shroud" "position" "print" "protect_location" "protect_unit"
145 "race" "random" "recall" "recalls" "recruit" "recruits" "redraw"
146 "ref" "remove_shroud" "remove_unit_overlay" "removeitem" "replay"
147 "replay_start" "resistance" "resolution" "results" "role" "save"
148 "scenario" "scroll" "scroll_to" "scroll_to_unit" "section"
149 "set_recruit" "set_variable" "side" "side_playing" "snapshot"
150 "sound" "source" "specials" "statistics" "status" "stone"
151 "store_gold" "store_locations" "store_starting_location"
152 "store_unit" "story" "target" "team" "teleport" "teleport_anim"
153 "terrain" "terrain_graphics" "test" "theme" "then" "tile" "time"
154 "time_area" "time_of_day" "topic" "toplevel" "trait" "turn"
155 "tutorial" "unhide_unit" "unit" "unit_abilities"
156 "unit_alignment" "unit_description" "unit_hp" "unit_image"
157 "unit_level" "unit_moves" "unit_overlay" "unit_profile"
158 "unit_status" "unit_traits" "unit_type" "unit_weapons" "unit_xp"
159 "units" "unstone" "unstore_unit" "upkeep" "variable" "variables"
160 "village" "villages" "while")
161 "A list containing all tags which are available for use in WML.")
163 (defun wesnoth-insert-tag ()
164 "Inserts the specified opening tag and it's matching closing tag.
165 Both the opening and closing tags will be placed on their own
166 lines with point positioned between them. Completion of tags at
167 the prompt uses `wesnoth-tags-list'."
176 (unless (looking-at "^[\t ]*$")
180 (insert "[" tagname
"]")
181 (wesnoth-indent-line)
183 (wesnoth-indent-line)
185 (insert "\n[/" tagname
"]")
186 (wesnoth-indent-line)
187 (forward-line -
1)))))
189 (defun wesnoth-jump-forward (repeat)
190 "Move point to the end of the next tag.
191 REPEAT is an optional numeric argument. If REPEAT is non-nil,
192 jump forward the specified number of tags."
194 (or repeat
(setq repeat
1))
195 (and (< repeat
0) (wesnoth-jump-backward (abs repeat
)))
196 (let ((iterations 0))
197 (while (< iterations repeat
)
199 (search-forward-regexp
200 (concat "^[\t ]*\\(\\[\\w+\\]\\|"
201 wesnoth-preprocessor-opening-regexp
"\\)")
203 (setq iterations
(1+ iterations
)))))
205 (defun wesnoth-jump-backward (repeat)
206 "Move point to the beginning of the previous tag.
207 REPEAT is an optional numeric argument. If REPEAT is non-nil,
208 jump backward the specified number of tags."
210 (or repeat
(setq repeat
1))
211 (and (< repeat
0) (wesnoth-jump-forward (abs repeat
)))
212 (let ((iterations 0))
213 (while (< iterations repeat
)
215 (search-backward-regexp
216 (concat "^[\t ]*\\(\\[\\w+\\]\\|"
217 wesnoth-preprocessor-opening-regexp
"\\)")
220 (search-forward-regexp "[^[:blank:]]")
222 (setq iterations
(1+ iterations
)))))
224 (defun wesnoth-jump-to-matching ()
225 "Jump point to the matching opening/closing tag.
226 A tag must be on the same line as point for jumping to occur. If
227 the tag structure is not correct this may have unexpected
233 (search-backward nil
))
237 (concat "^[\t ]*\\(\\[\\|"
238 wesnoth-preprocessor-regexp
"\\)"))
240 (concat "^[\t ]*\\(\\[/\\|#\\(?:endif\\|enddef\\)\\)"))
241 (setq search-backward t
))
242 (if (wesnoth-wml-start-pos)
243 (if (> (point) (wesnoth-wml-start-pos))
244 (search-backward-regexp
245 (concat "^[\t ]*\\(\\[\\|"
246 wesnoth-preprocessor-regexp
"\\)")
248 (goto-char (point-min))
249 (search-forward-regexp
250 (concat "^[\t ]*\\(\\[\\|"
251 wesnoth-preprocessor-regexp
"\\)"))
253 (error "%s" "Unable to locate tag to jump from")))
258 (or (< open-tags
0) (not search-started
))
259 (search-backward-regexp
260 (concat "^[\t ]*\\(\\[\\|"
261 wesnoth-preprocessor-regexp
"\\)")
263 (setq search-started t
)
265 "^[\t ]*\\(\\[\\w+\\]\\|#\\(?:define\\|ifdef\\) \\)")
266 (setq open-tags
(1+ open-tags
))
268 (concat "^[\t ]*\\(\\[/\\w+\\]\\|#\\(?:endif\\|enddef\\)\\)"))
269 (setq open-tags
(1- open-tags
))))))
271 (or (> open-tags
0) (not search-started
))
272 (search-forward-regexp
273 (concat "^[\t ]*\\(\\[\\|"
274 wesnoth-preprocessor-regexp
"\\)")
277 (setq search-started t
)
279 "^[\t ]*\\(\\[\\w+\\]\\|#\\(?:define\\|ifdef\\) \\)")
280 (setq open-tags
(1+ open-tags
))
282 (concat "^[\t ]*\\(\\[/\\w+\\]\\|#\\(?:endif\\|enddef\\)\\)"))
283 (setq open-tags
(1- open-tags
))))
285 (setq tag-position
(point)))
287 (goto-char tag-position
)
290 (search-backward-regexp "\\[\\|#"))
292 (defun wesnoth-wml-start-pos ()
293 "Determine the position of `point' relative to where the actual WML begins.
294 Return the likely starting position of the WML if it is found.
295 Otherwise return nil."
297 (goto-char (point-min))
298 (when (search-forward-regexp
299 (concat "^[\t ]*\\(\\[\\)\\|\\("
300 wesnoth-preprocessor-opening-regexp
306 (defun wesnoth-indent-line-default ()
307 "Indent the current line as WML using normal-style indentation."
309 (if (or (not (wesnoth-wml-start-pos))
310 (<= (point) (wesnoth-wml-start-pos))
311 (nth 3 (syntax-ppss (point))))
313 (let ((not-indented t
) cur-indent
)
315 (concat "^[ \t]*\\(\\[\\/[^]]*?\\|\\("
316 wesnoth-preprocessor-closing-regexp
"\\)\\)"))
319 (search-backward-regexp
320 (concat "^[\t ]*\\[\\|\\(" wesnoth-preprocessor-regexp
"\\)"))
321 (setq cur-indent
(current-indentation))
324 (concat "^[\t ]*\\(\\[/.+\\]\\|\\("
325 wesnoth-preprocessor-closing-regexp
327 (setq cur-indent
(- (current-indentation) wesnoth-indent
))))
329 (setq cur-indent
0)))
331 (concat "^[ \t]*\\(\\[[^/]*?\\]\\|\\("
332 wesnoth-preprocessor-closing-regexp
"\\)\\)")))
334 (search-backward-regexp
335 (concat "^[\t ]*\\(\\[\\|\\("
336 wesnoth-preprocessor-regexp
"\\)\\)"))
338 (concat "^[\t ]*\\(\\[/.+\\]\\|\\("
339 wesnoth-preprocessor-closing-regexp
342 (setq cur-indent
(- (current-indentation) wesnoth-indent
))
345 (setq not-indented nil
))
346 (setq cur-indent
(current-indentation))
347 (setq not-indented nil
)))
350 (search-backward-regexp
351 (concat "^[\t ]*\\([[}]\\|\\("
352 wesnoth-preprocessor-regexp
355 (concat "^[ \t]*\\(\\[[^/]*?\\]\\|\\("
356 wesnoth-preprocessor-opening-regexp
"\\)\\)"))
357 (setq cur-indent
(+ (current-indentation) wesnoth-indent
))
358 (setq cur-indent
(current-indentation)))))))
359 (unless (and (not cur-indent
) (= (current-indentation) cur-indent
))
360 (indent-line-to cur-indent
))))
363 (defun wesnoth-indent-line-savefile ()
364 "Indent the current line as WML code using savefile-style indentation."
366 (if (or (not (wesnoth-wml-start-pos))
367 (<= (point) (wesnoth-wml-start-pos))
368 (nth 3 (syntax-ppss (point))))
372 (concat "^[ \t]*\\(\\[\\/[^]]*?\\|\\("
373 wesnoth-preprocessor-closing-regexp
"\\)\\)"))
376 (search-backward-regexp "^[ \t]+.\\|^[{[#]")
377 (setq cur-indent
(- (current-indentation) wesnoth-indent
))
379 (concat "^[ \t]*\\(\\[[^/].+\\]\\|\\("
380 wesnoth-preprocessor-opening-regexp
"\\)\\)"))
381 (setq cur-indent
(current-indentation))))
383 (setq cur-indent
0)))
386 (search-backward-regexp "^[\t ]*\\([[#}]\\)")
388 (concat "^[ \t]*\\(\\[[^/]+?\\]\\|\\("
389 wesnoth-preprocessor-opening-regexp
"\\)\\)"))
390 (setq cur-indent
(+ (current-indentation) wesnoth-indent
))
391 (setq cur-indent
(current-indentation))))))
392 (unless (and (not cur-indent
)
393 (= (current-indentation) cur-indent
))
394 (indent-line-to cur-indent
))))
397 (defun wesnoth-newline ()
398 "Indent both the current line and the newline created.
399 If `wesnoth-auto-indent-flag' is nil, indentation will not be
402 (when wesnoth-auto-indent-flag
405 (if (looking-at "^[\t ]*$")
407 (wesnoth-indent-line))))
410 (defun wesnoth-newline-and-indent ()
411 "Indent both the current line and the newline created.
412 If `wesnoth-auto-indent-flag' is nil, indentation will not be
416 (wesnoth-indent-line))
418 (defun wesnoth-check-tag-names ()
419 "Check the names of all tags in the buffer for correctness.
420 If a tag is found which is not present in the list an error will
421 be signalled and point will be moved to the corresponding
424 (let ((tag-position nil
)
425 (missing-tag-name nil
))
427 (goto-char (point-min))
429 (search-forward-regexp "^[\t ]*\\[" (point-max) t
)
432 (when (looking-at "^[\t ]*\\[[/]?\\(\\w+\\)\\]")
433 (unless (member (match-string-no-properties 1) wesnoth-tags-list
)
434 (setq tag-position
(point))
435 (setq missing-tag-name
(match-string-no-properties 1))))
438 (goto-char tag-position
)
439 (message "'%s' is not known to exist"
442 (defun wesnoth-check-structure ()
443 "Check the buffer for correct nesting of elements.
444 If a problem is found in the structure, point will be placed at
445 the location which an element was expected and the expected
446 element will be displayed in the minibuffer."
448 (let ((unmatched-tag-list '())
449 (error-position nil
))
451 (goto-char (point-min))
453 (search-forward-regexp
454 (concat "^[\t ]*\\[\\|\\(" wesnoth-preprocessor-regexp
"\\)")
456 (not error-position
))
458 (if (looking-at "^[\t ]*#\\(\\w+\\)")
459 (let ((preprocessor-name (match-string-no-properties 1)))
461 ((string= preprocessor-name
"define")
462 (setq unmatched-tag-list
463 (cons preprocessor-name unmatched-tag-list
)))
464 ((string= preprocessor-name
"ifdef")
465 (setq unmatched-tag-list
466 (cons preprocessor-name unmatched-tag-list
)))
467 ((string= preprocessor-name
"else")
468 (unless (string= (car unmatched-tag-list
) "ifdef")
469 (setq error-position
(point))))
470 ((string= preprocessor-name
"endif")
471 (if (string= (car unmatched-tag-list
) "ifdef")
472 (setq unmatched-tag-list
(cdr unmatched-tag-list
))
473 (setq error-position
(point))))
474 ((string= preprocessor-name
"enddef")
475 (if (string= (car unmatched-tag-list
) "define")
476 (setq unmatched-tag-list
(cdr unmatched-tag-list
))
477 (setq error-position
(point))))))
478 (if (looking-at "^[\t ]*\\[\\(\\w+\\)\\]")
479 (setq unmatched-tag-list
480 (cons (match-string-no-properties 1)
482 (when (looking-at "^[\t ]*\\[/\\(\\w+\\)\\]")
483 (if (string= (match-string-no-properties 1)
484 (car unmatched-tag-list
))
485 (setq unmatched-tag-list
(cdr unmatched-tag-list
))
486 (setq error-position
(point))))))
488 (when (or unmatched-tag-list error-position
)
490 (goto-char error-position
)
491 (goto-char (point-max)))
492 (let ((expected nil
))
493 (cond ((string= (car unmatched-tag-list
) "define")
494 (setq expected
"#enddef"))
495 ((string= (car unmatched-tag-list
) "ifdef")
496 (setq expected
"#endif"))
497 ((not unmatched-tag-list
)
498 (setq expected
"end of file")))
500 (message "Expecting: '%s'"
502 expected
(concat "[/" (car unmatched-tag-list
)
504 (or expected
(concat "[/" (car unmatched-tag-list
) "]"))))))
506 (defun wesnoth-fix-structure ()
507 "Attempt to fix all faults in the structure of the current buffer."
509 (let ((element (wesnoth-check-structure)))
511 (if (string= element
"end of file")
512 (delete-region (point) (point-max))
516 (wesnoth-indent-line))
517 (setq element
(wesnoth-check-structure)))))
519 (defun wesnoth-indent-line ()
520 "Determine and performs indentation on the current line.
521 The Indentation style can be customised by modifying
522 `wesnoth-indentation-function'."
524 (funcall wesnoth-indentation-function
))
526 (define-derived-mode wesnoth-mode fundamental-mode
"wesnoth-mode"
527 "Major mode for editing WML."
528 (set-syntax-table wesnoth-syntax-table
)
529 (set (make-local-variable 'outline-regexp
) "^[\t ]*\\[\\w+")
530 (set (make-local-variable 'comment-start
) "#")
531 (set (make-local-variable 'indent-line-function
) 'wesnoth-indent-line
)
532 (set (make-local-variable 'font-lock-defaults
)
533 '(wesnoth-font-lock-keywords
535 (font-lock-syntactic-keywords . wesnoth-syntactic-keywords
)))
536 (setq mode-name
"WML")
537 (run-hooks 'wesnoth-mode-hook
))
539 (provide 'wesnoth-mode
)