Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
19 changes: 16 additions & 3 deletions sess/R/handlers.R
Original file line number Diff line number Diff line change
Expand Up @@ -261,12 +261,25 @@ dataview_columns <- function(state) {
}, list(state$headers, state$types, seq_along(state$headers), state$columns), NULL)
}

dataview_register <- function(data) {
dataview_new_id <- function() {
repeat {
ts <- gsub("[^0-9]", "", format(Sys.time(), "%Y%m%d%H%M%OS6"), perl = TRUE)
view_id <- sprintf("dv_%s_%06d", ts, sample.int(999999L, 1L))
if (is.null(.sess_env$dataviews) || is.null(.sess_env$dataviews[[view_id]])) {
return(view_id)
}
}
Comment on lines +265 to +271
}

dataview_register <- function(data, view_id = NULL) {
if (is.null(.sess_env$dataviews)) {
.sess_env$dataviews <- list()
}
ts <- gsub("[^0-9]", "", format(Sys.time(), "%Y%m%d%H%M%OS6"), perl = TRUE)
view_id <- sprintf("dv_%s_%06d", ts, sample.int(999999L, 1L))

if (is.null(view_id)) {
view_id <- dataview_new_id()
}

state <- dataview_to_state(data)
.sess_env$dataviews[[view_id]] <- state
list(
Comment on lines +274 to 285
Expand Down
23 changes: 20 additions & 3 deletions sess/R/hooks.R
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
#' @export
register_hooks <- function(use_rstudioapi = TRUE, use_httpgd = TRUE) {
# 1. Override View() to serve table data via paged RPC.
if (is.null(.sess_env$dataview_registry)) {
.sess_env$dataview_registry <- new.env(parent = emptyenv())
}

show_dataview <- function(x, title = deparse(substitute(x))) {
# make sure title is computed.
force(title)
Expand All @@ -14,14 +18,27 @@ register_hooks <- function(use_rstudioapi = TRUE, use_httpgd = TRUE) {
}

if (is.data.frame(x) || is.matrix(x)) {
registration <- dataview_register(x)
title_key <- paste(as.character(title), collapse = "\n")
dataview_registry <- .sess_env$dataview_registry
has_view_id <- nzchar(title_key) &&
exists(title_key, envir = dataview_registry, inherits = FALSE)
view_id <- if (has_view_id) {
get(title_key, envir = dataview_registry, inherits = FALSE)
} else {
id <- dataview_new_id()
if (nzchar(title_key)) {
assign(title_key, id, envir = dataview_registry)
}
id
}

registration <- dataview_register(x, view_id = view_id)

notify_client("dataview", list(
title = title,
source = "table",
type = "json",
view_id = registration$view_id,
total_rows = registration$total_rows
view_id = registration$view_id
))
} else if (is.list(x)) {
file_path <- tempfile(tmpdir = .sess_env$tempdir, fileext = ".json")
Expand Down
3 changes: 3 additions & 0 deletions sess/R/server.R
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ connect <- function(pipe_path = NULL, use_rstudioapi = TRUE, use_httpgd = TRUE)
.sess_env$pending_responses <- list()
.sess_env$read_buffer <- ""
.sess_env$dataviews <- list()
if (is.null(.sess_env$dataview_registry)) {
.sess_env$dataview_registry <- new.env(parent = emptyenv())
}

.sess_env$tempdir <- file.path(tempdir(), "sess")
dir.create(.sess_env$tempdir, showWarnings = FALSE, recursive = TRUE)
Expand Down
42 changes: 23 additions & 19 deletions src/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,8 @@ interface DataViewRequestMessage {
filterModel?: Record<string, unknown>;
}

const dynamicDataViewPanels = new WeakSet<vscode.WebviewPanel>();
const dynamicDataViewPanels = new Map<string, vscode.WebviewPanel>();
let dynamicDataViewReloadRevision = 0;

function escapeHtml(text: string): string {
const map: Record<string, string> = {
Expand All @@ -127,13 +128,7 @@ function escapeHtml(text: string): string {
return text.replace(/[&<>"']/g, c => map[c]);
}

function formatDataViewPanelTitle(baseTitle: string, totalRows: number): string {
return `${baseTitle} (rows: ${totalRows.toLocaleString()})`;
}

function attachDynamicDataViewBridge(panel: vscode.WebviewPanel, viewId: string, baseTitle: string): void {
dynamicDataViewPanels.add(panel);

const postResponse = (requestId: number, ok: boolean, result?: unknown, error?: string) => {
void panel.webview.postMessage({
message: 'dataview/response',
Expand All @@ -159,9 +154,7 @@ function attachDynamicDataViewBridge(panel: vscode.WebviewPanel, viewId: string,
if (!result || typeof result.totalRows !== 'number') {
throw new Error('Invalid dataview_init response: missing or invalid totalRows');
}
if (Number.isFinite(result.totalRows)) {
panel.title = formatDataViewPanelTitle(baseTitle, result.totalRows);
}
panel.title = baseTitle;
postResponse(msg.requestId, true, result);
return;
}
Expand All @@ -180,9 +173,7 @@ function attachDynamicDataViewBridge(panel: vscode.WebviewPanel, viewId: string,
if (!result || typeof result.totalRows !== 'number') {
throw new Error('Invalid dataview_page response: missing or invalid totalRows');
}
if (Number.isFinite(result.totalRows)) {
panel.title = formatDataViewPanelTitle(baseTitle, result.totalRows);
}
panel.title = baseTitle;
postResponse(msg.requestId, true, result);
return;
}
Expand All @@ -192,8 +183,9 @@ function attachDynamicDataViewBridge(panel: vscode.WebviewPanel, viewId: string,
method: 'dataview_dispose',
params: { view_id: viewId },
});
// Remove from panels set to prevent duplicate disposal on panel close
dynamicDataViewPanels.delete(panel);
if (dynamicDataViewPanels.get(viewId) === panel) {
dynamicDataViewPanels.delete(viewId);
}
postResponse(msg.requestId, true, true);
return;
}
Expand All @@ -205,10 +197,10 @@ function attachDynamicDataViewBridge(panel: vscode.WebviewPanel, viewId: string,
});

panel.onDidDispose(() => {
if (!dynamicDataViewPanels.has(panel)) {
if (dynamicDataViewPanels.get(viewId) !== panel) {
return;
}
dynamicDataViewPanels.delete(panel);
dynamicDataViewPanels.delete(viewId);
void sessionRequest({
method: 'dataview_dispose',
params: { view_id: viewId },
Expand Down Expand Up @@ -641,6 +633,17 @@ export async function showDataView(source: string, type: string, title: string,
console.info(`[showDataView] source: ${source}, type: ${type}, title: ${title}, file: ${file}, viewer: ${viewer}, viewId: ${String(viewId ?? '')}`);

if (source === 'table') {
if (viewId) {
const existing = dynamicDataViewPanels.get(viewId);
if (existing) {
existing.title = title;
existing.reveal(ViewColumn[viewer as keyof typeof ViewColumn], true);
const content = await getTableHtml(existing.webview, undefined, title);
existing.webview.html = `${content}\n<!-- dataview-reload:${++dynamicDataViewReloadRevision} -->`;
return;
}
}

const panel = window.createWebviewPanel('dataview', title,
{
preserveFocus: true,
Expand All @@ -652,12 +655,13 @@ export async function showDataView(source: string, type: string, title: string,
retainContextWhenHidden: true,
localResourceRoots: [Uri.file(resDir)],
});
const content = await getTableHtml(panel.webview, file || undefined, title);
panel.iconPath = new UriIcon('open-preview');
panel.webview.html = content;
if (viewId) {
dynamicDataViewPanels.set(viewId, panel);
attachDynamicDataViewBridge(panel, viewId, title);
}
const content = await getTableHtml(panel.webview, file || undefined, title);
panel.webview.html = content;
} else if (source === 'list') {
const panel = window.createWebviewPanel('dataview', title,
{
Expand Down
Loading