(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)
(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"
\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)
(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"
- ["Indent Function" octave-indent-defun t]
- ["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)))
(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)."
(save-excursion
(back-to-indentation)
- (when (and (not (octave-in-string-or-comment-p))
- (looking-at-p "\\s<\\(?:[^{}]\\|$\\)")
- (not (looking-at-p "\\s<\\s<")))
- (comment-choose-indent))))
+ (cond
+ ((octave-in-string-or-comment-p) nil)
+ ((looking-at-p "\\(\\s<\\)\\1\\{2,\\}")
+ 0)
+ ;; Exclude %{, %} and %!.
+ ((and (looking-at-p "\\s<\\(?:[^{}!]\\|$\\)")
+ (not (looking-at-p "\\(\\s<\\)\\1")))
+ (comment-choose-indent)))))
\f
(defvar octave-font-lock-keywords
(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)
(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)
(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))
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
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
;; 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))
(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
(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
(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 ()
(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.
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\\(?:_DLD\\)?\\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\".
(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
\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))
-(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
(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:
(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 ()
(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
(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
(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)))
(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
(user-error "%s" (or line (format "`%s' not found" fn)))
(require 'etags)
(ring-insert find-tag-marker-ring (point-marker))
- (find-file (funcall octave-find-definition-filename-function file))
- (or (octave-goto-function-definition)
- (forward-comment (point-max))))))
+ (setq file (funcall octave-find-definition-filename-function file))
+ (when file
+ (find-file file)
+ (octave-goto-function-definition fn)))))
(provide 'octave)