octave.el 72.2 KB
Newer Older
1
;;; octave.el --- editing octave source files under emacs  -*- lexical-binding: t; -*-
Richard M. Stallman's avatar
Richard M. Stallman committed
2

3
;; Copyright (C) 1997, 2001-2014 Free Software Foundation, Inc.
Richard M. Stallman's avatar
Richard M. Stallman committed
4

5
;; Author: Kurt Hornik <Kurt.Hornik@wu-wien.ac.at>
Glenn Morris's avatar
Glenn Morris committed
6
;;	   John Eaton <jwe@octave.org>
7
;; Maintainer: emacs-devel@gnu.org
Richard M. Stallman's avatar
Richard M. Stallman committed
8 9 10 11
;; Keywords: languages

;; This file is part of GNU Emacs.

12
;; GNU Emacs is free software: you can redistribute it and/or modify
Richard M. Stallman's avatar
Richard M. Stallman committed
13
;; it under the terms of the GNU General Public License as published by
14 15
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
Richard M. Stallman's avatar
Richard M. Stallman committed
16 17 18 19 20 21 22

;; GNU Emacs 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
23
;; along with GNU Emacs.  If not, see <http://www.gnu.org/licenses/>.
Richard M. Stallman's avatar
Richard M. Stallman committed
24 25 26

;;; Commentary:

27
;; This package provides Emacs support for Octave.  It defines a major
28 29
;; mode for editing Octave code and contains code for interacting with
;; an inferior Octave process using comint.
30

31 32
;; See the documentation of `octave-mode' and `run-octave' for further
;; information on usage and customization.
Richard M. Stallman's avatar
Richard M. Stallman committed
33

