;;; hm--html-indentation.el
;;; v1.00;  9-Feb-1997
;;; Copyright (C) 1997 Heiko Muenkel
;;; email: muenkel@tnt.uni-hannover.de
;;;
;;;  This program 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 1, or (at your option)
;;;  any later version.
;;;
;;;  This program 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 this program; if not, write to the Free Software
;;;  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
;;;
;;; 
;;; Description:
;;;
;;;	Defines functions for the indentation.
;;; 
;;; Installation: 
;;;   
;;;	Put this file in one of your load path directories.
;;;

(defun hm--html-point-between-strings-p (string-1
					 string-2
					 &optional boundary)
  "Returns non nil, if the current point is between STRING-1 and STRING-2."
  (when (and (re-search-backward (concat "\\("
					 (regexp-quote string-1)
					 "\\)\\|\\("
					 (regexp-quote string-2)
					 "\\)")
				 boundary
				 t)
	     (match-string 1))
    (point)))

(defun hm--html-in-comment-p ()
  "Checks if the current point is in a comment block.
If this is the case, then the start point of the comment is returned.
Otherwise nil is returned."
  (save-excursion
    (hm--html-point-between-strings-p comment-start comment-end)))

(defun hm--html-previous-line-start ()
  "Returns the start of the previous non blank line."
  (save-excursion
    (beginning-of-line)
    (skip-chars-backward " \t\n")
    (beginning-of-line)
    (point)))

(defun hm--html-look-at-comment-end-p ()
  "T, if the current line starts with the comment end."
  (looking-at (regexp-quote comment-end)))

(defun hm--html-column-of-previous-regexp (regexp)
  "Returns the column of the start of the previous REGEXP.
It searches backward until the REGEXP is found. If no
REGEXP is found, then it returns 0."
  (save-excursion
    (if (re-search-backward regexp nil t)
	(current-column)
      0)))

(defun hm--html-look-at-end-tag-p ()
  "Returns the end tag name if the point is at the start of an end tag.
nil is returned otherwise."
  (when (looking-at "\\(<[ \t\n]*/[ \t\n]*\\)\\([^ \t\n>]+\\)")
    (match-string 2)))
    

(defun hm--html-previous-line-indentation ()
  "Returns the indentation of the previous non blank line."
  (save-excursion
    (beginning-of-line)
    (skip-chars-backward " \t\n")
    (back-to-indentation)
    (current-column)))

(defun hm--html-in-tag-p ()
  "Checks if the current point is in a tag.
If this is the case, then the start point of the tag is returned.
Otherwise nil is returned."
  (save-excursion
    (let ((start (re-search-backward "\\(<\\)\\|\\(>\\)" nil t)))
      (when (match-string 1)
	start))))

(defun hm--html-return-beginning-of-line ()
  "Returns the beginning of the current line."
  (save-excursion
    (beginning-of-line)
    (point)))

(defun hm--html-return-end-of-line ()
  "Returns the end of the current line."
  (save-excursion
    (end-of-line)
    (point)))

(defun hm--html-paramter-column-in-line-after-point (point)
  "Returns the column where the second non blank text after POINT starts.
This point must be in the line with POINT otherwise it returns nil."
  (save-excursion
    (goto-char point)
    (when (re-search-forward "<[ \t]*[^ \t]+[ \t]"
			     (hm--html-return-end-of-line)
			     t)
      (when (looking-at "[^\n]")
	(current-column)))))

(defun hm--html-column-of-point (point)
  "Returns the column of the POINT."
  (save-excursion
    (goto-char point)
    (current-column)))

(defun hm--html-search-previous-tag-in-current-line ()
  "Searches tags from the `(point)' to the beginning of the line.
It returns nil, if there is no tag and the tag name, if there is
a tag. The tag name contains a leading /, if it is an end tag."
  (when (re-search-backward ">" (hm--html-return-beginning-of-line) t)
    (when (re-search-backward
	   "\\(<[ \t\n]*\\(/?\\)\\([ \t\n]*[^> \t\n]+\\)[^>]*\\)"
	   nil
	   t)
      (concat (match-string 2) (match-string 3)))))

(defun hm--html-search-start-tag (tag-name until)
  "Searches start tag backwards from the current point until the point UNTIL.
The name of the tag is TAG-NAME. After this function the point is at UNTIL
 (then it returns nil) or at the start of the tag, then it returns t."
  (if (re-search-backward (concat "\\(<[ \t\n]*\\)\\(/?\\)\\("
				  tag-name 
				  "\\)\\([^>]*>\\)") until t)
      (if (string= "/" (match-string 2))
	  (progn
	    (hm--html-search-start-tag tag-name until)
	    (hm--html-search-start-tag tag-name until)) 
	t)
    (goto-char until)
    nil))

