]> git.donarmstrong.com Git - lib.git/blob - emacs_el/multi-web-mode.el
fix missing ) for org-mode
[lib.git] / emacs_el / multi-web-mode.el
1 ;;; multi-web-mode.el --- multiple major mode support for web editing
2
3 ;; Copyright (C) 2012 Fabián Ezequiel Gallina.
4
5 ;; Author: Fabián E. Gallina <fabian@anue.biz>
6 ;; URL: https://github.com/fgallina/multi-web-mode
7 ;; Version: 0.1
8 ;; Created: Feb 2009
9 ;; Keywords: convenience, languages, wp
10
11 ;; This file is part of Multi Web Mode
12
13 ;; Multi Web Mode is free software: you can redistribute it and/or
14 ;; modify it under the terms of the GNU General Public License as
15 ;; published by the Free Software Foundation, either version 3 of the
16 ;; License, or (at your option) any later version.
17
18 ;; Multi Web Mode is distributed in the hope that it will be useful,
19 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
20 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
21 ;; General Public License for more details.
22
23 ;; You should have received a copy of the GNU General Public License
24 ;; along with Multi Web Mode. If not, see <http://www.gnu.org/licenses/>.
25
26 ;;; Commentary:
27
28 ;; Multi Web Mode is a minor mode wich makes web editing in Emacs much easier.
29
30 ;; Basically what it does is select the appropriate major mode
31 ;; automatically when you move the point and also calculates the
32 ;; correct indentation of chunks according to the indentation of the
33 ;; most relevant major mode.
34
35 ;;
36 \f
37 ;;; Code:
38
39 (eval-when-compile
40   (require 'cl)
41   (defvar multi-web-mode))
42
43 (defvar mweb-mode-map
44   (let ((mweb-mode-map (make-sparse-keymap)))
45     (define-key mweb-mode-map (kbd "M-<f11>") 'mweb-set-default-major-mode)
46     (define-key mweb-mode-map (kbd "M-<f12>") 'mweb-set-extra-indentation)
47     (define-key mweb-mode-map [remap mark-whole-buffer] 'mweb-mark-whole-buffer)
48     mweb-mode-map)
49   "Keymaps for command `multi-web-mode'.")
50
51 (defvar mweb-mode-hook nil
52   "Hooks to run when command `multi-web-mode' is initialized.")
53
54 (defvar mweb-extra-indentation 0
55   "Extra indentation for chunks.
56 Automatically calculated when the major mode has changed.")
57
58 (defcustom mweb-default-major-mode nil
59   "Default major mode when not in chunk."
60   :type 'symbol
61   :group 'multi-web-mode
62   :safe 'symbolp)
63
64 (defcustom mweb-filename-extensions
65   nil
66   "File extensions that trigger activation.
67
68 This is an example configuration:
69 '(\"php\" \"htm\" \"html\" \"ctp\" \"phtml\" \"php4\" \"php5\")"
70   :type '(list string)
71   :group 'multi-web-mode
72   :safe #'(lambda (extensions)
73             (not (catch 'fail
74                    (dolist (ext extensions)
75                      (when (not (stringp ext))
76                        (throw 'fail t)))))))
77
78 ;; What you read in the docstring translates to:
79 ;; ((php-mode "<\\?php\\|<\\? \\|<\\?=" "\\?>")
80 ;;  (js-mode "<script +\\(type=\"text/javascript\"\\|language=\"javascript\"\\)[^>]*>" "</script>")
81 ;;  (css-mode "<style +type=\"text/css\"[^>]*>" "</style>"))
82 (defcustom mweb-tags
83   nil
84   "Tags enabled for command `multi-web-mode'.
85 This var is an alist on which each element has the form
86 \(major-mode \"open tag regex\" \"close tag regex\").
87
88 This is an example configuration:
89
90 \(\(php-mode \"<\\\\?php\\|<\\\\? \\|<\\\\?=\" \"\\\\?>\")
91  \(js-mode
92  \"<script +\\\\(type=\\\"text/javascript\\\"\\\\
93 |language=\\\"javascript\\\"\\\\)[^>]*>\"
94  \"</script>\")
95  \(css-mode \"<style +type=\\\"text/css\\\"[^>]*>\" \"</style>\"))"
96   :type '(repeat (symbol string string))
97   :group 'multi-web-mode
98   :safe #'(lambda (tags)
99             (not (catch 'fail
100                    (dolist (tag tags)
101                      (when (or
102                             (not (symbolp (mweb-get-tag-attr tag 'mode)))
103                             (not (stringp (mweb-get-tag-attr tag 'open)))
104                             (not (stringp (mweb-get-tag-attr tag 'close))))
105                        (throw 'fail t)))))))
106
107 (defcustom mweb-submode-indent-offset 2
108   "Indentation offset for code inside chunks."
109   :type 'integer
110   :group 'multi-web-mode
111   :safe 'integerp)
112
113 (defcustom mweb-ignored-commands
114   (list
115    'undo
116    'yas/expand
117    'yas/next-field-or-maybe-expand
118    'isearch-forward
119    'isearch-backward
120    'isearch-other-control-char)
121   "Commands that prevent changing the major mode."
122   :type '(repeat symbol)
123   :group 'multi-web-mode
124   :safe #'(lambda (names)
125             (not (catch 'fail
126                    (dolist (name names)
127                      (when (not (symbolp name))
128                        (throw 'fail t)))))))
129
130 (defun mweb-get-tag-attr (tag attribute)
131   "Get TAG ATTRIBUTE.
132 ATTRIBUTE values can be 'mode to get the tag's major mode or
133 'open/'close to get the open/close regexp respectively."
134   (case attribute
135     (mode (car tag))
136     (open (cadr tag))
137     (close (caddr tag))))
138
139 (defun mweb-get-tag (tag-major-mode)
140   "Return tag from `mweb-tags' matching TAG-MAJOR-MODE."
141   (assoc tag-major-mode mweb-tags))
142
143 (defun mweb--looking-at-tag (&optional type)
144   "Return non-nil if pointer is looking at an open or close tag.
145
146 Possible values of TYPE are:
147     * nil: to check if point is looking at an open or close tag.
148     * 'open: to check if point is looking at an open tag
149     * 'close: to check if point is looking at a close tag"
150   (let ((index 0)
151         (looking)
152         (open-tag)
153         (close-tag)
154         (tag-regexp))
155     (save-excursion
156       (back-to-indentation)
157       (while (and (< index (length mweb-tags))
158                   (not looking))
159         (setq open-tag (mweb-get-tag-attr (elt mweb-tags index) 'open))
160         (setq close-tag (mweb-get-tag-attr (elt mweb-tags index) 'close))
161         (case type
162           (open (setq tag-regexp open-tag))
163           (close (setq tag-regexp close-tag))
164           (otherwise (setq tag-regexp (concat open-tag "\\|" close-tag))))
165         (when (looking-at tag-regexp)
166           (setq looking t))
167         (setq index (+ 1 index))))
168     looking))
169
170 (defsubst mweb-looking-at-open-tag-p ()
171   "Return t if point is looking at an open tag."
172   (mweb--looking-at-tag 'open))
173
174 (defsubst mweb-looking-at-close-tag-p ()
175   "Return t if point is looking at a close tag."
176   (mweb--looking-at-tag 'close))
177
178 (defsubst mweb-looking-at-tag-p ()
179   "Return t if point is looking at an open or close tag."
180   (mweb--looking-at-tag))
181
182 (defun mweb-change-major-mode ()
183   "Call the appropriate major mode for the pointed chunk.
184 If the current `major-mode' is the correct one it doesn't funcall the
185 major mode and returns nil, otherwise changes the `major-mode' and
186 returns a symbol with its name."
187   (let ((closest-chunk-point 0)
188         (closest-chunk-mode mweb-default-major-mode)
189         (result nil))
190     (save-restriction
191       (widen)
192       (dolist (tag mweb-tags)
193         (setq result (mweb-closest-starting-chunk-point tag))
194         (when (and (integerp result)
195                    (<= closest-chunk-point result))
196           (setq closest-chunk-point result)
197           (setq closest-chunk-mode (mweb-get-tag-attr tag 'mode)))))
198     (when (not (equal closest-chunk-mode major-mode))
199       (funcall closest-chunk-mode)
200       closest-chunk-mode)))
201
202 (defun mweb-change-indent-line-function ()
203   "Set the correct value for `indent-line-function'.
204 Depending of `major-mode'."
205   (when (not (equal major-mode mweb-default-major-mode))
206     (setq indent-line-function 'mweb-indent-line)))
207
208 (defun mweb-closest-starting-chunk-point (tag)
209   "Return the point of the closest chunk for TAG.
210 Where TAG is one of the tags contained in the `mweb-tags'
211 list.  If the chunk is not found then it returns nil."
212   (let ((open-tag)
213         (close-tag))
214     (save-excursion
215       (setq open-tag (re-search-backward (mweb-get-tag-attr tag 'open) nil t)))
216     (save-excursion
217       (setq close-tag (re-search-backward (mweb-get-tag-attr tag 'close) nil t)))
218     (cond ((not open-tag)
219            nil)
220           ((and open-tag
221                 (not close-tag))
222            open-tag)
223           ((> open-tag close-tag)
224            open-tag))))
225
226 (defun mweb-multiple-chunks-p ()
227   "Check if multiple chunks exist in the current buffer."
228   (save-excursion
229     (save-restriction
230       (widen)
231       (goto-char (point-min))
232       (re-search-forward "[^\s\t\n]" nil t)
233       (or (not (mweb-looking-at-open-tag-p))
234           (catch 'break
235             (dolist (tag mweb-tags)
236               (when (re-search-forward (mweb-get-tag-attr tag 'close) nil t)
237                 (throw 'break (not (not (re-search-forward "[^\s\t\n]" nil t)))))))))))
238
239 (defun mweb-update-context ()
240   "Update extra indentation value for chunks."
241   (let ((changed-major-mode (mweb-change-major-mode)))
242     (if (and changed-major-mode
243              (not (equal major-mode mweb-default-major-mode)))
244         (setq mweb-extra-indentation (mweb-calculate-indentation))
245       (setq mweb-extra-indentation 0)))
246   (mweb-change-indent-line-function))
247
248 (defun mweb-calculate-indentation ()
249   "Calculate the correct indentation given previous submode."
250   (let ((indentation 0)
251         (prev-line-pos)
252         (changed-major-mode major-mode)
253         (buffer-modified-flag (buffer-modified-p)))
254     (save-restriction
255       (widen)
256       (save-excursion
257         (mweb-goto-current-mode-open-tag)
258         (if (progn (mweb-forward-nonblank-line -1) (bobp))
259             (if (mweb-multiple-chunks-p)
260                 (setq indentation 0)
261               (setq indentation (- mweb-submode-indent-offset)))
262           (end-of-line)
263           (setq prev-line-pos (point-marker))
264           (insert "\na")
265           (mweb-change-major-mode)
266           (indent-according-to-mode)
267           (setq indentation (current-indentation))
268           (delete-region prev-line-pos (line-end-position))))
269       (funcall changed-major-mode)
270       (set-buffer-modified-p buffer-modified-flag)
271       indentation)))
272
273 (defun mweb-mark-whole-buffer ()
274   "Multi-web-mode's version of `mark-whole-buffer'."
275   (interactive)
276   (push-mark (point))
277   (goto-char (point-min))
278   (mweb-change-major-mode)
279   (push-mark (point-max) nil t))
280
281 (defun mweb-indent-line ()
282   "Function to use when indenting a submode line."
283   (interactive)
284   ;; Yes, indent according to mode will do what we expect
285   (setq mweb-extra-indentation (mweb-calculate-indentation))
286   (if (not (mweb-looking-at-open-tag-p))
287       (if (not (mweb-looking-at-close-tag-p))
288           ;; Normal indentation
289           (if (equal major-mode mweb-default-major-mode)
290               (indent-according-to-mode)
291             (save-excursion
292               (beginning-of-line)
293               (delete-horizontal-space)
294               (unless (bobp)
295                 (indent-according-to-mode)
296                 (indent-to (+ mweb-extra-indentation mweb-submode-indent-offset)))))
297         ;; Close tag indentation routine
298         (let ((open-tag-indentation 0))
299           (save-excursion
300             (mweb-goto-current-mode-open-tag)
301             (setq open-tag-indentation (current-indentation)))
302           (beginning-of-line)
303           (delete-horizontal-space)
304           (indent-to open-tag-indentation)))
305     ;; Open tag indentation routine
306     (beginning-of-line)
307     (delete-horizontal-space)
308     (insert "a")
309     (delete-horizontal-space)
310     (beginning-of-line)
311     (mweb-update-context)
312     (indent-according-to-mode)
313     (indent-to (+ mweb-extra-indentation mweb-submode-indent-offset))
314     (delete-char 1))
315   (and (bolp) (back-to-indentation)))
316
317 (defun mweb-indent-region (start end)
318   "Indent a region taking care of chunks.
319 This routine considers the relative position of the chunks within
320 the buffer.  It follows the same filosophy than
321 `mweb-indent-line-forward' because that function is what is used
322 to indent the chunks which are not for the default major mode.
323 Called from a program, START and END specify the region to indent."
324   (interactive "r")
325   (let ((delete-active-region nil)
326         (line-end))
327     (save-excursion
328       (goto-char end)
329       (setq end (point-marker))
330       (goto-char start)
331       (mweb-change-major-mode)
332       (or (bolp) (forward-line 1))
333       (while (< (point) end)
334         (mweb-update-context)
335         (if (equal major-mode mweb-default-major-mode)
336             (indent-according-to-mode)
337           (mweb-indent-line))
338         (forward-line 1))
339       (move-marker end nil))))
340
341 (defun mweb-get-current-mode-tag-point (type)
342   "Gets the point marker of current chunk's open/close tag.
343
344 The TYPE argument can be a 'open for the open tag or 'close for
345 the close tag."
346   (when (not (equal major-mode mweb-default-major-mode))
347     (let ((index 0)
348           (found nil)
349           (tag)
350           (result nil)
351           (re-search-func (if (equal type 'open)
352                               're-search-backward
353                             're-search-forward)))
354       (while (and (< index (length mweb-tags))
355                   (not found))
356         (setq tag (elt mweb-tags index))
357         (when (or (equal (mweb-get-tag-attr tag 'mode) major-mode)
358                   (equal major-mode mweb-default-major-mode))
359           (setq found t)
360           (save-excursion
361             (if (looking-at (mweb-get-tag-attr tag type))
362                 (progn
363                   (back-to-indentation)
364                   (setq result (point)))
365               (setq result (funcall re-search-func
366                                     (mweb-get-tag-attr tag type)
367                                     nil t)))))
368         (setq index (+ 1 index)))
369       result)))
370
371 (defun mweb-goto-current-mode-open-tag ()
372   "Move the point to the open tag of the current chunk."
373   (interactive)
374   (let ((tag-point (mweb-get-current-mode-tag-point 'open)))
375     (when tag-point
376       (goto-char tag-point))))
377
378 (defun mweb-goto-current-mode-close-tag ()
379   "Move the point to the close tag of the current chunk."
380   (interactive)
381   (let ((tag-point (mweb-get-current-mode-tag-point 'close)))
382     (when tag-point
383       (goto-char tag-point))))
384
385 (defun mweb-set-extra-indentation (number)
386   "Set the new value for `mweb-extra-indentation' to NUMBER."
387   (interactive "nNew mweb-extra-indentation value: ")
388   (setq mweb-extra-indentation number)
389   (message "mweb-extra-indentation = %d" mweb-extra-indentation))
390
391 (defun mweb-set-default-major-mode (major-mode)
392   "Set the new value for `mweb-default-major-mode' to MAJOR-MODE."
393   (interactive "CNew default major mode: ")
394   (setq mweb-default-major-mode major-mode)
395   (mweb-change-major-mode)
396   (message "mweb-default-major-mode = %s" mweb-default-major-mode))
397
398 (defun mweb-forward-nonblank-line (&optional number)
399   "Move the cursor to the next/previous non blank line.
400
401 When NUMBER is positive it moves forward and when is negative
402 it moves backwards."
403   (when (not number)
404     (setq number 1))
405   (when (> number 1)
406     (setq number 1))
407   (when (< number -1)
408     (setq number -1))
409   (forward-line number)
410   (while (and (equal (mweb-get-current-line-trimmed-contents) "")
411               (not (or (bobp) (eobp))))
412     (forward-line number)))
413
414 (defun mweb-get-current-line-trimmed-contents ()
415   "Gets the contents of the current line.
416 It trims all space characters at the beginning and end of the line."
417   (let ((start-point)
418         (end-point)
419         (contents))
420     (save-excursion
421       (beginning-of-line)
422       (setq start-point (point))
423       (end-of-line)
424       (setq end-point (point))
425       (setq contents (buffer-substring start-point end-point))
426       (when (string-match "[ \t]*$" contents)
427         (setq contents (replace-match "" nil nil contents)))
428       (when (string-match "^[ \t]*" contents)
429         (setq contents (replace-match "" nil nil contents))))
430     contents))
431
432 (defun mweb-post-command-hook ()
433   "The function which is appended to the `post-command-hook'."
434   (when (and multi-web-mode
435              (not (region-active-p))
436              (not (member last-command mweb-ignored-commands)))
437     (mweb-update-context)))
438
439 (defun mweb-enable ()
440   "Setup the minor mode."
441   (set (make-local-variable 'indent-region-function)
442        'mweb-indent-region)
443   (make-local-variable 'indent-line-function)
444   (add-hook 'post-command-hook 'mweb-post-command-hook)
445   (assq-delete-all 'multi-web-mode minor-mode-map-alist)
446   (push (cons 'multi-web-mode mweb-mode-map)
447         minor-mode-map-alist)
448   (run-hooks 'mweb-mode-hook))
449
450 (defun mweb-disable ()
451   "Disable the minor mode."
452   (assq-delete-all 'multi-web-mode minor-mode-map-alist))
453
454 ;;;###autoload
455 (define-minor-mode multi-web-mode
456   "Enables the multi web mode chunk detection and indentation"
457   :lighter " Multi-Web" :group 'convenience
458   (if multi-web-mode
459       (mweb-enable)
460     (mweb-disable)))
461
462 (defun multi-web-mode-maybe ()
463   "Used to turn on the globalized minor mode."
464   (when (member
465          (file-name-extension (or buffer-file-name ""))
466          mweb-filename-extensions)
467     (multi-web-mode 1)))
468
469 (define-globalized-minor-mode multi-web-global-mode
470   multi-web-mode multi-web-mode-maybe
471   :group 'multi-web-mode
472   :require 'multi-web-mode)
473
474 (provide 'multi-web-mode)
475 ;;; multi-web-mode.el ends here