@@ -755,13 +755,160 @@ export async function getTableHtml(webview: Webview, file: string | undefined, t
755755 [class*="vscode"] .text-right {
756756 text-align: right;
757757 }
758+
759+ #gridContainer {
760+ position: relative;
761+ height: 100%;
762+ }
763+
764+ #fetchStatus {
765+ position: absolute;
766+ top: var(--fetch-status-top, 52px);
767+ right: 8px;
768+ z-index: 20;
769+ display: none;
770+ align-items: center;
771+ gap: 8px;
772+ padding: 6px 10px;
773+ border-radius: 4px;
774+ border: 1px solid var(--vscode-panel-border);
775+ background-color: var(--vscode-editorWidget-background);
776+ color: var(--vscode-editorWidget-foreground);
777+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.25);
778+ font-size: 12px;
779+ max-width: min(68vw, 560px);
780+ }
781+
782+ #fetchStatus.visible {
783+ display: flex;
784+ }
785+
786+ #fetchStatus[data-state="warning"] {
787+ border-color: var(--vscode-inputValidation-warningBorder);
788+ }
789+
790+ #fetchStatus[data-state="error"] {
791+ border-color: var(--vscode-inputValidation-errorBorder);
792+ }
793+
794+ #fetchStatusText {
795+ word-break: break-word;
796+ }
797+
798+ #fetchRetryBtn {
799+ display: none;
800+ border: 0;
801+ padding: 3px 8px;
802+ background-color: var(--vscode-button-background);
803+ color: var(--vscode-button-foreground);
804+ cursor: pointer;
805+ white-space: nowrap;
806+ }
807+
808+ #fetchStatus.show-retry #fetchRetryBtn {
809+ display: inline-block;
810+ }
758811 </style>
759812 <script src="${ String ( webview . asWebviewUri ( Uri . file ( path . join ( resDir , 'ag-grid-community.min.noStyle.js' ) ) ) ) } "></script>
760813 <script>
761814 const vscode = typeof acquireVsCodeApi === 'function' ? acquireVsCodeApi() : { postMessage: () => {} };
762815 let requestIdSeq = 1;
763816 const pending = new Map();
764817 let gridApi;
818+ let activeFetches = 0;
819+ let longFetchTimer;
820+ const LONG_FETCH_DELAY_MS = 2000;
821+
822+ function clearLongFetchTimer() {
823+ if (longFetchTimer) {
824+ clearTimeout(longFetchTimer);
825+ longFetchTimer = undefined;
826+ }
827+ }
828+
829+ function setFetchStatus(state, message, showRetry) {
830+ const statusEl = document.querySelector('#fetchStatus');
831+ const textEl = document.querySelector('#fetchStatusText');
832+ if (!statusEl || !textEl) {
833+ return;
834+ }
835+
836+ if (state === 'hidden') {
837+ statusEl.classList.remove('visible', 'show-retry');
838+ statusEl.dataset.state = '';
839+ textEl.textContent = '';
840+ return;
841+ }
842+
843+ statusEl.dataset.state = state;
844+ textEl.textContent = message;
845+ statusEl.classList.add('visible');
846+ statusEl.classList.toggle('show-retry', Boolean(showRetry));
847+ }
848+
849+ function updateFetchStatusPosition() {
850+ const containerEl = document.querySelector('#gridContainer');
851+ if (!containerEl) {
852+ return;
853+ }
854+
855+ let headerHeight = 0;
856+ if (gridApi && typeof gridApi.getSizesForCurrentTheme === 'function') {
857+ const sizes = gridApi.getSizesForCurrentTheme();
858+ if (sizes && Number.isFinite(sizes.headerHeight)) {
859+ headerHeight = Number(sizes.headerHeight);
860+ }
861+ }
862+
863+ if (!headerHeight) {
864+ const headerEl = document.querySelector('#myGrid .ag-header');
865+ if (headerEl) {
866+ headerHeight = headerEl.getBoundingClientRect().height;
867+ }
868+ }
869+
870+ const topOffset = Math.max(8, Math.round(headerHeight) + 8);
871+ containerEl.style.setProperty('--fetch-status-top', String(topOffset) + 'px');
872+ }
873+
874+ function beginFetch(message) {
875+ activeFetches += 1;
876+ if (activeFetches === 1) {
877+ setFetchStatus('loading', message || 'Fetching data...', false);
878+ clearLongFetchTimer();
879+ longFetchTimer = setTimeout(() => {
880+ if (activeFetches > 0) {
881+ setFetchStatus('warning', 'Still waiting for R session response. It may be busy running code.', false);
882+ }
883+ }, LONG_FETCH_DELAY_MS);
884+ }
885+ }
886+
887+ function finishFetch(ok, errorMessage) {
888+ activeFetches = Math.max(0, activeFetches - 1);
889+ if (activeFetches !== 0) {
890+ return;
891+ }
892+
893+ clearLongFetchTimer();
894+ if (ok) {
895+ setFetchStatus('hidden', '', false);
896+ } else {
897+ setFetchStatus('error', errorMessage || 'Failed to fetch data from R session.', true);
898+ }
899+ }
900+
901+ function retryCurrentPage() {
902+ if (!gridApi) {
903+ return;
904+ }
905+ setFetchStatus('loading', 'Retrying data fetch...', false);
906+ if (typeof gridApi.refreshInfiniteCache === 'function') {
907+ gridApi.refreshInfiniteCache();
908+ } else if (typeof gridApi.purgeInfiniteCache === 'function') {
909+ gridApi.purgeInfiniteCache();
910+ }
911+ }
765912
766913 function request(action, payload) {
767914 const requestId = requestIdSeq++;
@@ -815,13 +962,24 @@ export async function getTableHtml(webview: Webview, file: string | undefined, t
815962 if (gridApi) {
816963 gridApi.setGridOption('theme', getAgTheme());
817964 }
965+ updateFetchStatusPosition();
818966 }
819967
820968 async function initialize() {
821969 console.log('[dataview] agGrid object:', window.agGrid);
822970 console.log('[dataview] agGrid.Grid:', typeof window.agGrid.Grid);
823-
824- const init = await request('init', {});
971+
972+ beginFetch('Loading data viewer metadata...');
973+ let init;
974+ try {
975+ init = await request('init', {});
976+ finishFetch(true);
977+ } catch (e) {
978+ const initError = e instanceof Error ? e.message : String(e);
979+ finishFetch(false, 'Failed to initialize data viewer: ' + initError);
980+ throw e;
981+ }
982+
825983 const columns = Array.isArray(init.columns) ? init.columns : [];
826984
827985 columns.forEach((column) => {
@@ -858,6 +1016,7 @@ export async function getTableHtml(webview: Webview, file: string | undefined, t
8581016
8591017 const datasource = {
8601018 getRows: async function(params) {
1019+ beginFetch('Fetching rows from R session...');
8611020 try {
8621021 const result = await request('page', {
8631022 startRow: params.startRow,
@@ -867,9 +1026,12 @@ export async function getTableHtml(webview: Webview, file: string | undefined, t
8671026 });
8681027 const resolvedLastRow = Number.isFinite(result.totalRows) ? result.totalRows : result.lastRow;
8691028 params.successCallback(result.rows || [], resolvedLastRow);
1029+ finishFetch(true);
8701030 } catch (e) {
8711031 console.error('[dataview] Failed to load page', e);
8721032 params.failCallback();
1033+ const pageError = e instanceof Error ? e.message : String(e);
1034+ finishFetch(false, 'Failed to fetch page: ' + pageError);
8731035 }
8741036 }
8751037 };
@@ -900,6 +1062,7 @@ export async function getTableHtml(webview: Webview, file: string | undefined, t
9001062 if (gridApi) {
9011063 gridApi.columnApi?.autoSizeAllColumns(false);
9021064 }
1065+ updateFetchStatusPosition();
9031066 }
9041067 };
9051068
@@ -908,6 +1071,7 @@ export async function getTableHtml(webview: Webview, file: string | undefined, t
9081071 console.log('[dataview] Creating grid with options:', gridOptions);
9091072 gridApi = window.agGrid.createGrid(gridDiv, gridOptions);
9101073 console.log('[dataview] Grid created successfully');
1074+ updateFetchStatusPosition();
9111075 } catch (e) {
9121076 console.error('[dataview] Grid creation failed:', e);
9131077 console.error('[dataview] Error stack:', e instanceof Error ? e.stack : 'N/A');
@@ -916,9 +1080,16 @@ export async function getTableHtml(webview: Webview, file: string | undefined, t
9161080 }
9171081
9181082 document.addEventListener('DOMContentLoaded', () => {
1083+ const retryBtn = document.querySelector('#fetchRetryBtn');
1084+ if (retryBtn) {
1085+ retryBtn.addEventListener('click', retryCurrentPage);
1086+ }
1087+
9191088 updateTheme();
9201089 initialize().catch((e) => {
9211090 console.error('[dataview] Initialization failed', e);
1091+ const initError = e instanceof Error ? e.message : String(e);
1092+ setFetchStatus('error', 'Initialization failed: ' + initError, true);
9221093 });
9231094
9241095 const observer = new MutationObserver(function () {
@@ -934,7 +1105,13 @@ export async function getTableHtml(webview: Webview, file: string | undefined, t
9341105 </script>
9351106</head>
9361107<body>
937- <div id="myGrid" style="height: 100%;"></div>
1108+ <div id="gridContainer">
1109+ <div id="myGrid" style="height: 100%;"></div>
1110+ <div id="fetchStatus" data-state="" role="status" aria-live="polite">
1111+ <span id="fetchStatusText"></span>
1112+ <button id="fetchRetryBtn" type="button">Retry</button>
1113+ </div>
1114+ </div>
9381115</body>
9391116</html>
9401117` ;
0 commit comments