Use octave-help-mode for the Octave Help buffer
[bpt/emacs.git] / lisp / progmodes / octave.el
index 138c194..4f1e6c2 100644 (file)
@@ -38,7 +38,9 @@
 (require 'newcomment)
 (eval-and-compile
   (unless (fboundp 'user-error)
-    (defalias 'user-error 'error)))
+    (defalias 'user-error 'error))
+  (unless (fboundp 'delete-consecutive-dups)
+    (defalias 'delete-consecutive-dups 'delete-dups)))
 (eval-when-compile
   (unless (fboundp 'setq-local)
     (defmacro setq-local (var val)
@@ -60,28 +62,29 @@ Used in `octave-mode' and `inferior-octave-mode' buffers.")
 (defvar octave-comment-char ?#
   "Character to start an Octave comment.")
 
-(defvar octave-comment-start
-  (string octave-comment-char ?\s)
-  "String to insert to start a new Octave in-line comment.")
+(defvar octave-comment-start (char-to-string octave-comment-char)
+  "Octave-specific `comment-start' (which see).")
 
-(defvar octave-comment-start-skip "\\s<+\\s-*"
-  "Regexp to match the start of an Octave comment up to its body.")
+(defvar octave-comment-start-skip "\\(^\\|\\S<\\)\\(?:%!\\|\\s<+\\)\\s-*"
+  "Octave-specific `comment-start-skip' (which see).")
 
 (defvar octave-begin-keywords
-  '("do" "for" "function" "if" "switch" "try" "unwind_protect" "while"))
+  '("classdef" "do" "enumeration" "events" "for" "function" "if" "methods"
+    "parfor" "properties" "switch" "try" "unwind_protect" "while"))
 
 (defvar octave-else-keywords
   '("case" "catch" "else" "elseif" "otherwise" "unwind_protect_cleanup"))
 
 (defvar octave-end-keywords
-  '("endfor" "endfunction" "endif" "endswitch" "end_try_catch"
+  '("endclassdef" "endenumeration" "endevents" "endfor" "endfunction" "endif"
+    "endmethods" "endparfor" "endproperties" "endswitch" "end_try_catch"
     "end_unwind_protect" "endwhile" "until" "end"))
 
 (defvar octave-reserved-words
   (append octave-begin-keywords
          octave-else-keywords
          octave-end-keywords
-         '("break" "continue" "end" "global" "persistent" "return"))
+         '("break" "continue" "global" "persistent" "return"))
   "Reserved words in Octave.")
 
 (defvar octave-function-header-regexp
@@ -94,9 +97,8 @@ parenthetical grouping.")
 \f
 (defvar octave-mode-map
   (let ((map (make-sparse-keymap)))
-    (define-key map "\M-." 'octave-find-definition)
-    (define-key map "\e\n" 'octave-indent-new-comment-line)
-    (define-key map "\M-\C-q" 'octave-indent-defun)
+    (define-key map "\M-."     'octave-find-definition)
+    (define-key map "\M-\C-j"  'octave-indent-new-comment-line)
     (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)
@@ -131,27 +133,26 @@ parenthetical grouping.")
   "Menu for Octave mode."
   '("Octave"
     ("Lines"
-      ["Previous Code Line"    octave-previous-code-line t]
-      ["Next Code Line"                octave-next-code-line t]
-      ["Begin of Continuation" octave-beginning-of-line t]
-      ["End of Continuation"   octave-end-of-line t]
-      ["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 Continuation"  octave-beginning-of-line t]
+     ["End of Continuation"    octave-end-of-line t]
+     ["Split Line at Point"    octave-indent-new-comment-line t])
     ("Blocks"
-      ["Mark Block"            octave-mark-block t]
-      ["Close Block"           smie-close-block t])
+     ["Mark Block"             octave-mark-block t]
+     ["Close Block"            smie-close-block t])
     ("Functions"
-      ["Indent Function"       octave-indent-defun t]
-      ["Insert Function"       octave-insert-defun t]
-      ["Update function file comment" octave-update-function-file-comment t])
+     ["Insert Function"        octave-insert-defun t]
+     ["Update function file comment" octave-update-function-file-comment t])
     "-"
     ("Debug"
-      ["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]
-      ["Show Process Buffer"   octave-show-process-buffer t]
-      ["Hide Process Buffer"   octave-hide-process-buffer t]
-      ["Kill Process"          octave-kill-process t])
+     ["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]
+     ["Show Process Buffer"    octave-show-process-buffer t]
+     ["Hide Process Buffer"    octave-hide-process-buffer t]
+     ["Kill Process"           octave-kill-process t])
     "-"
     ["Indent Line"             indent-according-to-mode t]
     ["Complete Symbol"         completion-at-point t]
@@ -303,6 +304,8 @@ Non-nil means always go to the next Octave code line after sending."
          ("unwind_protect" exp "unwind_protect_cleanup" exp "end")
          ("for" exp "endfor")
          ("for" exp "end")
+         ("parfor" exp "endparfor")
+         ("parfor" exp "end")
          ("do" exp "until" atom)
          ("while" exp "endwhile")
          ("while" exp "end")
@@ -316,7 +319,17 @@ Non-nil means always go to the next Octave code line after sending."
          ("switch" exp "case" exp "case" exp "otherwise" exp "endswitch")
          ("switch" exp "case" exp "case" exp "otherwise" exp "end")
          ("function" exp "endfunction")
-         ("function" exp "end"))
+         ("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"))
     ;; (fundesc (atom "=" atom))
     ))
 
@@ -406,14 +419,29 @@ Non-nil means always go to the next Octave code line after sending."
     ;; aligns it with "switch".
     (`(:before . "case") (if (not (smie-rule-sibling-p)) octave-block-offset))
     (`(:after . ";")
-     (if (smie-rule-parent-p "function" "if" "while" "else" "elseif" "for"
-                             "otherwise" "case" "try" "catch" "unwind_protect"
+     (if (smie-rule-parent-p "classdef" "events" "enumeration" "function" "if"
+                             "while" "else" "elseif" "for" "parfor"
+                             "properties" "methods" "otherwise" "case"
+                             "try" "catch" "unwind_protect"
                              "unwind_protect_cleanup")
          (smie-rule-parent octave-block-offset)
        ;; For (invalid) code between switch and case.
        ;; (if (smie-parent-p "switch") 4)
        0))))
 
+(defun octave-indent-comment ()
+  "A function for `smie-indent-functions' (which see)."
+  (save-excursion
+    (back-to-indentation)
+    (cond
+     ((octave-in-string-or-comment-p) nil)
+     ((looking-at-p "\\s<\\{3,\\}")
+      0)
+     ;; Exclude %{, %} and %!.
+     ((and (looking-at-p "\\s<\\(?:[^{}!]\\|$\\)")
+           (not (looking-at-p "\\s<\\s<")))
+      (comment-choose-indent)))))
+
 \f
 (defvar octave-font-lock-keywords
   (list
@@ -429,13 +457,14 @@ Non-nil means always go to the next Octave code line after sending."
              (let ((beg (match-beginning 0))
                    (end (match-end 0)))
                (unless (octave-in-string-or-comment-p)
-                 (unwind-protect
+                 (condition-case nil
                      (progn
                        (goto-char beg)
                        (backward-up-list)
                        (when (memq (char-after) '(?\( ?\[ ?\{))
-                         (put-text-property beg end 'face nil)))
-                   (goto-char end)))))
+                         (put-text-property beg end 'face nil))
+                       (goto-char end))
+                   (error (goto-char end))))))
            nil))
    ;; Fontify all operators.
    (cons octave-operator-regexp 'font-lock-builtin-face)
@@ -488,6 +517,7 @@ definitions can also be stored in files and used in batch mode."
               :forward-token  #'octave-smie-forward-token
               :backward-token #'octave-smie-backward-token)
   (setq-local smie-indent-basic 'octave-block-offset)
+  (add-hook 'smie-indent-functions #'octave-indent-comment nil t)
 
   (setq-local smie-blink-matching-triggers
               (cons ?\; smie-blink-matching-triggers))
@@ -502,10 +532,7 @@ definitions can also be stored in files and used in batch mode."
 
   (setq-local comment-start octave-comment-start)
   (setq-local comment-end "")
-  ;; Don't set it here: it's not really a property of the language,
-  ;; just a personal preference of the author.
-  ;; (setq-local comment-column 32)
-  (setq-local comment-start-skip "\\s<+\\s-*")
+  (setq-local comment-start-skip octave-comment-start-skip)
   (setq-local comment-add 1)
 
   (setq-local parse-sexp-ignore-comments t)
@@ -530,6 +557,7 @@ definitions can also be stored in files and used in batch mode."
   (add-hook 'before-save-hook 'octave-sync-function-file-names nil t)
   (setq-local beginning-of-defun-function 'octave-beginning-of-defun)
   (and octave-font-lock-texinfo-comment (octave-font-lock-texinfo-comment))
+  (setq-local eldoc-documentation-function 'octave-eldoc-function)
 
   (easy-menu-add octave-mode-menu))
 
@@ -634,6 +662,7 @@ in the Inferior Octave buffer.")
   (setq font-lock-defaults '(inferior-octave-font-lock-keywords nil nil))
 
   (setq-local info-lookup-mode 'octave-mode)
+  (setq-local eldoc-documentation-function 'octave-eldoc-function)
 
   (setq comint-input-ring-file-name
        (or (getenv "OCTAVE_HISTFILE") "~/.octave_hist")
@@ -678,6 +707,11 @@ startup file, `~/.emacs-octave'."
                inferior-octave-buffer
                inferior-octave-program
                (append (list "-i" "--no-line-editing")
+                       ;; --no-gui is introduced in Octave > 3.7
+                       (when (zerop (process-file inferior-octave-program
+                                                  nil nil nil
+                                                  "--no-gui" "--help"))
+                         (list "--no-gui"))
                        inferior-octave-startup-args))))
     (set-process-filter proc 'inferior-octave-output-digest)
     (setq inferior-octave-process proc
@@ -708,6 +742,12 @@ startup file, `~/.emacs-octave'."
                         (car inferior-octave-output-list))
       (inferior-octave-send-list-and-digest (list "PS2 (\"> \");\n")))
 
+    (inferior-octave-send-list-and-digest
+     (list "disp(getenv(\"OCTAVE_SRCDIR\"))\n"))
+    (process-put proc 'octave-srcdir
+                 (unless (equal (car inferior-octave-output-list) "")
+                   (car inferior-octave-output-list)))
+
     ;; O.K., now we are ready for the Inferior Octave startup commands.
     (inferior-octave-send-list-and-digest
      (list "more off;\n"
@@ -716,7 +756,6 @@ startup file, `~/.emacs-octave'."
            (when (and inferior-octave-startup-file
                       (file-exists-p inferior-octave-startup-file))
              (format "source (\"%s\");\n" inferior-octave-startup-file))))
-    ;; XXX: the first prompt is incorrectly highlighted
     (insert-before-markers
      (concat
       (if inferior-octave-output-list
@@ -729,25 +768,42 @@ startup file, `~/.emacs-octave'."
     (set-process-filter proc 'comint-output-filter)
     ;; Just in case, to be sure a cd in the startup file
     ;; won't have detrimental effects.
-    (inferior-octave-resync-dirs)))
-
-(defun inferior-octave-completion-table ()
-  (completion-table-dynamic
-   (lambda (command)
-     (inferior-octave-send-list-and-digest
-      (list (concat "completion_matches (\"" command "\");\n")))
-     (sort (delete-dups inferior-octave-output-list)
-           'string-lessp))))
+    (inferior-octave-resync-dirs)
+    ;; A trick to get the prompt highlighted.
+    (comint-send-string proc "\n")))
+
+(defvar inferior-octave-completion-table
+  ;;
+  ;; Use cache to avoid repetitive computation of completions due to
+  ;; bug#11906 - http://debbugs.gnu.org/11906 - which may cause
+  ;; noticeable delay.  CACHE: (CMD TIME VALUE).
+  (let ((cache))
+    (completion-table-dynamic
+     (lambda (command)
+       (unless (and (equal (car cache) command)
+                    (< (float-time) (+ 5 (cadr cache))))
+         (inferior-octave-send-list-and-digest
+          (list (concat "completion_matches (\"" command "\");\n")))
+         (setq cache (list command (float-time)
+                           (delete-consecutive-dups
+                            (sort inferior-octave-output-list 'string-lessp)))))
+       (car (cddr cache))))))
 
 (defun inferior-octave-completion-at-point ()
   "Return the data to complete the Octave symbol at point."
-  (let* ((end (point))
+  ;; http://debbugs.gnu.org/14300
+  (let* ((filecomp (string-match-p
+                    "/" (or (comint--match-partial-filename) "")))
+         (end (point))
         (start
-         (save-excursion
-           (skip-syntax-backward "w_" (comint-line-beginning-position))
-            (point))))
-    (when (> end start)
-      (list start end (inferior-octave-completion-table)))))
+         (unless filecomp
+            (save-excursion
+              (skip-syntax-backward "w_" (comint-line-beginning-position))
+              (point)))))
+    (when (and start (> end start))
+      (list start end (completion-table-in-turn
+                       inferior-octave-completion-table
+                       'comint-completion-file-name-table)))))
 
 (define-obsolete-function-alias 'inferior-octave-complete
   'completion-at-point "24.1")
@@ -793,14 +849,17 @@ the rest to `inferior-octave-output-string'."
       (setq inferior-octave-receive-in-progress nil))
   (setq inferior-octave-output-string string))
 
+(defun inferior-octave-check-process ()
+  (or (and inferior-octave-process
+           (process-live-p inferior-octave-process))
+      (error (substitute-command-keys
+              "No inferior octave process running. Type \\[run-octave]"))))
+
 (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'."
-  (or (and inferior-octave-process
-           (process-live-p inferior-octave-process))
-      (error (substitute-command-keys
-              "No inferior octave process running. Type \\[run-octave]")))
+  (inferior-octave-check-process)
   (let* ((proc inferior-octave-process)
         (filter (process-filter proc))
         string)
@@ -871,16 +930,24 @@ directory and makes this the current buffer's default directory."
     (completing-read
      (format (if def "Function (default %s): "
                "Function: ") def)
-     (inferior-octave-completion-table)
+     inferior-octave-completion-table
      nil nil nil nil def)))
 
-(defun octave-goto-function-definition ()
-  "Go to the first function definition."
-  (when (save-excursion
-          (goto-char (point-min))
-          (re-search-forward octave-function-header-regexp nil t))
-    (goto-char (match-beginning 3))
-    (match-string 3)))
+(defun octave-goto-function-definition (fn)
+  "Go to the function definition of FN in current buffer."
+  (goto-char (point-min))
+  (let ((search
+         (lambda (re sub)
+           (let (done)
+             (while (and (not done) (re-search-forward re nil t))
+               (when (and (equal (match-string sub) fn)
+                          (not (nth 8 (syntax-ppss))))
+                 (setq done t)))
+             (or done (goto-char (point-min)))))))
+    (pcase (file-name-extension (buffer-file-name))
+      (`"cc" (funcall search
+                      "\\_<DEFUN\\s-*(\\s-*\\(\\(?:\\sw\\|\\s_\\)+\\)" 1))
+      (t (funcall search octave-function-header-regexp 3)))))
 
 (defun octave-function-file-p ()
   "Return non-nil if the first token is \"function\".
@@ -1013,7 +1080,8 @@ q: Don't fix\n" func file))
     (font-lock-add-keywords
      nil
      `((,(lambda (limit)
-           (while (and (search-forward "-*- texinfo -*-" limit t)
+           (while (and (< (point) limit)
+                       (search-forward "-*- texinfo -*-" limit t)
                        (octave-in-comment-p))
              (let ((beg (nth 8 (syntax-ppss)))
                    (end (progn
@@ -1048,14 +1116,8 @@ The new line is properly indented."
     (insert (concat " " octave-continuation-string))
     (reindent-then-newline-and-indent))))
 
-(defun octave-indent-defun ()
-  "Properly indent the Octave function which contains point."
-  (interactive)
-  (save-excursion
-    (mark-defun)
-    (message "Indenting function...")
-    (indent-region (point) (mark) nil))
-  (message "Indenting function...done."))
+(define-obsolete-function-alias
+  'octave-indent-defun 'prog-indent-sexp "24.4")
 
 \f
 ;;; Motion
@@ -1091,45 +1153,43 @@ On success, return 0.  Otherwise, go as far as possible and return -1."
 If on an empty or comment line, go to the beginning of that line.
 Otherwise, move backward to the beginning of the first Octave code line
 which is not inside a continuation statement, i.e., which does not
-follow a code line ending in `...' or `\\', or is inside an open
+follow a code line ending with `...' or is inside an open
 parenthesis list."
   (interactive)
   (beginning-of-line)
-  (if (not (looking-at "\\s-*\\($\\|\\s<\\)"))
-      (while (or (condition-case nil
-                    (progn
-                      (up-list -1)
-                      (beginning-of-line)
-                      t)
-                  (error nil))
-                (and (or (looking-at "\\s-*\\($\\|\\s<\\)")
-                         (save-excursion
-                           (if (zerop (octave-previous-code-line))
-                               (looking-at octave-continuation-regexp))))
-                     (zerop (forward-line -1)))))))
+  (unless (looking-at "\\s-*\\($\\|\\s<\\)")
+    (while (or (when (cadr (syntax-ppss))
+                 (goto-char (cadr (syntax-ppss)))
+                 (beginning-of-line)
+                 t)
+               (and (or (looking-at "\\s-*\\($\\|\\s<\\)")
+                        (save-excursion
+                          (if (zerop (octave-previous-code-line))
+                              (looking-at octave-continuation-regexp))))
+                    (zerop (forward-line -1)))))))
 
 (defun octave-end-of-line ()
   "Move point to end of current Octave line.
 If on an empty or comment line, go to the end of that line.
 Otherwise, move forward to the end of the first Octave code line which
-does not end in `...' or `\\' or is inside an open parenthesis list."
+does not end with `...' or is inside an open parenthesis list."
   (interactive)
   (end-of-line)
-  (if (save-excursion
-       (beginning-of-line)
-       (looking-at "\\s-*\\($\\|\\s<\\)"))
-      ()
-    (while (or (condition-case nil
-                  (progn
-                    (up-list 1)
-                    (end-of-line)
-                    t)
-                (error nil))
-              (and (save-excursion
-                     (beginning-of-line)
-                     (or (looking-at "\\s-*\\($\\|\\s<\\)")
-                         (looking-at octave-continuation-regexp)))
-                   (zerop (forward-line 1)))))
+  (unless (save-excursion
+            (beginning-of-line)
+            (looking-at "\\s-*\\($\\|\\s<\\)"))
+    (while (or (when (cadr (syntax-ppss))
+                 (condition-case nil
+                     (progn
+                       (up-list 1)
+                       (end-of-line)
+                       t)
+                   (error nil)))
+               (and (save-excursion
+                      (beginning-of-line)
+                      (or (looking-at "\\s-*\\($\\|\\s<\\)")
+                          (looking-at octave-continuation-regexp)))
+                    (zerop (forward-line 1)))))
     (end-of-line)))
 
 (defun octave-mark-block ()
@@ -1148,26 +1208,23 @@ The block marked is the one that contains point or follows point."
   (mark-sexp))
 
 (defun octave-beginning-of-defun (&optional arg)
-  "Move backward to the beginning of an Octave function.
-With positive ARG, do it that many times.  Negative argument -N means
-move forward to Nth following beginning of a function.
-Returns t unless search stops at the beginning or end of the buffer."
-  (let* ((arg (or arg 1))
-        (inc (if (> arg 0) 1 -1))
-        (found nil)
-         (case-fold-search nil))
-    (and (not (eobp))
-        (not (and (> arg 0) (looking-at "\\_<function\\_>")))
-        (skip-syntax-forward "w"))
-    (while (and (/= arg 0)
-               (setq found
-                     (re-search-backward "\\_<function\\_>" inc)))
-      (unless (octave-in-string-or-comment-p)
-        (setq arg (- arg inc))))
-    (if found
-       (progn
-         (and (< inc 0) (goto-char (match-beginning 0)))
-         t))))
+  "Octave-specific `beginning-of-defun-function' (which see)."
+  (or arg (setq arg 1))
+  ;; Move out of strings or comments.
+  (when (octave-in-string-or-comment-p)
+    (goto-char (octave-in-string-or-comment-p)))
+  (letrec ((orig (point))
+           (toplevel (lambda (pos)
+                       (condition-case nil
+                           (progn
+                             (backward-up-list 1)
+                             (funcall toplevel (point)))
+                         (scan-error pos)))))
+    (goto-char (funcall toplevel (point)))
+    (when (and (> arg 0) (/= orig (point)))
+      (setq arg (1- arg)))
+    (forward-sexp (- arg))
+    (/= orig (point))))
 
 \f
 ;;; Filling
@@ -1315,7 +1372,7 @@ otherwise."
     (when (> end beg)
       (list beg end (or (and inferior-octave-process
                              (process-live-p inferior-octave-process)
-                             (inferior-octave-completion-table))
+                             inferior-octave-completion-table)
                         octave-reserved-words)))))
 
 (define-obsolete-function-alias 'octave-complete-symbol
@@ -1449,6 +1506,70 @@ code line."
 
 \f
 
+(defcustom octave-eldoc-message-style 'auto
+  "Octave eldoc message style: auto, oneline, multiline."
+  :type '(choice (const :tag "Automatic" auto)
+                 (const :tag "One Line" oneline)
+                 (const :tag "Multi Line" multiline))
+  :group 'octave
+  :version "24.4")
+
+;; (FN SIGNATURE1 SIGNATURE2 ...)
+(defvar octave-eldoc-cache nil)
+
+(defun octave-eldoc-function-signatures (fn)
+  (unless (equal fn (car octave-eldoc-cache))
+    (inferior-octave-send-list-and-digest
+     (list (format "\
+if ismember(exist(\"%s\"), [2 3 5 103]) print_usage(\"%s\") endif\n"
+                   fn fn)))
+    (let (result)
+      (dolist (line inferior-octave-output-list)
+        (when (string-match
+               "\\s-*\\(?:--[^:]+\\|usage\\):\\s-*\\(.*\\)$"
+               line)
+          (push (match-string 1 line) result)))
+      (setq octave-eldoc-cache
+            (cons (substring-no-properties fn)
+                  (nreverse result)))))
+  (cdr octave-eldoc-cache))
+
+(defun octave-eldoc-function ()
+  "A function for `eldoc-documentation-function' (which see)."
+  (when (and inferior-octave-process
+             (process-live-p inferior-octave-process))
+    (let* ((ppss (syntax-ppss))
+           (paren-pos (cadr ppss))
+           (fn (save-excursion
+                 (if (and paren-pos
+                          ;; PAREN-POS must be after the prompt
+                          (>= paren-pos
+                              (if (eq (get-buffer-process (current-buffer))
+                                      inferior-octave-process)
+                                  (process-mark inferior-octave-process)
+                                (point-min)))
+                          (or (not (eq (get-buffer-process (current-buffer))
+                                       inferior-octave-process))
+                              (< (process-mark inferior-octave-process)
+                                 paren-pos))
+                          (eq (char-after paren-pos) ?\())
+                     (goto-char paren-pos)
+                   (setq paren-pos nil))
+                 (when (or (< (skip-syntax-backward "-") 0) paren-pos)
+                   (thing-at-point 'symbol))))
+           (sigs (and fn (octave-eldoc-function-signatures fn)))
+           (oneline (mapconcat 'identity sigs
+                               (propertize " | " 'face 'warning)))
+           (multiline (mapconcat (lambda (s) (concat "-- " s)) sigs "\n")))
+      ;;
+      ;; Return the value according to style.
+      (pcase octave-eldoc-message-style
+        (`auto (if (< (length oneline) (window-width (minibuffer-window)))
+                   oneline
+                 multiline))
+        (`oneline oneline)
+        (`multiline multiline)))))
+
 (defcustom octave-help-buffer "*Octave Help*"
   "Buffer name for `octave-help'."
   :type 'string
@@ -1466,14 +1587,54 @@ code line."
             (octave-help
              (buffer-substring (button-start b) (button-end b)))))
 
-(defvar help-xref-following)
+(defvar octave-help-mode-map
+  (let ((map (make-sparse-keymap)))
+    (define-key map "\M-." 'octave-find-definition)
+    (define-key map "\C-hd" 'octave-help)
+    map))
+
+(define-derived-mode octave-help-mode help-mode "OctHelp"
+  "Major mode for displaying Octave documentation."
+  :abbrev-table nil
+  :syntax-table octave-mode-syntax-table
+  (eval-and-compile (require 'help-mode))
+  ;; Mostly stolen from `help-make-xrefs'.
+  (let ((inhibit-read-only t))
+    (setq-local info-lookup-mode 'octave-mode)
+    ;; Delete extraneous newlines at the end of the docstring
+    (goto-char (point-max))
+    (while (and (not (bobp)) (bolp))
+      (delete-char -1))
+    (insert "\n")
+    (when (or help-xref-stack help-xref-forward-stack)
+      (insert "\n"))
+    (when help-xref-stack
+      (help-insert-xref-button help-back-label 'help-back
+                               (current-buffer)))
+    (when help-xref-forward-stack
+      (when help-xref-stack
+        (insert "\t"))
+      (help-insert-xref-button help-forward-label 'help-forward
+                               (current-buffer)))
+    (when (or help-xref-stack help-xref-forward-stack)
+      (insert "\n"))))
+
+(defvar octave-help-mode-finish-hook nil
+  "Octave specific hook for `temp-buffer-show-hook'.")
+
+(defun octave-help-mode-finish ()
+  (when (eq major-mode 'octave-help-mode)
+    (run-hooks 'octave-help-mode-finish-hook)))
+
+(add-hook 'temp-buffer-show-hook 'octave-help-mode-finish)
 
 (defun octave-help (fn)
   "Display the documentation of FN."
   (interactive (list (octave-completing-read)))
   (inferior-octave-send-list-and-digest
    (list (format "help \"%s\"\n" fn)))
-  (let ((lines inferior-octave-output-list))
+  (let ((lines inferior-octave-output-list)
+        (inhibit-read-only t))
     (when (string-match "error: \\(.*\\)$" (car lines))
       (error "%s" (match-string 1 (car lines))))
     (with-help-window octave-help-buffer
@@ -1484,7 +1645,6 @@ code line."
         (let ((help-xref-following t))
           (help-setup-xref (list 'octave-help fn)
                            (called-interactively-p 'interactive)))
-        (setq-local info-lookup-mode 'octave-mode)
         ;; Note: can be turned off by suppress_verbose_help_message.
         ;;
         ;; Remove boring trailing text: Additional help for built-in functions
@@ -1510,18 +1670,55 @@ code line."
             (while (re-search-forward "\\_<\\(?:\\sw\\|\\s_\\)+\\_>" nil t)
               (make-text-button (match-beginning 0)
                                 (match-end 0)
-                                :type 'octave-help-function))))))))
+                                :type 'octave-help-function))))
+        (octave-help-mode)))))
 
-(defcustom octave-binary-file-extensions '("oct" "mex")
-  "A list of binary file extensions for Octave."
-  :type '(repeat string)
+(defcustom octave-source-directories nil
+  "A list of directories for Octave sources.
+If the environment variable OCTAVE_SRCDIR is set, it is searched first."
+  :type '(repeat directory)
   :group 'octave
   :version "24.4")
 
+(defun octave-source-directories ()
+  (let ((srcdir (or (and inferior-octave-process
+                         (process-get inferior-octave-process 'octave-srcdir))
+                    (getenv "OCTAVE_SRCDIR"))))
+    (if srcdir
+        (cons srcdir octave-source-directories)
+      octave-source-directories)))
+
+(defvar octave-find-definition-filename-function
+  #'octave-find-definition-default-filename)
+
+(defun octave-find-definition-default-filename (name)
+  "Default value for `octave-find-definition-filename-function'."
+  (pcase (file-name-extension name)
+    (`"oct"
+     (octave-find-definition-default-filename
+      (concat "libinterp/dldfcn/"
+              (file-name-sans-extension (file-name-nondirectory name))
+              ".cc")))
+    (`"cc"
+     (let ((file (or (locate-file name (octave-source-directories))
+                     (locate-file (file-name-nondirectory name)
+                                  (octave-source-directories)))))
+       (or (and file (file-exists-p file))
+           (error "File `%s' not found" name))
+       file))
+    (`"mex"
+     (if (yes-or-no-p (format "File `%s' may be binary; open? "
+                              (file-name-nondirectory name)))
+         name
+       (user-error "Aborted")))
+    (t name)))
+
 (defvar find-tag-marker-ring)
 
 (defun octave-find-definition (fn)
-  "Find the definition of FN."
+  "Find the definition of FN.
+Functions implemented in C++ can be found if
+`octave-source-directories' is set correctly."
   (interactive (list (octave-completing-read)))
   (inferior-octave-send-list-and-digest
    ;; help NAME is more verbose
@@ -1533,15 +1730,12 @@ if iskeyword(\"%s\") disp(\"`%s' is a keyword\") else which(\"%s\") endif\n"
                  (match-string 1 line))))
     (if (not file)
         (user-error "%s" (or line (format "`%s' not found" fn)))
-      (when (and (member (file-name-extension file)
-                         octave-binary-file-extensions)
-                 (not (yes-or-no-p (format "File `%s' may be binary; open? "
-                                           (file-name-nondirectory file)))))
-        (error "Aborted"))
       (require 'etags)
       (ring-insert find-tag-marker-ring (point-marker))
-      (find-file file)
-      (octave-goto-function-definition))))
+      (setq file (funcall octave-find-definition-filename-function file))
+      (when file
+        (find-file file)
+        (octave-goto-function-definition fn)))))
 
 
 (provide 'octave)