* wesnoth-mode.el: Update history for upcoming release.
[wesnoth-mode.git] / wesnoth-mode.el
blob850406d61f0af477b2a9ebd04fca2bef2aa17074
1 ;;; wesnoth-mode.el --- A major mode for editing WML.
2 ;; Copyright (C) 2006, 2007, 2008 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,
17 ;; MA 02139, USA.
19 ;;; Description:
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.
25 ;;; Commentary:
26 ;; Add the following to your .emacs:
27 ;; (add-to-list 'load-path "path/to/wesnoth-mode")
28 ;; (autoload 'wesnoth-mode "wesnoth-mode" "Major mode for editing WML." t)
29 ;; Optionally adding:
30 ;; (add-to-list 'auto-mode-alist '("\\.cfg\\'" . wesnoth-mode))
31 ;; to automatically load wesnoth-mode for all files ending in '.cfg'.
33 ;;; History:
34 ;; 1.2.5
35 ;; * `wesnoth-insert-tag' now takes an optional numeric argument indicating
36 ;; how many blocks to wrap across instead of a region.
37 ;; * Indentation customisation removed; all indentation is performed to
38 ;; current conventions.
39 ;; 1.2.4
40 ;; * Improved syntax-highlighting for macro calls.
41 ;; * Underscore is now treated as whitespace.
42 ;; * Fixed incorrect indentation when preprocessor preceeded by whitespace.
43 ;; * Point is now placed at the first non-whitespace character of the line,
44 ;; instead of the last.
45 ;; * Corrected minor indentation bugs.
46 ;; * Indenting across large regions is now much more efficient.
47 ;; * Fix hooks modifying wesnoth-mode-map causing default bindings not being
48 ;; applied.
49 ;; 1.2.3
50 ;; * Now compatible with GNU Emacs 21.4.
51 ;; * Added support for several new tags.
52 ;; * Added menu entry for wesnoth-mode.
53 ;; * Significant speed increase to indentation.
54 ;; * Indentation can now be customised using `wesnoth-indent-preprocessor-bol'
55 ;; and `wesnoth-indent-savefile'; support for `wesnoth-indentation-function'
56 ;; has been removed.
57 ;; * Trailing whitespace is no longer created when creating a second
58 ;; consecutive newline.
59 ;; * Spurious newlines are no longer created when inserting a tag elements
60 ;; around a region.
61 ;; 1.2.2
62 ;; * Added functions: `wesnoth-indent', `wesnoth-element-closing',
63 ;; `wesnoth-element', `wesnoth-element-opening',
64 ;; `wesnoth-insert-and-indent', `wesnoth-insert-missing-closing'.
65 ;; * Renamed `wesnoth-indent-line-default', `wesnoth-indent-line-savefile' and
66 ;; `wesnoth-jump-backward', `wesnoth-jump-forward' to
67 ;; `wesnoth-indent-withtags-inline', `wesnoth-indent-default-inline' and
68 ;; `wesnoth-backward-tag', `wesnoth-forward-tag', respectively.
69 ;; * Fixed a bug in indentation where content was needed between elements pairs
70 ;; for indentation to work.
71 ;; * Fixed `wesnoth-newline-and-indent' ignoring the state of
72 ;; `wesnoth-auto-indent-flag'.
73 ;; * Fixed `{...}' and `#endif' not font-locking correctly.
74 ;; * Added indentation styles: `wesnoth-indent-default',
75 ;; `wesnoth-indent-withtags' which implement a a similar indentation
76 ;; style to the existing styles, however all preprocessor statements are
77 ;; indented to the first column.
78 ;; * Added support for several new tags.
79 ;; * Modified `wesnoth-newline' to behave more consistently.
80 ;; * `wesnoth-jump-to-matching', `wesnoth-forward-tag', `wesnoth-backward-tag'
81 ;; now leaves point at the beginning (when moving backward) or end (when
82 ;; moving forward) of the match.
83 ;; * `wesnoth-jump-to-matching' now attempts to find a target if necessary and
84 ;; will now work on preprocessor statements. Will now warn if jump
85 ;; destination may not be correct (due to errors in WML structure).
86 ;; * Indentation style is now determined by `wesnoth-indentation-function'.
87 ;; * `wesnoth-check-structure' can now be applied over an active region and
88 ;; now checks preprocessor statements for correct nesting.
89 ;; * `wesnoth-newline' and `wesnoth-newline-and-indent' can now be forced to
90 ;; perform indentation by providing a prefix argument.
91 ;; * Indentation styles now leave point at the first non-whitespace character
92 ;; of the line.
93 ;; * `wesnoth-check-tag-names' now reports on success.
94 ;; * `wesnoth-insert-tag' is now able to insert tags around a region.
95 ;; * `outline-minor-mode' now works on macro definitions.
96 ;; 1.2.1
97 ;; * Base indent now defaults to 4.
98 ;; * Added support for #ifndef.
100 ;;; Code:
101 (defconst wesnoth-mode-version "1.2.4+git"
102 "The current version of `wesnoth-mode'.")
104 (defgroup wesnoth-mode nil "Wesnoth-mode access"
105 :group 'languages
106 :prefix "wesnoth-")
108 (defcustom wesnoth-auto-indent-flag t
109 "Non-nil means indent the current line upon creating a newline."
110 :type 'boolean
111 :group 'wesnoth-mode)
113 (defcustom wesnoth-base-indent 4
114 "The number of columns to indent WML."
115 :type 'integer
116 :group 'wesnoth-mode)
118 (defconst wesnoth-preprocessor-regexp
119 "[\t ]*#\\(?:enddef\\|define \\|e\\(?:lse\\|nd\\(?:\\(?:de\\|i\\)f\\)\\)\\|\\(?:ifn?\\|un\\)def\\)"
120 "Regular expression to match all preprocessor statements.")
122 (defconst wesnoth-preprocessor-opening-regexp
123 "[\t ]*#\\(?:define \\|else\\|ifdef \\|ifndef \\)"
124 "Regular expression to match \"opening\" preprocessor statements.")
126 (defconst wesnoth-preprocessor-closing-regexp
127 "[\t ]*#e\\(?:lse\\|nd\\(?:\\(?:de\\|i\\)f\\)\\)"
128 "Regular expression to match \"closing\" preprocessor statements.")
130 (defvar wesnoth-define-blocks '()
131 "Cache of all toplevel #define and #enddef pairs.")
133 (defvar wesnoth-mode-hook nil)
135 (defvar wesnoth-mode-map
136 (let ((map (make-sparse-keymap)))
137 (define-key map "\C-\M-a" 'wesnoth-backward-element)
138 (define-key map "\C-\M-e" 'wesnoth-forward-element)
139 (define-key map "\C-c\C-m" 'wesnoth-jump-to-matching)
140 (define-key map "\C-cm" 'wesnoth-jump-to-matching)
141 (define-key map "\C-m" 'wesnoth-newline)
142 (define-key map "\C-j" 'wesnoth-newline-and-indent)
143 (define-key map "\C-c\C-c" 'wesnoth-check-structure)
144 (define-key map "\C-cc" 'wesnoth-check-structure)
145 (define-key map "\C-c\C-n" 'wesnoth-check-tag-names)
146 (define-key map "\C-cn" 'wesnoth-check-tag-names)
147 (define-key map "\C-c\C-e" 'wesnoth-insert-tag)
148 (define-key map "\C-ce" 'wesnoth-insert-tag)
149 (define-key map (kbd "C-c C-/") 'wesnoth-insert-missing-closing)
150 (define-key map (kbd "C-c /") 'wesnoth-insert-missing-closing)
151 ;; Menu
152 (define-key map [menu-bar wesnoth]
153 (cons "WML" (make-sparse-keymap "WML")))
154 (define-key map [menu-bar wesnoth insert-tag]
155 '("Insert Tag" . wesnoth-insert-tag))
156 (define-key map [menu-bar wesnoth check-names]
157 '("Check Tag Names" . wesnoth-check-tag-names))
158 (define-key map [menu-bar wesnoth check-structure]
159 '("Check Structure" . wesnoth-check-structure))
160 (define-key map [menu-bar wesnoth jump-to-matching]
161 '("Jump to Matching" . wesnoth-jump-to-matching))
162 (define-key map [menu-bar wesnoth insert-missing-closing]
163 '("Insert Missing Tag" . wesnoth-insert-missing-closing))
164 map)
165 "Keymap used in wesnoth mode.")
167 (defvar wesnoth-syntax-table
168 (let ((wesnoth-syntax-table (make-syntax-table)))
169 (modify-syntax-entry ?= "." wesnoth-syntax-table)
170 (modify-syntax-entry ?_ "_" wesnoth-syntax-table)
171 (modify-syntax-entry ?- "_" wesnoth-syntax-table)
172 (modify-syntax-entry ?. "_" wesnoth-syntax-table)
173 (modify-syntax-entry ?\n ">" wesnoth-syntax-table)
174 (modify-syntax-entry ?\r ">" wesnoth-syntax-table)
175 wesnoth-syntax-table)
176 "Syntax table for `wesnoth-mode'.")
178 ;; Prevents automatic syntax-highlighting of elements which might be
179 ;; pre-processor statements.
180 (defvar wesnoth-syntactic-keywords
181 (list
182 '("\\(^[\t ]*\\(#\\(?:define \\|e\\(?:lse\\|nd\\(?:\\(?:de\\|i\\)f\\)\\)\\|\\(?:ifn?\\|un\\)def \\)\\)\\|#enddef\\)" 1 "w")
183 '("\\(#[\t ]*.*$\\)" 1 "<"))
184 "Highlighting syntactic keywords within `wesnoth-mode'.")
186 (defun wesnoth-preprocessor-best-face ()
187 "Use `font-lock-preprocessor-face' when available."
188 (if (boundp 'font-lock-preprocessor-face)
189 (copy-face 'font-lock-preprocessor-face 'wesnoth-preprocessor-face)
190 (copy-face 'font-lock-keyword-face 'wesnoth-preprocessor-face)))
192 (defvar wesnoth-font-lock-keywords
193 (list
194 '("#\\(?:define\\|\\(?:ifn?\\|un\\)def\\)" . 'wesnoth-preprocessor-face)
195 '("\\(#\\(?:define\\|\\(?:ifn?\\|un\\)def\\)\\)[\t ]+\\(\\(\\w\\|_\\)+\\)"
196 2 font-lock-function-name-face)
197 '("\\(#e\\(?:lse\\|nd\\(?:\\(?:de\\|i\\)f\\)\\)\\)" .
198 'wesnoth-preprocessor-face)
199 '("\\({[@~]?\\(\\w\\|\\.\\|/\\|-\\)+}\\)"
200 (1 font-lock-function-name-face))
201 '("\\({\\(\\w\\|:\\|_\\)+\\|{[~@]?\\)"
202 (1 font-lock-function-name-face))
203 '("}" . font-lock-function-name-face)
204 '("\\[[^]]+\\]" . font-lock-type-face)
205 '("\\$\\(\\w\\|_\\)+" . font-lock-variable-name-face)
206 '("\\(\\(\\w\\|_\\)+\\(\\,[\t ]*\\(\\w\\|_\\)+\\)*\\)="
207 1 font-lock-variable-name-face))
208 "Syntax highlighting for `wesnoth-mode'.")
210 (defconst wesnoth-element-closing "[ \t]*\\(\\[/\\|#enddef\\)"
211 "String to use for a closing element.")
213 (defconst wesnoth-element-opening "[ \t]*\\(\\[[^/]\\|#define\\)"
214 "String to use for an opening element.")
216 (defconst wesnoth-element "[\t ]*\\(\\[[^]]?\\|#define\\|#enddef\\)"
217 "String to use for an opening or closing element.")
219 ;;; Insertion
220 (defvar wesnoth-tags-list
221 (list "abilities" "about" "advances" "advancefrom" "ai" "allow_recruit"
222 "and" "animation" "array" "attack_anim" "attack_filter" "attack" "attacks"
223 "avoid" "binary_path" "bold" "campaign" "capture_village"
224 "choose""clear_variable" "colour_adjust" "command" "deaths" "debug_message"
225 "defend" "defends" "defense" "delay" "destination" "disallow_recruit" "do"
226 "effect" "else" "end_turn" "endlevel" "entry" "era" "event" "expenses"
227 "filter" "filter_adjacent_location" "filter_location" "filter_radius"
228 "filter_second" "filter_vision" "format" "frame" "game_config" "generator"
229 "gold" "have_location" "have_unit" "header" "hide_unit" "if"
230 "illuminated_time" "image" "img" "income" "italic" "item" "jump" "kill"
231 "killed" "label" "language" "leader_goal" "main_map" "menu" "message"
232 "mini_map" "missile_frame" "modifications" "modify_side" "modify_turns"
233 "move" "move_unit_fake" "movement_costs" "movetype" "multiplayer"
234 "multiplayer_side" "music" "not" "num_units" "object" "objectives"
235 "objective" "observers" "option" "or" "panel" "part" "place_shroud"
236 "position" "print" "protect_location" "protect_unit" "race" "random"
237 "recall" "recalls" "recruit" "recruits" "redraw" "ref" "regenerate"
238 "remove_shroud" "remove_unit_overlay" "removeitem" "replay" "replay_start"
239 "resistance" "resolution" "results" "role" "save" "scenario" "scroll"
240 "scroll_to" "scroll_to_unit" "section" "set_menu_item" "set_specials"
241 "set_recruit" "set_variable" "show_if" "side" "side_playing" "snapshot"
242 "sound" "source" "specials" "statistics" "status" "stone" "store_gold"
243 "store_locations" "store_starting_location" "store_side" "store_unit"
244 "story" "target" "team" "teleport" "teleport_anim" "terrain"
245 "terrain_graphics" "test" "text_input" "textdomain" "theme" "then" "tile"
246 "time" "time_area" "time_of_day" "topic" "toplevel" "trait" "turn"
247 "tutorial" "unhide_unit" "unit" "unit_abilities" "unit_alignment"
248 "unit_description" "unit_hp" "unit_image" "unit_level" "unit_moves"
249 "unit_overlay" "unit_profile" "unit_status" "unit_traits" "unit_type"
250 "unit_weapons" "unit_xp" "units" "unstone" "unstore_unit" "upkeep"
251 "variable" "variables" "village" "villages" "while" "wml_filter")
252 "A list containing all tags which are available in WML.")
254 (defvar wesnoth-completion-cache '()
255 "List of tags which have been generated by `wesnoth-build-completion'.")
257 (defun wesnoth-build-completion (&optional rebuild)
258 "Create a new list for tag completion if necessary.
259 Rebuilding list is required for versions of GNU Emacs earlier
260 than 22. If REBUILD is non-nil, regenerate `wesnoth-completion-cache'."
261 (interactive "P")
262 (if (> emacs-major-version 21)
263 wesnoth-tags-list
264 (if (and wesnoth-completion-cache (not rebuild))
265 wesnoth-completion-cache
266 (let ((tags '())
267 (iter 0))
268 (dolist (tag wesnoth-tags-list)
269 (setq iter (1+ iter))
270 (setq tags (append tags (list (cons tag iter)))))
271 (setq wesnoth-completion-cache tags)))))
273 (defun wesnoth-insert-tag (&optional elements tagname)
274 "Insert the specified opening tag and it's matching closing tag.
275 Both the opening and closing tags will be placed on their own
276 lines with point positioned between them. Completion of tags at
277 the prompt uses `wesnoth-tags-list'.
279 ELEMENTS is specifies the number of following blocks which the
280 tag should wrap around.
282 TAGNAME is the name of the tag to be inserted."
283 (interactive "Ps")
284 (unless tagname
285 (setq tagname (completing-read "Tag: " (wesnoth-build-completion))))
286 (or elements (setq elements 0))
287 (let ((depth 0)
288 (start (save-excursion (forward-line -1) (point))))
289 (wesnoth-insert-element-separately "[" tagname "]")
290 (when (= elements 0)
291 (newline))
292 (save-excursion
293 (when (= elements 0)
294 (newline))
295 (while (> elements 0)
296 (wesnoth-search-for-matching-tag
297 'search-forward-regexp wesnoth-element-closing 'point-max)
298 (decf elements)
299 (beginning-of-line))
300 (wesnoth-insert-element-separately "[/" tagname "]")
301 (indent-region start (point)))
302 (wesnoth-indent)))
304 (defun wesnoth-insert-element-separately (&rest strings)
305 "Concatenate STRINGS and insert them on a line of their own."
306 (let ((create-newline nil))
307 (save-excursion
308 (beginning-of-line)
309 (unless (looking-at "^[\t ]*$")
310 (setq create-newline t)))
311 (when create-newline
312 (if (> (point)
313 (save-excursion
314 (back-to-indentation)
315 (point)))
316 (progn
317 (end-of-line)
318 (newline))
319 (beginning-of-line))
320 (open-line 1))
321 (insert (apply 'concat strings))))
323 (defun wesnoth-insert-missing-closing (&optional start end)
324 "Insert the next expected closing element at point.
326 START and END define the region to check for missing closing
327 elements. If function `transient-mark-mode' is enabled, the region
328 specified will be used as START and END. Otherwise, START and
329 END will be the minimum and maximum positions of the buffer,
330 respectively."
331 (interactive)
332 (if (and transient-mark-mode mark-active)
333 (setq start (region-beginning)
334 end (copy-marker (region-end)))
335 (setq start (point-min)
336 end (point-max)))
337 (let ((element (wesnoth-check-structure start end)))
338 (if (not element)
339 (error "%s" "Unable to find element to insert")
340 (when (string= element "Unexpected end of file")
341 (error "%s" element))
342 (when (not (looking-at "[\t ]*$"))
343 (end-of-line)
344 (wesnoth-newline))
345 (wesnoth-insert-and-indent element))))
347 (defun wesnoth-insert-and-indent (&rest args)
348 "Concatenate and insert the given string(s) before indenting.
350 ARGS is a list of strings to be inserted."
351 (insert (apply 'concat args))
352 (wesnoth-indent))
354 (defun wesnoth-newline (&optional indent)
355 "Indent both the current line and the newline created.
356 If `wesnoth-auto-indent-flag' is nil, indentation will not be
357 performed. Indentation can be forced by setting INDENT to
358 non-nil."
359 (interactive)
360 (save-excursion
361 (when (and (or wesnoth-auto-indent-flag indent)
362 (not (looking-at "^[\t ]*$")))
363 (wesnoth-indent)))
364 (newline))
366 ;;; Movement
367 (defmacro wesnoth-navigate-element (repeat search-function bound)
368 "Move point to the tag in the given direction REPEAT times.
370 SEARCH-FUNCTION is the symbol of the function for searching in
371 the required direction, with BOUND marking the furthest point to
372 search."
373 `(progn
374 (or ,repeat (setq ,repeat 1))
375 (while (> ,repeat 0)
376 (and (eq ,search-function 'search-forward-regexp) (end-of-line))
377 (funcall ,search-function wesnoth-element-opening
378 ,bound t)
379 (back-to-indentation)
380 (decf ,repeat))))
382 (defun wesnoth-forward-element (repeat)
383 "Move point to the end of the next tag.
384 REPEAT is an optional numeric argument. If REPEAT is non-nil,
385 jump forward the specified number of tags."
386 (interactive "p")
387 (if (< repeat 0)
388 (wesnoth-backward-element (abs repeat))
389 (wesnoth-navigate-element repeat 'search-forward-regexp (point-max))))
391 (defun wesnoth-backward-element (repeat)
392 "Move point to the beginning of the previous tag.
393 REPEAT is an optional numeric argument. If REPEAT is non-nil,
394 jump backward the specified number of tags."
395 (interactive "p")
396 (if (< repeat 0)
397 (wesnoth-forward-element (abs repeat))
398 (wesnoth-navigate-element repeat 'search-backward-regexp (point-min))))
400 (defmacro wesnoth-search-for-matching-tag (search-function search-string bound)
401 "Search for the matching tag for the current line.
403 SEARCH-FUNCTION is the name of the function used to perform the search.
404 SEARCH-STRING is a string representing the matching tag type.
405 BOUND is the bound to be passed to the search function."
406 `(let ((depth 1))
407 (unless (looking-at ,search-string)
408 (unless (> (point) (funcall ,bound)) (end-of-line))
409 (while (and (> depth 0)
410 (funcall ,search-function wesnoth-element
411 (funcall ,bound) t))
412 (if (string-match ,search-string (match-string 0))
413 (decf depth)
414 (incf depth))))))
416 (defun wesnoth-jump-to-matching ()
417 "Jump point to the matching opening/closing tag."
418 (interactive)
419 (beginning-of-line)
420 (if (looking-at wesnoth-element-opening)
421 (wesnoth-search-for-matching-tag
422 'search-forward-regexp wesnoth-element-closing 'point-max)
423 (wesnoth-search-for-matching-tag
424 'search-backward-regexp wesnoth-element-opening 'wesnoth-wml-start-pos))
425 (back-to-indentation))
427 (defun wesnoth-wml-start-pos ()
428 "Determine the position of `point' relative to where the actual WML begins.
429 Return the likely starting position of the WML if it is found.
430 Otherwise return nil."
431 (save-excursion
432 (goto-char (point-min))
433 (when (search-forward-regexp wesnoth-element (point-max) t)
434 (beginning-of-line)
435 (point))))
437 (defun first-column-indent-p (point)
438 "Return non-nil if the current line should not be indented.
440 POINT is the position in the buffer to check.
441 CONTEXT represents the type of element which precedes the current element."
442 (or (not (wesnoth-wml-start-pos))
443 (<= (point) (wesnoth-wml-start-pos))
444 (nth 3 (parse-partial-sexp (point-min) point))
445 (looking-at wesnoth-preprocessor-regexp)))
447 (defun wesnoth-indent ()
448 "Indent the current line as WML."
449 (beginning-of-line)
450 (unless (first-column-indent-p (point))
451 (multiple-value-bind (context ref-indent)
452 (wesnoth-determine-context (point))
453 (let ((cur-indent 0))
454 (cond
455 ((eq context 'opening)
456 (if (or (looking-at "^[\t ]*\\[[^/]")
457 (looking-at wesnoth-element-opening)
458 (not (looking-at wesnoth-element-closing)))
459 (setq cur-indent (+ ref-indent wesnoth-base-indent))
460 (setq cur-indent ref-indent)))
461 ((eq context 'closing)
462 (if (looking-at "^[\t ]*\\[/")
463 (setq cur-indent (- ref-indent wesnoth-base-indent))
464 (setq cur-indent ref-indent))))
465 (indent-line-to (max cur-indent 0))))))
467 (defun wesnoth-within-define (position)
468 "Determine whether point is currently inside a #define block.
469 POSITION is the initial cursor position."
470 (let ((depth 0))
471 (dolist (element (or wesnoth-define-blocks
472 (wesnoth-find-macro-definitions)))
473 (when (= (cadr (sort (append (mapcar 'marker-position (cadr element))
474 (list position))
475 '>))
476 position)
477 (setq depth (max (car element) depth))))
478 depth))
480 (defun wesnoth-find-macro-definitions ()
481 "Return information regarding positioning of macro definitions."
482 (save-excursion
483 (goto-char (point-min))
484 (let ((depth 0)
485 openings cache)
486 (while (search-forward-regexp "^[\t ]*\\(#define\\|#enddef\\)" (point-max) t)
487 (and (string= (match-string 1) "#define") (beginning-of-line))
488 (setq depth
489 (if (string= (match-string 1) "#define")
490 (progn
491 (add-to-list 'openings (point-marker))
492 (1+ depth))
493 (add-to-list 'cache
494 (list depth (list (car openings) (point-marker))))
495 (setq openings (cdr openings))
496 (1- depth)))
497 (end-of-line))
498 cache)))
500 (defun wesnoth-indent-region (start end)
501 "Indent the region from START to END.
503 Creates and destroys a cache of macro definition details as necessary."
504 (interactive "r")
505 (unwind-protect
506 (save-excursion
507 (goto-char end)
508 (setq end (point-marker))
509 (goto-char start)
510 (setq wesnoth-define-blocks (wesnoth-find-macro-definitions))
511 (or (bolp) (forward-line 1))
512 (while (< (point) end)
513 (if (looking-at "^[\t ]*$")
514 (indent-line-to 0)
515 (funcall indent-line-function))
516 (forward-line 1)))
517 (setq wesnoth-define-blocks nil)))
519 (defun wesnoth-determine-context (position)
520 "Determine the type of the last relevant element.
522 POSITION is the buffer position of the element for which to
523 determine the context."
524 (save-excursion
525 (search-backward-regexp wesnoth-element (wesnoth-wml-start-pos) t)
526 (let ((match (or (match-string 1) ""))
527 (depth (wesnoth-within-define position)))
528 (while (and (> (wesnoth-within-define (point)) depth)
529 (not (= (point) (wesnoth-wml-start-pos))))
530 (search-backward-regexp wesnoth-element
531 (wesnoth-wml-start-pos) t)
532 (setq match (match-string 1)))
533 (when (and (= (point) (wesnoth-wml-start-pos)) (= depth 0)
534 (string-match "#define" match))
535 ;; Found nothing of use; reset match and assume top-level tag.
536 (setq match ""))
537 (cond
538 ((string-match "\\[/\\|#enddef" match)
539 (values 'closing (current-indentation)))
540 ((string-match "\\[[^/]?\\|#define" match)
541 (values 'opening (current-indentation)))))))
543 (defun wesnoth-newline-and-indent (&optional indent)
544 "Indent both the current line and the newline created.
545 If `wesnoth-auto-indent-flag' is nil, indentation will not be
546 performed.
548 If the optional argument, INDENT is non-nil, force indentation to
549 be performed."
550 (interactive)
551 (wesnoth-newline)
552 (when (or wesnoth-auto-indent-flag indent)
553 (wesnoth-indent)))
555 ;;; WML checks
556 (defun wesnoth-check-tag-names ()
557 "Check the names of all tags in the buffer for correctness.
558 If a tag is found which is not present in the list an error will
559 be signalled and point will be moved to the corresponding
560 position."
561 (interactive)
562 (let ((tag-position nil)
563 (missing-tag-name nil))
564 (save-excursion
565 (goto-char (point-min))
566 (while (and (search-forward-regexp "^[\t ]*\\[" (point-max) t)
567 (not tag-position))
568 (beginning-of-line)
569 (when (looking-at "^[\t ]*\\[/?\\(\\(\\w\\|_\\)+\\|_\\)\\]")
570 (unless (member (match-string-no-properties 1) wesnoth-tags-list)
571 (setq tag-position (point))
572 (setq missing-tag-name (match-string-no-properties 1))))
573 (end-of-line)))
574 (if (not tag-position)
575 (message "%s" "No unknown tag names found.")
576 (goto-char tag-position)
577 (back-to-indentation)
578 (message "'%s' is not known to exist"
579 missing-tag-name))))
581 (defun wesnoth-check-structure (&optional start end)
582 "Check the buffer for correct nesting of elements.
583 If a problem is found in the structure, point will be placed at
584 the location which an element was expected and the expected
585 element will be displayed in the mini-buffer.
587 START and END define the region to be checked. If
588 function `transient-mark-mode' is enabled, the region specified will be
589 checked. Otherwise START and END will be the minimum and maximum
590 positions of the buffer, respectively."
591 (interactive)
592 (unless (or start end)
593 (if (and transient-mark-mode mark-active)
594 (setq start (region-beginning)
595 end (copy-marker (region-end)))
596 (setq start (point-min)
597 end (point-max))))
598 (let ((unmatched-tag-list '())
599 (error-position nil)
600 (expected nil))
601 (save-excursion
602 (goto-char start)
603 (while (and (search-forward-regexp
604 wesnoth-element
605 end t)
606 (not error-position))
607 (search-backward-regexp
608 wesnoth-element
609 start t)
610 (if (looking-at wesnoth-preprocessor-regexp)
611 (let ((preprocessor-name (match-string-no-properties 1)))
612 (cond
613 ((member 't
614 (mapcar
615 '(lambda (preproc)
616 (string= preprocessor-name preproc))
617 '("define" "ifdef" "ifndef")))
618 (setq unmatched-tag-list
619 (cons preprocessor-name unmatched-tag-list)))
620 ((string= preprocessor-name "else")
621 (unless (string-match "ifn?def" (car unmatched-tag-list))
622 (setq error-position (point))))
623 ((string= preprocessor-name "endif")
624 (if (string-match "ifn?def" (car unmatched-tag-list))
625 (setq unmatched-tag-list (cdr unmatched-tag-list))
626 (setq error-position (point))))
627 ((string= preprocessor-name "enddef")
628 (if (string= (car unmatched-tag-list) "define")
629 (setq unmatched-tag-list (cdr unmatched-tag-list))
630 (setq error-position (point))))))
631 (if (looking-at "^[\t ]*\\[\\+?\\(\\(\\w\\|_\\)+\\)\\]")
632 (setq unmatched-tag-list
633 (cons (match-string-no-properties 1)
634 unmatched-tag-list))
635 (when (looking-at "^[\t ]*\\[/\\(\\(\\w\\|_\\)+\\)\\]")
636 (if (string= (match-string-no-properties 1)
637 (car unmatched-tag-list))
638 (setq unmatched-tag-list (cdr unmatched-tag-list))
639 (setq error-position (point))))))
640 (end-of-line)))
641 (when unmatched-tag-list
642 (cond ((string= (car unmatched-tag-list) "define")
643 (setq expected "#enddef"))
644 ((string-match "ifn?def" (car unmatched-tag-list))
645 (setq expected "#endif"))
646 ((not unmatched-tag-list)
647 (setq expected "Unexpected end of file"))))
648 (if (interactive-p)
649 (if (not (or unmatched-tag-list error-position))
650 (message "%s" "Structure appears consistent.")
651 (and error-position
652 (goto-char error-position))
653 (if (string= expected "Unexpected end of file")
654 (message "Error %s" expected)
655 (message "Error: Expecting %s"
657 expected
658 (concat " [/" (car unmatched-tag-list) "]")))))
659 (when (or expected unmatched-tag-list)
660 (or expected (concat "[/" (car unmatched-tag-list) "]"))))))
662 ;;; wesnoth-mode
663 (define-derived-mode wesnoth-mode fundamental-mode "wesnoth-mode"
664 "Major mode for editing WML."
665 (wesnoth-preprocessor-best-face)
666 (set-syntax-table wesnoth-syntax-table)
667 (set (make-local-variable 'outline-regexp) "[\t ]*#define")
668 (set (make-local-variable 'comment-start) "#")
669 (set (make-local-variable 'indent-line-function) 'wesnoth-indent)
670 (set (make-local-variable 'indent-region-function) 'wesnoth-indent-region)
671 (set (make-local-variable 'font-lock-defaults)
672 '(wesnoth-font-lock-keywords
673 nil t nil nil
674 (font-lock-syntactic-keywords . wesnoth-syntactic-keywords)))
675 (setq indent-tabs-mode nil)
676 (setq mode-name "WML")
677 (run-hooks 'wesnoth-mode-hook))
679 (provide 'wesnoth-mode)
681 ;;; wesnoth-mode.el ends here