]> code.delx.au - gnu-emacs/blobdiff - lisp/progmodes/octave.el
* lisp/emacs-lisp/smie.el (smie-highlight-matching-block): Always turn
[gnu-emacs] / lisp / progmodes / octave.el
index 451ac2791a7a0562d217540950a404bca4214d8e..bacd37b3d3cc22304c33a57672a29fd7c84c0fe8 100644 (file)
@@ -62,12 +62,11 @@ 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
   '("classdef" "do" "enumeration" "events" "for" "function" "if" "methods"
@@ -133,38 +132,49 @@ parenthetical grouping.")
 (easy-menu-define octave-mode-menu octave-mode-map
   "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])
-    ("Blocks"
-     ["Mark Block"             octave-mark-block t]
-     ["Close Block"            smie-close-block t])
-    ("Functions"
-     ["Insert Function"        octave-insert-defun t]
-     ["Update function file comment" octave-update-function-file-comment 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 Line"                octave-beginning-of-line t]
+    ["End of Line"                  octave-end-of-line t]
+    ["Mark Block"                   octave-mark-block t]
+    ["Close Block"                  smie-close-block t]
+    "---"
+    ["Start Octave Process"         run-octave t]
+    ["Documentation Lookup"         info-lookup-symbol t]
+    ["Help on Function"             octave-help t]
+    ["Find Function Definition"     octave-find-definition t]
+    ["Insert Function"              octave-insert-defun t]
+    ["Update Function File Comment" octave-update-function-file-comment t]
+    "---"
+    ["Function Syntax Hints" (call-interactively
+                              (if (fboundp 'eldoc-post-insert-mode)
+                                  'eldoc-post-insert-mode
+                                'eldoc-mode))
+     :style toggle :selected (or eldoc-post-insert-mode eldoc-mode)
+     :help "Display function signatures after typing `SPC' or `('"]
+    ["Delimiter Matching"           smie-highlight-matching-block-mode
+     :style toggle :selected smie-highlight-matching-block-mode
+     :help "Highlight matched pairs such as `if ... end'"
+     :visible (fboundp 'smie-highlight-matching-block-mode)]
+    ["Auto Fill"                    auto-fill-mode
+     :style toggle :selected auto-fill-function
+     :help "Automatic line breaking"]
+    ["Electric Layout"              electric-layout-mode
+     :style toggle :selected electric-layout-mode
+     :help "Automatically insert newlines around some chars"]
+    "---"
     ("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])
-    "-"
-    ["Indent Line"             indent-according-to-mode t]
-    ["Complete Symbol"         completion-at-point t]
-    ["Toggle Auto-Fill Mode"   auto-fill-mode
-     :style toggle :selected auto-fill-function]
-    "-"
-    ["Describe Octave Mode"    describe-mode t]
-    ["Lookup Octave Index"     info-lookup-symbol t]
-    ["Customize Octave" (customize-group 'octave) t]
-    "-"
-    ["Submit Bug Report"       report-emacs-bug 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])
+    "---"
+    ["Customize Octave"         (customize-group 'octave) t]
+    ["Submit Bug Report"        report-emacs-bug t]))
 
 (defvar octave-mode-syntax-table
   (let ((table (make-syntax-table)))
@@ -428,7 +438,7 @@ Non-nil means always go to the next Octave code line after sending."
          (smie-rule-parent octave-block-offset)
        ;; For (invalid) code between switch and case.
        ;; (if (smie-parent-p "switch") 4)
-       0))))
+       nil))))
 
 (defun octave-indent-comment ()
   "A function for `smie-indent-functions' (which see)."
@@ -436,11 +446,11 @@ Non-nil means always go to the next Octave code line after sending."
     (back-to-indentation)
     (cond
      ((octave-in-string-or-comment-p) nil)
-     ((looking-at-p "\\s<\\{3,\\}")
+     ((looking-at-p "\\(\\s<\\)\\1\\{2,\\}")
       0)
      ;; Exclude %{, %} and %!.
      ((and (looking-at-p "\\s<\\(?:[^{}!]\\|$\\)")
-           (not (looking-at-p "\\s<\\s<")))
+           (not (looking-at-p "\\(\\s<\\)\\1")))
       (comment-choose-indent)))))
 
 \f
@@ -541,11 +551,11 @@ definitions can also be stored in files and used in batch mode."
   (setq-local paragraph-separate paragraph-start)
   (setq-local paragraph-ignore-fill-prefix t)
   (setq-local fill-paragraph-function 'octave-fill-paragraph)
-  ;; FIXME: Why disable it?
-  ;; (setq-local adaptive-fill-regexp nil)
-  ;; Again, this is not a property of the language, don't set it here.
-  ;; (setq fill-column 72)
-  (setq-local normal-auto-fill-function 'octave-auto-fill)
+
+  (setq-local fill-nobreak-predicate
+              (lambda () (eq (octave-in-string-p) ?')))
+  (add-function :around (local 'comment-line-break-function)
+                #'octave--indent-new-comment-line)
 
   (setq font-lock-defaults '(octave-font-lock-keywords))
 
@@ -690,12 +700,12 @@ the file specified by `inferior-octave-startup-file' or by the default
 startup file, `~/.emacs-octave'."
   (interactive "P")
   (let ((buffer (get-buffer-create inferior-octave-buffer)))
+    (unless arg
+      (pop-to-buffer buffer))
     (unless (comint-check-proc buffer)
       (with-current-buffer buffer
         (inferior-octave-startup)
         (inferior-octave-mode)))
-    (unless arg
-      (pop-to-buffer buffer))
     buffer))
 
 ;;;###autoload
@@ -708,6 +718,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
@@ -720,6 +735,8 @@ startup file, `~/.emacs-octave'."
     ;; output may be mixed up).  Hence, we need to digest the Octave
     ;; output to see when it issues a prompt.
     (while inferior-octave-receive-in-progress
+      (or (process-live-p inferior-octave-process)
+          (error "Process `%s' died" inferior-octave-process))
       (accept-process-output inferior-octave-process))
     (goto-char (point-max))
     (set-marker (process-mark proc) (point))
@@ -739,8 +756,10 @@ startup file, `~/.emacs-octave'."
       (inferior-octave-send-list-and-digest (list "PS2 (\"> \");\n")))
 
     (inferior-octave-send-list-and-digest
-     (list "if exist(\"__octave_srcdir__\") disp(__octave_srcdir__) endif\n"))
-    (process-put proc 'octave-srcdir (car inferior-octave-output-list))
+     (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
@@ -750,20 +769,17 @@ 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))))
-    (insert-before-markers
-     (concat
-      (if inferior-octave-output-list
-          (concat (mapconcat
-                   'identity inferior-octave-output-list "\n")
-                  "\n"))
-      inferior-octave-output-string))
+    (when inferior-octave-output-list
+      (insert-before-markers
+       (mapconcat 'identity inferior-octave-output-list "\n")))
 
     ;; And finally, everything is back to normal.
     (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)
-    ;; A trick to get the prompt highlighted.
+    ;; Generate a proper prompt, which is critical to
+    ;; `comint-history-isearch-backward-regexp'.  Bug#14433.
     (comint-send-string proc "\n")))
 
 (defvar inferior-octave-completion-table
@@ -869,15 +885,25 @@ output is passed to the filter `inferior-octave-output-digest'."
          (setq list (cdr list)))
       (set-process-filter proc filter))))
 
+(defvar inferior-octave-directory-tracker-resync nil)
+(make-variable-buffer-local 'inferior-octave-directory-tracker-resync)
+
 (defun inferior-octave-directory-tracker (string)
   "Tracks `cd' commands issued to the inferior Octave process.
 Use \\[inferior-octave-resync-dirs] to resync if Emacs gets confused."
+  (when inferior-octave-directory-tracker-resync
+    (setq inferior-octave-directory-tracker-resync nil)
+    (inferior-octave-resync-dirs))
   (cond
    ((string-match "^[ \t]*cd[ \t;]*$" string)
     (cd "~"))
    ((string-match "^[ \t]*cd[ \t]+\\([^ \t\n;]*\\)[ \t\n;]*" string)
-    (with-demoted-errors             ; in case directory doesn't exist
-      (cd (substring string (match-beginning 1) (match-end 1)))))))
+    (condition-case err
+        (cd (match-string 1 string))
+      (error (setq inferior-octave-directory-tracker-resync t)
+             (message "%s: `%s'"
+                      (error-message-string err)
+                      (match-string 1 string)))))))
 
 (defun inferior-octave-resync-dirs ()
   "Resync the buffer's idea of the current directory.
@@ -940,7 +966,7 @@ directory and makes this the current buffer's default directory."
              (or done (goto-char (point-min)))))))
     (pcase (file-name-extension (buffer-file-name))
       (`"cc" (funcall search
-                      "\\_<DEFUN\\s-*(\\s-*\\(\\(?:\\sw\\|\\s_\\)+\\)" 1))
+                      "\\_<DEFUN\\(?:_DLD\\)?\\s-*(\\s-*\\(\\(?:\\sw\\|\\s_\\)+\\)" 1))
       (t (funcall search octave-function-header-regexp 3)))))
 
 (defun octave-function-file-p ()
@@ -1094,21 +1120,30 @@ q: Don't fix\n" func file))
 \f
 ;;; Indentation
 
-(defun octave-indent-new-comment-line ()
+(defun octave-indent-new-comment-line (&optional soft)
+  ;; FIXME: C-M-j should probably be bound globally to a function like
+  ;; this one.
   "Break Octave line at point, continuing comment if within one.
-If within code, insert `octave-continuation-string' before breaking the
-line.  If within a string, signal an error.
-The new line is properly indented."
+Insert `octave-continuation-string' before breaking the line
+unless inside a list.  Signal an error if within a single-quoted
+string."
   (interactive)
-  (delete-horizontal-space)
+  (funcall comment-line-break-function soft))
+
+(defun octave--indent-new-comment-line (orig &rest args)
   (cond
-   ((octave-in-comment-p)
-    (indent-new-comment-line))
-   ((octave-in-string-p)
-    (error "Cannot split a code line inside a string"))
+   ((octave-in-comment-p) nil)
+   ((eq (octave-in-string-p) ?')
+    (error "Cannot split a single-quoted string"))
+   ((eq (octave-in-string-p) ?\")
+    (insert octave-continuation-string))
    (t
-    (insert (concat " " octave-continuation-string))
-    (reindent-then-newline-and-indent))))
+    (delete-horizontal-space)
+    (unless (and (cadr (syntax-ppss))
+                 (eq (char-after (cadr (syntax-ppss))) ?\())
+      (insert " " octave-continuation-string))))
+  (apply orig args)
+  (indent-according-to-mode))
 
 (define-obsolete-function-alias
   'octave-indent-defun 'prog-indent-sexp "24.4")
@@ -1218,71 +1253,9 @@ The block marked is the one that contains point or follows point."
     (when (and (> arg 0) (/= orig (point)))
       (setq arg (1- arg)))
     (forward-sexp (- arg))
+    (and (< arg 0) (forward-sexp -1))
     (/= orig (point))))
 
-\f
-;;; Filling
-(defun octave-auto-fill ()
-  "Perform auto-fill in Octave mode.
-Returns nil if no feasible place to break the line could be found, and t
-otherwise."
-  (let (fc give-up)
-    (if (or (null (setq fc (current-fill-column)))
-           (save-excursion
-             (beginning-of-line)
-             (and auto-fill-inhibit-regexp
-                  (octave-looking-at-kw auto-fill-inhibit-regexp))))
-       nil                             ; Can't do anything
-      (if (and (not (octave-in-comment-p))
-              (> (current-column) fc))
-         (setq fc (- fc (+ (length octave-continuation-string) 1))))
-      (while (and (not give-up) (> (current-column) fc))
-       (let* ((opoint (point))
-              (fpoint
-               (save-excursion
-                 (move-to-column (+ fc 1))
-                 (skip-chars-backward "^ \t\n")
-                 ;; If we're at the beginning of the line, break after
-                 ;; the first word
-                 (if (bolp)
-                     (re-search-forward "[ \t]" opoint t))
-                 ;; If we're in a comment line, don't break after the
-                 ;; comment chars
-                 (if (save-excursion
-                       (skip-syntax-backward " <")
-                       (bolp))
-                     (re-search-forward "[ \t]" (line-end-position)
-                                        'move))
-                 ;; If we're not in a comment line and just ahead the
-                 ;; continuation string, don't break here.
-                 (if (and (not (octave-in-comment-p))
-                          (looking-at
-                           (concat "\\s-*"
-                                   (regexp-quote
-                                    octave-continuation-string)
-                                   "\\s-*$")))
-                     (end-of-line))
-                 (skip-chars-backward " \t")
-                 (point))))
-         (if (save-excursion
-               (goto-char fpoint)
-               (not (or (bolp) (eolp))))
-             (let ((prev-column (current-column)))
-               (if (save-excursion
-                     (skip-chars-backward " \t")
-                     (= (point) fpoint))
-                   (progn
-                     (octave-maybe-insert-continuation-string)
-                     (indent-new-comment-line t))
-                 (save-excursion
-                   (goto-char fpoint)
-                   (octave-maybe-insert-continuation-string)
-                   (indent-new-comment-line t)))
-               (if (>= (current-column) prev-column)
-                   (setq give-up t)))
-           (setq give-up t))))
-      (not give-up))))
-
 (defun octave-fill-paragraph (&optional _arg)
   "Fill paragraph of Octave code, handling Octave comments."
   ;; FIXME: difference with generic fill-paragraph:
@@ -1348,11 +1321,10 @@ otherwise."
                 (and (= (current-column) cfc) (eolp)))
             (forward-line 1)
           (if (not (eolp)) (insert " "))
-          (or (octave-auto-fill)
+          (or (funcall normal-auto-fill-function)
               (forward-line 1))))
       t)))
 
-\f
 ;;; Completions
 
 (defun octave-completion-at-point ()
@@ -1581,14 +1553,54 @@ if ismember(exist(\"%s\"), [2 3 5 103]) print_usage(\"%s\") endif\n"
             (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
@@ -1599,7 +1611,6 @@ if ismember(exist(\"%s\"), [2 3 5 103]) print_usage(\"%s\") endif\n"
         (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
@@ -1613,29 +1624,40 @@ if ismember(exist(\"%s\"), [2 3 5 103]) print_usage(\"%s\") endif\n"
         (when (re-search-forward "from the file \\(.*\\)$"
                                  (line-end-position)
                                  t)
-          (let ((file (match-string 1)))
+          (let* ((file (match-string 1))
+                 (dir (file-name-directory
+                       (directory-file-name (file-name-directory file)))))
             (replace-match "" nil nil nil 1)
             (insert "`")
-            (help-insert-xref-button (file-name-nondirectory file)
+            ;; Include the parent directory which may be regarded as
+            ;; the category for the FN.
+            (help-insert-xref-button (file-relative-name file dir)
                                      'octave-help-file fn)
             (insert "'")))
         ;; Make 'See also' clickable
         (with-syntax-table octave-mode-syntax-table
           (when (re-search-forward "^\\s-*See also:" nil t)
-            (while (re-search-forward "\\_<\\(?:\\sw\\|\\s_\\)+\\_>" nil t)
-              (make-text-button (match-beginning 0)
-                                (match-end 0)
-                                :type 'octave-help-function))))))))
+            (let ((end (save-excursion (re-search-forward "^\\s-*$" nil t))))
+              (while (re-search-forward "\\_<\\(?:\\sw\\|\\s_\\)+\\_>" end t)
+                (make-text-button (match-beginning 0)
+                                  ;; If the match ends with . exclude it.
+                                  (if (eq (char-before (match-end 0)) ?.)
+                                      (1- (match-end 0))
+                                    (match-end 0))
+                                  :type 'octave-help-function)))))
+        (octave-help-mode)))))
 
 (defcustom octave-source-directories nil
-  "A list of directories for Octave sources."
+  "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 ()
-  (inferior-octave-check-process)
-  (let ((srcdir (process-get inferior-octave-process 'octave-srcdir)))
+  (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)))
@@ -1669,7 +1691,7 @@ if ismember(exist(\"%s\"), [2 3 5 103]) print_usage(\"%s\") endif\n"
 
 (defun octave-find-definition (fn)
   "Find the definition of FN.
-Definitions for functions implemented in C++ can be found if
+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