X-Git-Url: https://code.delx.au/gnu-emacs/blobdiff_plain/a9519e2685d19b13ce4e3e1ba13f97569013627e..f236dd8432cad5f39088e4b99a44478e21930e4d:/lisp/progmodes/octave.el diff --git a/lisp/progmodes/octave.el b/lisp/progmodes/octave.el index ab2f570ccc..bacd37b3d3 100644 --- a/lisp/progmodes/octave.el +++ b/lisp/progmodes/octave.el @@ -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,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" @@ -131,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))) @@ -426,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)." @@ -434,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))))) @@ -539,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)) @@ -688,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 @@ -706,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 @@ -718,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)) @@ -737,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 @@ -748,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 @@ -777,8 +795,8 @@ startup file, `~/.emacs-octave'." (inferior-octave-send-list-and-digest (list (concat "completion_matches (\"" command "\");\n"))) (setq cache (list command (float-time) - (sort (delete-dups inferior-octave-output-list) - 'string-lessp)))) + (delete-consecutive-dups + (sort inferior-octave-output-list 'string-lessp))))) (car (cddr cache)))))) (defun inferior-octave-completion-at-point () @@ -867,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. @@ -938,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 - "\\_ arg 0) (/= orig (point))) (setq arg (1- arg))) (forward-sexp (- arg)) + (and (< arg 0) (forward-sexp -1)) (/= orig (point)))) - -;;; 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: @@ -1346,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))) - ;;; Completions (defun octave-completion-at-point () @@ -1579,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 @@ -1597,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 @@ -1611,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))) @@ -1667,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