-;;; multi-web-mode.el --- multiple major mode support for web editing
-
-;; Copyright (C) 2012 Fabián Ezequiel Gallina.
-
-;; Author: Fabián E. Gallina <fabian@anue.biz>
-;; 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 <http://www.gnu.org/licenses/>.
-
-;;; 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.
-
-;;
-\f
-;;; 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-<f11>") 'mweb-set-default-major-mode)
- (define-key mweb-mode-map (kbd "M-<f12>") '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 "<script +\\(type=\"text/javascript\"\\|language=\"javascript\"\\)[^>]*>" "</script>")
-;; (css-mode "<style +type=\"text/css\"[^>]*>" "</style>"))
-(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
- \"<script +\\\\(type=\\\"text/javascript\\\"\\\\
-|language=\\\"javascript\\\"\\\\)[^>]*>\"
- \"</script>\")
- \(css-mode \"<style +type=\\\"text/css\\\"[^>]*>\" \"</style>\"))"
- :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