Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
652c4da
Hover works with update
andycraig Nov 6, 2019
9835e46
Attach active command switches session
andycraig Nov 12, 2019
b4976d5
Detects changes to plot
andycraig Nov 14, 2019
19e985a
First implementation of showWebView
renkun-ken Nov 16, 2019
f4d470d
Not change pid on webview response
renkun-ken Nov 18, 2019
e432dc5
Use vscode.open to open plot file on update
renkun-ken Nov 19, 2019
fd43c8d
Markdown hover text
renkun-ken Nov 24, 2019
a31db47
Update view column
renkun-ken Nov 24, 2019
963a282
Update webview options
renkun-ken Nov 24, 2019
8c4e7fe
Use console logging
renkun-ken Nov 24, 2019
06e468a
start log watcher on activation
renkun-ken Nov 25, 2019
2108a17
Add status bar item
renkun-ken Nov 25, 2019
8b528f0
Update status bar
renkun-ken Nov 25, 2019
ad8bcf4
Add session init R script
renkun-ken Dec 1, 2019
7aabe6a
Add opt-in r.sessionWatcher option
renkun-ken Dec 1, 2019
de6037c
Implement deploySessionWatcher
renkun-ken Dec 1, 2019
d972c47
Read file in async method
renkun-ken Dec 1, 2019
ab12e4b
Refine updateSessionWatcher
renkun-ken Dec 1, 2019
0757656
Remove unused data output init.R
renkun-ken Dec 1, 2019
a8501f3
Add plot hook in session init.R to support ggplot2
renkun-ken Dec 2, 2019
541253b
Remove session files on terminal close
renkun-ken Dec 2, 2019
4114754
Add rebind to init.R
renkun-ken Dec 6, 2019
10e5be6
Add time stamp in respond
renkun-ken Dec 6, 2019
e96f489
Fix typo in init.R
renkun-ken Dec 6, 2019
02a2c23
Implement showDataView
renkun-ken Dec 6, 2019
1bb4d57
Force color in showWebView
renkun-ken Dec 6, 2019
ca58601
Update table class
renkun-ken Dec 6, 2019
cc22e16
Refine table font size
renkun-ken Dec 6, 2019
dc8b078
Include dataview resources
renkun-ken Dec 6, 2019
b63151f
Support View matrix
renkun-ken Dec 7, 2019
e85253f
Not rely on tempdir(check=TRUE) which requires R >= 3.5.0
renkun-ken Dec 9, 2019
3b01012
Support browser command
renkun-ken Dec 11, 2019
b097711
Add portMapping to WebView created in showBrowser
renkun-ken Dec 12, 2019
73f3078
Change name and title of browser WebView
renkun-ken Dec 12, 2019
811cdd9
Change WebView title of browser
renkun-ken Dec 12, 2019
6c8fbe1
Use workspaceFolders instead of deprecated rootPath
renkun-ken Dec 13, 2019
b3150bf
Use WebView.asWebviewUri
renkun-ken Dec 13, 2019
cb85e62
Use WebView.asWebviewUri
renkun-ken Dec 13, 2019
42eeb97
Add R session watcher section to README.md
renkun-ken Dec 13, 2019
4637935
Update README.md
renkun-ken Dec 14, 2019
9afee39
Fix for tslint
andycraig Dec 14, 2019
26e36c2
Fix error message
andycraig Dec 14, 2019
176896d
Use json for View(data.frame)
renkun-ken Dec 15, 2019
690a5b8
Refine table_to_json in init.R
renkun-ken Dec 15, 2019
b8b607b
Check windows in source script
renkun-ken Dec 15, 2019
3e825de
Check if init.R already sourced
renkun-ken Dec 15, 2019
b5c0e88
Use DataTables JS sourced data for View(data.frame)
renkun-ken Dec 16, 2019
7821b12
Refine showDataView
renkun-ken Dec 17, 2019
18019f5
remove outside files
Ikuyadeu Dec 17, 2019
0f23089
Use webpack to copy resources to dist
renkun-ken Dec 17, 2019
f1d2f05
Remove resources folder as no longer needed
renkun-ken Dec 17, 2019
1279f88
Fix bootstrap dependency
renkun-ken Dec 17, 2019
307a8e1
Update README.md
renkun-ken Dec 17, 2019
0114f66
Use column.type to fix ordering in View
renkun-ken Dec 18, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/ISSUE_TEMPLATE/bug_report.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ Yes / No

