Klimi's new dotfiles with stow.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1035 lines
39 KiB

пре 4 година
  1. ;;; ess-roxy.el --- convenient editing of in-code roxygen documentation
  2. ;;
  3. ;; Copyright (C) 2009--2018 Henning Redestig, A.J. Rossini, Richard
  4. ;; M. Heiberger, Martin Maechler, Kurt Hornik, Rodney Sparapani, Stephen
  5. ;; Eglen, Vitalie Spinu, and J. Alexander Branham.
  6. ;;
  7. ;; Author: Henning Redestig <henning.red * go0glemail c-m>
  8. ;; Keywords: convenience, tools
  9. ;;
  10. ;; This file is part of ESS
  11. ;;
  12. ;; This program is free software; you can redistribute it and/or
  13. ;; modify it under the terms of the GNU General Public License as
  14. ;; published by the Free Software Foundation; either version 3 of the
  15. ;; License, or (at your option) any later version.
  16. ;;
  17. ;; This program is distributed in the hope that it will be useful, but
  18. ;; WITHOUT ANY WARRANTY; without even the implied warranty of
  19. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  20. ;; General Public License for more details.
  21. ;;
  22. ;; You should have received a copy of the GNU General Public License
  23. ;; along with this program. If not, see
  24. ;; <https://www.gnu.org/licenses/>.
  25. ;;; Commentary:
  26. ;; Lots of inspiration from doc-mode,
  27. ;; https://nschum.de/src/emacs/doc-mode/
  28. ;;
  29. ;; Features::
  30. ;;
  31. ;; - basic highlighting
  32. ;; - generating and updating templates from function definition and customized default template
  33. ;; - C-c C-o C-o :: update template
  34. ;; - navigating and filling roxygen fields
  35. ;; - C-c TAB, M-q, C-a, ENTER, M-h :: advised tag completion, fill-paragraph,
  36. ;; ess-roxy-move-beginning-of-line, newline-and-indent
  37. ;; - C-c C-o n,p :: next, previous roxygen entry
  38. ;; - C-c C-o C-c :: Unroxygen region. Convenient for editing examples.
  39. ;; - folding visibility using hs-minor-mode
  40. ;; - TAB :: advised ess-indent-command, hide entry if in roxygen doc.
  41. ;; - preview
  42. ;; - C-c C-o C-r :: create a preview of the Rd file as generated
  43. ;; using roxygen
  44. ;; - C-c C-o C-t :: create a preview of the Rd HTML file as generated
  45. ;; using roxygen and the tools package
  46. ;; - C-c C-o t :: create a preview of the Rd text file
  47. ;;
  48. ;; Known issues:
  49. ;;
  50. ;; - hideshow mode does not work very well. In particular, if ordinary
  51. ;; comments precede a roxygen entry, then both will be hidden in the
  52. ;; same overlay from start and not unfoldable using TAB since the
  53. ;; roxygen prefix is not present. The planned solution is implement
  54. ;; a replacement for hideshow.
  55. ;; - only limited functionality for S4 documentation.
  56. ;;; Code:
  57. (require 'ess-utils)
  58. (require 'hideshow)
  59. (require 'outline)
  60. (eval-when-compile
  61. (require 'cl-lib)
  62. (require 'subr-x))
  63. (require 'ess-rd)
  64. (require 'ess-r-syntax)
  65. (defvar roxy-str)
  66. (defvar ess-r-mode-syntax-table)
  67. (declare-function ess-fill-args "ess-r-mode")
  68. (declare-function ess-fill-continuations "ess-r-mode")
  69. (declare-function inferior-ess-r-force "ess-r-mode")
  70. (defvar-local ess-roxy-re nil
  71. "Regular expression to recognize roxygen blocks.")
  72. ;;*;; Roxy Minor Mode
  73. (defvar ess-roxy-mode-map
  74. (let ((map (make-sparse-keymap)))
  75. (define-key map (kbd "C-c C-o h") #'ess-roxy-hide-all)
  76. (define-key map (kbd "C-c C-o n") #'ess-roxy-next-entry)
  77. (define-key map (kbd "C-c C-o p") #'ess-roxy-previous-entry)
  78. (define-key map (kbd "C-c C-o C-o") #'ess-roxy-update-entry)
  79. (define-key map (kbd "C-c C-o C-r") #'ess-roxy-preview-Rd)
  80. (define-key map (kbd "C-c C-o C-w") #'ess-roxy-preview-HTML)
  81. (define-key map (kbd "C-c C-o C-t") #'ess-roxy-preview-text)
  82. (define-key map (kbd "C-c C-o C-c") #'ess-roxy-toggle-roxy-region)
  83. (define-key map [remap back-to-indentation] #'ess-roxy-goto-end-of-roxy-comment)
  84. (define-key map [remap newline] #'ess-roxy-newline-and-indent)
  85. (define-key map [remap newline-and-indent] #'ess-roxy-newline-and-indent)
  86. (define-key map [remap ess-indent-command] #'ess-roxy-ess-indent-command)
  87. (define-key map [remap move-beginning-of-line] #'ess-roxy-move-beginning-of-line)
  88. (define-key map [remap beginning-of-visual-line] #'ess-roxy-move-beginning-of-line)
  89. map))
  90. (defvar ess-roxy-font-lock-keywords nil
  91. "Cache set by `ess-roxy-generate-keywords'.
  92. Used to remove keywords added by function `ess-roxy-mode'.")
  93. (defun ess-roxy-generate-keywords ()
  94. "Generate a list of keywords suitable for `font-lock-add-keywords'."
  95. (setq-local ess-roxy-font-lock-keywords
  96. `((,(concat ess-roxy-re " *\\([@\\]"
  97. (regexp-opt ess-roxy-tags-param t)
  98. "\\)\\>")
  99. (1 'font-lock-keyword-face prepend))
  100. (,(concat ess-roxy-re " *\\(@"
  101. (regexp-opt '("param" "importFrom" "importClassesFrom"
  102. "importMethodsFrom" "describeIn")
  103. 'words)
  104. "\\)\\(?:[ \t]+\\(\\(?:\\sw+,?\\)+\\)\\)")
  105. (1 'font-lock-keyword-face prepend)
  106. (3 'font-lock-variable-name-face prepend))
  107. (,(concat "[@\\]" (regexp-opt ess-roxy-tags-noparam t) "\\>")
  108. (0 'font-lock-variable-name-face prepend))
  109. (,(concat ess-roxy-re)
  110. (0 'bold prepend)))))
  111. (defvar ess-roxy-fold-examples nil
  112. "Whether to fold `@examples' when opening a buffer.
  113. Use you regular key for `outline-show-entry' to reveal it.")
  114. ;;;###autoload
  115. (define-minor-mode ess-roxy-mode
  116. "Minor mode for editing ROxygen documentation."
  117. :keymap ess-roxy-mode-map
  118. (if ess-roxy-mode
  119. ;; Turn on `ess-roxy-mode':
  120. (progn
  121. (setq-local ess-roxy-re (concat "^" (string-trim comment-start) "+'"))
  122. (font-lock-add-keywords nil (ess-roxy-generate-keywords))
  123. (add-hook 'completion-at-point-functions #'ess-roxy-complete-tag nil t)
  124. ;; Hideshow Integration
  125. (when ess-roxy-hide-show-p
  126. (hs-minor-mode 1)
  127. (when ess-roxy-start-hidden-p
  128. (ess-roxy-hide-all)))
  129. ;; Outline Integration
  130. (when ess-roxy-fold-examples
  131. (ess-roxy-hide-all-examples))
  132. ;; Autofill
  133. (setq-local paragraph-start (concat "\\(" ess-roxy-re "\\)*" paragraph-start))
  134. (setq-local paragraph-separate (concat "\\(" ess-roxy-re "\\)*" paragraph-separate))
  135. (setq-local adaptive-fill-function 'ess-roxy-adaptive-fill-function)
  136. ;; Hooks
  137. (add-hook 'ess-presend-filter-functions 'ess-roxy-remove-roxy-re nil t))
  138. ;; Turn off `ess-roxy-mode':
  139. ;; Hideshow
  140. (when (and ess-roxy-hide-show-p hs-minor-mode)
  141. (hs-show-all)
  142. (hs-minor-mode))
  143. ;; Hooks
  144. (remove-hook 'ess-presend-filter-functions 'ess-roxy-remove-roxy-re t)
  145. (font-lock-remove-keywords nil ess-roxy-font-lock-keywords)
  146. ;; (setq-local syntax-propertize-function nil)
  147. ;; (setq-local font-lock-fontify-region-function nil)
  148. ;; (setq-local font-lock-unfontify-region-function nil)
  149. )
  150. ;; Regardless of turning on or off we need to re-fontify the buffer:
  151. (when font-lock-mode
  152. (font-lock-flush)))
  153. ;;*;; Outline Integration
  154. (defvar ess-roxy-outline-regexp "^#+' +@examples\\|^[^#]")
  155. (defun ess-roxy-substitute-outline-regexp (command)
  156. (let ((outline-regexp (if (ess-roxy-entry-p "examples")
  157. ess-roxy-outline-regexp
  158. outline-regexp)))
  159. (funcall command)))
  160. (declare-function outline-cycle "outline-magic")
  161. (defun ess-roxy-cycle-example ()
  162. (interactive)
  163. (unless (featurep 'outline-magic)
  164. (error "Please install and load outline-magic"))
  165. ;; Don't show children when cycling @examples
  166. (let ((this-command 'outline-cycle-overwiew))
  167. (ess-roxy-substitute-outline-regexp #'outline-cycle)))
  168. (defun ess-roxy-show-example ()
  169. (interactive)
  170. (ess-roxy-substitute-outline-regexp #'outline-show-entry))
  171. (defun ess-roxy-hide-example ()
  172. (interactive)
  173. (ess-roxy-substitute-outline-regexp #'outline-hide-entry))
  174. (defun ess-roxy-hide-all-examples ()
  175. (interactive)
  176. (save-excursion
  177. (goto-char (point-min))
  178. (while (re-search-forward "^#+' +@examples\\b" nil t)
  179. ;; Handle edge cases
  180. (when (ess-roxy-entry-p "examples")
  181. (ess-roxy-hide-example)))))
  182. (when (featurep 'outline-magic)
  183. (substitute-key-definition 'outline-cyle
  184. 'ess-roxy-cyle-example
  185. ess-roxy-mode-map outline-mode-menu-bar-map))
  186. (substitute-key-definition 'outline-hide-entry
  187. 'ess-roxy-hide-example
  188. ess-roxy-mode-map outline-minor-mode-map)
  189. (substitute-key-definition 'outline-show-entry
  190. 'ess-roxy-show-example
  191. ess-roxy-mode-map outline-minor-mode-map)
  192. ;;*;; Function definitions
  193. (defun ess-back-to-roxy ()
  194. "Go to roxy prefix."
  195. (end-of-line)
  196. (re-search-backward (concat ess-roxy-re " ?") (point-at-bol))
  197. (goto-char (match-end 0)))
  198. (defun ess-roxy-beg-of-entry ()
  199. "Get point number at start of current entry, 0 if not in entry."
  200. (save-excursion
  201. (let (beg)
  202. (beginning-of-line)
  203. (setq beg -1)
  204. (if (not (ess-roxy-entry-p))
  205. (setq beg 0)
  206. (setq beg (point)))
  207. (while (and (= (forward-line -1) 0) (ess-roxy-entry-p))
  208. (setq beg (point)))
  209. beg)))
  210. (defun ess-roxy-in-header-p ()
  211. "True if point is the description / details field."
  212. (save-excursion
  213. (let ((res t)
  214. (cont (ess-roxy-entry-p)))
  215. (beginning-of-line)
  216. (while cont
  217. (if (looking-at (concat ess-roxy-re " *[@].+"))
  218. (progn (setq res nil)
  219. (setq cont nil)))
  220. (setq cont (and (= (forward-line -1) 0) (ess-roxy-entry-p)))
  221. )res)))
  222. (defun ess-roxy-beg-of-field ()
  223. "Get point number at beginning of current field, 0 if not in entry."
  224. (save-excursion
  225. (let (cont beg)
  226. (beginning-of-line)
  227. (setq beg 0)
  228. (setq cont t)
  229. (while (and (ess-roxy-entry-p) cont)
  230. (setq beg (point))
  231. (if (looking-at (concat ess-roxy-re " *[@].+"))
  232. (setq cont nil))
  233. (if (ess-roxy-in-header-p)
  234. (if (looking-at (concat ess-roxy-re " *$"))
  235. (progn
  236. (forward-line 1)
  237. (setq beg (point))
  238. (setq cont nil))))
  239. (if cont (setq cont (= (forward-line -1) 0))))
  240. beg)))
  241. (defun ess-roxy-end-of-entry ()
  242. "Get point number at end of current entry, 0 if not in entry."
  243. (save-excursion
  244. (let ((end))
  245. (end-of-line)
  246. (setq end -1)
  247. (if (not (ess-roxy-entry-p))
  248. (setq end 0)
  249. (setq end (point)))
  250. (while (and (= (forward-line 1) 0) (ess-roxy-entry-p))
  251. (end-of-line)
  252. (setq end (point)))
  253. end)))
  254. (defun ess-roxy-end-of-field ()
  255. "Get point number at end of current field, 0 if not in entry."
  256. (save-excursion
  257. (let ((end nil)
  258. (cont nil))
  259. (setq end 0)
  260. (if (ess-roxy-entry-p) (progn (end-of-line) (setq end (point))))
  261. (beginning-of-line)
  262. (forward-line 1)
  263. (setq cont t)
  264. (while (and (ess-roxy-entry-p) cont)
  265. (save-excursion
  266. (end-of-line)
  267. (setq end (point)))
  268. (if (or (and (ess-roxy-in-header-p)
  269. (looking-at (concat ess-roxy-re " *$")))
  270. (looking-at (concat ess-roxy-re " *[@].+")))
  271. (progn
  272. (forward-line -1)
  273. (end-of-line)
  274. (setq end (point))
  275. (setq cont nil)))
  276. (if cont (setq cont (= (forward-line 1) 0))))
  277. end)))
  278. (defun ess-roxy-entry-p (&optional field)
  279. "Non-nil if point is in a roxy entry.
  280. FIELD allows checking for a specific field with
  281. `ess-roxy-current-field'."
  282. (and ess-roxy-mode
  283. (save-excursion
  284. (beginning-of-line)
  285. (looking-at-p ess-roxy-re))
  286. (or (null field)
  287. (string= (ess-roxy-current-field) field))))
  288. (defun ess-roxy-narrow-to-field ()
  289. "Go to to the start of current field."
  290. (interactive)
  291. (let ((beg (ess-roxy-beg-of-field))
  292. (end (ess-roxy-end-of-field)))
  293. (narrow-to-region beg end)))
  294. (defun ess-roxy-extract-field ()
  295. (let ((field (buffer-substring (ess-roxy-beg-of-entry)
  296. (ess-roxy-end-of-entry)))
  297. (prefix-re (ess-roxy-guess-str))
  298. (roxy-re ess-roxy-re))
  299. (with-temp-buffer
  300. (setq ess-roxy-re roxy-re)
  301. (insert field)
  302. (goto-char (point-min))
  303. (while (re-search-forward prefix-re (point-max) 'noerror)
  304. (replace-match ""))
  305. (buffer-substring (point-min) (point-max)))))
  306. (defun ess-roxy-adaptive-fill-function ()
  307. "Return prefix for filling paragraph or nil if not determined."
  308. (when (ess-roxy-entry-p)
  309. (let ((roxy-str (car (split-string (ess-roxy-guess-str) "'"))))
  310. (if (ess-roxy-in-header-p)
  311. (save-excursion
  312. (ess-back-to-roxy)
  313. (re-search-forward "\\([ \t]*\\)" (line-end-position) t)
  314. (concat roxy-str "' " (match-string 1)))
  315. (concat roxy-str "' " (make-string ess-indent-offset ? ))))))
  316. (defun ess-roxy-current-field ()
  317. "Return the name of the field at point."
  318. (and (not (ess-roxy-in-header-p))
  319. (save-excursion
  320. (goto-char (ess-roxy-beg-of-field))
  321. (if (re-search-forward (concat ess-roxy-re
  322. "[ \t]+@\\([[:alpha:]]+\\)")
  323. (line-end-position) t)
  324. (match-string-no-properties 1)))))
  325. (defun ess-roxy-maybe-indent-line ()
  326. "Indent line when point is in a field, but not in its first line."
  327. (when (and (not (ess-roxy-in-header-p))
  328. (not (equal (ess-roxy-current-field) "examples"))
  329. (save-excursion
  330. (beginning-of-line)
  331. (let ((line-n (count-lines 1 (point))))
  332. (goto-char (ess-roxy-beg-of-field))
  333. (not (equal line-n (count-lines 1 (point)))))))
  334. (ess-back-to-roxy)
  335. (delete-region (point) (progn (skip-chars-forward " \t") (point)))
  336. (insert (make-string ess-indent-offset ? ))))
  337. (defun ess-roxy-goto-func-def ()
  338. "Put point at start of function.
  339. Go to the beginning of the current one or below the current
  340. roxygen entry, error otherwise"
  341. (if (ess-roxy-entry-p)
  342. (progn
  343. (ess-roxy-goto-end-of-entry)
  344. (forward-line 1)
  345. (beginning-of-line))
  346. (unless (looking-at-p ess-function-pattern)
  347. (beginning-of-defun))))
  348. (defun ess-roxy-get-args-list-from-def ()
  349. "Get args list for current function."
  350. (save-excursion
  351. (ess-roxy-goto-func-def)
  352. (let ((args (ess-roxy-get-function-args)))
  353. (mapcar (lambda (x) (cons x '(""))) args))))
  354. (defun ess-roxy-insert-args (args &optional here)
  355. "Insert an ARGS list to the end of the current roxygen entry.
  356. If HERE is supplied start inputting `here'. Finish at end of
  357. line."
  358. (let* ((roxy-str (ess-roxy-guess-str))
  359. arg-des)
  360. (if (and here (< 1 here))
  361. (goto-char here)
  362. (ess-roxy-goto-end-of-entry)
  363. (beginning-of-line)
  364. (when (not (looking-at-p "="))
  365. (end-of-line)))
  366. (while (stringp (caar args))
  367. (setq arg-des (pop args))
  368. (unless (string= (car arg-des) "")
  369. (insert (concat "\n" roxy-str " @param " (car arg-des) " "))
  370. (insert
  371. (ess-replace-in-string (concat (car (cdr arg-des))) "\n"
  372. (concat "\n" roxy-str)))
  373. (when ess-roxy-fill-param-p
  374. (fill-paragraph))))))
  375. (defun ess-roxy-merge-args (fun ent)
  376. "Take two args lists (alists) and return their union.
  377. The result holds all keys from both FUN and ENT but no duplicates and
  378. association from ent are preferred over entries from fun. Also,
  379. drop entries from ent that are not in fun and are associated with
  380. the empty string."
  381. (let ((res-arg nil)
  382. (arg-des))
  383. (while (stringp (caar fun))
  384. (setq arg-des (pop fun))
  385. (if (assoc (car arg-des) ent)
  386. (setq res-arg
  387. (cons (cons (car arg-des) (cdr (assoc (car arg-des) ent))) res-arg))
  388. (setq res-arg (cons (cons (car arg-des) '("")) res-arg))))
  389. (while (stringp (caar ent))
  390. (setq arg-des (pop ent))
  391. (if (and (not (assoc (car arg-des) res-arg)) (not (string= (car (cdr arg-des)) "")))
  392. (setq res-arg (cons (cons (car arg-des) (cdr arg-des)) res-arg))))
  393. (nreverse res-arg)))
  394. (defun ess-roxy-update-entry ()
  395. "Update the entry at point or the entry above the current function.
  396. Add a template empty roxygen documentation if no roxygen entry is
  397. available. The template can be customized via the variable
  398. `ess-roxy-template-alist'. The parameter descriptions can are
  399. filled if `ess-roxy-fill-param-p' is non-nil."
  400. (interactive)
  401. (unless (derived-mode-p 'ess-r-mode)
  402. (user-error "%s mode not yet supported" major-mode))
  403. (save-excursion
  404. (let* ((args-fun (ess-roxy-get-args-list-from-def))
  405. (args-ent (ess-roxy-get-args-list-from-entry))
  406. (args (ess-roxy-merge-args args-fun args-ent))
  407. (roxy-str (ess-roxy-guess-str))
  408. (line-break "")
  409. template tag-def)
  410. (ess-roxy-goto-func-def)
  411. (when (not (= (forward-line -1) 0))
  412. (insert "\n")
  413. (forward-line -1))
  414. (when (and (not (looking-at "^\n")) (not (ess-roxy-entry-p)))
  415. (end-of-line)
  416. (insert "\n"))
  417. (if (ess-roxy-entry-p)
  418. (ess-roxy-insert-args args (1- (ess-roxy-delete-args)))
  419. (setq template (copy-sequence ess-roxy-template-alist))
  420. (while (stringp (caar template))
  421. (setq tag-def (pop template))
  422. (if (string= (car tag-def) "param")
  423. (ess-roxy-insert-args args (point))
  424. (if (string= (car tag-def) "description")
  425. (insert (concat line-break roxy-str " "
  426. (cdr tag-def) "\n" roxy-str))
  427. (if (string= (car tag-def) "details")
  428. (insert (concat line-break roxy-str " " (cdr tag-def)))
  429. (insert (concat line-break roxy-str " @"
  430. (car tag-def) " " (cdr tag-def))))))
  431. (setq line-break "\n"))))))
  432. (defun ess-roxy-goto-end-of-entry ()
  433. "Put point at the bottom of the current entry or above the function at point.
  434. Return t if the point is left in a roxygen entry, otherwise nil.
  435. Error if point is not in function or roxygen entry."
  436. (when (not (ess-roxy-entry-p))
  437. (beginning-of-defun)
  438. (forward-line -1))
  439. (if (ess-roxy-entry-p)
  440. (progn (goto-char (ess-roxy-end-of-entry))
  441. t)
  442. (forward-line)
  443. nil)
  444. (ess-roxy-entry-p))
  445. (defun ess-roxy-goto-beg-of-entry ()
  446. "Put point at the top of the entry at point or above the function at point.
  447. Return t if the point is left in a roxygen
  448. entry, otherwise nil. Error if point is not in function or
  449. roxygen entry."
  450. (if (not (ess-roxy-entry-p))
  451. (progn
  452. (goto-char (nth 0 (end-of-defun)))
  453. (forward-line -1)))
  454. (if (ess-roxy-entry-p)
  455. (progn
  456. (goto-char (ess-roxy-beg-of-entry))
  457. t)
  458. (forward-line) nil))
  459. (defun ess-roxy-delete-args ()
  460. "Remove all args from the entry at point or above the function at point.
  461. Return 0 if no deletions were made other wise the point at where
  462. the last deletion ended"
  463. (save-excursion
  464. (let* ((cont t)
  465. (field-beg 0)
  466. entry-beg entry-end field-end)
  467. (ess-roxy-goto-end-of-entry)
  468. (setq entry-beg (ess-roxy-beg-of-entry))
  469. (setq entry-end (ess-roxy-end-of-entry))
  470. (goto-char entry-end)
  471. (beginning-of-line)
  472. (while (and (<= entry-beg (point)) (> entry-beg 0) cont)
  473. (if (looking-at
  474. (concat ess-roxy-re " *@param"))
  475. (progn
  476. (setq field-beg (ess-roxy-beg-of-field))
  477. (setq field-end (ess-roxy-end-of-field))
  478. (delete-region field-beg (+ field-end 1))))
  479. (setq cont nil)
  480. (if (= (forward-line -1) 0)
  481. (setq cont t)))
  482. field-beg)))
  483. (defun ess-roxy-get-args-list-from-entry ()
  484. "Fill an args list from the entry above the function where the point is."
  485. (save-excursion
  486. (let* (args entry-beg field-beg field-end args-text arg-name desc)
  487. (if (ess-roxy-goto-end-of-entry)
  488. (progn
  489. (setq roxy-str (ess-roxy-guess-str))
  490. (beginning-of-line)
  491. (setq entry-beg (ess-roxy-beg-of-entry))
  492. (while (and (< entry-beg (point)) (> entry-beg 0))
  493. (if (looking-at
  494. (concat ess-roxy-re " *@param"))
  495. (progn
  496. (setq field-beg (ess-roxy-beg-of-field))
  497. (setq field-end (ess-roxy-end-of-field))
  498. (setq args-text (buffer-substring-no-properties
  499. field-beg field-end))
  500. (setq args-text
  501. (ess-replace-in-string args-text roxy-str ""))
  502. (setq args-text
  503. (ess-replace-in-string
  504. args-text "[[:space:]]*@param *" ""))
  505. ;; (setq args-text
  506. ;; (ess-replace-in-string args-text "\n" ""))
  507. (string-match "[^[:space:]]*" args-text)
  508. (setq arg-name (match-string 0 args-text))
  509. (setq desc (replace-regexp-in-string
  510. (concat "^" (regexp-quote arg-name) " *") "" args-text))
  511. (setq args (cons (list (concat arg-name)
  512. (concat desc))
  513. args))))
  514. (forward-line -1))
  515. args)
  516. nil))))
  517. (defun ess-roxy-toggle-roxy-region (beg end)
  518. "Toggle prefix roxygen string from BEG to END.
  519. Add the prefix if missing, remove if found. BEG and END default
  520. to the region, if active, and otherwise the entire line. This is
  521. convenient for editing example fields."
  522. (interactive "r")
  523. (unless (and beg end)
  524. (setq beg (line-beginning-position)
  525. end (line-end-position)))
  526. (ess-roxy-roxy-region beg end (ess-roxy-entry-p)))
  527. (defun ess-roxy-roxy-region (beg end &optional on)
  528. (save-excursion
  529. (let (RE to-string
  530. (roxy-str (ess-roxy-guess-str)))
  531. (narrow-to-region beg (- end 1))
  532. (if on
  533. (progn (setq RE (concat ess-roxy-re " +?"))
  534. (setq to-string ""))
  535. (setq RE "^")
  536. (setq to-string (concat roxy-str " ")))
  537. (goto-char beg)
  538. (while (re-search-forward RE (point-max) 'noerror)
  539. (replace-match to-string))
  540. (widen))))
  541. (defun ess-roxy-preview ()
  542. "Generate documentation for roxygen entry at point.
  543. Use a connected R session (starting one if necessary) and
  544. `ess-roxy-package' to generate the Rd code for the entry at
  545. point. Place it in a buffer and return that buffer."
  546. (unless (derived-mode-p 'ess-r-mode)
  547. (user-error "Preview only supported in R buffers, try `ess-r-devtools-document-package' instead"))
  548. (let* ((beg (ess-roxy-beg-of-entry))
  549. (tmpf (make-temp-file "ess-roxy"))
  550. (roxy-buf (get-buffer-create " *RoxygenPreview*"))
  551. (R-old-roxy
  552. (concat
  553. "..results <- roxygen2:::roc_process(rd_roclet(), parse.files(P), \"\");"
  554. "cat(vapply(..results, function(x) roxygen2:::rd_out_cache$compute(x, format(x)), character(1)))" ))
  555. (R-new-roxy
  556. (concat
  557. "..results <- roc_proc_text(rd_roclet(), readChar(P, file.info(P)$size));"
  558. "cat(vapply(..results, format, character(1)), \"\n\")" ))
  559. (out-rd-roclet
  560. (cond ((string= "roxygen" ess-roxy-package)
  561. "make.Rd2.roclet()$parse")
  562. ;; must not line break strings to avoid getting +s in the output
  563. ((string= "roxygen2" ess-roxy-package)
  564. (concat "(function(P) { if(packageVersion('roxygen2') < '3.0.0') {"
  565. R-old-roxy "} else {" R-new-roxy "} })"))
  566. (t (error "Need to hard code the roclet output call for roxygen package '%s'"
  567. ess-roxy-package)))))
  568. (when (= beg 0)
  569. (error "Point is not in a Roxygen entry"))
  570. (save-excursion
  571. (goto-char (ess-roxy-end-of-entry))
  572. (forward-line 1)
  573. (if (end-of-defun)
  574. (append-to-file beg (point) tmpf)
  575. (while (and (forward-line 1)
  576. (not (looking-at-p "^$"))
  577. (not (eobp))
  578. (not (looking-at-p ess-roxy-re))))
  579. (append-to-file beg (point) tmpf))
  580. (inferior-ess-r-force)
  581. (ess-force-buffer-current)
  582. (unless (ess-boolean-command (concat "print(suppressWarnings(require(" ess-roxy-package
  583. ", quietly=TRUE)))\n"))
  584. (error (concat "Failed to load the " ess-roxy-package " package; "
  585. "in R, try install.packages(\"" ess-roxy-package "\")")))
  586. (ess-command (concat out-rd-roclet "(\"" tmpf "\")\n") roxy-buf)
  587. (with-current-buffer roxy-buf
  588. ;; Kill characters up to % in case we missed stripping prompts
  589. ;; or +'s:
  590. (goto-char (point-min))
  591. (when (re-search-forward "%" (line-end-position) t)
  592. (backward-char)
  593. (delete-region (line-beginning-position) (point)))))
  594. (delete-file tmpf)
  595. roxy-buf))
  596. (defun ess-roxy-preview-HTML (&optional visit-instead-of-browse)
  597. "Use a (possibly newly) connected R session and the roxygen package to
  598. generate a HTML page for the roxygen entry at point and open that
  599. buffer in a browser. Visit the HTML file instead of showing it in
  600. a browser if `visit-instead-of-browse' is non-nil."
  601. (interactive "P")
  602. (let* ((roxy-buf (ess-roxy-preview))
  603. (rd-tmp-file (make-temp-file "ess-roxy-" nil ".Rd"))
  604. (html-tmp-file (make-temp-file "ess-roxy-" nil ".html"))
  605. (rd-to-html (concat "Rd2HTML(\"" rd-tmp-file "\",\""
  606. html-tmp-file "\", stages=c(\"render\"))"))
  607. )
  608. (with-current-buffer roxy-buf
  609. (set-visited-file-name rd-tmp-file)
  610. (save-buffer)
  611. (kill-buffer roxy-buf))
  612. (ess-force-buffer-current)
  613. (ess-command "print(suppressWarnings(require(tools, quietly=TRUE)))\n")
  614. (if visit-instead-of-browse
  615. (progn
  616. (ess-command (concat rd-to-html "\n"))
  617. (find-file html-tmp-file))
  618. (ess-command (concat "browseURL(" rd-to-html ")\n")))))
  619. (defun ess-roxy-preview-text ()
  620. "Use the connected R session and the roxygen package to
  621. generate the text help page of the roxygen entry at point."
  622. (interactive)
  623. (with-current-buffer (ess-roxy-preview)
  624. (Rd-preview-help)))
  625. (defun ess-roxy-preview-Rd (&optional name-file)
  626. "Preview Rd for the roxygen entry at point.
  627. Use the connected R session and the roxygen package to
  628. generate the Rd code for the roxygen entry at point. If called
  629. with a non-nil NAME-FILE (\\[universal-argument]),
  630. also set the visited file name of the created buffer to
  631. facilitate saving that file."
  632. (interactive "P")
  633. (let ((roxy-buf (ess-roxy-preview)))
  634. (pop-to-buffer roxy-buf)
  635. (if name-file
  636. (save-excursion
  637. (goto-char 1)
  638. (search-forward-regexp "name{\\(.+\\)}")
  639. (set-visited-file-name (concat (match-string 1) ".Rd"))))
  640. (Rd-mode)
  641. ;; why should the following be needed here? [[currently has no effect !!]]
  642. ;; usually in a *.Rd file fontification happens automatically
  643. (font-lock-ensure)))
  644. (defun ess-roxy-guess-str (&optional not-here)
  645. "Guess the prefix used in the current roxygen block.
  646. If NOT-HERE is non-nil, guess the prefix for nearest roxygen
  647. block before the point."
  648. (save-excursion
  649. (if (ess-roxy-entry-p)
  650. (progn
  651. (goto-char (point-at-bol))
  652. (search-forward-regexp ess-roxy-re))
  653. (if not-here
  654. (search-backward-regexp ess-roxy-re)))
  655. (if (or not-here (ess-roxy-entry-p))
  656. (match-string 0)
  657. (if (derived-mode-p 'ess-r-mode)
  658. ess-roxy-str
  659. (concat (string-trim comment-start) "'")))))
  660. (defun ess-roxy-hide-block ()
  661. "Hide current roxygen comment block."
  662. (interactive)
  663. (save-excursion
  664. (let ((end-of-entry (ess-roxy-end-of-entry))
  665. (beg-of-entry (ess-roxy-beg-of-entry)))
  666. (hs-hide-block-at-point nil (list beg-of-entry end-of-entry)))))
  667. (defun ess-roxy-toggle-hiding ()
  668. "Toggle hiding/showing of a block.
  669. See `hs-show-block' and `ess-roxy-hide-block'."
  670. (interactive)
  671. (hs-life-goes-on
  672. (if (hs-overlay-at (point-at-eol))
  673. (hs-show-block)
  674. (ess-roxy-hide-block))))
  675. (defun ess-roxy-show-all ()
  676. "Hide all Roxygen entries in current buffer."
  677. (interactive)
  678. (ess-roxy-hide-all t))
  679. (defun ess-roxy-hide-all (&optional show)
  680. "Hide all Roxygen entries in current buffer."
  681. (interactive)
  682. (when (not ess-roxy-hide-show-p)
  683. (user-error "First enable hide-show with `ess-roxy-hide-show-p'"))
  684. (hs-life-goes-on
  685. (save-excursion
  686. (goto-char (point-min))
  687. (while (re-search-forward (concat ess-roxy-re) (point-max) t 1)
  688. (let ((end-of-entry (ess-roxy-end-of-entry)))
  689. (if show
  690. (hs-show-block)
  691. (ess-roxy-hide-block))
  692. (goto-char end-of-entry)
  693. (forward-line 1))))))
  694. (defun ess-roxy-previous-entry ()
  695. "Go to beginning of previous Roxygen entry."
  696. (interactive)
  697. (if (ess-roxy-entry-p)
  698. (progn
  699. (goto-char (ess-roxy-beg-of-entry))
  700. (forward-line -1)))
  701. (search-backward-regexp ess-roxy-re (point-min) t 1)
  702. (goto-char (ess-roxy-beg-of-entry)))
  703. (defun ess-roxy-next-entry ()
  704. "Go to beginning of next Roxygen entry."
  705. (interactive)
  706. (if (ess-roxy-entry-p)
  707. (progn
  708. (goto-char (ess-roxy-end-of-entry))
  709. (forward-line 1)))
  710. (search-forward-regexp ess-roxy-re (point-max) t 1)
  711. (goto-char (ess-roxy-beg-of-entry)))
  712. (defun ess-roxy-get-function-args ()
  713. "Return the arguments specified for the current function as a list of strings.
  714. Assumes point is at the beginning of the function."
  715. (save-excursion
  716. (let ((args-txt
  717. (buffer-substring-no-properties
  718. (progn
  719. (search-forward-regexp "\\([=,-]+ *function *\\|^\s*function\\)" nil nil 1)
  720. (+ (point) 1))
  721. (progn
  722. (ess-roxy-match-paren)
  723. (point)))))
  724. (setq args-txt (replace-regexp-in-string "#+[^\"']*\n" "" args-txt))
  725. (setq args-txt (replace-regexp-in-string "([^)]+)" "" args-txt))
  726. (setq args-txt (replace-regexp-in-string "=[^,]+" "" args-txt))
  727. (setq args-txt (replace-regexp-in-string "[ \t\n]+" "" args-txt))
  728. (split-string args-txt ","))))
  729. (defun ess-roxy-match-paren ()
  730. "Go to the matching parenthesis."
  731. (cond ((looking-at "\\s\(") (forward-list 1) (backward-char 1))
  732. ((looking-at "\\s\)") (forward-char 1) (backward-list 1))))
  733. (defun ess-roxy-complete-tag ()
  734. "Complete the tag at point."
  735. (let ((bounds (ess-bounds-of-symbol)))
  736. (when (and bounds
  737. (save-excursion
  738. (goto-char (car bounds))
  739. (eq (following-char) ?@)))
  740. (list (1+ (car bounds)) (cdr bounds)
  741. (append ess-roxy-tags-noparam ess-roxy-tags-param)))))
  742. (defun ess-roxy-tag-completion ()
  743. "Completion data for Emacs >= 24."
  744. (when (save-excursion (re-search-backward "@\\<\\(\\w*\\)" (point-at-bol) t))
  745. (let ((beg (match-beginning 1))
  746. (end (match-end 1)))
  747. (when (and end (= end (point)))
  748. (list beg end (append ess-roxy-tags-noparam ess-roxy-tags-param) :exclusive 'no)))))
  749. (defun ess-roxy-remove-roxy-re (string)
  750. "Remove `ess-roxy-str' from STRING before sending to R process.
  751. Useful for sending code from example section. This function is
  752. placed in `ess-presend-filter-functions'."
  753. ;; Only strip the prefix in the @examples field, and only when
  754. ;; STRING is entirely contained inside it. This allows better
  755. ;; behavior for evaluation of regions.
  756. (let ((roxy-re ess-roxy-re))
  757. (if (and (ess-roxy-entry-p "examples")
  758. ;; don't send just @examples if we're looking at a line
  759. ;; like: ##' @examples
  760. (not (string-match-p (concat roxy-re "[[:space:]]*@") string))
  761. ;; (with-temp-buffer
  762. ;; ;; Need to carry the buffer-local value of
  763. ;; ;; `ess-roxy-re' into the temp buffer:
  764. ;; (setq ess-roxy-re roxy-re)
  765. ;; (insert string)
  766. ;; (ess-roxy-entry-p))
  767. )
  768. (replace-regexp-in-string (concat ess-roxy-re "\\s-*") "" string)
  769. string)))
  770. (defun ess-roxy-find-par-end (stop-point &rest stoppers)
  771. (mapc #'(lambda (stopper)
  772. (when (and (> stop-point (point))
  773. (save-excursion
  774. (re-search-forward stopper stop-point t)))
  775. (setq stop-point (match-beginning 0))))
  776. stoppers)
  777. (save-excursion
  778. (goto-char stop-point)
  779. (line-end-position 0)))
  780. ;;*;; Advices
  781. (defmacro ess-roxy-with-filling-context (examples &rest body)
  782. "Setup context (e.g. `comment-start') for filling roxygen BODY.
  783. EXAMPLES should be non-nil if filling an example block."
  784. (declare (indent 2) (debug (&rest form)))
  785. `(let ((comment-start (concat ess-roxy-re "[ \t]+#"))
  786. (comment-start-skip (concat ess-roxy-re "[ \t]+# *"))
  787. (comment-use-syntax nil)
  788. (adaptive-fill-first-line-regexp (concat ess-roxy-re "[ \t]*"))
  789. (paragraph-start (concat "\\(" ess-roxy-re "\\(" paragraph-start
  790. "\\|[ \t]*@" "\\)" "\\)\\|\\(" paragraph-start "\\)"))
  791. (temp-table (if ,examples
  792. (make-syntax-table ess-r-mode-syntax-table)
  793. Rd-mode-syntax-table)))
  794. (when ,examples
  795. ;; Prevent the roxy prefix to be interpreted as comment or string
  796. ;; starter
  797. (modify-syntax-entry ?# "w" temp-table)
  798. (modify-syntax-entry ?' "w" temp-table))
  799. ;; Neutralize (comment-normalize-vars) because it modifies the
  800. ;; comment-start regexp in such a way that paragraph filling of
  801. ;; comments in @examples fields does not work
  802. (cl-letf (((symbol-function 'comment-normalize-vars) #'ignore))
  803. (with-syntax-table temp-table
  804. ,@body))))
  805. (defun ess-roxy-ess-indent-command (&optional whole-exp)
  806. "Hide this block if we are at the beginning of the line.
  807. Else call `ess-indent-command'."
  808. (interactive "P")
  809. (if (and (bolp) (ess-roxy-entry-p) ess-roxy-hide-show-p)
  810. (progn (ess-roxy-toggle-hiding))
  811. (ess-indent-command whole-exp)))
  812. (defun ess--roxy-fill-block (fun &optional args)
  813. "Fill a roxygen block.
  814. FUN should be a filling function and ARGS gets passed to it."
  815. (let* ((saved-pos (point))
  816. (par-start (save-excursion
  817. (if (save-excursion
  818. (and (backward-paragraph)
  819. (forward-paragraph)
  820. (<= (point) saved-pos)))
  821. (line-beginning-position)
  822. (progn (backward-paragraph) (point)))))
  823. (par-end (ess-roxy-find-par-end
  824. (save-excursion
  825. (forward-paragraph)
  826. (point))
  827. (concat ess-roxy-re "[ \t]*@examples\\b") "^[^#]")))
  828. ;; Refill the whole structural paragraph sequentially, field by
  829. ;; field, stopping at @examples
  830. (ess-roxy-with-filling-context nil
  831. (save-excursion
  832. (save-restriction
  833. (narrow-to-region par-start par-end)
  834. (goto-char (point-min))
  835. (while (< (point) (point-max))
  836. (ess-roxy-maybe-indent-line)
  837. (apply fun args)
  838. (forward-paragraph)))))))
  839. (defun ess-r--fill-paragraph (orig-fun &rest args)
  840. "ESS fill paragraph for R mode.
  841. Overrides `fill-paragraph' which is ORIG-FUN when necessary and
  842. passes ARGS to it."
  843. (cond
  844. ;; Regular case
  845. ((not (derived-mode-p 'ess-r-mode))
  846. (apply orig-fun args))
  847. ;; Filling of code comments in @examples roxy field
  848. ((and (ess-roxy-entry-p)
  849. (save-excursion
  850. (ess-roxy-goto-end-of-roxy-comment)
  851. (looking-at "#")))
  852. (ess-roxy-with-filling-context t
  853. (apply orig-fun args)))
  854. ((and (not (ess-roxy-entry-p))
  855. (ess-inside-comment-p))
  856. (apply orig-fun args))
  857. ;; Filling of call arguments with point on call name
  858. ((and ess-fill-calls
  859. (ess-inside-call-name-p))
  860. (save-excursion
  861. (skip-chars-forward "^([")
  862. (forward-char)
  863. (ess-fill-args)))
  864. ;; Filling of continuations
  865. ((and ess-fill-continuations
  866. (ess-inside-continuation-p))
  867. (ess-fill-continuations))
  868. ;; Filling of call arguments
  869. ((and ess-fill-calls
  870. (ess-inside-call-p))
  871. (ess-fill-args))
  872. ;; Filling of roxy blocks
  873. ((ess-roxy-entry-p)
  874. (ess--roxy-fill-block orig-fun args))
  875. (t
  876. (apply orig-fun args))))
  877. (advice-add 'fill-paragraph :around #'ess-r--fill-paragraph)
  878. (defun ess-roxy-move-beginning-of-line (arg)
  879. "Move point to the beginning of the current line or roxygen comment.
  880. If not in a roxygen comment, call `move-beginning-of-line', which
  881. see for ARG. If in a roxygen field, leave point at the end of a
  882. roxygen comment. If already there, move to the beginning of the
  883. line."
  884. (interactive "^p")
  885. (if (ess-roxy-entry-p)
  886. (let ((pos (point)))
  887. (ess-roxy-goto-end-of-roxy-comment)
  888. (when (eql (point) pos)
  889. (move-beginning-of-line nil)))
  890. (move-beginning-of-line arg)))
  891. (defun ess-roxy-goto-end-of-roxy-comment ()
  892. "Leave point at the end of a roxygen comment.
  893. If not in a roxygen entry, call `back-to-indentation'."
  894. (interactive)
  895. (if (ess-roxy-entry-p)
  896. (progn
  897. (end-of-line)
  898. (re-search-backward (concat ess-roxy-re " *") (point-at-bol) t)
  899. (goto-char (match-end 0)))
  900. (back-to-indentation)))
  901. (defun ess-roxy-indent-new-comment-line ()
  902. (if (not (ess-roxy-entry-p))
  903. (indent-new-comment-line)
  904. (ess-roxy-indent-on-newline)))
  905. (defun ess-roxy-newline-and-indent ()
  906. "Start a newline and insert the roxygen prefix.
  907. Only do this if in a roxygen block and
  908. `ess-roxy-insert-prefix-on-newline' is non-nil."
  909. (interactive)
  910. (if (and (ess-roxy-entry-p)
  911. ess-roxy-insert-prefix-on-newline)
  912. (ess-roxy-indent-on-newline)
  913. (newline-and-indent)))
  914. (defun ess-roxy-indent-on-newline ()
  915. "Insert a newline in a roxygen field."
  916. (cond
  917. ;; Point at beginning of first line of entry; do nothing
  918. ((= (point) (ess-roxy-beg-of-entry))
  919. (newline-and-indent))
  920. ;; Otherwise: skip over roxy comment string if necessary and then
  921. ;; newline and then inset new roxy comment string
  922. (t
  923. (let ((point-after-roxy-string
  924. (save-excursion (forward-line 0)
  925. (ess-back-to-roxy)
  926. (point))))
  927. (goto-char (max (point) point-after-roxy-string)))
  928. (newline-and-indent)
  929. (insert (concat (ess-roxy-guess-str t) " ")))))
  930. (defun ess-roxy-cpp-fill-paragraph (&rest _args)
  931. "Advice for `c-fill-paragraph' that accounts for roxygen comments."
  932. (cond
  933. ;; Fill roxy @example's.
  934. ((ess-roxy-entry-p "examples")
  935. (ess--roxy-fill-block 'fill-paragraph) nil)
  936. ;; Fill roxy entries.
  937. ((ess-roxy-entry-p)
  938. (ess--roxy-fill-block 'fill-paragraph) nil)
  939. ;; Return t to signal to go on to `c-fill-paragraph'.
  940. (t t)))
  941. (advice-add 'c-fill-paragraph :before-while 'ess-roxy-cpp-fill-paragraph)
  942. (defun ess-roxy-enable-in-cpp ()
  943. "Enable `ess-roxy-mode' in C++ buffers in R packages."
  944. (when (and (fboundp 'ess-r-package-project)
  945. (ess-r-package-project))
  946. (ess-roxy-mode)))
  947. (with-eval-after-load "cc-mode"
  948. (add-hook 'c++-mode-hook #'ess-roxy-enable-in-cpp))
  949. (provide 'ess-roxy)
  950. ;;; ess-roxy.el ends here