;; File : ZeBuLoN.el
;; This is emacs' config file for ZeBuLoN input files. 
;; To install it, 
;;    - either add the command (load "path_to_this_file/ZeBuLoN") in your ~/.emacs file.
;;    - or both commands : 
;;        (setq load-path (append (list "path_to_the_file") load-path))
;;        (load "ZeBuLoN")
;;    - or use the 'autoload on demand' mechanism (not documented here) 
;;
;;
;; JDG  12/06/2005
;; Last modif 13 jan. 2006

;; The main inspiration when writing this mode came from 
;; http://www.emacswiki.org/ (esp. define-generic-mode and defface)
;; http://two-wugs.net/emacs/mode-tutorial.html
;; http://www.gnu.org/software/emacs/#Manuals  (emacs manual and elisp reference)
;; http://www.cs.elte.hu/local/texinfo/emacs/emacs_16.html#SEC85 (regexp and syntax tables in emacs)
;; and of course from emacs' help (C-h) and (C-h f)


;;; Todo-list : 
;;        add  M-n='goto-next-n-stars-block' and 'M-previous' ... 
;;        failproof the indent function
;;        complete the list of keywords
;;        rewrite this as a derived mode, and add a syntax list (is this really useful ?)
;;        add an indent-region-function (see elisp-reference manual, sec 32.17.3)
;;        see what can be done with "time" and "*time" and U1 R2 W3
;;        add dark-background colors, etc ... 
;;        teach it the default values of some instructions (?), so that it suggests newton after a ***resolution
;;        C-c C-e : inserts a-la-LaTeX a bloc ****mesher with its default options !!!
;;        M-o pour ouvrir le fichier materiau ou geof qui est sous le curseur
;;        add RU1-3 as reactions, in a slightly different color from R1-3 (say font-lock-DOFs-face and font-lock-derived-variable-face)
;;        'help-on-keyword should open the manual at the right page 
;;                         and not open an *Async shell output* (semi-fixed)
;;        complete the 'local-set-key list
;;        look up the load-on-demand ...
;;        add a Z-menu 
;;        add a (M-x Zrun) command (detects whether its a -m , -o , -g .... )  ==> (partly) done, see bug list and merge it with the (M-x Zmaster) command
;;        improve the 'open' command by something like (mouse-face highlight help-echo "M-o: visit this file in other window")  (from dired.el:745) 
;;        see what can be done with completing-read  (proposes a choice list ...) 
;;        make a "table of contents", à la reftex
;;        lancer une regexp-optimize sur la liste de keywords ; mettre une couleur differente pour les keywords peu importants. 

;;; Bug-list :
;;        it looks like there's a problem with <TAB> chars : if a buffer is opened in Z-mode, other buffers (e.g. fundamental ones) also apply this "replace TAB by space" ... 
;;        regexp seem to work differently on SGI-Unix (has got emacs 20.4.2)
;;        pretty-print function is not working 
;;        handle an error return from the function guess-option-for-Zrun (more precisely from the search for ****) 
;;        


;;;; code starts here

(defalias 'zebulon-mode 'ZeBuLoN-mode)

;; .z7p files should be highlighted as c++ :
(add-to-list 'auto-mode-alist '("\\.z7p\\'" . c++-mode))


