;;; yatexenv.el --- YaTeX environment-specific functions
;;; (c) 1994-2019 by HIROSE Yuuji.[yuuji@yatex.org]
;;; Last modified Sat May 25 14:46:33 2019 on firestorm
;;; $Id: yatexenv.el,v b9ba8aacbea6 2019-12-26 12:53 +0859 yuuji $

;;; Code:
;;;
;; Functions for tabular environment
;;;

;; Showing the matching column of tabular environment.
(defun YaTeX-array-what-column-internal ()
  "Return the cons of matching column and its title of array environment.
When calling from a program, make sure to be in array/tabular environment."
  (let ((p (point)) bot beg eot bor eoll (nlptn "\\\\\\\\") (andptn "[^\\]&")
	(n 0) j
	(firsterr "This line might be the first row."))
    (save-excursion
      (YaTeX-beginning-of-environment)
      (setq eoll (save-excursion	;end of logical line
		   (YaTeX-goto-corresponding-environment) (point)))
      (search-forward "{" p) (up-list 1)
      (search-forward "{" p) (up-list 1)
      ;;(re-search-forward andptn p)
      (setq bot (point))		;beginning of tabular
      (while (progn (search-forward "&" eoll)
		    (equal (char-after (1- (match-beginning 0))) ?\\ )))
      (setq beg (1- (point)))		;beg is the point of the first &
      (or (re-search-forward nlptn eoll t)
	  (error firsterr))
      (setq eot (point))		;eot is the point of the first \\
      (goto-char p)
      (setq bor (if (re-search-backward nlptn bot 1)
		     (point)		;bor is the beginning of this row.
		  bot))
      (while (< (1- (point)) p)
	(if (equal (following-char) ?&)
	    (forward-char 1)
	  (re-search-forward andptn nil 1))
	(setq n (1+ n)))		;Check current column number
      (goto-char p)
      (cond				;Start searching \multicolumn{N}
       ((> n 1)
	(re-search-backward andptn)	;Sure to find!
	(while (re-search-backward "\\\\multicolumn{\\([0-9]+\\)}" bor t)
	  (setq n (+ n (YaTeX-str2int
			(buffer-substring (match-beginning 1)
					  (match-end 1)))
		     -1)))))
      (message "%s" n)
      (goto-char (1- beg))
      (beginning-of-line)
      (setq j n)
      (while (> j 1)
	(or (re-search-forward andptn p nil)
	    (error "This column exceeds the limit."))
	(setq j (1- j)))
      (skip-chars-forward "\\s ")
      (list n
	    (YaTeX-buffer-substring
	     (progn (skip-chars-forward "\n \t") (point))
	     (progn
	       (re-search-forward (concat andptn "\\|" nlptn) eot)
	       (goto-char (match-beginning 0))
	       (if (looking-at andptn)
		   (forward-char 1))
	       (skip-chars-backward "\\s ")
	       (point)))))))

(defun YaTeX-array-what-column ()
  "Show matching column title of array environment.
When calling from a program, make sure to be in array/tabular environment."
  (apply 'message
	 "This is the column(#%d) of: %s"
	 (YaTeX-array-what-column-internal)))