// Use bracketed paste mode
"r.bracketedPaste": false,

// Enable R session watcher (experimental)
"r.sessionWatcher": false,
```

**Expected behavior**
Expand Down
3 changes: 3 additions & 0 deletions ISSUE_TEMPLATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,7 @@ Yes / No

// Use bracketed paste mode
"r.bracketedPaste": false,

// Enable R session watcher (experimental)
"r.sessionWatcher": false,
```
184 changes: 184 additions & 0 deletions R/init.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
if (interactive() &&
is.null(getOption("vscodeR")) &&
!identical(Sys.getenv("RSTUDIO"), "1")) {
local({
pid <- Sys.getpid()
tempdir <- tempdir()
dir <- normalizePath(file.path(".vscode", "vscode-R"), mustWork = FALSE)
dir_session <- file.path(dir, pid)
if (dir.create(dir_session, showWarnings = FALSE, recursive = TRUE) || dir.exists(dir_session)) {
reg.finalizer(.GlobalEnv, function(e) {
unlink(dir_session, recursive = TRUE, force = TRUE)
}, onexit = TRUE)

response_file <- file.path(dir, "response.log")
globalenv_file <- file.path(dir_session, "globalenv.json")
plot_file <- file.path(dir_session, "plot.png")
plot_updated <- FALSE

options(vscodeR = environment())
options(device = function(...) {
pdf(NULL, bg = "white")
dev.control(displaylist = "enable")
})
setHook("plot.new", function(...) {
plot_updated <<- TRUE
})
setHook("grid.newpage", function(...) {
plot_updated <<- TRUE
})

options(browser = function(url, ...) {
respond("browser", url = url)
})
options(viewer = function(url, ...) {
respond("webview", file = url)
})
options(page_viewer = function(url, ...) {
respond("webview", file = url)
})

respond <- function(command, ...) {
json <- jsonlite::toJSON(list(
time = Sys.time(),
pid = pid,
command = command,
...
), auto_unbox = TRUE)
cat(json, "\n", file = response_file, append = TRUE)
}

update <- function(...) {
objs <- eapply(.GlobalEnv, function(obj) {
list(
class = class(obj),
type = typeof(obj),
length = length(obj),
str = trimws(utils::capture.output(str(obj, max.level = 0, give.attr = FALSE)))
)
}, all.names = FALSE, USE.NAMES = TRUE)
jsonlite::write_json(objs, globalenv_file, auto_unbox = TRUE, pretty = TRUE)
if (plot_updated && dev.cur() == 2L) {
plot_updated <<- FALSE
record <- recordPlot()
if (length(record[[1]])) {
png(plot_file)
on.exit(dev.off())
replayPlot(record)
}
}
TRUE
}

attach <- function() {
respond("attach")
}

dataview_data_type <- function(x) {
if (is.numeric(x)) {
if (is.null(attr(x, "class"))) {
"num"
} else {
"num-fmt"
}
} else if (inherits(x, "Date")) {
"date"
} else {
"string"
}
}

dataview_table <- function(data) {
if (is.data.frame(data)) {
colnames <- colnames(data)
if (is.null(colnames)) {
colnames <- sprintf("(X%d)", seq_len(ncol(data)))
} else {
colnames <- trimws(colnames)
}
if (.row_names_info(data) > 0L) {
rownames <- rownames(data)
rownames(data) <- NULL
data <- cbind(rownames, data, stringsAsFactors = FALSE)
colnames <- c(" ", colnames)
}
types <- vapply(data, dataview_data_type,
character(1L), USE.NAMES = FALSE)
data <- vapply(data, function(x) {
trimws(format(x))
}, character(nrow(data)), USE.NAMES = FALSE)
} else if (is.matrix(data)) {
if (is.factor(data)) {
data <- format(data)
}
types <- rep(dataview_data_type(data), ncol(data))
colnames <- colnames(data)
colnames(data) <- NULL
if (is.null(colnames)) {
colnames <- sprintf("(X%d)", seq_len(ncol(data)))
} else {
colnames <- trimws(colnames)
}
rownames <- rownames(data)
rownames(data) <- NULL
data <- trimws(format(data))
if (!is.null(rownames)) {
types <- c("string", types)
colnames <- c(" ", colnames)
data <- cbind(` ` = trimws(rownames), data)
}
} else {
stop("data must be data.frame or matrix")
}
columns <- .mapply(function(title, type) {
class <- if (type == "string") "text-left" else "text-right"
list(title = jsonlite::unbox(title),
className = jsonlite::unbox(class),
type = jsonlite::unbox(type))
}, list(colnames, types), NULL)
list(columns = columns, data = data)
}

dataview <- function(x, title) {
if (missing(title)) {
title <- deparse(substitute(x))[[1]]
}
if (is.data.frame(x) || is.matrix(x)) {
data <- dataview_table(x)
file <- tempfile(tmpdir = tempdir, fileext = ".json")
jsonlite::write_json(data, file, matrix = "rowmajor")
respond("dataview", source = "table", type = "json",
title = title, file = file)
} else if (is.list(x)) {
file <- tempfile(tmpdir = tempdir, fileext = ".json")
jsonlite::write_json(x, file, auto_unbox = TRUE)
respond("dataview", source = "list", type = "json",
title = title, file = file)
} else {
stop("Unsupported object class")
}
}

rebind <- function(sym, value, ns) {
ns <- getNamespace(ns)
unlockBinding(sym, ns)
on.exit(lockBinding(sym, ns))
assign(sym, value, envir = ns)
}

rebind(".External.graphics", function(...) {
plot_updated <<- TRUE
.prim <- .Primitive(".External.graphics")
.prim(...)
}, "base")
rebind("View", dataview, "utils")

update()
addTaskCallback(update, name = "vscode-R")
lockEnvironment(environment(), bindings = TRUE)
unlockBinding("plot_updated", environment())
attach()
}
invisible()
})
}
40 changes: 39 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,12 +57,50 @@ This extension contributes the following settings:
* `r.source.focus`: Keeping focus when running (editor or terminal)
* `r.alwaysUseActiveTerminal`: Use active terminal for all commands, rather than creating a new R terminal
* `r.bracketedPaste`: For consoles supporting bracketed paste mode (such as Radian)
* `r.sessionWatcher`: Enable R session watcher (experimental)

