yatex / yatexhlp.el
;;; yatexhlp.el --- YaTeX helper for LaTeX -*- coding: sjis -*-
;;; (c)1994,1998,2004,2014,2015 by HIROSE Yuuji.[]
;;; Last modified Sun Sep 17 10:23:19 2017 on firestorm
;;; $Id$

;;; Code:
(let ((help-file (concat "YATEXHLP."
			 (cond (YaTeX-japan "jp")
			       (t "eng"))))
	((and (boundp 'site-directory) site-directory) site-directory)
	((string-match "\\.app/" doc-directory)	;For
	 (expand-file-name "../site-lisp/yatex" doc-directory))
	 (expand-file-name "../../site-lisp/yatex" doc-directory))
	(t exec-directory))))
  (defvar YaTeX-help-file
    (expand-file-name help-file help-dir)
    "*Help file of LaTeX/TeX commands or macros.")
  (defvar YaTeX-help-file-private
    (expand-file-name (concat "~/" help-file))
    "*Private help file of LaTeX/TeX macros."))

(defvar YaTeX-help-delimiter "\C-_" "Delimiter of each help entry.")
(defvar YaTeX-help-entry-map (copy-keymap YaTeX-mode-map)
  "Key map used in help entry.")
(defvar YaTeX-help-file-current nil
  "Holds help file name to which the description in current buffer should go.")
(defvar YaTeX-help-command-current nil
  "Holds command name on which the user currently write description.")
(defvar YaTeX-help-saved-config nil
  "Holds window configruation before the editing of manual.")
(defvar YaTeX-help-synopsis
  (cond (YaTeX-japan "【書式】")
	(t "[[ Synopsis ]]"))
  "Section header of synopsis.")
(defvar YaTeX-help-description
  (cond (YaTeX-japan "【説明】")
	(t "[[ Description ]]"))
  "Section header of description.")

