;;; ess-help.el --- Support for viewing ESS help files ;; Copyright (C) 1989-1994 Bates, Kademan, Ritter and Smith ;; Copyright (C) 1997, A.J. Rossini ;; Copyright (C) 1998--2001 A.J. Rossini, Martin Maechler, Kurt Hornik and ;; Richard M. Heiberger . ;; Copyright (C) 2001--2004 A.J. Rossini, Rich M. Heiberger, Martin ;; Maechler, Kurt Hornik, Rodney Sparapani, and Stephen Eglen. ;; Original Author: David Smith ;; Created: 7 Jan 1994 ;; Maintainers: ESS-core ;; 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. ;; You should have received a copy of the GNU General Public License ;; along with GNU Emacs; see the file COPYING. If not, write to ;; the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. ;;; Commentary: ;; Code for dealing with ESS help files. See README. where ;; is one of `S', `SAS', `Stata'or `XLispStat'. ;;; Code: ; Requires and autoloads (eval-when-compile (require 'reporter) (require 'ess-inf) (require 'info)) (require 'ess) (autoload 'ess-eval-region "ess-inf" "[autoload]" t) (autoload 'ess-eval-region-and-go "ess-inf" "[autoload]" t) (autoload 'ess-eval-function "ess-inf" "[autoload]" t) (autoload 'ess-eval-function-and-go "ess-inf" "[autoload]" t) (autoload 'ess-eval-line "ess-inf" "[autoload]" t) (autoload 'ess-eval-line-and-go "ess-inf" "[autoload]" t) (autoload 'ess-eval-line-and-step "ess-inf" "[autoload]" t) (autoload 'ess-beginning-of-function "ess-mode" "[autoload]" t) (autoload 'ess-end-of-function "ess-mode" "[autoload]" t) (autoload 'ess-load-file "ess-inf" "[autoload]" t) (autoload 'ess-command "ess-inf" "(autoload)" nil) (autoload 'ess-display-temp-buffer "ess-inf" "(autoload)" nil) (autoload 'ess-switch-to-ESS "ess-inf" "(autoload)" nil) (autoload 'ess-read-object-name-default "ess-inf" "(autoload)" nil) (autoload 'ess-make-buffer-current "ess-inf" "(autoload)" nil) (autoload 'ess-search-list "ess-inf" "(autoload)" nil) (autoload 'ess-get-object-list "ess-inf" "(autoload)" nil) ; ess-help-mode ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;;; In this section: ;;;; ;;;; * The function ess-display-help-on-object ;;;; * The major mode ess-help-mode ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (defun ess-help-bogus-buffer-p (buffer &optional nr-first return-match debug) "Return non-nil if BUFFER looks like a bogus ESS help buffer. Return pair of (match-beg. match-end) when optional RETURN-MATCH is non-nil. Utility used in \\[ess-display-help-on-object]." (let* ((searching nil) (buffer-ok (bufferp buffer)) (res (or (not buffer-ok) (save-excursion;; ask for new buffer if old one looks bogus .. (set-buffer buffer) (if debug (ess-write-to-dribble-buffer (format "(ess-help-bogus-buffer-p %s)" (buffer-name)))) (let ((PM (point-min))) (or ;; evaluate up to first non-nil (or end): (< (- (point-max) PM) 80); buffer less than 80 chars (not (setq searching t)) (not (setq case-fold-search t)) ;; search in first nr-first (default 120) chars only (and nil (if (not nr-first) (setq nr-first 120))) (progn (goto-char PM) ;; R: (re-search-forward "Error in help" nr-first t)) (progn (goto-char PM) ;; R with help.try.all.packages = TRUE: (re-search-forward "topic \`.*\' is not in any loaded" 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)) ))) ))) (if debug (ess-write-to-dribble-buffer (format " |--> %s [searching %s]\n" res searching))) (if (and res return-match searching) (list (match-beginning 0) (match-end 0)) ;; else res))) ;;*;; Access function for displaying help (defun ess-display-help-on-object (object) "Display documentation for OBJECT in another window. If prefix arg is given, forces a query of the ESS process for the help file. Otherwise just pops to an existing buffer if it exists. Uses the variable `inferior-ess-help-command' for the actual help command." (interactive (ess-find-help-file "Help on: ")) (let* ((hb-name (concat "*help[" ess-current-process-name "](" object ")*")) (old-hb-p (get-buffer hb-name)) (curr-win-mode major-mode) (tbuffer (get-buffer-create hb-name)) (curr-help-command inferior-ess-help-command) ;;-- pass the buffer-local 'ess-help-sec-..' to the ess-help buffer: (curr-help-sec-regex ess-help-sec-regex) (curr-help-sec-keys-alist ess-help-sec-keys-alist) (curr-help-syntax-table (syntax-table)) (alist ess-local-customize-alist)) (set-buffer tbuffer) (ess-setq-vars-local (eval alist)) (setq ess-help-sec-regex curr-help-sec-regex) (setq ess-help-sec-keys-alist curr-help-sec-keys-alist) ;; see above, do same for inferior-ess-help-command... (i.e. remove ;; hack, restore old code :-). (if (or (not old-hb-p) current-prefix-arg (ess-help-bogus-buffer-p old-hb-p nil nil 'debug) ) ;; Ask the corresponding ESS process for the help file: (progn (if buffer-read-only (setq buffer-read-only nil)) (delete-region (point-min) (point-max)) (ess-help-mode) (setq ess-local-process-name ess-current-process-name) (ess-command (format curr-help-command object) tbuffer) ;; was inferior-ess-help-command (ess-help-underline) ;; Stata is clean, so we get a big BARF from this. (if (not (string= ess-language "STA")) (ess-nuke-help-bs)) (goto-char (point-min)))) (save-excursion (let ((PM (point-min)) (nodocs (ess-help-bogus-buffer-p (current-buffer) nil 'give-match)) ) (goto-char PM) (if (and nodocs ess-help-kill-bogus-buffers) (progn (if (not (listp nodocs)) (setq nodocs (list PM (point-max)))) (ess-write-to-dribble-buffer (format "(ess-help: error-buffer '%s' nodocs (%d %d)\n" (buffer-name) (car nodocs) (cadr nodocs))) ;; Avoid using 'message here -- may be %'s in string ;;(princ (buffer-substring (car nodocs) (cadr nodocs)) t) ;; MM [3/2000]: why avoid? Yes, I *do* want message: (message "%s" (buffer-substring (car nodocs) (cadr nodocs))) ;; ^^^ fixme : remove new lines from the above {and abbrev.} (ding) (kill-buffer tbuffer)) ;; else : show it ;;dbg (ess-write-to-dribble-buffer ;;dbg (format "(ess-help '%s' before switch-to..\n" hb-name) (let ((special-display-regexps (if ess-help-own-frame '(".") nil)) (special-display-frame-alist ess-help-frame-alist) (special-display-function (if (eq ess-help-own-frame 'one) 'ess-help-own-frame special-display-function))) (if (eq curr-win-mode 'ess-help-mode) (if ess-help-own-frame (pop-to-buffer tbuffer) (switch-to-buffer tbuffer)) (ess-display-temp-buffer tbuffer))) (if curr-help-syntax-table (set-syntax-table curr-help-syntax-table)) (set-buffer-modified-p 'nil) (toggle-read-only t)))))) (defvar ess-help-frame nil "Stores the frame used for displaying R help buffers.") (defun ess-help-own-frame (buffer &rest ignore) "Put all ESS help buffers into `ess-help-frame'." ;; SJE: Code adapted from Kevin Rodgers. (if (frame-live-p ess-help-frame) (progn (or (frame-visible-p ess-help-frame) (make-frame-visible ess-help-frame)) (raise-frame ess-help-frame) (select-frame ess-help-frame) (switch-to-buffer buffer) (selected-window)) ;; else (let ((window (special-display-popup-frame buffer))) (set-window-dedicated-p window nil) (setq ess-help-frame (window-frame window)) window))) ;;; THIS WORKS! ;;(require 'w3) ;(defun ess-display-w3-help-on-object-other-window (object) ; "Display R-documentation for OBJECT using W3" ; (interactive "s Help on :") ; (let* ((ess-help-url (concat ess-help-w3-url-prefix ; ess-help-w3-url-funs ; object ; ".html"))) ;;(w3-fetch-other-window ess-help-url) ; )) ;;*;; Major mode definition (defvar ess-help-sec-map nil "Sub-keymap for ESS help mode.") (if ess-help-sec-map nil (setq ess-help-sec-map (make-sparse-keymap)) (mapcar '(lambda (key) (define-key ess-help-sec-map (char-to-string key) 'ess-skip-to-help-section)) (mapcar 'car ess-help-sec-keys-alist)) (define-key ess-help-sec-map "?" 'ess-describe-sec-map) (define-key ess-help-sec-map ">" 'end-of-buffer) (define-key ess-help-sec-map "<" 'beginning-of-buffer) ) (defvar ess-help-mode-map nil "Keymap for ESS help mode.") (if ess-help-mode-map nil (setq ess-help-mode-map (make-keymap)); Full keymap, in order to (suppress-keymap ess-help-mode-map) ; suppress all usual "printing" characters (define-key ess-help-mode-map " " 'scroll-up) (define-key ess-help-mode-map "b" 'scroll-down) (define-key ess-help-mode-map "q" 'ess-switch-to-end-of-ESS) (define-key ess-help-mode-map "\C-m" 'next-line) (define-key ess-help-mode-map "\177" 'scroll-down) ; DEL (define-key ess-help-mode-map "s" ess-help-sec-map) (define-key ess-help-mode-map "h" 'ess-display-help-on-object) ;; TODO: `electric mouse-2' ;; (define-key ess-help-mode-map [mouse-2] 'ess-display-help-on-object) (define-key ess-help-mode-map "l" 'ess-eval-line-and-step) (define-key ess-help-mode-map "r" 'ess-eval-region-and-go) (define-key ess-help-mode-map "n" 'ess-skip-to-next-section) (define-key ess-help-mode-map "p" 'ess-skip-to-previous-section) (define-key ess-help-mode-map "/" 'isearch-forward) (define-key ess-help-mode-map ">" 'end-of-buffer) (define-key ess-help-mode-map "<" 'beginning-of-buffer) (define-key ess-help-mode-map "x" 'ess-kill-buffer-and-go) (define-key ess-help-mode-map "k" 'kill-buffer) (define-key ess-help-mode-map "?" 'ess-describe-help-mode) ;;-- those should be "inherited" from ess-mode-map : (define-key ess-help-mode-map "\C-c\C-s" 'ess-switch-process) (define-key ess-help-mode-map "\C-c\C-r" 'ess-eval-region) (define-key ess-help-mode-map "\C-c\M-r" 'ess-eval-region-and-go) (define-key ess-help-mode-map "\C-c\C-f" 'ess-eval-function) (define-key ess-help-mode-map "\M-\C-x" 'ess-eval-function) (define-key ess-help-mode-map "\C-c\M-f" 'ess-eval-function-and-go) (define-key ess-help-mode-map "\C-c\C-j" 'ess-eval-line) (define-key ess-help-mode-map "\C-c\M-j" 'ess-eval-line-and-go) (define-key ess-help-mode-map "\M-\C-a" 'ess-beginning-of-function) (define-key ess-help-mode-map "\M-\C-e" 'ess-end-of-function) (define-key ess-help-mode-map "\C-c\C-y" 'ess-switch-to-ESS) (define-key ess-help-mode-map "\C-c\C-z" 'ess-switch-to-end-of-ESS) (define-key ess-help-mode-map "\C-c\C-l" 'ess-load-file) (define-key ess-help-mode-map "\C-c\C-v" 'ess-display-help-on-object) (define-key ess-help-mode-map "\C-c\C-k" 'ess-request-a-process)) ;; One reason for the following menu is to the user about key strokes (defvar ess-help-mode-menu (list "ESS-help" ["Next Section" ess-skip-to-next-section t] ["Previous Section" ess-skip-to-previous-section t] ["Search Forwards" isearch-forward 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] "-" ["Eval Line" ess-eval-line-and-step t] ["Eval Region & Go" ess-eval-region-and-go t] ["Switch to ESS Process" ess-switch-to-ESS t] "-" ["Describe ESS-help Mode" ess-describe-help-mode t] "-" ["Kill Buffer" kill-buffer t] ["Kill Buffer & Go" ess-kill-buffer-and-go t] ["Back to end of ESS Pr." ess-switch-to-end-of-ESS t] ) "Menu used in ess-help mode.") (defun ess-help-mode () ;;; Largely ripped from more-mode.el, ;;; originally by Wolfgang Rupprecht wolfgang@mgm.mit.edu "Mode for viewing ESS help files. Use SPC and DEL to page back and forth through the file. Use `n' and `p' to move to next and previous section, `s' to jump to a particular section; `s ?' for help. Use `q' to return to your ESS session; `x' to kill this buffer first. The usual commands for evaluating ESS source are available. Other keybindings are as follows: \\{ess-help-mode-map}" (interactive) (setq major-mode 'ess-help-mode) (setq mode-name "ESS Help") (use-local-map ess-help-mode-map) (make-local-variable 'ess-local-process-name) ;;; Keep out of the code. (make-local-variable 'indent-tabs-mode) (setq indent-tabs-mode nil) (require 'easymenu) (easy-menu-define ess-help-mode-menu-map ess-help-mode-map "Menu keymap for ess-help mode." ess-help-mode-menu) (easy-menu-add ess-help-mode-menu-map ess-help-mode-map) (run-hooks 'ess-help-mode-hook)) ;;*;; User commands defined in ESS help mode (defun ess-skip-to-help-section nil "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)) (goto-char (point-min)) (let ((the-sec (cdr (assoc last-command-char ess-help-sec-keys-alist)))) (if (not the-sec) (error "Invalid section key: %c" last-command-char) (if (re-search-forward (concat "^" the-sec) nil t) nil (message "No %s section in this help. Sorry." the-sec) (goto-char old-point)))))) (defun ess-skip-to-next-section nil "Jump to next section in ESS help buffer." (interactive) (let ((case-fold-search nil)) (if (re-search-forward ess-help-sec-regex nil 'no-error) nil (message "No more sections.")))) (defun ess-skip-to-previous-section nil "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) nil (message "No previous section.")))) (defun ess-describe-help-mode nil "Display help for `ess-mode'." (interactive) (describe-function 'ess-help-mode)) (defun ess-kill-buffer-and-go nil "Kill the current buffer and switch back to the ESS process." (interactive) (kill-buffer (current-buffer)) (ess-switch-to-ESS nil)) (defun ess-describe-sec-map nil "Display help for the `s' key." (interactive) (describe-function 'ess-skip-to-help-section) (save-excursion (set-buffer "*Help*") (toggle-read-only nil) (goto-char (point-max)) (insert "\n\nCurrently defined keys are: Keystroke Section --------- -------\n") (mapcar '(lambda (cs) (insert " " (car cs) " " (cdr cs) "\n")) ess-help-sec-keys-alist) (insert "\nFull list of key definitions:\n" (substitute-command-keys "\\{ess-help-sec-map}")))) (defun ess-read-helpobj-name-default (olist) ;;; Returns the object name at point, or else the name of the ;;; function call point is in if that has a help file. A name has a ;;; help file if it is a member of olist. (or (car (assoc (ess-read-object-name-default) olist)) (condition-case () (save-excursion (save-restriction (narrow-to-region (max (point-min) (- (point) 1000)) (point-max)) (backward-up-list 1) (backward-char 1) (car (assoc (ess-read-object-name-default) olist)))) (error nil)))) (defun ess-find-help-file (p-string) "Find help, prompting for P-STRING. Note that we can't search SAS, Stata or XLispStat for additional information." (ess-make-buffer-current) (if (not (or (string-match "XLS" ess-language) (string-match "STA" ess-language) (string-match "SAS" ess-language))) (let* ((help-files-list (or (ess-get-help-files-list) (mapcar 'list (ess-get-object-list ess-current-process-name)))) (default (ess-read-helpobj-name-default help-files-list)) (prompt-string (if default (format "%s(default %s) " p-string default) p-string)) (spec (completing-read prompt-string help-files-list))) (list (cond ((string= spec "") default) (t spec)))) (let* ((spec (read-string p-string))) (list spec)))) ;;*;; Utility functions (defun ess-get-help-files-list () "Return a list of files which have help available." (mapcar 'list (apply 'append (mapcar '(lambda (dirname) (if (file-directory-p dirname) (directory-files dirname))) (mapcar '(lambda (str) (concat str "/.Help")) (ess-search-list)))))) (defun ess-nuke-help-bs () (interactive "*") ;;; 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))))))) ;; 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) ;;(other-window 1) (Info-goto-node (concat "(ess)" node))) ; Bug Reporting (defun ess-submit-bug-report () "Submit a bug report on the ess-mode package." (interactive) (require 'ess-mode) (require 'reporter) (let ((reporter-prompt-for-summary-p 't)) (reporter-submit-bug-report "ess-bugs@stat.math.ethz.ch" (concat "ess-mode " ess-version) (list 'ess-language 'ess-dialect 'ess-ask-for-ess-directory 'ess-ask-about-transfile 'ess-directory 'ess-keep-dump-files 'ess-source-directory) nil (lambda () (goto-char (point-max)) (insert-buffer "*ESS*"))))) ;;; Provide (provide 'ess-help) ; Local variables section ;;; This file is automatically placed in Outline minor mode. ;;; The file is structured as follows: ;;; Chapters: ^L ; ;;; Sections: ;;*;; ;;; Subsections: ;;;*;;; ;;; Components: defuns, defvars, defconsts ;;; Random code beginning with a ;;;;* comment ;;; Local variables: ;;; mode: emacs-lisp ;;; mode: outline-minor ;;; outline-regexp: "\^L\\|\\`;\\|;;\\*\\|;;;\\*\\|(def[cvu]\\|(setq\\|;;;;\\*" ;;; End: ;;; ess-help.el ends here