(define-generic-mode 'ZeBuLoN-mode    ;; see "M-x help f define-generic-mode" for help on this command
  '("%" "#")    ;; comment characters ; according to zman user, "//" is not a comment
  '("ALL_ELEMENT" "ALL_NODE" 
    "newton" "eeeeee" "p1p1p1" "p1p2p2" "p1p2p3" 
    "TP" "PR" "EZ" 
    "plane_stress" "plane_strain"
    "absolu" "classic_updated_lagrangian" "linear_elastic" "gen_evp" "lagrange_rotate_no_J"
    )    ;; list of keywords to highlight.  
  '( ;; patterns to highlight
    ("\t" . 'highlight)  ;; dangerous <TAB> characters are highlighted
    ("\\(func\\(tion\\)? \\)\\(.*;\\)" 3 'font-lock-ZeBuLoNfunc-face keep t)          ;; see elisp-reference sec 23.5.2 (explains the '3' keep and t )
    ("\\*\\{4\\} *[[:alnum:]_]+ *[[:alnum:]_]*" . 'font-lock-ZeBuLoN4star-face) ;;****keywords
    ("\\*\\{3\\} *[[:alnum:]_]+" . 'font-lock-ZeBuLoN3star-face) ;; highlighting  ***keywords
    ("\\*\\{2\\} *[[:alnum:]_]+" . 'font-lock-ZeBuLoN2star-face) ;; highlighting   **keywords
    ("\\*\\{1\\} *[[:alnum:]_]+" . 'font-lock-ZeBuLoN1star-face) ;; highlighting   **keywords
;    ("\\*\\{1\\} *\\w+" . 'font-lock-ZeBuLoN1star-face) ;; highlighting    *keywords ... \\w+ means "a whole word" (this is based on the syntax-table)
;old version :  ("^ *\\*\\{1\\} *[[:alnum:]_]+" . 'font-lock-ZeBuLoN1star-face) ;; highlighting    *keywords
    ("\\<time\\>" . 'font-lock-keyword-face) ;; si je le mets dans la liste de keywords (cad en tete), *time est mal colore ; \\<...\\> signifie "mots complets"
    ("\\<constant\\>" . 'font-lock-keyword-face) ;; idem
    ("\\<[URW][1-3]\\>" . 'font-lock-keyword-face) ; DOF names (cf. zman user, p.326 DOFs). Dans la liste des keywords, une regexp ne passe pas ?
    )
  '(".inp\\'" ".geof\\'" ".mat\\'" ".mast\\'")   ;; file-names patterns
  (list  ;; additional setup functions : 
   (lambda () (set (make-local-variable 'indent-tabs-mode) nil) )   ;; replace <TAB> by spaces
;;;   (lambda () (setq default-tab-width 1))   ;; size of indentation
   (lambda () (set (make-local-variable 'tab-width) 1))
   (lambda () (set (make-local-variable 'indent-line-function) 'ZeBuLoN-indent-line))
   (lambda () (local-set-key [f1] 'ZeBuLoN-help-on-keyword))   
   (lambda () (local-set-key "\M-o" 'ZeBuLoN-find-file-under-cursor))

   (lambda () (setq mode-name "ZeBuLoN"))
;   (lambda () (modify-syntax-entry ?_ "w"))  ; on lui apprend que l'underscore est un caractere constituant les mots
   (lambda () (message "Welcome to ZeBuLoN mode. Type 'C-h m' to get some help."))
   )
  "Major mode for editing ZeBuLoN files (.inp .geof .mat). 
Version 1.0  (12 june 2005)

Features : 
   - highlights ***keywords
   - puts functions in italics (requires the terminating semicolon ;) 
   - indentation
   - recognizes a few common keywords
   - etc ... (see features.inp)

Please report bugs and suggest improvements to jean-didier[dot]garaud[at]onera[dot]fr
"
  )




;;; the next section is about indentation :


(defun ZeBuLoN-line-is-blank ()
  "Returns t if line is blank ... the function should already exist, but I couldn't find it !"
  (save-excursion
    (beginning-of-line)
    (looking-at "^[[:blank:]]*$")
    )
  )

(defun ZeBuLoN-line-is-a-comment ()
  "Returns t if line is a comment ... again, the function should already exist, but I couldn't find it !"
  (save-excursion
    (beginning-of-line)
    (looking-at "^[[:blank:]]*\\(%\\|#\\)") 
;    (looking-at "^[[:blank:]]*%")   ;originally 
    )
)


(defun ZeBuLoN-count-stars ()
  "Returns the number of stars at the beginning of the current line."
;  (interactive)
  (save-excursion
    (beginning-of-line)
    (cond 
     ( (looking-at "^ *\\*\\{4\\}") 4 )
     ( (looking-at "^ *\\*\\{3\\}") 3 )
     ( (looking-at "^ *\\*\\{2\\}") 2 )
     ( (looking-at "^ *\\*\\{1\\}") 1 )
     ( t 0 )
     )
    )
  )

(defun ZeBuLoN-indent-line-simple ()
  "A simple 'indent current line' for ZeBuLoN files, according to the number of stars."
  (interactive)  ;; means i can do M-x ZeBuLoN-indent-line-simple in emacs
  (save-excursion  ;;keep cursor position while indenting 
    (indent-line-to (- 4 (ZeBuLoN-count-stars))) 
    )
  )


(defun ZeBuLoN-indent-line ()
  "Indent current line for ZeBuLoN files, aligning it with the previous same star-level entry."
  (interactive)
  (when (not (ZeBuLoN-line-is-a-comment))  ;; we do not want to indent comment lines  ( (when ..) is an (if..) without the 'else' part)
    (if (ZeBuLoN-line-is-blank) 
	(indent-line-to 0)  ; <- if line is blank 
			    ; else : 
      (let ((cur-indent 0) (not-indented t)  (star-lvl (ZeBuLoN-count-stars)) ) 
	(save-excursion
	  (beginning-of-line)
	  (while (and not-indented (not (bobp)))
	    (forward-line -1)
	    (when (and (not (ZeBuLoN-line-is-blank)) (not (ZeBuLoN-line-is-a-comment)) (<= star-lvl (ZeBuLoN-count-stars)))    ; we ignore blank and comment lines 
	      (setq not-indented nil)
	      (setq cur-indent (+ (current-indentation) (* tab-width (- (ZeBuLoN-count-stars) star-lvl))))
	      ;;		(message "indenting to %d" cur-indent)
	      )
	    )
	  ); end of save-excursion, so we return to the actual line 
	(save-excursion (indent-line-to cur-indent) )   ; do the actual indenting ; the save-excursion keeps the cursor under the correct character
	) ; end of let 
      )
    )
  )



;;; the next section is about help and Zman 

(defun ZeBuLoN-keyword-under-cursor ()
  "Returns the word under the cursor, presuming it is a Z-keyword (i.e. only contains  [*\-a-zA-Z0-9/_.] characters). 
The function certainly already exists somewhere ? (yes : it's called current-word ) "
;  (interactive)
  (save-excursion 
    (let (
	  (beg (+ (point) (skip-chars-backward "a-zA-Z0-9_.")))
	  (end (+ (point) (skip-chars-forward  "a-zA-Z0-9_.")))
	  )
      (buffer-substring-no-properties beg end)
      )
    )
  )


(defun ZeBuLoN-help-on-keyword ()
  "Runs zman user, and (not yet) opens a 'find' window containing the current word ... pas encore au point ... "
  (interactive)
  (message "Looking for help on keyword : %s" (ZeBuLoN-keyword-under-cursor))

  (shell-command "zman user &")   ; todo : recherche le mot courant 
  (delete-other-windows)  ; pour ne pas que la fenetre Asynx shell output n'apparaisse ... pas encore au point, si 2fenetres etaient ouvertes

)



;;; the next section is about sub-files quick opening 

(defun ZeBuLoN-filename-under-cursor ()
  "Returns the word under the cursor, presuming it is a filename."
;  (interactive) 
  (save-excursion 
    (let (
	  (beg (+ (point) (skip-chars-backward "\-a-zA-Z0-9/~_.")))
	  (end (+ (point) (skip-chars-forward  "\-a-zA-Z0-9/~_.")))
	  )
      (buffer-substring-no-properties beg end) 
      )
    )
  )

(defun ZeBuLoN-find-file-under-cursor ()
  "Finds the file whose name is under the cursor (e.g. the geof or material file)."
  (interactive) 
  (let (
        (filename (ZeBuLoN-filename-under-cursor))
        (buffername (file-name-nondirectory (ZeBuLoN-filename-under-cursor)))   ; (file-name-nondirectory filename) ne passe pas : cf l'aide de (let ...) : les variables sont initialisees en qq sorte d'un bloc, et pas les unes apres les autres .  
        )
    (if (buffer-live-p (get-buffer buffername))
        (progn 
          (message "Displaying %s" buffername)
          (display-buffer buffername)  ; better than pop-buffer, because focus remains on the first file
          )
      (if (file-readable-p filename)
          (progn
;            (find-file-other-window filename)
            (find-file-noselect filename)
            (display-buffer buffername) 
            (message "Opening %s. Type C-x 1 to hide new window." (file-truename filename))
            )
        (message "File '%s' doesn't exist or is not readable (in directory %s)" filename (file-name-directory (file-truename filename))) 
        )
      )
    )
)




;;; the next section is about the Zrun command : 


(defun Zrun () 
  "Zruns the current file, trying to guess the right options. "
  (interactive)  ; todo : ajouter un prompt argument pour les command line options ; 
     ; apparemment la doc de l'emacs refcard a un exemple qui utilise les prefix : (interactive "P") ... (prefix-numeric-value delargument)
     ; voir aussi l'aide de prefix-numeric-value
  (let ( 
        (lacommande (concat "Zrun " (ZeBuLoN-select-right-option-for-Zrun) (buffer-name) " &" ) )
        )
    (when (buffer-modified-p)
      (when (y-or-n-p "Save current buffer? ")
        (save-buffer)
        )
      )
    (shell-command  (read-from-minibuffer "Run command: " lacommande)  (concat "*" lacommande " output*")  )
    )
  ) 

(defun Zmaster () 
  "Zmasters the current file, trying to guess the right options. "
  (interactive)  ; todo : ajouter un prompt argument pour les command line options ; 
     ; apparemment la doc de l'emacs refcard a un exemple qui utilise les prefix : (interactive "P") ... (prefix-numeric-value delargument)
     ; voir aussi l'aide de prefix-numeric-value
  (let ( 
        (lacommande (concat "Zmaster " (buffer-name) " &" ) )
        )
    (when (buffer-modified-p)
      (when (y-or-n-p "Save current buffer? ")
        (save-buffer)
        )
      )
    (shell-command  (read-from-minibuffer "Run command: " lacommande)  (concat "*" lacommande " output*")  )
    )
  ) 



(defun ZeBuLoN-type-of-inp-file ()
  "Returns the first ****command it finds"
  ;; ici, en utilisant (file-name-extension (buffer-name) t), je pourrais verifier que j'ai bien un fichier .inp ... 
  (save-excursion 
    (goto-char (point-min))          ; goes to the beginning of file
    (search-forward "****")
    (ZeBuLoN-filename-under-cursor)  ; happens to do exactly what i need ... current-word should also be ok ... 
    )
  )

(defun ZeBuLoN-select-right-option-for-Zrun ()
  "This function knows the right option for each type of .inp file, in order too provide it to `Zrun'."
  (let (
        (ftype (ZeBuLoN-type-of-inp-file))
        )
    (cond ;;;; i should make a list of correspondance btw types and options to be more emacs-like
     ((string-equal ftype "calcul") "" )
     ((string-equal ftype "mesher") "-m ")  ; l'espace est important !
     ((string-equal ftype "optimize") "-o ") 
     ((string-equal ftype "post_processing") "-pp ") 
     (t (message "This type of .inp file is not recognized. Sorry, but I cannot suggest the right option for Zrun.") "" )  ;; should add a message to tech emacs ? 
     )
    )
  )





;;; the next sectio is about printing ... but is buggy right now

;; (defun ZeBuLoN-pretty-print ()
;;   "Prints to file, using font-faces"
;;   (interactive)
;;   (ps-print-buffer-with-faces "~/toto.ps") 
;; )
  



;;; the next section is about COLORS !!

; M-x list-colors-display to get a list of available colors... 
; can also be customized from emacs : M-x customize-face 

(defface font-lock-ZeBuLoN4star-face
  '(
;    ( ((background dark)) (:foreground "SeaGreen3" :bold t) )
    ( ((background dark)) (:foreground "Gold" :bold t) )
    ( t (:foreground "DarkBlue" :bold t )) 
    )
  "Font Lock mode face used to highlight ****keywords."
  :group 'font-lock-highlighting-faces)

(defface font-lock-ZeBuLoN3star-face
  '( 
    ( ((background dark)) (:foreground "chartreuse") )
    (t (:foreground "Blue" )) 
    )
  "Font Lock mode face used to highlight ***keywords."
  :group 'font-lock-highlighting-faces)

(defface font-lock-ZeBuLoN2star-face
  '(
    ( ((background dark)) (:foreground "aquamarine") )   ; "deep sky blue"
    (t (:foreground "DarkGreen")) 
    )
  "Font Lock mode face used to highlight **keywords."
  :group 'font-lock-highlighting-faces)

(defface font-lock-ZeBuLoN1star-face
  '(
    ( ((background dark)) (:foreground "SkyBlue1") ) ; "LightSeaGreen"
    (t (:foreground "DarkCyan")) 
    )
  "Font Lock mode face used to highlight *keywords."
   :group 'font-lock-highlighting-faces)

(defface font-lock-ZeBuLoNfunc-face
  '(
    (((class color) (background light)) (:foreground "DimGrey" :italic t))
    (((class color) (background dark)) (:foreground "Light Grey" :italic t))
    (t (:italic t))
    )
  "Font Lock mode face used to highlight functions."
  :group 'font-lock-highlighting-faces)


;;;; and code finishes here