update to the wml emacs mode

This commit is contained in:
Fabian Müller 2008-10-11 15:20:34 +00:00
parent be7411fc8b
commit 18e54dca6d
4 changed files with 734 additions and 136 deletions

View file

@ -1,6 +1,8 @@
;;; wesnoth-mode.el --- A major mode for editing WML.
;; Copyright (C) 2006, 2007, 2008 Chris Mann
;; This file is part of wesnoth-mode.
;; This program is free software; you can redistribute it and/or
;; modify it under the terms of the GNU General Public License as
;; published by the Free Software Foundation; either version 2 of the
@ -31,6 +33,26 @@
;; to automatically load wesnoth-mode for all files ending in '.cfg'.
;;; History:
;; 1.3.0
;; * Added support for Xemacs.
;; * WML checking is now context sensitive; checks attributes and macros.
;; * WML checks are now always performed on the entire buffer, with results
;; displayed in a temporary buffer.
;; * Context-sensitive completion for attributes and tags.
;; * Completion for built-in and project-specific macros.
;; * Changed the following bindings:
;; `wesnoth-insert-tag' - C-c e -> C-c t
;; `wesnoth-jump-to-matching' - C-c m -> C-c o
;; `wesnoth-check-structure' -> `wesnoth-check-wml' - C-c c
;; * Added the following bindings:
;; `wesnoth-complete-attribute' - C-c a
;; `wesnoth-complete-macro' - C-c m
;; `wesnoth-complete-tag' - C-c t
;; * Removed the following bindings:
;; `wesnoth-check-tag-names' - C-c n
;; * Removed `wesnoth-check-tag-names'. Replaced by `wesnoth-check-wml'.
;; * Completion for an incomplete attribute, tag, or macro at point is
;; attempted via TAB.
;; 1.2.5
;; * Fixed support for GNU Emacs 21.
;; * Added several new tags to `wesnoth-tags-list'.
@ -103,9 +125,13 @@
;; * Added support for #ifndef.
;;; Code:
(require 'cl)
(eval-when-compile
(require 'cl))
(require 'easymenu)
(require 'wesnoth-update)
(require 'wesnoth-wml-data)
(defconst wesnoth-mode-version "1.2.5a"
(defconst wesnoth-mode-version "1.3.0"
"The current version of `wesnoth-mode'.")
(defgroup wesnoth-mode nil "Wesnoth-mode access"
@ -130,7 +156,7 @@ level as their parent.")
:group 'wesnoth-mode)
(defconst wesnoth-preprocessor-regexp
"[\t ]*#\\(enddef\\|define \\|e\\(lse\\|nd\\(?:\\(?:de\\|i\\)f\\)\\)\\|\\(ifn?\\|un\\)def\\)"
"[\t ]*#\\(enddef\\|define \\|e\\(lse\\|nd\\(\\(de\\|i\\)f\\)\\)\\|\\(ifn?\\|un\\)def\\)"
"Regular expression to match all preprocessor statements.")
(defconst wesnoth-preprocessor-opening-regexp
@ -138,7 +164,7 @@ level as their parent.")
"Regular expression to match \"opening\" preprocessor statements.")
(defconst wesnoth-preprocessor-closing-regexp
"[\t ]*#e\\(lse\\|nd\\(\\(de\\|i\\)f\\)\\)"
"[\t ]*#\\(e\\(lse\\|nd\\(\\(de\\|i\\)f\\)\\)\\)"
"Regular expression to match \"closing\" preprocessor statements.")
(defvar wesnoth-define-blocks '()
@ -152,31 +178,29 @@ level as their parent.")
(define-key map (kbd "C-M-e") 'wesnoth-forward-element)
(define-key map (kbd "C-m") 'wesnoth-newline)
(define-key map (kbd "C-j") 'wesnoth-newline-and-indent)
(define-key map (kbd "C-c c") 'wesnoth-check-structure)
(define-key map (kbd "C-c C-c") 'wesnoth-check-structure)
(define-key map (kbd "C-c e") 'wesnoth-insert-tag)
(define-key map (kbd "C-c C-e") 'wesnoth-insert-tag)
(define-key map (kbd "M-TAB") 'wesnoth-insert-tag)
(define-key map (kbd "C-c m") 'wesnoth-jump-to-matching)
(define-key map (kbd "C-c C-m") 'wesnoth-jump-to-matching)
(define-key map (kbd "C-c n") 'wesnoth-check-tag-names)
(define-key map (kbd "C-c C-n") 'wesnoth-check-tag-names)
(define-key map (kbd "C-c /") 'wesnoth-insert-missing-closing)
(define-key map (kbd "C-c C-c") 'wesnoth-check-wml)
(define-key map (kbd "C-c C-a") 'wesnoth-complete-attribute)
(define-key map (kbd "C-c C-t") 'wesnoth-complete-tag)
(define-key map (kbd "M-TAB") 'wesnoth-complete-tag)
(define-key map (kbd "C-c C-m") 'wesnoth-complete-macro)
(define-key map (kbd "C-c C-o") 'wesnoth-jump-to-matching)
(define-key map (kbd "C-c C-/") 'wesnoth-insert-missing-closing)
(define-key map [menu-bar wesnoth]
(cons "WML" (make-sparse-keymap "WML")))
(define-key map [menu-bar wesnoth insert-tag]
'("Insert Tag" . wesnoth-insert-tag))
(define-key map [menu-bar wesnoth check-names]
'("Check Tag Names" . wesnoth-check-tag-names))
(define-key map [menu-bar wesnoth check-structure]
'("Check Structure" . wesnoth-check-structure))
(define-key map [menu-bar wesnoth jump-to-matching]
'("Jump to Matching" . wesnoth-jump-to-matching))
(define-key map [menu-bar wesnoth insert-missing-closing]
'("Insert Missing Tag" . wesnoth-insert-missing-closing))
(define-key map (kbd "TAB") 'wesnoth-indent-or-complete)
map)
"Keymap used in wesnoth mode.")
"Keymap used in `wesnoth-mode'.")
(easy-menu-define wesnoth-menu wesnoth-mode-map "Menu for wesnoth-mode"
'("WML"
["Check WML" wesnoth-check-wml t]
["Indent or Complete" wesnoth-indent-or-complete t]
["Indent buffer" (lambda ()
(interactive)
(wesnoth-indent-region (point-min) (point-max))) t]
["Insert Tag" wesnoth-complete-tag t]
["Insert Attribute" wesnoth-complete-attribute t]
["Insert Macro" wesnoth-complete-macro t]
["Jump to Matching" wesnoth-jump-to-matching t]
["Insert Missing Tag" wesnoth-insert-missing-closing t]))
(defvar wesnoth-syntax-table
(let ((wesnoth-syntax-table (make-syntax-table)))
@ -197,24 +221,15 @@ level as their parent.")
'("\\(#[\t ]*.*$\\)" 1 "<"))
"Highlighting syntactic keywords within `wesnoth-mode'.")
(defun wesnoth-preprocessor-best-face ()
"Use `font-lock-preprocessor-face' when available."
(when global-font-lock-mode
(if (boundp 'font-lock-preprocessor-face)
(copy-face 'font-lock-preprocessor-face 'wesnoth-preprocessor-face)
(copy-face 'font-lock-keyword-face 'wesnoth-preprocessor-face))))
(defvar wesnoth-font-lock-keywords
(list
'("#\\(?:define\\|\\(?:ifn?\\|un\\)def\\)" . 'wesnoth-preprocessor-face)
'("#\\(?:define\\|\\(?:ifn?\\|un\\)def\\)" . font-lock-keyword-face)
'("\\(#e\\(?:lse\\|nd\\(?:\\(?:de\\|i\\)f\\)\\)\\)" .
font-lock-keyword-face)
'("\\(#\\(?:define\\|\\(?:ifn?\\|un\\)def\\)\\)[\t ]+\\(\\(\\w\\|_\\)+\\)"
2 font-lock-function-name-face)
'("\\(#e\\(?:lse\\|nd\\(?:\\(?:de\\|i\\)f\\)\\)\\)" .
'wesnoth-preprocessor-face)
'("\\({[@~]?\\(\\w\\|\\.\\|/\\|-\\)+}\\)"
(1 font-lock-function-name-face))
'("\\({\\(\\w\\|:\\|_\\)+\\|{[~@]?\\)"
(1 font-lock-function-name-face))
'("\\({[@~]?\\(\\w\\|\\.\\|/\\|-\\)+}\\)" (1 font-lock-function-name-face))
'("\\({\\(\\w\\|:\\|_\\)+\\|{[~@]?\\)" (1 font-lock-function-name-face))
'("}" . font-lock-function-name-face)
'("^[\t ]*\\(\\[[^]]+\\]\\)" 1 font-lock-type-face)
'("\\$\\(\\w\\|_\\)+" . font-lock-variable-name-face)
@ -231,60 +246,170 @@ level as their parent.")
(defconst wesnoth-element "^[\t ]*\\(\\[[^]]?\\|#define\\|#enddef\\)"
"String to use for an opening or closing element.")
;;; Insertion
(defvar wesnoth-tags-list
(list
"abilities" "about" "advances" "advancefrom" "ai" "allow_recruit" "and"
"animation" "array" "attack" "attack_anim" "attacks" "avoid" "binary_path"
"bold" "campaign" "capture_village" "choose""clear_variable"
"colour_adjust" "command" "deaths" "debug_message" "defend" "defends"
"defense" "delay" "destination" "disallow_recruit" "do" "effect" "else"
"end_turn" "endlevel" "entry" "era" "event" "expenses" "filter"
"filter_attack" "filter_adjacent_location" "filter_location"
"filter_radius" "filter_second" "filter_vision" "format" "frame"
"game_config" "generator" "gold" "have_unit" "header" "hide_unit" "if"
"illuminated_time" "image" "img" "income" "italic" "item" "jump" "kill"
"killed" "label" "language" "leader_goal" "main_map" "menu" "message"
"mini_map" "missile_frame" "modifications" "modify_side" "modify_turns"
"move" "move_unit_fake" "movement_costs" "movetype" "multiplayer"
"multiplayer_side" "music" "not" "num_units" "object" "objectives"
"objective" "observers" "option" "or" "panel" "part" "place_shroud"
"position" "print" "protect_location" "protect_unit" "race" "random"
"recall" "recalls" "recruit" "recruits" "redraw" "ref" "remove_shroud"
"remove_unit_overlay" "removeitem" "replay" "replay_start" "resistance"
"resolution" "results" "role" "save" "scenario" "scroll" "scroll_to"
"scroll_to_unit" "section" "set_menu_item" "set_recruit" "set_specials"
"set_variable" "show_if" "side" "side_playing" "snapshot" "sound" "source"
"specials" "statistics" "status" "stone" "store_gold" "store_locations"
"store_starting_location" "store_side" "store_unit" "story" "target" "team"
"teleport" "teleport_anim" "terrain" "terrain_graphics" "terrain_mask"
"test" "text_input" "textdomain" "theme" "then" "tile" "time" "time_area"
"time_of_day" "topic" "toplevel" "trait" "turn" "tutorial" "unhide_unit"
"unit" "unit_abilities" "unit_alignment" "unit_description" "unit_hp"
"unit_image" "unit_level" "unit_moves" "unit_overlay" "unit_profile"
"unit_status" "unit_traits" "unit_type" "unit_weapons" "unit_xp" "units"
"unstone" "unstore_unit" "upkeep" "variable" "variables" "village"
"villages" "while" "wml_filter")
"A list containing all tags which are available in WML.")
;;; Insertion and completion
(defmacro wesnoth-element-completion (completions prompt partial)
"Process completion of COMPLETIONS, displaying PROMPT.
PARTIAL is the partial string on which to attempt completion."
`(let* ((element (when ,partial (try-completion ,partial ,completions))))
(cond ((eq element t)
(setq element nil))
((null element)
(setq element
(completing-read ,prompt ,completions)))
((not (if (listp (car ,completions))
(assoc element ,completions)
(member element ,completions)))
(setq element
(completing-read ,prompt ,completions
nil nil ,partial))))
element))
(defvar wesnoth-completion-cache '()
"List of tags which have been generated by `wesnoth-build-completion'.")
(defun wesnoth-parent-tag ()
"Return the name of the parent tag, nil otherwise."
(save-excursion
(let ((parent (when (and (wesnoth-wml-start-pos)
(> (point) (wesnoth-wml-start-pos)))
(wesnoth-check-structure (wesnoth-wml-start-pos)
(point)))))
(when parent
(if (string-match wesnoth-preprocessor-closing-regexp parent)
t
(substring parent 2 (1- (length parent))))))))
(defun wesnoth-build-completion (&optional rebuild)
(defun wesnoth-indent-or-complete (&optional elements)
"Indent or complete the line at point, depending on context.
ELEMENTS is the number of elements to wrap around if inserting
matching tags."
(interactive "P")
(or elements (setq elements 0))
(let ((target nil))
(save-excursion
(back-to-indentation)
(cond ((looking-at "\\(\\(\\w\\|_\\)+\\)[\t ]*$")
(wesnoth-complete-attribute t))
((looking-at "\\[\\(\\(\\w\\|_\\)*\\)[\t ]*$")
(wesnoth-complete-tag elements t))
((looking-at "{\\(\\(\\w\\|_\\)*\\)[\t ]*$")
(wesnoth-complete-macro t))
((looking-at "\\[/\\(\\(\\w\\|_\\)*\\)[\t ]*$")
(delete-region (point) (progn (end-of-line) (point)))
(wesnoth-insert-missing-closing)
(end-of-line))
(t
(wesnoth-indent)))
(setq target (point)))
(goto-char target)))
(defun wesnoth-complete-macro (&optional completep)
"Complete and insert the macro at point.
If COMPLETEP is non-nil, attempt to complete the macro at point."
(interactive)
(let* ((macro-information (append wesnoth-macro-data
wesnoth-local-macro-data))
(completions (wesnoth-emacs-completion-formats
(mapcar 'car macro-information)))
(partial (when completep
(save-excursion
(back-to-indentation)
(when (looking-at "{\\(\\(\\w\\|_\\)*\\)")
(match-string 1)))))
(macro (or (wesnoth-element-completion completions "Macro: " partial)
partial))
(args (cadr (assoc macro macro-information))))
(when macro
(if partial
(progn
(delete-region (progn (back-to-indentation) (point))
(progn (end-of-line) (point)))
(insert "{" macro (if args " }" "}")))
(wesnoth-insert-element-separately "{" macro (if args " }" "}")))
(save-excursion
(wesnoth-indent))
(when args
(forward-char -1)))))
(defun wesnoth-complete-attribute (&optional completep)
"Insert the attribute at point.
If COMPLETEP is non-nil, attempt to complete the attribute at point."
(interactive)
(let* ((completions (wesnoth-build-completion 2))
(partial (when completep
(save-excursion
(back-to-indentation)
(when (looking-at "\\(\\(\\w\\|_\\)+\\)")
(match-string 1)))))
(attribute (or (wesnoth-element-completion completions "Attribute: "
partial)
partial)))
(when attribute
(if (and partial completep)
(progn
(delete-region (progn (back-to-indentation) (point))
(progn (end-of-line) (point)))
(insert attribute "="))
(wesnoth-insert-element-separately attribute
(if (string-match "=" attribute)
""
"=")))
(save-excursion
(wesnoth-indent)))))
(defun wesnoth-complete-tag (&optional elements completep)
"Complete and insert the tag at point.
ELEMENTS is the number of elements to wrap around.
If COMPLETEP is non-nil, attempt to complete tag at point."
(interactive "P")
(or elements (setq elements 0))
(let* ((completions (wesnoth-build-completion 1))
(partial (when completep
(save-excursion
(back-to-indentation)
(when (looking-at "\\[\\(\\(\\w\\|_\\)+\\)")
(match-string 1)))))
(tag (or (wesnoth-element-completion completions "Tag: " partial)
partial)))
(let ((closed-p nil))
(save-excursion
(wesnoth-jump-to-matching)
(back-to-indentation)
(when (and (looking-at "\\[/\\(\\(\\w\\|_\\)+\\)")
(string= tag (match-string 1)))
(setq closed-p t)))
(when completep
(delete-region (progn (back-to-indentation) (point))
(progn (end-of-line) (point))))
(if (and closed-p completep)
(progn
(wesnoth-insert-and-indent "[" tag "]")
(end-of-line))
(wesnoth-insert-tag elements tag)))))
(defun wesnoth-build-completion (position)
"Create a new list for tag completion if necessary.
Rebuilding list is required for versions of GNU Emacs earlier
than 22. If REBUILD is non-nil, regenerate `wesnoth-completion-cache'."
than 22. POSITION is the argument passed to `nth' for
`wesnoth-tag-data'."
(interactive "P")
(let* ((parent (wesnoth-parent-tag))
(candidates
(if (or (stringp parent) (null parent))
(dolist (tag wesnoth-tag-data)
(when (string= (car tag) (wesnoth-parent-tag))
(return (nth position tag))))
(mapcar 'car wesnoth-tag-data))))
(wesnoth-emacs-completion-formats candidates)))
(defun wesnoth-emacs-completion-formats (candidates)
"Return the completions in the correct format for `emacs-major-version'.
CANDIDATES is a list of all possible completions."
(if (> emacs-major-version 21)
wesnoth-tags-list
(if (and wesnoth-completion-cache (not rebuild))
wesnoth-completion-cache
(let ((tags '())
(iter 0))
(dolist (tag wesnoth-tags-list)
(setq iter (1+ iter))
(setq tags (append tags (list (cons tag iter)))))
(setq wesnoth-completion-cache tags)))))
candidates
(let ((tags '())
(iter 0))
(dolist (tag candidates)
(setq iter (1+ iter))
(setq tags (append tags (list (cons tag iter)))))
tags)))
(defun wesnoth-insert-tag (&optional elements tagname)
"Insert the specified opening tag and it's matching closing tag.
@ -298,8 +423,11 @@ tag should wrap around.
TAGNAME is the name of the tag to be inserted."
(interactive "Ps")
(unless tagname
(setq tagname (completing-read "Tag: " (wesnoth-build-completion))))
(or elements (setq elements 0))
(setq tagname (completing-read "Tag: " (wesnoth-build-completion 1))))
(when (or (not elements)
(looking-at (concat "[\t ]*\\(:?\\[/\\|"
wesnoth-preprocessor-regexp "\\)")))
(setq elements 0))
(let ((depth 0)
(start (save-excursion (forward-line -1) (point)))
(end (unless (= elements 0)
@ -321,16 +449,27 @@ COUNT is a positive number representing the number of balanced
pairs to move across.
`point' is returned as a marker object."
(save-excursion
(while (> count 0)
;; Currently looking-at target tag. Stop here to avoid
;; incorrect nesting.
(unless (wesnoth-search-for-matching-tag
'search-forward-regexp wesnoth-element-closing 'point-max)
(setq count 0)
(search-backward-regexp wesnoth-element-closing (point-min) t))
(and (> (decf count) 0) (forward-line 1)))
(end-of-line)
(point-marker)))
(let ((start (point))
(failed nil))
(if (> (point) (save-excursion (back-to-indentation) (point)))
(end-of-line)
(beginning-of-line))
(while (> count 0)
;; Currently looking-at target tag. Stop here to avoid
;; incorrect nesting.
(unless (wesnoth-search-for-matching-tag
'search-forward-regexp wesnoth-element-closing 'point-max)
(setq count 0)
(unless (or (= (point) (point-max))
(progn (beginning-of-line)
(search-backward-regexp wesnoth-element-closing
start t)))
(setq failed t)))
(and (> (decf count) 0) (forward-line 1)))
(if failed
(beginning-of-line)
(end-of-line))
(point-marker))))
(defun wesnoth-insert-element-separately (&rest strings)
"Concatenate STRINGS and insert them on a line of their own."
@ -355,7 +494,9 @@ specified will be used as START and END. Otherwise, START and
END will be the minimum and maximum positions of the buffer,
respectively."
(interactive)
(if (and transient-mark-mode mark-active)
(if (and (boundp 'transient-mark-mode)
transient-mark-mode
mark-active)
(setq start (region-beginning)
end (copy-marker (region-end)))
(setq start (point-min)
@ -366,7 +507,8 @@ respectively."
(when (string= element "Unexpected end of file")
(error "%s" element))
(wesnoth-insert-element-separately element)))
(wesnoth-indent))
(wesnoth-indent)
(end-of-line))
(defun wesnoth-insert-and-indent (&rest args)
"Concatenate and insert the given string(s) before indenting.
@ -427,15 +569,15 @@ SEARCH-FUNCTION is the name of the function used to perform the search.
SEARCH-STRING is a string representing the matching tag type.
BOUND is the bound to be passed to the search function."
`(let ((depth 1))
(unless (looking-at ,search-string)
(unless (> (point) (funcall ,bound)) (end-of-line))
(while (and (> depth 0)
(funcall ,search-function wesnoth-element
(funcall ,bound) t))
(if (string-match ,search-string (match-string 0))
(decf depth)
(incf depth)))
t)))
(when (funcall ,search-function wesnoth-element (funcall ,bound) t)
(unless (string-match ,search-string (match-string 0))
(while (and (> depth 0)
(funcall ,search-function wesnoth-element
(funcall ,bound) t))
(if (string-match ,search-string (match-string 0))
(decf depth)
(incf depth)))
t))))
(defun wesnoth-jump-to-matching ()
"Jump point to the matching opening/closing tag."
@ -444,6 +586,7 @@ BOUND is the bound to be passed to the search function."
(if (looking-at wesnoth-element-opening)
(wesnoth-search-for-matching-tag
'search-forward-regexp wesnoth-element-closing 'point-max)
(end-of-line)
(wesnoth-search-for-matching-tag
'search-backward-regexp wesnoth-element-opening 'wesnoth-wml-start-pos))
(back-to-indentation))
@ -577,30 +720,140 @@ be performed."
(wesnoth-indent)))
;;; WML checks
(defun wesnoth-check-tag-names ()
"Check the names of all tags in the buffer for correctness.
If a tag is found which is not present in the list an error will
be signalled and point will be moved to the corresponding
position."
(interactive)
(let ((tag-position nil)
(missing-tag-name nil))
(defun wesnoth-check-element-type (position last-tag)
"Determine the context of the element.
POSITION is the position of the element in the list.
LAST-TAG is the parent element."
(if (or (string= last-tag "#define")
(string= last-tag "#ifndef")
(string= last-tag "#ifdef"))
(member (match-string-no-properties 1)
(mapcar 'car wesnoth-tag-data))
(let ((result '()))
(dolist (tag wesnoth-tag-data)
(when (member (match-string-no-properties 1)
(funcall position tag))
(add-to-list 'result (car tag))))
(member last-tag result))))
;; Provide `line-number-at-pos' implementation (not available in Emacs 21).
(defun wesnoth-line-number-at-pos (&optional pos)
"Return (narrowed) buffer line number at position POS.
If POS is nil, use current buffer location.
Counting starts at (point-min), so the value refers
to the contents of the accessible portion of the buffer."
(let ((opoint (or pos (point))) start)
(save-excursion
(goto-char (point-min))
(while (and (search-forward-regexp "^[\t ]*\\[" (point-max) t)
(not tag-position))
(setq start (point))
(goto-char opoint)
(forward-line 0)
(1+ (count-lines start (point))))))
(defun wesnoth-check-output (buffer format-string &rest args)
"Output the string as passed to `format'.
BUFFER is the buffer to output the result.
FORMAT-STRING is the string as the first argument of `format'.
ARGS is any additional data required by `format' to handle FORMAT-STRING."
(save-excursion
(let ((lnap (wesnoth-line-number-at-pos)))
(set-buffer buffer)
(insert (apply 'format (concat "Line %d: " format-string "\n")
lnap args)))))
(defun wesnoth-check-wml ()
"Perform context-sensitive analysis of WML-code."
(interactive)
(wesnoth-update-project-information)
(unless wesnoth-tag-data
(error "WML data not available; can not generate report"))
(let ((unmatched-tag-list '())
(outbuf (get-buffer-create "*WML*")))
(save-excursion
(let ((buffer (buffer-name)))
(set-buffer outbuf)
(erase-buffer)
(insert (format "Checking %s...\n" buffer))
(message (format "Checking %s..." buffer))))
(save-excursion
(goto-char (or (wesnoth-wml-start-pos) (point-min)))
(while (search-forward-regexp
(concat "^[\t ]*\\(\\[[+/]?\\(\\(\\w\\|_\\)+\\)\\]\\|"
"\\(\\w\\|_\\)+=\\|{\\(\\(\\w\\|_\\)+\\).*}\\|"
wesnoth-preprocessor-regexp "\\)")
(point-max) t)
(beginning-of-line)
(when (looking-at "^[\t ]*\\[/?\\(\\(\\w\\|_\\)+\\|_\\)\\]")
(unless (member (match-string-no-properties 1) wesnoth-tags-list)
(setq tag-position (point))
(setq missing-tag-name (match-string-no-properties 1))))
(end-of-line)))
(if (not tag-position)
(message "%s" "No unknown tag names found.")
(goto-char tag-position)
(back-to-indentation)
(message "'%s' is not known to exist"
missing-tag-name))))
(cond ((looking-at "^[\t ]*\\[\\+?\\(\\(\\w\\|_\\)+\\)\\]")
(unless (wesnoth-check-element-type 'second
(car unmatched-tag-list))
(wesnoth-check-output outbuf
"Tag not available in this context: '%s'"
(match-string-no-properties 1)))
(setq unmatched-tag-list (cons
(match-string-no-properties 1)
unmatched-tag-list)))
((looking-at "[\t ]*\\(#define\\|#ifdef\\|#ifndef\\) ")
(setq unmatched-tag-list (cons (match-string-no-properties 1)
unmatched-tag-list)))
((looking-at wesnoth-preprocessor-closing-regexp)
(unless (string= (car unmatched-tag-list)
(second (assoc (match-string-no-properties 1)
'(("enddef" "#define")
("ifdef" "#endif")
("ifndef" "#endif")))))
(wesnoth-check-output
outbuf
"Preprocessor statement does not nest correctly"))
(setq unmatched-tag-list (cdr unmatched-tag-list)))
((looking-at "^[\t ]*\\(\\(\\w\\|_\\)+\\)=\\(.+\\)?")
(unless (wesnoth-check-element-type 'third
(car unmatched-tag-list))
(wesnoth-check-output
outbuf "Attribute not available in this context: '%s'"
(match-string-no-properties 1)))
(unless (match-string 3)
(wesnoth-check-output
outbuf "Attribute has no value")))
((looking-at "^[\t ]*#else")
(unless (string-match "ifn?def" (car unmatched-tag-list))
(if (string= (car unmatched-tag-list) "#define")
(wesnoth-check-output outbuf "Expecting: '%s'"
(car unmatched-tag-list))
(wesnoth-check-output outbuf "Expecting: '[/%s]'"
(car unmatched-tag-list)))))
((looking-at "^[\t ]*{\\(\\(\\w\\|_\\)+\\).*}")
(unless (assoc (match-string-no-properties 1)
(append wesnoth-local-macro-data
wesnoth-macro-data))
(wesnoth-check-output outbuf "Unknown macro definition: '{%s}'"
(match-string-no-properties 1))))
((or (looking-at "^[\t ]*\\[/\\(\\(\\w\\|_\\)+\\)\\]"))
(unless (string= (match-string-no-properties 1)
(car unmatched-tag-list))
(if (string-match "^#.+" (car unmatched-tag-list))
(wesnoth-check-output outbuf "Expecting: '#%s'"
(car
(assoc (car unmatched-tag-list)
'(("define" "#enddef")
("endif" "#ifdef")
("endif" "#ifndef")))))
(wesnoth-check-output outbuf "Expecting: '[/%s]'"
(car unmatched-tag-list))))
(setq unmatched-tag-list (cdr unmatched-tag-list))))
(end-of-line))
(if unmatched-tag-list
(dolist (tag unmatched-tag-list)
(wesnoth-check-output outbuf "Unmatched tag: '%s'"
(car unmatched-tag-list)))))
(save-excursion
(let ((buffer (buffer-name)))
(set-buffer outbuf)
(display-buffer outbuf t)
(let ((warnings (- (wesnoth-line-number-at-pos
(save-excursion (goto-char (point-max)))) 2)))
(insert (format (concat "\nCheck complete. %d warning"
(if (= warnings 1) "." "s.")) warnings)))
(message (format "Checking %s...done" buffer))))))
(defmacro wesnoth-element-requires (element requirement &optional pop)
"Process requirements for corresponding preprocessor elements.
@ -647,7 +900,8 @@ checked. Otherwise START and END will be the minimum and maximum
positions of the buffer, respectively."
(interactive)
(unless (or start end)
(if (and transient-mark-mode mark-active)
(if (and (boundp 'transient-mark-mode)
transient-mark-mode mark-active)
(setq start (region-beginning)
end (copy-marker (region-end)))
(setq start (point-min)
@ -681,7 +935,10 @@ positions of the buffer, respectively."
;;; wesnoth-mode
(define-derived-mode wesnoth-mode fundamental-mode "wesnoth-mode"
"Major mode for editing WML."
(wesnoth-preprocessor-best-face)
(kill-all-local-variables)
(use-local-map wesnoth-mode-map)
(setq major-mode 'wesnoth-mode)
(setq mode-name "WML")
(set-syntax-table wesnoth-syntax-table)
(set (make-local-variable 'outline-regexp) "[\t ]*#define")
(set (make-local-variable 'comment-start) "#")
@ -692,7 +949,7 @@ positions of the buffer, respectively."
nil t nil nil
(font-lock-syntactic-keywords . wesnoth-syntactic-keywords)))
(setq indent-tabs-mode nil)
(setq mode-name "WML")
(easy-menu-add wesnoth-menu wesnoth-mode-map)
(run-hooks 'wesnoth-mode-hook))
(provide 'wesnoth-mode)

View file

@ -0,0 +1,297 @@
;;; wesnoth-update.el --- Update known WML data via existing valid WML.
;; Copyright (C) 2008 Chris Mann
;; This file is part of wesnoth-mode.
;; This program is free software; you can redistribute it and/or
;; modify it under the terms of the GNU General Public License as
;; published by the Free Software Foundation; either version 2 of the
;; License, or (at your option) any later version.
;; This program is distributed in the hope that it will be useful, but
;; WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
;; General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program; see the file COPYING. If not, write to the
;; Free Software Foundation, 51 Franklin Street, Fifth Floor, Boston,
;; MA 02139, USA.
;;; Commentary:
;; Update WML information using WML built-in to Wesnoth.
;; The following should be added to your .emacs so that `wesnoth-update' can
;; correctly generate WML data:
;; (setq wesnoth-root-directory "/path/to/wesnoth/"
;; wesnoth-update-output-directory "/path/to/wesnoth-mode/"
;; wesnoth-addition-file "/path/to/wesnoth-mode/wesnoth-wml-additions.cfg")
;; Specifying the appropriate path in each case.
;; Although WML data is provided along with wesnoth-mode, you can generate
;; update-to-date, version-specific WML reference data for `wesnoth-mode'
;; using `wesnoth-update'. This requires Wesnoth to be install and its
;; pathname set for this to behave correctly. for example:
;; (setq wesnoth-root-directory "/usr/share/wesnoth/")
;; Then set the output directory for `wesnoth-update's results:
;; (setq wesnoth-update-output-directory "/path/to/wesnoth-mode/")
;; This is recommended to be in the same directory as `wesnoth-mode' and
;; must be in `load-path'.
;; Once set, `wesnoth-update' will produce 'wesnoth-wml-data.el' in
;; `wesnoth-update-output-directory' and the information will automatically
;; be available in the future sessions.
;; Although much data is retreived, it is unlikely to be completely
;; comprehensive. wesnoth-mode can be taught about additional tags,
;; attributes and macros using the current project, or a single file, using
;; `wesnoth-update-wml-additions'.
;; To teach wesnoth-mode about elements it may have missed, you can extend the
;; sample additions included with wesnoth-mode; namely
;; wesnoth-wml-additions.cfg (although any source of WML can be used). To
;; enable this, do the following:
;; Set `wesnoth-addition-file' appropriately, for example:
;; (setq wesnoth-addition-file "/path/to/wesnoth-wml-additions.cfg")
;; Once set correctly, running M-x wesnoth-update will update the WML data
;; available to `wesnoth-mode'.
;;; History:
;; 0.1
;; * Initial version
;;; Code:
(defvar wesnoth-update-version "0.1"
"Version of `wesnoth-update'.")
(defcustom wesnoth-root-directory nil
"Root directory of wesnoth."
:type 'directory
:group 'wesnoth-mode)
(defcustom wesnoth-addition-file nil
"Filename to the file containing additional WML information."
:type 'file
:group 'wesnoth-mode)
(defcustom wesnoth-update-output-directory nil
"Directory to write discovered WML syntax information.
Ensure this directory is in your `load-path'."
:type 'directory
:group 'wesnoth-mode)
(defconst wesnoth-macro-directory "data/core/macros"
"Directory which built-in macros are stored.
This is relative to the wesnoth directory in `wesnoth-root-directory.'.")
(defvar wesnoth-found-cfgs '()
"Temporary list of all .cfg files found.")
(defvar wesnoth-tag-data '()
"All information regarding the relation of tags and attributes.")
(defvar wesnoth-macro-data '()
"Information regarding built-in macros.")
(defvar wesnoth-local-macro-data '()
"All macro definitions available in the current project.")
(defun wesnoth-file-cfg-p (file)
"Return non-nil if FILE has a '.cfg' extension."
(and (not (file-directory-p file)) (string-match "\\.cfg$" file)))
(defun wesnoth-fetch-all-dirs (dir)
"Retrieve a list of subdirectories to scan.
DIR is the directory to check."
(let ((dirs-to-scan (wesnoth-files-in-dir dir)))
(while dirs-to-scan
(setq dirs-to-scan (append (wesnoth-files-in-dir (pop dirs-to-scan))
dirs-to-scan)))))
(defun wesnoth-files-in-dir (dir)
"Add cfgs to `wesnoth-files-in-dir'.
Returns a list of sub-directories in DIR."
(let ((cfgs (wesnoth-cfg-files-in-dir dir)))
(when cfgs
(setq wesnoth-found-cfgs (append cfgs wesnoth-found-cfgs))))
(let ((dirs '()))
(dolist (file (directory-files dir t))
(unless (string-match "^\\..*" (file-name-nondirectory file))
(cond ((file-directory-p file)
(add-to-list 'dirs file))
((wesnoth-file-cfg-p file)
(add-to-list 'wesnoth-found-cfgs file)))))
dirs))
(defun wesnoth-cfg-files-in-dir (dir)
"Return all cfg files in DIR."
(let ((result '()))
(dolist (file (directory-files dir t))
(and (wesnoth-file-cfg-p file)
(add-to-list 'result file)))
result))
(defun wesnoth-determine-details (dir-or-file function)
"Process .cfg files in DIR-OR-FILE using FUNCTION.
DIR-OR-FILE can be a file, a directory, or a list of files."
(cond ((listp dir-or-file)
(dolist (file dir-or-file)
(wesnoth-handle-file function file)))
((and (file-exists-p dir-or-file)
(not (file-directory-p dir-or-file)))
(wesnoth-handle-file function dir-or-file))
(t
(wesnoth-fetch-all-dirs dir-or-file)
(while wesnoth-found-cfgs
(unless (string-match "^\\..+" (file-name-nondirectory
(car wesnoth-found-cfgs)))
(wesnoth-handle-file function (car wesnoth-found-cfgs))
(setq wesnoth-found-cfgs (cdr wesnoth-found-cfgs)))))))
(defun wesnoth-handle-file (function file)
"Perform FUNCTION on FILE."
(with-temp-buffer
(when (file-exists-p file)
(insert-file-contents file)
(funcall function))))
(defun wesnoth-extract-tag-information ()
"Retrieve relevant tag and attribute information."
(let ((unmatched-tag-list '()))
(goto-char (point-min))
(while (search-forward-regexp
"^[\t ]*\\(\\[[+/]?\\(\\(\\w\\|_\\)+\\)\\]\\|\\(\\w\\|_\\)+=\\)"
(point-max) t)
(beginning-of-line)
(cond
((and (save-excursion
(search-backward-regexp
"^[\t ]*\\(\\[[^/]]?\\|#define \\|#enddef \\)"
(point-min) t))
(string-match "#define " (match-string 1))
(looking-at "^[\t ]*\\[\\+?\\(\\(\\w\\|_\\)+\\)\\]"))
(wesnoth-append-tag-information (match-string-no-properties 1) nil nil)
(setq unmatched-tag-list (cons (match-string-no-properties 1)
unmatched-tag-list)))
((looking-at "^[\t ]*\\[\\+?\\(\\(\\w\\|_\\)+\\)\\]")
(wesnoth-append-tag-information (car unmatched-tag-list)
(match-string-no-properties 1)
nil)
(wesnoth-append-tag-information (match-string-no-properties 1) nil nil)
(setq unmatched-tag-list (cons (match-string-no-properties 1)
unmatched-tag-list)))
((looking-at "^[\t ]*\\(\\(\\w\\|_\\)+\\)=")
(wesnoth-append-tag-information (car unmatched-tag-list)
nil (match-string-no-properties 1)))
((looking-at "^[\t ]*\\[/\\(\\(\\w\\|_\\)+\\)\\]\\|")
(when (string= (match-string-no-properties 1)
(car unmatched-tag-list))
(setq unmatched-tag-list (cdr unmatched-tag-list)))))
(end-of-line))))
(defun wesnoth-append-tag-information (tag subtag attribute)
"Add the information regarding TAG to the list.
SUBTAG and ATTRIBUTE are a children of TAG to be added."
(let ((match (assoc tag wesnoth-tag-data)))
(if (not match)
(add-to-list 'wesnoth-tag-data (list tag (and subtag (list subtag))
(and attribute (list attribute))))
(if subtag
(let ((tmp (nth 1 match)))
(when (not (member subtag tmp))
(add-to-list 'tmp subtag)
(setq match (list tag tmp (car (last match))))))
(when attribute (let ((tmp (nth 2 match)))
(when (not (member attribute tmp))
(add-to-list 'tmp attribute)
(setq match (list tag (nth 1 match) tmp))))))
(setq wesnoth-tag-data
(remove (assoc tag wesnoth-tag-data)
wesnoth-tag-data))
(add-to-list 'wesnoth-tag-data match))))
(defmacro wesnoth-determine-macro-information (macro-list)
"Process the buffer, retrieving macro definition information.
MACRO-LIST is the variable to append macro information."
`(progn
(goto-char (point-min))
(while (search-forward-regexp
"#define \\(\\(\\w\\|_\\)+\\)\\([\t ]+\\(\\w\\|_\\)+\\)?"
(point-max) t)
(beginning-of-line)
(add-to-list ,macro-list (list (match-string 1)
(when (match-string 3) t)))
(end-of-line))))
(defun wesnoth-determine-macro-builtins ()
"Retrieve built-in macro definition information."
(wesnoth-determine-macro-information 'wesnoth-macro-data))
(defun wesnoth-output-path ()
"Determine the path to output wml information via `wesnoth-update'."
(or wesnoth-update-output-directory
(and (boundp 'user-emacs-directory)
user-emacs-directory)
"~/.emacs.d/"))
(defun wesnoth-update-wml-additions ()
"Update WML information contained in `wesnoth-addition-file'."
(wesnoth-determine-details wesnoth-addition-file
'wesnoth-extract-tag-information)
(wesnoth-determine-details wesnoth-addition-file
'wesnoth-determine-macro-builtins))
(defun wesnoth-update ()
"Update WML information.
Path to WML information included in wesnoth is set by
`wesnoth-root-directory.'."
(interactive)
(setq wesnoth-tag-data nil
wesnoth-macro-data nil
wesnoth-found-cfgs nil)
(unless (and (stringp wesnoth-root-directory)
(file-exists-p wesnoth-root-directory))
;; Update failed; restore data.
(load "wesnoth-wml-data")
(error "%s: directory does not exist"
wesnoth-root-directory))
(message "Updating WML information...")
(wesnoth-determine-details wesnoth-root-directory
'wesnoth-extract-tag-information)
(wesnoth-update-wml-additions)
(wesnoth-determine-details (concat wesnoth-root-directory
wesnoth-macro-directory)
'wesnoth-determine-macro-builtins)
(with-temp-buffer
(insert (format "(setq wesnoth-tag-data '%S)\n\n" wesnoth-tag-data))
(insert (format "(setq wesnoth-macro-data '%S)\n\n" wesnoth-macro-data))
(insert "(provide 'wesnoth-wml-data)\n")
(write-file (expand-file-name (format "wesnoth-wml-data.el")
(wesnoth-output-path)))
(load "wesnoth-wml-data"))
(message "Updating WML information...done"))
(defun wesnoth-update-project-information ()
"Update WML macro information for the current project."
(interactive)
(wesnoth-determine-details (wesnoth-cfg-files-in-dir default-directory)
(lambda ()
(wesnoth-determine-macro-information
'wesnoth-local-macro-data))))
(defun wesnoth-update-teach-wesnoth-mode (file-or-dir)
"Update WML tag and attribute information for the current project.
If FILE-OR-DIR is provided, perform the update using only that location."
(interactive)
(wesnoth-determine-details
file-or-dir
(lambda ()
(wesnoth-determine-macro-information 'wesnoth-macro-data)))
(wesnoth-determine-details file-or-dir
'wesnoth-extract-tag-information))
(provide 'wesnoth-update)
;;; wesnoth-update.el ends here

View file

@ -0,0 +1,39 @@
# This is a sample file to demonstrate the format for providing additional WML
# information to wesnoth-mode. The format is an outline of a valid element
# structure in WML; attributes do not currently requiring a value.
[multiplayer]
[music]
name=
append=
play_once=
immediate=
ms_before=
ms_after=
[/music]
[event]
[unit]
[status]
poisoned=
slowed=
stone=
hides=
[/status]
[modifications]
[object]
[effect]
attack_weight=
[/effect]
[/object]
[/modifications]
[/unit]
[/event]
[/multiplayer]
[era]
[multiplayer_side]
[/multiplayer_side]
[/era]
# wesnoth-wml-additions.cfg ends here

File diff suppressed because one or more lines are too long