* Language server(developing [here](https://github.com/REditorSupport/languageserver))

## R Session Watcher (Experimental)

*This experimental feature is still under development and the behavior
**may change without notice**. Please file an issue [here](https://github.com/Ikuyadeu/vscode-R/issues) if you experience problems or have any suggestions.*

An opt-in experimental R session watcher is implemented to support the following features:

* Watch any R session
* Show symbol value in hover
* `View()` data frames and list objects
* Show plot output on update
* Show htmlwidgets and shiny apps

To enable this feature, turn on `r.sessionWatcher` and append the following code to your `.Rprofile` (in your home directory):

```r
source(file.path(Sys.getenv(if (.Platform$OS.type == "windows") "HOMEPATH" else "HOME"), ".vscode-R", "init.R"))
```

This script writes the metadata of symbols in the global environment and plot file to `${workspaceFolder}/.vscode/vscode-R/PID` where `PID` is the R process ID. It also captures user input and append command lines to `${workspaceFolder}/.vscode/vscode-R/response.log`, which enables the communication between vscode-R and a live R sesson.

Each time the extension is activated, the latest session watcher script (`init.R`) will be deployed to `~/.vscode-R/init.R`.

R sessions started from the workspace root folder will be automatically attached. The session watcher is designed to work in a wide range of scenarios:

* Official R terminal or `radian` console
* R session started by vscode-R or user
* R session in a `tmux` or `screen` window
* Switch between multiple running R sessions
* [Remote Development](https://code.visualstudio.com/docs/remote/remote-overview)

The status bar item shows the process id of the attached R session. Click the status bar item and it will
attach to currently active session.

![Attached R process](./images/RStatusBarItem.png)

![R session watcher](https://user-images.githubusercontent.com/4662568/70815935-65391480-1e09-11ea-9ad6-7ebbebf9a9c8.gif)

## TODO

* Output Plot
* Debug

## CONTRIBUTING
Expand Down
Binary file added images/RStatusBarItem.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading