|
|
- ;;; ess-help.el --- Support for viewing ESS help files -*- lexical-binding: t; -*-
-
- ;; Copyright (C) 1989-1994, 2017 Bates, Kademan, Ritter and Smith
- ;; Copyright (C) 1997, A.J. Rossini <rossini@stat.sc.edu>
- ;; Copyright (C) 1998--2001 A.J. Rossini, Martin Maechler, Kurt Hornik and
- ;; Richard M. Heiberger <rmh@temple.edu>.
- ;; Copyright (C) 2001--2010 A.J. Rossini, Richard M. Heiberger, Martin
- ;; Maechler, Kurt Hornik, Rodney Sparapani, and Stephen Eglen.
- ;; Copyright (C) 2011--2012 A.J. Rossini, Richard M. Heiberger, Martin Maechler,
- ;; Kurt Hornik, Rodney Sparapani, Stephen Eglen and Vitalie Spinu.
-
- ;; Author: David Smith <dsmith@stats.adelaide.edu.au>
- ;; Created: 7 Jan 1994
- ;; Maintainer: ESS-core <ESS-core@r-project.org>
-
- ;; This file is part of ESS
-
- ;; This file is free software; you can 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 file 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 at
- ;; https://www.r-project.org/Licenses/
-
- ;;; Commentary:
-
- ;; Code for dealing with ESS help files. See README.<LANGUAGE> where
- ;; <LANGUAGE> is one of `S', `SAS', `Stata' or `XLispStat'.
-
- ;;; Code:
-
- ; Requires and autoloads
- (require 'cl-lib)
- (eval-when-compile
- (require 'tramp))
- (require 'info)
- (require 'ess-mode)
- (require 'ess-inf)
- (require 'ess-utils)
- (require 'ansi-color)
-
- (declare-function ess-r-help-mode "ess-r-mode")
- (declare-function ess-stata-help-mode "ess-stata-lang")
-
-
- (defcustom ess-help-mode-hook nil
- "Functions to call when entering `ess-help-mode'."
- :group 'ess-hooks
- :type 'hook)
-
- (defvar ess--help-frame nil
- "Stores the frame used for displaying R help buffers.")
-
- ; ess-help-mode
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
- ;;;; In this section:
- ;;;;
- ;;;; * The function ess-display-help-on-object
- ;;;; * The major mode ess-help-mode
- ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
-
- (cl-defgeneric ess--help-major-mode ()
- "Determine which help major mode to call, and call it.
- Uses `ess-dialect' to determine the appropriate help mode."
- (ess-help-mode))
-
- (defun ess--help-get-bogus-buffer-substring (buffer &optional nr-first)
- "Return non-nil if BUFFER looks like a bogus ESS help buffer.
- Return the pair (match-beg. match-end) which can be used in error
- message. NR-FIRST is the number of characters at the start of the
- buffer to examine when deciding if the buffer if bogus. If nil,
- the first 150 characters of the buffer are searched."
- (if (not nr-first) (setq nr-first 150))
- (with-current-buffer buffer
- (let ((PM (point-min))
- (case-fold-search t)
- searching res)
- (setq res
- (or ;; evaluate up to first non-nil (or end):
- (< (- (point-max) PM) 80); buffer less than 80 chars
- (not (setq searching t))
- ;; todo: move to customize-alist
- (progn (goto-char PM) ;; R:
- (re-search-forward "Error in help" nr-first t))
- (progn (goto-char PM) ;; S-plus 5.1 :
- (re-search-forward "^cat: .*--" nr-first t))
- (progn (goto-char PM) ;; S version 3 ; R :
- (re-search-forward "no documentation .+" nr-first t))
- (progn (goto-char PM) ;; stata
- (re-search-forward "^help .*not found" nr-first t))))
- (ess-write-to-dribble-buffer
- (format " |--> %s [searching %s]\n" res searching))
- (when res
- (if searching
- (buffer-substring (match-beginning 0) (match-end 0))
- (buffer-string))))))
-
- (defun ess-help-get-local-help-buffers ()
- (ess-force-buffer-current)
- (cl-remove-if-not
- (lambda (buffer)
- (let* ((pattern (concat "*help[" ess-current-process-name "]("))
- (name (buffer-name buffer))
- (candidate (when (> (length name) (length pattern))
- (substring name 0 (length pattern))) ))
- (when (string= pattern candidate)
- buffer)))
- (buffer-list)))
-
- (defvar-local ess-help-type nil
- "Type of help file, help, index, vignettes etc.
- Local in `ess-help' buffers.")
-
- (defvar-local ess-help-object nil
- "Name of the object the help is displayed for.
- Is name of the package for package index.
- Local in `ess-help' buffers.")
- (put 'ess-help-object 'permanent-local t)
-
- (defun ess-display-help-on-object (object &optional command)
- "Display documentation for OBJECT.
- If prefix ARG is given, force an update of the cached help topics
- and query the ESS process for the help file instead of reusing an
- existing buffer if it exists. Uses the variable
- `inferior-ess-help-command' for the actual help command. Prompts
- for the object name based on the cursor location for all cases
- except the S-Plus GUI. With S-Plus on Windows (both GUI and in an
- inferior Emacs buffer) the GUI help window is used. If COMMAND is
- supplied, it is used instead of `inferior-ess-help-command'."
- (interactive
- (progn
- (ess-force-buffer-current)
- (when current-prefix-arg ;update cache if prefix
- (ess-process-put 'sp-for-help-changed? t))
- (list (ess-find-help-file "Help on"))))
- (let* ((hb-name (concat "*help[" ess-current-process-name "]("
- (replace-regexp-in-string "^\\?\\|`" "" object) ")*"))
- (old-hb-p (get-buffer hb-name))
- (tbuffer (get-buffer-create hb-name)))
- (when (or (not old-hb-p)
- (ess-process-get 'sp-for-help-changed?)
- (ess--help-get-bogus-buffer-substring old-hb-p))
- (ess-with-current-buffer tbuffer
- (ess--flush-help-into-current-buffer object command)
- (setq ess-help-object object)
- (ess--help-major-mode)
- (setq truncate-lines nil
- ess-help-type 'help)))
- (unless (ess--help-kill-bogus-buffer-maybe tbuffer)
- (ess-display-help tbuffer))))
-
- (defun ess-help-revert-buffer (_ignore-auto _noconfirm)
- "Revert the current help buffer.
- This reloads the documentation. IGNORE-AUTO and NOCONFIRM are
- ignored."
- (ess-process-put 'sp-for-help-changed? t)
- (ess-display-help-on-object ess-help-object))
-
- (defalias 'ess-help 'ess-display-help-on-object)
-
- (cl-defgeneric ess-build-help-command (object)
- "Build a string command for retrieving help on OBJECT."
- (format inferior-ess-help-command object))
-
- (defun ess--flush-help-into-current-buffer (object &optional command)
- (let ((inhibit-modification-hooks t)
- (inhibit-read-only t))
- (delete-region (point-min) (point-max))
- (let ((command (if (and command (string-match-p "%s" command))
- (format command object)
- command)))
- (ess-command (or command (ess-build-help-command object)) (current-buffer)))
- (ess-help-underline)
- (unless (string= ess-language "STA")
- (ess-nuke-help-bs))
- (goto-char (point-min))
- (set-buffer-modified-p 'nil)))
-
- (defun ess--help-kill-bogus-buffer-maybe (buffer)
- "Internal, try to kill bogus BUFFER with message. Return t if killed."
- (when ess-help-kill-bogus-buffers
- (let ((bog-mes (ess--help-get-bogus-buffer-substring buffer)))
- (when bog-mes
- ;; The following is giving erroneous messages when help is displayed in the browser
- ;; (when (< (length bog-mes) 10) ;;no message at all, how to treat this properly?
- ;; (setq bog-mes (format "No documentation found; %s" bog-mes)))
- (ess-write-to-dribble-buffer
- (format "(ess-help: kill bogus buffer %s ..\n" (buffer-name buffer)))
- (message "%s" (replace-regexp-in-string "\n" "" bog-mes))
- ;; (ding) ;; don't ding, in julia a lot of doc strings are very short
- (kill-buffer buffer)))))
-
- (defun ess-display-help-in-browser ()
- "Displaying HTML help where available, using \\[browse-url]."
- (interactive)
- ;; Three ways to find HTML help, 1) ask sub-process 2) get URL/file from subprocess
- ;; 3) call elisp function to get the file path
- ;; For 2 and 3 call browse-url on the output
- (let (com-html-help ;1) command for sub-process to trigger
- ;help, must contain %s for help obj
- com-get-file-path ;2) command for sub-process to return a
- ; location for the help page, must
- ; contain %s for help obj
- fun-get-file-path ;3) elisp function to return the
- ;location, gets one argument, help topic
- not-implemented
- )
- (cond
- ((string-match "^R" ess-dialect)
- (setq com-html-help "help('%s', help_type='html')\n"))
- (t (setq not-implemented t))
- )
- (if not-implemented
- (message "Sorry, not implemented for %s " ess-dialect)
- (if (or (not ess-help-object)
- (not (eq ess-help-type 'help)))
- (message "No help topic found")
- (if com-html-help
- (ess-command (format com-html-help ess-help-object))
- (require 'browse-url)
- (if com-get-file-path
- (browse-url (car (ess-get-words-from-vector
- (format com-get-file-path ess-help-object))))
- (when (functionp fun-get-file-path)
- (browse-url (funcall fun-get-file-path ess-help-object)))))))))
-
- (defun ess--button-action (&optional button)
- "Provide help on object at the beginning of line.
- It's intended to be used in R-index help pages. Load the package
- if necessary. It is bound to RET and C-m in R-index pages."
- (let* ((string (button-label button))
- (command (ess-build-help-command string)))
- (ess-display-help-on-object string command)))
-
- (cl-defgeneric ess-help-commands ()
- "Return an alist of dialect specific retriever commands.
- Currently understood commands:
- - package-for-object - command to get the package of current help object
- - packages - command to get a list of available packages (REQUIRED)
- - package-index - command to get the package index (REQUIRED)
- - index-keyword-reg - regexp used to find keywords for linking in index listing
- only (1st subexpression is used)
- - index-start-reg - regexp from where to start searching for keywords in index listing"
- (user-error "Not implemented for %s " ess-dialect))
-
- (cl-defmethod ess-help-commands (&context (ess-dialect "R"))
- '((package-for-object . "sub('package:', '', .ess.findFUN('%s'))\n")
- (packages . ".packages(all.available=TRUE)\n")
- (package-index . ".ess.help(package='%s', help.type='text')\n")
- (index-keyword-reg . "^\\([^ \t\n:]+\\)")
- (index-start-reg . "^Index:")))
-
- (defun ess-display-package-index (&optional package)
- "Prompt for package name and display its index."
- (interactive
- (list (let* ((coms (ess-help-commands))
- (all-packs (ess-get-words-from-vector (cdr (assoc 'packages coms))))
- (pack (or (when (and ess-help-object
- (cdr (assoc 'package-for-object coms))
- (eq ess-help-type 'help))
- (car (ess-get-words-from-vector
- (format (cdr (assoc 'package-for-object coms))
- ess-help-object))))
- (car (member (ess-read-object-name-default) all-packs)))))
- (ess-completing-read "Index of" all-packs nil nil nil nil pack))))
- (let ((coms (ess-help-commands)))
- (ess--display-indexed-help-page
- (format (cdr (assoc 'package-index coms)) package)
- (cdr (assoc 'index-keyword-reg coms))
- (format "*help[%s](index:%s)*" ess-dialect package)
- 'index nil nil (cdr (assoc 'index-start-reg coms)) package)))
-
- (defun ess--display-indexed-help-page (command item-regexp title help-type
- &optional action help-echo reg-start help-object)
- "Internal function to display help pages with linked actions.
- COMMAND to produce the indexed help page,
- ITEM-REGEXP -- first subexpression is highlighted,
- TITLE of the help page,
- HELP-TYPE to be stored in `ess-help-type' local variable,
- ACTION is a function with no argument (default is `ess--button-action'),
- HELP-ECHO if given becomes the help-echo property of the button,
- REG-START gives the start location from where to search linkifying, and HELP-OBJECT becomes `ess-help-object'."
- (let ((inhibit-modification-hooks t)
- (alist ess-local-customize-alist)
- (pname ess-local-process-name)
- (buff (get-buffer-create title)))
- (ess-with-current-buffer buff
- (setq buffer-read-only nil)
- (delete-region (point-min) (point-max))
- (setq ess-local-process-name pname)
- (ess--help-major-mode)
- (ess-setq-vars-local (eval alist))
- (setq ess-help-object help-object
- ess-help-sec-regex "\\(^\\s-.*\n\\)\\|\\(^\n\\)")
- (ess-command command buff)
- (ess-help-underline)
- (set-buffer-modified-p 'nil)
- (goto-char (point-min))
- (when reg-start ;; go to the beginning of listing
- (re-search-forward reg-start nil t))
- (when item-regexp
- ;;linkify the buffer
- (save-excursion
- (while (re-search-forward item-regexp nil t)
- (make-text-button (match-beginning 1) (match-end 1)
- 'mouse-face 'highlight
- 'action (or action #'ess--button-action)
- 'help-object (buffer-substring-no-properties (match-beginning 1) (match-end 1))
- 'follow-link t
- 'help-echo (or help-echo "help on object")))))
- (save-excursion ;; why R help adds all these spaces?
- (goto-char (point-min))
- (when (re-search-forward "Index:\n\n" nil t)
- (let ((beg (point)))
- (forward-paragraph 1)
- (align-regexp beg (point) "\\(\\s-+\\)"))))
- (setq buffer-read-only t)
- (setq ess-help-type help-type)
- (setq truncate-lines nil))
- (unless (ess--help-kill-bogus-buffer-maybe buff)
- (ess-display-help buff))))
-
- (defun ess-display-help-apropos (&optional pattern)
- "Create an ess-apropos buffer with a *linked* list of apropos topics."
- (interactive "sPattern: ")
- (let (com regexp)
- (cond ((equal ess-dialect "R")
- (setq com "help.search('%s')\n"
- regexp "^\\([^ \t\n:]+::[^ \t\n:]+\\)[ \t\n]+"))
- ((equal ess-dialect "julia")
- (setq com "apropos(\"%s\")\n"
- regexp "^\\(\\(\\w\\|\\s_\\)+\\)("))
- ((equal ess-dialect "stata")
- (setq com "hsearch %s\n"
- regexp "^[\t ]*[0-9]+\\.[\t ]+\\(.+\\)$"))
- (t (error "Not implemented for dialect %s" ess-dialect)))
- (ess--display-indexed-help-page
- (format com pattern) regexp
- (format "*ess-apropos[%s](%s)*" ess-current-process-name pattern)
- 'appropos)))
-
- (defun ess-display-demos ()
- "Create an ess-demos buffer with a *linked* list of available demos."
- (interactive)
- (let (com regexp)
- (cond ((equal ess-dialect "R")
- (setq com "demo()\n"
- regexp "^\\([^ \n:]+\\) +"))
- (t (error "Not implemented for dialect %s" ess-dialect)))
- (ess--display-indexed-help-page
- com regexp
- (format "*ess-demos[%s]*" ess-current-process-name)
- 'demos #'ess--action-demo)))
-
- (defun ess--action-demo (&optional button)
- "Provide help on object at the beginning of line.
- It's intended to be used in R-index help pages. Load the package
- if necessary. It is bound to RET and C-m in R-index pages."
- (let* ((string (button-label button))
- (command
- (cond ((equal ess-dialect "R")
- (format "demo('%s')\n" string))
- (t (error "Not implemented for dialect %s" ess-dialect)))))
- (ess-eval-linewise command)
- (ess-switch-to-end-of-ESS)))
-
- (defun ess-display-vignettes (&optional all)
- "Display vignettes if available for the current dialect.
- With (prefix) ALL non-nil, use `vignette(*, all=TRUE)`, i.e., from all installed
- packages, which can be *very* slow."
- (interactive "P")
- (ess--display-vignettes-override all))
-
- (cl-defgeneric ess--display-vignettes-override (_all)
- "Display vignettes for the current dialect.
- See `ess-display-vignettes' for ALL."
- (user-error "Sorry, not implemented for %s" ess-dialect))
-
- (defun ess--action-open-in-emacs (pos)
- (display-buffer (find-file-noselect (get-text-property pos 'help-echo))))
- (defun ess--action-R-open-vignette (pos)
- (ess-eval-linewise (format "vignette('%s', package='%s')\n"
- (get-text-property pos 'vignette)
- (get-text-property pos 'package))))
-
- (defalias 'ess-help-quit 'quit-window)
- (make-obsolete 'ess-help-quit 'quit-window "16.04")
-
- (defun ess-display-help (buff)
- "Display buffer BUFF.
- If `ess-help-pop-to-buffer' is non-nil, call `pop-to-buffer',
- otherwise call `display-buffer' to display the buffer.
-
- You may control how help buffers are displayed by EITHER setting
- an entry in `display-buffer-alist' (see examples in info
- node `(ess) Controlling buffer display') OR setting the
- ESS-specific variables `ess-help-own-frame',
- `ess-help-reuse-window', `ess-help-frame-alist', and
- `ess-display-buffer-reuse-frames'."
- (let* ((action (cond (ess-help-own-frame
- '(display-buffer-reuse-window
- display-buffer-use-some-frame
- display-buffer-pop-up-frame))
- (ess-help-reuse-window
- '(display-buffer-reuse-window
- ess-display-buffer-reuse-mode-window
- display-buffer-pop-up-window
- display-buffer-use-some-window))
- (t '(display-buffer-pop-up-window
- display-buffer-use-some-window))))
- (alist `((mode . (ess-help-mode ess-r-help-mode ess-stata-help-mode ess-julia-help-mode))
- (reusable-frames . ,ess-display-buffer-reuse-frames)
- ;; `display-buffer-use-some-frame' uses this to
- ;; determine whether to use the frame or not.
- (frame-predicate . (lambda (f)
- (when (equal ess-help-own-frame 'one)
- ;; Note we're always returning
- ;; nil for `ess-help-own-frame' t.
- (frame-parameter f 'ess-help-frame))))
- ;; If `display-buffer' makes a new frame, these are
- ;; given as frame parameters.
- (pop-up-frame-parameters . ,(append ess-help-frame-alist
- `((auto-hide-function . delete-frame)
- (ess-help-frame . ,(equal ess-help-own-frame 'one)))))))
- (display-alist `(,action . ,alist)))
- (if ess-help-pop-to-buffer
- (pop-to-buffer buff display-alist)
- (display-buffer buff display-alist))))
-
- (defun ess-help-web-search (cmd)
- "Search the web for documentation on CMD."
- (interactive "sSearch for: ")
- (ess--help-web-search-override cmd))
-
- (cl-defgeneric ess--help-web-search-override (_cmd)
- "Dialect-specific override for `ess-help-web-search', which see for CMD."
- (error "Not implemented for %s" ess-dialect))
-
- (defun ess-manual-lookup ()
- "Search manual for documentation."
- (interactive)
- (ess--manual-lookup-override))
-
- (cl-defgeneric ess--manual-lookup-override ()
- "Dialect-specific override for `ess-manual-lookup'."
- (error "Not implemented for %s" ess-dialect))
-
- (defvar ess-doc-map
- (let (ess-doc-map)
- (define-prefix-command 'ess-doc-map)
- (define-key ess-doc-map "\C-e" #'ess-describe-object-at-point)
- (define-key ess-doc-map "e" #'ess-describe-object-at-point)
- (define-key ess-doc-map "\C-d" #'ess-display-help-on-object)
- (define-key ess-doc-map "d" #'ess-display-help-on-object)
- (define-key ess-doc-map "\C-i" #'ess-display-package-index)
- (define-key ess-doc-map "i" #'ess-display-package-index)
- (define-key ess-doc-map "\C-a" #'ess-display-help-apropos)
- (define-key ess-doc-map "a" #'ess-display-help-apropos)
- (define-key ess-doc-map "\C-v" #'ess-display-vignettes)
- (define-key ess-doc-map "v" #'ess-display-vignettes)
- (define-key ess-doc-map "\C-o" #'ess-display-demos)
- (define-key ess-doc-map "o" #'ess-display-demos)
- (define-key ess-doc-map "\C-w" #'ess-help-web-search)
- (define-key ess-doc-map "w" #'ess-help-web-search)
- (define-key ess-doc-map "\C-m" #'ess-manual-lookup)
- (define-key ess-doc-map "m" #'ess-manual-lookup)
- ess-doc-map)
- "ESS documentation map.")
-
-
- (defvar ess-help-mode-map
- (let ((map (make-keymap)))
- (define-key map "\C-m" #'next-line)
- (define-key map "h" #'ess-display-help-on-object)
- (define-key map "w" #'ess-display-help-in-browser)
- (define-key map "i" #'ess-display-package-index)
- (define-key map "a" #'ess-display-help-apropos)
- (define-key map "v" #'ess-display-vignettes)
- (define-key map "l" #'ess-eval-line-visibly-and-step)
- (define-key map "r" #'ess-eval-region-and-go)
- (define-key map "f" #'ess-eval-function-or-paragraph-and-step)
- (define-key map "n" #'ess-skip-to-next-section)
- (define-key map "p" #'ess-skip-to-previous-section)
- (define-key map "/" #'isearch-forward)
- (define-key map "x" #'ess-kill-buffer-and-go)
- (define-key map "k" #'kill-this-buffer)
- (define-key map "\C-c\C-s" #'ess-switch-process)
- (define-key map "\C-c\C-r" #'ess-eval-region)
- (define-key map "\C-c\M-r" #'ess-eval-region-and-go)
- (define-key map "\C-c\C-f" #'ess-eval-function)
- (define-key map "\M-\C-x" #'ess-eval-function)
- (define-key map "\C-c\M-f" #'ess-eval-function-and-go)
- (define-key map "\C-c\C-j" #'ess-eval-line)
- (define-key map "\C-c\C-n" #'ess-eval-line-visibly-and-step)
- (define-key map "\C-c\C-c" #'ess-eval-region-or-function-or-paragraph-and-step)
- (define-key map [(control return)] #'ess-eval-region-or-line-visibly-and-step)
- (define-key map "\C-c\M-j" #'ess-eval-line-and-go)
- (define-key map "\M-\C-a" #'ess-goto-beginning-of-function-or-para)
- (define-key map "\M-\C-e" #'ess-goto-end-of-function-or-para)
- (define-key map "\C-c\C-y" #'ess-switch-to-ESS)
- (define-key map "\C-c\C-z" #'ess-switch-to-end-of-ESS)
- (define-key map "\C-c\C-l" #'ess-load-file)
- (define-key map "\C-c\M-l" #'ess-load-file); alias, as in #'iESS#' where C-c C-l is comint-list-*
- (define-key map "\C-c\C-v" #'ess-display-help-on-object)
- (define-key map "\C-c\C-k" #'ess-request-a-process)
- (define-key map "\C-c\C-d" 'ess-doc-map)
- (define-key map "\C-c\C-e" 'ess-extra-map)
- (define-key map "\C-c\C-t" 'ess-dev-map)
- map)
- "Keymap for ESS help mode.")
-
- ;; One reason for the following menu is to <TEACH> the user about key strokes
- (defvar ess-help-mode-menu
- '("ESS-help"
- ["Search forward" isearch-forward t]
- ["Next section" ess-skip-to-next-section t]
- ["Previous section" ess-skip-to-previous-section t]
- ["Help on section skipping" ess-describe-sec-map t]
- ["Beginning of buffer" beginning-of-buffer t]
- ["End of buffer" end-of-buffer t]
- "-"
- ["Help on ..." ess-display-help-on-object t]
- ["Apropos of ..." ess-display-help-apropos t]
- ["Index of ..." ess-display-package-index t]
- ["Vignettes" ess-display-vignettes t]
- ["Open in browser" ess-display-help-in-browser t]
- "-"
- ["Eval line" ess-eval-line-and-step t]
- ["Eval paragraph & step" ess-eval-paragraph-and-step t]
- ["Eval region & go" ess-eval-region-and-go t]
- ["Switch to ESS process" ess-switch-to-ESS t]
- ["Switch to end of ESS proc." ess-switch-to-end-of-ESS t]
- ["Switch _the_ process" ess-switch-process t]
- "-"
- ["Kill buffer" kill-this-buffer t]
- ["Kill buffer & go" ess-kill-buffer-and-go t]
- "-"
- ["Handy commands" ess-handy-commands t])
- "Menu used in ess-help mode.")
-
- (easy-menu-define ess-help-mode-menu-map ess-help-mode-map
- "Menu keymap for ess-help mode." ess-help-mode-menu)
-
- (define-derived-mode ess-help-mode special-mode "ESS Help"
- "Mode for viewing ESS help files."
- :group 'ess-help
- ;; FIXME
- ;; (if ess-mode-syntax-table ;;set in advance by ess-setq-local
- ;; (set-syntax-table ess-mode-syntax-table))
- (setq-local revert-buffer-function #'ess-help-revert-buffer)
- (setq show-trailing-whitespace nil))
-
- ;;*;; User commands defined in ESS help mode
-
- (defun ess-skip-to-help-section ()
- "Jump to a section heading of a help buffer.
- The section selected is determined by the command letter used to
- invoke the command, as indicated by `ess-help-sec-keys-alist'.
- Use \\[ess-describe-sec-map] to see which keystrokes find which
- sections."
- (interactive)
- (let ((old-point (point))
- (case-fold-search nil)
- (the-sec (cdr (assoc last-command-event ess-help-sec-keys-alist))))
- (cl-assert the-sec nil (format "Invalid section key: %c" last-command-event))
- (goto-char (point-min))
- (if (re-search-forward (concat "^" the-sec) nil t)
- (progn (recenter)
- (beginning-of-line))
- (message "No %s section in this help. Sorry." the-sec)
- (goto-char old-point))))
-
- (defun ess-skip-to-next-section ()
- "Jump to next section in ESS help buffer."
- (interactive)
- (let ((case-fold-search nil))
- (when (looking-at-p ess-help-sec-regex)
- (forward-line))
- (if (re-search-forward ess-help-sec-regex nil 'no-error)
- (beginning-of-line)
- (message "No more sections."))))
-
- (defun ess-skip-to-previous-section ()
- "Jump to previous section in ESS help buffer."
- (interactive)
- (let ((case-fold-search nil))
- (if (re-search-backward ess-help-sec-regex nil 'no-error)
- (beginning-of-line)
- (message "No previous section."))))
-
- (defun ess-kill-buffer-and-go nil
- "Kill the current buffer and switch back to the ESS process."
- (interactive)
- (kill-buffer (current-buffer))
- (when (and ess-current-process-name (get-process ess-current-process-name))
- (ess-switch-to-ESS nil)))
-
- (defun ess-describe-sec-map nil
- "Display help for the `s' key."
- (interactive)
- (let ((keys-alist ess-help-sec-keys-alist))
- (describe-function 'ess-skip-to-help-section)
-
- (with-current-buffer "*Help*"
- (setq buffer-read-only nil)
- (goto-char (point-max))
- (insert "\n\nCurrently defined keys are:
-
- Keystroke Section
- --------- -------\n")
- (dolist (cs keys-alist)
- (insert " "
- (car cs)
- " "
- (cdr cs) "\n")))))
-
- (defun ess-helpobjs-at-point--read-obj ()
- (let* ((obj (ess-read-object-name-default)))
- ;; Exclude numbers
- (unless (and obj (not (string-match "[[:alpha:]]" obj)))
- obj)))
-
- (defun ess-unqualify-symbol (object)
- (if (string-match "^[[:alnum:].]+::?" object)
- (substring object (match-end 0))
- object))
-
- (defun ess-helpobjs-at-point (slist)
- "Return a list (def obj fun).
- Obj is a name at point, fun is the name of the function call
- point is in, and def is either obj or fun (in that order) which
- has a a help file, i.e. it is a member of SLIST (string-list).
- nil otherwise."
- (let* ((obj (ess-helpobjs-at-point--read-obj))
- (unqualified-obj (and obj (ess-unqualify-symbol obj)))
- ;; FIXME: probably should use syntactic logic here
- (fun (ignore-errors
- (save-excursion
- (save-restriction
- (narrow-to-region (max (point-min) (- (point) 1000))
- (point-max))
- (backward-up-list 1)
- (backward-char 1)
- (ess-read-object-name-default))))))
- (list (or (car (member obj slist))
- (when (member unqualified-obj slist)
- obj)
- (car (member fun slist)))
- obj fun)))
-
- (cl-defgeneric ess-help-get-topics (proc)
- "Return a list of help topics from PROC."
- (user-error "Not supported for %s (process: %s)" ess-dialect proc))
-
- (defun ess-find-help-file (p-string)
- "Find help, prompting for P-STRING."
- (ess-make-buffer-current)
- (let* ((help-files-list (ess-help-get-topics ess-current-process-name))
- (hlpobjs (delq nil (ess-helpobjs-at-point help-files-list))))
- (ess-completing-read p-string (append hlpobjs help-files-list)
- nil nil nil nil (car hlpobjs))))
-
- ;;*;; Utility functions
-
- (defun ess-get-help-files-list ()
- "Return a list of files which have help available."
- (apply 'nconc
- (mapcar (lambda (str)
- (let ((dirname (concat str "/.Help")))
- (and (file-directory-p dirname)
- (directory-files dirname))))
- (ess-search-list))))
-
- (defun ess-get-help-aliases-list ()
- "Return a list of aliases which have help available."
- (message "Retrieving RDS aliases...")
- ;; ess-command locks display, make sure the above message is visible
- (redisplay t)
- (ess-write-to-dribble-buffer "Processing RDS files ...\n")
- (prog1 (ess-get-words-from-vector ".ess.getHelpAliases()\n")
- (message "Retrieving RDS aliases...done")))
-
- (defun ess-nuke-help-bs ()
- "Remove ASCII underlining and overstriking performed by ^H codes."
- ;; This function is a modification of nuke-nroff-bs in man.el from the
- ;; standard Emacs 18 lisp library.
- ;; Nuke underlining and overstriking (only by the same letter)
- (goto-char (point-min))
- (while (search-forward "\b" nil t)
- (let* ((preceding (char-after (- (point) 2)))
- (following (following-char)))
- (cond ((= preceding following)
- ;; x\bx
- (delete-char -2))
- ((= preceding ?\_)
- ;; _\b
- (delete-char -2))
- ((= following ?\_)
- ;; \b_
- (delete-region (1- (point)) (1+ (point)))))))
- (goto-char (point-min))
- (let ((case-fold-search nil)); 'URL' != 'url' ('libcurl: ' on ?capabilities)
- (while (re-search-forward "\\bURL: " nil t); test with ?rtags
- ;; quick fix for C-x f confusion (getting into tramp)
- (delete-region (match-beginning 0) (match-end 0))))
- ;; Crunch blank lines
- (goto-char (point-min))
- (while (re-search-forward "\n\n\n\n*" nil t)
- (replace-match "\n\n"))
- ;; Nuke blanks lines at start.
- (goto-char (point-min))
- (skip-chars-forward "\n")
- (delete-region (point-min) (point)))
-
- (defun ess-help-underline ()
- "Replace _^H codes with underline face."
- (save-excursion
- (goto-char (point-min))
- (while (search-forward "_" nil t)
- (backward-delete-char 2)
- (put-text-property (point) (1+ (point)) 'face 'underline))))
-
- ;;*;; Link to Info
- (defun ess-goto-info (node)
- "Display node NODE from `ess-mode' info."
- (require 'info)
- (split-window)
- (Info-goto-node (concat "(ess)" node)))
-
- ;; describe object at point
-
- (defvar-local ess-describe-object-at-point-commands nil
- "Commands cycled by `ess-describe-object-at-point'.
- Dialect specific.")
-
- (defvar ess--descr-o-a-p-commands nil)
-
- (defun ess-describe-object-at-point ()
- "Get info for object at point, & display it in an electric buffer or tooltip.
- If region is active use it instead of the object at point.
-
- This is an electric command (`ess--execute-electric-command'),
- which means that you can use the last key to cycle through the
- action set (in this case `C-e'). After invocation of this command
- all standard Emacs commands, except those containing 'window' in
- their names, remove the electric *ess-describe* buffer. Use
- `other-window' to switch to *ess-describe* window.
-
- Customize `ess-describe-at-point-method' if you wan to display
- the description in a tooltip. See also
- `ess-r-describe-object-at-point-commands' (and similar option for
- other dialects)."
- (interactive)
- (if (not ess-describe-object-at-point-commands)
- (message "Not implemented for dialect %s" ess-dialect)
- (ess-force-buffer-current)
- (let ((map (make-sparse-keymap))
- (objname (or (and (use-region-p)
- (buffer-substring-no-properties (point) (mark)))
- (ess-symbol-at-point)))
- ess--descr-o-a-p-commands) ;; used in ess--describe-object-at-point
- (unless objname (error "No object at point "))
- (define-key map (vector last-command-event) 'ess--describe-object-at-point)
- ;; todo: put digits into the map
- (let* ((inhibit-quit t) ;; C-g removes the buffer
- (buf (ess--execute-electric-command
- map (format "Press %s to cycle"
- (single-key-description last-command-event))
- nil nil objname))
- ;; read full command
- (keys (read-key-sequence-vector ""))
- (command (and keys (key-binding keys))))
- (when (and (commandp command)
- (bufferp buf)
- (or (not (symbolp command)) ;; kill on lambdas
- (not (string-match "window" (symbol-name command)))))
- (kill-buffer buf)) ;; bury does not work here :( (Emacs bug?)
- (setq unread-command-events
- (append keys unread-command-events))))))
-
- (defun ess--describe-object-at-point (_ev objname)
- (setq ess--descr-o-a-p-commands (or ess--descr-o-a-p-commands
- (symbol-value ess-describe-object-at-point-commands)))
- (let* ((com (format (car (pop ess--descr-o-a-p-commands)) objname))
- (buf (get-buffer-create "*ess-describe*"))
- pos)
- (unless (eq ess-describe-at-point-method 'tooltip)
- ;; can take some time for the command to execute
- (display-buffer buf))
- (sit-for .01)
- (ess-command (concat com "\n") buf) ;; erases buf
- (with-current-buffer buf
- (goto-char (point-min))
- (insert (propertize (format "%s:\n\n" com) 'face 'font-lock-string-face))
- (forward-line -1)
- (setq pos (point))
- ;; set the keys that we are used to in help mode
- (special-mode)
- (let ((inhibit-read-only t))
- (ansi-color-apply-on-region (point-min) (point-max))))
- (if (eq ess-describe-at-point-method 'tooltip)
- (ess-tooltip-show-at-point
- (with-current-buffer buf (buffer-string)) 0 30)
- (display-buffer buf)
- (set-window-point (get-buffer-window buf) pos) ;; don't move window point
- buf)))
-
- (with-no-warnings
- ;; We're just backporting here, don't care about compiler warnings
- (defalias 'ess-display-buffer-reuse-mode-window
- ;; TODO: Remove once we drop support for Emacs 25
- (if (fboundp 'display-buffer-reuse-mode-window)
- 'display-buffer-reuse-mode-window
- (lambda (buffer alist)
- (let* ((alist-entry (assq 'reusable-frames alist))
- (alist-mode-entry (assq 'mode alist))
- (frames (cond (alist-entry (cdr alist-entry))
- ((if (eq pop-up-frames 'graphic-only)
- (display-graphic-p)
- pop-up-frames)
- 0)
- (display-buffer-reuse-frames 0)
- (t (last-nonminibuffer-frame))))
- (inhibit-same-window-p (cdr (assq 'inhibit-same-window alist)))
- (windows (window-list-1 nil 'nomini frames))
- (buffer-mode (with-current-buffer buffer major-mode))
- (allowed-modes (if alist-mode-entry
- (cdr alist-mode-entry)
- buffer-mode))
- (curwin (selected-window))
- (curframe (selected-frame)))
- (unless (listp allowed-modes)
- (setq allowed-modes (list allowed-modes)))
- (let (same-mode-same-frame
- same-mode-other-frame
- derived-mode-same-frame
- derived-mode-other-frame)
- (dolist (window windows)
- (let ((mode?
- (with-current-buffer (window-buffer window)
- (cond ((memq major-mode allowed-modes)
- 'same)
- ((derived-mode-p allowed-modes)
- 'derived)))))
- (when (and mode?
- (not (and inhibit-same-window-p
- (eq window curwin))))
- (push window (if (eq curframe (window-frame window))
- (if (eq mode? 'same)
- same-mode-same-frame
- derived-mode-same-frame)
- (if (eq mode? 'same)
- same-mode-other-frame
- derived-mode-other-frame))))))
- (let ((window (car (nconc same-mode-same-frame
- same-mode-other-frame
- derived-mode-same-frame
- derived-mode-other-frame))))
- (when (window-live-p window)
- (prog1 (window--display-buffer buffer window 'reuse alist)
- (unless (cdr (assq 'inhibit-switch-frame alist))
- (window--maybe-raise-frame (window-frame window))))))))))))
-
- (provide 'ess-help)
- ;;; ess-help.el ends here
|