From: Don Armstrong Date: Tue, 8 Jan 2013 01:54:42 +0000 (-0800) Subject: add multi-web-mode X-Git-Url: https://git.donarmstrong.com/?p=lib.git;a=commitdiff_plain;h=ff59dd8048c0f5d831b88346dd614305bb373120 add multi-web-mode --- diff --git a/emacs_el/multi-web-mode.el b/emacs_el/multi-web-mode.el new file mode 100644 index 0000000..74b04e9 --- /dev/null +++ b/emacs_el/multi-web-mode.el @@ -0,0 +1,475 @@ +;;; multi-web-mode.el --- multiple major mode support for web editing + +;; Copyright (C) 2012 Fabián Ezequiel Gallina. + +;; Author: Fabián E. Gallina +;; URL: https://github.com/fgallina/multi-web-mode +;; Version: 0.1 +;; Created: Feb 2009 +;; Keywords: convenience, languages, wp + +;; This file is part of Multi Web Mode + +;; Multi Web Mode 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 3 of the +;; License, or (at your option) any later version. + +;; Multi Web Mode 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 Multi Web Mode. If not, see . + +;;; Commentary: + +;; Multi Web Mode is a minor mode wich makes web editing in Emacs much easier. + +;; Basically what it does is select the appropriate major mode +;; automatically when you move the point and also calculates the +;; correct indentation of chunks according to the indentation of the +;; most relevant major mode. + +;; + +;;; Code: + +(eval-when-compile + (require 'cl) + (defvar multi-web-mode)) + +(defvar mweb-mode-map + (let ((mweb-mode-map (make-sparse-keymap))) + (define-key mweb-mode-map (kbd "M-") 'mweb-set-default-major-mode) + (define-key mweb-mode-map (kbd "M-") 'mweb-set-extra-indentation) + (define-key mweb-mode-map [remap mark-whole-buffer] 'mweb-mark-whole-buffer) + mweb-mode-map) + "Keymaps for command `multi-web-mode'.") + +(defvar mweb-mode-hook nil + "Hooks to run when command `multi-web-mode' is initialized.") + +(defvar mweb-extra-indentation 0 + "Extra indentation for chunks. +Automatically calculated when the major mode has changed.") + +(defcustom mweb-default-major-mode nil + "Default major mode when not in chunk." + :type 'symbol + :group 'multi-web-mode + :safe 'symbolp) + +(defcustom mweb-filename-extensions + nil + "File extensions that trigger activation. + +This is an example configuration: +'(\"php\" \"htm\" \"html\" \"ctp\" \"phtml\" \"php4\" \"php5\")" + :type '(list string) + :group 'multi-web-mode + :safe #'(lambda (extensions) + (not (catch 'fail + (dolist (ext extensions) + (when (not (stringp ext)) + (throw 'fail t))))))) + +;; What you read in the docstring translates to: +;; ((php-mode "<\\?php\\|<\\? \\|<\\?=" "\\?>") +;; (js-mode "") +;; (css-mode "")) +(defcustom mweb-tags + nil + "Tags enabled for command `multi-web-mode'. +This var is an alist on which each element has the form +\(major-mode \"open tag regex\" \"close tag regex\"). + +This is an example configuration: + +\(\(php-mode \"<\\\\?php\\|<\\\\? \\|<\\\\?=\" \"\\\\?>\") + \(js-mode + \"\") + \(css-mode \"\"))" + :type '(repeat (symbol string string)) + :group 'multi-web-mode + :safe #'(lambda (tags) + (not (catch 'fail + (dolist (tag tags) + (when (or + (not (symbolp (mweb-get-tag-attr tag 'mode))) + (not (stringp (mweb-get-tag-attr tag 'open))) + (not (stringp (mweb-get-tag-attr tag 'close)))) + (throw 'fail t))))))) + +(defcustom mweb-submode-indent-offset 2 + "Indentation offset for code inside chunks." + :type 'integer + :group 'multi-web-mode + :safe 'integerp) + +(defcustom mweb-ignored-commands + (list + 'undo + 'yas/expand + 'yas/next-field-or-maybe-expand + 'isearch-forward + 'isearch-backward + 'isearch-other-control-char) + "Commands that prevent changing the major mode." + :type '(repeat symbol) + :group 'multi-web-mode + :safe #'(lambda (names) + (not (catch 'fail + (dolist (name names) + (when (not (symbolp name)) + (throw 'fail t))))))) + +(defun mweb-get-tag-attr (tag attribute) + "Get TAG ATTRIBUTE. +ATTRIBUTE values can be 'mode to get the tag's major mode or +'open/'close to get the open/close regexp respectively." + (case attribute + (mode (car tag)) + (open (cadr tag)) + (close (caddr tag)))) + +(defun mweb-get-tag (tag-major-mode) + "Return tag from `mweb-tags' matching TAG-MAJOR-MODE." + (assoc tag-major-mode mweb-tags)) + +(defun mweb--looking-at-tag (&optional type) + "Return non-nil if pointer is looking at an open or close tag. + +Possible values of TYPE are: + * nil: to check if point is looking at an open or close tag. + * 'open: to check if point is looking at an open tag + * 'close: to check if point is looking at a close tag" + (let ((index 0) + (looking) + (open-tag) + (close-tag) + (tag-regexp)) + (save-excursion + (back-to-indentation) + (while (and (< index (length mweb-tags)) + (not looking)) + (setq open-tag (mweb-get-tag-attr (elt mweb-tags index) 'open)) + (setq close-tag (mweb-get-tag-attr (elt mweb-tags index) 'close)) + (case type + (open (setq tag-regexp open-tag)) + (close (setq tag-regexp close-tag)) + (otherwise (setq tag-regexp (concat open-tag "\\|" close-tag)))) + (when (looking-at tag-regexp) + (setq looking t)) + (setq index (+ 1 index)))) + looking)) + +(defsubst mweb-looking-at-open-tag-p () + "Return t if point is looking at an open tag." + (mweb--looking-at-tag 'open)) + +(defsubst mweb-looking-at-close-tag-p () + "Return t if point is looking at a close tag." + (mweb--looking-at-tag 'close)) + +(defsubst mweb-looking-at-tag-p () + "Return t if point is looking at an open or close tag." + (mweb--looking-at-tag)) + +(defun mweb-change-major-mode () + "Call the appropriate major mode for the pointed chunk. +If the current `major-mode' is the correct one it doesn't funcall the +major mode and returns nil, otherwise changes the `major-mode' and +returns a symbol with its name." + (let ((closest-chunk-point 0) + (closest-chunk-mode mweb-default-major-mode) + (result nil)) + (save-restriction + (widen) + (dolist (tag mweb-tags) + (setq result (mweb-closest-starting-chunk-point tag)) + (when (and (integerp result) + (<= closest-chunk-point result)) + (setq closest-chunk-point result) + (setq closest-chunk-mode (mweb-get-tag-attr tag 'mode))))) + (when (not (equal closest-chunk-mode major-mode)) + (funcall closest-chunk-mode) + closest-chunk-mode))) + +(defun mweb-change-indent-line-function () + "Set the correct value for `indent-line-function'. +Depending of `major-mode'." + (when (not (equal major-mode mweb-default-major-mode)) + (setq indent-line-function 'mweb-indent-line))) + +(defun mweb-closest-starting-chunk-point (tag) + "Return the point of the closest chunk for TAG. +Where TAG is one of the tags contained in the `mweb-tags' +list. If the chunk is not found then it returns nil." + (let ((open-tag) + (close-tag)) + (save-excursion + (setq open-tag (re-search-backward (mweb-get-tag-attr tag 'open) nil t))) + (save-excursion + (setq close-tag (re-search-backward (mweb-get-tag-attr tag 'close) nil t))) + (cond ((not open-tag) + nil) + ((and open-tag + (not close-tag)) + open-tag) + ((> open-tag close-tag) + open-tag)))) + +(defun mweb-multiple-chunks-p () + "Check if multiple chunks exist in the current buffer." + (save-excursion + (save-restriction + (widen) + (goto-char (point-min)) + (re-search-forward "[^\s\t\n]" nil t) + (or (not (mweb-looking-at-open-tag-p)) + (catch 'break + (dolist (tag mweb-tags) + (when (re-search-forward (mweb-get-tag-attr tag 'close) nil t) + (throw 'break (not (not (re-search-forward "[^\s\t\n]" nil t))))))))))) + +(defun mweb-update-context () + "Update extra indentation value for chunks." + (let ((changed-major-mode (mweb-change-major-mode))) + (if (and changed-major-mode + (not (equal major-mode mweb-default-major-mode))) + (setq mweb-extra-indentation (mweb-calculate-indentation)) + (setq mweb-extra-indentation 0))) + (mweb-change-indent-line-function)) + +(defun mweb-calculate-indentation () + "Calculate the correct indentation given previous submode." + (let ((indentation 0) + (prev-line-pos) + (changed-major-mode major-mode) + (buffer-modified-flag (buffer-modified-p))) + (save-restriction + (widen) + (save-excursion + (mweb-goto-current-mode-open-tag) + (if (progn (mweb-forward-nonblank-line -1) (bobp)) + (if (mweb-multiple-chunks-p) + (setq indentation 0) + (setq indentation (- mweb-submode-indent-offset))) + (end-of-line) + (setq prev-line-pos (point-marker)) + (insert "\na") + (mweb-change-major-mode) + (indent-according-to-mode) + (setq indentation (current-indentation)) + (delete-region prev-line-pos (line-end-position)))) + (funcall changed-major-mode) + (set-buffer-modified-p buffer-modified-flag) + indentation))) + +(defun mweb-mark-whole-buffer () + "Multi-web-mode's version of `mark-whole-buffer'." + (interactive) + (push-mark (point)) + (goto-char (point-min)) + (mweb-change-major-mode) + (push-mark (point-max) nil t)) + +(defun mweb-indent-line () + "Function to use when indenting a submode line." + (interactive) + ;; Yes, indent according to mode will do what we expect + (setq mweb-extra-indentation (mweb-calculate-indentation)) + (if (not (mweb-looking-at-open-tag-p)) + (if (not (mweb-looking-at-close-tag-p)) + ;; Normal indentation + (if (equal major-mode mweb-default-major-mode) + (indent-according-to-mode) + (save-excursion + (beginning-of-line) + (delete-horizontal-space) + (unless (bobp) + (indent-according-to-mode) + (indent-to (+ mweb-extra-indentation mweb-submode-indent-offset))))) + ;; Close tag indentation routine + (let ((open-tag-indentation 0)) + (save-excursion + (mweb-goto-current-mode-open-tag) + (setq open-tag-indentation (current-indentation))) + (beginning-of-line) + (delete-horizontal-space) + (indent-to open-tag-indentation))) + ;; Open tag indentation routine + (beginning-of-line) + (delete-horizontal-space) + (insert "a") + (delete-horizontal-space) + (beginning-of-line) + (mweb-update-context) + (indent-according-to-mode) + (indent-to (+ mweb-extra-indentation mweb-submode-indent-offset)) + (delete-char 1)) + (and (bolp) (back-to-indentation))) + +(defun mweb-indent-region (start end) + "Indent a region taking care of chunks. +This routine considers the relative position of the chunks within +the buffer. It follows the same filosophy than +`mweb-indent-line-forward' because that function is what is used +to indent the chunks which are not for the default major mode. +Called from a program, START and END specify the region to indent." + (interactive "r") + (let ((delete-active-region nil) + (line-end)) + (save-excursion + (goto-char end) + (setq end (point-marker)) + (goto-char start) + (mweb-change-major-mode) + (or (bolp) (forward-line 1)) + (while (< (point) end) + (mweb-update-context) + (if (equal major-mode mweb-default-major-mode) + (indent-according-to-mode) + (mweb-indent-line)) + (forward-line 1)) + (move-marker end nil)))) + +(defun mweb-get-current-mode-tag-point (type) + "Gets the point marker of current chunk's open/close tag. + +The TYPE argument can be a 'open for the open tag or 'close for +the close tag." + (when (not (equal major-mode mweb-default-major-mode)) + (let ((index 0) + (found nil) + (tag) + (result nil) + (re-search-func (if (equal type 'open) + 're-search-backward + 're-search-forward))) + (while (and (< index (length mweb-tags)) + (not found)) + (setq tag (elt mweb-tags index)) + (when (or (equal (mweb-get-tag-attr tag 'mode) major-mode) + (equal major-mode mweb-default-major-mode)) + (setq found t) + (save-excursion + (if (looking-at (mweb-get-tag-attr tag type)) + (progn + (back-to-indentation) + (setq result (point))) + (setq result (funcall re-search-func + (mweb-get-tag-attr tag type) + nil t))))) + (setq index (+ 1 index))) + result))) + +(defun mweb-goto-current-mode-open-tag () + "Move the point to the open tag of the current chunk." + (interactive) + (let ((tag-point (mweb-get-current-mode-tag-point 'open))) + (when tag-point + (goto-char tag-point)))) + +(defun mweb-goto-current-mode-close-tag () + "Move the point to the close tag of the current chunk." + (interactive) + (let ((tag-point (mweb-get-current-mode-tag-point 'close))) + (when tag-point + (goto-char tag-point)))) + +(defun mweb-set-extra-indentation (number) + "Set the new value for `mweb-extra-indentation' to NUMBER." + (interactive "nNew mweb-extra-indentation value: ") + (setq mweb-extra-indentation number) + (message "mweb-extra-indentation = %d" mweb-extra-indentation)) + +(defun mweb-set-default-major-mode (major-mode) + "Set the new value for `mweb-default-major-mode' to MAJOR-MODE." + (interactive "CNew default major mode: ") + (setq mweb-default-major-mode major-mode) + (mweb-change-major-mode) + (message "mweb-default-major-mode = %s" mweb-default-major-mode)) + +(defun mweb-forward-nonblank-line (&optional number) + "Move the cursor to the next/previous non blank line. + +When NUMBER is positive it moves forward and when is negative +it moves backwards." + (when (not number) + (setq number 1)) + (when (> number 1) + (setq number 1)) + (when (< number -1) + (setq number -1)) + (forward-line number) + (while (and (equal (mweb-get-current-line-trimmed-contents) "") + (not (or (bobp) (eobp)))) + (forward-line number))) + +(defun mweb-get-current-line-trimmed-contents () + "Gets the contents of the current line. +It trims all space characters at the beginning and end of the line." + (let ((start-point) + (end-point) + (contents)) + (save-excursion + (beginning-of-line) + (setq start-point (point)) + (end-of-line) + (setq end-point (point)) + (setq contents (buffer-substring start-point end-point)) + (when (string-match "[ \t]*$" contents) + (setq contents (replace-match "" nil nil contents))) + (when (string-match "^[ \t]*" contents) + (setq contents (replace-match "" nil nil contents)))) + contents)) + +(defun mweb-post-command-hook () + "The function which is appended to the `post-command-hook'." + (when (and multi-web-mode + (not (region-active-p)) + (not (member last-command mweb-ignored-commands))) + (mweb-update-context))) + +(defun mweb-enable () + "Setup the minor mode." + (set (make-local-variable 'indent-region-function) + 'mweb-indent-region) + (make-local-variable 'indent-line-function) + (add-hook 'post-command-hook 'mweb-post-command-hook) + (assq-delete-all 'multi-web-mode minor-mode-map-alist) + (push (cons 'multi-web-mode mweb-mode-map) + minor-mode-map-alist) + (run-hooks 'mweb-mode-hook)) + +(defun mweb-disable () + "Disable the minor mode." + (assq-delete-all 'multi-web-mode minor-mode-map-alist)) + +;;;###autoload +(define-minor-mode multi-web-mode + "Enables the multi web mode chunk detection and indentation" + :lighter " Multi-Web" :group 'convenience + (if multi-web-mode + (mweb-enable) + (mweb-disable))) + +(defun multi-web-mode-maybe () + "Used to turn on the globalized minor mode." + (when (member + (file-name-extension (or buffer-file-name "")) + mweb-filename-extensions) + (multi-web-mode 1))) + +(define-globalized-minor-mode multi-web-global-mode + multi-web-mode multi-web-mode-maybe + :group 'multi-web-mode + :require 'multi-web-mode) + +(provide 'multi-web-mode) +;;; multi-web-mode.el ends here