(defun hm--html-is-one-element-tag-p (tag-name)
  "Returns t, if the tag with the tag-name is a one element tag."
  (assoc ':hm--html-one-element-tag
	 (cdr (assoc* (downcase tag-name)
		      hm--html-tag-name-alist
		      :test 'string=))))

(defun hm--html-calculate-indent-according-to-previous-tags ()
  "Calculate the indent according to the previous tags in this line.
If no tags are found, then nil is returned."
  (save-excursion
    (let ((tag (hm--html-search-previous-tag-in-current-line)))
      (cond ((not tag) nil)
	    
	    ((eq ?/ (elt tag 0)) ; end tag found
	     (if (hm--html-search-start-tag 
		  (substring tag 1)
		  (point-min))
		 (or (hm--html-calculate-indent-according-to-previous-tags)
		     (progn
		       (backward-to-indentation 0)
		       (current-column)))
	       0)) ; it may be that the current indentation is better here

	    ((hm--html-is-one-element-tag-p tag) ; one element tag
	     (or (hm--html-calculate-indent-according-to-previous-tags)
		 (progn
		   (backward-to-indentation 0)
		   (current-column))))

	    (t ; start tag found
	     (+ (current-column) hm--html-inter-tag-indent))))))


(defun hm--html-calculate-indent ()
  "Calculate the indentation of the current line."
  (let ((match-point)
	(tag))
    (save-excursion
      (beginning-of-line)
      (back-to-indentation)
      (cond ((eq (count-lines (point-min) (point)) 0) 0) ; Filestart
	    
	    ((setq match-point (hm--html-in-comment-p)) ; in a comment
	     (if (>= match-point (hm--html-previous-line-start)) ; 1. line
		 (if (hm--html-look-at-comment-end-p)
		     (hm--html-column-of-previous-regexp
		      (regexp-quote comment-start))
		   (+ (hm--html-column-of-previous-regexp
		       (regexp-quote comment-start))
		      hm--html-comment-indent))
	       (if (hm--html-look-at-comment-end-p)
		   (- (hm--html-previous-line-indentation)
		      hm--html-comment-indent)
		 (hm--html-previous-line-indentation))))
	    
	    ((setq tag (hm--html-look-at-end-tag-p)) ; look at end tag
	     (hm--html-search-start-tag tag (point-min))
	     (current-column))
	    
	    ((looking-at ">")
	     (hm--html-column-of-previous-regexp "<"))

	    ((setq match-point (hm--html-in-tag-p))
	     (if (>= match-point (hm--html-previous-line-start)) ; 1. line
		 (or (hm--html-paramter-column-in-line-after-point match-point)
		     (+ (hm--html-column-of-point match-point)
			hm--html-intra-tag-indent))
	       (hm--html-previous-line-indentation)))

	    (t (or (save-excursion  ; check previous line
		     (skip-chars-backward " \t\n")
		     (hm--html-calculate-indent-according-to-previous-tags))
		   (hm--html-previous-line-indentation)))
	    ))))


;;; Indentation commands

(defun hm--html-indent-line ()
  "Indent the current line line."
  (interactive)
  (unless hm--html-disable-indentation
    (let ((pos (- (point-max) (point))))
      (indent-line-to (max 0 (hm--html-calculate-indent)))
      (when (> (- (point-max) pos) (point))
	(goto-char (- (point-max) pos))))))

;(defun hm--html-indent-region (begin end)
;  "Indents the region between BEGIN and END according to the major mode."
;  (interactive "d\nm")
;  (when (< end begin)
;    (let ((a end))
;      (setq end begin)
;      (setq begin a)))
;  (save-excursion
;    (goto-char begin)
;    (let ((old-point))
;      (while (and (<= (point) end)
;		  (not (eq (point) old-point)))
;	(setq old-point (point))
;	(indent-according-to-mode)
;	(forward-line)
;	))))

(defun hm--html-indent-region (begin end)
  "Indents the region between BEGIN and END according to the major mode."
  (interactive "d\nm")
  (when (< end begin)
    (let ((a end))
      (setq end begin)
      (setq begin a)))
  (let ((lines (count-lines begin end)))
    (save-excursion
      (goto-char begin)
      (loop repeat lines
            do (indent-according-to-mode)
            (forward-line))))
  )

(provide 'hm--html-indentation)
