;; End auto-generated keywords sections

(defvar gretl-function-header-regexp
  (concat "^\\s-*\\<\\(function\\)\\>"
	  "\\([^=;\n]*=[ \t]*\\|[ \t]*\\)\\(\\w+\\)\\>")
  "Regexp to match a gretl function header.
The string `function' and its name are given by the first and third
parenthetical grouping.")

(defvar gretl-font-lock-keywords
  (list
   ;; Fontify all builtin keywords.
   (cons (concat "\\<\\("
		 (mapconcat 'identity gretl-keywords "\\|")
		 "\\)\\>")
	 'font-lock-keyword-face)
   ;; Fontify all option flags.
   (cons (concat "[ \t]--\\("
		 (mapconcat 'identity gretl-option-flags "\\|")
		 "\\)")
	 'font-lock-constant-face)
   ;; Fontify all command words.
   (cons (concat "\\<\\("
		 (mapconcat 'identity gretl-command-words "\\|")
		 "\\)\\>")
	 'font-lock-builtin-face)
   ;; Fontify all builtin operators.
   (cons "\\(&\\||\\|<=\\|>=\\|==\\|<\\|>\\|!=\\|!\\)"
	 (if (boundp 'font-lock-builtin-face)
	     'font-lock-builtin-face
	   'font-lock-preprocessor-face))
   ;; Fontify all internal variables.
   (cons (concat "\\$\\("
		 (mapconcat 'identity gretl-internal-vars "\\|")
		 "\\)\\>")
	 'font-lock-variable-name-face)

   ;; Fontify all genr functions.
   (cons (concat "\\<\\("
		 (mapconcat 'identity gretl-genr-functions "\\|")
		 "\\)\\>")
	 'font-lock-variable-name-face)
   ;; Fontify all function declarations.
   (list gretl-function-header-regexp
	 '(1 font-lock-keyword-face)
	 '(3 font-lock-function-name-face nil t)))
  "Additional gretl expressions to highlight.")

(defvar gretl-mode-map nil
  "Keymap used in gretl mode.")
(if gretl-mode-map
    ()
  (let ((map (make-sparse-keymap)))
    (define-key map "`" 'gretl-abbrev-start)
    (define-key map ";" 'gretl-electric-semi)
    (define-key map " " 'gretl-electric-space)
    (define-key map "\n" 'gretl-reindent-then-newline-and-indent)
    (define-key map "\t" 'indent-according-to-mode)
    (define-key map "\e;" 'gretl-indent-for-comment)
    (define-key map "\e\n" 'gretl-indent-new-comment-line)
    (define-key map "\e\t" 'gretl-complete-symbol)
    (define-key map "\M-\C-a" 'gretl-beginning-of-defun)
    (define-key map "\M-\C-e" 'gretl-end-of-defun)
    (define-key map "\M-\C-h" 'gretl-mark-defun)
    (define-key map "\M-\C-q" 'gretl-indent-defun)
    (define-key map "\C-c;" 'gretl-comment-region)
    (define-key map "\C-c:" 'gretl-uncomment-region)
    (define-key map "\C-c\C-b" 'gretl-submit-bug-report)
    (define-key map "\C-c\C-p" 'gretl-previous-code-line)
    (define-key map "\C-c\C-n" 'gretl-next-code-line)
    (define-key map "\C-c\C-a" 'gretl-beginning-of-line)
    (define-key map "\C-c\C-e" 'gretl-end-of-line)
    (define-key map "\C-c\C-f" 'gretl-run-buffer)
    (define-key map "\C-c\M-\C-n" 'gretl-forward-block)
    (define-key map "\C-c\M-\C-p" 'gretl-backward-block)
    (define-key map "\C-c\M-\C-u" 'gretl-backward-up-block)
    (define-key map "\C-c\M-\C-d" 'gretl-down-block)
    (define-key map "\C-c\M-\C-h" 'gretl-mark-block)
    (define-key map "\C-c]" 'gretl-close-block)
    (setq gretl-mode-map map)))

(defvar gretl-mode-menu
  (list "gretl"
	(list "Lines"
	      ["Previous Code Line"	gretl-previous-code-line t]
	      ["Next Code Line"		gretl-next-code-line t]
	      ["Begin of Continuation"	gretl-beginning-of-line t]
	      ["End of Continuation"	gretl-end-of-line t]
	      ["Split Line at Point"	gretl-indent-new-comment-line t])
	(list "Blocks"
	      ["Next Block"		gretl-forward-block t]
	      ["Previous Block"		gretl-backward-block t]
	      ["Down Block"		gretl-down-block t]
	      ["Up Block"		gretl-backward-up-block t]
	      ["Mark Block"		gretl-mark-block t]
	      ["Close Block"		gretl-close-block t])
	(list "Functions"
	      ["Begin of Function"	gretl-beginning-of-defun t]
	      ["End of Function"	gretl-end-of-defun t]
	      ["Mark Function"		gretl-mark-defun t]
	      ["Indent Function"	gretl-indent-defun t])
	"-"
	[ "Run Current Buffer"          gretl-run-buffer t]
	"-"
	["Indent Line"			indent-according-to-mode t]
	["Complete Symbol"		gretl-complete-symbol t]
	"-"
	["Toggle Abbrev Mode"		abbrev-mode t]
	["Toggle Auto-Fill Mode"	auto-fill-mode t]
	"-"
	["Submit Bug Report"		gretl-submit-bug-report t]
	"-"
	["Describe gretl Mode"		gretl-describe-major-mode t])
  "Menu for gretl mode.")

(defvar gretl-mode-syntax-table nil
  "Syntax table in use in gretl-mode buffers.")
(if gretl-mode-syntax-table
    ()
  (let ((table (make-syntax-table)))
    (modify-syntax-entry ?\r " "  table)
    (modify-syntax-entry ?+ "."   table)
    (modify-syntax-entry ?- "."   table)
    (modify-syntax-entry ?= "."   table)
    (modify-syntax-entry ?* "."   table)
    (modify-syntax-entry ?/ "."   table)
    (modify-syntax-entry ?> "."   table)
    (modify-syntax-entry ?< "."   table)
    (modify-syntax-entry ?& "."   table)
    (modify-syntax-entry ?| "."   table)
    (modify-syntax-entry ?! "."   table)
    (modify-syntax-entry ?\\ "\\" table)
    (modify-syntax-entry ?\' "."  table)
    (modify-syntax-entry ?\` "w"  table)
    (modify-syntax-entry ?\" "\"" table)
    (modify-syntax-entry ?. "w"   table)
    (modify-syntax-entry ?_ "w"   table)
    (modify-syntax-entry ?\% "."  table)
    (modify-syntax-entry ?\# "<"  table)
    (modify-syntax-entry ?\n ">"  table)
    (setq gretl-mode-syntax-table table)))

(defcustom gretl-auto-indent nil
  "*Non-nil means indent line after a semicolon or space in gretl mode."
  :type 'boolean
  :group 'gretl)

(defcustom gretl-auto-newline nil
  "*Non-nil means automatically newline after a semicolon in gretl mode."
  :type 'boolean
  :group 'gretl)

(defcustom gretl-blink-matching-block t
  "*Control the blinking of matching gretl block keywords.
Non-nil means show matching begin of block when inserting a space,
newline or semicolon after an else or end keyword."
  :type 'boolean
  :group 'gretl)

(defcustom gretl-block-offset 4
  "*Extra indentation applied to statements in gretl block structures."
  :type 'integer
  :group 'gretl)

(defcustom gretl-program "gretlcli"
  "*The program to use for running gretl scripts."
  :type 'string
  :group 'gretl)

(defcustom gretl-remove-file "rm"
  "*The command to use for deleting a temporary file."
  :type 'string
  :group 'gretl)

(defcustom gretl-output-in-new-frame nil
  "*Non-nil means send gretl script output to a separate frame."
  :type 'boolean
  :group 'gretl)

(defvar gretl-block-begin-regexp
  (concat "\\<\\("
	  (mapconcat 'identity gretl-begin-keywords "\\|")
	  "\\)\\>"))

(defvar gretl-block-else-regexp
  (concat "\\<\\("
	  (mapconcat 'identity gretl-else-keywords "\\|")
	  "\\)\\>"))

(defvar gretl-block-end-regexp
  (concat "\\<\\("
	  (mapconcat 'identity gretl-end-keywords "\\|")
	  "\\)\\>"))

(defvar gretl-block-begin-or-end-regexp
  (concat gretl-block-begin-regexp "\\|" gretl-block-end-regexp))

(defvar gretl-block-else-or-end-regexp
  (concat gretl-block-else-regexp "\\|" gretl-block-end-regexp))

(defvar gretl-block-match-alist
  '(("loop" . ("endloop"))
    ("if" . ("else" "elif" "endif"))
    ("nls" . ("end"))
    ("mle" . ("end"))
    ("gmm" . ("end"))
    ("foreign" . ("end"))
    ("restrict" . ("end"))
    ("kalman" . ("end"))
    ("function" . ("end function"))
    ("system" . ("end")))
  "Alist with gretl's matching block keywords.
Has gretl's begin keywords as keys and a list of the matching else or
end keywords as associated values.")

(defvar gretl-block-comment-start
  (concat (make-string 2 gretl-comment-char) " ")
  "String to insert to start a new gretl comment on an empty line.")

(defcustom gretl-continuation-offset 4
  "*Extra indentation applied to gretl continuation lines."
  :type 'integer
  :group 'gretl)
(defvar gretl-continuation-regexp
  "[^#%\n]*\\(\\\\\\|\\.\\.\\.\\)\\s-*\\(\\s<.*\\)?$")
(defcustom gretl-continuation-string "\\"
  "*Character string used for gretl continuation lines.  Normally \\."
  :type 'string
  :group 'gretl)

(defvar gretl-completion-alist nil
  "Alist of gretl symbols for completion in gretl mode.
Each element looks like (VAR . VAR), where the car and cdr are the same
symbol (an gretl command or variable name).
Currently, only builtin variables can be completed.")

(defvar gretl-mode-imenu-generic-expression
  (list
   ;; Functions
   (list nil gretl-function-header-regexp 3))
  "Imenu expression for gretl mode.  See `imenu-generic-expression'.")

(defcustom gretl-mode-hook nil
  "*Hook to be run when gretl mode is started."
  :type 'hook
  :group 'gretl)


;;;###autoload
(defun gretl-mode ()
  "Major mode for editing gretl code.

This mode makes it easier to write gretl code by helping with
indentation, doing some of the typing for you (with Abbrev mode) and by
showing keywords, comments, strings, etc. in different faces (with
Font Lock mode on terminals that support it).

The latest released version of gretl is always available via http from
http://gretl.sourceforge.net.  Complete source and binaries for
GNU-Linux and MS Windows are available.

Type \\[list-abbrevs] to display the built-in abbrevs for gretl keywords.

Keybindings
===========

\\{gretl-mode-map}

Variables you can use to customize gretl mode
==============================================

gretl-auto-indent
  Non-nil means indent current line after a semicolon or space.
  Default is nil.

gretl-auto-newline
  Non-nil means auto-insert a newline and indent after a semicolon.
  Default is nil.

gretl-blink-matching-block
  Non-nil means show matching begin of block when inserting a space,
  newline or semicolon after an else or end keyword.  Default is t.

gretl-block-offset
  Extra indentation applied to statements in block structures.
  Default is 4.

gretl-continuation-offset
  Extra indentation applied to gretl continuation lines.
  Default is 4.

gretl-continuation-string
  String used for gretl continuation lines.
  Default is a backslash.

Turning on gretl mode runs the hook `gretl-mode-hook'.

To begin using this mode for all `.inp' files that you edit, add the
following lines to your `.emacs' file:

  (autoload 'gretl-mode \"gretl\" nil t)
  (setq auto-mode-alist
        (cons '(\"\\\\.inp$\" . gretl-mode) auto-mode-alist))

To automatically turn on the abbrev, auto-fill and font-lock features,
add the following lines to your `.emacs' file as well:

  (add-hook 'gretl-mode-hook
	    (lambda ()
	      (abbrev-mode 1)
	      (auto-fill-mode 1)
	      (if (eq window-system 'x)
		  (font-lock-mode 1))))

To submit a problem report, enter \\[gretl-submit-bug-report] from \
an gretl mode buffer.
This automatically sets up a mail buffer with version information
already added.  You just need to add a description of the problem,
including a reproducible test case and send the message."
  (interactive)
  (kill-all-local-variables)

  (use-local-map gretl-mode-map)
  (setq major-mode 'gretl-mode)
  (setq mode-name "gretl")
  (setq local-abbrev-table gretl-abbrev-table)
  (set-syntax-table gretl-mode-syntax-table)

  (make-local-variable 'indent-line-function)
  (setq indent-line-function 'gretl-indent-line)

  (make-local-variable 'comment-start)
  (setq comment-start gretl-comment-start)
  (make-local-variable 'comment-end)
  (setq comment-end "")
  (make-local-variable 'comment-start-skip)
  (setq comment-start-skip "\\s<+\\s-*")
  (make-local-variable 'comment-indent-function)
  (setq comment-indent-function 'gretl-comment-indent)

  (make-local-variable 'parse-sexp-ignore-comments)
  (setq parse-sexp-ignore-comments t)
  (make-local-variable 'paragraph-start)
  (setq paragraph-start (concat "\\s-*$\\|" 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 'fill-paragraph-function)
  (setq fill-paragraph-function 'gretl-fill-paragraph)
  (make-local-variable 'adaptive-fill-regexp)
  (setq adaptive-fill-regexp nil)
  (make-local-variable 'fill-column)
  (setq fill-column 72)
  (make-local-variable 'normal-auto-fill-function)
  (setq normal-auto-fill-function 'gretl-auto-fill)

  (make-local-variable 'font-lock-defaults)
  (setq font-lock-defaults '(gretl-font-lock-keywords nil nil))

  (make-local-variable 'imenu-generic-expression)
  (setq imenu-generic-expression gretl-mode-imenu-generic-expression
        imenu-case-fold-search nil)

  (gretl-add-gretl-menu)
  (gretl-initialize-completions)
  (run-hooks 'gretl-mode-hook))

;;; Miscellaneous useful functions

(defun gretl-describe-major-mode ()
  "Describe the current major mode."
  (interactive)
  (describe-function major-mode))

(defun gretl-point (position)
  "Returns the value of point at certain positions."
  (save-excursion
    (cond
     ((eq position 'bol)  (beginning-of-line))
     ((eq position 'eol)  (end-of-line))
     ((eq position 'boi)  (back-to-indentation))
     ((eq position 'bonl) (forward-line 1))
     ((eq position 'bopl) (forward-line -1))
     (t (error "unknown buffer position requested: %s" position)))
    (point)))

(defsubst gretl-in-comment-p ()
  "Returns t if point is inside an gretl comment, nil otherwise."
  (interactive)
  (save-excursion
    (nth 4 (parse-partial-sexp (gretl-point 'bol) (point)))))

(defsubst gretl-in-string-p ()
  "Returns t if point is inside an gretl string, nil otherwise."
  (interactive)
  (save-excursion
    (nth 3 (parse-partial-sexp (gretl-point 'bol) (point)))))

(defsubst gretl-not-in-string-or-comment-p ()
  "Returns t iff point is not inside an gretl string or comment."
  (let ((pps (parse-partial-sexp (gretl-point 'bol) (point))))
    (not (or (nth 3 pps) (nth 4 pps)))))

(defun gretl-in-block-p ()
  "Returns t if point is inside an gretl block, nil otherwise.
The block is taken to start at the first letter of the begin keyword and
to end after the end keyword."
  (let ((pos (point)))
    (save-excursion
      (condition-case nil
	  (progn
	    (skip-syntax-forward "w")
	    (gretl-up-block -1)
	    (gretl-forward-block)
	    t)
	(error nil))
      (< pos (point)))))

(defun gretl-in-defun-p ()
  "Returns t iff point is inside an gretl function declaration.
The function is taken to start at the `f' of `function' and to end after
the end keyword."
  (let ((pos (point)))
    (save-excursion
      (or (and (looking-at "\\<function\\>")
	       (gretl-not-in-string-or-comment-p))
	  (and (gretl-beginning-of-defun)
	       (condition-case nil
		   (progn
		     (gretl-forward-block)
		     t)
		 (error nil))
	       (< pos (point)))))))

(defun gretl-maybe-insert-continuation-string ()
  (if (or (gretl-in-comment-p)
	  (save-excursion
	    (beginning-of-line)
	    (looking-at gretl-continuation-regexp)))
      nil
    (delete-horizontal-space)
    (insert (concat " " gretl-continuation-string))))

(defvar gretl-xemacs-p
  (string-match "XEmacs\\|Lucid" emacs-version))

;;; Comments
(defun gretl-comment-region (beg end &optional arg)
  "Comment or uncomment each line in the region as gretl code.
See `comment-region'."
  (interactive "r\nP")
  (let ((comment-start (char-to-string gretl-comment-char)))
    (comment-region beg end arg)))

(defun gretl-uncomment-region (beg end &optional arg)
  "Uncomment each line in the region as gretl code."
  (interactive "r\nP")
  (or arg (setq arg 1))
  (gretl-comment-region beg end (- arg)))

;;; Indentation

(defun calculate-gretl-indent ()
  "Return appropriate indentation for current line as gretl code.
Returns an integer (the column to indent to) unless the line is a
comment line with fixed goal golumn.  In that case, returns a list whose
car is the column to indent to, and whose cdr is the current indentation
level."
  (let ((is-continuation-line
	 (save-excursion
	   (if (zerop (gretl-previous-code-line))
	       (looking-at gretl-continuation-regexp))))
	(icol 0))
    (save-excursion
      (beginning-of-line)
      ;; If we can move backward out one level of parentheses, take 1
      ;; plus the indentation of that parenthesis.  Otherwise, go back
      ;; to the beginning of the previous code line, and compute the
      ;; offset this line gives.
      (if (condition-case nil
	      (progn (up-list -1) t)
	    (error nil))
	  (setq icol (+ 1 (current-column)))
	(if (zerop (gretl-previous-code-line))
	    (progn
	      (gretl-beginning-of-line)
	      (back-to-indentation)
	      (setq icol (current-column))
	      (let ((bot (point))
		    (eol (gretl-point 'eol)))
		(while (< (point) eol)
		  (if (gretl-not-in-string-or-comment-p)
		      (cond
		       ((looking-at gretl-block-begin-regexp)
			(setq icol (+ icol gretl-block-offset)))
		       ((looking-at gretl-block-else-regexp)
			(if (= bot (point))
			    (setq icol (+ icol gretl-block-offset))))
		       ((looking-at "end function")
			(setq icol (- icol (gretl-block-end-offset)))
			)))
		  (forward-char)))
	      (if is-continuation-line
		  (setq icol (+ icol gretl-continuation-offset)))
	      ) ;; progn
	  ) ;; if zerop
	))
    (save-excursion
      (back-to-indentation)
      (cond
       ((looking-at gretl-block-else-regexp)
	(setq icol (- icol gretl-block-offset)))
       ((looking-at gretl-block-end-regexp)
	(setq icol (- icol (gretl-block-end-offset))))
       ((looking-at "\\s<\\s<\\s<\\S<")
	(setq icol (list 0 icol)))
       ((looking-at "\\s<\\S<")
	(setq icol (list icol icol)))))
    icol))

(defun gretl-block-end-offset ()
  (save-excursion
    (gretl-backward-up-block 1)
    (* gretl-block-offset
       (if (string-match (match-string 0) "switch") 2 1))))

(defun gretl-before-magic-comment-p ()
  (save-excursion
    (beginning-of-line)
    (and (bobp) (looking-at "\\s-*#!"))))

(defun gretl-comment-indent ()
  (if (or (looking-at "\\s<\\s<\\s<")
	  (gretl-before-magic-comment-p))
      0
    (if (looking-at "\\s<\\s<")
	(calculate-gretl-indent)
      (skip-syntax-backward " ")
      (max (if (bolp) 0 (+ 1 (current-column)))
	   comment-column))))

(defun gretl-indent-for-comment ()
  "Maybe insert and indent an gretl comment.
If there is no comment already on this line, create a code-level comment
(started by two comment characters) if the line is empty, or an in-line
comment (started by one comment character) otherwise.
Point is left after the start of the comment which is properly aligned."
  (interactive)
  (indent-for-comment)
  (indent-according-to-mode))

(defun gretl-indent-line (&optional arg)
  "Indent current line as gretl code.
With optional ARG, use this as offset unless this line is a comment with
fixed goal column."
  (interactive)
  (or arg (setq arg 0))
  (let ((icol (calculate-gretl-indent))
	(relpos (- (current-column) (current-indentation))))
    (if (listp icol)
	(setq icol (car icol))
      (setq icol (+ icol arg)))
    (if (< icol 0)
	(error "Unmatched end keyword")
      (indent-line-to icol)
      (if (> relpos 0)
	  (move-to-column (+ icol relpos))))))

(defun gretl-indent-new-comment-line ()
  "Break gretl line at point, continuing comment if within one.
If within code, insert `gretl-continuation-string' before breaking the
line.  If within a string, signal an error.
The new line is properly indented."
  (interactive)
  (delete-horizontal-space)
  (cond
   ((gretl-in-comment-p)
    (indent-new-comment-line))
   ((gretl-in-string-p)
    (error "Cannot split a code line inside a string"))
   (t
    (insert (concat " " gretl-continuation-string))
    (gretl-reindent-then-newline-and-indent))))

(defun gretl-indent-defun ()
  "Properly indents the gretl function which contains point."
  (interactive)
  (save-excursion
    (gretl-mark-defun)
    (message "Indenting function...")
    (indent-region (point) (mark) nil))
  (message "Indenting function...done."))

;;; Motion

(defun gretl-next-code-line (&optional arg)
  "Move ARG lines of gretl code forward (backward if ARG is negative).
Skips past all empty and comment lines.  Default for ARG is 1.

On success, return 0.  Otherwise, go as far as possible and return -1."
  (interactive "p")
  (or arg (setq arg 1))
  (beginning-of-line)
  (let ((n 0)
	(inc (if (> arg 0) 1 -1)))
    (while (and (/= arg 0) (= n 0))
      (setq n (forward-line inc))
      (while (and (= n 0)
		  (looking-at "\\s-*\\($\\|\\s<\\)"))
	(setq n (forward-line inc)))
      (setq arg (- arg inc)))
    n))

(defun gretl-previous-code-line (&optional arg)
  "Move ARG lines of gretl code backward (forward if ARG is negative).
Skips past all empty and comment lines.  Default for ARG is 1.

On success, return 0.  Otherwise, go as far as possible and return -1."
  (interactive "p")
  (or arg (setq arg 1))
  (gretl-next-code-line (- arg)))

(defun gretl-beginning-of-line ()
  "Move point to beginning of current gretl line.
If on an empty or comment line, go to the beginning of that line.
Otherwise, move backward to the beginning of the first gretl code line
which is not inside a continuation statement, i.e., which does not
follow a code line ending in `...' or `\\', or is inside an open
parenthesis list."
  (interactive)
  (beginning-of-line)
  (if (not (looking-at "\\s-*\\($\\|\\s<\\)"))
      (while (or (condition-case nil
		     (progn
		       (up-list -1)
		       (beginning-of-line)
		       t)
		   (error nil))
		 (and (or (looking-at "\\s-*\\($\\|\\s<\\)")
			  (save-excursion
			    (if (zerop (gretl-previous-code-line))
				(looking-at gretl-continuation-regexp))))
		      (zerop (forward-line -1)))))))

(defun gretl-end-of-line ()
  "Move point to end of current gretl line.
If on an empty or comment line, go to the end of that line.
Otherwise, move forward to the end of the first gretl code line which
does not end in `...' or `\\' or is inside an open parenthesis list."
  (interactive)
  (end-of-line)
  (if (save-excursion
	(beginning-of-line)
	(looking-at "\\s-*\\($\\|\\s<\\)"))
      ()
    (while (or (condition-case nil
		   (progn
		     (up-list 1)
		     (end-of-line)
		     t)
		 (error nil))
	       (and (save-excursion
		      (beginning-of-line)
		      (or (looking-at "\\s-*\\($\\|\\s<\\)")
			  (looking-at gretl-continuation-regexp)))
		    (zerop (forward-line 1)))))
    (end-of-line)))

(defun gretl-scan-blocks (from count depth)
  "Scan from character number FROM by COUNT gretl begin-end blocks.
Returns the character number of the position thus found.

If DEPTH is nonzero, block depth begins counting from that value.
Only places where the depth in blocks becomes zero are candidates for
stopping; COUNT such places are counted.

If the beginning or end of the buffer is reached and the depth is wrong,
an error is signaled."
  (let ((min-depth (if (> depth 0) 0 depth))
	(inc (if (> count 0) 1 -1)))
    (save-excursion
      (while (/= count 0)
	(catch 'foo
	  (while (or (re-search-forward
		      gretl-block-begin-or-end-regexp nil 'move inc)
		     (if (/= depth 0)
			 (error "Unbalanced block")))
	    (if (gretl-not-in-string-or-comment-p)
		(progn
		  (cond
		   ((match-end 1)
		    (setq depth (+ depth inc)))
		   ((match-end 2)
		    (setq depth (- depth inc))))
		  (if (< depth min-depth)
		      (error "Containing expression ends prematurely"))
		  (if (= depth 0)
		      (throw 'foo nil))))))
	(setq count (- count inc)))
      (point))))

(defun gretl-forward-block (&optional arg)
  "Move forward across one balanced gretl begin-end block.
With argument, do it that many times.
Negative arg -N means move backward across N blocks."
  (interactive "p")
  (or arg (setq arg 1))
  (goto-char (or (gretl-scan-blocks (point) arg 0) (buffer-end arg))))

(defun gretl-backward-block (&optional arg)
  "Move backward across one balanced gretl begin-end block.
With argument, do it that many times.
Negative arg -N means move forward across N blocks."
  (interactive "p")
  (or arg (setq arg 1))
  (gretl-forward-block (- arg)))

(defun gretl-down-block (arg)
  "Move forward down one begin-end block level of gretl code.
With argument, do this that many times.
A negative argument means move backward but still go down a level.
In Lisp programs, an argument is required."
  (interactive "p")
  (let ((inc (if (> arg 0) 1 -1)))
    (while (/= arg 0)
      (goto-char (or (gretl-scan-blocks (point) inc -1)
		     (buffer-end arg)))
      (setq arg (- arg inc)))))

(defun gretl-backward-up-block (arg)
  "Move backward out of one begin-end block level of gretl code.
With argument, do this that many times.
A negative argument means move forward but still to a less deep spot.
In Lisp programs, an argument is required."
  (interactive "p")
  (gretl-up-block (- arg)))

(defun gretl-up-block (arg)
  "Move forward out of one begin-end block level of gretl code.
With argument, do this that many times.
A negative argument means move backward but still to a less deep spot.
In Lisp programs, an argument is required."
  (interactive "p")
  (let ((inc (if (> arg 0) 1 -1)))
    (while (/= arg 0)
      (goto-char (or (gretl-scan-blocks (point) inc 1)
		     (buffer-end arg)))
      (setq arg (- arg inc)))))

(defun gretl-mark-block ()
  "Put point at the beginning of this gretl block, mark at the end.
The block marked is the one that contains point or follows point."
  (interactive)
  (let ((pos (point)))
    (if (or (and (gretl-in-block-p)
		 (skip-syntax-forward "w"))
	    (condition-case nil
		(progn
		  (gretl-down-block 1)
		  (gretl-in-block-p))
	      (error nil)))
	(progn
	  (gretl-up-block -1)
	  (push-mark (point))
	  (gretl-forward-block)
	  (exchange-point-and-mark))
      (goto-char pos)
      (message "No block to mark found"))))

(defun gretl-close-block ()
  "Close the current gretl block on a separate line.
An error is signaled if no block to close is found."
  (interactive)
  (let (bb-keyword)
    (condition-case nil
	(progn
	  (save-excursion
	    (gretl-backward-up-block 1)
	    (setq bb-keyword (buffer-substring-no-properties
			      (match-beginning 1) (match-end 1))))
	  (if (save-excursion
		(beginning-of-line)
		(looking-at "^\\s-*$"))
	      (indent-according-to-mode)
	    (gretl-reindent-then-newline-and-indent))
	  (insert (car (reverse
			(assoc bb-keyword
			       gretl-block-match-alist))))
	  (gretl-reindent-then-newline-and-indent)
	  t)
      (error (message "No block to close found")))))

(defun gretl-blink-matching-block-open ()
  "Blink the matching gretl begin block keyword.
If point is right after an gretl else or end type block keyword, move
cursor momentarily to the corresponding begin keyword.
Signal an error if the keywords are incompatible."
  (interactive)
  (let (bb-keyword bb-arg eb-keyword pos eol)
    (if (and (gretl-not-in-string-or-comment-p)
	     (looking-at "\\>")
	     (save-excursion
	       (skip-syntax-backward "w")
	       (looking-at gretl-block-else-or-end-regexp)))
	(save-excursion
	  (cond
	   ((match-end 1)
	    (setq eb-keyword
		  (buffer-substring-no-properties
		   (match-beginning 1) (match-end 1)))
	    (gretl-backward-up-block 1))
	   ((match-end 2)
	    (setq eb-keyword
		  (buffer-substring-no-properties
		   (match-beginning 2) (match-end 2)))
	    (gretl-backward-block)))
	  (setq pos (match-end 0)
		bb-keyword
		(buffer-substring-no-properties
		 (match-beginning 0) pos)
		pos (+ pos 1)
		eol (gretl-point 'eol)
		bb-arg
		(save-excursion
		  (save-restriction
		    (goto-char pos)
		    (while (and (skip-syntax-forward "^<" eol)
				(gretl-in-string-p)
				(not (forward-char 1))))
		    (skip-syntax-backward " ")
		    (buffer-substring-no-properties pos (point)))))
	  (if (member eb-keyword
		      (cdr (assoc bb-keyword gretl-block-match-alist)))
	      (progn
		(message "Matches `%s %s'" bb-keyword bb-arg)
		(if (pos-visible-in-window-p)
		    (sit-for blink-matching-delay)))
	    (error "Block keywords `%s' and `%s' do not match"
		   bb-keyword eb-keyword))))))

(defun gretl-beginning-of-defun (&optional arg)
  "Move backward to the beginning of an gretl function.
With positive ARG, do it that many times.  Negative argument -N means
move forward to Nth following beginning of a function.
Returns t unless search stops at the beginning or end of the buffer."
  (interactive "p")
  (let* ((arg (or arg 1))
	 (inc (if (> arg 0) 1 -1))
	 (found))
    (and (not (eobp))
	 (not (and (> arg 0) (looking-at "\\<function\\>")))
	 (skip-syntax-forward "w"))
    (while (and (/= arg 0)
		(setq found
		      (re-search-backward "\\<function\\>" nil 'move inc)))
      (if (gretl-not-in-string-or-comment-p)
	  (setq arg (- arg inc))))
    (if found
	(progn
	  (and (< inc 0) (goto-char (match-beginning 0)))
	  t))))

(defun gretl-end-of-defun (&optional arg)
  "Move forward to the end of an gretl function.
With positive ARG, do it that many times.  Negative argument -N means
move back to Nth preceding end of a function.

An end of a function occurs right after the end keyword matching the
`function' keyword that starts the function."
  (interactive "p")
  (or arg (setq arg 1))
  (and (< arg 0) (skip-syntax-backward "w"))
  (and (> arg 0) (skip-syntax-forward "w"))
  (if (gretl-in-defun-p)
      (setq arg (- arg 1)))
  (if (= arg 0) (setq arg -1))
  (if (gretl-beginning-of-defun (- arg))
      (gretl-forward-block)))

(defun gretl-mark-defun ()
  "Put point at the beginning of this gretl function, mark at its end.
The function marked is the one containing point or following point."
  (interactive)
  (let ((pos (point)))
    (if (or (gretl-in-defun-p)
	    (and (gretl-beginning-of-defun -1)
		 (gretl-in-defun-p)))
	(progn
	  (skip-syntax-forward "w")
	  (gretl-beginning-of-defun)
	  (push-mark (point))
	  (gretl-end-of-defun)
	  (exchange-point-and-mark))
      (goto-char pos)
      (message "No function to mark found"))))

;;; Filling

(defun gretl-auto-fill ()
  "Perform auto-fill in gretl mode.
Returns nil if no feasible place to break the line could be found, and t
otherwise."
  (let (fc give-up)
    (if (or (null (setq fc (current-fill-column)))
	    (save-excursion
	      (beginning-of-line)
	      (and auto-fill-inhibit-regexp
		   (looking-at auto-fill-inhibit-regexp))))
	nil				; Can't do anything
      (if (and (not (gretl-in-comment-p))
	       (> (current-column) fc))
	  (setq fc (- fc (+ (length gretl-continuation-string) 1))))
      (while (and (not give-up) (> (current-column) fc))
	(let* ((opoint (point))
	       (fpoint
		(save-excursion
		  (move-to-column (+ fc 1))
		  (skip-chars-backward "^ \t\n")
		  ;; If we're at the beginning of the line, break after
		  ;; the first word
		  (if (bolp)
		      (re-search-forward "[ \t]" opoint t))
		  ;; If we're in a comment line, don't break after the
		  ;; comment chars
		  (if (save-excursion
			(skip-syntax-backward " <")
			(bolp))
		      (re-search-forward "[ \t]" (gretl-point 'eol)
					 'move))
		  ;; If we're not in a comment line and just ahead the
		  ;; continuation string, don't break here.
		  (if (and (not (gretl-in-comment-p))
			   (looking-at
			    (concat "\\s-*"
				    (regexp-quote
				     gretl-continuation-string)
				    "\\s-*$")))
		      (end-of-line))
		  (skip-chars-backward " \t")
		  (point))))
	  (if (save-excursion
		(goto-char fpoint)
		(not (or (bolp) (eolp))))
	      (let ((prev-column (current-column)))
		(if (save-excursion
		      (skip-chars-backward " \t")
		      (= (point) fpoint))
		    (progn
		      (gretl-maybe-insert-continuation-string)
		      (indent-new-comment-line t))
		  (save-excursion
		    (goto-char fpoint)
		    (gretl-maybe-insert-continuation-string)
		    (indent-new-comment-line t)))
		(if (>= (current-column) prev-column)
		    (setq give-up t)))
	    (setq give-up t))))
      (not give-up))))

(defun gretl-fill-paragraph (&optional arg)
 "Fill paragraph of gretl code, handling gretl comments."
 (interactive "P")
 (save-excursion
   (let ((end (progn (forward-paragraph) (point)))
	 (beg (progn
		(forward-paragraph -1)
		(skip-chars-forward " \t\n")
		(beginning-of-line)
		(point)))
	 (cfc (current-fill-column))
	 (ind (calculate-gretl-indent))
	 comment-prefix)
     (save-restriction
       (goto-char beg)
       (narrow-to-region beg end)
       (if (listp ind) (setq ind (nth 1 ind)))
       (while (not (eobp))
	 (condition-case nil
	     (gretl-indent-line ind)
	   (error nil))
	 (if (and (> ind 0)
		  (not
		   (save-excursion
		     (beginning-of-line)
		     (looking-at "^\\s-*\\($\\|\\s<+\\)"))))
	     (setq ind 0))
	 (move-to-column cfc)
	 ;; First check whether we need to combine non-empty comment lines
	 (if (and (< (current-column) cfc)
		  (gretl-in-comment-p)
		  (not (save-excursion
			 (beginning-of-line)
			 (looking-at "^\\s-*\\s<+\\s-*$"))))
	     ;; This is a nonempty comment line which does not extend
	     ;; past the fill column.  If it is followed by a nonempty
	     ;; comment line with the same comment prefix, try to
	     ;; combine them, and repeat this until either we reach the
	     ;; fill-column or there is nothing more to combine.
	     (progn
	       ;; Get the comment prefix
	       (save-excursion
		 (beginning-of-line)
		 (while (and (re-search-forward "\\s<+")
			     (not (gretl-in-comment-p))))
		 (setq comment-prefix (match-string 0)))
	       ;; And keep combining ...
	       (while (and (< (current-column) cfc)
			   (save-excursion
			     (forward-line 1)
			     (and (looking-at
				   (concat "^\\s-*"
					   comment-prefix
					   "\\S<"))
				  (not (looking-at
					(concat "^\\s-*"
						comment-prefix
						"\\s-*$"))))))
		 (delete-char 1)
		 (re-search-forward comment-prefix)
		 (delete-region (match-beginning 0) (match-end 0))
		 (fixup-whitespace)
		 (move-to-column cfc))))
	 ;; We might also try to combine continued code lines>  Perhaps
	 ;; some other time ...
	 (skip-chars-forward "^ \t\n")
	 (delete-horizontal-space)
	 (if (or (< (current-column) cfc)
		 (and (= (current-column) cfc) (eolp)))
	     (forward-line 1)
	   (if (not (eolp)) (insert " "))
	   (or (gretl-auto-fill)
	       (forward-line 1)))))
     t)))

;;; Completions

(defun gretl-initialize-completions ()
  "Create an alist for gretl completions."
  (if gretl-completion-alist
      ()
    (setq gretl-completion-alist
	  (mapcar #'(lambda (var) (cons var var))
		  (append gretl-command-words
			  gretl-genr-functions
			  gretl-internal-vars)))))

(defun gretl-complete-symbol ()
  "Perform completion on gretl symbol preceding point.
Compare that symbol against gretl's reserved words and builtin
variables."
  ;; This code taken from lisp-complete-symbol
  (interactive)
  (let* ((end (point))
	 (beg (save-excursion (backward-sexp 1) (point)))
	 (string (buffer-substring-no-properties beg end))
	 (completion (try-completion string gretl-completion-alist)))
    (cond ((eq completion t))		; ???
	  ((null completion)
	   (message "Can't find completion for \"%s\"" string)
	   (ding))
	  ((not (string= string completion))
           (delete-region beg end)
           (insert completion))
	  (t
	   (let ((list (all-completions string gretl-completion-alist))
		 (conf (current-window-configuration)))
	     ;; Taken from comint.el
	     (message "Making completion list...")
	     (with-output-to-temp-buffer "*Completions*"
	       (display-completion-list list))
	     (message "Hit space to flush")
	     (let (key first)
	       (if (with-current-buffer (get-buffer "*Completions*")
		     (setq key (read-key-sequence nil)
			   first (aref key 0))
		     (and (consp first) (consp (event-start first))
			  (eq (window-buffer (posn-window (event-start
							   first)))
			      (get-buffer "*Completions*"))
			  (eq (key-binding key) 'choose-completion)))
		   (progn
		     (choose-completion first)
		     (set-window-configuration conf))
		 (if (eq first ?\ )
		     (set-window-configuration conf)
		   (setq unread-command-events
			 (listify-key-sequence key))))))))))

;;; Electric characters && friends

(defun gretl-reindent-then-newline-and-indent ()
  "Reindent current gretl line, insert newline, and indent the new line.
If Abbrev mode is on, expand abbrevs first."
  (interactive)
  (if abbrev-mode (expand-abbrev))
  (if gretl-blink-matching-block
      (gretl-blink-matching-block-open))
  (save-excursion
    (delete-region (point) (progn (skip-chars-backward " \t") (point)))
    (indent-according-to-mode))
  (insert "\n")
  (indent-according-to-mode))

(defun gretl-electric-semi ()
  "Insert a semicolon in gretl mode.
Maybe expand abbrevs and blink matching block open keywords.
Reindent the line of `gretl-auto-indent' is non-nil.
Insert a newline if `gretl-auto-newline' is non-nil."
  (interactive)
  (if (not (gretl-not-in-string-or-comment-p))
      (insert ";")
    (if abbrev-mode (expand-abbrev))
    (if gretl-blink-matching-block
	(gretl-blink-matching-block-open))
    (if gretl-auto-indent
	(indent-according-to-mode))
    (insert ";")
    (if gretl-auto-newline
	(newline-and-indent))))

(defun gretl-electric-space ()
  "Insert a space in gretl mode.
Maybe expand abbrevs and blink matching block open keywords.
Reindent the line of `gretl-auto-indent' is non-nil."
  (interactive)
  (setq last-command-event ? )
  (if (not (gretl-not-in-string-or-comment-p))
      (progn
	(indent-according-to-mode)
	(self-insert-command 1))
    (if abbrev-mode (expand-abbrev))
    (if gretl-blink-matching-block
	(gretl-blink-matching-block-open))
    (if (and gretl-auto-indent
	     (save-excursion
	       (skip-syntax-backward " ")
	       (not (bolp))))
	(indent-according-to-mode))
    (self-insert-command 1)))

(defun gretl-abbrev-start ()
  "Start entering an gretl abbreviation.
If Abbrev mode is turned on, typing ` (grave accent) followed by ? or
\\[help-command] lists all gretl abbrevs.  Any other key combination is
executed normally.
Note that all gretl mode abbrevs start with a grave accent."
  (interactive)
  (if (not abbrev-mode)
      (self-insert-command 1)
    (let (c)
      (insert last-command-event)
      (if (if gretl-xemacs-p
	      (or (eq (event-to-character (setq c (next-event))) ??)
		  (eq (event-to-character c) help-char))
	    (or (eq (setq c (read-event)) ??)
		(eq c help-char)))
	  (let ((abbrev-table-name-list '(gretl-abbrev-table)))
	    (list-abbrevs))
	(setq unread-command-events (list c))))))

;;; Menu

(defun gretl-add-gretl-menu ()
  "Adds the `gretl' menu to the menu bar in gretl mode."
  (require 'easymenu)
  (easy-menu-define gretl-mode-menu-map gretl-mode-map
		    "Menu keymap for gretl mode." gretl-mode-menu)
  (easy-menu-add gretl-mode-menu-map gretl-mode-map))

;;; Execution

(defun gretl-run-buffer ()
  "Run the current gretl script, sending the results to a buffer."
  (interactive)
  (let ((save-pop-frames pop-up-frames) (gretl-buf (buffer-name)))
    (defvar gretl-tempfile)
    (setq gretl-tempfile "/tmp/gretltmp.inp")
    (setq pop-up-frames gretl-output-in-new-frame)
    (write-region (point-min) (point-max) gretl-tempfile nil 1 nil nil)
    (get-buffer-create "gretl_output")
    (pop-to-buffer "gretl_output" nil nil)
    (erase-buffer)
    (call-process gretl-program nil "gretl_output" nil  "-q" "-b" gretl-tempfile)
    (goto-char (point-min))
    (call-process gretl-remove-file nil nil nil gretl-tempfile)
    (pop-to-buffer gretl-buf)
    (setq pop-up-frames save-pop-frames)))

;;; Bug reporting

(defun gretl-submit-bug-report ()
  "Submit a bug report on the Emacs gretl package via mail."
  (interactive)
  (require 'reporter)
  (and
   (y-or-n-p "Do you want to submit a bug report? ")
   (reporter-submit-bug-report
    gretl-maintainer-address
    (concat "Emacs version " emacs-version)
    (list
     'gretl-auto-indent
     'gretl-auto-newline
     'gretl-blink-matching-block
     'gretl-block-offset
     'gretl-comment-char
     'gretl-continuation-offset
     'gretl-continuation-string))))

;;; provide ourself

(provide 'gretl)

;;; gretl.el ends here
