From d3965dfc78081c197c6ce3d458ee0bb126741cf5 Mon Sep 17 00:00:00 2001 From: Chris Mann Date: Mon, 6 Oct 2008 19:27:06 +1030 Subject: [PATCH] * wesnoth-mode.el (wesnoth-mode-version): Updated to 1.3.0-git. (wesnoth-preprocessor-regexp, wesnoth-preprocessor-closing-regexp, wesnoth-preprocessor-opening-regexp): Removed shy matches. (wesnoth-local-macro-data): New varable. (wesnoth-mode-map, wesnoth-build-completion, wesnoth-insert-tag): Updated for new context-sensitive commands. (wesnoth-tags-list, wesnoth-check-tag-names): Removed. Obsoleted. (wesnoth-parent-tag, wesnoth-emacs-completion-formats, wesnoth-check-element-type): New functions. (wesnoth-indent-or-complete, wesnoth-complete-macro, wesnoth-complete-attribute, wesnoth-complete-tag, wesnoth-check-wml): New commands. (wesnoth-element-completion): New macros. --- wesnoth-mode.el | 350 ++++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 261 insertions(+), 89 deletions(-) diff --git a/wesnoth-mode.el b/wesnoth-mode.el index 1aa43c6..4b7e19e 100644 --- a/wesnoth-mode.el +++ b/wesnoth-mode.el @@ -31,6 +31,22 @@ ;; to automatically load wesnoth-mode for all files ending in '.cfg'. ;;; History: +;; 1.3.0 +;; * WML checking is now context sensitive; checks attributes and macros. +;; * Context-sensitive completion for attributes and tags implemented. +;; * Completion for built-in macros implemented. +;; * 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 any incomplete element at point is attempted via TAB. ;; 1.2.5 ;; * Fixed support for GNU Emacs 21. ;; * Added several new tags to `wesnoth-tags-list'. @@ -104,8 +120,9 @@ ;;; Code: (require 'cl) +(require 'wesnoth-wml-data) -(defconst wesnoth-mode-version "1.2.5a" +(defconst wesnoth-mode-version "1.3.0-git" "The current version of `wesnoth-mode'.") (defgroup wesnoth-mode nil "Wesnoth-mode access" @@ -130,7 +147,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,12 +155,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 ]*#\\(e\\(lse\\|nd\\(\\(de\\|i\\)f\\)\\)\\)" "Regular expression to match \"closing\" preprocessor statements.") (defvar wesnoth-define-blocks '() "Cache of all toplevel #define and #enddef pairs.") +(defvar wesnoth-local-macro-data '() + "All macro definitions available in the current project.") + (defvar wesnoth-mode-hook nil) (defvar wesnoth-mode-map @@ -152,25 +172,30 @@ 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 "C-c c") 'wesnoth-check-wml) + (define-key map (kbd "C-c C-c") 'wesnoth-check-wml) + (define-key map (kbd "C-c a") 'wesnoth-complete-attribute) + (define-key map (kbd "C-c C-a") 'wesnoth-complete-attribute) + (define-key map (kbd "C-c t") 'wesnoth-complete-tag) + (define-key map (kbd "C-c C-t") 'wesnoth-complete-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 m") 'wesnoth-complete-macro) + (define-key map (kbd "C-c C-m") 'wesnoth-complete-macro) + (define-key map (kbd "C-c o") 'wesnoth-jump-to-matching) + (define-key map (kbd "C-c C-o") 'wesnoth-jump-to-matching) (define-key map (kbd "C-c /") 'wesnoth-insert-missing-closing) (define-key map (kbd "C-c C-/") 'wesnoth-insert-missing-closing) + (define-key map (kbd "TAB") 'wesnoth-indent-or-complete) (define-key map [menu-bar wesnoth] (cons "WML" (make-sparse-keymap "WML"))) + (define-key map [menu-bar wesnoth check-structure] + '("Check WML" . wesnoth-check-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 complete-attribute] + '("Insert Attribute" . wesnoth-complete-attribute)) + (define-key map [menu-bar wesnoth complete-macro] + '("Insert Macro" . wesnoth-complete-macro)) (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] @@ -203,7 +228,7 @@ level as their parent.") (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) @@ -231,60 +256,136 @@ 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.") - -(defvar wesnoth-completion-cache '() - "List of tags which have been generated by `wesnoth-build-completion'.") - -(defun wesnoth-build-completion (&optional rebuild) +;;; Insertion and completion +(defun wesnoth-parent-tag () + "Return the name of the parent tag, nil otherwise." + (save-excursion + (let ((parent (when (> (point) (wesnoth-wml-start-pos)) + (wesnoth-check-structure (wesnoth-wml-start-pos) + (point))))) + (when parent + (if (string-match wesnoth-preprocessor-closing-regexp parent) + t + (subseq parent 2 (1- (length parent)))))))) + +(defun wesnoth-indent-or-complete () + "Indent or complete the line at point, depending on context." + (interactive) + (let ((target nil)) + (save-excursion + (back-to-indentation) + (cond ((looking-at "\\(\\(\\w\\|_\\)+\\)[\t ]*$") + (wesnoth-complete-attribute)) + ((looking-at "\\[\\(\\(\\w\\|_\\)*\\)[\t ]*$") + (wesnoth-complete-tag)) + ((looking-at "{\\(\\(\\w\\|_\\)*\\)[\t ]*$") + (wesnoth-complete-macro)) + ((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 () + "Complete and insert the macro at point." + (interactive) + (wesnoth-update-project-information) + (let* ((macro-information (append wesnoth-macro-information + wesnoth-local-macro-information)) + (completions (wesnoth-emacs-completion-formats + (mapcar 'car macro-information))) + (macro (wesnoth-element-completion completions "Macro: ")) + (args (second (find macro macro-information + :key 'car :test 'string=)))) + (when macro + (delete-region (point) (progn (end-of-line) (point))) + (insert (concat "{" macro (if args " }" "}"))) + (when args + (forward-char -1))))) + +(defun wesnoth-complete-attribute () + "Complete and insert the attribute at point." + (interactive) + (let* ((completions (wesnoth-build-completion 2)) + (partial (save-excursion + (back-to-indentation) + (looking-at "\\(\\(\\w\\|_\\)+\\)") + (match-string 1))) + (attribute (or (wesnoth-element-completion completions "Attribute: ") + partial))) + (when attribute + (delete-region (point) (progn (end-of-line) (point))) + (insert (concat attribute "="))))) + +(defun wesnoth-complete-tag () + "Complete and insert the tag at point." + (interactive) + (let* ((completions (wesnoth-build-completion 1)) + (partial (save-excursion + (back-to-indentation) + (looking-at "\\[\\(\\(\\w\\|_\\)+\\)") + (match-string 1))) + (tag (or (wesnoth-element-completion completions "Tag: ") + 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))) + (delete-region (point) (progn (end-of-line) (point))) + (if closed-p + (progn + (wesnoth-insert-and-indent "[" tag "]") + (end-of-line)) + (wesnoth-insert-tag nil tag))))) + +(defmacro wesnoth-element-completion (completions prompt) + "Process completion of COMPLETIONS, displaying PROMPT." + (let ((partial (gensym)) + (element (gensym))) + `(let* ((,partial (match-string-no-properties 1)) + (,element (when ,partial (try-completion ,partial ,completions)))) + (cond ((eq ,element t) + (setq ,element nil)) + ((null ,element) + (setq ,element + (completing-read ,prompt ,completions))) + ((not (member ,element ,completions)) + (setq ,element + (completing-read ,prompt ,completions + nil nil ,partial)))) + ,element))) + +(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)) + (nth position (find (wesnoth-parent-tag) wesnoth-tag-data + :key 'car :test 'string=)) + (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,7 +399,7 @@ 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)))) + (setq tagname (completing-read "Tag: " (wesnoth-build-completion 1)))) (or elements (setq elements 0)) (let ((depth 0) (start (save-excursion (forward-line -1) (point))) @@ -366,7 +467,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. @@ -577,30 +679,100 @@ 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." +(defun wesnoth-check-element-type (position last-tag) + "Determine the context of the 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)) + (member last-tag + (mapcar 'car + (remove-if-not + (lambda (list) + (member (match-string-no-properties 1) + list)) + wesnoth-tag-data :key position))))) + +(defun wesnoth-check-wml () + "Perform context-sensitive checks on WML-code." (interactive) - (let ((tag-position nil) - (missing-tag-name nil)) + (wesnoth-update-project-information) + (let ((unmatched-tag-list '()) + (error-pos nil)) (save-excursion - (goto-char (point-min)) - (while (and (search-forward-regexp "^[\t ]*\\[" (point-max) t) - (not tag-position)) + (goto-char (or (wesnoth-wml-start-pos) (point-min))) + (while (and (search-forward-regexp + (concat "^[\t ]*\\(\\[[+/]?\\(\\(\\w\\|_\\)+\\)\\]\\|" + "\\(\\w\\|_\\)+=\\|{\\(\\(\\w\\|_\\)+\\).*}\\|" + wesnoth-preprocessor-regexp "\\)") + (point-max) t) + (not error-pos)) (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\\|_\\)+\\)\\]") + (if (wesnoth-check-element-type 'second + (car unmatched-tag-list)) + (setq unmatched-tag-list (cons + (match-string-no-properties 1) + unmatched-tag-list)) + (message "Tag not available in this context: %s" + (match-string-no-properties 1)) + (setq error-pos (point)))) + ((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) + (if (string= (car unmatched-tag-list) + (second (find (match-string-no-properties 1) + '(("enddef" "#define") + ("ifdef" "#endif") + ("ifndef" "#endif")) + :key 'car :test 'string=))) + (setq unmatched-tag-list (cdr unmatched-tag-list)) + (message "Preprocessor statement does not nest correctly") + (setq error-pos (point)))) + ((looking-at "^[\t ]*\\(\\(\\w\\|_\\)+\\)=") + (unless (wesnoth-check-element-type 'third + (car unmatched-tag-list)) + (message "Attribute not available in this context: %s" + (match-string-no-properties 1)) + (setq error-pos (point)))) + ((looking-at "^[\t ]*#else") + (unless (string-match "ifn?def" (car unmatched-tag-list)) + (if (string= (car unmatched-tag-list) "#define") + (message "Expecting: %s" (car unmatched-tag-list)) + (message "Expecting: [/%s]" (car unmatched-tag-list))))) + ((looking-at "^[\t ]*{\\(\\(\\w\\|_\\)+\\).*}") + (unless (find (match-string-no-properties 1) + (append wesnoth-local-macro-data + wesnoth-macro-data) + :test 'string= :key 'car) + (message "Unknown macro definition: {%s}" + (match-string-no-properties 1)) + (setq error-pos (point)))) + ((or (looking-at "^[\t ]*\\[/\\(\\(\\w\\|_\\)+\\)\\]")) + (if (string= (match-string-no-properties 1) + (car unmatched-tag-list)) + (setq unmatched-tag-list (cdr unmatched-tag-list)) + (if (string= "#" (subseq (car unmatched-tag-list) 0 1)) + (message "Expecting: #%s" + (car + (find (car unmatched-tag-list) + '(("enddef" "#define") + ("ifdef" "#endif") + ("ifndef" "#endif")) + :key 'second :test 'string=))) + (message "Expecting: [/%s]" (car unmatched-tag-list))) + (setq error-pos (point))))) + (end-of-line)) + (if unmatched-tag-list + (unless error-pos + (message "Unmatched tag: %s" (car unmatched-tag-list)) + (setq error-pos (point))) + (message "WML appears fine."))) + (when error-pos + (goto-char error-pos) + (back-to-indentation)))) (defmacro wesnoth-element-requires (element requirement &optional pop) "Process requirements for corresponding preprocessor elements. -- 2.11.4.GIT