Skip to content

Commit b79afee

Browse files
committed
Fix data viewer code
1 parent 4f2abff commit b79afee

2 files changed

Lines changed: 59 additions & 17 deletions

File tree

sess/R/handlers.R

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -437,8 +437,7 @@ dataview_apply_sort_model <- function(state, sort_model, row_idx) {
437437
return(row_idx)
438438
}
439439

440-
sort_vectors <- list()
441-
decreasing <- logical(0)
440+
sort_specs <- list()
442441

443442
for (sort_item in sort_model) {
444443
col_id <- as.character(sort_item$colId %||% "")
@@ -448,22 +447,46 @@ dataview_apply_sort_model <- function(state, sort_model, row_idx) {
448447
}
449448
raw_values <- state$columns[[col_pos]][row_idx]
450449
type_hint <- state$types[[col_pos]]
450+
is_desc <- identical(as.character(sort_item$sort), "desc")
451+
451452
if (type_hint %in% c("num", "num-fmt")) {
452-
sort_vectors[[length(sort_vectors) + 1L]] <- suppressWarnings(as.numeric(raw_values))
453+
sort_vec <- suppressWarnings(as.numeric(raw_values))
453454
} else if (type_hint == "date") {
454-
sort_vectors[[length(sort_vectors) + 1L]] <-
455-
suppressWarnings(as.Date(as.character(raw_values)))
455+
sort_vec <- suppressWarnings(as.Date(as.character(raw_values)))
456456
} else {
457-
sort_vectors[[length(sort_vectors) + 1L]] <- tolower(as.character(raw_values))
457+
sort_vec <- tolower(as.character(raw_values))
458458
}
459-
decreasing <- c(decreasing, identical(as.character(sort_item$sort), "desc"))
459+
460+
sort_specs[[length(sort_specs) + 1L]] <- list(
461+
values = sort_vec,
462+
decreasing = is_desc
463+
)
460464
}
461465

462-
if (!length(sort_vectors)) {
466+
if (!length(sort_specs)) {
463467
return(row_idx)
464468
}
465469

466-
ord <- do.call(order, c(sort_vectors, list(na.last = TRUE, decreasing = decreasing)))
470+
# Build arguments for order() with per-column decreasing handling.
471+
# R's order() doesn't support per-column decreasing, so transform values:
472+
# - For numeric: negate for descending
473+
# - For other types: negate rank for descending
474+
order_args <- list()
475+
for (i in seq_along(sort_specs)) {
476+
spec <- sort_specs[[i]]
477+
if (spec$decreasing) {
478+
if (is.numeric(spec$values)) {
479+
order_args[[i]] <- -spec$values
480+
} else {
481+
order_args[[i]] <- -rank(spec$values, na.last = "keep")
482+
}
483+
} else {
484+
order_args[[i]] <- spec$values
485+
}
486+
}
487+
order_args$na.last <- TRUE
488+
489+
ord <- do.call(order, order_args)
467490
row_idx[ord]
468491
}
469492

src/session.ts

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,17 @@ interface DataViewRequestMessage {
116116

117117
const dynamicDataViewPanels = new WeakSet<vscode.WebviewPanel>();
118118

119+
function escapeHtml(text: string): string {
120+
const map: Record<string, string> = {
121+
'&': '&amp;',
122+
'<': '&lt;',
123+
'>': '&gt;',
124+
'"': '&quot;',
125+
'\'': '&#39;',
126+
};
127+
return text.replace(/[&<>"']/g, c => map[c]);
128+
}
129+
119130
function formatDataViewPanelTitle(baseTitle: string, totalRows: number): string {
120131
return `${baseTitle} (rows: ${totalRows.toLocaleString()})`;
121132
}
@@ -144,7 +155,10 @@ function attachDynamicDataViewBridge(panel: vscode.WebviewPanel, viewId: string,
144155
const result = await sessionRequest({
145156
method: 'dataview_init',
146157
params: { view_id: viewId },
147-
}) as DataViewInitResult;
158+
}) as DataViewInitResult | undefined;
159+
if (!result || typeof result.totalRows !== 'number') {
160+
throw new Error('Invalid dataview_init response: missing or invalid totalRows');
161+
}
148162
if (Number.isFinite(result.totalRows)) {
149163
panel.title = formatDataViewPanelTitle(baseTitle, result.totalRows);
150164
}
@@ -162,7 +176,10 @@ function attachDynamicDataViewBridge(panel: vscode.WebviewPanel, viewId: string,
162176
sortModel: Array.isArray(msg.sortModel) ? msg.sortModel : [],
163177
filterModel: msg.filterModel ?? {},
164178
},
165-
}) as DataViewPageResult;
179+
}) as DataViewPageResult | undefined;
180+
if (!result || typeof result.totalRows !== 'number') {
181+
throw new Error('Invalid dataview_page response: missing or invalid totalRows');
182+
}
166183
if (Number.isFinite(result.totalRows)) {
167184
panel.title = formatDataViewPanelTitle(baseTitle, result.totalRows);
168185
}
@@ -175,6 +192,8 @@ function attachDynamicDataViewBridge(panel: vscode.WebviewPanel, viewId: string,
175192
method: 'dataview_dispose',
176193
params: { view_id: viewId },
177194
});
195+
// Remove from panels set to prevent duplicate disposal on panel close
196+
dynamicDataViewPanels.delete(panel);
178197
postResponse(msg.requestId, true, true);
179198
return;
180199
}
@@ -661,7 +680,7 @@ export async function getTableHtml(webview: Webview, file: string | undefined, t
661680
<head>
662681
<meta charset="utf-8">
663682
<meta name="viewport" content="width=device-width, initial-scale=1">
664-
<title>${title}</title>
683+
<title>${escapeHtml(title)}</title>
665684
<style media="only screen">
666685
html, body {
667686
height: 100%;
@@ -1058,9 +1077,9 @@ export async function getTableHtml(webview: Webview, file: string | undefined, t
10581077
enableCellTextSelection: true,
10591078
ensureDomOrder: true,
10601079
tooltipShowDelay: 100,
1061-
onFirstDataRendered: function() {
1062-
if (gridApi) {
1063-
gridApi.columnApi?.autoSizeAllColumns(false);
1080+
onFirstDataRendered: function(params) {
1081+
if (params.columnApi) {
1082+
params.columnApi.autoSizeAllColumns(false);
10641083
}
10651084
updateFetchStatusPosition();
10661085
}
@@ -1124,7 +1143,7 @@ export async function getTableHtml(webview: Webview, file: string | undefined, t
11241143
<head>
11251144
<meta charset="utf-8">
11261145
<meta name="viewport" content="width=device-width, initial-scale=1">
1127-
<title>${title}</title>
1146+
<title>${escapeHtml(title)}</title>
11281147
<style media="only screen">
11291148
html, body {
11301149
height: 100%;
@@ -1326,7 +1345,7 @@ export async function getListHtml(webview: Webview, file: string, title: string)
13261345
<head>
13271346
<meta charset="utf-8" />
13281347
<meta name="viewport" content="width=device-width, initial-scale=1">
1329-
<title>${title}</title>
1348+
<title>${escapeHtml(title)}</title>
13301349
<script src="${String(webview.asWebviewUri(Uri.file(path.join(resDir, 'jquery.min.js'))))}"></script>
13311350
<script src="${String(webview.asWebviewUri(Uri.file(path.join(resDir, 'jquery.json-viewer.js'))))}"></script>
13321351
<link href="${String(webview.asWebviewUri(Uri.file(path.join(resDir, 'jquery.json-viewer.css'))))}" rel="stylesheet">

0 commit comments

Comments
 (0)