* term/xterm.el (xterm--query): Stop after first matching handler. (Bug#14615)
[bpt/emacs.git] / lisp / progmodes / octave.el
CommitLineData
070ccca4 1;;; octave.el --- editing octave source files under emacs -*- lexical-binding: t; -*-
f2727dfb 2
ab422c4d 3;; Copyright (C) 1997, 2001-2013 Free Software Foundation, Inc.
f2727dfb 4
f7fba1a8 5;; Author: Kurt Hornik <Kurt.Hornik@wu-wien.ac.at>
daf3dc79 6;; John Eaton <jwe@octave.org>
71d4c2a5 7;; Maintainer: FSF
f2727dfb
RS
8;; Keywords: languages
9
10;; This file is part of GNU Emacs.
11
b1fc2b50 12;; GNU Emacs is free software: you can redistribute it and/or modify
f2727dfb 13;; it under the terms of the GNU General Public License as published by
b1fc2b50
GM
14;; the Free Software Foundation, either version 3 of the License, or
15;; (at your option) any later version.
f2727dfb
RS
16
17;; GNU Emacs is distributed in the hope that it will be useful,
18;; but WITHOUT ANY WARRANTY; without even the implied warranty of
19;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20;; GNU General Public License for more details.
21
22;; You should have received a copy of the GNU General Public License
b1fc2b50 23;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
f2727dfb
RS
24
25;;; Commentary:
26
02502a5f
LL
27;; This package provides emacs support for Octave. It defines a major
28;; mode for editing Octave code and contains code for interacting with
29;; an inferior Octave process using comint.
d1e49742 30
be64c05d
LL
31;; See the documentation of `octave-mode' and `run-octave' for further
32;; information on usage and customization.
f2727dfb 33
d1e49742 34;;; Code:
be64c05d 35(require 'comint)
f2727dfb 36
2640d52e
LL
37;;; For emacs < 24.3.
38(require 'newcomment)
d74a1581
LL
39(eval-and-compile
40 (unless (fboundp 'user-error)
c7a8fcac
LL
41 (defalias 'user-error 'error))
42 (unless (fboundp 'delete-consecutive-dups)
43 (defalias 'delete-consecutive-dups 'delete-dups)))
2640d52e
LL
44(eval-when-compile
45 (unless (fboundp 'setq-local)
46 (defmacro setq-local (var val)
47 "Set variable VAR to value VAL in current buffer."
48 (list 'set (list 'make-local-variable (list 'quote var)) val))))
49
28d16ed3 50(defgroup octave nil
be64c05d 51 "Editing Octave code."
8ec3bce0 52 :link '(custom-group-link :tag "Font Lock Faces group" font-lock-faces)
28d16ed3
AS
53 :group 'languages)
54
070ccca4
LL
55(define-obsolete-function-alias 'octave-submit-bug-report
56 'report-emacs-bug "24.4")
e7017ef9 57
9b92c13b 58(define-abbrev-table 'octave-abbrev-table nil
e7017ef9 59 "Abbrev table for Octave's reserved words.
9b92c13b 60Used in `octave-mode' and `inferior-octave-mode' buffers.")
e7017ef9
RS
61
62(defvar octave-comment-char ?#
63 "Character to start an Octave comment.")
9b92c13b 64
42caeb89
LL
65(defvar octave-comment-start (char-to-string octave-comment-char)
66 "Octave-specific `comment-start' (which see).")
9b92c13b 67
42caeb89
LL
68(defvar octave-comment-start-skip "\\(^\\|\\S<\\)\\(?:%!\\|\\s<+\\)\\s-*"
69 "Octave-specific `comment-start-skip' (which see).")
e7017ef9
RS
70
71(defvar octave-begin-keywords
6980b0ca
LL
72 '("classdef" "do" "enumeration" "events" "for" "function" "if" "methods"
73 "parfor" "properties" "switch" "try" "unwind_protect" "while"))
9b92c13b 74
e7017ef9 75(defvar octave-else-keywords
6e6d2764 76 '("case" "catch" "else" "elseif" "otherwise" "unwind_protect_cleanup"))
9b92c13b 77
e7017ef9 78(defvar octave-end-keywords
6980b0ca
LL
79 '("endclassdef" "endenumeration" "endevents" "endfor" "endfunction" "endif"
80 "endmethods" "endparfor" "endproperties" "endswitch" "end_try_catch"
b2ad70b6 81 "end_unwind_protect" "endwhile" "until" "end"))
e7017ef9
RS
82
83(defvar octave-reserved-words
6e6d2764
KH
84 (append octave-begin-keywords
85 octave-else-keywords
86 octave-end-keywords
6980b0ca 87 '("break" "continue" "global" "persistent" "return"))
e7017ef9
RS
88 "Reserved words in Octave.")
89
e7017ef9 90(defvar octave-function-header-regexp
e92f3bd3 91 (concat "^\\s-*\\_<\\(function\\)\\_>"
ce8209d4 92 "\\([^=;(\n]*=[ \t]*\\|[ \t]*\\)\\(\\(?:\\w\\|\\s_\\)+\\)\\_>")
e7017ef9
RS
93 "Regexp to match an Octave function header.
94The string `function' and its name are given by the first and third
95parenthetical grouping.")
96
be64c05d 97\f
e22bbd48 98(defvar octave-mode-map
f2727dfb 99 (let ((map (make-sparse-keymap)))
f5a9432f
LL
100 (define-key map "\M-." 'octave-find-definition)
101 (define-key map "\M-\C-j" 'octave-indent-new-comment-line)
f2727dfb
RS
102 (define-key map "\C-c\C-p" 'octave-previous-code-line)
103 (define-key map "\C-c\C-n" 'octave-next-code-line)
104 (define-key map "\C-c\C-a" 'octave-beginning-of-line)
a1506d29 105 (define-key map "\C-c\C-e" 'octave-end-of-line)
ec5d3ff7 106 (define-key map [remap down-list] 'smie-down-list)
f2727dfb 107 (define-key map "\C-c\M-\C-h" 'octave-mark-block)
e17b68ed
SM
108 (define-key map "\C-c]" 'smie-close-block)
109 (define-key map "\C-c/" 'smie-close-block)
b7260dd4 110 (define-key map "\C-c;" 'octave-update-function-file-comment)
d4d0f9b3 111 (define-key map "\C-hd" 'octave-help)
34a463f1 112 (define-key map "\C-c\C-f" 'octave-insert-defun)
34a463f1
RS
113 (define-key map "\C-c\C-il" 'octave-send-line)
114 (define-key map "\C-c\C-ib" 'octave-send-block)
115 (define-key map "\C-c\C-if" 'octave-send-defun)
a1506d29 116 (define-key map "\C-c\C-ir" 'octave-send-region)
34a463f1 117 (define-key map "\C-c\C-is" 'octave-show-process-buffer)
ad64b83d 118 (define-key map "\C-c\C-iq" 'octave-hide-process-buffer)
34a463f1
RS
119 (define-key map "\C-c\C-ik" 'octave-kill-process)
120 (define-key map "\C-c\C-i\C-l" 'octave-send-line)
121 (define-key map "\C-c\C-i\C-b" 'octave-send-block)
122 (define-key map "\C-c\C-i\C-f" 'octave-send-defun)
a1506d29 123 (define-key map "\C-c\C-i\C-r" 'octave-send-region)
34a463f1 124 (define-key map "\C-c\C-i\C-s" 'octave-show-process-buffer)
7a1beb6c 125 (define-key map "\C-c\C-i\C-q" 'octave-hide-process-buffer)
34a463f1 126 (define-key map "\C-c\C-i\C-k" 'octave-kill-process)
e22bbd48
SM
127 map)
128 "Keymap used in Octave mode.")
129
f2727dfb 130
ff80a446
SM
131
132(easy-menu-define octave-mode-menu octave-mode-map
133 "Menu for Octave mode."
e22bbd48 134 '("Octave"
ee44b62a
LL
135 ["Split Line at Point" octave-indent-new-comment-line t]
136 ["Previous Code Line" octave-previous-code-line t]
137 ["Next Code Line" octave-next-code-line t]
138 ["Begin of Line" octave-beginning-of-line t]
139 ["End of Line" octave-end-of-line t]
140 ["Mark Block" octave-mark-block t]
141 ["Close Block" smie-close-block t]
142 "---"
143 ["Start Octave Process" run-octave t]
3ac7cff4 144 ["Documentation Lookup" info-lookup-symbol t]
ee44b62a
LL
145 ["Help on Function" octave-help t]
146 ["Find Function Definition" octave-find-definition t]
147 ["Insert Function" octave-insert-defun t]
148 ["Update Function File Comment" octave-update-function-file-comment t]
149 "---"
150 ["Function Syntax Hints" (call-interactively
151 (if (fboundp 'eldoc-post-insert-mode)
152 'eldoc-post-insert-mode
153 'eldoc-mode))
154 :style toggle :selected (or eldoc-post-insert-mode eldoc-mode)
155 :help "Display function signatures after typing `SPC' or `('"]
976cb066
LL
156 ["Delimiter Matching" show-paren-mode
157 :style toggle :selected show-paren-mode
ee44b62a 158 :help "Highlight matched pairs such as `if ... end'"
976cb066 159 :visible (fboundp 'smie--matching-block-data)]
ee44b62a
LL
160 ["Auto Fill" auto-fill-mode
161 :style toggle :selected auto-fill-function
162 :help "Automatic line breaking"]
163 ["Electric Layout" electric-layout-mode
164 :style toggle :selected electric-layout-mode
165 :help "Automatically insert newlines around some chars"]
166 "---"
67885e8c 167 ("Debug"
ee44b62a
LL
168 ["Send Current Line" octave-send-line t]
169 ["Send Current Block" octave-send-block t]
170 ["Send Current Function" octave-send-defun t]
171 ["Send Region" octave-send-region t]
172 ["Show Process Buffer" octave-show-process-buffer t]
173 ["Hide Process Buffer" octave-hide-process-buffer t]
174 ["Kill Process" octave-kill-process t])
175 "---"
176 ["Customize Octave" (customize-group 'octave) t]
177 ["Submit Bug Report" report-emacs-bug t]))
f2727dfb 178
cb3d3ec1 179(defvar octave-mode-syntax-table
f2727dfb
RS
180 (let ((table (make-syntax-table)))
181 (modify-syntax-entry ?\r " " table)
182 (modify-syntax-entry ?+ "." table)
183 (modify-syntax-entry ?- "." table)
184 (modify-syntax-entry ?= "." table)
185 (modify-syntax-entry ?* "." table)
186 (modify-syntax-entry ?/ "." table)
187 (modify-syntax-entry ?> "." table)
188 (modify-syntax-entry ?< "." table)
189 (modify-syntax-entry ?& "." table)
190 (modify-syntax-entry ?| "." table)
191 (modify-syntax-entry ?! "." table)
27135018 192 (modify-syntax-entry ?\\ "." table)
f2727dfb 193 (modify-syntax-entry ?\' "." table)
7210c33f 194 (modify-syntax-entry ?\` "." table)
c75c93c7 195 (modify-syntax-entry ?. "." table)
f2727dfb 196 (modify-syntax-entry ?\" "\"" table)
e92f3bd3 197 (modify-syntax-entry ?_ "_" table)
c5683ceb 198 ;; The "b" flag only applies to the second letter of the comstart
7e82caa7
SM
199 ;; and the first letter of the comend, i.e. the "4b" below is ineffective.
200 ;; If we try to put `b' on the single-line comments, we get a similar
201 ;; problem where the % and # chars appear as first chars of the 2-char
202 ;; comend, so the multi-line ender is also turned into style-b.
c5683ceb 203 ;; So we need the new "c" comment style.
c82d5b11
SM
204 (modify-syntax-entry ?\% "< 13" table)
205 (modify-syntax-entry ?\# "< 13" table)
c5683ceb
SM
206 (modify-syntax-entry ?\{ "(} 2c" table)
207 (modify-syntax-entry ?\} "){ 4c" table)
f2727dfb 208 (modify-syntax-entry ?\n ">" table)
cb3d3ec1
SM
209 table)
210 "Syntax table in use in `octave-mode' buffers.")
f2727dfb 211
5b78d7fc
LL
212(defcustom octave-font-lock-texinfo-comment t
213 "Control whether to highlight the texinfo comment block."
214 :type 'boolean
215 :group 'octave
216 :version "24.4")
217
28d16ed3 218(defcustom octave-blink-matching-block t
e22bbd48 219 "Control the blinking of matching Octave block keywords.
f2727dfb 220Non-nil means show matching begin of block when inserting a space,
28d16ed3
AS
221newline or semicolon after an else or end keyword."
222 :type 'boolean
223 :group 'octave)
ff80a446 224
28d16ed3 225(defcustom octave-block-offset 2
e22bbd48 226 "Extra indentation applied to statements in Octave block structures."
28d16ed3
AS
227 :type 'integer
228 :group 'octave)
f2727dfb 229
f2727dfb
RS
230(defvar octave-block-comment-start
231 (concat (make-string 2 octave-comment-char) " ")
232 "String to insert to start a new Octave comment on an empty line.")
233
28d16ed3 234(defcustom octave-continuation-offset 4
e22bbd48 235 "Extra indentation applied to Octave continuation lines."
28d16ed3
AS
236 :type 'integer
237 :group 'octave)
38d8527b 238
e17b68ed
SM
239(eval-and-compile
240 (defconst octave-continuation-marker-regexp "\\\\\\|\\.\\.\\."))
38d8527b 241
f2727dfb 242(defvar octave-continuation-regexp
e17b68ed
SM
243 (concat "[^#%\n]*\\(" octave-continuation-marker-regexp
244 "\\)\\s-*\\(\\s<.*\\)?$"))
38d8527b
LL
245
246;; Char \ is considered a bad decision for continuing a line.
247(defconst octave-continuation-string "..."
248 "Character string used for Octave continuation lines.")
f2727dfb 249
f2727dfb
RS
250(defvar octave-mode-imenu-generic-expression
251 (list
252 ;; Functions
253 (list nil octave-function-header-regexp 3))
254 "Imenu expression for Octave mode. See `imenu-generic-expression'.")
255
28d16ed3 256(defcustom octave-mode-hook nil
e22bbd48 257 "Hook to be run when Octave mode is started."
28d16ed3
AS
258 :type 'hook
259 :group 'octave)
260
261(defcustom octave-send-show-buffer t
e22bbd48 262 "Non-nil means display `inferior-octave-buffer' after sending to it."
28d16ed3
AS
263 :type 'boolean
264 :group 'octave)
9b92c13b 265
28d16ed3 266(defcustom octave-send-line-auto-forward t
e22bbd48 267 "Control auto-forward after sending to the inferior Octave process.
28d16ed3
AS
268Non-nil means always go to the next Octave code line after sending."
269 :type 'boolean
270 :group 'octave)
9b92c13b 271
28d16ed3 272(defcustom octave-send-echo-input t
e22bbd48 273 "Non-nil means echo input sent to the inferior Octave process."
28d16ed3
AS
274 :type 'boolean
275 :group 'octave)
f2727dfb
RS
276
277\f
e17b68ed
SM
278;;; SMIE indentation
279
280(require 'smie)
281
203a5572 282;; Use '__operators__' in Octave REPL to get a full list.
e17b68ed
SM
283(defconst octave-operator-table
284 '((assoc ";" "\n") (assoc ",") ; The doc claims they have equal precedence!?
285 (right "=" "+=" "-=" "*=" "/=")
286 (assoc "&&") (assoc "||") ; The doc claims they have equal precedence!?
287 (assoc "&") (assoc "|") ; The doc claims they have equal precedence!?
288 (nonassoc "<" "<=" "==" ">=" ">" "!=" "~=")
289 (nonassoc ":") ;No idea what this is.
290 (assoc "+" "-")
291 (assoc "*" "/" "\\" ".\\" ".*" "./")
292 (nonassoc "'" ".'")
293 (nonassoc "++" "--" "!" "~") ;And unary "+" and "-".
294 (right "^" "**" ".^" ".**")
295 ;; It's not really an operator, but for indentation purposes it
296 ;; could be convenient to treat it as one.
297 (assoc "...")))
298
89acf735
SM
299(defconst octave-smie-bnf-table
300 '((atom)
301 ;; We can't distinguish the first element in a sequence with
302 ;; precedence grammars, so we can't distinguish the condition
303 ;; if the `if' from the subsequent body, for example.
304 ;; This has to be done later in the indentation rules.
305 (exp (exp "\n" exp)
306 ;; We need to mention at least one of the operators in this part
307 ;; of the grammar: if the BNF and the operator table have
308 ;; no overlap, SMIE can't know how they relate.
309 (exp ";" exp)
310 ("try" exp "catch" exp "end_try_catch")
311 ("try" exp "catch" exp "end")
312 ("unwind_protect" exp
313 "unwind_protect_cleanup" exp "end_unwind_protect")
314 ("unwind_protect" exp "unwind_protect_cleanup" exp "end")
315 ("for" exp "endfor")
316 ("for" exp "end")
6980b0ca
LL
317 ("parfor" exp "endparfor")
318 ("parfor" exp "end")
89acf735
SM
319 ("do" exp "until" atom)
320 ("while" exp "endwhile")
321 ("while" exp "end")
322 ("if" exp "endif")
323 ("if" exp "else" exp "endif")
324 ("if" exp "elseif" exp "else" exp "endif")
325 ("if" exp "elseif" exp "elseif" exp "else" exp "endif")
326 ("if" exp "elseif" exp "elseif" exp "else" exp "end")
327 ("switch" exp "case" exp "endswitch")
328 ("switch" exp "case" exp "otherwise" exp "endswitch")
329 ("switch" exp "case" exp "case" exp "otherwise" exp "endswitch")
330 ("switch" exp "case" exp "case" exp "otherwise" exp "end")
331 ("function" exp "endfunction")
6980b0ca
LL
332 ("function" exp "end")
333 ("enumeration" exp "endenumeration")
334 ("enumeration" exp "end")
335 ("events" exp "endevents")
336 ("events" exp "end")
337 ("methods" exp "endmethods")
338 ("methods" exp "end")
339 ("properties" exp "endproperties")
340 ("properties" exp "end")
341 ("classdef" exp "endclassdef")
342 ("classdef" exp "end"))
89acf735
SM
343 ;; (fundesc (atom "=" atom))
344 ))
345
674728d4
SM
346(defconst octave-smie-grammar
347 (smie-prec2->grammar
e17b68ed 348 (smie-merge-prec2s
674728d4
SM
349 (smie-bnf->prec2 octave-smie-bnf-table
350 '((assoc "\n" ";")))
e17b68ed 351
674728d4 352 (smie-precs->prec2 octave-operator-table))))
e17b68ed
SM
353
354;; Tokenizing needs to be refined so that ";;" is treated as two
355;; tokens and also so as to recognize the \n separator (and
356;; corresponding continuation lines).
357
358(defconst octave-operator-regexp
359 (regexp-opt (apply 'append (mapcar 'cdr octave-operator-table))))
360
361(defun octave-smie-backward-token ()
362 (let ((pos (point)))
363 (forward-comment (- (point)))
364 (cond
365 ((and (not (eq (char-before) ?\;)) ;Coalesce ";" and "\n".
366 (> pos (line-end-position))
367 (if (looking-back octave-continuation-marker-regexp (- (point) 3))
368 (progn
369 (goto-char (match-beginning 0))
370 (forward-comment (- (point)))
371 nil)
372 t)
373 ;; Ignore it if it's within parentheses.
374 (let ((ppss (syntax-ppss)))
375 (not (and (nth 1 ppss)
376 (eq ?\( (char-after (nth 1 ppss)))))))
377 (skip-chars-forward " \t")
378 ;; Why bother distinguishing \n and ;?
379 ";") ;;"\n"
380 ((and (looking-back octave-operator-regexp (- (point) 3) 'greedy)
381 ;; Don't mistake a string quote for a transpose.
382 (not (looking-back "\\s\"" (1- (point)))))
383 (goto-char (match-beginning 0))
384 (match-string-no-properties 0))
385 (t
386 (smie-default-backward-token)))))
387
388(defun octave-smie-forward-token ()
389 (skip-chars-forward " \t")
390 (when (looking-at (eval-when-compile
391 (concat "\\(" octave-continuation-marker-regexp
392 "\\)[ \t]*\\($\\|[%#]\\)")))
393 (goto-char (match-end 1))
394 (forward-comment 1))
395 (cond
ceb57e59
SM
396 ((and (looking-at "[%#\n]")
397 (not (or (save-excursion (skip-chars-backward " \t")
398 ;; Only add implicit ; when needed.
150194c3 399 (or (bolp) (eq (char-before) ?\;)))
ceb57e59
SM
400 ;; Ignore it if it's within parentheses.
401 (let ((ppss (syntax-ppss)))
402 (and (nth 1 ppss)
403 (eq ?\( (char-after (nth 1 ppss))))))))
404 (if (eolp) (forward-char 1) (forward-comment 1))
e17b68ed
SM
405 ;; Why bother distinguishing \n and ;?
406 ";") ;;"\n"
ceb57e59 407 ((progn (forward-comment (point-max)) nil))
e17b68ed
SM
408 ((looking-at ";[ \t]*\\($\\|[%#]\\)")
409 ;; Combine the ; with the subsequent \n.
410 (goto-char (match-beginning 1))
411 (forward-comment 1)
412 ";")
413 ((and (looking-at octave-operator-regexp)
414 ;; Don't mistake a string quote for a transpose.
415 (not (looking-at "\\s\"")))
416 (goto-char (match-end 0))
417 (match-string-no-properties 0))
418 (t
419 (smie-default-forward-token))))
420
c4d17d50
SM
421(defun octave-smie-rules (kind token)
422 (pcase (cons kind token)
674728d4
SM
423 ;; We could set smie-indent-basic instead, but that would have two
424 ;; disadvantages:
425 ;; - changes to octave-block-offset wouldn't take effect immediately.
426 ;; - edebug wouldn't show the use of this variable.
c4d17d50 427 (`(:elem . basic) octave-block-offset)
674728d4
SM
428 ;; Since "case" is in the same BNF rules as switch..end, SMIE by default
429 ;; aligns it with "switch".
430 (`(:before . "case") (if (not (smie-rule-sibling-p)) octave-block-offset))
c4d17d50 431 (`(:after . ";")
6980b0ca
LL
432 (if (smie-rule-parent-p "classdef" "events" "enumeration" "function" "if"
433 "while" "else" "elseif" "for" "parfor"
434 "properties" "methods" "otherwise" "case"
435 "try" "catch" "unwind_protect"
674728d4
SM
436 "unwind_protect_cleanup")
437 (smie-rule-parent octave-block-offset)
c4d17d50
SM
438 ;; For (invalid) code between switch and case.
439 ;; (if (smie-parent-p "switch") 4)
650cff3d 440 nil))))
e17b68ed 441
9dbdb67e
LL
442(defun octave-indent-comment ()
443 "A function for `smie-indent-functions' (which see)."
444 (save-excursion
445 (back-to-indentation)
2aeb3a1d
LL
446 (cond
447 ((octave-in-string-or-comment-p) nil)
f236dd84 448 ((looking-at-p "\\(\\s<\\)\\1\\{2,\\}")
2aeb3a1d 449 0)
b0e069c2
LL
450 ;; Exclude %{, %} and %!.
451 ((and (looking-at-p "\\s<\\(?:[^{}!]\\|$\\)")
f236dd84 452 (not (looking-at-p "\\(\\s<\\)\\1")))
2aeb3a1d 453 (comment-choose-indent)))))
9dbdb67e 454
203a5572
LL
455\f
456(defvar octave-font-lock-keywords
457 (list
458 ;; Fontify all builtin keywords.
459 (cons (concat "\\_<\\("
460 (regexp-opt octave-reserved-words)
461 "\\)\\_>")
462 'font-lock-keyword-face)
463 ;; Note: 'end' also serves as the last index in an indexing expression.
464 ;; Ref: http://www.mathworks.com/help/matlab/ref/end.html
465 (list (lambda (limit)
466 (while (re-search-forward "\\_<end\\_>" limit 'move)
467 (let ((beg (match-beginning 0))
468 (end (match-end 0)))
469 (unless (octave-in-string-or-comment-p)
4d25fd7e 470 (condition-case nil
203a5572
LL
471 (progn
472 (goto-char beg)
473 (backward-up-list)
474 (when (memq (char-after) '(?\( ?\[ ?\{))
4d25fd7e
LL
475 (put-text-property beg end 'face nil))
476 (goto-char end))
477 (error (goto-char end))))))
203a5572
LL
478 nil))
479 ;; Fontify all operators.
480 (cons octave-operator-regexp 'font-lock-builtin-face)
481 ;; Fontify all function declarations.
482 (list octave-function-header-regexp
483 '(1 font-lock-keyword-face)
484 '(3 font-lock-function-name-face nil t)))
485 "Additional Octave expressions to highlight.")
486
487(defun octave-syntax-propertize-function (start end)
488 (goto-char start)
489 (octave-syntax-propertize-sqs end)
490 (funcall (syntax-propertize-rules
491 ("\\\\" (0 (when (eq (nth 3 (save-excursion
492 (syntax-ppss (match-beginning 0))))
493 ?\")
494 (string-to-syntax "\\"))))
495 ;; Try to distinguish the string-quotes from the transpose-quotes.
496 ("\\(?:^\\|[[({,; ]\\)\\('\\)"
497 (1 (prog1 "\"'" (octave-syntax-propertize-sqs end)))))
498 (point) end))
499
500(defun octave-syntax-propertize-sqs (end)
501 "Propertize the content/end of single-quote strings."
502 (when (eq (nth 3 (syntax-ppss)) ?\')
503 ;; A '..' string.
504 (when (re-search-forward
505 "\\(?:\\=\\|[^']\\)\\(?:''\\)*\\('\\)\\($\\|[^']\\)" end 'move)
506 (goto-char (match-beginning 2))
507 (when (eq (char-before (match-beginning 1)) ?\\)
508 ;; Backslash cannot escape a single quote.
509 (put-text-property (1- (match-beginning 1)) (match-beginning 1)
510 'syntax-table (string-to-syntax ".")))
511 (put-text-property (match-beginning 1) (match-end 1)
512 'syntax-table (string-to-syntax "\"'")))))
513
03f70355 514(defvar electric-layout-rules)
b073dc4b 515
f2727dfb 516;;;###autoload
c82d5b11 517(define-derived-mode octave-mode prog-mode "Octave"
f2727dfb
RS
518 "Major mode for editing Octave code.
519
b7260dd4
LL
520Octave is a high-level language, primarily intended for numerical
521computations. It provides a convenient command line interface
522for solving linear and nonlinear problems numerically. Function
523definitions can also be stored in files and used in batch mode."
9b92c13b 524 :abbrev-table octave-abbrev-table
c82d5b11 525
674728d4 526 (smie-setup octave-smie-grammar #'octave-smie-rules
c4d17d50
SM
527 :forward-token #'octave-smie-forward-token
528 :backward-token #'octave-smie-backward-token)
070ccca4 529 (setq-local smie-indent-basic 'octave-block-offset)
9dbdb67e 530 (add-hook 'smie-indent-functions #'octave-indent-comment nil t)
c4d17d50 531
070ccca4
LL
532 (setq-local smie-blink-matching-triggers
533 (cons ?\; smie-blink-matching-triggers))
c4d17d50
SM
534 (unless octave-blink-matching-block
535 (remove-hook 'post-self-insert-hook #'smie-blink-matching-open 'local))
89acf735 536
070ccca4
LL
537 (setq-local electric-indent-chars
538 (cons ?\; electric-indent-chars))
03f70355
SM
539 ;; IIUC matlab-mode takes the opposite approach: it makes RET insert
540 ;; a ";" at those places where it's correct (i.e. outside of parens).
070ccca4 541 (setq-local electric-layout-rules '((?\; . after)))
c82d5b11 542
a175bf33 543 (setq-local comment-use-global-state t)
070ccca4
LL
544 (setq-local comment-start octave-comment-start)
545 (setq-local comment-end "")
b0e069c2 546 (setq-local comment-start-skip octave-comment-start-skip)
070ccca4
LL
547 (setq-local comment-add 1)
548
549 (setq-local parse-sexp-ignore-comments t)
550 (setq-local paragraph-start (concat "\\s-*$\\|" page-delimiter))
551 (setq-local paragraph-separate paragraph-start)
552 (setq-local paragraph-ignore-fill-prefix t)
553 (setq-local fill-paragraph-function 'octave-fill-paragraph)
9cc3e83f 554
ee44b62a
LL
555 (setq-local fill-nobreak-predicate
556 (lambda () (eq (octave-in-string-p) ?')))
650cff3d
SM
557 (add-function :around (local 'comment-line-break-function)
558 #'octave--indent-new-comment-line)
c82d5b11 559
070ccca4 560 (setq font-lock-defaults '(octave-font-lock-keywords))
cf38dd42 561
070ccca4 562 (setq-local syntax-propertize-function #'octave-syntax-propertize-function)
c82d5b11 563
02502a5f
LL
564 (setq-local imenu-generic-expression octave-mode-imenu-generic-expression)
565 (setq-local imenu-case-fold-search nil)
c82d5b11 566
fda74125
LL
567 (setq-local add-log-current-defun-function #'octave-add-log-current-defun)
568
203a5572 569 (add-hook 'completion-at-point-functions 'octave-completion-at-point nil t)
e55d3b04 570 (add-hook 'before-save-hook 'octave-sync-function-file-names nil t)
070ccca4 571 (setq-local beginning-of-defun-function 'octave-beginning-of-defun)
5b78d7fc 572 (and octave-font-lock-texinfo-comment (octave-font-lock-texinfo-comment))
c8730c3a 573 (setq-local eldoc-documentation-function 'octave-eldoc-function)
f2727dfb 574
584ea277 575 (easy-menu-add octave-mode-menu))
be64c05d
LL
576
577\f
578(defcustom inferior-octave-program "octave"
579 "Program invoked by `inferior-octave'."
580 :type 'string
581 :group 'octave)
582
583(defcustom inferior-octave-buffer "*Inferior Octave*"
584 "Name of buffer for running an inferior Octave process."
585 :type 'string
586 :group 'octave)
587
588(defcustom inferior-octave-prompt
589 "\\(^octave\\(\\|.bin\\|.exe\\)\\(-[.0-9]+\\)?\\(:[0-9]+\\)?\\|^debug\\|^\\)>+ "
590 "Regexp to match prompts for the inferior Octave process."
591 :type 'regexp
592 :group 'octave)
593
b12d33d7 594(defcustom inferior-octave-prompt-read-only comint-prompt-read-only
5147fc17 595 "If non-nil, the Octave prompt is read only.
b12d33d7 596See `comint-prompt-read-only' for details."
5147fc17
RS
597 :type 'boolean
598 :group 'octave
599 :version "24.4")
600
0d634d3a
LL
601(defcustom inferior-octave-startup-file
602 (convert-standard-filename
603 (concat "~/.emacs-" (file-name-nondirectory inferior-octave-program)))
be64c05d
LL
604 "Name of the inferior Octave startup file.
605The contents of this file are sent to the inferior Octave process on
606startup."
0d634d3a
LL
607 :type '(choice (const :tag "None" nil) file)
608 :group 'octave
609 :version "24.4")
be64c05d 610
daabf15a 611(defcustom inferior-octave-startup-args '("-i" "--no-line-editing")
be64c05d
LL
612 "List of command line arguments for the inferior Octave process.
613For example, for suppressing the startup message and using `traditional'
daabf15a 614mode, include \"-q\" and \"--traditional\"."
be64c05d 615 :type '(repeat string)
daabf15a
LL
616 :group 'octave
617 :version "24.4")
be64c05d
LL
618
619(defcustom inferior-octave-mode-hook nil
620 "Hook to be run when Inferior Octave mode is started."
621 :type 'hook
622 :group 'octave)
623
624(defvar inferior-octave-process nil)
625
626(defvar inferior-octave-mode-map
627 (let ((map (make-sparse-keymap)))
628 (set-keymap-parent map comint-mode-map)
d4d0f9b3 629 (define-key map "\M-." 'octave-find-definition)
c129b51f 630 (define-key map "\t" 'completion-at-point)
d4d0f9b3 631 (define-key map "\C-hd" 'octave-help)
c129b51f 632 ;; Same as in `shell-mode'.
be64c05d
LL
633 (define-key map "\M-?" 'comint-dynamic-list-filename-completions)
634 (define-key map "\C-c\C-l" 'inferior-octave-dynamic-list-input-ring)
635 (define-key map [menu-bar inout list-history]
636 '("List Input History" . inferior-octave-dynamic-list-input-ring))
637 map)
638 "Keymap used in Inferior Octave mode.")
639
640(defvar inferior-octave-mode-syntax-table
641 (let ((table (make-syntax-table octave-mode-syntax-table)))
642 table)
643 "Syntax table in use in inferior-octave-mode buffers.")
644
645(defvar inferior-octave-font-lock-keywords
646 (list
647 (cons inferior-octave-prompt 'font-lock-type-face))
648 ;; Could certainly do more font locking in inferior Octave ...
649 "Additional expressions to highlight in Inferior Octave mode.")
650
be64c05d
LL
651(defvar inferior-octave-output-list nil)
652(defvar inferior-octave-output-string nil)
653(defvar inferior-octave-receive-in-progress nil)
654
2ec12cb0
LL
655(define-obsolete-variable-alias 'inferior-octave-startup-hook
656 'inferior-octave-mode-hook "24.4")
be64c05d 657
be64c05d
LL
658(defvar inferior-octave-dynamic-complete-functions
659 '(inferior-octave-completion-at-point comint-filename-completion)
660 "List of functions called to perform completion for inferior Octave.
661This variable is used to initialize `comint-dynamic-complete-functions'
662in the Inferior Octave buffer.")
663
664(defvar info-lookup-mode)
665
666(define-derived-mode inferior-octave-mode comint-mode "Inferior Octave"
b7260dd4 667 "Major mode for interacting with an inferior Octave process."
9b92c13b
LL
668 :abbrev-table octave-abbrev-table
669 (setq comint-prompt-regexp inferior-octave-prompt)
be64c05d 670
a175bf33 671 (setq-local comment-use-global-state t)
070ccca4
LL
672 (setq-local comment-start octave-comment-start)
673 (setq-local comment-end "")
674 (setq comment-column 32)
675 (setq-local comment-start-skip octave-comment-start-skip)
be64c05d 676
070ccca4 677 (setq font-lock-defaults '(inferior-octave-font-lock-keywords nil nil))
be64c05d 678
02502a5f 679 (setq-local info-lookup-mode 'octave-mode)
c8730c3a 680 (setq-local eldoc-documentation-function 'octave-eldoc-function)
be64c05d
LL
681
682 (setq comint-input-ring-file-name
c75c93c7
LL
683 (or (getenv "OCTAVE_HISTFILE") "~/.octave_hist")
684 comint-input-ring-size (or (getenv "OCTAVE_HISTSIZE") 1024))
070ccca4
LL
685 (setq-local comint-dynamic-complete-functions
686 inferior-octave-dynamic-complete-functions)
5147fc17 687 (setq-local comint-prompt-read-only inferior-octave-prompt-read-only)
be64c05d 688 (add-hook 'comint-input-filter-functions
203a5572 689 'inferior-octave-directory-tracker nil t)
c75c93c7
LL
690 ;; http://thread.gmane.org/gmane.comp.gnu.octave.general/48572
691 (add-hook 'window-configuration-change-hook
692 'inferior-octave-track-window-width-change nil t)
be64c05d
LL
693 (comint-read-input-ring t))
694
695;;;###autoload
696(defun inferior-octave (&optional arg)
697 "Run an inferior Octave process, I/O via `inferior-octave-buffer'.
698This buffer is put in Inferior Octave mode. See `inferior-octave-mode'.
699
700Unless ARG is non-nil, switches to this buffer.
701
702The elements of the list `inferior-octave-startup-args' are sent as
703command line arguments to the inferior Octave process on startup.
704
705Additional commands to be executed on startup can be provided either in
706the file specified by `inferior-octave-startup-file' or by the default
707startup file, `~/.emacs-octave'."
708 (interactive "P")
0d634d3a 709 (let ((buffer (get-buffer-create inferior-octave-buffer)))
ee44b62a
LL
710 (unless arg
711 (pop-to-buffer buffer))
0d634d3a 712 (unless (comint-check-proc buffer)
be64c05d 713 (with-current-buffer buffer
0d634d3a
LL
714 (inferior-octave-startup)
715 (inferior-octave-mode)))
0d634d3a 716 buffer))
be64c05d
LL
717
718;;;###autoload
719(defalias 'run-octave 'inferior-octave)
720
721(defun inferior-octave-startup ()
722 "Start an inferior Octave process."
723 (let ((proc (comint-exec-1
0d634d3a
LL
724 (substring inferior-octave-buffer 1 -1)
725 inferior-octave-buffer
726 inferior-octave-program
daabf15a
LL
727 (append
728 inferior-octave-startup-args
729 ;; --no-gui is introduced in Octave > 3.7
730 (and (not (member "--no-gui" inferior-octave-startup-args))
731 (zerop (process-file inferior-octave-program
732 nil nil nil "--no-gui" "--help"))
733 '("--no-gui"))))))
be64c05d 734 (set-process-filter proc 'inferior-octave-output-digest)
0d634d3a
LL
735 (setq inferior-octave-process proc
736 inferior-octave-output-list nil
737 inferior-octave-output-string nil
738 inferior-octave-receive-in-progress t)
be64c05d
LL
739
740 ;; This may look complicated ... However, we need to make sure that
741 ;; we additional startup code only AFTER Octave is ready (otherwise,
742 ;; output may be mixed up). Hence, we need to digest the Octave
743 ;; output to see when it issues a prompt.
744 (while inferior-octave-receive-in-progress
ee44b62a
LL
745 (or (process-live-p inferior-octave-process)
746 (error "Process `%s' died" inferior-octave-process))
be64c05d
LL
747 (accept-process-output inferior-octave-process))
748 (goto-char (point-max))
749 (set-marker (process-mark proc) (point))
750 (insert-before-markers
751 (concat
752 (if (not (bobp)) "\f\n")
753 (if inferior-octave-output-list
0d634d3a
LL
754 (concat (mapconcat
755 'identity inferior-octave-output-list "\n")
756 "\n"))))
be64c05d 757
be64c05d
LL
758 ;; An empty secondary prompt, as e.g. obtained by '--braindead',
759 ;; means trouble.
760 (inferior-octave-send-list-and-digest (list "PS2\n"))
0d634d3a
LL
761 (when (string-match "\\(PS2\\|ans\\) = *$"
762 (car inferior-octave-output-list))
daabf15a 763 (inferior-octave-send-list-and-digest (list "PS2 ('> ');\n")))
0d634d3a 764
30ea8374 765 (inferior-octave-send-list-and-digest
daabf15a 766 (list "disp (getenv ('OCTAVE_SRCDIR'))\n"))
5e80b74f
LL
767 (process-put proc 'octave-srcdir
768 (unless (equal (car inferior-octave-output-list) "")
769 (car inferior-octave-output-list)))
30ea8374 770
0d634d3a
LL
771 ;; O.K., now we are ready for the Inferior Octave startup commands.
772 (inferior-octave-send-list-and-digest
773 (list "more off;\n"
774 (unless (equal inferior-octave-output-string ">> ")
cbb3eb52 775 "PS1 ('\\s> ');\n")
0d634d3a
LL
776 (when (and inferior-octave-startup-file
777 (file-exists-p inferior-octave-startup-file))
daabf15a 778 (format "source ('%s');\n" inferior-octave-startup-file))))
85d090a9
LL
779 (when inferior-octave-output-list
780 (insert-before-markers
781 (mapconcat 'identity inferior-octave-output-list "\n")))
be64c05d
LL
782
783 ;; And finally, everything is back to normal.
c129b51f 784 (set-process-filter proc 'comint-output-filter)
daabf15a
LL
785 ;; Just in case, to be sure a cd in the startup file won't have
786 ;; detrimental effects.
787 (with-demoted-errors (inferior-octave-resync-dirs))
85d090a9
LL
788 ;; Generate a proper prompt, which is critical to
789 ;; `comint-history-isearch-backward-regexp'. Bug#14433.
455851dd 790 (comint-send-string proc "\n")))
be64c05d 791
f71c50d0
LL
792(defvar inferior-octave-completion-table
793 ;;
30ea8374 794 ;; Use cache to avoid repetitive computation of completions due to
f71c50d0
LL
795 ;; bug#11906 - http://debbugs.gnu.org/11906 - which may cause
796 ;; noticeable delay. CACHE: (CMD TIME VALUE).
797 (let ((cache))
798 (completion-table-dynamic
799 (lambda (command)
800 (unless (and (equal (car cache) command)
801 (< (float-time) (+ 5 (cadr cache))))
802 (inferior-octave-send-list-and-digest
daabf15a 803 (list (format "completion_matches ('%s');\n" command)))
f71c50d0 804 (setq cache (list command (float-time)
c7a8fcac
LL
805 (delete-consecutive-dups
806 (sort inferior-octave-output-list 'string-lessp)))))
f71c50d0 807 (car (cddr cache))))))
584ea277 808
be64c05d
LL
809(defun inferior-octave-completion-at-point ()
810 "Return the data to complete the Octave symbol at point."
455851dd
LL
811 ;; http://debbugs.gnu.org/14300
812 (let* ((filecomp (string-match-p
813 "/" (or (comint--match-partial-filename) "")))
814 (end (point))
be64c05d 815 (start
455851dd
LL
816 (unless filecomp
817 (save-excursion
818 (skip-syntax-backward "w_" (comint-line-beginning-position))
819 (point)))))
820 (when (and start (> end start))
821 (list start end (completion-table-in-turn
f71c50d0 822 inferior-octave-completion-table
455851dd 823 'comint-completion-file-name-table)))))
be64c05d
LL
824
825(define-obsolete-function-alias 'inferior-octave-complete
826 'completion-at-point "24.1")
827
828(defun inferior-octave-dynamic-list-input-ring ()
829 "List the buffer's input history in a help buffer."
830 ;; We cannot use `comint-dynamic-list-input-ring', because it replaces
831 ;; "completion" by "history reference" ...
832 (interactive)
833 (if (or (not (ring-p comint-input-ring))
834 (ring-empty-p comint-input-ring))
835 (message "No history")
836 (let ((history nil)
837 (history-buffer " *Input History*")
838 (index (1- (ring-length comint-input-ring)))
839 (conf (current-window-configuration)))
840 ;; We have to build up a list ourselves from the ring vector.
841 (while (>= index 0)
842 (setq history (cons (ring-ref comint-input-ring index) history)
843 index (1- index)))
844 ;; Change "completion" to "history reference"
845 ;; to make the display accurate.
846 (with-output-to-temp-buffer history-buffer
847 (display-completion-list history)
848 (set-buffer history-buffer))
849 (message "Hit space to flush")
850 (let ((ch (read-event)))
851 (if (eq ch ?\ )
852 (set-window-configuration conf)
853 (setq unread-command-events (list ch)))))))
854
be64c05d
LL
855(defun inferior-octave-output-digest (_proc string)
856 "Special output filter for the inferior Octave process.
857Save all output between newlines into `inferior-octave-output-list', and
858the rest to `inferior-octave-output-string'."
859 (setq string (concat inferior-octave-output-string string))
860 (while (string-match "\n" string)
861 (setq inferior-octave-output-list
862 (append inferior-octave-output-list
863 (list (substring string 0 (match-beginning 0))))
864 string (substring string (match-end 0))))
865 (if (string-match inferior-octave-prompt string)
866 (setq inferior-octave-receive-in-progress nil))
867 (setq inferior-octave-output-string string))
868
30ea8374
LL
869(defun inferior-octave-check-process ()
870 (or (and inferior-octave-process
871 (process-live-p inferior-octave-process))
872 (error (substitute-command-keys
873 "No inferior octave process running. Type \\[run-octave]"))))
874
be64c05d
LL
875(defun inferior-octave-send-list-and-digest (list)
876 "Send LIST to the inferior Octave process and digest the output.
877The elements of LIST have to be strings and are sent one by one. All
878output is passed to the filter `inferior-octave-output-digest'."
30ea8374 879 (inferior-octave-check-process)
be64c05d
LL
880 (let* ((proc inferior-octave-process)
881 (filter (process-filter proc))
882 string)
883 (set-process-filter proc 'inferior-octave-output-digest)
884 (setq inferior-octave-output-list nil)
885 (unwind-protect
886 (while (setq string (car list))
887 (setq inferior-octave-output-string nil
888 inferior-octave-receive-in-progress t)
889 (comint-send-string proc string)
890 (while inferior-octave-receive-in-progress
891 (accept-process-output proc))
892 (setq list (cdr list)))
893 (set-process-filter proc filter))))
894
837fd9af
LL
895(defvar inferior-octave-directory-tracker-resync nil)
896(make-variable-buffer-local 'inferior-octave-directory-tracker-resync)
897
be64c05d
LL
898(defun inferior-octave-directory-tracker (string)
899 "Tracks `cd' commands issued to the inferior Octave process.
900Use \\[inferior-octave-resync-dirs] to resync if Emacs gets confused."
837fd9af 901 (when inferior-octave-directory-tracker-resync
daabf15a
LL
902 (or (inferior-octave-resync-dirs 'noerror)
903 (setq inferior-octave-directory-tracker-resync nil)))
be64c05d
LL
904 (cond
905 ((string-match "^[ \t]*cd[ \t;]*$" string)
906 (cd "~"))
907 ((string-match "^[ \t]*cd[ \t]+\\([^ \t\n;]*\\)[ \t\n;]*" string)
837fd9af
LL
908 (condition-case err
909 (cd (match-string 1 string))
910 (error (setq inferior-octave-directory-tracker-resync t)
911 (message "%s: `%s'"
912 (error-message-string err)
913 (match-string 1 string)))))))
be64c05d 914
daabf15a 915(defun inferior-octave-resync-dirs (&optional noerror)
be64c05d
LL
916 "Resync the buffer's idea of the current directory.
917This command queries the inferior Octave process about its current
918directory and makes this the current buffer's default directory."
919 (interactive)
920 (inferior-octave-send-list-and-digest '("disp (pwd ())\n"))
daabf15a
LL
921 (condition-case err
922 (progn
923 (cd (car inferior-octave-output-list))
924 t)
925 (error (unless noerror (signal (car err) (cdr err))))))
be64c05d 926
c75c93c7
LL
927(defcustom inferior-octave-minimal-columns 80
928 "The minimal column width for the inferior Octave process."
929 :type 'integer
930 :group 'octave
931 :version "24.4")
932
933(defvar inferior-octave-last-column-width nil)
934
935(defun inferior-octave-track-window-width-change ()
936 ;; http://thread.gmane.org/gmane.comp.gnu.octave.general/48572
937 (let ((width (max inferior-octave-minimal-columns (window-width))))
938 (unless (eq inferior-octave-last-column-width width)
939 (setq-local inferior-octave-last-column-width width)
940 (when (and inferior-octave-process
941 (process-live-p inferior-octave-process))
942 (inferior-octave-send-list-and-digest
daabf15a 943 (list (format "putenv ('COLUMNS', '%s');\n" width)))))))
c75c93c7 944
f2727dfb
RS
945\f
946;;; Miscellaneous useful functions
f2727dfb 947
070ccca4
LL
948(defun octave-in-comment-p ()
949 "Return non-nil if point is inside an Octave comment."
602ea69d 950 (nth 4 (syntax-ppss)))
f2727dfb 951
070ccca4
LL
952(defun octave-in-string-p ()
953 "Return non-nil if point is inside an Octave string."
602ea69d 954 (nth 3 (syntax-ppss)))
f2727dfb 955
070ccca4
LL
956(defun octave-in-string-or-comment-p ()
957 "Return non-nil if point is inside an Octave string or comment."
958 (nth 8 (syntax-ppss)))
f2727dfb 959
a584f30f 960(defun octave-looking-at-kw (regexp)
050a4b35 961 "Like `looking-at', but sets `case-fold-search' nil."
a584f30f
GM
962 (let ((case-fold-search nil))
963 (looking-at regexp)))
964
d83ee578
KH
965(defun octave-maybe-insert-continuation-string ()
966 (if (or (octave-in-comment-p)
967 (save-excursion
968 (beginning-of-line)
969 (looking-at octave-continuation-regexp)))
970 nil
971 (delete-horizontal-space)
972 (insert (concat " " octave-continuation-string))))
e55d3b04 973
d4d0f9b3
LL
974(defun octave-completing-read ()
975 (let ((def (or (thing-at-point 'symbol)
976 (save-excursion
977 (skip-syntax-backward "-(")
978 (thing-at-point 'symbol)))))
979 (completing-read
980 (format (if def "Function (default %s): "
981 "Function: ") def)
f71c50d0 982 inferior-octave-completion-table
d4d0f9b3
LL
983 nil nil nil nil def)))
984
e3772e98
LL
985(defun octave-goto-function-definition (fn)
986 "Go to the function definition of FN in current buffer."
e3772e98
LL
987 (let ((search
988 (lambda (re sub)
fda74125
LL
989 (let ((orig (point)) found)
990 (goto-char (point-min))
991 (while (and (not found) (re-search-forward re nil t))
e3772e98
LL
992 (when (and (equal (match-string sub) fn)
993 (not (nth 8 (syntax-ppss))))
fda74125
LL
994 (setq found t)))
995 (unless found (goto-char orig))
996 found))))
daabf15a 997 (pcase (and buffer-file-name (file-name-extension buffer-file-name))
e3772e98 998 (`"cc" (funcall search
9cc3e83f 999 "\\_<DEFUN\\(?:_DLD\\)?\\s-*(\\s-*\\(\\(?:\\sw\\|\\s_\\)+\\)" 1))
e3772e98 1000 (t (funcall search octave-function-header-regexp 3)))))
d4d0f9b3 1001
b7260dd4
LL
1002(defun octave-function-file-p ()
1003 "Return non-nil if the first token is \"function\".
1004The value is (START END NAME-START NAME-END) of the function."
1005 (save-excursion
1006 (goto-char (point-min))
1007 (when (equal (funcall smie-forward-token-function) "function")
1008 (forward-word -1)
1009 (let* ((start (point))
1010 (end (progn (forward-sexp 1) (point)))
1011 (name (when (progn
1012 (goto-char start)
1013 (re-search-forward octave-function-header-regexp
1014 end t))
1015 (list (match-beginning 3) (match-end 3)))))
1016 (cons start (cons end name))))))
1017
1018;; Like forward-comment but stop at non-comment blank
1019(defun octave-skip-comment-forward (limit)
1020 (let ((ppss (syntax-ppss)))
1021 (if (nth 4 ppss)
1022 (goto-char (nth 8 ppss))
1023 (goto-char (or (comment-search-forward limit t) (point)))))
1024 (while (and (< (point) limit) (looking-at-p "\\s<"))
1025 (forward-comment 1)))
1026
1027;;; First non-copyright comment block
1028(defun octave-function-file-comment ()
2ec12cb0 1029 "Beginning and end positions of the function file comment."
b7260dd4
LL
1030 (save-excursion
1031 (goto-char (point-min))
38d8527b
LL
1032 ;; Copyright block: octave/libinterp/parse-tree/lex.ll around line 1634
1033 (while (save-excursion
1034 (when (comment-search-forward (point-max) t)
1035 (when (eq (char-after) ?\{) ; case of block comment
1036 (forward-char 1))
1037 (skip-syntax-forward "-")
1038 (let ((case-fold-search t))
1039 (looking-at-p "\\(?:copyright\\|author\\)\\_>"))))
1040 (octave-skip-comment-forward (point-max)))
1041 (let ((beg (comment-search-forward (point-max) t)))
1042 (when beg
1043 (goto-char beg)
1044 (octave-skip-comment-forward (point-max))
1045 (list beg (point))))))
b7260dd4 1046
e55d3b04
LL
1047(defun octave-sync-function-file-names ()
1048 "Ensure function name agree with function file name.
1049See Info node `(octave)Function Files'."
1050 (interactive)
b7260dd4
LL
1051 (when buffer-file-name
1052 (pcase-let ((`(,start ,_end ,name-start ,name-end)
1053 (octave-function-file-p)))
1054 (when (and start name-start)
2ec12cb0
LL
1055 (let* ((func (buffer-substring name-start name-end))
1056 (file (file-name-sans-extension
1057 (file-name-nondirectory buffer-file-name)))
1058 (help-form (format "\
1059a: Use function name `%s'
1060b: Use file name `%s'
1061q: Don't fix\n" func file))
1062 (c (unless (equal file func)
1063 (save-window-excursion
1064 (help-form-show)
1065 (read-char-choice
1066 "Which name to use? (a/b/q) " '(?a ?b ?q))))))
1067 (pcase c
1068 (`?a (let ((newname (expand-file-name
1069 (concat func (file-name-extension
1070 buffer-file-name t)))))
1071 (when (or (not (file-exists-p newname))
1072 (yes-or-no-p
1073 (format "Target file %s exists; proceed? " newname)))
1074 (when (file-exists-p buffer-file-name)
1075 (rename-file buffer-file-name newname t))
1076 (set-visited-file-name newname))))
1077 (`?b (save-excursion
1078 (goto-char name-start)
1079 (delete-region name-start name-end)
1080 (insert file)))))))))
b7260dd4
LL
1081
1082(defun octave-update-function-file-comment (beg end)
1083 "Query replace function names in function file comment."
1084 (interactive
1085 (progn
1086 (barf-if-buffer-read-only)
1087 (if (use-region-p)
1088 (list (region-beginning) (region-end))
1089 (or (octave-function-file-comment)
1090 (error "No function file comment found")))))
e55d3b04 1091 (save-excursion
b7260dd4
LL
1092 (let* ((bounds (or (octave-function-file-p)
1093 (error "Not in a function file buffer")))
1094 (func (if (cddr bounds)
1095 (apply #'buffer-substring (cddr bounds))
1096 (error "Function name not found")))
1097 (old-func (progn
1098 (goto-char beg)
38d8527b
LL
1099 (when (re-search-forward
1100 "[=}]\\s-*\\(\\(?:\\sw\\|\\s_\\)+\\)\\_>"
1101 (min (line-end-position 4) end)
1102 t)
b7260dd4
LL
1103 (match-string 1))))
1104 (old-func (read-string (format (if old-func
1105 "Name to replace (default %s): "
1106 "Name to replace: ")
1107 old-func)
1108 nil nil old-func)))
1109 (if (and func old-func (not (equal func old-func)))
1110 (perform-replace old-func func 'query
1111 nil 'delimited nil nil beg end)
1112 (message "Function names match")))))
e55d3b04 1113
5b78d7fc
LL
1114(defface octave-function-comment-block
1115 '((t (:inherit font-lock-doc-face)))
1116 "Face used to highlight function comment block."
1117 :group 'octave)
1118
1e2c18df
SM
1119(eval-when-compile (require 'texinfo))
1120
5b78d7fc 1121(defun octave-font-lock-texinfo-comment ()
1e2c18df
SM
1122 (let ((kws
1123 (eval-when-compile
1124 (delq nil (mapcar
1125 (lambda (kw)
1126 (if (numberp (nth 1 kw))
1127 `(,(nth 0 kw) ,(nth 1 kw) ,(nth 2 kw) prepend)
1128 (message "Ignoring Texinfo highlight: %S" kw)))
1129 texinfo-font-lock-keywords)))))
1130 (font-lock-add-keywords
1131 nil
1132 `((,(lambda (limit)
6073d8f4
LL
1133 (while (and (< (point) limit)
1134 (search-forward "-*- texinfo -*-" limit t)
1e2c18df
SM
1135 (octave-in-comment-p))
1136 (let ((beg (nth 8 (syntax-ppss)))
1137 (end (progn
1138 (octave-skip-comment-forward (point-max))
1139 (point))))
1140 (put-text-property beg end 'font-lock-multiline t)
1141 (font-lock-prepend-text-property
1142 beg end 'face 'octave-function-comment-block)
1143 (dolist (kw kws)
1144 (goto-char beg)
1145 (while (re-search-forward (car kw) end 'move)
1146 (font-lock-apply-highlight (cdr kw))))))
1147 nil)))
1148 'append)))
5b78d7fc 1149
f2727dfb
RS
1150\f
1151;;; Indentation
f2727dfb 1152
9cc3e83f 1153(defun octave-indent-new-comment-line (&optional soft)
650cff3d
SM
1154 ;; FIXME: C-M-j should probably be bound globally to a function like
1155 ;; this one.
f2727dfb 1156 "Break Octave line at point, continuing comment if within one.
9cc3e83f
LL
1157Insert `octave-continuation-string' before breaking the line
1158unless inside a list. Signal an error if within a single-quoted
1159string."
f2727dfb 1160 (interactive)
650cff3d
SM
1161 (funcall comment-line-break-function soft))
1162
1163(defun octave--indent-new-comment-line (orig &rest args)
f2727dfb 1164 (cond
9cc3e83f
LL
1165 ((octave-in-comment-p) nil)
1166 ((eq (octave-in-string-p) ?')
1167 (error "Cannot split a single-quoted string"))
1168 ((eq (octave-in-string-p) ?\")
1169 (insert octave-continuation-string))
f2727dfb 1170 (t
9cc3e83f
LL
1171 (delete-horizontal-space)
1172 (unless (and (cadr (syntax-ppss))
1173 (eq (char-after (cadr (syntax-ppss))) ?\())
1174 (insert " " octave-continuation-string))))
650cff3d 1175 (apply orig args)
9cc3e83f 1176 (indent-according-to-mode))
f2727dfb 1177
2d4bf34b
LL
1178(define-obsolete-function-alias
1179 'octave-indent-defun 'prog-indent-sexp "24.4")
f2727dfb
RS
1180
1181\f
1182;;; Motion
1183(defun octave-next-code-line (&optional arg)
1184 "Move ARG lines of Octave code forward (backward if ARG is negative).
1185Skips past all empty and comment lines. Default for ARG is 1.
1186
1187On success, return 0. Otherwise, go as far as possible and return -1."
1188 (interactive "p")
1189 (or arg (setq arg 1))
1190 (beginning-of-line)
1191 (let ((n 0)
1192 (inc (if (> arg 0) 1 -1)))
1193 (while (and (/= arg 0) (= n 0))
1194 (setq n (forward-line inc))
1195 (while (and (= n 0)
1196 (looking-at "\\s-*\\($\\|\\s<\\)"))
1197 (setq n (forward-line inc)))
1198 (setq arg (- arg inc)))
1199 n))
a1506d29 1200
f2727dfb
RS
1201(defun octave-previous-code-line (&optional arg)
1202 "Move ARG lines of Octave code backward (forward if ARG is negative).
1203Skips past all empty and comment lines. Default for ARG is 1.
1204
1205On success, return 0. Otherwise, go as far as possible and return -1."
1206 (interactive "p")
1207 (or arg (setq arg 1))
1208 (octave-next-code-line (- arg)))
1209
1210(defun octave-beginning-of-line ()
1211 "Move point to beginning of current Octave line.
1212If on an empty or comment line, go to the beginning of that line.
1213Otherwise, move backward to the beginning of the first Octave code line
1214which is not inside a continuation statement, i.e., which does not
083fe0d7 1215follow a code line ending with `...' or is inside an open
f2727dfb
RS
1216parenthesis list."
1217 (interactive)
1218 (beginning-of-line)
083fe0d7
LL
1219 (unless (looking-at "\\s-*\\($\\|\\s<\\)")
1220 (while (or (when (cadr (syntax-ppss))
1221 (goto-char (cadr (syntax-ppss)))
1222 (beginning-of-line)
1223 t)
1224 (and (or (looking-at "\\s-*\\($\\|\\s<\\)")
1225 (save-excursion
1226 (if (zerop (octave-previous-code-line))
1227 (looking-at octave-continuation-regexp))))
1228 (zerop (forward-line -1)))))))
f2727dfb
RS
1229
1230(defun octave-end-of-line ()
1231 "Move point to end of current Octave line.
1232If on an empty or comment line, go to the end of that line.
1233Otherwise, move forward to the end of the first Octave code line which
083fe0d7 1234does not end with `...' or is inside an open parenthesis list."
f2727dfb
RS
1235 (interactive)
1236 (end-of-line)
083fe0d7
LL
1237 (unless (save-excursion
1238 (beginning-of-line)
1239 (looking-at "\\s-*\\($\\|\\s<\\)"))
1240 (while (or (when (cadr (syntax-ppss))
1241 (condition-case nil
1242 (progn
1243 (up-list 1)
1244 (end-of-line)
1245 t)
1246 (error nil)))
1247 (and (save-excursion
1248 (beginning-of-line)
1249 (or (looking-at "\\s-*\\($\\|\\s<\\)")
1250 (looking-at octave-continuation-regexp)))
1251 (zerop (forward-line 1)))))
f2727dfb 1252 (end-of-line)))
a1506d29 1253
f2727dfb
RS
1254(defun octave-mark-block ()
1255 "Put point at the beginning of this Octave block, mark at the end.
1256The block marked is the one that contains point or follows point."
1257 (interactive)
ef54d315
SM
1258 (if (and (looking-at "\\sw\\|\\s_")
1259 (looking-back "\\sw\\|\\s_" (1- (point))))
1260 (skip-syntax-forward "w_"))
ec5d3ff7
SM
1261 (unless (or (looking-at "\\s(")
1262 (save-excursion
1263 (let* ((token (funcall smie-forward-token-function))
6d2a1e35 1264 (level (assoc token smie-grammar)))
ef54d315 1265 (and level (not (numberp (cadr level)))))))
ec5d3ff7
SM
1266 (backward-up-list 1))
1267 (mark-sexp))
f2727dfb 1268
f2727dfb 1269(defun octave-beginning-of-defun (&optional arg)
f71c50d0
LL
1270 "Octave-specific `beginning-of-defun-function' (which see)."
1271 (or arg (setq arg 1))
1272 ;; Move out of strings or comments.
1273 (when (octave-in-string-or-comment-p)
1274 (goto-char (octave-in-string-or-comment-p)))
1275 (letrec ((orig (point))
1276 (toplevel (lambda (pos)
1277 (condition-case nil
1278 (progn
1279 (backward-up-list 1)
1280 (funcall toplevel (point)))
1281 (scan-error pos)))))
1282 (goto-char (funcall toplevel (point)))
1283 (when (and (> arg 0) (/= orig (point)))
1284 (setq arg (1- arg)))
1285 (forward-sexp (- arg))
9cc3e83f 1286 (and (< arg 0) (forward-sexp -1))
f71c50d0 1287 (/= orig (point))))
f2727dfb 1288
e02f48d7 1289(defun octave-fill-paragraph (&optional _arg)
ec5d3ff7
SM
1290 "Fill paragraph of Octave code, handling Octave comments."
1291 ;; FIXME: difference with generic fill-paragraph:
1292 ;; - code lines are only split, never joined.
1293 ;; - \n that end comments are never removed.
1294 ;; - insert continuation marker when splitting code lines.
1295 (interactive "P")
1296 (save-excursion
1297 (let ((end (progn (forward-paragraph) (copy-marker (point) t)))
1298 (beg (progn
1299 (forward-paragraph -1)
1300 (skip-chars-forward " \t\n")
1301 (beginning-of-line)
1302 (point)))
1303 (cfc (current-fill-column))
1304 comment-prefix)
1305 (goto-char beg)
1306 (while (< (point) end)
1307 (condition-case nil
1308 (indent-according-to-mode)
1309 (error nil))
1310 (move-to-column cfc)
1311 ;; First check whether we need to combine non-empty comment lines
1312 (if (and (< (current-column) cfc)
1313 (octave-in-comment-p)
1314 (not (save-excursion
1315 (beginning-of-line)
1316 (looking-at "^\\s-*\\s<+\\s-*$"))))
1317 ;; This is a nonempty comment line which does not extend
1318 ;; past the fill column. If it is followed by a nonempty
1319 ;; comment line with the same comment prefix, try to
1320 ;; combine them, and repeat this until either we reach the
1321 ;; fill-column or there is nothing more to combine.
1322 (progn
1323 ;; Get the comment prefix
1324 (save-excursion
1325 (beginning-of-line)
1326 (while (and (re-search-forward "\\s<+")
1327 (not (octave-in-comment-p))))
1328 (setq comment-prefix (match-string 0)))
1329 ;; And keep combining ...
1330 (while (and (< (current-column) cfc)
1331 (save-excursion
1332 (forward-line 1)
1333 (and (looking-at
1334 (concat "^\\s-*"
1335 comment-prefix
1336 "\\S<"))
1337 (not (looking-at
1338 (concat "^\\s-*"
1339 comment-prefix
1340 "\\s-*$"))))))
1341 (delete-char 1)
1342 (re-search-forward comment-prefix)
1343 (delete-region (match-beginning 0) (match-end 0))
1344 (fixup-whitespace)
1345 (move-to-column cfc))))
1346 ;; We might also try to combine continued code lines> Perhaps
1347 ;; some other time ...
1348 (skip-chars-forward "^ \t\n")
1349 (delete-horizontal-space)
1350 (if (or (< (current-column) cfc)
1351 (and (= (current-column) cfc) (eolp)))
1352 (forward-line 1)
1353 (if (not (eolp)) (insert " "))
ee44b62a 1354 (or (funcall normal-auto-fill-function)
ec5d3ff7
SM
1355 (forward-line 1))))
1356 t)))
f2727dfb 1357
203a5572 1358(defun octave-completion-at-point ()
c82d5b11 1359 "Find the text to complete and the corresponding table."
e92f3bd3 1360 (let* ((beg (save-excursion (skip-syntax-backward "w_") (point)))
ff80a446
SM
1361 (end (point)))
1362 (if (< beg (point))
1363 ;; Extend region past point, if applicable.
e92f3bd3
SM
1364 (save-excursion (skip-syntax-forward "w_")
1365 (setq end (point))))
203a5572
LL
1366 (when (> end beg)
1367 (list beg end (or (and inferior-octave-process
1368 (process-live-p inferior-octave-process)
f71c50d0 1369 inferior-octave-completion-table)
203a5572 1370 octave-reserved-words)))))
f2727dfb 1371
bcd70d97
SM
1372(define-obsolete-function-alias 'octave-complete-symbol
1373 'completion-at-point "24.1")
fda74125
LL
1374
1375(defun octave-add-log-current-defun ()
1376 "A function for `add-log-current-defun-function' (which see)."
1377 (save-excursion
1378 (end-of-line)
1379 (and (beginning-of-defun)
1380 (re-search-forward octave-function-header-regexp
1381 (line-end-position) t)
1382 (match-string 3))))
1383
f2727dfb
RS
1384\f
1385;;; Electric characters && friends
c82d5b11 1386(define-skeleton octave-insert-defun
f2727dfb
RS
1387 "Insert an Octave function skeleton.
1388Prompt for the function's name, arguments and return values (to be
1389entered without parens)."
070ccca4 1390 (let* ((defname (file-name-sans-extension (buffer-name)))
c82d5b11
SM
1391 (name (read-string (format "Function name (default %s): " defname)
1392 nil nil defname))
1393 (args (read-string "Arguments: "))
1394 (vals (read-string "Return values: ")))
1395 (format "%s%s (%s)"
1396 (cond
1397 ((string-equal vals "") vals)
1398 ((string-match "[ ,]" vals) (concat "[" vals "] = "))
1399 (t (concat vals " = ")))
1400 name
1401 args))
b7260dd4
LL
1402 \n octave-block-comment-start "usage: " str \n
1403 octave-block-comment-start '(delete-horizontal-space) \n
1404 octave-block-comment-start '(delete-horizontal-space) \n
1405 "function " > str \n
1406 _ \n
c82d5b11 1407 "endfunction" > \n)
fda74125 1408
f2727dfb
RS
1409;;; Communication with the inferior Octave process
1410(defun octave-kill-process ()
1411 "Kill inferior Octave process and its buffer."
1412 (interactive)
1413 (if inferior-octave-process
1414 (progn
1415 (process-send-string inferior-octave-process "quit;\n")
1416 (accept-process-output inferior-octave-process)))
1417 (if inferior-octave-buffer
1418 (kill-buffer inferior-octave-buffer)))
1419
1420(defun octave-show-process-buffer ()
1421 "Make sure that `inferior-octave-buffer' is displayed."
1422 (interactive)
1423 (if (get-buffer inferior-octave-buffer)
1424 (display-buffer inferior-octave-buffer)
1425 (message "No buffer named %s" inferior-octave-buffer)))
1426
1427(defun octave-hide-process-buffer ()
1428 "Delete all windows that display `inferior-octave-buffer'."
1429 (interactive)
1430 (if (get-buffer inferior-octave-buffer)
1431 (delete-windows-on inferior-octave-buffer)
1432 (message "No buffer named %s" inferior-octave-buffer)))
1433
1434(defun octave-send-region (beg end)
1435 "Send current region to the inferior Octave process."
1436 (interactive "r")
a1506d29 1437 (inferior-octave t)
f2727dfb 1438 (let ((proc inferior-octave-process)
c129b51f
LL
1439 (string (buffer-substring-no-properties beg end))
1440 line)
5d8137ab 1441 (with-current-buffer inferior-octave-buffer
f2727dfb
RS
1442 (setq inferior-octave-output-list nil)
1443 (while (not (string-equal string ""))
c129b51f
LL
1444 (if (string-match "\n" string)
1445 (setq line (substring string 0 (match-beginning 0))
1446 string (substring string (match-end 0)))
1447 (setq line string string ""))
1448 (setq inferior-octave-receive-in-progress t)
1449 (inferior-octave-send-list-and-digest (list (concat line "\n")))
1450 (while inferior-octave-receive-in-progress
1451 (accept-process-output proc))
1452 (insert-before-markers
1453 (mapconcat 'identity
1454 (append
1455 (if octave-send-echo-input (list line) (list ""))
1456 inferior-octave-output-list
1457 (list inferior-octave-output-string))
1458 "\n")))))
f2727dfb
RS
1459 (if octave-send-show-buffer
1460 (display-buffer inferior-octave-buffer)))
1461
1462(defun octave-send-block ()
a1506d29 1463 "Send current Octave block to the inferior Octave process."
f2727dfb
RS
1464 (interactive)
1465 (save-excursion
1466 (octave-mark-block)
1467 (octave-send-region (point) (mark))))
1468
1469(defun octave-send-defun ()
1470 "Send current Octave function to the inferior Octave process."
1471 (interactive)
1472 (save-excursion
ff80a446 1473 (mark-defun)
f2727dfb
RS
1474 (octave-send-region (point) (mark))))
1475
1476(defun octave-send-line (&optional arg)
1477 "Send current Octave code line to the inferior Octave process.
1478With positive prefix ARG, send that many lines.
1479If `octave-send-line-auto-forward' is non-nil, go to the next unsent
1480code line."
1481 (interactive "P")
1482 (or arg (setq arg 1))
1483 (if (> arg 0)
1484 (let (beg end)
1485 (beginning-of-line)
1486 (setq beg (point))
1487 (octave-next-code-line (- arg 1))
1488 (end-of-line)
1489 (setq end (point))
1490 (if octave-send-line-auto-forward
1491 (octave-next-code-line 1))
1492 (octave-send-region beg end))))
1493
1494(defun octave-eval-print-last-sexp ()
1495 "Evaluate Octave sexp before point and print value into current buffer."
1496 (interactive)
1497 (inferior-octave t)
1498 (let ((standard-output (current-buffer))
1499 (print-escape-newlines nil)
1500 (opoint (point)))
1501 (terpri)
a1506d29 1502 (prin1
f2727dfb
RS
1503 (save-excursion
1504 (forward-sexp -1)
1505 (inferior-octave-send-list-and-digest
1506 (list (concat (buffer-substring-no-properties (point) opoint)
1507 "\n")))
1508 (mapconcat 'identity inferior-octave-output-list "\n")))
1509 (terpri)))
070ccca4 1510
d4d0f9b3
LL
1511\f
1512
c8730c3a
LL
1513(defcustom octave-eldoc-message-style 'auto
1514 "Octave eldoc message style: auto, oneline, multiline."
1515 :type '(choice (const :tag "Automatic" auto)
1516 (const :tag "One Line" oneline)
1517 (const :tag "Multi Line" multiline))
1518 :group 'octave
1519 :version "24.4")
1520
1521;; (FN SIGNATURE1 SIGNATURE2 ...)
1522(defvar octave-eldoc-cache nil)
1523
1524(defun octave-eldoc-function-signatures (fn)
1525 (unless (equal fn (car octave-eldoc-cache))
1526 (inferior-octave-send-list-and-digest
daabf15a 1527 (list (format "print_usage ('%s');\n" fn)))
c8730c3a
LL
1528 (let (result)
1529 (dolist (line inferior-octave-output-list)
1530 (when (string-match
1531 "\\s-*\\(?:--[^:]+\\|usage\\):\\s-*\\(.*\\)$"
1532 line)
1533 (push (match-string 1 line) result)))
1534 (setq octave-eldoc-cache
1535 (cons (substring-no-properties fn)
1536 (nreverse result)))))
1537 (cdr octave-eldoc-cache))
1538
1539(defun octave-eldoc-function ()
1540 "A function for `eldoc-documentation-function' (which see)."
1541 (when (and inferior-octave-process
1542 (process-live-p inferior-octave-process))
1543 (let* ((ppss (syntax-ppss))
1544 (paren-pos (cadr ppss))
1545 (fn (save-excursion
1546 (if (and paren-pos
1547 ;; PAREN-POS must be after the prompt
1548 (>= paren-pos
1549 (if (eq (get-buffer-process (current-buffer))
1550 inferior-octave-process)
1551 (process-mark inferior-octave-process)
1552 (point-min)))
1553 (or (not (eq (get-buffer-process (current-buffer))
1554 inferior-octave-process))
1555 (< (process-mark inferior-octave-process)
1556 paren-pos))
1557 (eq (char-after paren-pos) ?\())
1558 (goto-char paren-pos)
1559 (setq paren-pos nil))
1560 (when (or (< (skip-syntax-backward "-") 0) paren-pos)
1561 (thing-at-point 'symbol))))
1562 (sigs (and fn (octave-eldoc-function-signatures fn)))
1563 (oneline (mapconcat 'identity sigs
1564 (propertize " | " 'face 'warning)))
1565 (multiline (mapconcat (lambda (s) (concat "-- " s)) sigs "\n")))
1566 ;;
1567 ;; Return the value according to style.
1568 (pcase octave-eldoc-message-style
1569 (`auto (if (< (length oneline) (window-width (minibuffer-window)))
1570 oneline
1571 multiline))
1572 (`oneline oneline)
1573 (`multiline multiline)))))
1574
d4d0f9b3
LL
1575(defcustom octave-help-buffer "*Octave Help*"
1576 "Buffer name for `octave-help'."
1577 :type 'string
1578 :group 'octave
1579 :version "24.4")
1580
1581(define-button-type 'octave-help-file
1582 'follow-link t
1583 'action #'help-button-action
c129b51f 1584 'help-function 'octave-find-definition)
d4d0f9b3
LL
1585
1586(define-button-type 'octave-help-function
1587 'follow-link t
1588 'action (lambda (b)
1589 (octave-help
1590 (buffer-substring (button-start b) (button-end b)))))
1591
b4da2cbb
LL
1592(defvar octave-help-mode-map
1593 (let ((map (make-sparse-keymap)))
1594 (define-key map "\M-." 'octave-find-definition)
1595 (define-key map "\C-hd" 'octave-help)
1596 map))
1597
1598(define-derived-mode octave-help-mode help-mode "OctHelp"
1599 "Major mode for displaying Octave documentation."
1600 :abbrev-table nil
1601 :syntax-table octave-mode-syntax-table
1602 (eval-and-compile (require 'help-mode))
1603 ;; Mostly stolen from `help-make-xrefs'.
1604 (let ((inhibit-read-only t))
1605 (setq-local info-lookup-mode 'octave-mode)
1606 ;; Delete extraneous newlines at the end of the docstring
1607 (goto-char (point-max))
1608 (while (and (not (bobp)) (bolp))
1609 (delete-char -1))
1610 (insert "\n")
1611 (when (or help-xref-stack help-xref-forward-stack)
1612 (insert "\n"))
1613 (when help-xref-stack
1614 (help-insert-xref-button help-back-label 'help-back
1615 (current-buffer)))
1616 (when help-xref-forward-stack
1617 (when help-xref-stack
1618 (insert "\t"))
1619 (help-insert-xref-button help-forward-label 'help-forward
1620 (current-buffer)))
1621 (when (or help-xref-stack help-xref-forward-stack)
1622 (insert "\n"))))
1623
d4d0f9b3
LL
1624(defun octave-help (fn)
1625 "Display the documentation of FN."
1626 (interactive (list (octave-completing-read)))
1627 (inferior-octave-send-list-and-digest
daabf15a 1628 (list (format "help ('%s');\n" fn)))
b4da2cbb
LL
1629 (let ((lines inferior-octave-output-list)
1630 (inhibit-read-only t))
d4d0f9b3
LL
1631 (when (string-match "error: \\(.*\\)$" (car lines))
1632 (error "%s" (match-string 1 (car lines))))
1633 (with-help-window octave-help-buffer
1634 (princ (mapconcat 'identity lines "\n"))
1635 (with-current-buffer octave-help-buffer
1636 ;; Bound to t so that `help-buffer' returns current buffer for
1637 ;; `help-setup-xref'.
1638 (let ((help-xref-following t))
1639 (help-setup-xref (list 'octave-help fn)
1640 (called-interactively-p 'interactive)))
1641 ;; Note: can be turned off by suppress_verbose_help_message.
1642 ;;
1643 ;; Remove boring trailing text: Additional help for built-in functions
1644 ;; and operators ...
1645 (goto-char (point-max))
1646 (when (search-backward "\n\n\n" nil t)
1647 (goto-char (match-beginning 0))
1648 (delete-region (point) (point-max)))
1649 ;; File name highlight
1650 (goto-char (point-min))
1651 (when (re-search-forward "from the file \\(.*\\)$"
1652 (line-end-position)
1653 t)
9cc3e83f
LL
1654 (let* ((file (match-string 1))
1655 (dir (file-name-directory
1656 (directory-file-name (file-name-directory file)))))
d4d0f9b3
LL
1657 (replace-match "" nil nil nil 1)
1658 (insert "`")
9cc3e83f
LL
1659 ;; Include the parent directory which may be regarded as
1660 ;; the category for the FN.
1661 (help-insert-xref-button (file-relative-name file dir)
c129b51f 1662 'octave-help-file fn)
d4d0f9b3 1663 (insert "'")))
daabf15a 1664 ;; Make 'See also' clickable.
d4d0f9b3
LL
1665 (with-syntax-table octave-mode-syntax-table
1666 (when (re-search-forward "^\\s-*See also:" nil t)
f236dd84 1667 (let ((end (save-excursion (re-search-forward "^\\s-*$" nil t))))
daabf15a
LL
1668 (while (re-search-forward
1669 ;; Match operators and symbols.
1670 "\\(?1:\\s.+?\\)\\(?:$\\|[,;]\\|\\s-\\)\\|\\_<\\(?1:\\(?:\\sw\\|\\s_\\)+\\)\\_>"
1671 end t)
1672 (make-text-button (match-beginning 1) (match-end 1)
837fd9af 1673 :type 'octave-help-function)))))
b4da2cbb 1674 (octave-help-mode)))))
d4d0f9b3 1675
30ea8374 1676(defcustom octave-source-directories nil
5e80b74f
LL
1677 "A list of directories for Octave sources.
1678If the environment variable OCTAVE_SRCDIR is set, it is searched first."
30ea8374 1679 :type '(repeat directory)
c129b51f
LL
1680 :group 'octave
1681 :version "24.4")
1682
30ea8374 1683(defun octave-source-directories ()
5e80b74f
LL
1684 (let ((srcdir (or (and inferior-octave-process
1685 (process-get inferior-octave-process 'octave-srcdir))
1686 (getenv "OCTAVE_SRCDIR"))))
30ea8374
LL
1687 (if srcdir
1688 (cons srcdir octave-source-directories)
1689 octave-source-directories)))
1690
1691(defvar octave-find-definition-filename-function
1692 #'octave-find-definition-default-filename)
1693
1694(defun octave-find-definition-default-filename (name)
1695 "Default value for `octave-find-definition-filename-function'."
1696 (pcase (file-name-extension name)
1697 (`"oct"
1698 (octave-find-definition-default-filename
1699 (concat "libinterp/dldfcn/"
1700 (file-name-sans-extension (file-name-nondirectory name))
1701 ".cc")))
1702 (`"cc"
1703 (let ((file (or (locate-file name (octave-source-directories))
1704 (locate-file (file-name-nondirectory name)
1705 (octave-source-directories)))))
1706 (or (and file (file-exists-p file))
1707 (error "File `%s' not found" name))
1708 file))
1709 (`"mex"
1710 (if (yes-or-no-p (format "File `%s' may be binary; open? "
1711 (file-name-nondirectory name)))
1712 name
1713 (user-error "Aborted")))
1714 (t name)))
1715
d4d0f9b3
LL
1716(defvar find-tag-marker-ring)
1717
1718(defun octave-find-definition (fn)
30ea8374 1719 "Find the definition of FN.
5e80b74f 1720Functions implemented in C++ can be found if
30ea8374 1721`octave-source-directories' is set correctly."
d4d0f9b3 1722 (interactive (list (octave-completing-read)))
fda74125
LL
1723 (require 'etags)
1724 (let ((orig (point)))
daabf15a
LL
1725 (if (and (derived-mode-p 'octave-mode)
1726 (octave-goto-function-definition fn))
fda74125
LL
1727 (ring-insert find-tag-marker-ring (copy-marker orig))
1728 (inferior-octave-send-list-and-digest
1729 ;; help NAME is more verbose
1730 (list (format "\
daabf15a 1731if iskeyword('%s') disp('`%s'' is a keyword') else which('%s') endif\n"
fda74125
LL
1732 fn fn fn)))
1733 (let (line file)
1734 ;; Skip garbage lines such as
1735 ;; warning: fmincg.m: possible Matlab-style ....
1736 (while (and (not file) (consp inferior-octave-output-list))
1737 (setq line (pop inferior-octave-output-list))
1738 (when (string-match "from the file \\(.*\\)$" line)
1739 (setq file (match-string 1 line))))
1740 (if (not file)
1741 (user-error "%s" (or line (format "`%s' not found" fn)))
1742 (ring-insert find-tag-marker-ring (point-marker))
1743 (setq file (funcall octave-find-definition-filename-function file))
1744 (when file
1745 (find-file file)
1746 (octave-goto-function-definition fn)))))))
d4d0f9b3 1747
be64c05d
LL
1748(provide 'octave)
1749;;; octave.el ends here