|
|
- ;;; ess-rdired.el --- prototype object browser for R, looks like dired mode. -*- lexical-binding: t; -*-
-
- ;; Copyright (C) 2002--2019 A.J. Rossini, Richard M. Heiberger, Martin
- ;; Maechler, Kurt Hornik, Rodney Sparapani, Stephen Eglen, and J. Alexander Branham.
-
- ;; Author: Stephen Eglen <stephen@anc.ed.ac.uk>
- ;; Created: Thu 24 Oct 2002
- ;; Maintainer: ESS-core <ESS-core@r-project.org>
-
- ;; This file is part of ESS
-
- ;; This file is not part of GNU Emacs.
-
- ;; ess-rdired.el 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.
-
- ;; ess-rdired.el 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/
-
- ;; This provides a dired-like buffer for R objects. Instead of
- ;; operating on files, we operate on R objects in the current
- ;; environment. Objects can be viewed, edited, deleted, plotted and
- ;; so on.
-
- ;;; Commentary:
-
- ;; Do "M-x R" to start an R session, then create a few variables:
- ;;
- ;; s <- sin(seq(from=0, to=8*pi, length=100))
- ;; x <- c(1, 4, 9)
- ;; y <- rnorm(20)
- ;; z <- TRUE
-
- ;; Then in Emacs, do "M-x ess-rdired" and you should see the following in
- ;; the buffer *R dired*:
- ;; Name Class Length Size
- ;; s numeric 100 848 bytes
- ;; x numeric 3 80 bytes
- ;; y numeric 20 208 bytes
- ;; z logical 1 56 bytes
-
- ;; Type "?" in the buffer to see the documentation. e.g. when the
- ;; cursor is on the line for `s', type 'p' to plot it, or `v' to view
- ;; its contents in a buffer. Then type 'd' to delete it.
-
- ;; How it works.
-
- ;; Most of the hard work is done by the R routine .rdired.objects(),
- ;; which, when called, produces the list of objects in a tidy format.
- ;; This function is stored within the Lisp variable `ess-rdired-objects'.
-
- ;; Todo - How to select alternative environments? Currently only
- ;; shows objects in the .GlobalEnv? See BrowseEnv() in 1.6.x for way
- ;; of browsing other environments.
-
- ;; Todo - problem with fix -- have to wait for fix() command to return
- ;; before *R* buffer can be used again. This can get stuck, umm. not
- ;; sure what is going wrong here. Maybe add a hook to the temp buffer
- ;; so that when buffer is killed, we send an instruction to R to
- ;; update the value of the variable to the contents of the buffer.
- ;; This way *R* doesn't have to wait.
-
- ;; Todo - small bug in .rdired.objects -- if we have a variable called
- ;; `my.x', its value is replaced by the value of my.x used in the
- ;; sapply() calls within .rdired.objects().
-
- ;;; Code:
-
- (require 'ess-inf)
-
- (eval-when-compile
- (require 'subr-x))
-
- (defvar ess-rdired-objects "local({.rdired.objects <- function(objs) {
- if (length(objs)==0) {
- \"No objects to view!\"
- } else {
- mode <- sapply(objs, function(my.x) {
- eval( parse( text=sprintf('data.class(get(\"%s\"))', my.x))) })
- length <- sapply(objs, function(my.x) {
- eval( parse( text=sprintf('length(get(\"%s\"))', my.x))) })
- size <- sapply(objs, function(my.x) {
- eval( parse( text=sprintf('format(object.size(get(\"%s\")), units=\"b\")', my.x))) })
- d <- data.frame(mode, length, size)
-
- var.names <- row.names(d)
-
- ## If any names contain spaces, we need to quote around them.
- quotes = rep('', length(var.names))
- spaces = grep(' ', var.names)
- if (any(spaces))
- quotes[spaces] <- '\"'
- var.names = paste(quotes, var.names, quotes, sep='')
- row.names(d) <- paste(' ', var.names, sep='')
- d
- }
- }; cat('\n'); print(.rdired.objects(ls(envir = .GlobalEnv)))})\n"
- "Function to call within R to print information on objects.
- The last line of this string should be the instruction to call
- the function which prints the output for rdired.")
-
- (defvar ess-rdired-buffer "*R dired*"
- "Name of buffer for displaying R objects.")
-
- (defvar ess-rdired-auto-update-timer nil
- "The timer object for auto updates.")
-
- (defcustom ess-rdired-auto-update-interval 5
- "Seconds between refreshes of the `ess-rdired' buffer."
- :type '(choice (const nil :tag "No auto updates") (integer :tag "Seconds"))
- :group 'ess-R
- :package-version '(ess . "19.04"))
-
- (defvar ess-rdired-mode-map
- (let ((map (make-sparse-keymap)))
- (define-key map "d" #'ess-rdired-delete)
- (define-key map "x" #'ess-rdired-delete)
- (define-key map "v" #'ess-rdired-view)
- (define-key map "V" #'ess-rdired-View)
- (define-key map "p" #'ess-rdired-plot)
- (define-key map "y" #'ess-rdired-type)
- (define-key map "\C-c\C-s" #'ess-rdired-switch-process)
- (define-key map "\C-c\C-y" #'ess-switch-to-ESS)
- (define-key map "\C-c\C-z" #'ess-switch-to-end-of-ESS)
- map))
-
- (define-derived-mode ess-rdired-mode tabulated-list-mode "Rdired"
- "Major mode for output from `ess-rdired'.
- `ess-rdired' provides a dired-like mode for R objects. It shows the
- list of current objects in the current environment, one-per-line. You
- can then examine these objects, plot them, and so on."
- :group 'ess-R
- (setq mode-name (concat "RDired " ess-local-process-name))
- (setq tabulated-list-format
- `[("Name" 18 t)
- ("Class" 10 t)
- ("Length" 10 ess-rdired--length-predicate)
- ("Size" 10 ess-rdired--size-predicate)])
- (add-hook 'tabulated-list-revert-hook #'ess-rdired-refresh nil t)
- (when (and (not ess-rdired-auto-update-timer)
- ess-rdired-auto-update-interval)
- (setq ess-rdired-auto-update-timer
- (run-at-time t ess-rdired-auto-update-interval #'ess-rdired-refresh)))
- (add-hook 'kill-buffer-hook #'ess-rdired-cancel-auto-update-timer nil t)
- (tabulated-list-init-header))
-
- ;;;###autoload
- (defun ess-rdired ()
- "Show R objects from the global environment in a separate buffer.
- You may interact with these objects, see `ess-rdired-mode' for
- details."
- (interactive)
- (unless (and (string= "R" ess-dialect)
- ess-local-process-name)
- (error "Not in an R buffer with attached process"))
- (let ((proc ess-local-process-name))
- (pop-to-buffer (get-buffer-create ess-rdired-buffer))
- (setq ess-local-process-name proc)
- (ess-rdired-mode)
- (ess-rdired-refresh)))
-
- (defun ess-rdired-refresh ()
- "Refresh the `ess-rdired' buffer."
- (let* ((buff (get-buffer-create ess-rdired-buffer))
- (proc-name (buffer-local-value 'ess-local-process-name buff))
- (proc (get-process proc-name))
- (out-buff (get-buffer-create " *ess-rdired-output*"))
- text)
- (when (and proc-name proc
- (not (process-get proc 'busy)))
- (ess-command ess-rdired-objects out-buff nil nil nil proc)
- (with-current-buffer out-buff
- (goto-char (point-min))
- ;; Delete two lines. One filled with +'s from R's prompt
- ;; printing, the other with the header info from the data.frame
- (delete-region (point-min) (1+ (point-at-eol 2)))
- (setq text (split-string (buffer-string) "\n" t "\n"))
- (erase-buffer))
- (with-current-buffer buff
- (setq tabulated-list-entries
- (mapcar #'ess-rdired--tabulated-list-entries text))
- (let ((entry (tabulated-list-get-id))
- (col (current-column)))
- (tabulated-list-print)
- (while (not (equal entry (tabulated-list-get-id)))
- (forward-line))
- (move-to-column col))))))
-
- (defun ess-rdired-cancel-auto-update-timer ()
- "Cancel the timer `ess-rdired-auto-update-timer'."
- (setq ess-rdired-auto-update-timer
- (cancel-timer ess-rdired-auto-update-timer)))
-
- (defun ess-rdired--tabulated-list-entries (text)
- "Return a value suitable for `tabulated-list-entries' from TEXT."
- (let (name class length size)
- (if (not (string-match-p " +\"" text))
- ;; Normal-world
- (setq text (split-string text " " t)
- name (nth 0 text)
- text (cdr text))
- ;; Else, someone has spaces in their variable names
- (string-match "\"\\([^\"]+\\)" text)
- (setq name (substring (match-string 0 text) 1)
- text (split-string (substring text (1+ (match-end 0))) " " t)))
- (setq class (nth 0 text)
- length (nth 1 text)
- size (nth 2 text))
- (list name
- `[(,name
- help-echo "mouse-2, RET: View this object"
- action ess-rdired-view)
- ,class
- ,length
- ,size])))
-
- (defun ess-rdired-edit ()
- "Edit the object at point."
- (interactive)
- (ess-command (concat "edit(" (tabulated-list-get-id) ")\n")))
-
- (defun ess-rdired-view (&optional _button)
- "View the object at point."
- (interactive)
- (ess-execute (ess-rdired-get (tabulated-list-get-id))
- nil "R view" ))
-
- (defun ess-rdired-get (name)
- "Generate R code to get the value of the variable NAME.
- This is complicated because some variables might have spaces in their names.
- Otherwise, we could just pass the variable name directly to *R*."
- (concat "get(" (ess-rdired-quote name) ")")
- )
-
- (defun ess-rdired-quote (name)
- "Quote NAME if not already quoted."
- (if (equal (substring name 0 1) "\"")
- name
- (concat "\"" name "\"")))
-
- (defun ess-rdired-View ()
- "View the object at point in its own buffer.
- Like `ess-rdired-view', but the object gets its own buffer name."
- (interactive)
- (let ((objname (tabulated-list-get-id)))
- (ess-execute (ess-rdired-get objname)
- nil (concat "R view " objname ))))
-
- (defun ess-rdired-plot ()
- "Plot the object on current line."
- (interactive)
- (let ((objname (tabulated-list-get-id)))
- (ess-eval-linewise (format "plot(%s)" (ess-rdired-get objname)))))
-
- (defun ess-rdired-type ()
- "Run the mode() on command at point."
- (interactive)
- (let ((objname (tabulated-list-get-id))
- ;; create a temp buffer, and then show output in echo area
- (tmpbuf (get-buffer-create "**ess-rdired-mode**")))
- (if objname
- (progn
- (ess-command (concat "mode(" (ess-rdired-get objname) ")\n")
- tmpbuf )
- (set-buffer tmpbuf)
- (message "%s" (concat
- objname ": "
- (buffer-substring (+ 4 (point-min)) (1- (point-max)))))
- (kill-buffer tmpbuf)))))
-
- (defalias 'ess-rdired-expunge #'ess-rdired-delete)
-
- (defun ess-rdired-delete ()
- "Delete the object at point."
- (interactive)
- (let ((objname (tabulated-list-get-id)))
- (when (yes-or-no-p (format "Really delete %s? " objname))
- (ess-eval-linewise (format "rm(%s)" (ess-rdired-quote objname)) nil nil nil t)
- (revert-buffer))))
-
- (defun ess-rdired-switch-process ()
- "Switch to examine different *R* process.
- If you have multiple R processes running, e.g. *R*, *R:2*, *R:3*, you can
- use this command to choose which R process you would like to examine.
- After switching to a new process, the buffer is updated."
- (interactive)
- (ess-switch-process)
- (ess-rdired))
-
- (defun ess-rdired--length-predicate (A B)
- "Enable sorting by length in `ess-rdired' buffers.
- Return t if A's length is < than B's length."
- (let ((lenA (aref (cadr A) 2))
- (lenB (aref (cadr B) 2)))
- (< (string-to-number lenA) (string-to-number lenB))))
-
- (defun ess-rdired--size-predicate (A B)
- "Enable sorting by size in `ess-rdired' buffers.
- Return t if A's size is < than B's size."
- (let ((lenA (aref (cadr A) 3))
- (lenB (aref (cadr B) 3)))
- (< (string-to-number lenA) (string-to-number lenB))))
-
- (define-obsolete-function-alias 'ess-rdired-quit #'quit-window "ESS 19.04")
- (define-obsolete-function-alias 'ess-rdired-next-line #'forward-to-indentation "ESS 19.04")
- (define-obsolete-function-alias 'ess-rdired-previous-line #'backward-to-indentation "ESS 19.04")
- (define-obsolete-function-alias 'ess-rdired-move-to-object #'back-to-indentation "ESS 19.04")
-
- (provide 'ess-rdired)
-
- ;;; ess-rdired.el ends here
|