34
;;; Code:
35
(require 'comint)
Richard M. Stallman's avatar
Richard M. Stallman committed
36

37 38
;;; For emacs < 24.3.
(require 'newcomment)
39 40
(eval-and-compile
  (unless (fboundp 'user-error)
41 42
    (defalias 'user-error 'error))
  (unless (fboundp 'delete-consecutive-dups)
Leo Liu's avatar
Leo Liu committed
43 44 45 46 47 48 49 50 51 52 53 54 55
    (defalias 'delete-consecutive-dups 'delete-dups))
  (unless (fboundp 'completion-table-with-cache)
    (defun completion-table-with-cache (fun &optional ignore-case)
      ;; See eg bug#11906.
      (let* (last-arg last-result
                      (new-fun
                       (lambda (arg)
                         (if (and last-arg (string-prefix-p last-arg arg ignore-case))
                             last-result
                           (prog1
                               (setq last-result (funcall fun arg))
                             (setq last-arg arg))))))
        (completion-table-dynamic new-fun)))))
56 57 58 59 60 61
(eval-when-compile
  (unless (fboundp 'setq-local)
    (defmacro setq-local (var val)
      "Set variable VAR to value VAL in current buffer."
      (list 'set (list 'make-local-variable (list 'quote var)) val))))

Andreas Schwab's avatar
Andreas Schwab committed
62
(defgroup octave nil
63
  "Editing Octave code."
64 65
  :link '(custom-manual "(octave-mode)Top")
  :link '(url-link "http://www.gnu.org/s/octave")
66
  :link '(custom-group-link :tag "Font Lock Faces group" font-lock-faces)
Andreas Schwab's avatar
Andreas Schwab committed
67 68
  :group 'languages)

69 70
(define-obsolete-function-alias 'octave-submit-bug-report
  'report-emacs-bug "24.4")
71

72
(define-abbrev-table 'octave-abbrev-table nil
73
  "Abbrev table for Octave's reserved words.
74
Used in `octave-mode' and `inferior-octave-mode' buffers.")
75 76 77

(defvar octave-comment-char ?#
  "Character to start an Octave comment.")
78

79 80
(defvar octave-comment-start (char-to-string octave-comment-char)
  "Octave-specific `comment-start' (which see).")
81

82 83
(defvar octave-comment-start-skip "\\(^\\|\\S<\\)\\(?:%!\\|\\s<+\\)\\s-*"
  "Octave-specific `comment-start-skip' (which see).")
84 85

(defvar octave-begin-keywords
86 87
  '("classdef" "do" "enumeration" "events" "for" "function" "if" "methods"
    "parfor" "properties" "switch" "try" "unwind_protect" "while"))
88

89
(defvar octave-else-keywords
90
  '("case" "catch" "else" "elseif" "otherwise" "unwind_protect_cleanup"))
91

92
(defvar octave-end-keywords
93 94
  '("endclassdef" "endenumeration" "endevents" "endfor" "endfunction" "endif"
    "endmethods" "endparfor" "endproperties" "endswitch" "end_try_catch"
95
    "end_unwind_protect" "endwhile" "until" "end"))
96 97

(defvar octave-reserved-words
98 99 100
  (append octave-begin-keywords
	  octave-else-keywords
	  octave-end-keywords
101
	  '("break" "continue" "global" "persistent" "return"))
102 103 104
  "Reserved words in Octave.")

(defvar octave-function-header-regexp
105
  (concat "^\\s-*\\_<\\(function\\)\\_>"
106
	  "\\([^=;(\n]*=[ \t]*\\|[ \t]*\\)\\(\\(?:\\w\\|\\s_\\)+\\)\\_>")
107 108 109 110 111
  "Regexp to match an Octave function header.
The string `function' and its name are given by the first and third
parenthetical grouping.")


112
(defvar octave-mode-map
Richard M. Stallman's avatar
Richard M. Stallman committed
113
  (let ((map (make-sparse-keymap)))
114 115
    (define-key map "\M-."     'octave-find-definition)
    (define-key map "\M-\C-j"  'octave-indent-new-comment-line)
Richard M. Stallman's avatar
Richard M. Stallman committed
116 117 118
    (define-key map "\C-c\C-p" 'octave-previous-code-line)
    (define-key map "\C-c\C-n" 'octave-next-code-line)
    (define-key map "\C-c\C-a" 'octave-beginning-of-line)
119
    (define-key map "\C-c\C-e" 'octave-end-of-line)
120
    (define-key map [remap down-list] 'smie-down-list)
Richard M. Stallman's avatar
Richard M. Stallman committed
121
    (define-key map "\C-c\M-\C-h" 'octave-mark-block)
Stefan Monnier's avatar
Stefan Monnier committed
122 123
    (define-key map "\C-c]" 'smie-close-block)
    (define-key map "\C-c/" 'smie-close-block)
124
    (define-key map "\C-c;" 'octave-update-function-file-comment)
125
    (define-key map "\C-hd" 'octave-help)
126
    (define-key map "\C-ha" 'octave-lookfor)
127
    (define-key map "\C-c\C-l" 'octave-source-file)
128 129 130 131
    (define-key map "\C-c\C-f" 'octave-insert-defun)
    (define-key map "\C-c\C-il" 'octave-send-line)
    (define-key map "\C-c\C-ib" 'octave-send-block)
    (define-key map "\C-c\C-if" 'octave-send-defun)
132
    (define-key map "\C-c\C-ir" 'octave-send-region)
133
    (define-key map "\C-c\C-ia" 'octave-send-buffer)
134
    (define-key map "\C-c\C-is" 'octave-show-process-buffer)
Leo Liu's avatar
Leo Liu committed
135
    (define-key map "\C-c\C-iq" 'octave-hide-process-buffer)
136 137 138 139
    (define-key map "\C-c\C-ik" 'octave-kill-process)
    (define-key map "\C-c\C-i\C-l" 'octave-send-line)
    (define-key map "\C-c\C-i\C-b" 'octave-send-block)
    (define-key map "\C-c\C-i\C-f" 'octave-send-defun)
140
    (define-key map "\C-c\C-i\C-r" 'octave-send-region)
141
    (define-key map "\C-c\C-i\C-a" 'octave-send-buffer)
142
    (define-key map "\C-c\C-i\C-s" 'octave-show-process-buffer)
143
    (define-key map "\C-c\C-i\C-q" 'octave-hide-process-buffer)
144
    (define-key map "\C-c\C-i\C-k" 'octave-kill-process)
145 146 147
    map)
  "Keymap used in Octave mode.")

Richard M. Stallman's avatar
Richard M. Stallman committed
148

149 150 151

(easy-menu-define octave-mode-menu octave-mode-map
  "Menu for Octave mode."
152
  '("Octave"
153 154 155 156 157 158 159 160 161
    ["Split Line at Point"          octave-indent-new-comment-line t]
    ["Previous Code Line"           octave-previous-code-line t]
    ["Next Code Line"               octave-next-code-line t]
    ["Begin of Line"                octave-beginning-of-line t]
    ["End of Line"                  octave-end-of-line t]
    ["Mark Block"                   octave-mark-block t]
    ["Close Block"                  smie-close-block t]
    "---"
    ["Start Octave Process"         run-octave t]
Leo Liu's avatar
Leo Liu committed
162
    ["Documentation Lookup"         info-lookup-symbol t]
163
    ["Help on Function"             octave-help t]
164
    ["Search help"                  octave-lookfor t]
165 166 167 168
    ["Find Function Definition"     octave-find-definition t]
    ["Insert Function"              octave-insert-defun t]
    ["Update Function File Comment" octave-update-function-file-comment t]
    "---"
169 170
    ["Function Syntax Hints" (eldoc-mode 'toggle)
     :style toggle :selected (bound-and-true-p eldoc-mode)
171
     :help "Display function signatures after typing `SPC' or `('"]
172 173
    ["Delimiter Matching"           show-paren-mode
     :style toggle :selected show-paren-mode
174
     :help "Highlight matched pairs such as `if ... end'"
175
     :visible (fboundp 'smie--matching-block-data)]
176 177 178 179 180 181 182
    ["Auto Fill"                    auto-fill-mode
     :style toggle :selected auto-fill-function
     :help "Automatic line breaking"]
    ["Electric Layout"              electric-layout-mode
     :style toggle :selected electric-layout-mode
     :help "Automatically insert newlines around some chars"]
    "---"
183
    ("Debug"
184 185 186 187
     ["Send Current Line"       octave-send-line t]
     ["Send Current Block"      octave-send-block t]
     ["Send Current Function"   octave-send-defun t]
     ["Send Region"             octave-send-region t]
188
     ["Send Buffer"             octave-send-buffer t]
189
     ["Source Current File"     octave-source-file t]
190 191 192 193
     ["Show Process Buffer"     octave-show-process-buffer t]
     ["Hide Process Buffer"     octave-hide-process-buffer t]
     ["Kill Process"            octave-kill-process t])
    "---"
194
    ["Octave Mode Manual"       (info "(octave-mode)Top") t]
195 196
    ["Customize Octave"         (customize-group 'octave) t]
    ["Submit Bug Report"        report-emacs-bug t]))
Richard M. Stallman's avatar
Richard M. Stallman committed
197

198
(defvar octave-mode-syntax-table
Richard M. Stallman's avatar
Richard M. Stallman committed
199 200 201 202 203 204 205 206 207 208 209 210
  (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)
211
    (modify-syntax-entry ?\\ "."  table)
Richard M. Stallman's avatar
Richard M. Stallman committed
212
    (modify-syntax-entry ?\' "."  table)
213
    (modify-syntax-entry ?\` "."  table)
214
    (modify-syntax-entry ?. "."   table)
Richard M. Stallman's avatar
Richard M. Stallman committed
215
    (modify-syntax-entry ?\" "\"" table)
216
    (modify-syntax-entry ?_ "_"   table)
217
    ;; The "b" flag only applies to the second letter of the comstart
Stefan Monnier's avatar
Stefan Monnier committed
218 219 220 221
    ;; and the first letter of the comend, i.e. the "4b" below is ineffective.
    ;; If we try to put `b' on the single-line comments, we get a similar
    ;; problem where the % and # chars appear as first chars of the 2-char
    ;; comend, so the multi-line ender is also turned into style-b.
222
    ;; So we need the new "c" comment style.
223 224
    (modify-syntax-entry ?\% "< 13"  table)
    (modify-syntax-entry ?\# "< 13"  table)
225 226
    (modify-syntax-entry ?\{ "(} 2c"  table)
    (modify-syntax-entry ?\} "){ 4c"  table)
Richard M. Stallman's avatar
Richard M. Stallman committed
227
    (modify-syntax-entry ?\n ">"  table)
228 229
    table)
  "Syntax table in use in `octave-mode' buffers.")
Richard M. Stallman's avatar
Richard M. Stallman committed
230

231 232 233 234 235 236
(defcustom octave-font-lock-texinfo-comment t
  "Control whether to highlight the texinfo comment block."
  :type 'boolean
  :group 'octave
  :version "24.4")

Andreas Schwab's avatar
Andreas Schwab committed
237
(defcustom octave-blink-matching-block t
238
  "Control the blinking of matching Octave block keywords.
Richard M. Stallman's avatar
Richard M. Stallman committed
239
Non-nil means show matching begin of block when inserting a space,
Andreas Schwab's avatar
Andreas Schwab committed
240 241 242
newline or semicolon after an else or end keyword."
  :type 'boolean
  :group 'octave)
243

Andreas Schwab's avatar
Andreas Schwab committed
244
(defcustom octave-block-offset 2
245
  "Extra indentation applied to statements in Octave block structures."
Andreas Schwab's avatar
Andreas Schwab committed
246 247
  :type 'integer
  :group 'octave)
Richard M. Stallman's avatar
Richard M. Stallman committed
248 249 250 251 252

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

Andreas Schwab's avatar
Andreas Schwab committed
253
(defcustom octave-continuation-offset 4
254
  "Extra indentation applied to Octave continuation lines."
Andreas Schwab's avatar
Andreas Schwab committed
255 256
  :type 'integer
  :group 'octave)
257

Stefan Monnier's avatar
Stefan Monnier committed
258 259
(eval-and-compile
  (defconst octave-continuation-marker-regexp "\\\\\\|\\.\\.\\."))
260

Richard M. Stallman's avatar
Richard M. Stallman committed
261
(defvar octave-continuation-regexp
Stefan Monnier's avatar
Stefan Monnier committed
262 263
  (concat "[^#%\n]*\\(" octave-continuation-marker-regexp
          "\\)\\s-*\\(\\s<.*\\)?$"))
264 265 266 267

;; Char \ is considered a bad decision for continuing a line.
(defconst octave-continuation-string "..."
  "Character string used for Octave continuation lines.")
Richard M. Stallman's avatar
Richard M. Stallman committed
268 269 270 271 272 273 274

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

Andreas Schwab's avatar
Andreas Schwab committed
275
(defcustom octave-mode-hook nil
276
  "Hook to be run when Octave mode is started."
Andreas Schwab's avatar
Andreas Schwab committed
277 278 279 280
  :type 'hook
  :group 'octave)

(defcustom octave-send-show-buffer t
281
  "Non-nil means display `inferior-octave-buffer' after sending to it."
Andreas Schwab's avatar
Andreas Schwab committed
282 283
  :type 'boolean
  :group 'octave)
284

Andreas Schwab's avatar
Andreas Schwab committed
285
(defcustom octave-send-line-auto-forward t
286
  "Control auto-forward after sending to the inferior Octave process.
Andreas Schwab's avatar
Andreas Schwab committed
287 288 289
Non-nil means always go to the next Octave code line after sending."
  :type 'boolean
  :group 'octave)
290

Andreas Schwab's avatar
Andreas Schwab committed
291
(defcustom octave-send-echo-input t
292
  "Non-nil means echo input sent to the inferior Octave process."
Andreas Schwab's avatar
Andreas Schwab committed
293 294
  :type 'boolean
  :group 'octave)
Richard M. Stallman's avatar
Richard M. Stallman committed
295 296


Stefan Monnier's avatar
Stefan Monnier committed
297 298 299 300
;;; SMIE indentation

(require 'smie)

301
;; Use '__operators__' in Octave REPL to get a full list.
Stefan Monnier's avatar
Stefan Monnier committed
302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317
(defconst octave-operator-table
  '((assoc ";" "\n") (assoc ",") ; The doc claims they have equal precedence!?
    (right "=" "+=" "-=" "*=" "/=")
    (assoc "&&") (assoc "||") ; The doc claims they have equal precedence!?
    (assoc "&") (assoc "|")   ; The doc claims they have equal precedence!?
    (nonassoc "<" "<=" "==" ">=" ">" "!=" "~=")
    (nonassoc ":")                      ;No idea what this is.
    (assoc "+" "-")
    (assoc "*" "/" "\\" ".\\" ".*" "./")
    (nonassoc "'" ".'")
    (nonassoc "++" "--" "!" "~")        ;And unary "+" and "-".
    (right "^" "**" ".^" ".**")
    ;; It's not really an operator, but for indentation purposes it
    ;; could be convenient to treat it as one.
    (assoc "...")))

318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335
(defconst octave-smie-bnf-table
  '((atom)
    ;; We can't distinguish the first element in a sequence with
    ;; precedence grammars, so we can't distinguish the condition
    ;; if the `if' from the subsequent body, for example.
    ;; This has to be done later in the indentation rules.
    (exp (exp "\n" exp)
         ;; We need to mention at least one of the operators in this part
         ;; of the grammar: if the BNF and the operator table have
         ;; no overlap, SMIE can't know how they relate.
         (exp ";" exp)
         ("try" exp "catch" exp "end_try_catch")
         ("try" exp "catch" exp "end")
         ("unwind_protect" exp
          "unwind_protect_cleanup" exp "end_unwind_protect")
         ("unwind_protect" exp "unwind_protect_cleanup" exp "end")
         ("for" exp "endfor")
         ("for" exp "end")
336 337
         ("parfor" exp "endparfor")
         ("parfor" exp "end")
338 339 340 341 342 343 344 345 346 347 348 349 350
         ("do" exp "until" atom)
         ("while" exp "endwhile")
         ("while" exp "end")
         ("if" exp "endif")
         ("if" exp "else" exp "endif")
         ("if" exp "elseif" exp "else" exp "endif")
         ("if" exp "elseif" exp "elseif" exp "else" exp "endif")
         ("if" exp "elseif" exp "elseif" exp "else" exp "end")
         ("switch" exp "case" exp "endswitch")
         ("switch" exp "case" exp "otherwise" exp "endswitch")
         ("switch" exp "case" exp "case" exp "otherwise" exp "endswitch")
         ("switch" exp "case" exp "case" exp "otherwise" exp "end")
         ("function" exp "endfunction")
351 352 353 354 355 356 357 358 359 360 361
         ("function" exp "end")
         ("enumeration" exp "endenumeration")
         ("enumeration" exp "end")
         ("events" exp "endevents")
         ("events" exp "end")
         ("methods" exp "endmethods")
         ("methods" exp "end")
         ("properties" exp "endproperties")
         ("properties" exp "end")
         ("classdef" exp "endclassdef")
         ("classdef" exp "end"))
362 363 364
    ;; (fundesc (atom "=" atom))
    ))

365 366
(defconst octave-smie-grammar
  (smie-prec2->grammar
Stefan Monnier's avatar
Stefan Monnier committed
367
   (smie-merge-prec2s
368 369
    (smie-bnf->prec2 octave-smie-bnf-table
                     '((assoc "\n" ";")))
Stefan Monnier's avatar
Stefan Monnier committed
370

371
    (smie-precs->prec2 octave-operator-table))))
Stefan Monnier's avatar
Stefan Monnier committed
372 373 374 375 376 377

;; Tokenizing needs to be refined so that ";;" is treated as two
;; tokens and also so as to recognize the \n separator (and
;; corresponding continuation lines).

(defconst octave-operator-regexp
378 379
  (regexp-opt (remove "\n" (apply 'append
                                  (mapcar 'cdr octave-operator-table)))))
Stefan Monnier's avatar
Stefan Monnier committed
380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415

(defun octave-smie-backward-token ()
  (let ((pos (point)))
    (forward-comment (- (point)))
    (cond
     ((and (not (eq (char-before) ?\;)) ;Coalesce ";" and "\n".
           (> pos (line-end-position))
           (if (looking-back octave-continuation-marker-regexp (- (point) 3))
               (progn
                 (goto-char (match-beginning 0))
                 (forward-comment (- (point)))
                 nil)
             t)
           ;; Ignore it if it's within parentheses.
           (let ((ppss (syntax-ppss)))
             (not (and (nth 1 ppss)
                       (eq ?\( (char-after (nth 1 ppss)))))))
      (skip-chars-forward " \t")
      ;; Why bother distinguishing \n and ;?
      ";") ;;"\n"
     ((and (looking-back octave-operator-regexp (- (point) 3) 'greedy)
           ;; Don't mistake a string quote for a transpose.
           (not (looking-back "\\s\"" (1- (point)))))
      (goto-char (match-beginning 0))
      (match-string-no-properties 0))
     (t
      (smie-default-backward-token)))))

(defun octave-smie-forward-token ()
  (skip-chars-forward " \t")
  (when (looking-at (eval-when-compile
                      (concat "\\(" octave-continuation-marker-regexp
                              "\\)[ \t]*\\($\\|[%#]\\)")))
    (goto-char (match-end 1))
    (forward-comment 1))
  (cond
416 417 418
   ((and (looking-at "[%#\n]")
         (not (or (save-excursion (skip-chars-backward " \t")
                                  ;; Only add implicit ; when needed.
419
                                  (or (bolp) (eq (char-before) ?\;)))
420 421 422 423 424
                  ;; Ignore it if it's within parentheses.
                  (let ((ppss (syntax-ppss)))
                    (and (nth 1 ppss)
                         (eq ?\( (char-after (nth 1 ppss))))))))
    (if (eolp) (forward-char 1) (forward-comment 1))
Stefan Monnier's avatar
Stefan Monnier committed
425 426
    ;; Why bother distinguishing \n and ;?
    ";") ;;"\n"
427
   ((progn (forward-comment (point-max)) nil))
Stefan Monnier's avatar
Stefan Monnier committed
428 429 430 431 432 433 434 435 436 437 438 439 440
   ((looking-at ";[ \t]*\\($\\|[%#]\\)")
    ;; Combine the ; with the subsequent \n.
    (goto-char (match-beginning 1))
    (forward-comment 1)
    ";")
   ((and (looking-at octave-operator-regexp)
         ;; Don't mistake a string quote for a transpose.
         (not (looking-at "\\s\"")))
    (goto-char (match-end 0))
    (match-string-no-properties 0))
   (t
    (smie-default-forward-token))))

441 442
(defun octave-smie-rules (kind token)
  (pcase (cons kind token)
443 444 445 446
    ;; We could set smie-indent-basic instead, but that would have two
    ;; disadvantages:
    ;; - changes to octave-block-offset wouldn't take effect immediately.
    ;; - edebug wouldn't show the use of this variable.
447
    (`(:elem . basic) octave-block-offset)
448 449 450
    ;; Since "case" is in the same BNF rules as switch..end, SMIE by default
    ;; aligns it with "switch".
    (`(:before . "case") (if (not (smie-rule-sibling-p)) octave-block-offset))
451
    (`(:after . ";")
452 453 454 455
     (if (smie-rule-parent-p "classdef" "events" "enumeration" "function" "if"
                             "while" "else" "elseif" "for" "parfor"
                             "properties" "methods" "otherwise" "case"
                             "try" "catch" "unwind_protect"
456 457
                             "unwind_protect_cleanup")
         (smie-rule-parent octave-block-offset)
458
       ;; For (invalid) code between switch and case.
459
       ;; (if (smie-rule-parent-p "switch") 4)
460
       nil))))
Stefan Monnier's avatar
Stefan Monnier committed
461

462 463 464 465
(defun octave-indent-comment ()
  "A function for `smie-indent-functions' (which see)."
  (save-excursion
    (back-to-indentation)
466 467
    (cond
     ((octave-in-string-or-comment-p) nil)
468
     ((looking-at-p "\\(\\s<\\)\\1\\{2,\\}")
469
      0)
470 471
     ;; Exclude %{, %} and %!.
     ((and (looking-at-p "\\s<\\(?:[^{}!]\\|$\\)")
472
           (not (looking-at-p "\\(\\s<\\)\\1")))
473
      (comment-choose-indent)))))
474

475 476 477 478 479 480 481 482 483 484 485 486 487 488 489

(defvar octave-font-lock-keywords
  (list
   ;; Fontify all builtin keywords.
   (cons (concat "\\_<\\("
                 (regexp-opt octave-reserved-words)
                 "\\)\\_>")
         'font-lock-keyword-face)
   ;; Note: 'end' also serves as the last index in an indexing expression.
   ;; Ref: http://www.mathworks.com/help/matlab/ref/end.html
   (list (lambda (limit)
           (while (re-search-forward "\\_<end\\_>" limit 'move)
             (let ((beg (match-beginning 0))
                   (end (match-end 0)))
               (unless (octave-in-string-or-comment-p)
490
                 (condition-case nil
491 492 493 494
                     (progn
                       (goto-char beg)
                       (backward-up-list)
                       (when (memq (char-after) '(?\( ?\[ ?\{))
495 496 497
                         (put-text-property beg end 'face nil))
                       (goto-char end))
                   (error (goto-char end))))))
498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533
           nil))
   ;; Fontify all operators.
   (cons octave-operator-regexp 'font-lock-builtin-face)
   ;; Fontify all function declarations.
   (list octave-function-header-regexp
         '(1 font-lock-keyword-face)
         '(3 font-lock-function-name-face nil t)))
  "Additional Octave expressions to highlight.")

(defun octave-syntax-propertize-function (start end)
  (goto-char start)
  (octave-syntax-propertize-sqs end)
  (funcall (syntax-propertize-rules
            ("\\\\" (0 (when (eq (nth 3 (save-excursion
                                          (syntax-ppss (match-beginning 0))))
                                 ?\")
                         (string-to-syntax "\\"))))
            ;; Try to distinguish the string-quotes from the transpose-quotes.
            ("\\(?:^\\|[[({,; ]\\)\\('\\)"
             (1 (prog1 "\"'" (octave-syntax-propertize-sqs end)))))
           (point) end))

(defun octave-syntax-propertize-sqs (end)
  "Propertize the content/end of single-quote strings."
  (when (eq (nth 3 (syntax-ppss)) ?\')
    ;; A '..' string.
    (when (re-search-forward
           "\\(?:\\=\\|[^']\\)\\(?:''\\)*\\('\\)\\($\\|[^']\\)" end 'move)
      (goto-char (match-beginning 2))
      (when (eq (char-before (match-beginning 1)) ?\\)
        ;; Backslash cannot escape a single quote.
        (put-text-property (1- (match-beginning 1)) (match-beginning 1)
                           'syntax-table (string-to-syntax ".")))
      (put-text-property (match-beginning 1) (match-end 1)
                         'syntax-table (string-to-syntax "\"'")))))

534
(defvar electric-layout-rules)
535

Richard M. Stallman's avatar
Richard M. Stallman committed
536
;;;###autoload
537
(define-derived-mode octave-mode prog-mode "Octave"
Richard M. Stallman's avatar
Richard M. Stallman committed
538 539
  "Major mode for editing Octave code.

540 541 542
Octave is a high-level language, primarily intended for numerical
computations.  It provides a convenient command line interface
for solving linear and nonlinear problems numerically.  Function
543 544 545 546 547 548
definitions can also be stored in files and used in batch mode.

See Info node `(octave-mode) Using Octave Mode' for more details.

Key bindings:
\\{octave-mode-map}"
549
  :abbrev-table octave-abbrev-table
550
  :group 'octave
551

552
  (smie-setup octave-smie-grammar #'octave-smie-rules
553 554
              :forward-token  #'octave-smie-forward-token
              :backward-token #'octave-smie-backward-token)
555
  (setq-local smie-indent-basic 'octave-block-offset)
556
  (add-hook 'smie-indent-functions #'octave-indent-comment nil t)
557

558 559
  (setq-local smie-blink-matching-triggers
              (cons ?\; smie-blink-matching-triggers))
560 561
  (unless octave-blink-matching-block
    (remove-hook 'post-self-insert-hook #'smie-blink-matching-open 'local))
562

563 564
  (setq-local electric-indent-chars
              (cons ?\; electric-indent-chars))
565 566
  ;; IIUC matlab-mode takes the opposite approach: it makes RET insert
  ;; a ";" at those places where it's correct (i.e. outside of parens).
567
  (setq-local electric-layout-rules '((?\; . after)))
568

569
  (setq-local comment-use-syntax t)
570 571
  (setq-local comment-start octave-comment-start)
  (setq-local comment-end "")
572
  (setq-local comment-start-skip octave-comment-start-skip)
573 574 575 576 577 578 579
  (setq-local comment-add 1)

  (setq-local parse-sexp-ignore-comments t)
  (setq-local paragraph-start (concat "\\s-*$\\|" page-delimiter))
  (setq-local paragraph-separate paragraph-start)
  (setq-local paragraph-ignore-fill-prefix t)
  (setq-local fill-paragraph-function 'octave-fill-paragraph)
580

581 582
  (setq-local fill-nobreak-predicate
              (lambda () (eq (octave-in-string-p) ?')))
583 584 585 586 587 588 589
  (with-no-warnings
    (if (fboundp 'add-function)         ; new in 24.4
        (add-function :around (local 'comment-line-break-function)
                      #'octave--indent-new-comment-line)
      (setq-local comment-line-break-function
                  (apply-partially #'octave--indent-new-comment-line
                                   #'comment-indent-new-line))))
590

591
  (setq font-lock-defaults '(octave-font-lock-keywords))
592

593
  (setq-local syntax-propertize-function #'octave-syntax-propertize-function)
594

595 596
  (setq-local imenu-generic-expression octave-mode-imenu-generic-expression)
  (setq-local imenu-case-fold-search nil)
597

598 599
  (setq-local add-log-current-defun-function #'octave-add-log-current-defun)

600
  (add-hook 'completion-at-point-functions 'octave-completion-at-point nil t)
601
  (add-hook 'before-save-hook 'octave-sync-function-file-names nil t)
602
  (setq-local beginning-of-defun-function 'octave-beginning-of-defun)
603
  (and octave-font-lock-texinfo-comment (octave-font-lock-texinfo-comment))
604
  (setq-local eldoc-documentation-function 'octave-eldoc-function)
Richard M. Stallman's avatar
Richard M. Stallman committed
605

606
  (easy-menu-add octave-mode-menu))
607 608 609 610 611 612 613 614 615 616 617 618 619


(defcustom inferior-octave-program "octave"
  "Program invoked by `inferior-octave'."
  :type 'string
  :group 'octave)

(defcustom inferior-octave-buffer "*Inferior Octave*"
  "Name of buffer for running an inferior Octave process."
  :type 'string
  :group 'octave)

(defcustom inferior-octave-prompt
620 621 622
  ;; For Octave >= 3.8, default is always 'octave', see
  ;; http://hg.savannah.gnu.org/hgweb/octave/rev/708173343c50
  "\\(?:^octave\\(?:.bin\\|.exe\\)?\\(?:-[.0-9]+\\)?\\(?::[0-9]+\\)?\\|^debug\\|^\\)>+ "
623 624 625 626
  "Regexp to match prompts for the inferior Octave process."
  :type 'regexp
  :group 'octave)

627
(defcustom inferior-octave-prompt-read-only comint-prompt-read-only
628
  "If non-nil, the Octave prompt is read only.
629
See `comint-prompt-read-only' for details."
630 631 632 633
  :type 'boolean
  :group 'octave
  :version "24.4")

634
(defcustom inferior-octave-startup-file
635 636
  (let ((n (file-name-nondirectory inferior-octave-program)))
    (locate-user-emacs-file (format "init_%s.m" n) (format ".emacs-%s" n)))
637 638 639
  "Name of the inferior Octave startup file.
The contents of this file are sent to the inferior Octave process on
startup."
640 641 642
  :type '(choice (const :tag "None" nil) file)
  :group 'octave
  :version "24.4")
643

644
(defcustom inferior-octave-startup-args '("-i" "--no-line-editing")
645 646
  "List of command line arguments for the inferior Octave process.
For example, for suppressing the startup message and using `traditional'
647
mode, include \"-q\" and \"--traditional\"."
648
  :type '(repeat string)
649 650
  :group 'octave
  :version "24.4")
651 652 653 654 655 656

(defcustom inferior-octave-mode-hook nil
  "Hook to be run when Inferior Octave mode is started."
  :type 'hook
  :group 'octave)

657 658 659 660 661 662
(defcustom inferior-octave-error-regexp-alist
  '(("error:\\s-*\\(.*?\\) at line \\([0-9]+\\), column \\([0-9]+\\)"
     1 2 3 2 1)
    ("warning:\\s-*\\([^:\n]+\\):.*at line \\([0-9]+\\), column \\([0-9]+\\)"
     1 2 3 1 1))
  "Value for `compilation-error-regexp-alist' in inferior octave."
663
  :version "24.4"
664 665 666 667 668 669 670 671 672 673 674 675
  :type '(repeat (choice (symbol :tag "Predefined symbol")
                         (sexp :tag "Error specification")))
  :group 'octave)

(defvar inferior-octave-compilation-font-lock-keywords
  '(("\\_<PASS\\_>" . compilation-info-face)
    ("\\_<FAIL\\_>" . compilation-error-face)
    ("\\_<\\(warning\\):" 1 compilation-warning-face)
    ("\\_<\\(error\\):" 1 compilation-error-face)
    ("^\\s-*!!!!!.*\\|^.*failed$" . compilation-error-face))
  "Value for `compilation-mode-font-lock-keywords' in inferior octave.")

676 677 678 679 680
(defvar inferior-octave-process nil)

(defvar inferior-octave-mode-map
  (let ((map (make-sparse-keymap)))
    (set-keymap-parent map comint-mode-map)
681
    (define-key map "\M-." 'octave-find-definition)
682
    (define-key map "\t" 'completion-at-point)
683
    (define-key map "\C-hd" 'octave-help)
684
    (define-key map "\C-ha" 'octave-lookfor)
685
    ;; Same as in `shell-mode'.
686 687 688 689 690 691 692 693 694 695
    (define-key map "\M-?" 'comint-dynamic-list-filename-completions)
    (define-key map "\C-c\C-l" 'inferior-octave-dynamic-list-input-ring)
    (define-key map [menu-bar inout list-history]
      '("List Input History" . inferior-octave-dynamic-list-input-ring))
    map)
  "Keymap used in Inferior Octave mode.")

(defvar inferior-octave-mode-syntax-table
  (let ((table (make-syntax-table octave-mode-syntax-table)))
    table)
696
  "Syntax table in use in `inferior-octave-mode' buffers.")
697 698 699 700 701 702 703 704 705 706 707

(defvar inferior-octave-font-lock-keywords
  (list
   (cons inferior-octave-prompt 'font-lock-type-face))
  ;; Could certainly do more font locking in inferior Octave ...
  "Additional expressions to highlight in Inferior Octave mode.")

(defvar inferior-octave-output-list nil)
(defvar inferior-octave-output-string nil)
(defvar inferior-octave-receive-in-progress nil)

708 709
(define-obsolete-variable-alias 'inferior-octave-startup-hook
  'inferior-octave-mode-hook "24.4")
710 711 712 713 714 715 716 717

(defvar inferior-octave-dynamic-complete-functions
  '(inferior-octave-completion-at-point comint-filename-completion)
  "List of functions called to perform completion for inferior Octave.
This variable is used to initialize `comint-dynamic-complete-functions'
in the Inferior Octave buffer.")

(defvar info-lookup-mode)
718 719
(defvar compilation-error-regexp-alist)
(defvar compilation-mode-font-lock-keywords)
720

721 722
(declare-function compilation-forget-errors "compile" ())

723
(defun inferior-octave-process-live-p ()
724
  (process-live-p inferior-octave-process))
725

726
(define-derived-mode inferior-octave-mode comint-mode "Inferior Octave"
727 728 729 730 731 732 733
  "Major mode for interacting with an inferior Octave process.

See Info node `(octave-mode) Running Octave from Within Emacs' for more
details.

Key bindings:
\\{inferior-octave-mode-map}"
734
  :abbrev-table octave-abbrev-table
735 736
  :group 'octave

737
  (setq comint-prompt-regexp inferior-octave-prompt)
738

739
  (setq-local comment-use-syntax t)
740 741 742 743
  (setq-local comment-start octave-comment-start)
  (setq-local comment-end "")
  (setq comment-column 32)
  (setq-local comment-start-skip octave-comment-start-skip)
744

745
  (setq font-lock-defaults '(inferior-octave-font-lock-keywords nil nil))
746

747
  (setq-local info-lookup-mode 'octave-mode)
748
  (setq-local eldoc-documentation-function 'octave-eldoc-function)
749 750

  (setq comint-input-ring-file-name
751 752
        (or (getenv "OCTAVE_HISTFILE") "~/.octave_hist")
        comint-input-ring-size (or (getenv "OCTAVE_HISTSIZE") 1024))
753
  (comint-read-input-ring t)
754 755
  (setq-local comint-dynamic-complete-functions
              inferior-octave-dynamic-complete-functions)
756
  (setq-local comint-prompt-read-only inferior-octave-prompt-read-only)
757
  (add-hook 'comint-input-filter-functions
758
            'inferior-octave-directory-tracker nil t)
759 760 761
  ;; http://thread.gmane.org/gmane.comp.gnu.octave.general/48572
  (add-hook 'window-configuration-change-hook
            'inferior-octave-track-window-width-change nil t)
762 763 764
  (setq-local compilation-error-regexp-alist inferior-octave-error-regexp-alist)
  (setq-local compilation-mode-font-lock-keywords
              inferior-octave-compilation-font-lock-keywords)
765 766
  (compilation-shell-minor-mode 1)
  (compilation-forget-errors))
767 768 769 770 771 772 773 774 775 776 777 778 779 780 781

;;;###autoload
(defun inferior-octave (&optional arg)
  "Run an inferior Octave process, I/O via `inferior-octave-buffer'.
This buffer is put in Inferior Octave mode.  See `inferior-octave-mode'.

Unless ARG is non-nil, switches to this buffer.

The elements of the list `inferior-octave-startup-args' are sent as
command line arguments to the inferior Octave process on startup.

Additional commands to be executed on startup can be provided either in
the file specified by `inferior-octave-startup-file' or by the default
startup file, `~/.emacs-octave'."
  (interactive "P")
782
  (let ((buffer (get-buffer-create inferior-octave-buffer)))
783 784
    (unless arg
      (pop-to-buffer buffer))
785
    (unless (comint-check-proc buffer)
786
      (with-current-buffer buffer
787 788 789
        (inferior-octave-startup)
        (inferior-octave-mode)))
    buffer))
790 791 792 793 794 795 796

;;;###autoload
(defalias 'run-octave 'inferior-octave)

(defun inferior-octave-startup ()
  "Start an inferior Octave process."
  (let ((proc (comint-exec-1
797 798 799
               (substring inferior-octave-buffer 1 -1)
               inferior-octave-buffer
               inferior-octave-program
800 801 802 803 804 805 806
               (append
                inferior-octave-startup-args
                ;; --no-gui is introduced in Octave > 3.7
                (and (not (member "--no-gui" inferior-octave-startup-args))
                     (zerop (process-file inferior-octave-program
                                          nil nil nil "--no-gui" "--help"))
                     '("--no-gui"))))))
807
    (set-process-filter proc 'inferior-octave-output-digest)
808 809 810 811
    (setq inferior-octave-process proc
          inferior-octave-output-list nil
          inferior-octave-output-string nil
          inferior-octave-receive-in-progress t)
812 813 814 815 816 817

    ;; This may look complicated ... However, we need to make sure that
    ;; we additional startup code only AFTER Octave is ready (otherwise,
    ;; output may be mixed up).  Hence, we need to digest the Octave
    ;; output to see when it issues a prompt.
    (while inferior-octave-receive-in-progress
818 819 820 821 822 823 824
      (unless (inferior-octave-process-live-p)
        ;; Spit out the error messages.
        (when inferior-octave-output-list
          (princ (concat (mapconcat 'identity inferior-octave-output-list "\n")
                         "\n")
                 (process-mark inferior-octave-process)))
        (error "Process `%s' died" inferior-octave-process))
825 826 827 828 829 830 831
      (accept-process-output inferior-octave-process))
    (goto-char (point-max))
    (set-marker (process-mark proc) (point))
    (insert-before-markers
     (concat
      (if (not (bobp)) "\n")
      (if inferior-octave-output-list
832 833 834
          (concat (mapconcat
                   'identity inferior-octave-output-list "\n")
                  "\n"))))
835 836 837 838

    ;; An empty secondary prompt, as e.g. obtained by '--braindead',
    ;; means trouble.
    (inferior-octave-send-list-and-digest (list "PS2\n"))
839 840
    (when (string-match "\\(PS2\\|ans\\) = *$"
                        (car inferior-octave-output-list))
841
      (inferior-octave-send-list-and-digest (list "PS2 ('> ');\n")))
842

843
    (inferior-octave-send-list-and-digest
844
     (list "disp (getenv ('OCTAVE_SRCDIR'))\n"))
845 846 847
    (process-put proc 'octave-srcdir
                 (unless (equal (car inferior-octave-output-list) "")
                   (car inferior-octave-output-list)))
848

849 850 851 852
    ;; O.K., now we are ready for the Inferior Octave startup commands.
    (inferior-octave-send-list-and-digest
     (list "more off;\n"
           (unless (equal inferior-octave-output-string ">> ")
853 854
             ;; See http://hg.savannah.gnu.org/hgweb/octave/rev/708173343c50
             "PS1 ('octave> ');\n")
855 856
           (when (and inferior-octave-startup-file
                      (file-exists-p inferior-octave-startup-file))
857
             (format "source ('%s');\n" inferior-octave-startup-file))))
858 859 860
    (when inferior-octave-output-list
      (insert-before-markers
       (mapconcat 'identity inferior-octave-output-list "\n")))
861 862

    ;; And finally, everything is back to normal.
863
    (set-process-filter proc 'comint-output-filter)
864 865 866
    ;; Just in case, to be sure a cd in the startup file won't have
    ;; detrimental effects.
    (with-demoted-errors (inferior-octave-resync-dirs))
867 868
    ;; Generate a proper prompt, which is critical to
    ;; `comint-history-isearch-backward-regexp'.  Bug#14433.
869
    (comint-send-string proc "\n")))
870

871 872 873 874 875 876 877
(defun inferior-octave-completion-table ()
  (completion-table-with-cache
   (lambda (command)
     (inferior-octave-send-list-and-digest
      (list (format "completion_matches ('%s');\n" command)))
     (delete-consecutive-dups
      (sort inferior-octave-output-list 'string-lessp)))))
878

879 880
(defun inferior-octave-completion-at-point ()
  "Return the data to complete the Octave symbol at point."
881
  ;; http://debbugs.gnu.org/14300
882 883 884 885 886 887 888
  (unless (string-match-p "/" (or (comint--match-partial-filename) ""))
    (let ((beg (save-excursion
                 (skip-syntax-backward "w_" (comint-line-beginning-position))
                 (point)))
          (end (point)))
      (when (and beg (> end beg))
        (list beg end (completion-table-in-turn
889
                       (inferior-octave-completion-table)
890
                       'comint-completion-file-name-table))))))
891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935

(define-obsolete-function-alias 'inferior-octave-complete
  'completion-at-point "24.1")

(defun inferior-octave-dynamic-list-input-ring ()
  "List the buffer's input history in a help buffer."
  ;; We cannot use `comint-dynamic-list-input-ring', because it replaces
  ;; "completion" by "history reference" ...
  (interactive)
  (if (or (not (ring-p comint-input-ring))
          (ring-empty-p comint-input-ring))
      (message "No history")
    (let ((history nil)
          (history-buffer " *Input History*")
          (index (1- (ring-length comint-input-ring)))
          (conf (current-window-configuration)))
      ;; We have to build up a list ourselves from the ring vector.
      (while (>= index 0)
        (setq history (cons (ring-ref comint-input-ring index) history)
              index (1- index)))
      ;; Change "completion" to "history reference"
      ;; to make the display accurate.
      (with-output-to-temp-buffer history-buffer
        (display-completion-list history)
        (set-buffer history-buffer))
      (message "Hit space to flush")
      (let ((ch (read-event)))
        (if (eq ch ?\ )
            (set-window-configuration conf)
          (setq unread-command-events (list ch)))))))

(defun inferior-octave-output-digest (_proc string)
  "Special output filter for the inferior Octave process.
Save all output between newlines into `inferior-octave-output-list', and
the rest to `inferior-octave-output-string'."
  (setq string (concat inferior-octave-output-string string))
  (while (string-match "\n" string)
    (setq inferior-octave-output-list
	  (append inferior-octave-output-list
		  (list (substring string 0 (match-beginning 0))))
	  string (substring string (match-end 0))))
  (if (string-match inferior-octave-prompt string)
      (setq inferior-octave-receive-in-progress nil))
  (setq inferior-octave-output-string string))

936
(defun inferior-octave-check-process ()
937
  (or (inferior-octave-process-live-p)
938 939 940
      (error (substitute-command-keys
              "No inferior octave process running. Type \\[run-octave]"))))

941 942 943 944
(defun inferior-octave-send-list-and-digest (list)
  "Send LIST to the inferior Octave process and digest the output.
The elements of LIST have to be strings and are sent one by one.  All
output is passed to the filter `inferior-octave-output-digest'."
945
  (inferior-octave-check-process)
946 947 948 949 950 951 952 953 954 955 956 957 958 959 960
  (let* ((proc inferior-octave-process)
	 (filter (process-filter proc))
	 string)
    (set-process-filter proc 'inferior-octave-output-digest)
    (setq inferior-octave-output-list nil)
    (unwind-protect
	(while (setq string (car list))
	  (setq inferior-octave-output-string nil
		inferior-octave-receive-in-progress t)
	  (comint-send-string proc string)
	  (while inferior-octave-receive-in-progress
	    (accept-process-output proc))
	  (setq list (cdr list)))
      (set-process-filter proc filter))))

961 962 963
(defvar inferior-octave-directory-tracker-resync nil)
(make-variable-buffer-local 'inferior-octave-directory-tracker-resync)

964 965 966
(defun inferior-octave-directory-tracker (string)
  "Tracks `cd' commands issued to the inferior Octave process.
Use \\[inferior-octave-resync-dirs] to resync if Emacs gets confused."
967
  (when inferior-octave-directory-tracker-resync
968