Skip to content

Commit 21125bc

Browse files
committed
feat(data viewer): Allow reusing data viewer instead of opening a new one
1 parent e2974f6 commit 21125bc

4 files changed

Lines changed: 65 additions & 15 deletions

File tree

sess/R/handlers.R

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -261,12 +261,25 @@ dataview_columns <- function(state) {
261261
}, list(state$headers, state$types, seq_along(state$headers), state$columns), NULL)
262262
}
263263

264-
dataview_register <- function(data) {
264+
dataview_new_id <- function() {
265+
repeat {
266+
ts <- gsub("[^0-9]", "", format(Sys.time(), "%Y%m%d%H%M%OS6"), perl = TRUE)
267+
view_id <- sprintf("dv_%s_%06d", ts, sample.int(999999L, 1L))
268+
if (is.null(.sess_env$dataviews) || is.null(.sess_env$dataviews[[view_id]])) {
269+
return(view_id)
270+
}
271+
}
272+
}
273+
274+
dataview_register <- function(data, view_id = NULL) {
265275
if (is.null(.sess_env$dataviews)) {
266276
.sess_env$dataviews <- list()
267277
}
268-
ts <- gsub("[^0-9]", "", format(Sys.time(), "%Y%m%d%H%M%OS6"), perl = TRUE)
269-
view_id <- sprintf("dv_%s_%06d", ts, sample.int(999999L, 1L))
278+
279+
if (is.null(view_id)) {
280+
view_id <- dataview_new_id()
281+
}
282+
270283
state <- dataview_to_state(data)
271284
.sess_env$dataviews[[view_id]] <- state
272285
list(

sess/R/hooks.R

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@
55
#' @export
66
register_hooks <- function(use_rstudioapi = TRUE, use_httpgd = TRUE) {
77
# 1. Override View() to serve table data via paged RPC.
8+
if (is.null(.sess_env$dataview_registry)) {
9+
.sess_env$dataview_registry <- new.env(parent = emptyenv())
10+
}
11+
812
show_dataview <- function(x, title = deparse(substitute(x))) {
913
# make sure title is computed.
1014
force(title)
@@ -14,7 +18,21 @@ register_hooks <- function(use_rstudioapi = TRUE, use_httpgd = TRUE) {
1418
}
1519

1620
if (is.data.frame(x) || is.matrix(x)) {
17-
registration <- dataview_register(x)
21+
title_key <- paste(as.character(title), collapse = "\n")
22+
dataview_registry <- .sess_env$dataview_registry
23+
has_view_id <- nzchar(title_key) &&
24+
exists(title_key, envir = dataview_registry, inherits = FALSE)
25+
view_id <- if (has_view_id) {
26+
get(title_key, envir = dataview_registry, inherits = FALSE)
27+
} else {
28+
id <- dataview_new_id()
29+
if (nzchar(title_key)) {
30+
assign(title_key, id, envir = dataview_registry)
31+
}
32+
id
33+
}
34+
35+
registration <- dataview_register(x, view_id = view_id)
1836

1937
notify_client("dataview", list(
2038
title = title,

sess/R/server.R

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ connect <- function(pipe_path = NULL, use_rstudioapi = TRUE, use_httpgd = TRUE)
1111
.sess_env$pending_responses <- list()
1212
.sess_env$read_buffer <- ""
1313
.sess_env$dataviews <- list()
14+
if (is.null(.sess_env$dataview_registry)) {
15+
.sess_env$dataview_registry <- new.env(parent = emptyenv())
16+
}
1417

1518
.sess_env$tempdir <- file.path(tempdir(), "sess")
1619
dir.create(.sess_env$tempdir, showWarnings = FALSE, recursive = TRUE)

src/session.ts

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,8 @@ interface DataViewRequestMessage {
114114
filterModel?: Record<string, unknown>;
115115
}
116116

117-
const dynamicDataViewPanels = new WeakSet<vscode.WebviewPanel>();
117+
const dynamicDataViewPanels = new Map<string, vscode.WebviewPanel>();
118+
let dynamicDataViewReloadRevision = 0;
118119

119120
function escapeHtml(text: string): string {
120121
const map: Record<string, string> = {
@@ -132,8 +133,6 @@ function formatDataViewPanelTitle(baseTitle: string, totalRows: number): string
132133
}
133134

134135
function attachDynamicDataViewBridge(panel: vscode.WebviewPanel, viewId: string, baseTitle: string): void {
135-
dynamicDataViewPanels.add(panel);
136-
137136
const postResponse = (requestId: number, ok: boolean, result?: unknown, error?: string) => {
138137
void panel.webview.postMessage({
139138
message: 'dataview/response',
@@ -192,8 +191,9 @@ function attachDynamicDataViewBridge(panel: vscode.WebviewPanel, viewId: string,
192191
method: 'dataview_dispose',
193192
params: { view_id: viewId },
194193
});
195-
// Remove from panels set to prevent duplicate disposal on panel close
196-
dynamicDataViewPanels.delete(panel);
194+
if (dynamicDataViewPanels.get(viewId) === panel) {
195+
dynamicDataViewPanels.delete(viewId);
196+
}
197197
postResponse(msg.requestId, true, true);
198198
return;
199199
}
@@ -205,10 +205,10 @@ function attachDynamicDataViewBridge(panel: vscode.WebviewPanel, viewId: string,
205205
});
206206

207207
panel.onDidDispose(() => {
208-
if (!dynamicDataViewPanels.has(panel)) {
208+
if (dynamicDataViewPanels.get(viewId) !== panel) {
209209
return;
210210
}
211-
dynamicDataViewPanels.delete(panel);
211+
dynamicDataViewPanels.delete(viewId);
212212
void sessionRequest({
213213
method: 'dataview_dispose',
214214
params: { view_id: viewId },
@@ -637,11 +637,25 @@ export function openExternalBrowser(): void {
637637
}
638638
}
639639

640-
export async function showDataView(source: string, type: string, title: string, file: string, viewer: string, viewId?: string): Promise<void> {
640+
export async function showDataView(source: string, type: string, title: string, file: string, viewer: string, viewId?: string, totalRows?: number): Promise<void> {
641641
console.info(`[showDataView] source: ${source}, type: ${type}, title: ${title}, file: ${file}, viewer: ${viewer}, viewId: ${String(viewId ?? '')}`);
642+
const panelTitle = totalRows !== undefined && Number.isFinite(totalRows)
643+
? formatDataViewPanelTitle(title, totalRows)
644+
: title;
642645

643646
if (source === 'table') {
644-
const panel = window.createWebviewPanel('dataview', title,
647+
if (viewId) {
648+
const existing = dynamicDataViewPanels.get(viewId);
649+
if (existing) {
650+
existing.title = panelTitle;
651+
existing.reveal(ViewColumn[viewer as keyof typeof ViewColumn], true);
652+
const content = await getTableHtml(existing.webview, undefined, title);
653+
existing.webview.html = `${content}\n<!-- dataview-reload:${++dynamicDataViewReloadRevision} -->`;
654+
return;
655+
}
656+
}
657+
658+
const panel = window.createWebviewPanel('dataview', panelTitle,
645659
{
646660
preserveFocus: true,
647661
viewColumn: ViewColumn[viewer as keyof typeof ViewColumn],
@@ -652,12 +666,13 @@ export async function showDataView(source: string, type: string, title: string,
652666
retainContextWhenHidden: true,
653667
localResourceRoots: [Uri.file(resDir)],
654668
});
655-
const content = await getTableHtml(panel.webview, file || undefined, title);
656669
panel.iconPath = new UriIcon('open-preview');
657-
panel.webview.html = content;
658670
if (viewId) {
671+
dynamicDataViewPanels.set(viewId, panel);
659672
attachDynamicDataViewBridge(panel, viewId, title);
660673
}
674+
const content = await getTableHtml(panel.webview, file || undefined, title);
675+
panel.webview.html = content;
661676
} else if (source === 'list') {
662677
const panel = window.createWebviewPanel('dataview', title,
663678
{
@@ -1559,6 +1574,7 @@ async function handleNotification(message: Record<string, unknown>, socket: IpcS
15591574
String(params.file ?? ''),
15601575
viewer,
15611576
params.view_id ? String(params.view_id) : undefined,
1577+
typeof params.total_rows === 'number' ? params.total_rows : undefined,
15621578
);
15631579
}
15641580
}

0 commit comments

Comments
 (0)