(defvar YaTeX-help-mode-map nil "Keymap used in YaTeX-help buffer")
(if YaTeX-help-mode-map nil
  (setq YaTeX-help-mode-map (make-sparse-keymap))
  (let ((map YaTeX-help-mode-map))
    (suppress-keymap map)
    (define-key map "j" (function (lambda () (interactive) (scroll-up 1))))
    (define-key map "k" (function (lambda () (interactive) (scroll-up -1))))
    (define-key map "n" 'next-line)
    (define-key map "p" 'previous-line)
    (define-key map " " 'scroll-up)
    (define-key map "\C-?" 'scroll-down)
    (define-key map "o" 'other-window)
    (define-key map "h" 'describe-bindings)
    (define-key map "q" 'YaTeX-help-quit)
    (define-key map "<" 'beginning-of-buffer)
    (define-key map ">" 'end-of-buffer)))

(defun YaTeX-help-quit ()
  "Close help and return to privious buffer"
  (bury-buffer (current-buffer))
  (set-window-configuration YaTeX-help-saved-config))

(defvar YaTeX-help-reference-regexp "<refer\\s +\\([^>]+\\)>"
  "Regexp of reference format of YaTeX-help file.")
(defvar YaTeX-help-buffer "** YaTeX HELP **" "Help buffer name for yatexhlp")

(defun YaTeX-help-entries ()
  "Return the alist which contains all the entries in YaTeX-help file."
  (let (entries entry)
	(lambda (help)
	  (if (file-exists-p help)
		(set-buffer (find-file-noselect help))
		  (goto-char (point-min))
		  (while (re-search-forward
			  (concat "^" (regexp-quote YaTeX-help-delimiter)
				  "\\(.+\\)$") nil t)
		    (setq entry (buffer-substring
				 (match-beginning 1) (match-end 1)))
		    (or (assoc entry entries)
			(setq entries (cons (list entry) entries)))))))))
       (list YaTeX-help-file YaTeX-help-file-private)))

(defvar YaTeX-help-entries nil
  "Helo entries alist.")
(setq YaTeX-help-entries (YaTeX-help-entries))

(defun YaTeX-help-resolve-reference (buffer1 buffer2 &optional done-list)
  "Replace reference format in buffer1 with refered contents in buffer2."
  (let (ref ref-list beg end)
      (switch-to-buffer buffer1)
      (goto-char (point-min))
      (while (re-search-forward YaTeX-help-reference-regexp nil t)
	(setq ref (buffer-substring (match-beginning 1) (match-end 1))
	      ref-list (cons (list ref) ref-list))
	(replace-match "")
	(if (assoc ref done-list) nil	;already documented.
	  (switch-to-buffer buffer2)
	    (goto-char (point-min))
	    (if (re-search-forward
		 (concat (regexp-quote YaTeX-help-delimiter)
			 (regexp-quote ref)
			 "$") nil t)
		  (setq beg (progn (forward-line 2) (point))
			end (progn
			       (concat "^" (regexp-quote YaTeX-help-delimiter))
			       nil 1)
			      (goto-char (match-beginning 0))
			      (forward-line -1)
			      (while (and (bolp) (eolp) (not (bobp)))
				(forward-char -1))
		  (switch-to-buffer buffer1)
		  (insert-buffer-substring buffer2 beg end))))
	  (switch-to-buffer buffer1)))
      (if beg (YaTeX-help-resolve-reference
	       buffer1 buffer2 (append done-list ref-list))))))

(defun YaTeX-refer-help (command help-file &optional append)
  "Refer the COMMAND's help into HELP-FILE.
\[Help-file format\]
<DELIM><LaTeX/TeX command without escape character(\\)><NL>
Where:	<DELIM> is the value of YaTeX-help-delimiter.
	<NL> is newline.
	<TERM> is newline or end of buffer."
  (let ((hfbuf (find-file-noselect help-file))
	(hbuf (get-buffer-create YaTeX-help-buffer))
	(curwin (selected-window))
	sb se db de)
    (set-buffer hfbuf)
    (goto-char (point-min))
    (if (null
	 (let ((case-fold-search nil))
	    (concat (regexp-quote YaTeX-help-delimiter)
		    (regexp-quote command)
		    "$") nil t)))
	nil				;if not found, return nil
      (forward-line 1)
      (setq sb (point)
	    se (progn (forward-line 1) (point))
	    db (point)
	    de (progn
		  (concat "^" (regexp-quote YaTeX-help-delimiter)) nil 1)
		 (- (point) (length YaTeX-help-delimiter))))
       hbuf 'YaTeX-showup-buffer-bottom-most t)
      (set-buffer hbuf)
      (setq buffer-read-only nil)
      (if append (goto-char (point-max)) (erase-buffer))
      (insert YaTeX-help-synopsis "\n")
      (insert-buffer-substring hfbuf sb se)
      (insert "\n" YaTeX-help-description "\n")
      (insert-buffer-substring hfbuf db de)
      (YaTeX-help-resolve-reference hbuf hfbuf (list (list command)))
      (goto-char (point-min))
      (setq buffer-read-only t)
      (set-buffer-modified-p nil)
      (select-window curwin)

(defun YaTeX-help-mode ()
  (use-local-map YaTeX-help-mode-map)
  (setq major-mode 'yatex-help-mode
	mode-name "YaTeX-HELP"))

(defun YaTeX-help-newline (&optional arg)
  (interactive "P")
  (if (and (= (current-column) 1) (= (preceding-char) ?.) (eolp))
      (let ((cbuf (current-buffer)))
	(delete-region (point) (progn (forward-line 1) (point)))
	   YaTeX-help-command-current YaTeX-help-file-current))
	(set-window-configuration YaTeX-help-saved-config)
	(bury-buffer cbuf))
    (newline arg)))

(defun YaTeX-help-add-entry (command help-file)
  (let ((hfbuf (find-file-noselect help-file))
	(dbuf (current-buffer)) beg end)
    (goto-char (point-min))
    (re-search-forward (concat "^" (regexp-quote YaTeX-help-synopsis)))
    (forward-line 1)	(setq beg (point))
    (end-of-line)	(setq end (point))
    (set-buffer hfbuf)
    (goto-char (point-min))
    (insert YaTeX-help-delimiter command "\n")
    (insert-buffer-substring dbuf beg end)
    (insert "\n")
    (set-buffer dbuf)
    (re-search-forward (concat "^" (regexp-quote YaTeX-help-description)))
    (forward-line 1)
    (setq beg (point))
    (setq end (point-max))
    (set-buffer hfbuf)
    (insert-buffer-substring dbuf beg end)
    (insert "\n\n")
    (forward-line -1)
    (let ((make-backup-files t))
    (bury-buffer hfbuf)
    (setq YaTeX-help-entries (cons (list command) YaTeX-help-entries))))

(defun YaTeX-help-prepare-entry (command help-file)
  "Read help description on COMMAND and add it to HELP-FILE."
  (let ((buf (get-buffer-create "**Description**"))
	(conf (current-window-configuration)))
     buf 'YaTeX-showup-buffer-bottom-most t)
    (make-local-variable 'YaTeX-help-file-current)
    (make-local-variable 'YaTeX-help-command-current)
    (make-local-variable 'YaTeX-help-saved-config)
    (setq YaTeX-help-file-current help-file
	  YaTeX-help-command-current command
	  YaTeX-help-saved-config conf
	  mode-name "Text"
	  major-mode 'text)
    (insert YaTeX-help-synopsis "\n\n" YaTeX-help-description "\n\n")
    (define-key YaTeX-help-entry-map "\r" 'YaTeX-help-newline)
    (use-local-map YaTeX-help-entry-map)
     (cond (YaTeX-japan "入力を終えたら . のみ入力してRET")
	   (t "Type only `.' and RET to exit.")))))

(defun YaTeX-enrich-help (command)
  "Add the COMMAND's help to help file."
  (if (y-or-n-p (format "No help on `%s'. Create help?" command))
       (if (y-or-n-p "Add help to global documentation?")
	   YaTeX-help-file YaTeX-help-file-private))))

(defun YaTeX-help-sort (&optional help-file)
  "Sort help file HELP-FILE.
If HELP-FILE is nil or called interactively, sort current buffer
as a help file."
  (if help-file (set-buffer (find-file-noselect help-file)))
   nil "\\(\\sw+\\)\\([^]+\\|\\s'\\)" "\\1" (point-min) (point-max)))

(defun YaTeX-apropos-file (keyword help-file &optional append)
  (let ((hb (find-file-noselect help-file))
	(ab (get-buffer-create YaTeX-help-buffer))
	(sw (selected-window))
	(head (concat "^" (regexp-quote YaTeX-help-delimiter)))
	pt command)
    (YaTeX-showup-buffer ab 'YaTeX-showup-buffer-bottom-most)
    (select-window (get-buffer-window ab))
    (set-buffer ab)			;assertion
    (setq buffer-read-only nil)
    (or append (erase-buffer))
    (set-buffer hb)
    (goto-char (point-min))
    (while (re-search-forward keyword nil t)
      (setq pt (point))
      (re-search-backward head nil t)
      (setq command (buffer-substring (match-end 0) (point-end-of-line)))
      (switch-to-buffer ab)
      (goto-char (point-max))
      (insert-char ?- (1- (window-width)))
      (insert (format "\n<<%s>>\n" command))
      (YaTeX-refer-help command help-file t) ;append mode
      (setq buffer-read-only nil)
      (set-buffer hb)
      (goto-char pt)
      (if (re-search-forward head nil 1)
	  (goto-char (1- (match-beginning 0)))))
    (setq buffer-read-only t)
    (select-window sw)

(defun YaTeX-apropos (key)
  (interactive "sLaTeX apropos (regexp): ")
  (if (string= "" key) (error "Nothing to show"))
  (setq YaTeX-help-saved-config (current-window-configuration))
  (or (YaTeX-apropos-file key YaTeX-help-file)
      (YaTeX-apropos-file key YaTeX-help-file-private t)
      (message "No matches found.")))

(defun YaTeX-help (&optional macro)
  "Show help buffer of LaTeX/TeX commands or macros.
Optional argument MACRO, if supplied, is directly selected to keyword."
  (let (p beg end command)
      (if (looking-at YaTeX-ec-regexp)
	  (goto-char (match-end 0)))
      (setq p (point))			;remember current position.
       (macro nil)
	;;if on \begin or \end, extract its environment.
	(setq command
	      (cond ((match-beginning 1)
		     (buffer-substring (match-beginning 1) (match-end 1)))
		    ((match-beginning 2)
		     (buffer-substring (match-beginning 2) (match-end 2))))))
       ((search-backward YaTeX-ec (point-beginning-of-line) t)
	(goto-char (setq beg (match-end 0)))
	(re-search-forward YaTeX-TeX-token-regexp (point-end-of-line) t)
	(setq end (point))
	(if (and (<= beg p) (<= p end))
	    (setq command (buffer-substring beg end)))))
      (if (or (string= command "begin") (string= command "end"))
	    (search-forward "{" (point-end-of-line))
	    (setq beg (point))
	    (search-forward "}" (point-end-of-line))
	    (setq command (buffer-substring beg (match-beginning 0)))))
      (setq command
	    (or macro
		 "Describe (La)TeX command: "
		 YaTeX-help-entries nil nil command))));end excursion

    (setq YaTeX-help-saved-config (current-window-configuration))
    (or (YaTeX-refer-help command YaTeX-help-file)
	(YaTeX-refer-help command YaTeX-help-file-private)
	(YaTeX-enrich-help command))))