;;;###autoload
(defun YaTeX-what-column ()
  "Show which kind of column the current position is belonging to."
  (interactive)
  (cond
   ((YaTeX-quick-in-environment-p '("tabular" "tabular*" "array" "array*"))
    (YaTeX-array-what-column))
   (t (message "Not in array/tabular environment."))))

(defun YaTeX-tabular-parse-format-count-cols (beg end)
  (goto-char beg)
  (let (elt (cols 0))
    (while (< (point) end)
	(setq elt (following-char))
	(cond
	 ((string-match (char-to-string elt) "clrX") ;normal indicators.
	  (setq cols (1+ cols))
	  (forward-char 1))
	 ((equal elt ?|)		;vertical
	  (forward-char 1))
	 ((string-match (char-to-string elt) "p@") ;p or @ expression
	  (setq cols (+ (if (eq elt ?p) 1 0) cols))
	  ;;(skip-chars-forward "^{" p)
	  (skip-chars-forward "^{" end)
	  (forward-list 1))
	 ((equal elt ?*)		;*{N}{EXP} -> Repeat EXP N times
	  (skip-chars-forward "^{" end)
	  (setq cols (* (YaTeX-str2int
			 (buffer-substring
			  (1+ (point))
			  (progn (forward-list 1) (1- (point)))))
			(YaTeX-tabular-parse-format-count-cols
			 (progn (skip-chars-forward "^{" end) (1+ (point)))
			 (progn (forward-list 1) (1- (point)))))))
	 (t (forward-char 1))		;unknown char
	 ))
    cols))

(defun YaTeX-tabular-parse-format (&optional type)
  "Parse `tabular' format.
Return the list of (No.ofCols PointEndofFormat)"
  (let ((p (point)) boform eoform (cols 0))
    (save-excursion
      (if (null (YaTeX-beginning-of-environment t))
	  (error "Beginning of tabular not found."))
      (skip-chars-forward "^{")
      (forward-list 1)
      (cond
       ((eq type 'tabular*)
	(skip-chars-forward "^{")
	(forward-list 1)))
      (skip-chars-forward "^{" p)
      (if (/= (following-char) ?\{) (error "Tabular format not found."))
      (setq boform (1+ (point))
	    eoform (progn (forward-list 1) (1- (point))))
      (if (> eoform p) (error "Non-terminated tabular format."))
      (goto-char boform)
      (setq cols
	    (cond
	     ((eq type 'alignat)
	      (max
	       1
	       (* 2 (YaTeX-str2int
		     (buffer-substring
		      (point)
		      (progn (up-list -1) (forward-list 1) (1- (point))))))))
	     (t
	      (YaTeX-tabular-parse-format-count-cols (point) eoform))))
      (list cols (1+ eoform)))))

;; Insert &
(defun YaTeX-intelligent-newline-tabular (&optional type)
  "Parse current tabular format and insert that many `&'s."
  (let*((p (point)) (format (YaTeX-tabular-parse-format type))
	(cols (car format)) (beg (car (cdr format)))
	space hline)
    (cond
     ((search-backward "&" beg t)
      (goto-char p)
      (setq hline (search-backward "\\hline" beg t))
      (setq space (if (search-backward "\t&" beg t) "\t" " "))
      (goto-char p))
     (t ;;(insert "\\hline\n")
	(setq space " ")))
    (goto-char p)
    (while (> (1- cols) 0)
      (insert "&" space)
      (setq cols (1- cols)))
    (insert "\\\\")
    (if hline (insert " \\hline"))
    (goto-char p)
    (YaTeX-indent-line)))

(defun YaTeX-intelligent-newline-tabular* ()
  "Parse current tabular* format and insert that many `&'s."
  (YaTeX-intelligent-newline-tabular 'tabular*))

(fset 'YaTeX-intelligent-newline-array 'YaTeX-intelligent-newline-tabular)
(fset 'YaTeX-intelligent-newline-supertabular 'YaTeX-intelligent-newline-tabular)
(fset 'YaTeX-intelligent-newline-longtable 'YaTeX-intelligent-newline-tabular)
(fset 'YaTeX-intelligent-newline-tabularx 'YaTeX-intelligent-newline-tabular*)

(defun YaTeX-intelligent-newline-align ()
  "Intelligent newline function for align.
Count the number of & in the first align line and insert that many &s."
  (let*((p (point)) (amps 0))
    (if (string-match "alignat" env)
	(setq amps (1- (car (YaTeX-tabular-parse-format 'alignat))))
      (save-excursion
	(YaTeX-beginning-of-environment)
	(catch 'done
	  (while (YaTeX-re-search-active-forward
		  "\\(&\\)\\|\\(\\\\\\\\\\)" YaTeX-comment-prefix p t)
	    (if (match-beginning 1) (setq amps (1+ amps)) (throw 'done t))))))
    (save-excursion
      (forward-line -1)
      (skip-chars-forward " \t")
      (or (prog1 (looking-at "\\\\begin{") (end-of-line))
	  (save-excursion
	    (skip-chars-backward " \t")
	    (and (= (preceding-char) ?\\) (= (char-after (- (point) 2)) ?\\)))
	  (insert "\\\\")))
    (save-excursion
      (while (>= (setq amps (1- amps)) 0)
	(insert "& ")))
    (YaTeX-indent-line)))

(mapcar
 (function
  (lambda (s)
    (fset (intern (concat  "YaTeX-intelligent-newline-"
			   (symbol-name s)))
	  'YaTeX-intelligent-newline-align)))
 '(align* flalign  flalign* matrix pmatrix bmatrix Bmatrix vmatrix Vmatrix
   cases eqnarray eqnarray* alignat alignat*))

;;;
;; Functions for tabbing environment
;;;
(defun YaTeX-intelligent-newline-tabbing ()
  "Check the number of \\= in the first line and insert that many \\>."
  (let ((p (point)) begenv tabcount)
    (save-excursion
      (YaTeX-beginning-of-environment)
      (setq begenv (point-end-of-line))
      (if (YaTeX-search-active-forward "\\\\" YaTeX-comment-prefix p t)
	  (progn
	    (setq tabcount 0)
	    (while (> (point) begenv)
	      (if (search-backward "\\=" begenv 1)
		  (setq tabcount (1+ tabcount)))))))
    (YaTeX-indent-line)
    (if tabcount
	(progn
	  (save-excursion
	    (while (> tabcount 0)
	      (insert "\\>\t")
	      (setq tabcount (1- tabcount))))
	  (forward-char 2))
      (insert "\\= \\\\")
      (forward-char -5))))

;;;
;; Functions for itemize/enumerate/list environments
;;;

(defun YaTeX-intelligent-newline-itemize ()
  "Insert '\\item '."
  (insert "\\item ")
  (YaTeX-indent-line))

(fset 'YaTeX-intelligent-newline-enumerate 'YaTeX-intelligent-newline-itemize)
(fset 'YaTeX-intelligent-newline-list 'YaTeX-intelligent-newline-itemize)

(defun YaTeX-intelligent-newline-description ()
  (insert "\\item[] ")
  (forward-char -2)
  (YaTeX-indent-line))

(defun YaTeX-intelligent-newline-thebibliography ()
  "Insert '\\bibitem '."
  (YaTeX-indent-line)
  (YaTeX-make-section nil nil nil "bibitem")
  (YaTeX-indent-line))

;;;
;; For document environment
;;;
(defun YaTeX-intelligent-newline-document ()
  "New paragraph by null line or `\\par'."
  (if (< (count-lines
	  (or (get 'YaTeX-inner-environment 'point)
	      (max 1 (- (point) 17)))	;"\begin{document}\n" == 17
	  (point))
	 2)
      nil
    (if (save-excursion (re-search-backward "\\\\par\\>" nil t))
	(progn
	  (YaTeX-indent-line)
	  (insert "\\par")))
    (newline))
  (YaTeX-indent-line))

;;;
;; Intelligent newline
;;;
;;;###autoload
(defun YaTeX-intelligent-newline (arg)
  "Insert newline and environment-specific entry.
`\\item'	for some itemizing environment,
`\\> \\> \\'	for tabbing environemnt,
`& & \\ \hline'	for tabular environment."
  (interactive "P")
  (let*(env func)
    (end-of-line)
    (setq env (YaTeX-inner-environment))
    (if arg (setq env (YaTeX-read-environment "For what environment? ")))
    (setq func (intern-soft (concat "YaTeX-intelligent-newline-" env)))
    (end-of-line)
    (newline)
    (undo-boundary)
    (if (and env func (fboundp func))
	(funcall func))))

;;;
;; Environment-specific line indenting functions
;;;
;;;###autoload
(defun YaTeX-indent-line-equation ()
  "Indent a line in equation family."
  (let ((p (point)) (l-r 0) right-p peol depth (mp YaTeX-environment-indent))
    (if (save-excursion
	  (beginning-of-line)
	  (skip-chars-forward " \t")
	  (looking-at "\\\\right\\b"))
	(progn (YaTeX-reindent
		(save-excursion (YaTeX-goto-corresponding-leftright)
				(- (current-column) 0))))
      (save-excursion
	(forward-line -1)
	(while (and (not (bobp)) (YaTeX-on-comment-p))
	  (forward-line -1))
	;;(beginning-of-line)	;must be unnecessary
	(skip-chars-forward " \t")
	(if (eolp) (error "Math-environment can't have a null line!!"))
	(setq depth (current-column)
	      peol (point-end-of-line))
	(while (re-search-forward
		"\\\\\\(\\(left\\)\\|\\(right\\)\\)\\b" peol t)
	  (setq l-r (+ l-r (if (match-beginning 2) 1 -1))))
	(cond
	 ((progn (beginning-of-line)
		 (re-search-forward "\\\\\\\\\\s *$" (point-end-of-line) t))
	  ;;If previous line has `\\', this indentation is always normal.
	  (setq depth (+ (YaTeX-current-indentation) mp)))
	 ((> l-r 0)
	  (beginning-of-line)
	  (search-forward "\\left" peol nil l-r)
	  (goto-char (1+ (match-beginning 0)))
	  (setq depth (current-column)))
	 ((< l-r 0)
	  (goto-char (match-beginning 0))	;should be \right
	  (YaTeX-goto-corresponding-leftright)
	  (beginning-of-line)
	  (skip-chars-forward " \t")
	  ;(setq depth (+ (current-column) mp)) ;+mp is good?
	  (setq depth (current-column)))
	 (t				;if \left - \right = 0
	  (cond
	   ((re-search-forward "\\\\\\\\\\s *$" peol t)
	    (setq depth (+ (YaTeX-current-indentation) mp)))
	   ((re-search-forward "\\\\end{" peol t)
	    nil)			;same indentation as previous line's
	   ((re-search-forward "\\\\begin{" peol t)
	    (setq depth (+ depth mp)))
	   (t
	    (or (bobp) (forward-line -1))
	    (cond
	     ((re-search-forward
	       "\\\\\\\\\\s *$\\|\\\\begin{" (point-end-of-line) t)
	      (setq depth (+ depth mp)))
	     )))))
	(goto-char p))
      (YaTeX-reindent depth))))

;;;###autoload
(defun YaTeX-goto-corresponding-leftright ()
  "Go to corresponding \left or \right."
  (let ((YaTeX-struct-begin "\\left%1")
	(YaTeX-struct-end "\\right%1")
	(YaTeX-struct-name-regexp "[][(){}\\.|]")
	(in-leftright-p t))
    (YaTeX-goto-corresponding-environment t)))

;;;
;; Functions for formatting region being enclosed with environment
;;;
; These functions must take two argument; region-beginning, region-end.

(defun YaTeX-enclose-equation (beg end)
  (goto-char beg)
  (save-restriction
    (let (m0 bsl)
      (narrow-to-region beg end)
      (while (YaTeX-re-search-active-forward
	      "\\(\\$\\)" YaTeX-comment-prefix nil t)
	(goto-char (setq m0 (match-beginning 0)))
	(setq bsl 0)
	(if (and (not (bobp)) (= (char-after (1- (point))) ?\\ ))
	    (while (progn (forward-char -1) (= (char-after (point)) ?\\ ))
	      (setq bsl (1+ bsl))))
	(goto-char m0)
	(if (= 0 (% bsl 2))
	    (delete-char 1)
	  (forward-char 1))))))

(fset 'YaTeX-enclose-eqnarray 'YaTeX-enclose-equation)
(fset 'YaTeX-enclose-eqnarray* 'YaTeX-enclose-equation)
(mapcar (function	;; Add all AMS LaTeX envs
	 (lambda (sym)
	   (fset (intern (concat "YaTeX-enclose-" (car sym)))
		 'YaTeX-enclose-equation)))
	YaTeX-ams-math-begin-alist)

(defun YaTeX-enclose-verbatim (beg end)) ;do nothing when enclose verbatim
(fset 'YaTeX-enclose-verbatim* 'YaTeX-enclose-verbatim)

(provide 'yatexenv)
