;; octave-mode.el - major mode for Octave function files
;;
;; Copyright (C) 1994 John W. Eaton
;; 
;; This file is part of Octave.
;; 
;; Octave 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 2, or (at your option) any
;; later version.
;; 
;; Octave 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 Octave; see the file COPYING.  If not, write to the Free
;; Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
;; 
;; This major mode for GNU emacs provides support for editing Octave
;; function files.  It requires matlab-mode.el, which was written by
;; Matthew R. Wette <mwette@csi.jpl.nasa.gov>.
;;
;; It automatically indents block structures, line continuations
;; (e.g., ...), and comments.  The usual paren matching support is
;; included.  Filling and auto-fill works for comment lines.  For
;; convenient use, put this file and matlab.el in your Emacs
;; load-path and add something like the following to your .emacs
;; start-up file:
;;
;;   (autoload 'octave-mode "octave-mode" "Enter Octave mode." t)
;;   (setq auto-mode-alist (cons '("\\.m$" . octave-mode) auto-mode-alist))
;;   (defun my-octave-mode-hook ()
;;     (setq fill-column 76)
;;     (turn-on-auto-fill))
;;   (setq octave-mode-hook 'my-octave-mode-hook)
;;
;; For Lucid Emacs, add ``(font-lock-mode 1)'' to your octave-bmode-hook.

;; Customize Matlab mode for Octave users.

(setq matlab-comment-column 0)
(setq matlab-comment-line-s "# ")
(setq matlab-comment-on-line-s "# ")
(setq matlab-comment-region-s "#$$$ ")

(defconst matlab-show-vers t
  "*If non-nil, shows the version number on load.")

;; octave-mode
(defun matlab-mode ()
  "Major mode for editing Octave function files.  Version 1.0.
Will run octave-mode-hook if it is non-nil.  Filling works (comments justify).

Special Key Bindings:
\\{matlab-mode-map}
Variables:
  matlab-indent-level                   Level to indent blocks.
  matlab-comment-column                 Goal column for on-line comments.
  fill-column                           Column used in auto-fill.
  matlab-comment-line-s	        	String to start comment line.
  matlab-comment-region-s	        String to put comment lines in region.
  matlab-indent-before-return		If t, matlab-indent-line before RET.
  matlab-indent-end-before-return	If t, indent-line before RET on end.
  matlab-show-vers			If t, show version on start-up.

Commands:
  matlab-mode                           Enter MATLAB major mode.
  matlab-return                         RET with post indenting.
  matlab-linefeed                       RET with pre and post indent.
  matlab-comment-return			RET for next-line comment.
  matlab-indent-line                    Indent line for structure.
  matlab-comment                        Add comment to current line.
  matlab-comment-indent                 Compute indent for comment.
  matlab-comment-region			Comment (with arg, uncomment) region.
  matlab-fill-region			Fill region (usually comments).
  matlab-justify-line			Delete space on end and justify.

To add automatic support put something like the following in your .emacs file:
  \(autoload 'matlab-mode \"matlab-mode\" \"Enter Matlab mode.\" t\)
  \(setq auto-mode-alist \(cons '\(\"\\\\.m$\" . matlab-mode\) \
auto-mode-alist\)\)
  \(defun my-matlab-mode-hook \(\)
    \(setq fill-column 76\)
    \(turn-on-auto-fill\)\)
  \(setq matlab-mode-hook 'my-matlab-mode-hook\)"
  (interactive)
  (kill-all-local-variables)
  (use-local-map matlab-mode-map)
  (setq major-mode 'matlab-mode)
  (setq mode-name "Matlab")
  (setq local-abbrev-table matlab-mode-abbrev-table)
  (set-syntax-table matlab-mode-syntax-table)
  (make-local-variable 'paragraph-start)
  (setq paragraph-start (concat "^$\\|" page-delimiter))
  (make-local-variable 'paragraph-separate)
  (setq paragraph-separate paragraph-start)
  (make-local-variable 'paragraph-ignore-fill-prefix)
  (setq paragraph-ignore-fill-prefix t)
  (make-local-variable 'indent-line-function)
  (setq indent-line-function 'matlab-indent-line)
  (make-local-variable 'comment-start-skip)
  (setq comment-start-skip "%[ \t]*")
  (make-local-variable 'comment-column)
  (setq comment-column 'matlab-comment-column)
  (make-local-variable 'auto-fill-hook)
  (setq auto-fill-hook 'matlab-auto-fill)
  (make-local-variable 'comment-indent-hook)
  (setq comment-indent-hook 'matlab-comment-indent)
  (make-local-variable 'fill-column)
  (setq fill-column default-fill-column)
  (make-local-variable 'fill-prefix)
  (setq fill-prefix matlab-comment-line-s)
  ;;(setq font-lock-keywords matlab-font-lock-keywords) ;; Lucid emacs
  ;;(setq (make-local-variable 'font-lock-keywords) matlab-font-lock-keywords)
  (run-hooks 'matlab-mode-hook)
  (if matlab-show-vers
      (message "matlab-mode, Version 1.06.0 Dated 23Nov93")))


(defconst mtlb-cline-start-skip "[ \t]*%[ \t]*"
  "*The regular expression for skipping comment start.")

;;
(defun matlab-auto-fill ()
  "Do filling."
  (interactive)
  (cond
   ((mtlb-ltype-comment) do-auto-fill)
   ((mtlb-lattr-comment) do-auto-fill)
   (t ())))
   

(defun matlab-return ()
  "Handle carriage return in matlab-mode."
  (interactive)
  (if matlab-indent-before-return
      (matlab-indent-line)
    (if matlab-indent-end-before-return
	(if (mtlb-ltype-block-end) (matlab-indent-line))))
  (newline)
  (matlab-indent-line))

(defun matlab-linefeed ()
  "Handle linefeed in matlab-mode.
Has effect of matlab-return with (not matlab-indent-before-return)."
  (interactive)
  (if (not matlab-indent-before-return) (matlab-indent-line))
  (newline)
  (matlab-indent-line))

(defun matlab-comment-return ()
  "Handle carriage return for matlab comment line."
  (interactive)
  (cond
   ((mtlb-ltype-comment)
    (mtlb-set-comment-fill-prefix) (newline) (insert fill-prefix)
    (matlab-indent-line))
   ((mtlb-lattr-comment)
    (newline) (indent-to matlab-comment-column)
    (insert matlab-comment-on-line-s))
   (t
    (newline) (matlab-comment) (matlab-indent-line))))

(defun matlab-indent-line ()
  "Indent a line in matlab-mode."
  (interactive)
  (save-excursion
    (beginning-of-line)
    (delete-horizontal-space)
    (indent-to (mtlb-calc-indent))
    ;; -- If line contains a comment, format it. --
    (if (or (mtlb-ltype-comment) (mtlb-lattr-comment))
	(matlab-comment)))
  (skip-chars-forward " \t%"))


(defun matlab-comment ()
  "Add a comment to the current line.  If one already exists, format it."
  (interactive)
  (cond
   ((mtlb-ltype-empty)
    (matlab-indent-line)
    (insert matlab-comment-line-s))
   ((mtlb-ltype-comment)
    (save-excursion
      (if (and (= 0 (forward-line -1)) (mtlb-ltype-comment))
	  (progn 
	    (mtlb-set-comment-fill-prefix)
	    (forward-line 1)
	    (beginning-of-line)
	    (delete-horizontal-space) (delete-char 1) (delete-horizontal-space)
	    (insert fill-prefix)))))
   ((mtlb-lattr-comment)
    (beginning-of-line)
    (search-forward "%")
    (forward-char -1)
    (delete-horizontal-space)
    (insert " ")
    (if (< (current-column) matlab-comment-column)
	  (indent-to matlab-comment-column))
    (skip-chars-forward "% "))
   (t
    (end-of-line)
    (re-search-backward "[^ \t\n^]" 0 t)
    (forward-char)
    (delete-horizontal-space)
    (if (< (current-column) matlab-comment-column)
        (indent-to matlab-comment-column)
      (insert " "))
    (insert matlab-comment-on-line-s))))


(defun matlab-comment-indent ()
  "Indent a comment line in matlab-mode."
  (mtlb-calc-indent))

(defun mtlb-calc-indent ()
  "Return the appropriate indentation for this line as an int."
  (let ((indent 0))
    (save-excursion
      (if (not (mtlb-prev-line))
	  0
	(setq indent (current-indentation))
	(setq indent
	      (+ indent
		 (cond
		  ((mtlb-ltype-comment) (mtlb-set-comment-fill-prefix) 0)
		  ((mtlb-ltype-block-beg) matlab-indent-level)
		  ((mtlb-ltype-block-end) 0)
		  (t (mtlb-calc-blok-indent)))))
	(if (mtlb-lattr-unbal-mexp)
	    (setq indent (+ indent (mtlb-calc-mexp-indent))))
	(if (mtlb-lattr-cont)
	    (setq indent (+ indent (* 2 matlab-indent-level)))))
      (if (mtlb-prev-line)
	  (if (mtlb-lattr-cont)
	    (setq indent (- indent (* 2 matlab-indent-level))))))
    (if (mtlb-ltype-block-end)
	(setq indent (- indent matlab-indent-level)))
    (if (< indent 0) (setq indent 0))
    indent))

(defun mtlb-prev-line ()
  "Find the previous line.  Return 0 if not found."
  (interactive)
  (if (/= 0 (forward-line -1))
      ()
    (if (mtlb-ltype-empty)
	(mtlb-prev-line)
      t)))

(defun matlab-comment-region (beg-region end-region arg)
  "Comments every line in the region.
Puts matlab-comment-region-s at the beginning of every line in the region. 
BEG-REGION and END-REGION are args which specify the region boundaries. 
With non-nil ARG, uncomments the region."
  (interactive "*r\nP")
  (let ((end-region-mark (make-marker)) (save-point (point-marker)))
    (set-marker end-region-mark end-region)
    (goto-char beg-region)
    (beginning-of-line)
    (if (not arg)			;comment the region
	(progn (insert matlab-comment-region-s)
	       (while (and  (= (forward-line 1) 0)
			    (< (point) end-region-mark))
		 (insert matlab-comment-region-s)))
      (let ((com (regexp-quote matlab-comment-region-s))) ;uncomment the region
	(if (looking-at com)
	    (delete-region (point) (match-end 0)))
	(while (and  (= (forward-line 1) 0)
		     (< (point) end-region-mark))
	  (if (looking-at com)
	      (delete-region (point) (match-end 0))))))
    (goto-char save-point)
    (set-marker end-region-mark nil)
    (set-marker save-point nil)))


(defun matlab-fill-region (beg-region end-region &optional justify-flag)
  "Fill the region. Non-nil arg means justify commment lines as well."
  (interactive "*r\nP")
  (let ((end-reg-mk (make-marker)))
    (set-marker end-reg-mk end-region)
    (goto-char beg-region)
    (beginning-of-line)
    (while (< (save-excursion (forward-line 1) (point)) end-reg-mk)
      (if (save-excursion (= (forward-line 1) 0))
	  (progn 
	    (cond
	     ((mtlb-ltype-comment)
	      (while (matlab-fill-comment-line))
	      (if justify-flag (justify-comment-line))))
	    (forward-line 1))))))

(defun matlab-fill-comment-line ()
  "Fill the current comment line."
  (interactive)
  (let ((prev-indent-col 0))
    (beginning-of-line)
    (re-search-forward mtlb-cline-start-skip)
    (setq prev-indent-col (current-column))
    (mtlb-set-comment-fill-prefix)
    (if (/= (forward-line 1) 0)
	()
      (beginning-of-line)
      (re-search-forward mtlb-cline-start-skip)
      (if (/= prev-indent-col (current-column))
	  (progn (forward-line -1) ())
	(mtlb-join-comment-lines)
	(if (mtlb-wrap-line)
	    (save-excursion
	      (forward-line 1)
	      (beginning-of-line)
	      (insert fill-prefix)
	      t))))))

(defun mtlb-join-comment-lines ()
  "Join current comment line to previous, deleting space and comment mark."
  (interactive)
  (beginning-of-line)
  (forward-char -1) (delete-char 1)	; delete newline
  (delete-horizontal-space)
  (delete-char 1)			; delete "%"
  (delete-horizontal-space)
  (insert " "))

(defun mtlb-wrap-line ()
  "Wrap line so last token on line does not exceed fill-column."
  (interactive)
  (save-excursion
    (end-of-line)
    (delete-horizontal-space)
    (if (< (current-column) fill-column)
	()
      (while (> (current-column) fill-column) (forward-char -1))
      (while (not (looking-at "[ \t]")) (forward-char -1))
      (delete-horizontal-space)
      (insert "\n")
      t)))

(defun mtlb-set-comment-fill-prefix ()
  "Set the fill-prefix for the current comment line."
  (setq fill-prefix
	(save-excursion
	  (beginning-of-line) 
	  (buffer-substring
	   (point)
	   (progn (re-search-forward mtlb-cline-start-skip) (point)))))
  (if (equal fill-prefix "")
      (setq fill-prefix nil)))

(defun matlab-justify-line ()
  "Delete space on end of line and justify."
  (interactive)
  (save-excursion
    (end-of-line)
    (delete-horizontal-space)
    (justify-current-line)))


;;;
;;; line attributes ...
(defun mtlb-lattr-comment ()
  "Returns t if current line contains a comment."
  (save-excursion
    (beginning-of-line)
    (looking-at ".*%")))

(defun mtlb-lattr-cont ()
  "Returns t if current line ends in .. and optional comment."
  (save-excursion
    (beginning-of-line)
    (re-search-forward
     "[^; \t.][ \t]*\\.\\.\\.+[ \t]*\\(%.*\\)?$"
     (save-excursion (end-of-line) (point))
     t)))

(defun mtlb-lattr-unbal-mexp ()
  (/= (mtlb-calc-mexp-indent) 0))


;;;
;;; line types ...
(defun mtlb-ltype-empty ()
  "Returns t if current line is empty."
  (save-excursion
    (beginning-of-line)
    (looking-at "^[ \t]*$")))

(defun mtlb-ltype-comment ()
  "Returns t if current line is a MATLAB comment line."
  (save-excursion
    (beginning-of-line)
    (looking-at "[ \t]*%")))

(defun mtlb-ltype-block-beg ()
  "Returns t if line contains beginning of MATLAB block."
  (save-excursion
    (beginning-of-line)
    (and
     (looking-at (concat "[^%\n]*" mtlb-block-beg-kw))
     (not (mtlb-ltype-blk-beg-end)))))

(defun mtlb-ltype-block-end ()
  "Returns t if line contains end of MATLAB block."
  (save-excursion
    (beginning-of-line)
    (and
     (looking-at (concat "\\([^%\n]*[ \t]\\)?" mtlb-block-end-kw))
     (not (mtlb-ltype-blk-beg-end)))))

(defun mtlb-ltype-blk-beg-end ()
  "Returns t if line contains matching block begin-end in matlab-mode."
  (save-excursion
    (beginning-of-line)
    (looking-at (concat "[^%\n]*" mtlb-block-beg-kw
			"[^%\n]+" mtlb-block-end-kw))))


;;;
;;; utility functions ...
(defconst mtlb-block-beg-kw "\\b\\(for\\|while\\|if\\|else\\|elseif\\)\\b"
  "Regular expression for keywords which begin blocks in matlab-mode.")

(defconst mtlb-block-end-kw "\\b\\(end\\|else\\|elseif\\)\\b"
  "Regular expression for keywords which end blocks.")

(defun mtlb-calc-blok-indent ()
  (let ((indent 0))
    (save-excursion
      (beginning-of-line)
      (while (< (point) (save-excursion (end-of-line) (point)))
	(cond
	 ((looking-at mtlb-block-beg-kw)
	  (setq indent (+ indent matlab-indent-level)))
	 ((looking-at mtlb-block-end-kw)
	  (setq indent (- indent matlab-indent-level))))
	(forward-char)))
    indent))

(defun mtlb-calc-mexp-indent ()
  (let ((indent 0))
    (save-excursion
      (beginning-of-line)
      (while (< (point) (save-excursion (end-of-line) (point)))
	(cond
	 ((looking-at "\\[")
	  (setq indent (+ indent matlab-indent-level)))
	 ((looking-at "\\]")
	  (setq indent (- indent matlab-indent-level))))
	(forward-char)))
    (* 2 indent)))

;;;
;;; -- debugging --
(defun matlab-show-line-attr ()
  "Display type and attributes of current line.  Used in debugging."
  (interactive)
  (let ((msg "matlab-show-line-attr:"))
    (cond
     ((mtlb-ltype-empty)
      (setq msg (concat msg " empty")))
     ((mtlb-ltype-comment)
      (setq msg (concat msg " comment")))
     ((mtlb-ltype-block-beg)
      (setq msg (concat msg " block-begin")))
     ((mtlb-ltype-block-end)
      (setq msg (concat msg " block-end")))
     (t
      (setq msg (concat msg " other"))))
    (if (mtlb-lattr-cont)
	(setq msg (concat msg " w/cont")))
    (if (mtlb-lattr-comment)
	(setq msg (concat msg " w/comm")))
    (if (mtlb-lattr-unbal-mexp)
	(setq msg (concat msg " w/unbal-mexp")))
    (message msg)))

(defun matlab-show-blok-indent ()
  "Display indentation for matrix expression.  Used in debugging."
  (interactive)
  (let ((msg "matlab-show-blok-indent: "))
    (setq msg (concat msg (mtlb-calc-blok-indent)))
    (message msg)))

(defun matlab-show-mexp-indent ()
  "Display indentation for matrix expression.  Used in debugging."
  (interactive)
  (let ((msg "matlab-show-mexp-indent: "))
    (setq msg (concat msg (mtlb-calc-mexp-indent)))
    (message msg)))


;;; --- version 19 stuff ...
(defvar matlab-font-lock-keywords
  (list
   '("\\b\\(break\\|else\\|elseif\\|end\\|for\\|if\\|return\\|while\\)\\b"
     . font-lock-keyword-face)
   '("\\bfunction[^=]+=[^)]+)" . font-lock-function-name-face)
   )
  "Expressions to hightlight in Matlab mode.")

(if (featurep 'lhilit)
    (hilit-set-mode-patterns
     'matlab-mode
     '(("%.*$" nil comment)
       ;;("'.*'" nil string)
       ("\\bfunction[^=]+=[^)]+)" nil defun)
       ;; key words
       ("\\<\\(load\\|save\\|clear\\)\\>" nil include)
       ("\\<\\(break\\|else\\|elseif\\|end\\|for\\|if\\|return\\)\\>"
	nil keyword)
       )))


;;; -- stuff which belongs elsewhere --
(defun justify-comment-line ()
  "Add spaces to comment line point is in, so it ends at fill-column."
  (interactive)
  (save-excursion
   (save-restriction
    (let (ncols beg)
      (beginning-of-line)
      (forward-char (length fill-prefix))
      (skip-chars-forward " \t")
      (setq beg (point))
      (end-of-line)
      (narrow-to-region beg (point))
      (goto-char beg)
      (while (re-search-forward "   *" nil t)
	(delete-region
	 (+ (match-beginning 0)
	    (if (save-excursion
		 (skip-chars-backward " ])\"'")
		 (memq (preceding-char) '(?. ?? ?!)))
		2 1))
	 (match-end 0)))
      (goto-char beg)
      (while (re-search-forward "[.?!][])""']*\n" nil t)
	(forward-char -1)
	(insert " "))
      (goto-char (point-max))
      (setq ncols (- fill-column (current-column)))
      (if (search-backward " " nil t)
	  (while (> ncols 0)
	    (let ((nmove (+ 3 (% (random) 3))))
	      (while (> nmove 0)
		(or (search-backward " " nil t)
		    (progn
		     (goto-char (point-max))
		     (search-backward " ")))
		(skip-chars-backward " ")
		(setq nmove (1- nmove))))
	    (insert " ")
	    (skip-chars-backward " ")
	    (setq ncols (1- ncols))))))))

(provide 'matlab-mode)
;; --- last line of matlab-mode.el --- 
