Devel branch inital commit.
[wesnoth-mode.git] / wesnoth-mode.el
blob22c8b1e4d9fe675a500f4abc6a678cd601143d53
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,
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 ;; Usage:
26 ;; Add the following to your .emacs:
27 ;; (add-to-list 'load-path "path/to/wesnoth-mode")
28 ;; (require 'wesnoth-mode)
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 (defconst wesnoth-mode-version "1.1.2.1"
34 "The current version of wesnoth-mode.")
36 (defgroup wesnoth-mode nil "Wesnoth-mode access"
37 :group 'languages
38 :prefix "wesnoth-")
40 (defcustom wesnoth-indent 2
41 "The number of columns to indent WML."
42 :type 'integer
43 :group 'wesnoth-mode)
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."
50 :type 'function
51 :group 'wesnoth-mode)
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
56 indent the line."
57 :type 'boolean
58 :group 'wesnoth-mode)
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-wml-structure
73 '(("multiplayer" ("id" "allow_new_game" "next_scenario" "description" "objectives" "name" "map_data" "turns" "turn_at" "random_start_time" "music" "victory_when_enemies_defeated" "experience_modifier" "theme" "map_generation") ("music" "story" "time" "illuminated_time" "time_area" "side" "event" "generator"))
74 ("test" ("id" "allow_new_game" "next_scenario" "description" "objectives" "name" "map_data" "turns" "turn_at" "random_start_time" "music" "victory_when_enemies_defeated" "experience_modifier" "theme" "map_generation") ("music" "story" "time" "illuminated_time" "time_area" "side" "event" "generator"))
75 ("tutorial" ("id" "allow_new_game" "next_scenario" "description" "objectives" "name" "map_data" "turns" "turn_at" "random_start_time" "music" "victory_when_enemies_defeated" "experience_modifier" "theme" "map_generation") ("music" "story" "time" "illuminated_time" "time_area" "side" "event" "generator"))
76 ("scenario" ("id" "allow_new_game" "next_scenario" "description" "objectives" "name" "map_data" "turns" "turn_at" "random_start_time" "music" "victory_when_enemies_defeated" "experience_modifier" "theme" "map_generation") ("music" "story" "time" "illuminated_time" "time_area" "side" "event" "generator"))
77 ("advances" '() '())
78 ("team" ("damage_inflicted" "damage_taken" "recall_cost" "recruit_cost") ("recruits" "cecalls" "advances" "deaths" "killed" "attacks" "defends"))
79 ("abilities" ("name" "name_inactive" "description" "description_inactive" "affect_self" "affect_allies" "affect_enemies" "cumulative" "id") ("heals" "regenerate" "resistance" "leadership" "skirmisher" "illuminates" "teleport" "hides" "adjacent_description" "filter" "filter_adjactent" "filter_adjacent_location" "affect_adjacent" "filter_self" "filter_base_value"))
80 ("heals" ("value" "poison") ())
81 ("regenerate" ("value" "poison") ())
82 ("resistance" ("value" "max_value" "add" "multiply" "apply_to" "active_on") ())
83 ("leadership" ("value") ())
84 ("illuminates" ("value" "max_value"))
85 ("hides" ("alert"))
86 ("specials" () ("damage" "attacks" "chance_to_hit" "slow" "poison" "stones" "berserk" "firststrike" "drains" "plague"))
87 ("damage" ("name" "name_inactive" "value" "description" "description_inactive" "active_on" "multiply" "cumulative" "id" "active_on" "apply_to") ("filter_adjacent" "filter_adjactent_location" "filter_self" "filter_opponent" "filter_attacker" "filter_defender" "filter_base_value"))
88 ("attacks" ("name" "name_inactive" "value" "description" "description_inactive" "active_on" "multiply" "cumulative" "id" "active_on" "apply_to") ("filter_adjacent" "filter_adjactent_location" "filter_self" "filter_opponent" "filter_attacker" "filter_defender" "filter_base_value"))
89 ("chance_to_hit" ("name" "value" "name_inactive" "description" "description_inactive" "active_on" "multiply" "cumulative" "id" "active_on" "apply_to") ("filter_adjacent" "filter_adjactent_location" "filter_self" "filter_opponent" "filter_attacker" "filter_defender" "filter_base_value"))
90 ("slow" ("name" "name_inactive" "description" "description_inactive" "active_on" "multiply" "cumulative" "id" "active_on" "apply_to") ("filter_adjacent" "filter_adjactent_location" "filter_self" "filter_opponent" "filter_attacker" "filter_defender" "filter_base_value"))
91 ("poison" ("name" "name_inactive" "description" "description_inactive" "active_on" "multiply" "cumulative" "id" "active_on" "apply_to") ("filter_adjacent" "filter_adjactent_location" "filter_self" "filter_opponent" "filter_attacker" "filter_defender" "filter_base_value"))
92 ("stones" ("name" "name_inactive" "description" "description_inactive" "active_on" "multiply" "cumulative" "id" "active_on" "apply_to") ("filter_adjacent" "filter_adjactent_location" "filter_self" "filter_opponent" "filter_attacker" "filter_defender" "filter_base_value"))
93 ("berserk" ("name" "values" "name_inactive" "description" "description_inactive" "active_on" "multiply" "cumulative" "id" "active_on" "apply_to") ("filter_adjacent" "filter_adjactent_location" "filter_self" "filter_opponent" "filter_attacker" "filter_defender" "filter_base_value"))
94 ("firststrike" ("name" "name_inactive" "description" "description_inactive" "active_on" "multiply" "cumulative" "id" "active_on" "apply_to") ("filter_adjacent" "filter_adjactent_location" "filter_self" "filter_opponent" "filter_attacker" "filter_defender" "filter_base_value"))
95 ("drains" ("name" "name_inactive" "description" "description_inactive" "active_on" "multiply" "cumulative" "id" "active_on" "apply_to") ("filter_adjacent" "filter_adjactent_location" "filter_self" "filter_opponent" "filter_attacker" "filter_defender" "filter_base_value"))
96 ("plague" ("name" "type" "name_inactive" "description" "description_inactive" "active_on" "multiply" "cumulative" "id" "active_on" "apply_to") ("filter_adjacent" "filter_adjactent_location" "filter_self" "filter_opponent" "filter_attacker" "filter_defender" "filter_base_value"))
97 ("unit" ("advancefrom" "advanceto" "alignment" "cost" "experience" "gender" "hide_help" "hitpoints" "id" "level" "movement" "movetype" "name" "num_traits" "profile" "race" "unit_description" "undead_variation" "usage" "zoc") ("advancement" "base_unit" "attack" "defend" "death" "teleport" "extra_anim" "event" "variation" "male" "female" "abilities"))
98 ("about" ("title" "artists" "units" "balancing" "text") ("entry"))
99 ("ai" ("time_of_day" "turns" "ai_algorithm" "python_script" "recruitment_pattern" "aggression" "caution" "village_value" "leader_value" "villages_per_scout" "recruitment_ignore_bad_movement" "recruitment_ignore_bad_combat" "passive_leader" "attack_depth" "simple_targetting" "scout_village_targetting" "grouping" "protect_leader") ("protect_location" "protect_unit" "target" "avoid" "leader_goal"))))
101 (defvar wesnoth-mode-hook nil)
103 (defvar wesnoth-mode-map ()
104 "Keymap used in wesnoth mode.")
105 (unless wesnoth-mode-map
106 (setq wesnoth-mode-map (make-sparse-keymap))
107 (define-key wesnoth-mode-map "\C-\M-a" 'wesnoth-jump-backward)
108 (define-key wesnoth-mode-map "\C-\M-e" 'wesnoth-jump-forward)
109 (define-key wesnoth-mode-map "\C-c\C-m" 'wesnoth-jump-to-matching)
110 (define-key wesnoth-mode-map "\C-cm" 'wesnoth-jump-to-matching)
111 (define-key wesnoth-mode-map "\C-m" 'wesnoth-newline)
112 (define-key wesnoth-mode-map "\C-j" 'wesnoth-newline-and-indent)
113 (define-key wesnoth-mode-map "\C-c\C-c" 'wesnoth-check-structure)
114 (define-key wesnoth-mode-map "\C-cc" 'wesnoth-check-structure)
115 (define-key wesnoth-mode-map "\C-c\C-n" 'wesnoth-check-tag-names)
116 (define-key wesnoth-mode-map "\C-cf" 'wesnoth-fix-structure)
117 (define-key wesnoth-mode-map "\C-c\C-f" 'wesnoth-fix-structure)
118 (define-key wesnoth-mode-map "\C-cn" 'wesnoth-check-tag-names)
119 (define-key wesnoth-mode-map "\C-c\C-e" 'wesnoth-insert-tag)
120 (define-key wesnoth-mode-map "\C-ce" 'wesnoth-insert-tag))
122 (defvar wesnoth-syntax-table
123 (let ((wesnoth-syntax-table (make-syntax-table)))
124 (modify-syntax-entry ?= "." wesnoth-syntax-table)
125 (modify-syntax-entry ?\_ "w" wesnoth-syntax-table)
126 (modify-syntax-entry ?- "_" wesnoth-syntax-table)
127 (modify-syntax-entry ?. "_" wesnoth-syntax-table)
128 (modify-syntax-entry ?\n ">" wesnoth-syntax-table)
129 (modify-syntax-entry ?\r ">" wesnoth-syntax-table)
130 wesnoth-syntax-table)
131 "Syntax table for wesnoth-mode.")
133 ;; Prevents automatic syntax-highlighting of elements which might be
134 ;; pre-processor statements.
135 (defvar wesnoth-syntactic-keywords
136 (list
137 '("^[\t ]*\\(#\\(?:define \\|e\\(?:lse\\|nd\\(?:\\(?:de\\|i\\)f\\)\\)\\|\\(?:if\\|un\\)def \\)\\)" 1 "w")
138 '("\\(#[\t ]*.*$\\)" 1 "<"))
139 "Highlighting syntactic keywords within wesnoth-mode.")
141 (defvar wesnoth-font-lock-keywords
142 (list
143 '("\\(#\\(?:define\\|\\(?:if\\|un\\)def\\)\\)"
144 1 font-lock-preprocessor-face)
145 '("\\(#\\(?:define\\|\\(?:if\\|un\\)def\\)\\)[\t ]+\\(\\w+\\)"
146 2 font-lock-function-name-face)
147 '("\\(#e\\(?:lse\\|nd\\(?:\\(?:de\\|i\\)f\\)\\)\\)" . font-lock-preprocessor-face)
148 '("[\t ]*\\({[[:word:]/\.]+\\|{[@~][[:word:]/\.]+\\).*\\(}\\)"
149 (1 font-lock-function-name-face)
150 (2 font-lock-function-name-face))
151 '("\\[[^]]+\\]" . font-lock-type-face)
152 '("\\$\\w+" . font-lock-variable-name-face)
153 '("\\(\\w+\\(\\,[\t ]*\\w+\\)*\\)="
154 1 font-lock-variable-name-face))
155 "Syntax highlighting for wesnoth-mode.")
157 (defvar wesnoth-tags-list
158 (list
159 "abilities" "about" "advances" "advancefrom" "ai" "allow_recruit"
160 "and" "animation" "array" "attack" "attacks" "avoid" "binary_path"
161 "bold" "campaign" "capture_village" "choose""clear_variable"
162 "colour_adjust" "command" "deaths" "defend" "defends" "defense"
163 "delay" "destination" "disallow_recruit" "do" "effect" "else"
164 "end_turn" "endlevel" "era" "event" "expenses" "filter"
165 "filter_radius" "filter_second" "format" "frame" "game_config"
166 "generator" "gold" "have_unit" "header" "hide_unit" "if"
167 "illuminated_time" "image" "img" "income" "italic" "item" "jump"
168 "kill" "killed" "label" "language" "leader_goal" "main_map" "menu"
169 "message" "mini_map" "missile_frame" "modifications" "modify_side"
170 "modify_turns" "move" "move_unit_fake" "movement_costs" "movetype"
171 "multiplayer" "multiplayer_side" "music" "not" "num_units" "object"
172 "objectives" "objective" "observers" "option" "or" "panel" "part"
173 "place_shroud" "position" "print" "protect_location" "protect_unit"
174 "race" "random" "recall" "recalls" "recruit" "recruits" "redraw"
175 "ref" "remove_shroud" "remove_unit_overlay" "removeitem" "replay"
176 "replay_start" "resistance" "resolution" "results" "role" "save"
177 "scenario" "scroll" "scroll_to" "scroll_to_unit" "section"
178 "set_recruit" "set_variable" "side" "side_playing" "snapshot"
179 "sound" "source" "specials" "statistics" "status" "stone"
180 "store_gold" "store_locations" "store_starting_location"
181 "store_unit" "story" "target" "team" "teleport" "teleport_anim"
182 "terrain" "terrain_graphics" "test" "theme" "then" "tile" "time"
183 "time_area" "time_of_day" "topic" "toplevel" "trait" "turn"
184 "tutorial" "unhide_unit" "unit" "unit_abilities"
185 "unit_alignment" "unit_description" "unit_hp" "unit_image"
186 "unit_level" "unit_moves" "unit_overlay" "unit_profile"
187 "unit_status" "unit_traits" "unit_type" "unit_weapons" "unit_xp"
188 "units" "unstone" "unstore_unit" "upkeep" "variable" "variables"
189 "village" "villages" "while")
190 "A list containing all tags which are available for use in WML.")
192 (defun wesnoth-insert-tag ()
193 "Inserts the specified opening tag and it's matching closing tag.
194 Both the opening and closing tags will be placed on their own
195 lines with point positioned between them. Completion of tags at
196 the prompt uses `wesnoth-tags-list'."
197 (interactive)
198 (let ((tagname
199 (completing-read
200 "Tag: "
201 wesnoth-tags-list
202 nil nil)))
203 (progn
204 (beginning-of-line)
205 (unless (looking-at "^[\t ]*$")
206 (end-of-line)
207 (insert "\n"))
208 (end-of-line)
209 (insert "[" tagname "]")
210 (wesnoth-indent-line)
211 (insert "\n")
212 (wesnoth-indent-line)
213 (save-excursion
214 (insert "\n[/" tagname "]")
215 (wesnoth-indent-line)
216 (forward-line -1)))))
218 (defun wesnoth-jump-forward (repeat)
219 "Move point to the end of the next tag.
220 REPEAT is an optional numeric argument. If REPEAT is non-nil,
221 jump forward the specified number of tags."
222 (interactive "p")
223 (or repeat (setq repeat 1))
224 (and (< repeat 0) (wesnoth-jump-backward (abs repeat)))
225 (let ((iterations 0))
226 (while (< iterations repeat)
227 (end-of-line)
228 (search-forward-regexp
229 (concat "^[\t ]*\\(\\[\\w+\\]\\|"
230 wesnoth-preprocessor-opening-regexp "\\)")
231 (buffer-size) t)
232 (setq iterations (1+ iterations)))))
234 (defun wesnoth-jump-backward (repeat)
235 "Move point to the beginning of the previous tag.
236 REPEAT is an optional numeric argument. If REPEAT is non-nil,
237 jump backward the specified number of tags."
238 (interactive "p")
239 (or repeat (setq repeat 1))
240 (and (< repeat 0) (wesnoth-jump-forward (abs repeat)))
241 (let ((iterations 0))
242 (while (< iterations repeat)
243 (beginning-of-line)
244 (search-backward-regexp
245 (concat "^[\t ]*\\(\\[\\w+\\]\\|"
246 wesnoth-preprocessor-opening-regexp "\\)")
247 0 t)
248 (unless (bobp)
249 (search-forward-regexp "[^[:blank:]]")
250 (backward-char))
251 (setq iterations (1+ iterations)))))
253 (defun wesnoth-jump-to-matching ()
254 "Jump point to the matching opening/closing tag.
255 A tag must be on the same line as point for jumping to occur. If
256 the tag structure is not correct this may have unexpected
257 results."
258 (interactive)
259 (let ((open-tags 0)
260 (search-started nil)
261 (tag-position nil)
262 (search-backward nil))
263 (save-excursion
264 (beginning-of-line)
265 (if (looking-at
266 (concat "^[\t ]*\\(\\[\\|"
267 wesnoth-preprocessor-regexp "\\)"))
268 (when (looking-at
269 (concat "^[\t ]*\\(\\[/\\|#\\(?:endif\\|enddef\\)\\)"))
270 (setq search-backward t))
271 (if (wesnoth-wml-start-pos)
272 (if (> (point) (wesnoth-wml-start-pos))
273 (search-backward-regexp
274 (concat "^[\t ]*\\(\\[\\|"
275 wesnoth-preprocessor-regexp "\\)")
276 (point-min) t)
277 (goto-char (point-min))
278 (search-forward-regexp
279 (concat "^[\t ]*\\(\\[\\|"
280 wesnoth-preprocessor-regexp "\\)"))
281 (beginning-of-line))
282 (error "Unable to locate tag to jump from")))
283 (if search-backward
284 (progn
285 (end-of-line)
286 (while (and
287 (or (< open-tags 0) (not search-started))
288 (search-backward-regexp
289 (concat "^[\t ]*\\(\\[\\|"
290 wesnoth-preprocessor-regexp "\\)")
291 (point-min) t))
292 (setq search-started t)
293 (if (looking-at
294 "^[\t ]*\\(\\[\\w+\\]\\|#\\(?:define\\|ifdef\\) \\)")
295 (setq open-tags (1+ open-tags))
296 (when (looking-at
297 (concat "^[\t ]*\\(\\[/\\w+\\]\\|#\\(?:endif\\|enddef\\)\\)"))
298 (setq open-tags (1- open-tags))))))
299 (while (and
300 (or (> open-tags 0) (not search-started))
301 (search-forward-regexp
302 (concat "^[\t ]*\\(\\[\\|"
303 wesnoth-preprocessor-regexp "\\)")
304 (point-max) t))
305 (beginning-of-line)
306 (setq search-started t)
307 (if (looking-at
308 "^[\t ]*\\(\\[\\w+\\]\\|#\\(?:define\\|ifdef\\) \\)")
309 (setq open-tags (1+ open-tags))
310 (when (looking-at
311 (concat "^[\t ]*\\(\\[/\\w+\\]\\|#\\(?:endif\\|enddef\\)\\)"))
312 (setq open-tags (1- open-tags))))
313 (end-of-line)))
314 (setq tag-position (point)))
315 (if interactive-p
316 (goto-char tag-position)
317 tag-position))
318 (end-of-line)
319 (search-backward-regexp "\\[\\|#"))
321 (defun wesnoth-wml-start-pos ()
322 "Determine the position of `point' relative to where the actual WML begins.
323 Return the likely starting position of the WML if it is found.
324 Otherwise return nil."
325 (save-excursion
326 (goto-char (point-min))
327 (when (search-forward-regexp
328 (concat "^[\t ]*\\(\\[\\)\\|\\("
329 wesnoth-preprocessor-opening-regexp
330 "\\)")
331 (buffer-size) t)
332 (beginning-of-line)
333 (point))))
335 (defun wesnoth-indent-line-default ()
336 "Indent the current line as WML using normal-style indentation."
337 (beginning-of-line)
338 (if (or (not (wesnoth-wml-start-pos))
339 (<= (point) (wesnoth-wml-start-pos))
340 (nth 3 (syntax-ppss (point))))
341 (indent-line-to 0)
342 (let ((not-indented t) cur-indent)
343 (if (looking-at
344 (concat "^[ \t]*\\(\\[\\/[^]]*?\\|\\("
345 wesnoth-preprocessor-closing-regexp "\\)\\)"))
346 (progn
347 (save-excursion
348 (search-backward-regexp
349 (concat "^[\t ]*\\[\\|\\(" wesnoth-preprocessor-regexp "\\)"))
350 (setq cur-indent (current-indentation))
351 (beginning-of-line)
352 (when (looking-at
353 (concat "^[\t ]*\\(\\[/.+\\]\\|\\("
354 wesnoth-preprocessor-closing-regexp
355 "\\)\\)"))
356 (setq cur-indent (- (current-indentation) wesnoth-indent))))
357 (if (< cur-indent 0)
358 (setq cur-indent 0)))
359 (if (not (looking-at
360 (concat "^[ \t]*\\(\\[[^/]*?\\]\\|\\("
361 wesnoth-preprocessor-closing-regexp "\\)\\)")))
362 (save-excursion
363 (search-backward-regexp
364 (concat "^[\t ]*\\(\\[\\|\\("
365 wesnoth-preprocessor-regexp "\\)\\)"))
366 (if (looking-at
367 (concat "^[\t ]*\\(\\[/.+\\]\\|\\("
368 wesnoth-preprocessor-closing-regexp
369 "\\)\\)"))
370 (progn
371 (setq cur-indent (- (current-indentation) wesnoth-indent))
372 (if (< cur-indent 0)
373 (setq cur-indent 0))
374 (setq not-indented nil))
375 (setq cur-indent (current-indentation))
376 (setq not-indented nil)))
377 (save-excursion
378 (unless cur-indent
379 (search-backward-regexp
380 (concat "^[\t ]*\\([[}]\\|\\("
381 wesnoth-preprocessor-regexp
382 "\\)\\)"))
383 (if (looking-at
384 (concat "^[ \t]*\\(\\[[^/]*?\\]\\|\\("
385 wesnoth-preprocessor-opening-regexp "\\)\\)"))
386 (setq cur-indent (+ (current-indentation) wesnoth-indent))
387 (setq cur-indent (current-indentation)))))))
388 (unless (and (not cur-indent) (= (current-indentation) cur-indent))
389 (indent-line-to cur-indent))))
390 (end-of-line))
392 (defun wesnoth-indent-line-savefile ()
393 "Indent the current line as WML code using savefile-style indentation."
394 (beginning-of-line)
395 (if (or (not (wesnoth-wml-start-pos))
396 (<= (point) (wesnoth-wml-start-pos))
397 (nth 3 (syntax-ppss (point))))
398 (indent-line-to 0)
399 (let ((cur-indent))
400 (if (looking-at
401 (concat "^[ \t]*\\(\\[\\/[^]]*?\\|\\("
402 wesnoth-preprocessor-closing-regexp "\\)\\)"))
403 (progn
404 (save-excursion
405 (search-backward-regexp "^[ \t]+.\\|^[{[#]")
406 (setq cur-indent (- (current-indentation) wesnoth-indent))
407 (when (looking-at
408 (concat "^[ \t]*\\(\\[[^/].+\\]\\|\\("
409 wesnoth-preprocessor-opening-regexp "\\)\\)"))
410 (setq cur-indent (current-indentation))))
411 (if (< cur-indent 0)
412 (setq cur-indent 0)))
413 (save-excursion
414 (unless cur-indent
415 (search-backward-regexp "^[\t ]*\\([[#}]\\)")
416 (if (looking-at
417 (concat "^[ \t]*\\(\\[[^/]+?\\]\\|\\("
418 wesnoth-preprocessor-opening-regexp "\\)\\)"))
419 (setq cur-indent (+ (current-indentation) wesnoth-indent))
420 (setq cur-indent (current-indentation))))))
421 (unless (and (not cur-indent)
422 (= (current-indentation) cur-indent))
423 (indent-line-to cur-indent))))
424 (end-of-line))
426 (defun wesnoth-newline ()
427 "Indent both the current line and the newline created.
428 If `wesnoth-auto-indent-flag' is nil, indentation will not be
429 performed."
430 (interactive)
431 (when wesnoth-auto-indent-flag
432 (save-excursion
433 (beginning-of-line)
434 (if (looking-at "^[\t ]*$")
435 (indent-line-to 0)
436 (wesnoth-indent-line))))
437 (newline))
439 (defun wesnoth-newline-and-indent ()
440 "Indent both the current line and the newline created.
441 If `wesnoth-auto-indent-flag' is nil, indentation will not be
442 performed."
443 (interactive)
444 (wesnoth-newline)
445 (wesnoth-indent-line))
447 (defun wesnoth-check-tag-names ()
448 "Check the names of all tags in the buffer for correctness.
449 If a tag is found which is not present in the list an error will
450 be signalled and point will be moved to the corresponding
451 position."
452 (interactive)
453 (let ((tag-position nil)
454 (missing-tag-name nil))
455 (save-excursion
456 (goto-char (point-min))
457 (while (and
458 (search-forward-regexp "^[\t ]*\\[" (point-max) t)
459 (not tag-position))
460 (beginning-of-line)
461 (when (looking-at "^[\t ]*\\[[/]?\\(\\w+\\)\\]")
462 (unless (member (match-string-no-properties 1) wesnoth-tags-list)
463 (setq tag-position (point))
464 (setq missing-tag-name (match-string-no-properties 1))))
465 (end-of-line)))
466 (when tag-position
467 (goto-char tag-position)
468 (message "'%s' is not known to exist"
469 missing-tag-name))))
471 (defun wesnoth-check-structure ()
472 "Check the buffer for correct nesting of elements.
473 If a problem is found in the structure, point will be placed at
474 the location which an element was expected and the expected
475 element will be displayed in the minibuffer."
476 (interactive)
477 (let ((unmatched-tag-list '())
478 (error-position nil))
479 (save-excursion
480 (goto-char (point-min))
481 (while (and
482 (search-forward-regexp
483 (concat "^[\t ]*\\[\\|\\(" wesnoth-preprocessor-regexp "\\)")
484 (point-max) t)
485 (not error-position))
486 (beginning-of-line)
487 (if (looking-at "^[\t ]*#\\(\\w+\\)")
488 (let ((preprocessor-name (match-string-no-properties 1)))
489 (cond
490 ((string= preprocessor-name "define")
491 (setq unmatched-tag-list
492 (cons preprocessor-name unmatched-tag-list)))
493 ((string= preprocessor-name "ifdef")
494 (setq unmatched-tag-list
495 (cons preprocessor-name unmatched-tag-list)))
496 ((string= preprocessor-name "else")
497 (unless (string= (car unmatched-tag-list) "ifdef")
498 (setq error-position (point))))
499 ((string= preprocessor-name "endif")
500 (if (string= (car unmatched-tag-list) "ifdef")
501 (setq unmatched-tag-list (cdr unmatched-tag-list))
502 (setq error-position (point))))
503 ((string= preprocessor-name "enddef")
504 (if (string= (car unmatched-tag-list) "define")
505 (setq unmatched-tag-list (cdr unmatched-tag-list))
506 (setq error-position (point))))))
507 (if (looking-at "^[\t ]*\\[\\(\\w+\\)\\]")
508 (setq unmatched-tag-list
509 (cons (match-string-no-properties 1)
510 unmatched-tag-list))
511 (when (looking-at "^[\t ]*\\[/\\(\\w+\\)\\]")
512 (if (string= (match-string-no-properties 1)
513 (car unmatched-tag-list))
514 (setq unmatched-tag-list (cdr unmatched-tag-list))
515 (setq error-position (point))))))
516 (end-of-line)))
517 (when (or unmatched-tag-list error-position)
518 (if error-position
519 (goto-char error-position)
520 (goto-char (point-max)))
521 (let ((expected nil))
522 (cond ((string= (car unmatched-tag-list) "define")
523 (setq expected "#enddef"))
524 ((string= (car unmatched-tag-list) "ifdef")
525 (setq expected "#endif"))
526 ((not unmatched-tag-list)
527 (setq expected "end of file")))
528 (and (interactive-p)
529 (message "Expecting: '%s'"
531 expected (concat "[/" (car unmatched-tag-list)
532 "]"))))
533 (or expected (concat "[/" (car unmatched-tag-list) "]"))))))
535 (defun wesnoth-analyse-structure ()
536 (interactive)
537 (save-excursion
538 (when (wesnoth-wml-start-pos)
539 (goto-char 0)
540 ;; search for both keys and tags
541 ;; if looking at a tag, append to list of tags
542 ;; check that tag is a possible child
543 ;; assess all keys found
544 ;; remove tag from list when closing is found
545 ;; repeat generating a list of errors/warnings as it goes (in compile buffer)
546 (while taglist
547 ;; (search-forward-regexp "\\[\\(\\w+\\)\\]")
548 (let ((tag (match-string 1))
549 (info (member tag wesnoth-wml-structure))
550 (keys (car info))
551 (subtags (cdr info))
552 (tag-end (wesnoth-jump-to-matching)))
553 )))))
555 (defun wesnoth-fix-structure ()
556 "Attempt to fix all faults in the structure of the current buffer."
557 (interactive)
558 (let ((element (wesnoth-check-structure)))
559 (while element
560 (if (string= element "end of file")
561 (delete-region (point) (point-max))
562 (beginning-of-line)
563 (open-line 1)
564 (insert element)
565 (wesnoth-indent-line))
566 (setq element (wesnoth-check-structure)))))
568 (defun wesnoth-indent-line ()
569 "Determine and performs indentation on the current line.
570 The Indentation style can be customised by modifying
571 `wesnoth-indentation-function'."
572 (interactive)
573 (funcall wesnoth-indentation-function))
575 (define-derived-mode wesnoth-mode fundamental-mode "wesnoth-mode"
576 "Major mode for editing WML."
577 (set-syntax-table wesnoth-syntax-table)
578 (set (make-local-variable 'outline-regexp) "^[\t ]*\\[\\w+")
579 (set (make-local-variable 'comment-start) "#")
580 (set (make-local-variable 'indent-line-function) 'wesnoth-indent-line)
581 (set (make-local-variable 'font-lock-defaults)
582 '(wesnoth-font-lock-keywords
583 nil t nil nil
584 (font-lock-syntactic-keywords . wesnoth-syntactic-keywords)))
585 (setq mode-name "WML")
586 (run-hooks 'wesnoth-mode-hook))
588 (provide 'wesnoth-mode)