;; ess-rd.el --- Support for editing R documentation (Rd) source -*- lexical-binding: t; -*-
|
|
|
|
;; Copyright (C) 1997--2005 A.J. Rossini, Richard M. Heiberger, Martin
|
|
;; Maechler, Kurt Hornik, Rodney Sparapani, and Stephen Eglen.
|
|
|
|
;; Author: KH <Kurt.Hornik@ci.tuwien.ac.at>
|
|
;; Created: 25 July 1997
|
|
;; Maintainer: ESS-core <ESS-core@r-project.org>
|
|
|
|
;; This file is part of ESS (Emacs Speaks Statistics).
|
|
|
|
;; This file is free software; you may redistribute it and/or modify it
|
|
;; under the terms of the GNU General Public License as published by the
|
|
;; Free Software Foundation; either version 2, or (at your option) any
|
|
;; later version.
|
|
;;
|
|
;; This is distributed in the hope that it will be useful, but WITHOUT
|
|
;; ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
;; FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
;; for more details.
|
|
;;
|
|
;; A copy of the GNU General Public License is available on the World
|
|
;; Wide Web at https://www.gnu.org/copyleft/gpl.html. You can also
|
|
;; obtain it by writing to the Free Software Foundation, Inc., 675 Mass
|
|
;; Ave, Cambridge, MA 02139, USA.
|
|
|
|
|
|
;;; Commentary:
|
|
;;
|
|
|
|
;;; Code:
|
|
(eval-when-compile
|
|
(require 'subr-x))
|
|
|
|
(require 'ess-help)
|
|
(require 'ess-inf)
|
|
;; Silence the byte compiler, see TODO below; can we remove these?
|
|
(defvar ess-help-r-sec-regex)
|
|
(defvar ess-help-r-sec-keys-alist)
|
|
(defvar ess-r-customize-alist)
|
|
|
|
(defcustom Rd-mode-hook nil
|
|
"Hook to be run when Rd mode is entered."
|
|
:type 'hook
|
|
:group 'ess-R
|
|
:group 'ess-hooks)
|
|
|
|
(define-abbrev-table 'Rd-mode-skeleton-abbrev-table
|
|
'(("`ag" "\\arguments" nil :system t)
|
|
("`al" "\\alias" nil :system t)
|
|
("`au" "\\author" nil :system t)
|
|
("`bf" "\\bold" nil :system t)
|
|
("`co" "\\code" nil :system t)
|
|
("`de" "\\describe" nil :system t)
|
|
("`dn" "\\description" nil :system t)
|
|
("`dt" "\\details" nil :system t)
|
|
("`em" "\\emph" nil :system t)
|
|
("`en" "\\enumerate" nil :system t)
|
|
("`ex" "\\examples" nil :system t)
|
|
("`fi" "\\file" nil :system t)
|
|
("`fo" "\\format" nil :system t)
|
|
("`it" "\\item" nil :system t)
|
|
("`iz" "\\itemize" nil :system t)
|
|
("`kw" "\\keyword" nil :system t)
|
|
("`li" "\\link" nil :system t)
|
|
("`me" "\\method" nil :system t)
|
|
("`na" "\\name" nil :system t)
|
|
("`no" "\\note" nil :system t)
|
|
("`re" "\\references" nil :system t)
|
|
("`sa" "\\seealso" nil :system t)
|
|
("`se" "\\section" nil :system t)
|
|
("`so" "\\source" nil :system t)
|
|
("`ss" "\\subsection" nil :system t)
|
|
("`sy" "\\synopsis" nil :system t)
|
|
("`ta" "\\tabular" nil :system t)
|
|
("`ti" "\\title" nil :system t)
|
|
("`us" "\\usage" nil :system t)
|
|
("`va" "\\value" nil :system t))
|
|
"Abbrev table for R documentation keywords.
|
|
All Rd mode abbrevs start with a grave accent (`)."
|
|
:case-fixed t)
|
|
|
|
(define-abbrev-table 'Rd-mode-abbrev-table ()
|
|
"Abbrev table for Rd mode."
|
|
:parents (list Rd-mode-skeleton-abbrev-table))
|
|
|
|
(defvar Rd-mode-syntax-table
|
|
(let ((tab (copy-syntax-table text-mode-syntax-table)))
|
|
(modify-syntax-entry ?\\ "\\" tab)
|
|
(modify-syntax-entry ?\{ "(}" tab)
|
|
(modify-syntax-entry ?\} "){" tab)
|
|
;; Nice for editing, not for parsing ...
|
|
(modify-syntax-entry ?\( "()" tab)
|
|
(modify-syntax-entry ?\) ")(" tab)
|
|
(modify-syntax-entry ?\[ "(]" tab)
|
|
(modify-syntax-entry ?\] ")[" tab)
|
|
;; To get strings right
|
|
;; (modify-syntax-entry ?\' "\"" Rd-mode-syntax-table)
|
|
(modify-syntax-entry ?\" "\"" tab)
|
|
;; To make abbrevs starting with a grave accent work ...
|
|
(modify-syntax-entry ?\` "w" tab)
|
|
;; Comments
|
|
(modify-syntax-entry ?\% "<" tab)
|
|
(modify-syntax-entry ?\n ">" tab)
|
|
tab)
|
|
"Syntax table for `Rd-mode'.")
|
|
|
|
(defvar Rd-mode-parse-syntax-table
|
|
(let ((tab (copy-syntax-table Rd-mode-syntax-table)))
|
|
;; To make parse-partial-sexps do the thing we want for computing
|
|
;; indentations
|
|
(modify-syntax-entry ?\( "_" tab)
|
|
(modify-syntax-entry ?\) "_" tab)
|
|
(modify-syntax-entry ?\[ "_" tab)
|
|
(modify-syntax-entry ?\] "_" tab)
|
|
tab)
|
|
"Syntax table for parsing Rd mode.")
|
|
|
|
(defvar Rd-section-names
|
|
'("Rdversion" "arguments" "alias" "author" "concept" "describe" "description"
|
|
"details" "docType" "encoding" "enumerate" "examples" "format"
|
|
"itemize" "keyword" "name" "note" "preformatted" "references"
|
|
"seealso" "section" "source" "subsection" "synopsis"
|
|
"tabular" "title" "usage"
|
|
"value"))
|
|
|
|
(defvar Rd-keywords
|
|
'(
|
|
;; the next two lines: only valid in R <= 2.8.1
|
|
;; commented out on 2011-01-14 for ESS version 5.13:
|
|
;; "Alpha" "Gamma" "alpha" "beta" "epsilon" "lambda" "mu" "pi" "sigma"
|
|
;; "ge" "le" "left" "right"
|
|
;;
|
|
"R" "RdOpts" "S3method" "S4method" "Sexpr" "acronym"
|
|
"bold" "cite" "code" "command" "cr" "dQuote" "deqn" "dfn" "dontrun"
|
|
"dontshow" "donttest" "dots" "email" "emph" "enc" "env" "eqn" "figure" "file"
|
|
"href" "if" "ifelse"
|
|
"item" "kbd" "ldots" "linkS4class" "link" "method"
|
|
"newcommand" "option" "out"
|
|
"pkg" "sQuote" "renewcommand"
|
|
"samp" "strong" "tab" "url" "var" "verb"
|
|
;; System macros (from <R>/share/Rd/macros/system.Rd ):
|
|
"CRANpkg" "PR" "sspace" "doi"
|
|
"packageTitle" "packageDescription" "packageAuthor"
|
|
"packageMaintainer" "packageDESCRIPTION" "packageIndices"
|
|
))
|
|
|
|
(defvar Rd-font-lock-keywords
|
|
(list
|
|
(cons
|
|
(concat "\\\\\\("
|
|
(mapconcat 'identity Rd-section-names "\\|")
|
|
"\\>\\)")
|
|
'font-lock-reference-face) ; Rd-bold-face
|
|
(cons
|
|
(concat "\\\\\\("
|
|
(mapconcat 'identity Rd-keywords "\\|")
|
|
"\\>\\)")
|
|
'font-lock-keyword-face)
|
|
'("^#\\(ifn?def\\)\\s-+\\(\\sw+\\)"
|
|
(1 font-lock-builtin-face)
|
|
(2 font-lock-variable-name-face nil t))
|
|
'("^#\\(endif\\)" 1 font-lock-builtin-face))
|
|
"Additional Rd expressions to highlight.")
|
|
|
|
(defvar Rd-indent-level 2
|
|
"Indentation of Rd code with respect to containing blocks.")
|
|
|
|
(defvar Rd-mode-map
|
|
(let ((map (make-sparse-keymap)))
|
|
(define-key map "\t" #'indent-according-to-mode)
|
|
(define-key map "\C-j" #'reindent-then-newline-and-indent)
|
|
(define-key map "\C-m" #'reindent-then-newline-and-indent)
|
|
(define-key map "\C-c\C-p" #'Rd-preview-help)
|
|
(define-key map "\C-c\C-j" #'Rd-mode-insert-item)
|
|
(define-key map "\C-c\C-e" #'Rd-mode-insert-skeleton)
|
|
(define-key map "\C-c\C-f" #'Rd-font)
|
|
(define-key map "\C-c\C-s" #'Rd-mode-insert-section)
|
|
(define-key map "\C-c\C-n" #'ess-eval-line-visibly-and-step)
|
|
(define-key map "\C-c\C-r" #'ess-eval-region)
|
|
(define-key map "\C-c\C-c" #'ess-eval-region-or-function-or-paragraph-and-step)
|
|
(define-key map "\C-\M-x" #'ess-eval-region-or-function-or-paragraph)
|
|
(define-key map "\C-c\C-v" #'ess-display-help-on-object)
|
|
(define-key map "\C-c\C-w" #'ess-switch-process)
|
|
(define-key map "\C-c\C-y" #'ess-switch-to-ESS)
|
|
(define-key map "\C-c\C-z" #'ess-switch-to-end-of-ESS)
|
|
map)
|
|
"Keymap used in Rd mode.")
|
|
|
|
(defvar Rd-mode-menu
|
|
(list "Rd"
|
|
["Markup [word]" Rd-font t]
|
|
["Insert Item" Rd-mode-insert-item t]
|
|
["Insert Section" Rd-mode-insert-section t]
|
|
["Insert Skeleton" Rd-mode-insert-skeleton t]
|
|
"-"
|
|
["Preview" Rd-preview-help t]
|
|
"-"
|
|
["Eval Line" ess-eval-line-visibly-and-step t]
|
|
["Eval Region" ess-eval-region t]
|
|
["Switch to ESS Process" ess-switch-to-ESS t]
|
|
["Switch the ESS Process" ess-switch-process t]
|
|
["Switch to end{ESS Pr}" ess-switch-to-end-of-ESS t]
|
|
"-"
|
|
["Toggle Abbrev Mode" abbrev-mode t]
|
|
["Toggle Auto-Fill Mode" auto-fill-mode t]
|
|
"-"
|
|
["Submit Bug Report" ess-submit-bug-report t]
|
|
"-"
|
|
["Describe Rd Mode" describe-mode t])
|
|
"Menu used in Rd mode.")
|
|
|
|
(defvar Rd-to-help-command "R CMD Rd2txt"
|
|
"Shell command for converting R documentation source to help text.")
|
|
|
|
(defvar Rd-font-list
|
|
'((?\C-b "\\bold{" "}")
|
|
(?\C-c "\\code{" "}")
|
|
(?\C-e "\\emph{" "}")
|
|
(?\C-l "\\link{" "}")
|
|
(?l "\\code{\\link{" "}}")
|
|
(?\C-m "\\email{" "}")
|
|
(?\C-q "\\eqn{" "}")
|
|
(?\C-u "\\url{" "}")
|
|
)
|
|
"List of \"fonts\" used by `Rd-font'.
|
|
Each entry is a list. The first element is the key to activate
|
|
the font. The second element is the string to insert before
|
|
point, and the third element is the string to insert after
|
|
point.")
|
|
|
|
|
|
;;;###autoload
|
|
(define-derived-mode Rd-mode text-mode "Rd"
|
|
"Major mode for editing R documentation source files.
|
|
|
|
Type \\[list-abbrevs] to display the built-in abbrevs for Rd
|
|
keywords.To automatically turn on the abbrev(iate) features, add
|
|
the following to your Emacs configuration file:
|
|
|
|
(add-hook 'Rd-mode-hook #'abbrev-mode)"
|
|
(setq ess-language "S" ess-dialect "R")
|
|
(require 'ess-r-mode)
|
|
(ess-setq-vars-local ess-r-customize-alist)
|
|
|
|
(setq-local indent-line-function 'Rd-mode-indent-line)
|
|
(setq fill-column 72)
|
|
(setq-local comment-start-skip "\\s<+\\s-*")
|
|
(setq-local comment-start "% ")
|
|
(setq-local comment-end "")
|
|
(setq font-lock-defaults
|
|
'(Rd-font-lock-keywords nil nil))
|
|
|
|
;; Here is a workaround for an Emacs bug related to indirect buffers and
|
|
;; spurious lockfiles that rears its ugly head with .Rd files
|
|
;; https://lists.gnu.org/archive/html/bug-gnu-emacs/2013-02/msg01368.html
|
|
;; https://debbugs.gnu.org/cgi/bugreport.cgi?bug=14328
|
|
(setq-local create-lockfiles nil)
|
|
|
|
(easy-menu-define Rd-mode-menu-map Rd-mode-map
|
|
"Menu keymap for Rd mode." Rd-mode-menu)
|
|
|
|
(turn-on-auto-fill))
|
|
|
|
;;;###autoload
|
|
(add-to-list 'auto-mode-alist '("\\.Rd\\'" . Rd-mode))
|
|
|
|
(defun Rd-describe-major-mode ()
|
|
"Describe the current major mode."
|
|
(declare (obsolete describe-mode "ESS 19.04"))
|
|
(describe-function major-mode))
|
|
|
|
(defun Rd-mode-in-verbatim-p ()
|
|
"Return non-nil if in a usage, examples, or synopsis."
|
|
(let ((pos (point)))
|
|
(save-excursion
|
|
(if (and (re-search-backward
|
|
"\\\\\\(usage\\|examples\\|synopsis\\)" nil t)
|
|
(re-search-forward "\\s(" nil t))
|
|
(condition-case ()
|
|
(progn
|
|
(up-list 1)
|
|
(< pos (point)))
|
|
(error t))
|
|
nil))))
|
|
|
|
(defun Rd-mode-in-preprocessor-line-p ()
|
|
"Return non-nil if in a preprocessor line."
|
|
(save-excursion
|
|
(beginning-of-line)
|
|
(looking-at "[ \t]*#\\(ifdef\\|endif\\)")))
|
|
|
|
(defun Rd-mode-calculate-indent ()
|
|
"Return appropriate indentation for current line in Rd mode."
|
|
(save-excursion
|
|
(beginning-of-line)
|
|
(cond
|
|
((Rd-mode-in-verbatim-p)
|
|
;; Don't do anything in verbatims
|
|
nil)
|
|
((Rd-mode-in-preprocessor-line-p)
|
|
;; Indent to 0
|
|
0)
|
|
(t
|
|
(let ((p (progn
|
|
(re-search-forward "[ \t]*\\s)*" (point-at-eol) t)
|
|
(point))))
|
|
(if (or (< (forward-line -1) 0)
|
|
(Rd-mode-in-verbatim-p))
|
|
0
|
|
(set-syntax-table Rd-mode-parse-syntax-table)
|
|
(while (and (or (looking-at "[ \t]*$")
|
|
(Rd-mode-in-preprocessor-line-p))
|
|
(not (bobp)))
|
|
(forward-line -1))
|
|
(re-search-forward "[ \t]*\\s)*" (point-at-eol) t)
|
|
(prog1
|
|
(+ (current-indentation)
|
|
(* (car (parse-partial-sexp (point) p))
|
|
Rd-indent-level))
|
|
(set-syntax-table Rd-mode-syntax-table))))))))
|
|
|
|
(defun Rd-mode-indent-line ()
|
|
"Indent current line as Rd source."
|
|
(when-let ((ic (Rd-mode-calculate-indent))
|
|
(rp (- (current-column) (current-indentation))))
|
|
(when (< ic 0)
|
|
(error "Unmatched parenthesis"))
|
|
(indent-line-to ic)
|
|
(when (> rp 0)
|
|
(move-to-column (+ ic rp)))))
|
|
|
|
(defun Rd-mode-insert-item ()
|
|
"Insert \\item{ on a newline."
|
|
(interactive)
|
|
(reindent-then-newline-and-indent)
|
|
(insert "\\item{")
|
|
)
|
|
|
|
(defun Rd-mode-insert-section ()
|
|
"Insert a section from `Rd-section-names'."
|
|
(interactive)
|
|
(let ((s (ess-completing-read
|
|
"Insert section: "
|
|
(mapcar (lambda (x) (cons x x)) Rd-section-names)
|
|
nil t)))
|
|
(if (string= s "")
|
|
(progn (insert "\\section{}{") (backward-char 2))
|
|
(insert (format "\\%s{" s)))))
|
|
|
|
(defun Rd-mode-insert-skeleton ()
|
|
"Insert several empty Rd fields."
|
|
(interactive)
|
|
;; Hmm: in theory this should be kept in sync with prompt()
|
|
;; --- maybe using prompt() [or promptClass()...] would be better anyway?!
|
|
(insert "\\name{}\n")
|
|
(insert "\\alias{}\n")
|
|
(insert "\\title{}\n")
|
|
(insert "\\description{\n}\n")
|
|
(insert "\\usage{\n}\n")
|
|
(insert "\\arguments{\n}\n")
|
|
(insert "\\value{\n}\n")
|
|
(insert "\\details{\n}\n")
|
|
(insert "\\references{\n}\n")
|
|
(insert "\\seealso{\n}\n")
|
|
(insert "\\examples{\n}\n")
|
|
(insert "\\author{}\n")
|
|
(insert "\\keyword{}\n"))
|
|
|
|
;; This is an `easy' version of (defun TeX-font ..) in AUCtex's tex.el ;
|
|
;; see TeX-font-list and also LaTeX-font-list in latex.el
|
|
|
|
(defun Rd-font (what)
|
|
"Insert template for font command.
|
|
WHAT determines the font to use, as specified by `Rd-font-list'."
|
|
(interactive "c")
|
|
;;TeX had : (Rd-update-style)
|
|
(let* ((entry (assoc what Rd-font-list))
|
|
(before (nth 1 entry))
|
|
(after (nth 2 entry)))
|
|
(cond ((null entry) ;; help on possibilities :
|
|
(let ((help
|
|
(concat
|
|
"Rd Markup (available from C-c C-f):\n\n\t"
|
|
"KEY Rd-Markup\n\n"
|
|
(mapconcat
|
|
(lambda (entry)
|
|
;; A textual description of an ENTRY in TeX-font-list.
|
|
(concat (format "%11s "
|
|
(key-description
|
|
(char-to-string (nth 0 entry))))
|
|
(format "%14s %-3s"
|
|
(nth 1 entry) (nth 2 entry))))
|
|
Rd-font-list "\n"))))
|
|
(with-output-to-temp-buffer "*Help*"
|
|
(set-buffer "*Help*")
|
|
(insert help))))
|
|
|
|
((region-active-p)
|
|
(save-excursion
|
|
(cond ((> (mark) (point))
|
|
(insert before)
|
|
(goto-char (mark))
|
|
(insert after))
|
|
(t
|
|
(insert after)
|
|
(goto-char (mark))
|
|
(insert before)))))
|
|
(t
|
|
(insert before)
|
|
(save-excursion
|
|
(insert after))))))
|
|
|
|
(defun Rd-preview-help (&optional via-shell)
|
|
"Preview the current Rd buffer contents as help.
|
|
If the current buffer is not associated with a file, create a
|
|
temporary one in variable `temporary-file-directory'."
|
|
(declare (advertised-calling-convention () "ESS 19.04"))
|
|
(interactive "P") ; If optional VIA-SHELL is set, using `Rd-to-help-command'.
|
|
(let ((file buffer-file-name)
|
|
(pbuf (get-buffer-create "R Help Preview"))
|
|
del-p)
|
|
(unless file
|
|
(setq file (make-temp-file "RD_" nil ".Rd"))
|
|
(write-region (point-min) (point-max) file)
|
|
(setq del-p t))
|
|
|
|
(if via-shell ;; FIXME eventually get rid of this option
|
|
;; only method in ESS <= 14.09 -- calls "R" even if in "R-devel"; slower
|
|
(let ((shcmd (format "%s '%s'" Rd-to-help-command file)))
|
|
(set-buffer pbuf)
|
|
(erase-buffer)
|
|
(ess-write-to-dribble-buffer
|
|
(format "Rd-preview-help: (shell-command |%s| t)" shcmd))
|
|
(shell-command shcmd t))
|
|
;; else directly:
|
|
(ess-force-buffer-current "R process to use: ")
|
|
(ess-command (format ".ess_Rd2txt(\"%s\")\n" file) pbuf)
|
|
(set-buffer pbuf))
|
|
|
|
;; FIXME(2): once got rid of via-shell, consider
|
|
;; (ess--flush-help-into-current-buffer file "tools::Rd2txt(\"%s\")\n")
|
|
;; instead of all this :
|
|
(ess-setq-vars-local ess-r-customize-alist)
|
|
;; mostly cut'n'paste from ess--flush-help* (see FIXME(2)):
|
|
(ess-help-underline)
|
|
(ess--help-major-mode)
|
|
;; FIXME: Is this really needed?
|
|
(setq ess-help-sec-regex ess-help-r-sec-regex
|
|
ess-help-sec-keys-alist ess-help-r-sec-keys-alist)
|
|
(goto-char (point-min))
|
|
(set-buffer-modified-p 'nil)
|
|
(setq buffer-read-only t)
|
|
(setq truncate-lines nil)
|
|
(when del-p (delete-file file))
|
|
(unless (get-buffer-window pbuf 'visible)
|
|
(display-buffer pbuf t))))
|
|
|
|
(define-obsolete-function-alias 'Rd-submit-bug-report 'ess-submit-bug-report "2018-08-16")
|
|
|
|
(provide 'ess-rd)
|
|
|
|
;;; ess-rd.el ends here
|