new upstream version of the emacs mode for wml.

for a changelog see the thread
http://www.wesnoth.org/forum/viewtopic.php?f=21&t=13798&start=15
This commit is contained in:
Fabian Müller 2008-10-26 14:18:59 +00:00
parent 0d9afacd1c
commit 8d923fb563
3 changed files with 439 additions and 222 deletions

View file

@ -20,9 +20,9 @@
;;; Description:
;; wesnoth-mode is a major mode for Emacs which assists in the editing
;; of Wesnoth Markup Language (WML) files. Currently, this mode
;; of Wesnoth Markup Language (WML) files. Currently, this major-mode
;; features syntax highlighting support, automatic indentation,
;; tag-completion and preliminary support for syntax checking.
;; context-sensitive completion and WML checking.
;;; Commentary:
;; Add the following to your .emacs:
@ -33,6 +33,15 @@
;; to automatically load wesnoth-mode for all files ending in '.cfg'.
;;; History:
;; 1.3.1
;; * Completion history available is now specific to wesnoth-mode.
;; * Added binding to explicitly update macro information from the current
;; buffer (C-c C-u).
;; * Significantly improved performance of completion and WML checking.
;; * Improved performance for inserting missing tags.
;; * Fixed a bug where #ifdef was never matched when checking WML.
;; * Added completion for preprocessor statements.
;; * Improved macro completion and checking.
;; 1.3.0
;; * Added support for Xemacs.
;; * WML checking is now context sensitive; checks attributes and macros.
@ -131,7 +140,7 @@
(require 'wesnoth-update)
(require 'wesnoth-wml-data)
(defconst wesnoth-mode-version "1.3.0"
(defconst wesnoth-mode-version "1.3.1"
"The current version of `wesnoth-mode'.")
(defgroup wesnoth-mode nil "Wesnoth-mode access"
@ -156,7 +165,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
@ -164,12 +173,15 @@ level as their parent.")
"Regular expression to match \"opening\" preprocessor statements.")
(defconst wesnoth-preprocessor-closing-regexp
"[\t ]*#\\(e\\(lse\\|nd\\(\\(de\\|i\\)f\\)\\)\\)"
"[\t ]*#\\(end\\(\\(de\\|i\\)f\\)\\)"
"Regular expression to match \"closing\" preprocessor statements.")
(defvar wesnoth-define-blocks '()
"Cache of all toplevel #define and #enddef pairs.")
(defvar wesnoth-history-list '()
"History of inserted WML elements.")
(defvar wesnoth-mode-hook nil)
(defvar wesnoth-mode-map
@ -181,6 +193,8 @@ level as their parent.")
(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 "C-c C-p") 'wesnoth-complete-preprocessor)
(define-key map (kbd "C-c C-u") 'wesnoth-update-project-information)
(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)
@ -199,15 +213,17 @@ level as their parent.")
["Insert Tag" wesnoth-complete-tag t]
["Insert Attribute" wesnoth-complete-attribute t]
["Insert Macro" wesnoth-complete-macro t]
["Insert Preprocessor" wesnoth-complete-preprocessor t]
["Insert Missing Tag" wesnoth-insert-missing-closing t]
["Jump to Matching" wesnoth-jump-to-matching t]
["Insert Missing Tag" wesnoth-insert-missing-closing t]))
["Update Macros" wesnoth-update-project-information t]))
(defvar wesnoth-syntax-table
(let ((wesnoth-syntax-table (make-syntax-table)))
(modify-syntax-entry ?= "." wesnoth-syntax-table)
(modify-syntax-entry ?= "." wesnoth-syntax-table)
(modify-syntax-entry ?_ "_" wesnoth-syntax-table)
(modify-syntax-entry ?- "_" wesnoth-syntax-table)
(modify-syntax-entry ?. "_" wesnoth-syntax-table)
(modify-syntax-entry ?- "_" wesnoth-syntax-table)
(modify-syntax-entry ?. "_" wesnoth-syntax-table)
(modify-syntax-entry ?\n ">" wesnoth-syntax-table)
(modify-syntax-entry ?\r ">" wesnoth-syntax-table)
wesnoth-syntax-table)
@ -217,7 +233,7 @@ level as their parent.")
;; pre-processor statements.
(defvar wesnoth-syntactic-keywords
(list
'("\\(^[\t ]*\\(#\\(?:define \\|e\\(?:lse\\|nd\\(?:\\(?:de\\|i\\)f\\)\\)\\|\\(?:ifn?\\|un\\)def \\)\\)\\|#enddef\\)" 1 "w")
'("\\(^[\t ]*\\(#\\(?:define \\|e\\(?:lse\\|nd\\(?:\\(?:de\\|i\\)f\\)\\)\\|\\(?:ifn?\\|un\\)def \\)\\)\\)" 1 "w")
'("\\(#[\t ]*.*$\\)" 1 "<"))
"Highlighting syntactic keywords within `wesnoth-mode'.")
@ -237,14 +253,36 @@ level as their parent.")
1 font-lock-variable-name-face))
"Syntax highlighting for `wesnoth-mode'.")
(defconst wesnoth-element-closing "^[\t ]*\\(\\[/\\|#enddef\\)"
"String to use for a closing element.")
(defun wesnoth-element-closing (&optional limited)
"Return the regexp to match a closing element.
If LIMITED is non-nil, return a regexp which matches only the
#enddef preprocessor."
(concat "^[\t ]*\\(\\[/\\(\\w\\|_\\)+\\|"
(if limited
"#enddef"
"#end\\(?:def\\|if\\)")
"\\)"))
(defconst wesnoth-element-opening "^[\t ]*\\(\\[[^/]\\|#define\\)"
"String to use for an opening element.")
(defun wesnoth-element-opening (&optional limited)
"Return the regexp to match a closing element.
If LIMITED is non-nil, return a regexp which matches only the
#define preprocessor."
(concat "^[\t ]*\\(\\[\\+?\\(\\w\\|_\\)+\\]\\|#define "
(if limited
""
"\\|#ifn?def ")
"\\)"))
(defun wesnoth-element (&optional limited)
"Return the regexp to match a closing element.
If LIMITED is non-nil, return a regexp which matches only the
#define and #enddef preprocessors."
(concat "^[\t ]*\\(\\[[/+]?\\(\\w\\|_\\)+\\]?\\|"
(if limited
"#define \\|#enddef"
(substring wesnoth-preprocessor-regexp 5))
"\\)"))
(defconst wesnoth-element "^[\t ]*\\(\\[[^]]?\\|#define\\|#enddef\\)"
"String to use for an opening or closing element.")
;;; Insertion and completion
(defmacro wesnoth-element-completion (completions prompt partial)
@ -255,26 +293,42 @@ PARTIAL is the partial string on which to attempt completion."
(setq element nil))
((null element)
(setq element
(completing-read ,prompt ,completions)))
(completing-read ,prompt ,completions nil nil nil
'wesnoth-history-list)))
((not (if (listp (car ,completions))
(assoc element ,completions)
(member element ,completions)))
(setq element
(completing-read ,prompt ,completions
nil nil ,partial))))
nil nil ,partial
'wesnoth-history-list))))
element))
(defun wesnoth-parent-tag ()
"Return the name of the parent tag, nil otherwise."
"Return the name of the parent tag.
If the parent is a preprocessor statement, return non-nil.
If the element does not have a parent, return nil.
Otherwise, return a string containing the name of the parent tag."
(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))))))))
(let ((start-point (point))
(depth 1))
(when (save-excursion (> (point) (progn (back-to-indentation)
(point))))
(end-of-line))
(while (and (> depth 0)
(search-backward-regexp (wesnoth-element)
(point-min) t))
(if (string-match "[\t ]*\\[/" (match-string 0))
(incf depth)
(decf depth)))
(beginning-of-line)
(if (> depth 0)
nil
(when (looking-at (wesnoth-element-opening))
(let ((parent (match-string-no-properties 1)))
(if (string-match wesnoth-preprocessor-opening-regexp parent)
t
(substring parent 1 (1- (length parent))))))))))
(defun wesnoth-indent-or-complete (&optional elements)
"Indent or complete the line at point, depending on context.
@ -291,6 +345,8 @@ matching tags."
(wesnoth-complete-tag elements t))
((looking-at "{\\(\\(\\w\\|_\\)*\\)[\t ]*$")
(wesnoth-complete-macro t))
((looking-at "^#\\w+$")
(wesnoth-complete-preprocessor elements t))
((looking-at "\\[/\\(\\(\\w\\|_\\)*\\)[\t ]*$")
(delete-region (point) (progn (end-of-line) (point)))
(wesnoth-insert-missing-closing)
@ -300,11 +356,87 @@ matching tags."
(setq target (point)))
(goto-char target)))
(defun wesnoth-preprocessor-closed-p (preprocessor)
"Determine whether PREPROCESSOR has been closed.
PREPROCESSOR is a string matching the preprocessor statement to
be inserted."
(save-excursion
(back-to-indentation)
(wesnoth-jump-to-matching preprocessor)
(looking-at
(if (string= preprocessor "#define ")
"#enddef"
"#endif"))))
(defun wesnoth-complete-preprocessor (&optional elements completep)
"Complete and insert the preprocessor at point.
ELEMENTS is the number of elements to wrap around.
If COMPLETEP is non-nil, attempt to complete preprocessor at point."
(interactive "P")
(or elements (setq elements 0))
(let* ((completions (wesnoth-emacs-completion-formats
'("define" "else" "ifdef" "ifndef"
"enddef" "endif" "undef")))
(partial (when completep
(save-excursion
(back-to-indentation)
(when (looking-at "#\\(\\w+\\)$")
(match-string-no-properties 1)))))
(preprocessor (or (wesnoth-element-completion
completions "Preprocessor: " partial)
partial))
(closedp
(save-excursion
(when preprocessor
(unless (string= "#" (substring preprocessor 0 1))
(setq preprocessor (concat "#" preprocessor)))
(when (string-match "#\\(define\\|ifn?def\\|undef\\)" preprocessor)
(setq preprocessor (concat preprocessor " ")))
(when partial
(delete-region (progn (back-to-indentation) (point))
(progn (end-of-line) (point))))
(wesnoth-preprocessor-closed-p preprocessor)))))
(when preprocessor
(when partial
(delete-region (progn (back-to-indentation) (point))
(progn (end-of-line) (point))))
(if (and (string-match "#\\(define \\|ifn?def\\)" preprocessor)
(not closedp))
(progn
(wesnoth-insert-tag elements preprocessor)
(forward-line -1)
(end-of-line))
(wesnoth-insert-element-separately preprocessor)))))
(defun wesnoth-macro-arguments ()
"Find any current macro arguments."
(let ((results '())
(depth (wesnoth-within-define (point))))
(save-excursion
(while (> depth 0)
(save-match-data
(search-backward-regexp
"[\t ]*#define \\(?:\\w+\\|_\\)*\\(\\([\t ]*\\(\\w\\|_\\)+\\)*\\)"
(point-min) t)
(when (<= (wesnoth-within-define (point)) depth)
(and (> depth 0)
(setq results
(append (mapcar (lambda (macro)
(list macro nil))
(split-string
(match-string-no-properties 1)))
results)))
(decf depth)))))
(message "%s" results)
results))
(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-update-project-information)
(let* ((macro-information (append (wesnoth-macro-arguments)
wesnoth-macro-data
wesnoth-local-macro-data))
(completions (wesnoth-emacs-completion-formats
(mapcar 'car macro-information)))
@ -312,7 +444,7 @@ If COMPLETEP is non-nil, attempt to complete the macro at point."
(save-excursion
(back-to-indentation)
(when (looking-at "{\\(\\(\\w\\|_\\)*\\)")
(match-string 1)))))
(match-string-no-properties 1)))))
(macro (or (wesnoth-element-completion completions "Macro: " partial)
partial))
(args (cadr (assoc macro macro-information))))
@ -337,7 +469,7 @@ If COMPLETEP is non-nil, attempt to complete the attribute at point."
(save-excursion
(back-to-indentation)
(when (looking-at "\\(\\(\\w\\|_\\)+\\)")
(match-string 1)))))
(match-string-no-properties 1)))))
(attribute (or (wesnoth-element-completion completions "Attribute: "
partial)
partial)))
@ -365,24 +497,23 @@ If COMPLETEP is non-nil, attempt to complete tag at point."
(save-excursion
(back-to-indentation)
(when (looking-at "\\[\\(\\(\\w\\|_\\)+\\)")
(match-string 1)))))
(match-string-no-properties 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)))))
partial))
(closedp
(save-excursion
(wesnoth-jump-to-matching)
(back-to-indentation)
(and (looking-at "\\[/\\(\\(\\w\\|_\\)+\\)")
(string= tag (match-string 1))))))
(when completep
(delete-region (progn (back-to-indentation) (point))
(progn (end-of-line) (point))))
(if (and closedp 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.
@ -393,9 +524,7 @@ than 22. POSITION is the argument passed to `nth' for
(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))))
(nth (1- position) (gethash parent wesnoth-tag-hash-table))
(mapcar 'car wesnoth-tag-data))))
(wesnoth-emacs-completion-formats candidates)))
@ -423,7 +552,8 @@ 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 1))))
(setq tagname (completing-read "Tag: " (wesnoth-build-completion 1)
nil nil nil wesnoth-history-list)))
(when (or (not elements)
(looking-at (concat "[\t ]*\\(:?\\[/\\|"
wesnoth-preprocessor-regexp "\\)")))
@ -432,58 +562,38 @@ TAGNAME is the name of the tag to be inserted."
(start (save-excursion (forward-line -1) (point)))
(end (unless (= elements 0)
(wesnoth-nth-pair-position elements))))
(wesnoth-insert-element-separately "[" tagname "]")
(if (string-match wesnoth-preprocessor-regexp tagname)
(wesnoth-insert-element-separately tagname)
(wesnoth-insert-element-separately "[" tagname "]"))
(save-excursion
(if end
(goto-char (marker-position end))
(newline 2))
(wesnoth-insert-element-separately "[/" tagname "]")
(newline (if (string-match wesnoth-preprocessor-regexp tagname) 1 2)))
(if (string-match wesnoth-preprocessor-opening-regexp tagname)
(wesnoth-insert-element-separately
(if (string= tagname "#define ")
"#enddef"
"#endif"))
(wesnoth-insert-element-separately "[/" tagname "]"))
(indent-region start (point) nil))
(unless end
(forward-line 1)))
(wesnoth-indent))
(defun wesnoth-nth-pair-position (count)
"Return `point' after COUNT number of matching element pairs.
COUNT is a positive number representing the number of balanced
pairs to move across.
`point' is returned as a marker object."
(save-excursion
(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."
(let ((create-newline (save-excursion
(beginning-of-line)
(if (looking-at "^[\t ]*$") nil t))))
(when create-newline
(if (> (point) (save-excursion (back-to-indentation) (point)))
(if (save-excursion (and (> (point) (progn (back-to-indentation) (point)))))
(if (save-excursion (forward-line 1) (looking-at "^[\t ]*$"))
(progn
(end-of-line)
(newline))
(beginning-of-line)
(open-line 1)))
(insert (apply 'concat strings))))
(forward-line 1)
(end-of-line))
(end-of-line)
(newline))
(beginning-of-line)
(if (looking-at "^[\t ]*$")
(end-of-line)
(open-line 1)))
(insert (apply 'concat strings)))
(defun wesnoth-insert-missing-closing (&optional start end)
"Insert the next expected closing element at point.
@ -491,16 +601,15 @@ pairs to move across.
START and END define the region to check for missing closing
elements. If function `transient-mark-mode' is enabled, the region
specified will be used as START and END. Otherwise, START and
END will be the minimum and maximum positions of the buffer,
END will be the minimum and current positions of the buffer,
respectively."
(interactive)
(if (and (boundp 'transient-mark-mode)
transient-mark-mode
mark-active)
transient-mark-mode mark-active)
(setq start (region-beginning)
end (copy-marker (region-end)))
(setq start (point-min)
end (point-max)))
end (point)))
(let ((element (wesnoth-check-structure start end)))
(if (not element)
(error "%s" "Unable to find element to insert")
@ -540,10 +649,38 @@ search."
(or ,repeat (setq ,repeat 1))
(while (> ,repeat 0)
(and (eq ,search-function 'search-forward-regexp) (end-of-line))
(funcall ,search-function wesnoth-element-opening ,bound t)
(funcall ,search-function (wesnoth-element-opening) ,bound t)
(back-to-indentation)
(decf ,repeat))))
(defun wesnoth-nth-pair-position (count)
"Return `point' after COUNT number of matching element pairs.
COUNT is a positive number representing the number of balanced
pairs to move across.
`point' is returned as a marker object."
(save-excursion
(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-forward-element (repeat)
"Move point to the end of the next tag.
REPEAT is an optional numeric argument. If REPEAT is non-nil,
@ -562,42 +699,86 @@ jump backward the specified number of tags."
(wesnoth-forward-element (abs repeat))
(wesnoth-navigate-element repeat 'search-backward-regexp (point-min))))
(defmacro wesnoth-search-for-matching-tag (search-function search-string bound)
(defmacro wesnoth-search-for-matching-tag (search-function
search-string bound &optional skip)
"Search for the matching tag for the current line.
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."
BOUND is the bound to be passed to the search function.
If SKIP is non-nil, skip the first element and continue from there."
`(let ((depth 1))
(when (funcall ,search-function wesnoth-element (funcall ,bound) t)
(unless (string-match ,search-string (match-string 0))
(when (or (and ,skip (forward-line 1))
(funcall ,search-function (wesnoth-element) (funcall ,bound) t))
(when (or ,skip (not (string-match ,search-string (match-string 0))))
(while (and (> depth 0)
(funcall ,search-function wesnoth-element
(funcall ,search-function (wesnoth-element)
(funcall ,bound) t))
(if (string-match ,search-string (match-string 0))
(decf depth)
(incf depth)))
t))))
(= depth 0)))))
(defun wesnoth-jump-to-matching ()
"Jump point to the matching opening/closing tag."
(defun wesnoth-jump-to-matching (&optional opening)
"Jump point to the matching opening/closing tag.
OPENING is an opening preprocessor statement to attempt to find a match for."
(interactive)
(beginning-of-line)
(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))
(let ((target nil)
(first-element nil))
(save-excursion
(if (or (and (stringp opening)
(string-match (wesnoth-element-opening) opening))
(looking-at (wesnoth-element-opening)))
(progn
(setq first-element (match-string-no-properties 0 opening))
(when (wesnoth-search-for-matching-tag
'search-forward-regexp (wesnoth-element-closing) 'point-max
(stringp opening))
(beginning-of-line)
(if (and (string-match wesnoth-preprocessor-opening-regexp
first-element)
(looking-at (wesnoth-element-closing)))
(when (string= (match-string-no-properties 0)
(cdr (assoc first-element
'(("#define " . "#enddef")
("#ifndef " . "#endif")
("#ifdef " . "#endif")))))
(setq target (point)))
(setq target (point)))))
(when (looking-at (wesnoth-element-closing))
(setq first-element (match-string-no-properties 0))
(end-of-line)
(when (wesnoth-search-for-matching-tag
'search-backward-regexp (wesnoth-element-opening)
'wesnoth-wml-start-pos)
(if (and (string-match wesnoth-preprocessor-closing-regexp
first-element)
(looking-at (wesnoth-element-opening)))
(progn
(when (or (and (string= "#enddef" first-element)
(string= "#define "
(match-string-no-properties 0)))
(and (string= "#endif" first-element)
(string-match
"#ifn?def "
(match-string-no-properties 0))))
(setq target (point))))
(setq target (point)))))))
(if target
(goto-char target)
(when (interactive-p)
(error "Tag does not appear to be matched"))))
(back-to-indentation))
;;; Indentation
(defun wesnoth-wml-start-pos ()
"Determine the position of `point' relative to where the actual WML begins.
Return the likely starting position of the WML if it is found.
Otherwise return nil."
(save-excursion
(goto-char (point-min))
(when (search-forward-regexp wesnoth-element (point-max) t)
(when (search-forward-regexp (wesnoth-element) (point-max) t)
(beginning-of-line)
(point))))
@ -621,14 +802,14 @@ CONTEXT represents the type of element which precedes the current element."
(cond
((eq context 'opening)
(if (or (and wesnoth-indent-savefile
(not (looking-at wesnoth-element-closing)))
(looking-at wesnoth-element-opening))
(not (looking-at (wesnoth-element-closing t))))
(looking-at (wesnoth-element-opening t)))
(setq cur-indent (+ ref-indent wesnoth-base-indent))
(setq cur-indent ref-indent)))
((eq context 'closing)
(if (or (looking-at "^[\t ]*\\[/")
(and (not wesnoth-indent-savefile)
(not (looking-at wesnoth-element-opening))))
(not (looking-at (wesnoth-element-opening t)))))
(setq cur-indent (- ref-indent wesnoth-base-indent))
(setq cur-indent ref-indent))))))
(indent-line-to (max cur-indent 0))))
@ -636,13 +817,14 @@ CONTEXT represents the type of element which precedes the current element."
(defun wesnoth-within-define (position)
"Determine whether point is currently inside a #define block.
POSITION is the initial cursor position."
(let ((depth 0))
(dolist (element (or wesnoth-define-blocks
(wesnoth-find-macro-definitions)))
(when (= (cadr (sort (append (mapcar 'marker-position (cadr element))
(list position)) '>)) position)
(setq depth (max (car element) depth))))
depth))
(save-match-data
(let ((depth 0))
(dolist (element (or wesnoth-define-blocks
(wesnoth-find-macro-definitions)))
(when (= (cadr (sort (append (mapcar 'marker-position (cadr element))
(list position)) '>)) position)
(setq depth (max (car element) depth))))
depth)))
(defun wesnoth-find-macro-definitions ()
"Return information regarding positioning of macro definitions."
@ -657,10 +839,14 @@ POSITION is the initial cursor position."
(progn
(add-to-list 'openings (point-marker))
(1+ depth))
(add-to-list 'cache
(list depth (list (car openings) (point-marker))))
(setq openings (cdr openings))
(1- depth)))
(if openings
(progn
(add-to-list 'cache
(list depth (list (car openings)
(point-marker))))
(setq openings (cdr openings))
(1- depth))
depth)))
(end-of-line))
cache)))
@ -689,12 +875,13 @@ Creates and destroys a cache of macro definition details as necessary."
POSITION is the buffer position of the element for which to
determine the context."
(save-excursion
(search-backward-regexp wesnoth-element (wesnoth-wml-start-pos) t)
(search-backward-regexp (wesnoth-element t)
(wesnoth-wml-start-pos) t)
(let ((match (or (match-string 1) ""))
(depth (wesnoth-within-define position)))
(while (and (> (wesnoth-within-define (point)) depth)
(not (= (point) (wesnoth-wml-start-pos))))
(search-backward-regexp wesnoth-element
(search-backward-regexp (wesnoth-element t)
(wesnoth-wml-start-pos) t)
(setq match (match-string 1)))
(when (and (= (point) (wesnoth-wml-start-pos)) (= depth 0)
@ -724,17 +911,14 @@ be performed."
"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"))
(if (or (not last-tag)
(save-match-data
(string-match "#\\(?:define\\|ifn?def\\)" last-tag)))
(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))))
(member (match-string-no-properties 1)
(nth position (gethash last-tag
wesnoth-tag-hash-table)))))
;; Provide `line-number-at-pos' implementation (not available in Emacs 21).
(defun wesnoth-line-number-at-pos (&optional pos)
@ -761,13 +945,24 @@ ARGS is any additional data required by `format' to handle FORMAT-STRING."
(insert (apply 'format (concat "Line %d: " format-string "\n")
lnap args)))))
(defun wesnoth-extract-macro-details (macro-arguments)
"Return a list of all macros in MACRO-ARGUMENTS."
(when macro-arguments
(let ((results '()))
(save-match-data
(dolist (macro (split-string macro-arguments "[{}][\t ]*" t))
(when (string-match "^\\(\\(?:\\w\\|_\\)+\\)"
macro)
(add-to-list 'results (match-string-no-properties 1 macro)))))
results)))
(defun wesnoth-check-wml ()
"Perform context-sensitive analysis of WML-code."
(interactive)
(wesnoth-update-project-information)
(unless wesnoth-tag-data
(when (= 0 (hash-table-count wesnoth-tag-hash-table))
(error "WML data not available; can not generate report"))
(let ((unmatched-tag-list '())
(let ((unmatched '())
(outbuf (get-buffer-create "*WML*")))
(save-excursion
(let ((buffer (buffer-name)))
@ -777,74 +972,81 @@ ARGS is any additional data required by `format' to handle FORMAT-STRING."
(message (format "Checking %s..." buffer))))
(save-excursion
(goto-char (or (wesnoth-wml-start-pos) (point-min)))
(while (search-forward-regexp "[\t ]*{\\(\\(\\(\\w\\|_\\)+\\)[^=
]*\\)}" (point-max) t)
(dolist (macro (wesnoth-extract-macro-details
(match-string-no-properties 1)))
(unless (assoc macro
(append (wesnoth-macro-arguments)
wesnoth-local-macro-data
wesnoth-macro-data))
(wesnoth-check-output outbuf "Unknown macro definition: '{%s}'"
macro))))
(goto-char (or (wesnoth-wml-start-pos) (point-min)))
(while (search-forward-regexp
(concat "^[\t ]*\\(\\[[+/]?\\(\\(\\w\\|_\\)+\\)\\]\\|"
"\\(\\w\\|_\\)+=\\|{\\(\\(\\w\\|_\\)+\\).*}\\|"
;; Match tags, preprocessor statements and attributes.
(concat "^[\t ]*\\(\\[[+/]?\\([a-z]\\(\\w\\|_\\)+\\)\\]\\|"
"\\(\\w\\|_\\)+=\\|"
wesnoth-preprocessor-regexp "\\)")
(point-max) t)
(beginning-of-line)
(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))
(save-excursion
(goto-char (match-beginning 1))
(cond ((nth 3 (parse-partial-sexp (point-min) (point)))
nil)
((looking-at "[\t ]*\\[\\+?\\(\\(\\w\\|_\\)+\\)\\]")
(unless (wesnoth-check-element-type 0 (car unmatched))
(wesnoth-check-output outbuf
"Tag not available in this context: '%s'"
(match-string-no-properties 1)))
(setq unmatched (cons (match-string-no-properties 1)
unmatched)))
((looking-at
(concat "[\t ]*\\(#define\\|#ifdef\\|#ifndef\\|#undef\\)"
"\\( \\(\\w\\|_\\)+\\)*"))
(unless (match-string-no-properties 2)
(wesnoth-check-output
outbuf (concat "Preprocessor statement has no argument: "
(match-string-no-properties 1))))
(unless (string= (match-string-no-properties 1) "#undef")
(setq unmatched (cons (match-string-no-properties 1)
unmatched))))
((looking-at wesnoth-preprocessor-closing-regexp)
(when (and unmatched
(not (string-match
(cdr (assoc (match-string-no-properties 1)
'(("enddef" . "#define")
("endif" . "#ifn?def"))))
(car unmatched))))
(wesnoth-check-output
outbuf
"Preprocessor statement does not nest correctly"))
(setq unmatched (cdr unmatched)))
((looking-at "[\t ]*\\(\\(\\w\\|_\\)+\\)=\\(.+\\)?")
(unless (wesnoth-check-element-type 1 (car unmatched))
(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))
(if (string= (car unmatched) "#define")
(wesnoth-check-output outbuf "Expecting: '%s'"
(car unmatched))
(wesnoth-check-output outbuf "Expecting: '[/%s]'"
(car unmatched)))))
((looking-at "[\t ]*\\[/\\(\\(\\w\\|_\\)+\\)\\]")
(when (and unmatched
(not (string= (match-string-no-properties 1)
(car unmatched))))
(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)))))
(car unmatched)))
(setq unmatched (cdr unmatched))))))
(if unmatched
(dolist (tag unmatched)
(wesnoth-check-output outbuf "Unmatched element: '%s'"
(car unmatched)))))
(save-excursion
(let ((buffer (buffer-name)))
(set-buffer outbuf)
@ -860,12 +1062,12 @@ ARGS is any additional data required by `format' to handle FORMAT-STRING."
ELEMENT is the current element being tested.
REQUIREMENT is the element required to exist for correct nesting.
POP is an optional argument indicating the element should be
removed from the unmatched-tag-list."
removed from the list of unmatched elements."
`(when (string= ,element (match-string-no-properties 1))
(if (string-match ,requirement (car unmatched-tag-list))
(if (string-match ,requirement (car unmatched))
(progn
(and ,pop (setq unmatched-tag-list (cdr unmatched-tag-list)))
(and ,pop (setq unmatched (cdr unmatched)))
t)
(setq error-position (point)))))
@ -906,7 +1108,7 @@ positions of the buffer, respectively."
end (copy-marker (region-end)))
(setq start (point-min)
end (point-max))))
(let ((unmatched-tag-list '())
(let ((unmatched '())
(error-position nil))
(save-excursion
(and start (goto-char start))
@ -917,8 +1119,8 @@ positions of the buffer, respectively."
(beginning-of-line)
(if (or (looking-at "^[\t ]*\\[\\(\\(\\w\\|_\\)+\\)\\]")
(looking-at "[\t ]*#\\(define \\|ifdef \\|ifndef \\)"))
(setq unmatched-tag-list (cons (match-string-no-properties 1)
unmatched-tag-list))
(setq unmatched (cons (match-string-no-properties 1)
unmatched))
(cond
((wesnoth-element-requires "#else" "ifn?def "))
((wesnoth-element-requires "#endif" "ifn?def " t))
@ -926,11 +1128,11 @@ positions of the buffer, respectively."
((looking-at (concat "^[\t ]*\\[/\\(\\(\\w\\|_\\)+\\)\\]\\|"
wesnoth-preprocessor-closing-regexp))
(if (string= (match-string-no-properties 1)
(car unmatched-tag-list))
(setq unmatched-tag-list (cdr unmatched-tag-list))
(car unmatched))
(setq unmatched (cdr unmatched))
(setq error-position (point))))))
(end-of-line)))
(wesnoth-structure-result error-position (car unmatched-tag-list))))
(wesnoth-structure-result error-position (car unmatched))))
;;; wesnoth-mode
(define-derived-mode wesnoth-mode fundamental-mode "wesnoth-mode"
@ -950,6 +1152,8 @@ positions of the buffer, respectively."
(font-lock-syntactic-keywords . wesnoth-syntactic-keywords)))
(setq indent-tabs-mode nil)
(easy-menu-add wesnoth-menu wesnoth-mode-map)
(wesnoth-create-wml-hash-table)
(wesnoth-update-project-information)
(run-hooks 'wesnoth-mode-hook))
(provide 'wesnoth-mode)

View file

@ -59,11 +59,16 @@
;; available to `wesnoth-mode'.
;;; History:
;; 0.1.1
;; * Provide means for increased performance when referencing attributes and
;; tags.
;; * Gather project macro information for the local buffer only, instead of
;; from files in the directory.
;; 0.1
;; * Initial version
;;; Code:
(defvar wesnoth-update-version "0.1"
(defvar wesnoth-update-version "0.1.1"
"Version of `wesnoth-update'.")
(defcustom wesnoth-root-directory nil
@ -98,6 +103,16 @@ This is relative to the wesnoth directory in `wesnoth-root-directory.'.")
(defvar wesnoth-local-macro-data '()
"All macro definitions available in the current project.")
(defvar wesnoth-tag-hash-table (make-hash-table :test 'equal
:size 350)
"Hash table of known WML tag data.")
(defun wesnoth-create-wml-hash-table ()
"Handle generation of `wesnoth-tag-hash-table'."
(clrhash wesnoth-tag-hash-table)
(dolist (tag wesnoth-tag-data)
(puthash (car tag) (cdr tag) wesnoth-tag-hash-table)))
(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)))
@ -215,14 +230,14 @@ SUBTAG and ATTRIBUTE are a children of TAG to be added."
(defmacro wesnoth-determine-macro-information (macro-list)
"Process the buffer, retrieving macro definition information.
MACRO-LIST is the variable to append macro information."
`(progn
`(save-excursion
(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)))
(add-to-list ,macro-list (list (match-string-no-properties 1)
(not (null (match-string 3)))))
(end-of-line))))
(defun wesnoth-determine-macro-builtins ()
@ -271,15 +286,13 @@ Path to WML information included in wesnoth is set by
(write-file (expand-file-name (format "wesnoth-wml-data.el")
(wesnoth-output-path)))
(load "wesnoth-wml-data"))
(wesnoth-create-wml-hash-table)
(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))))
(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.

File diff suppressed because one or more lines are too long