;;; yatexhie.el --- YaTeX hierarchy browser
;;; 
;;; (c)1995-2013 by HIROSE Yuuji [yuuji@yatex.org]
;;; Last modified Sun Dec 21 14:05:20 2014 on firestorm
;;; $Id: yatexhie.el,v 510106cf15fb 2015-07-12 10:06 +0900 yuuji $

;;; Code:
;; ----- Customizable variables -----
(defvar YaTeX-hierarchy-ignore-heading-regexp
  "\\$[A-Z][a-z]+: .* \\$\\|-\\*- .* -\\*-"
  "*Regexp of lines to ignore as files' headline.")

;; ----- General variables -----
(defvar YaTeX-default-TeX-extensions "\\.\\(tex\\|sty\\)")
(defvar YaTeX-hierarchy-current-main nil)
(defvar YaTeX-hierarchy-buffer-message
  (concat
   "n)ext p)rev N)extsame P)revsame u)p K)illbuf RET)select"
   (if (and YaTeX-emacs-19 window-system) " Mouse2)select" "")
   " ?)help"))
(defvar YaTeX-hierarchy-saved-wc nil "Saved window configuration.")

;; ----- Functions for parsing hierarchy -----

(defun YaTeX-all-included-files (&optional file)
  "Return all included files from FILE as a list.
If FILE is nil, use current buffer."
  (save-excursion
    (let ((include-regex (concat YaTeX-ec-regexp
				 "\\(\\(input\\)\\|"		;match#2
				 "\\(include\\)\\)\\b"))	;match#3
	  list file (cb (current-buffer)))
      (if file (set-buffer (YaTeX-switch-to-buffer file t)))
      (goto-char (point-min))
      (while (YaTeX-re-search-active-forward
	      include-regex YaTeX-comment-prefix nil t)
	(cond
	 ((match-beginning 2)		;\input, {} is optional, 1 argument
	  (skip-chars-forward " {")
	  (setq file (buffer-substring
		      (point)
		      (progn
			(skip-chars-forward
			 (concat "^ \t\n\r" YaTeX-ec-regexp "{}"))
			(point)))))
	 ((and (match-beginning 3) (looking-at "{"))
	  (skip-chars-forward "{")
	  (setq file (buffer-substring
		      (point)
		      (progn
			(forward-char -1) (forward-list 1) (1- (point)))))))
	(or (string-match YaTeX-default-TeX-extensions file)
	    (setq file (concat file ".tex")))
	(setq list (cons file list)))
      (set-buffer cb)
      (nreverse list))))

(defun YaTeX-document-hierarchy (&optional file basedir)
  "Return the document hierarchy beginning from FILE as a list.
If FILE is nil, beginning with current buffer's file."
  (setq file (or file buffer-file-name))
  (and YaTeX-search-file-from-top-directory
       (not (file-exists-p file))
       (string-match "^[^/].*/" file)
       (setq file (expand-file-name file basedir)))
  (message "Parsing [%s]..." (file-name-nondirectory file))
  (prog1
      (save-excursion
	(if (or (file-exists-p file) (null file))
	    (progn
	      (if file
		  (let ((parent buffer-file-name))
		    (YaTeX-switch-to-buffer file t)	;set buffer to file
		    (or YaTeX-parent-file
			(YaTeX-get-builtin "!")
			(setq YaTeX-parent-file parent))))
	      (cons (buffer-file-name (current-buffer))
		    (mapcar (function 		;return value
			     (lambda (f)
			       (YaTeX-document-hierarchy f basedir)))
			    (YaTeX-all-included-files))))))
    (message "Parsing [%s]...done" (file-name-nondirectory file))))

;; ----- Functions for displaying hierarchy -----

