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.

874 lines
37 KiB

преди 5 години
  1. ;;; ess-help.el --- Support for viewing ESS help files -*- lexical-binding: t; -*-
  2. ;; Copyright (C) 1989-1994, 2017 Bates, Kademan, Ritter and Smith
  3. ;; Copyright (C) 1997, A.J. Rossini <rossini@stat.sc.edu>
  4. ;; Copyright (C) 1998--2001 A.J. Rossini, Martin Maechler, Kurt Hornik and
  5. ;; Richard M. Heiberger <rmh@temple.edu>.
  6. ;; Copyright (C) 2001--2010 A.J. Rossini, Richard M. Heiberger, Martin
  7. ;; Maechler, Kurt Hornik, Rodney Sparapani, and Stephen Eglen.
  8. ;; Copyright (C) 2011--2012 A.J. Rossini, Richard M. Heiberger, Martin Maechler,
  9. ;; Kurt Hornik, Rodney Sparapani, Stephen Eglen and Vitalie Spinu.
  10. ;; Author: David Smith <dsmith@stats.adelaide.edu.au>
  11. ;; Created: 7 Jan 1994
  12. ;; Maintainer: ESS-core <ESS-core@r-project.org>
  13. ;; This file is part of ESS
  14. ;; This file is free software; you can redistribute it and/or modify
  15. ;; it under the terms of the GNU General Public License as published by
  16. ;; the Free Software Foundation; either version 2, or (at your option)
  17. ;; any later version.
  18. ;; This file is distributed in the hope that it will be useful,
  19. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  20. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  21. ;; GNU General Public License for more details.
  22. ;; A copy of the GNU General Public License is available at
  23. ;; https://www.r-project.org/Licenses/
  24. ;;; Commentary:
  25. ;; Code for dealing with ESS help files. See README.<LANGUAGE> where
  26. ;; <LANGUAGE> is one of `S', `SAS', `Stata' or `XLispStat'.
  27. ;;; Code:
  28. ; Requires and autoloads
  29. (require 'cl-lib)
  30. (eval-when-compile
  31. (require 'tramp))
  32. (require 'info)
  33. (require 'ess-mode)
  34. (require 'ess-inf)
  35. (require 'ess-utils)
  36. (require 'ansi-color)
  37. (declare-function ess-r-help-mode "ess-r-mode")
  38. (declare-function ess-stata-help-mode "ess-stata-lang")
  39. (defcustom ess-help-mode-hook nil
  40. "Functions to call when entering `ess-help-mode'."
  41. :group 'ess-hooks
  42. :type 'hook)
  43. (defvar ess--help-frame nil
  44. "Stores the frame used for displaying R help buffers.")
  45. ; ess-help-mode
  46. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  47. ;;;; In this section:
  48. ;;;;
  49. ;;;; * The function ess-display-help-on-object
  50. ;;;; * The major mode ess-help-mode
  51. ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  52. (cl-defgeneric ess--help-major-mode ()
  53. "Determine which help major mode to call, and call it.
  54. Uses `ess-dialect' to determine the appropriate help mode."
  55. (ess-help-mode))
  56. (defun ess--help-get-bogus-buffer-substring (buffer &optional nr-first)
  57. "Return non-nil if BUFFER looks like a bogus ESS help buffer.
  58. Return the pair (match-beg. match-end) which can be used in error
  59. message. NR-FIRST is the number of characters at the start of the
  60. buffer to examine when deciding if the buffer if bogus. If nil,
  61. the first 150 characters of the buffer are searched."
  62. (if (not nr-first) (setq nr-first 150))
  63. (with-current-buffer buffer
  64. (let ((PM (point-min))
  65. (case-fold-search t)
  66. searching res)
  67. (setq res
  68. (or ;; evaluate up to first non-nil (or end):
  69. (< (- (point-max) PM) 80); buffer less than 80 chars
  70. (not (setq searching t))
  71. ;; todo: move to customize-alist
  72. (progn (goto-char PM) ;; R:
  73. (re-search-forward "Error in help" nr-first t))
  74. (progn (goto-char PM) ;; S-plus 5.1 :
  75. (re-search-forward "^cat: .*--" nr-first t))
  76. (progn (goto-char PM) ;; S version 3 ; R :
  77. (re-search-forward "no documentation .+" nr-first t))
  78. (progn (goto-char PM) ;; stata
  79. (re-search-forward "^help .*not found" nr-first t))))
  80. (ess-write-to-dribble-buffer
  81. (format " |--> %s [searching %s]\n" res searching))
  82. (when res
  83. (if searching
  84. (buffer-substring (match-beginning 0) (match-end 0))
  85. (buffer-string))))))
  86. (defun ess-help-get-local-help-buffers ()
  87. (ess-force-buffer-current)
  88. (cl-remove-if-not
  89. (lambda (buffer)
  90. (let* ((pattern (concat "*help[" ess-current-process-name "]("))
  91. (name (buffer-name buffer))
  92. (candidate (when (> (length name) (length pattern))
  93. (substring name 0 (length pattern))) ))
  94. (when (string= pattern candidate)
  95. buffer)))
  96. (buffer-list)))
  97. (defvar-local ess-help-type nil
  98. "Type of help file, help, index, vignettes etc.
  99. Local in `ess-help' buffers.")
  100. (defvar-local ess-help-object nil
  101. "Name of the object the help is displayed for.
  102. Is name of the package for package index.
  103. Local in `ess-help' buffers.")
  104. (put 'ess-help-object 'permanent-local t)
  105. (defun ess-display-help-on-object (object &optional command)
  106. "Display documentation for OBJECT.
  107. If prefix ARG is given, force an update of the cached help topics
  108. and query the ESS process for the help file instead of reusing an
  109. existing buffer if it exists. Uses the variable
  110. `inferior-ess-help-command' for the actual help command. Prompts
  111. for the object name based on the cursor location for all cases
  112. except the S-Plus GUI. With S-Plus on Windows (both GUI and in an
  113. inferior Emacs buffer) the GUI help window is used. If COMMAND is
  114. supplied, it is used instead of `inferior-ess-help-command'."
  115. (interactive
  116. (progn
  117. (ess-force-buffer-current)
  118. (when current-prefix-arg ;update cache if prefix
  119. (ess-process-put 'sp-for-help-changed? t))
  120. (list (ess-find-help-file "Help on"))))
  121. (let* ((hb-name (concat "*help[" ess-current-process-name "]("
  122. (replace-regexp-in-string "^\\?\\|`" "" object) ")*"))
  123. (old-hb-p (get-buffer hb-name))
  124. (tbuffer (get-buffer-create hb-name)))
  125. (when (or (not old-hb-p)
  126. (ess-process-get 'sp-for-help-changed?)
  127. (ess--help-get-bogus-buffer-substring old-hb-p))
  128. (ess-with-current-buffer tbuffer
  129. (ess--flush-help-into-current-buffer object command)
  130. (setq ess-help-object object)
  131. (ess--help-major-mode)
  132. (setq truncate-lines nil
  133. ess-help-type 'help)))
  134. (unless (ess--help-kill-bogus-buffer-maybe tbuffer)
  135. (ess-display-help tbuffer))))
  136. (defun ess-help-revert-buffer (_ignore-auto _noconfirm)
  137. "Revert the current help buffer.
  138. This reloads the documentation. IGNORE-AUTO and NOCONFIRM are
  139. ignored."
  140. (ess-process-put 'sp-for-help-changed? t)
  141. (ess-display-help-on-object ess-help-object))
  142. (defalias 'ess-help 'ess-display-help-on-object)
  143. (cl-defgeneric ess-build-help-command (object)
  144. "Build a string command for retrieving help on OBJECT."
  145. (format inferior-ess-help-command object))
  146. (defun ess--flush-help-into-current-buffer (object &optional command)
  147. (let ((inhibit-modification-hooks t)
  148. (inhibit-read-only t))
  149. (delete-region (point-min) (point-max))
  150. (let ((command (if (and command (string-match-p "%s" command))
  151. (format command object)
  152. command)))
  153. (ess-command (or command (ess-build-help-command object)) (current-buffer)))
  154. (ess-help-underline)
  155. (unless (string= ess-language "STA")
  156. (ess-nuke-help-bs))
  157. (goto-char (point-min))
  158. (set-buffer-modified-p 'nil)))
  159. (defun ess--help-kill-bogus-buffer-maybe (buffer)
  160. "Internal, try to kill bogus BUFFER with message. Return t if killed."
  161. (when ess-help-kill-bogus-buffers
  162. (let ((bog-mes (ess--help-get-bogus-buffer-substring buffer)))
  163. (when bog-mes
  164. ;; The following is giving erroneous messages when help is displayed in the browser
  165. ;; (when (< (length bog-mes) 10) ;;no message at all, how to treat this properly?
  166. ;; (setq bog-mes (format "No documentation found; %s" bog-mes)))
  167. (ess-write-to-dribble-buffer
  168. (format "(ess-help: kill bogus buffer %s ..\n" (buffer-name buffer)))
  169. (message "%s" (replace-regexp-in-string "\n" "" bog-mes))
  170. ;; (ding) ;; don't ding, in julia a lot of doc strings are very short
  171. (kill-buffer buffer)))))
  172. (defun ess-display-help-in-browser ()
  173. "Displaying HTML help where available, using \\[browse-url]."
  174. (interactive)
  175. ;; Three ways to find HTML help, 1) ask sub-process 2) get URL/file from subprocess
  176. ;; 3) call elisp function to get the file path
  177. ;; For 2 and 3 call browse-url on the output
  178. (let (com-html-help ;1) command for sub-process to trigger
  179. ;help, must contain %s for help obj
  180. com-get-file-path ;2) command for sub-process to return a
  181. ; location for the help page, must
  182. ; contain %s for help obj
  183. fun-get-file-path ;3) elisp function to return the
  184. ;location, gets one argument, help topic
  185. not-implemented
  186. )
  187. (cond
  188. ((string-match "^R" ess-dialect)
  189. (setq com-html-help "help('%s', help_type='html')\n"))
  190. (t (setq not-implemented t))
  191. )
  192. (if not-implemented
  193. (message "Sorry, not implemented for %s " ess-dialect)
  194. (if (or (not ess-help-object)
  195. (not (eq ess-help-type 'help)))
  196. (message "No help topic found")
  197. (if com-html-help
  198. (ess-command (format com-html-help ess-help-object))
  199. (require 'browse-url)
  200. (if com-get-file-path
  201. (browse-url (car (ess-get-words-from-vector
  202. (format com-get-file-path ess-help-object))))
  203. (when (functionp fun-get-file-path)
  204. (browse-url (funcall fun-get-file-path ess-help-object)))))))))
  205. (defun ess--button-action (&optional button)
  206. "Provide help on object at the beginning of line.
  207. It's intended to be used in R-index help pages. Load the package
  208. if necessary. It is bound to RET and C-m in R-index pages."
  209. (let* ((string (button-label button))
  210. (command (ess-build-help-command string)))
  211. (ess-display-help-on-object string command)))
  212. (cl-defgeneric ess-help-commands ()
  213. "Return an alist of dialect specific retriever commands.
  214. Currently understood commands:
  215. - package-for-object - command to get the package of current help object
  216. - packages - command to get a list of available packages (REQUIRED)
  217. - package-index - command to get the package index (REQUIRED)
  218. - index-keyword-reg - regexp used to find keywords for linking in index listing
  219. only (1st subexpression is used)
  220. - index-start-reg - regexp from where to start searching for keywords in index listing"
  221. (user-error "Not implemented for %s " ess-dialect))
  222. (cl-defmethod ess-help-commands (&context (ess-dialect "R"))
  223. '((package-for-object . "sub('package:', '', .ess.findFUN('%s'))\n")
  224. (packages . ".packages(all.available=TRUE)\n")
  225. (package-index . ".ess.help(package='%s', help.type='text')\n")
  226. (index-keyword-reg . "^\\([^ \t\n:]+\\)")
  227. (index-start-reg . "^Index:")))
  228. (defun ess-display-package-index (&optional package)
  229. "Prompt for package name and display its index."
  230. (interactive
  231. (list (let* ((coms (ess-help-commands))
  232. (all-packs (ess-get-words-from-vector (cdr (assoc 'packages coms))))
  233. (pack (or (when (and ess-help-object
  234. (cdr (assoc 'package-for-object coms))
  235. (eq ess-help-type 'help))
  236. (car (ess-get-words-from-vector
  237. (format (cdr (assoc 'package-for-object coms))
  238. ess-help-object))))
  239. (car (member (ess-read-object-name-default) all-packs)))))
  240. (ess-completing-read "Index of" all-packs nil nil nil nil pack))))
  241. (let ((coms (ess-help-commands)))
  242. (ess--display-indexed-help-page
  243. (format (cdr (assoc 'package-index coms)) package)
  244. (cdr (assoc 'index-keyword-reg coms))
  245. (format "*help[%s](index:%s)*" ess-dialect package)
  246. 'index nil nil (cdr (assoc 'index-start-reg coms)) package)))
  247. (defun ess--display-indexed-help-page (command item-regexp title help-type
  248. &optional action help-echo reg-start help-object)
  249. "Internal function to display help pages with linked actions.
  250. COMMAND to produce the indexed help page,
  251. ITEM-REGEXP -- first subexpression is highlighted,
  252. TITLE of the help page,
  253. HELP-TYPE to be stored in `ess-help-type' local variable,
  254. ACTION is a function with no argument (default is `ess--button-action'),
  255. HELP-ECHO if given becomes the help-echo property of the button,
  256. REG-START gives the start location from where to search linkifying, and HELP-OBJECT becomes `ess-help-object'."
  257. (let ((inhibit-modification-hooks t)
  258. (alist ess-local-customize-alist)
  259. (pname ess-local-process-name)
  260. (buff (get-buffer-create title)))
  261. (ess-with-current-buffer buff
  262. (setq buffer-read-only nil)
  263. (delete-region (point-min) (point-max))
  264. (setq ess-local-process-name pname)
  265. (ess--help-major-mode)
  266. (ess-setq-vars-local (eval alist))
  267. (setq ess-help-object help-object
  268. ess-help-sec-regex "\\(^\\s-.*\n\\)\\|\\(^\n\\)")
  269. (ess-command command buff)
  270. (ess-help-underline)
  271. (set-buffer-modified-p 'nil)
  272. (goto-char (point-min))
  273. (when reg-start ;; go to the beginning of listing
  274. (re-search-forward reg-start nil t))
  275. (when item-regexp
  276. ;;linkify the buffer
  277. (save-excursion
  278. (while (re-search-forward item-regexp nil t)
  279. (make-text-button (match-beginning 1) (match-end 1)
  280. 'mouse-face 'highlight
  281. 'action (or action #'ess--button-action)
  282. 'help-object (buffer-substring-no-properties (match-beginning 1) (match-end 1))
  283. 'follow-link t
  284. 'help-echo (or help-echo "help on object")))))
  285. (save-excursion ;; why R help adds all these spaces?
  286. (goto-char (point-min))
  287. (when (re-search-forward "Index:\n\n" nil t)
  288. (let ((beg (point)))
  289. (forward-paragraph 1)
  290. (align-regexp beg (point) "\\(\\s-+\\)"))))
  291. (setq buffer-read-only t)
  292. (setq ess-help-type help-type)
  293. (setq truncate-lines nil))
  294. (unless (ess--help-kill-bogus-buffer-maybe buff)
  295. (ess-display-help buff))))
  296. (defun ess-display-help-apropos (&optional pattern)
  297. "Create an ess-apropos buffer with a *linked* list of apropos topics."
  298. (interactive "sPattern: ")
  299. (let (com regexp)
  300. (cond ((equal ess-dialect "R")
  301. (setq com "help.search('%s')\n"
  302. regexp "^\\([^ \t\n:]+::[^ \t\n:]+\\)[ \t\n]+"))
  303. ((equal ess-dialect "julia")
  304. (setq com "apropos(\"%s\")\n"
  305. regexp "^\\(\\(\\w\\|\\s_\\)+\\)("))
  306. ((equal ess-dialect "stata")
  307. (setq com "hsearch %s\n"
  308. regexp "^[\t ]*[0-9]+\\.[\t ]+\\(.+\\)$"))
  309. (t (error "Not implemented for dialect %s" ess-dialect)))
  310. (ess--display-indexed-help-page
  311. (format com pattern) regexp
  312. (format "*ess-apropos[%s](%s)*" ess-current-process-name pattern)
  313. 'appropos)))
  314. (defun ess-display-demos ()
  315. "Create an ess-demos buffer with a *linked* list of available demos."
  316. (interactive)
  317. (let (com regexp)
  318. (cond ((equal ess-dialect "R")
  319. (setq com "demo()\n"
  320. regexp "^\\([^ \n:]+\\) +"))
  321. (t (error "Not implemented for dialect %s" ess-dialect)))
  322. (ess--display-indexed-help-page
  323. com regexp
  324. (format "*ess-demos[%s]*" ess-current-process-name)
  325. 'demos #'ess--action-demo)))
  326. (defun ess--action-demo (&optional button)
  327. "Provide help on object at the beginning of line.
  328. It's intended to be used in R-index help pages. Load the package
  329. if necessary. It is bound to RET and C-m in R-index pages."
  330. (let* ((string (button-label button))
  331. (command
  332. (cond ((equal ess-dialect "R")
  333. (format "demo('%s')\n" string))
  334. (t (error "Not implemented for dialect %s" ess-dialect)))))
  335. (ess-eval-linewise command)
  336. (ess-switch-to-end-of-ESS)))
  337. (defun ess-display-vignettes (&optional all)
  338. "Display vignettes if available for the current dialect.
  339. With (prefix) ALL non-nil, use `vignette(*, all=TRUE)`, i.e., from all installed
  340. packages, which can be *very* slow."
  341. (interactive "P")
  342. (ess--display-vignettes-override all))
  343. (cl-defgeneric ess--display-vignettes-override (_all)
  344. "Display vignettes for the current dialect.
  345. See `ess-display-vignettes' for ALL."
  346. (user-error "Sorry, not implemented for %s" ess-dialect))
  347. (defun ess--action-open-in-emacs (pos)
  348. (display-buffer (find-file-noselect (get-text-property pos 'help-echo))))
  349. (defun ess--action-R-open-vignette (pos)
  350. (ess-eval-linewise (format "vignette('%s', package='%s')\n"
  351. (get-text-property pos 'vignette)
  352. (get-text-property pos 'package))))
  353. (defalias 'ess-help-quit 'quit-window)
  354. (make-obsolete 'ess-help-quit 'quit-window "16.04")
  355. (defun ess-display-help (buff)
  356. "Display buffer BUFF.
  357. If `ess-help-pop-to-buffer' is non-nil, call `pop-to-buffer',
  358. otherwise call `display-buffer' to display the buffer.
  359. You may control how help buffers are displayed by EITHER setting
  360. an entry in `display-buffer-alist' (see examples in info
  361. node `(ess) Controlling buffer display') OR setting the
  362. ESS-specific variables `ess-help-own-frame',
  363. `ess-help-reuse-window', `ess-help-frame-alist', and
  364. `ess-display-buffer-reuse-frames'."
  365. (let* ((action (cond (ess-help-own-frame
  366. '(display-buffer-reuse-window
  367. display-buffer-use-some-frame
  368. display-buffer-pop-up-frame))
  369. (ess-help-reuse-window
  370. '(display-buffer-reuse-window
  371. ess-display-buffer-reuse-mode-window
  372. display-buffer-pop-up-window
  373. display-buffer-use-some-window))
  374. (t '(display-buffer-pop-up-window
  375. display-buffer-use-some-window))))
  376. (alist `((mode . (ess-help-mode ess-r-help-mode ess-stata-help-mode ess-julia-help-mode))
  377. (reusable-frames . ,ess-display-buffer-reuse-frames)
  378. ;; `display-buffer-use-some-frame' uses this to
  379. ;; determine whether to use the frame or not.
  380. (frame-predicate . (lambda (f)
  381. (when (equal ess-help-own-frame 'one)
  382. ;; Note we're always returning
  383. ;; nil for `ess-help-own-frame' t.
  384. (frame-parameter f 'ess-help-frame))))
  385. ;; If `display-buffer' makes a new frame, these are
  386. ;; given as frame parameters.
  387. (pop-up-frame-parameters . ,(append ess-help-frame-alist
  388. `((auto-hide-function . delete-frame)
  389. (ess-help-frame . ,(equal ess-help-own-frame 'one)))))))
  390. (display-alist `(,action . ,alist)))
  391. (if ess-help-pop-to-buffer
  392. (pop-to-buffer buff display-alist)
  393. (display-buffer buff display-alist))))
  394. (defun ess-help-web-search (cmd)
  395. "Search the web for documentation on CMD."
  396. (interactive "sSearch for: ")
  397. (ess--help-web-search-override cmd))
  398. (cl-defgeneric ess--help-web-search-override (_cmd)
  399. "Dialect-specific override for `ess-help-web-search', which see for CMD."
  400. (error "Not implemented for %s" ess-dialect))
  401. (defun ess-manual-lookup ()
  402. "Search manual for documentation."
  403. (interactive)
  404. (ess--manual-lookup-override))
  405. (cl-defgeneric ess--manual-lookup-override ()
  406. "Dialect-specific override for `ess-manual-lookup'."
  407. (error "Not implemented for %s" ess-dialect))
  408. (defvar ess-doc-map
  409. (let (ess-doc-map)
  410. (define-prefix-command 'ess-doc-map)
  411. (define-key ess-doc-map "\C-e" #'ess-describe-object-at-point)
  412. (define-key ess-doc-map "e" #'ess-describe-object-at-point)
  413. (define-key ess-doc-map "\C-d" #'ess-display-help-on-object)
  414. (define-key ess-doc-map "d" #'ess-display-help-on-object)
  415. (define-key ess-doc-map "\C-i" #'ess-display-package-index)
  416. (define-key ess-doc-map "i" #'ess-display-package-index)
  417. (define-key ess-doc-map "\C-a" #'ess-display-help-apropos)
  418. (define-key ess-doc-map "a" #'ess-display-help-apropos)
  419. (define-key ess-doc-map "\C-v" #'ess-display-vignettes)
  420. (define-key ess-doc-map "v" #'ess-display-vignettes)
  421. (define-key ess-doc-map "\C-o" #'ess-display-demos)
  422. (define-key ess-doc-map "o" #'ess-display-demos)
  423. (define-key ess-doc-map "\C-w" #'ess-help-web-search)
  424. (define-key ess-doc-map "w" #'ess-help-web-search)
  425. (define-key ess-doc-map "\C-m" #'ess-manual-lookup)
  426. (define-key ess-doc-map "m" #'ess-manual-lookup)
  427. ess-doc-map)
  428. "ESS documentation map.")
  429. (defvar ess-help-mode-map
  430. (let ((map (make-keymap)))
  431. (define-key map "\C-m" #'next-line)
  432. (define-key map "h" #'ess-display-help-on-object)
  433. (define-key map "w" #'ess-display-help-in-browser)
  434. (define-key map "i" #'ess-display-package-index)
  435. (define-key map "a" #'ess-display-help-apropos)
  436. (define-key map "v" #'ess-display-vignettes)
  437. (define-key map "l" #'ess-eval-line-visibly-and-step)
  438. (define-key map "r" #'ess-eval-region-and-go)
  439. (define-key map "f" #'ess-eval-function-or-paragraph-and-step)
  440. (define-key map "n" #'ess-skip-to-next-section)
  441. (define-key map "p" #'ess-skip-to-previous-section)
  442. (define-key map "/" #'isearch-forward)
  443. (define-key map "x" #'ess-kill-buffer-and-go)
  444. (define-key map "k" #'kill-this-buffer)
  445. (define-key map "\C-c\C-s" #'ess-switch-process)
  446. (define-key map "\C-c\C-r" #'ess-eval-region)
  447. (define-key map "\C-c\M-r" #'ess-eval-region-and-go)
  448. (define-key map "\C-c\C-f" #'ess-eval-function)
  449. (define-key map "\M-\C-x" #'ess-eval-function)
  450. (define-key map "\C-c\M-f" #'ess-eval-function-and-go)
  451. (define-key map "\C-c\C-j" #'ess-eval-line)
  452. (define-key map "\C-c\C-n" #'ess-eval-line-visibly-and-step)
  453. (define-key map "\C-c\C-c" #'ess-eval-region-or-function-or-paragraph-and-step)
  454. (define-key map [(control return)] #'ess-eval-region-or-line-visibly-and-step)
  455. (define-key map "\C-c\M-j" #'ess-eval-line-and-go)
  456. (define-key map "\M-\C-a" #'ess-goto-beginning-of-function-or-para)
  457. (define-key map "\M-\C-e" #'ess-goto-end-of-function-or-para)
  458. (define-key map "\C-c\C-y" #'ess-switch-to-ESS)
  459. (define-key map "\C-c\C-z" #'ess-switch-to-end-of-ESS)
  460. (define-key map "\C-c\C-l" #'ess-load-file)
  461. (define-key map "\C-c\M-l" #'ess-load-file); alias, as in #'iESS#' where C-c C-l is comint-list-*
  462. (define-key map "\C-c\C-v" #'ess-display-help-on-object)
  463. (define-key map "\C-c\C-k" #'ess-request-a-process)
  464. (define-key map "\C-c\C-d" 'ess-doc-map)
  465. (define-key map "\C-c\C-e" 'ess-extra-map)
  466. (define-key map "\C-c\C-t" 'ess-dev-map)
  467. map)
  468. "Keymap for ESS help mode.")
  469. ;; One reason for the following menu is to <TEACH> the user about key strokes
  470. (defvar ess-help-mode-menu
  471. '("ESS-help"
  472. ["Search forward" isearch-forward t]
  473. ["Next section" ess-skip-to-next-section t]
  474. ["Previous section" ess-skip-to-previous-section t]
  475. ["Help on section skipping" ess-describe-sec-map t]
  476. ["Beginning of buffer" beginning-of-buffer t]
  477. ["End of buffer" end-of-buffer t]
  478. "-"
  479. ["Help on ..." ess-display-help-on-object t]
  480. ["Apropos of ..." ess-display-help-apropos t]
  481. ["Index of ..." ess-display-package-index t]
  482. ["Vignettes" ess-display-vignettes t]
  483. ["Open in browser" ess-display-help-in-browser t]
  484. "-"
  485. ["Eval line" ess-eval-line-and-step t]
  486. ["Eval paragraph & step" ess-eval-paragraph-and-step t]
  487. ["Eval region & go" ess-eval-region-and-go t]
  488. ["Switch to ESS process" ess-switch-to-ESS t]
  489. ["Switch to end of ESS proc." ess-switch-to-end-of-ESS t]
  490. ["Switch _the_ process" ess-switch-process t]
  491. "-"
  492. ["Kill buffer" kill-this-buffer t]
  493. ["Kill buffer & go" ess-kill-buffer-and-go t]
  494. "-"
  495. ["Handy commands" ess-handy-commands t])
  496. "Menu used in ess-help mode.")
  497. (easy-menu-define ess-help-mode-menu-map ess-help-mode-map
  498. "Menu keymap for ess-help mode." ess-help-mode-menu)
  499. (define-derived-mode ess-help-mode special-mode "ESS Help"
  500. "Mode for viewing ESS help files."
  501. :group 'ess-help
  502. ;; FIXME
  503. ;; (if ess-mode-syntax-table ;;set in advance by ess-setq-local
  504. ;; (set-syntax-table ess-mode-syntax-table))
  505. (setq-local revert-buffer-function #'ess-help-revert-buffer)
  506. (setq show-trailing-whitespace nil))
  507. ;;*;; User commands defined in ESS help mode
  508. (defun ess-skip-to-help-section ()
  509. "Jump to a section heading of a help buffer.
  510. The section selected is determined by the command letter used to
  511. invoke the command, as indicated by `ess-help-sec-keys-alist'.
  512. Use \\[ess-describe-sec-map] to see which keystrokes find which
  513. sections."
  514. (interactive)
  515. (let ((old-point (point))
  516. (case-fold-search nil)
  517. (the-sec (cdr (assoc last-command-event ess-help-sec-keys-alist))))
  518. (cl-assert the-sec nil (format "Invalid section key: %c" last-command-event))
  519. (goto-char (point-min))
  520. (if (re-search-forward (concat "^" the-sec) nil t)
  521. (progn (recenter)
  522. (beginning-of-line))
  523. (message "No %s section in this help. Sorry." the-sec)
  524. (goto-char old-point))))
  525. (defun ess-skip-to-next-section ()
  526. "Jump to next section in ESS help buffer."
  527. (interactive)
  528. (let ((case-fold-search nil))
  529. (when (looking-at-p ess-help-sec-regex)
  530. (forward-line))
  531. (if (re-search-forward ess-help-sec-regex nil 'no-error)
  532. (beginning-of-line)
  533. (message "No more sections."))))
  534. (defun ess-skip-to-previous-section ()
  535. "Jump to previous section in ESS help buffer."
  536. (interactive)
  537. (let ((case-fold-search nil))
  538. (if (re-search-backward ess-help-sec-regex nil 'no-error)
  539. (beginning-of-line)
  540. (message "No previous section."))))
  541. (defun ess-kill-buffer-and-go nil
  542. "Kill the current buffer and switch back to the ESS process."
  543. (interactive)
  544. (kill-buffer (current-buffer))
  545. (when (and ess-current-process-name (get-process ess-current-process-name))
  546. (ess-switch-to-ESS nil)))
  547. (defun ess-describe-sec-map nil
  548. "Display help for the `s' key."
  549. (interactive)
  550. (let ((keys-alist ess-help-sec-keys-alist))
  551. (describe-function 'ess-skip-to-help-section)
  552. (with-current-buffer "*Help*"
  553. (setq buffer-read-only nil)
  554. (goto-char (point-max))
  555. (insert "\n\nCurrently defined keys are:
  556. Keystroke Section
  557. --------- -------\n")
  558. (dolist (cs keys-alist)
  559. (insert " "
  560. (car cs)
  561. " "
  562. (cdr cs) "\n")))))
  563. (defun ess-helpobjs-at-point--read-obj ()
  564. (let* ((obj (ess-read-object-name-default)))
  565. ;; Exclude numbers
  566. (unless (and obj (not (string-match "[[:alpha:]]" obj)))
  567. obj)))
  568. (defun ess-unqualify-symbol (object)
  569. (if (string-match "^[[:alnum:].]+::?" object)
  570. (substring object (match-end 0))
  571. object))
  572. (defun ess-helpobjs-at-point (slist)
  573. "Return a list (def obj fun).
  574. Obj is a name at point, fun is the name of the function call
  575. point is in, and def is either obj or fun (in that order) which
  576. has a a help file, i.e. it is a member of SLIST (string-list).
  577. nil otherwise."
  578. (let* ((obj (ess-helpobjs-at-point--read-obj))
  579. (unqualified-obj (and obj (ess-unqualify-symbol obj)))
  580. ;; FIXME: probably should use syntactic logic here
  581. (fun (ignore-errors
  582. (save-excursion
  583. (save-restriction
  584. (narrow-to-region (max (point-min) (- (point) 1000))
  585. (point-max))
  586. (backward-up-list 1)
  587. (backward-char 1)
  588. (ess-read-object-name-default))))))
  589. (list (or (car (member obj slist))
  590. (when (member unqualified-obj slist)
  591. obj)
  592. (car (member fun slist)))
  593. obj fun)))
  594. (cl-defgeneric ess-help-get-topics (proc)
  595. "Return a list of help topics from PROC."
  596. (user-error "Not supported for %s (process: %s)" ess-dialect proc))
  597. (defun ess-find-help-file (p-string)
  598. "Find help, prompting for P-STRING."
  599. (ess-make-buffer-current)
  600. (let* ((help-files-list (ess-help-get-topics ess-current-process-name))
  601. (hlpobjs (delq nil (ess-helpobjs-at-point help-files-list))))
  602. (ess-completing-read p-string (append hlpobjs help-files-list)
  603. nil nil nil nil (car hlpobjs))))
  604. ;;*;; Utility functions
  605. (defun ess-get-help-files-list ()
  606. "Return a list of files which have help available."
  607. (apply 'nconc
  608. (mapcar (lambda (str)
  609. (let ((dirname (concat str "/.Help")))
  610. (and (file-directory-p dirname)
  611. (directory-files dirname))))
  612. (ess-search-list))))
  613. (defun ess-get-help-aliases-list ()
  614. "Return a list of aliases which have help available."
  615. (message "Retrieving RDS aliases...")
  616. ;; ess-command locks display, make sure the above message is visible
  617. (redisplay t)
  618. (ess-write-to-dribble-buffer "Processing RDS files ...\n")
  619. (prog1 (ess-get-words-from-vector ".ess.getHelpAliases()\n")
  620. (message "Retrieving RDS aliases...done")))
  621. (defun ess-nuke-help-bs ()
  622. "Remove ASCII underlining and overstriking performed by ^H codes."
  623. ;; This function is a modification of nuke-nroff-bs in man.el from the
  624. ;; standard Emacs 18 lisp library.
  625. ;; Nuke underlining and overstriking (only by the same letter)
  626. (goto-char (point-min))
  627. (while (search-forward "\b" nil t)
  628. (let* ((preceding (char-after (- (point) 2)))
  629. (following (following-char)))
  630. (cond ((= preceding following)
  631. ;; x\bx
  632. (delete-char -2))
  633. ((= preceding ?\_)
  634. ;; _\b
  635. (delete-char -2))
  636. ((= following ?\_)
  637. ;; \b_
  638. (delete-region (1- (point)) (1+ (point)))))))
  639. (goto-char (point-min))
  640. (let ((case-fold-search nil)); 'URL' != 'url' ('libcurl: ' on ?capabilities)
  641. (while (re-search-forward "\\bURL: " nil t); test with ?rtags
  642. ;; quick fix for C-x f confusion (getting into tramp)
  643. (delete-region (match-beginning 0) (match-end 0))))
  644. ;; Crunch blank lines
  645. (goto-char (point-min))
  646. (while (re-search-forward "\n\n\n\n*" nil t)
  647. (replace-match "\n\n"))
  648. ;; Nuke blanks lines at start.
  649. (goto-char (point-min))
  650. (skip-chars-forward "\n")
  651. (delete-region (point-min) (point)))
  652. (defun ess-help-underline ()
  653. "Replace _^H codes with underline face."
  654. (save-excursion
  655. (goto-char (point-min))
  656. (while (search-forward "_" nil t)
  657. (backward-delete-char 2)
  658. (put-text-property (point) (1+ (point)) 'face 'underline))))
  659. ;;*;; Link to Info
  660. (defun ess-goto-info (node)
  661. "Display node NODE from `ess-mode' info."
  662. (require 'info)
  663. (split-window)
  664. (Info-goto-node (concat "(ess)" node)))
  665. ;; describe object at point
  666. (defvar-local ess-describe-object-at-point-commands nil
  667. "Commands cycled by `ess-describe-object-at-point'.
  668. Dialect specific.")
  669. (defvar ess--descr-o-a-p-commands nil)
  670. (defun ess-describe-object-at-point ()
  671. "Get info for object at point, & display it in an electric buffer or tooltip.
  672. If region is active use it instead of the object at point.
  673. This is an electric command (`ess--execute-electric-command'),
  674. which means that you can use the last key to cycle through the
  675. action set (in this case `C-e'). After invocation of this command
  676. all standard Emacs commands, except those containing 'window' in
  677. their names, remove the electric *ess-describe* buffer. Use
  678. `other-window' to switch to *ess-describe* window.
  679. Customize `ess-describe-at-point-method' if you wan to display
  680. the description in a tooltip. See also
  681. `ess-r-describe-object-at-point-commands' (and similar option for
  682. other dialects)."
  683. (interactive)
  684. (if (not ess-describe-object-at-point-commands)
  685. (message "Not implemented for dialect %s" ess-dialect)
  686. (ess-force-buffer-current)
  687. (let ((map (make-sparse-keymap))
  688. (objname (or (and (use-region-p)
  689. (buffer-substring-no-properties (point) (mark)))
  690. (ess-symbol-at-point)))
  691. ess--descr-o-a-p-commands) ;; used in ess--describe-object-at-point
  692. (unless objname (error "No object at point "))
  693. (define-key map (vector last-command-event) 'ess--describe-object-at-point)
  694. ;; todo: put digits into the map
  695. (let* ((inhibit-quit t) ;; C-g removes the buffer
  696. (buf (ess--execute-electric-command
  697. map (format "Press %s to cycle"
  698. (single-key-description last-command-event))
  699. nil nil objname))
  700. ;; read full command
  701. (keys (read-key-sequence-vector ""))
  702. (command (and keys (key-binding keys))))
  703. (when (and (commandp command)
  704. (bufferp buf)
  705. (or (not (symbolp command)) ;; kill on lambdas
  706. (not (string-match "window" (symbol-name command)))))
  707. (kill-buffer buf)) ;; bury does not work here :( (Emacs bug?)
  708. (setq unread-command-events
  709. (append keys unread-command-events))))))
  710. (defun ess--describe-object-at-point (_ev objname)
  711. (setq ess--descr-o-a-p-commands (or ess--descr-o-a-p-commands
  712. (symbol-value ess-describe-object-at-point-commands)))
  713. (let* ((com (format (car (pop ess--descr-o-a-p-commands)) objname))
  714. (buf (get-buffer-create "*ess-describe*"))
  715. pos)
  716. (unless (eq ess-describe-at-point-method 'tooltip)
  717. ;; can take some time for the command to execute
  718. (display-buffer buf))
  719. (sit-for .01)
  720. (ess-command (concat com "\n") buf) ;; erases buf
  721. (with-current-buffer buf
  722. (goto-char (point-min))
  723. (insert (propertize (format "%s:\n\n" com) 'face 'font-lock-string-face))
  724. (forward-line -1)
  725. (setq pos (point))
  726. ;; set the keys that we are used to in help mode
  727. (special-mode)
  728. (let ((inhibit-read-only t))
  729. (ansi-color-apply-on-region (point-min) (point-max))))
  730. (if (eq ess-describe-at-point-method 'tooltip)
  731. (ess-tooltip-show-at-point
  732. (with-current-buffer buf (buffer-string)) 0 30)
  733. (display-buffer buf)
  734. (set-window-point (get-buffer-window buf) pos) ;; don't move window point
  735. buf)))
  736. (with-no-warnings
  737. ;; We're just backporting here, don't care about compiler warnings
  738. (defalias 'ess-display-buffer-reuse-mode-window
  739. ;; TODO: Remove once we drop support for Emacs 25
  740. (if (fboundp 'display-buffer-reuse-mode-window)
  741. 'display-buffer-reuse-mode-window
  742. (lambda (buffer alist)
  743. (let* ((alist-entry (assq 'reusable-frames alist))
  744. (alist-mode-entry (assq 'mode alist))
  745. (frames (cond (alist-entry (cdr alist-entry))
  746. ((if (eq pop-up-frames 'graphic-only)
  747. (display-graphic-p)
  748. pop-up-frames)
  749. 0)
  750. (display-buffer-reuse-frames 0)
  751. (t (last-nonminibuffer-frame))))
  752. (inhibit-same-window-p (cdr (assq 'inhibit-same-window alist)))
  753. (windows (window-list-1 nil 'nomini frames))
  754. (buffer-mode (with-current-buffer buffer major-mode))
  755. (allowed-modes (if alist-mode-entry
  756. (cdr alist-mode-entry)
  757. buffer-mode))
  758. (curwin (selected-window))
  759. (curframe (selected-frame)))
  760. (unless (listp allowed-modes)
  761. (setq allowed-modes (list allowed-modes)))
  762. (let (same-mode-same-frame
  763. same-mode-other-frame
  764. derived-mode-same-frame
  765. derived-mode-other-frame)
  766. (dolist (window windows)
  767. (let ((mode?
  768. (with-current-buffer (window-buffer window)
  769. (cond ((memq major-mode allowed-modes)
  770. 'same)
  771. ((derived-mode-p allowed-modes)
  772. 'derived)))))
  773. (when (and mode?
  774. (not (and inhibit-same-window-p
  775. (eq window curwin))))
  776. (push window (if (eq curframe (window-frame window))
  777. (if (eq mode? 'same)
  778. same-mode-same-frame
  779. derived-mode-same-frame)
  780. (if (eq mode? 'same)
  781. same-mode-other-frame
  782. derived-mode-other-frame))))))
  783. (let ((window (car (nconc same-mode-same-frame
  784. same-mode-other-frame
  785. derived-mode-same-frame
  786. derived-mode-other-frame))))
  787. (when (window-live-p window)
  788. (prog1 (window--display-buffer buffer window 'reuse alist)
  789. (unless (cdr (assq 'inhibit-switch-frame alist))
  790. (window--maybe-raise-frame (window-frame window))))))))))))
  791. (provide 'ess-help)
  792. ;;; ess-help.el ends here