Skip to content

Commit 20f32fe

Browse files
Fred-Wurenkun-ken
andauthored
feat(data viewer): Allow reusing data viewer instead of opening a new… (#1707)
* feat(data viewer): Allow reusing data viewer instead of opening a new one * Remove rows in data viewer panel title --------- Co-authored-by: Kun Ren <renkun@outlook.com>
1 parent e2974f6 commit 20f32fe

4 files changed

Lines changed: 62 additions & 25 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: 20 additions & 3 deletions
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,14 +18,27 @@ 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,
2139
source = "table",
2240
type = "json",
23-
view_id = registration$view_id,
24-
total_rows = registration$total_rows
41+
view_id = registration$view_id
2542
))
2643
} else if (is.list(x)) {
2744
file_path <- tempfile(tmpdir = .sess_env$tempdir, fileext = ".json")

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: 23 additions & 19 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> = {
@@ -127,13 +128,7 @@ function escapeHtml(text: string): string {
127128
return text.replace(/[&<>"']/g, c => map[c]);
128129
}
129130

130-
function formatDataViewPanelTitle(baseTitle: string, totalRows: number): string {
131-
return `${baseTitle} (rows: ${totalRows.toLocaleString()})`;
132-
}
133-
134131
function attachDynamicDataViewBridge(panel: vscode.WebviewPanel, viewId: string, baseTitle: string): void {
135-
dynamicDataViewPanels.add(panel);
136-
137132
const postResponse = (requestId: number, ok: boolean, result?: unknown, error?: string) => {
138133
void panel.webview.postMessage({
139134
message: 'dataview/response',
@@ -159,9 +154,7 @@ function attachDynamicDataViewBridge(panel: vscode.WebviewPanel, viewId: string,
159154
if (!result || typeof result.totalRows !== 'number') {
160155
throw new Error('Invalid dataview_init response: missing or invalid totalRows');
161156
}
162-
if (Number.isFinite(result.totalRows)) {
163-
panel.title = formatDataViewPanelTitle(baseTitle, result.totalRows);
164-
}
157+
panel.title = baseTitle;
165158
postResponse(msg.requestId, true, result);
166159
return;
167160
}
@@ -180,9 +173,7 @@ function attachDynamicDataViewBridge(panel: vscode.WebviewPanel, viewId: string,
180173
if (!result || typeof result.totalRows !== 'number') {
181174
throw new Error('Invalid dataview_page response: missing or invalid totalRows');
182175
}
183-
if (Number.isFinite(result.totalRows)) {
184-
panel.title = formatDataViewPanelTitle(baseTitle, result.totalRows);
185-
}
176+
panel.title = baseTitle;
186177
postResponse(msg.requestId, true, result);
187178
return;
188179
}
@@ -192,8 +183,9 @@ function attachDynamicDataViewBridge(panel: vscode.WebviewPanel, viewId: string,
192183
method: 'dataview_dispose',
193184
params: { view_id: viewId },
194185
});
195-
// Remove from panels set to prevent duplicate disposal on panel close
196-
dynamicDataViewPanels.delete(panel);
186+
if (dynamicDataViewPanels.get(viewId) === panel) {
187+
dynamicDataViewPanels.delete(viewId);
188+
}
197189
postResponse(msg.requestId, true, true);
198190
return;
199191
}
@@ -205,10 +197,10 @@ function attachDynamicDataViewBridge(panel: vscode.WebviewPanel, viewId: string,
205197
});
206198

207199
panel.onDidDispose(() => {
208-
if (!dynamicDataViewPanels.has(panel)) {
200+
if (dynamicDataViewPanels.get(viewId) !== panel) {
209201
return;
210202
}
211-
dynamicDataViewPanels.delete(panel);
203+
dynamicDataViewPanels.delete(viewId);
212204
void sessionRequest({
213205
method: 'dataview_dispose',
214206
params: { view_id: viewId },
@@ -641,6 +633,17 @@ export async function showDataView(source: string, type: string, title: string,
641633
console.info(`[showDataView] source: ${source}, type: ${type}, title: ${title}, file: ${file}, viewer: ${viewer}, viewId: ${String(viewId ?? '')}`);
642634

643635
if (source === 'table') {
636+
if (viewId) {
637+
const existing = dynamicDataViewPanels.get(viewId);
638+
if (existing) {
639+
existing.title = title;
640+
existing.reveal(ViewColumn[viewer as keyof typeof ViewColumn], true);
641+
const content = await getTableHtml(existing.webview, undefined, title);
642+
existing.webview.html = `${content}\n<!-- dataview-reload:${++dynamicDataViewReloadRevision} -->`;
643+
return;
644+
}
645+
}
646+
644647
const panel = window.createWebviewPanel('dataview', title,
645648
{
646649
preserveFocus: true,
@@ -652,12 +655,13 @@ export async function showDataView(source: string, type: string, title: string,
652655
retainContextWhenHidden: true,
653656
localResourceRoots: [Uri.file(resDir)],
654657
});
655-
const content = await getTableHtml(panel.webview, file || undefined, title);
656658
panel.iconPath = new UriIcon('open-preview');
657-
panel.webview.html = content;
658659
if (viewId) {
660+
dynamicDataViewPanels.set(viewId, panel);
659661
attachDynamicDataViewBridge(panel, viewId, title);
660662
}
663+
const content = await getTableHtml(panel.webview, file || undefined, title);
664+
panel.webview.html = content;
661665
} else if (source === 'list') {
662666
const panel = window.createWebviewPanel('dataview', title,
663667
{

0 commit comments

Comments
 (0)