(defun YaTeX-hierarchy-get-file-heading (file)
  "Get a FILE's heading."
  (save-excursion
    (set-buffer (find-file-noselect file))
    (save-excursion
      (let (p)
	(goto-char (point-min))
	(cond
	 ((re-search-forward
	   (concat YaTeX-ec-regexp YaTeX-sectioning-regexp) nil t)
	  (search-forward "{")
	  (forward-char -1)
	  (setq p (condition-case nil
		      (progn (forward-list 1) (1- (point)))
		    (error (point-end-of-line))))
	  (goto-char (1+ (match-beginning 0)))
	  (skip-chars-forward " \t\n")
	  (buffer-substring (point) (min (point-end-of-line) p)))
	 ((catch 'found
	    (while (re-search-forward "^ *%\\([^#]\\)" nil t)
	      (or (re-search-forward
		   YaTeX-hierarchy-ignore-heading-regexp
		   (point-end-of-line) t)
		  (throw 'found t))))
	  (beginning-of-line)
	  (search-forward "%")
	  (skip-chars-forward "% \t")
	  (buffer-substring (point) (point-end-of-line)))
	 (t ""))))))

(defun YaTeX-display-a-hierachy (hier level)
  "Put a HIER of document hierarchy.
LEVEL is including depth."
  (message "Formatting hierarchy buffer...")
  (let ((lastatomcol 0) list i p)
    (cond
     ((listp hier)
      (setq  list hier)
      (while list
	(YaTeX-display-a-hierachy (car list) (1+ level))
	(setq list (cdr list))))
     ((stringp hier)			;is an atom
      (insert "  ")
      (setq i level)
      (while (> i 2)
	(insert "|   ")
	(setq i (1- i)))
      (if (> level 1) (insert "+---"))
      (setq p (point))
      (insert (or (buffer-name (get-file-buffer hier))
		  (file-name-nondirectory hier)))
      (if (and window-system YaTeX-emacs-19)
	  (put-text-property p (point) 'mouse-face 'underline))
      (insert " ")
      (indent-to-column (1- (/ (window-width) 2)))
      (insert "% " (YaTeX-hierarchy-get-file-heading hier))
      (insert "\n"))))
  (message "Formatting hierarchy buffer..."))

(defun YaTeX-display-hierarchy (file &optional use-default)
  "Display document hierarchy that is beginning from FILE."
  (interactive "P")
  (setq YaTeX-hierarchy-saved-wc
	(list (current-window-configuration)
	      (and (featurep 'windows)
		   (boundp 'win:current-config)
		   win:current-config)))
  (let*((b-in (YaTeX-get-builtin "!"))
	default)
    ;;むーん↓このへんの仕様どうしたらいいか良く分からん...
    (if default (setq default (expand-file-name default)))
    (YaTeX-visit-main t)		;move to parent file
    (setq default buffer-file-name)
    (setq file
	  (or (if use-default default file)
	      (read-file-name
	       (format
		"Main .tex file%s: "
		(if default
		    (format "(default %s)"(file-name-nondirectory default))
		  ""))
	       "" default 1))))
  (setq file (expand-file-name file))
  (setq YaTeX-hierarchy-current-main file)
  (let ((dbuf "*document hierarchy*")
	(topdir default-directory))
    (YaTeX-showup-buffer dbuf nil t)
    (set-buffer (get-buffer dbuf))
    (setq truncate-lines t)
    (let ((buffer-read-only nil))
      (erase-buffer)
      (YaTeX-display-a-hierachy (YaTeX-document-hierarchy file topdir) 0))
    (goto-char (point-min))
    (YaTeX-hierarchy-next 0)
    (set-buffer-modified-p nil)
    (YaTeX-hierarchy-mode)
    ))

(defun YaTeX-display-hierarchy-directly ()
  "Same as YaTeX-display-hierarchy.  Call from mouse."
  (interactive)
  (YaTeX-display-hierarchy nil t))

(defun YaTeX-hierarchy-mode ()
  "Major mode to browse and select document hierarchy.

\\[YaTeX-hierarchy-next]	next line
\\[YaTeX-hierarchy-prev]	previous line
\\[YaTeX-hierarchy-forward]	move forward in the same level
\\[YaTeX-hierarchy-backward]	move backward in the same level
\\[YaTeX-hierarchy-up-document]	move to parent file
\\[delete-other-windows]	delete other windows
\\[other-window]	other window
\\[shrink-window]	shrink window
\\[enlarge-window]	enlarge window
\\[YaTeX-hierarchy-show]	show file contents in the next window
\\[YaTeX-hierarchy-scroll-up]	scroll up file contents buffer
\\[YaTeX-hierarchy-scroll-down]	scroll down file contents buffer
\\[YaTeX-hierarchy-top]	show the top of file contents
\\[YaTeX-hierarchy-bottom]	show the bottom of file contents
\\[YaTeX-hierarchy-lastpos]	return to the previous position
\\[YaTeX-hierarchy-select]	select file
\\[YaTeX-hierarchy-mouse-select]	select
"
  (setq major-mode 'YaTeX-hierarchy-mode
	mode-name "YaTeX hier")
  (use-local-map YaTeX-hierarchy-mode-map)
  (setq buffer-read-only t)
  (message YaTeX-hierarchy-buffer-message))

;; ----- Subfunctions for interactive functions -----
(defun YaTeX-hierarchy-get-current-file-buffer ()
  "Return the buffer associated with current line's file."
  (let ((file (buffer-substring
	       (point)
	       (save-excursion
		 (skip-chars-forward "^ \t" (point-end-of-line)) (point))))
	(hilit-auto-highlight) buffer)
    (set-buffer (find-file-noselect YaTeX-hierarchy-current-main))
    (if (get-buffer file)		;buffer is active
	(setq buffer (get-buffer file)) ;may contain `<2>'
      (if (string-match "<[2-9]>$" file)
	  (setq file (substring file 0 -3)))
      (save-excursion
	(setq buffer (YaTeX-switch-to-buffer file t)))))) ; open it!

;; ----- Interactive functions -----
(defun YaTeX-hierarchy-next (arg &optional quiet)
  "Move to next line's file in YaTeX document hierarchy buffer."
  (interactive "p")
  (forward-line arg)
  (skip-chars-forward "- +\\|")
  (if (and (/= arg 0) (not quiet))
      (YaTeX-hierarchy-select t))
  (message YaTeX-hierarchy-buffer-message))

(defun YaTeX-hierarchy-prev (arg)
  "Move to previous line's file in YaTeX document hierarchy buffer."
  (interactive "p")
  (YaTeX-hierarchy-next (- arg)))

(defun YaTeX-hierarchy-next-line (arg)
  (interactive "p")
  (YaTeX-hierarchy-next arg t))

(defun YaTeX-hierarchy-prev-line (arg)
  (interactive "p")
  (YaTeX-hierarchy-next (- arg) t))

(defun YaTeX-hierarchy-forward (arg)
  "Move to forward file in same hierarchy level."
  (interactive "p")
  (YaTeX-hierarchy-next 0)
  (let ((p (point))(column (current-column)) (i (if (> arg 0) arg (- arg))))
    (if (= column 0) (error "Not on file line."))
    (while (> i 0)
      (if (catch 'found
	    (while (and (not (eobp)) (not (bobp)))
	      (forward-line (if (> arg 0) 1 -1))
	      (move-to-column column)
	      (if (looking-at "[- +\\|]") nil
		(YaTeX-hierarchy-next 0)
		(if (= (current-column) column) (throw 'found t)))
	      (beginning-of-line)))
	  nil
	(goto-char p)
	(error "No same level file."))
      (setq i (1- i)))))

(defun YaTeX-hierarchy-backward (arg)
  "Move to backward file in same hierarchy level."
  (interactive "p")
  (YaTeX-hierarchy-forward (- arg)))

(defun YaTeX-hierarchy-up-document ()
  "Up level, that is, move to parent file position."
  (interactive)
  (YaTeX-hierarchy-next 0)		;adjust column
  (let ((p (point)) (line (count-lines (point-min) (point))) column)
    (if (or (<= line 1) (< (current-column) 6))
	(message "No more parent")
      (backward-char 1)
      (or (= (char-after (point)) ?-) (error "Unexpected hierarchy buffer"))
      (setq column (current-column))
      (while (and (> line 1) (looking-at "[- +\\|]"))
	(forward-line -1)
	(move-to-column column))
      (YaTeX-hierarchy-next 0)
      (push-mark p t)
      (message "Mark set to last position"))))

(defun YaTeX-hierarchy-kill-buffer (arg)
  "Kill buffer associated with current line's file."
  (interactive "p")
  (YaTeX-hierarchy-next 0)		;move to file name column
  (if (bolp) (error "Not on file name line"))
  (let ((file (buffer-substring
	       (point)
	       (progn (skip-chars-forward "^ \t") (point)))))
    (YaTeX-hierarchy-next arg)
    (cond
     ((get-buffer file)
      (kill-buffer (get-buffer file))
      (message "Buffer [%s] was killed" file))
     (t (message "Buffer [%s] is not active." file)))))

(defun YaTeX-hierarchy-select (arg)
  "Select current line's file in YaTeX document hierarchy buffer.
If ARG is non-nil, show the buffer in the next window."
  (interactive "P")
  (beginning-of-line)
  (skip-chars-forward "- +\\|")
  (or (eolp)
      (let ((buffer (YaTeX-hierarchy-get-current-file-buffer)))
	(if buffer			;if file was found
	    (if arg
		(YaTeX-showup-buffer buffer nil)
	      (if (and YaTeX-emacs-19 window-system
		       (get-buffer-window buffer t))
		  (goto-buffer-window buffer) ;select currently displaying
		(YaTeX-switch-to-buffer-other-window buffer)))))))

(defun YaTeX-hierarchy-show ()
  "Show current line's file in the next window."
  (interactive)
  (YaTeX-hierarchy-select t))

(defun YaTeX-hierarchy-mouse-select (event)
  (interactive "e")
  (mouse-set-point event)
  (YaTeX-hierarchy-select nil))

(defun YaTeX-hierarchy-quit ()
  "Quit from YaTeX-hierarchy buffer and restore window configuration."
  (interactive)
  (if (or (not (featurep 'windows))
	  (car YaTeX-hierarchy-saved-wc)
	  (and (= (car (cdr YaTeX-hierarchy-saved-wc)) win:current-config)))
      (set-window-configuration (car YaTeX-hierarchy-saved-wc))
    (bury-buffer nil)))

(defun YaTeX-hierarchy-scroll-up (arg &optional action)
  "Scroll up file contents of YaTeX-hierarchy."
  (interactive "P")
  (YaTeX-hierarchy-next 0 t)
  (let*((bufname (buffer-substring
		  (point)
		  (save-excursion (skip-chars-forward "^ \t") (point))))
	(buf (get-buffer bufname))
	(cw (selected-window)))
    (cond
     ((and buf (get-buffer-window buf))
      (select-window (get-buffer-window buf)))
     ((and buf (YaTeX-showup-buffer buf nil t)) t)
     (t (YaTeX-hierarchy-select nil)))
    (unwind-protect
	(cond
	 ((eq action 'down)	(scroll-down arg))
	 ((eq action 'top)	(beginning-of-buffer))
	 ((eq action 'bottom)	(end-of-buffer))
	 ((eq action 'last)	(exchange-point-and-mark))
	 (t (scroll-up arg)))
      (select-window cw))))

(defun YaTeX-hierarchy-scroll-down (arg)
  "Scroll down file contents of YaTeX-hierarchy."
  (interactive "P")
  (YaTeX-hierarchy-scroll-up arg 'down))

(defun YaTeX-hierarchy-top ()
  "Show the top of YaTeX-hierarchy inspection buffer's."
  (interactive)
  (YaTeX-hierarchy-scroll-up nil 'top)
)

(defun YaTeX-hierarchy-bottom ()
  "Show the top of YaTeX-hierarchy inspection buffer's."
  (interactive)
  (YaTeX-hierarchy-scroll-up nil 'bottom)
)

(defun YaTeX-hierarchy-lastpos ()
  "Go to last position in YaTeX-hierarchy buffer."
  (interactive)
  (YaTeX-hierarchy-scroll-up nil 'last)
)

;; ----- Setting up keymap -----
(defvar YaTeX-hierarchy-mode-map nil "Keymap used in YaTeX-hierarchy-mode.")
(if YaTeX-hierarchy-mode-map nil
  (setq YaTeX-hierarchy-mode-map (make-sparse-keymap))
  (define-key YaTeX-hierarchy-mode-map "n"	'YaTeX-hierarchy-next)
  (define-key YaTeX-hierarchy-mode-map "p"	'YaTeX-hierarchy-prev)
  (define-key YaTeX-hierarchy-mode-map "j"	'YaTeX-hierarchy-next-line)
  (define-key YaTeX-hierarchy-mode-map "k"	'YaTeX-hierarchy-prev-line)
  (substitute-all-key-definition
   'next-line 'YaTeX-hierarchy-next-line YaTeX-hierarchy-mode-map)
  (substitute-all-key-definition
   'previous-line 'YaTeX-hierarchy-prev-line YaTeX-hierarchy-mode-map)
  (define-key YaTeX-hierarchy-mode-map "N"	'YaTeX-hierarchy-forward)
  (define-key YaTeX-hierarchy-mode-map "P"	'YaTeX-hierarchy-backward)
  (define-key YaTeX-hierarchy-mode-map "u"	'YaTeX-hierarchy-up-document)
  (define-key YaTeX-hierarchy-mode-map "K"	'YaTeX-hierarchy-kill-buffer)
  (define-key YaTeX-hierarchy-mode-map "1"	'delete-other-windows)
  (define-key YaTeX-hierarchy-mode-map "o"	'other-window)
  (define-key YaTeX-hierarchy-mode-map "-"	'shrink-window)
  (define-key YaTeX-hierarchy-mode-map "+"	'enlarge-window)
  (define-key YaTeX-hierarchy-mode-map "."	'YaTeX-hierarchy-show)
  (define-key YaTeX-hierarchy-mode-map " "	'YaTeX-hierarchy-scroll-up)
  (define-key YaTeX-hierarchy-mode-map "b"	'YaTeX-hierarchy-scroll-down)
  (define-key YaTeX-hierarchy-mode-map "\C-?"	'YaTeX-hierarchy-scroll-down)
  (define-key YaTeX-hierarchy-mode-map "\C-m"	'YaTeX-hierarchy-select)
  (define-key YaTeX-hierarchy-mode-map "<"	'YaTeX-hierarchy-top)
  (define-key YaTeX-hierarchy-mode-map ">"	'YaTeX-hierarchy-bottom)
  (define-key YaTeX-hierarchy-mode-map "'"	'YaTeX-hierarchy-lastpos)
  (define-key YaTeX-hierarchy-mode-map "g"	'YaTeX-hierarchy-select)
  (define-key YaTeX-hierarchy-mode-map "q"	'YaTeX-hierarchy-quit)
  (define-key YaTeX-hierarchy-mode-map "?"	'describe-mode)
  (if (and YaTeX-emacs-19 window-system)
      (define-key YaTeX-hierarchy-mode-map
	[mouse-2] 'YaTeX-hierarchy-mouse-select))
  )

(provide 'yatexhie)
;;end of yatexhie.el
