|
13 | 13 | initNavOverride(); |
14 | 14 | initBreadcrumbs(); |
15 | 15 | initSearch(); |
| 16 | + initFunctionRows(); |
16 | 17 | initSorting(); |
17 | 18 | initToggleButtons(); |
18 | 19 | initCoverageNav(); |
|
929 | 930 | } |
930 | 931 | } |
931 | 932 |
|
| 933 | + // =========================================== |
| 934 | + // Progressive Function Row Rendering |
| 935 | + // =========================================== |
| 936 | + |
| 937 | + function initFunctionRows() { |
| 938 | + var dataEl = document.getElementById('functions-data'); |
| 939 | + if (!dataEl) return; |
| 940 | + |
| 941 | + var config = window.__functionsPageConfig || {}; |
| 942 | + var data = JSON.parse(dataEl.textContent); |
| 943 | + var container = document.querySelector('.functions-body'); |
| 944 | + var loadingEl = document.getElementById('functions-loading'); |
| 945 | + var showConditions = config.showConditions; |
| 946 | + var singlePage = config.singlePage; |
| 947 | + var currentFile = config.htmlFilename || ''; |
| 948 | + |
| 949 | + if (data.length === 0) { |
| 950 | + if (loadingEl) loadingEl.remove(); |
| 951 | + return; |
| 952 | + } |
| 953 | + |
| 954 | + // --- Virtual scrolling setup --- |
| 955 | + var ROW_HEIGHT = 52; |
| 956 | + var BUFFER = 10; |
| 957 | + var visibleCount = Math.max(30, Math.ceil(container.clientHeight / ROW_HEIGHT) + BUFFER * 2); |
| 958 | + var viewport, visibleEl; |
| 959 | + var lastStartIdx = -1; |
| 960 | + |
| 961 | + window.addEventListener('resize', function() { |
| 962 | + visibleCount = Math.max(30, Math.ceil(container.clientHeight / ROW_HEIGHT) + BUFFER * 2); |
| 963 | + lastStartIdx = -1; |
| 964 | + renderVisible(); |
| 965 | + }); |
| 966 | + |
| 967 | + function buildHref(entry) { |
| 968 | + if (singlePage) return '#' + entry.html_filename + '|l' + entry.line; |
| 969 | + if (currentFile !== entry.html_filename) return entry.html_filename + '#l' + entry.line; |
| 970 | + return '#l' + entry.line; |
| 971 | + } |
| 972 | + |
| 973 | + function entryKey(entry) { |
| 974 | + return entry.name + '|' + entry.filename + ':' + entry.line; |
| 975 | + } |
| 976 | + |
| 977 | + function el(tag, cls, text) { |
| 978 | + var node = document.createElement(tag); |
| 979 | + if (cls) node.className = cls; |
| 980 | + if (text !== undefined) node.textContent = text; |
| 981 | + return node; |
| 982 | + } |
| 983 | + |
| 984 | + function createRow(entry) { |
| 985 | + var row = el('div', 'function-row'); |
| 986 | + if (highlightKey && entryKey(entry) === highlightKey) { |
| 987 | + row.classList.add('function-row-visited'); |
| 988 | + } |
| 989 | + |
| 990 | + // col-function |
| 991 | + var colFn = el('div', 'col-function'); |
| 992 | + var a = document.createElement('a'); |
| 993 | + a.href = buildHref(entry); |
| 994 | + a.appendChild(el('span', 'function-name', entry.name)); |
| 995 | + a.appendChild(el('span', 'function-location', entry.filename + ':' + entry.line)); |
| 996 | + colFn.appendChild(a); |
| 997 | + row.appendChild(colFn); |
| 998 | + |
| 999 | + // col-calls |
| 1000 | + var colCalls = el('div', 'col-calls'); |
| 1001 | + var callSpan; |
| 1002 | + if (entry.excluded) { |
| 1003 | + callSpan = el('span', 'excluded', 'excluded'); |
| 1004 | + } else if (entry.execution_count === 0) { |
| 1005 | + callSpan = el('span', 'not-called', 'not called'); |
| 1006 | + } else { |
| 1007 | + callSpan = el('span', 'called', entry.execution_count + 'x'); |
| 1008 | + } |
| 1009 | + colCalls.appendChild(callSpan); |
| 1010 | + row.appendChild(colCalls); |
| 1011 | + |
| 1012 | + // col-lines |
| 1013 | + row.appendChild(el('div', 'col-lines', entry.line_coverage + '%')); |
| 1014 | + |
| 1015 | + // col-conditions (optional) |
| 1016 | + if (showConditions) { |
| 1017 | + row.appendChild(el('div', 'col-conditions', entry.condition_coverage + '%')); |
| 1018 | + } |
| 1019 | + |
| 1020 | + return row; |
| 1021 | + } |
| 1022 | + |
| 1023 | + function setupVirtualScroll() { |
| 1024 | + if (loadingEl) loadingEl.remove(); |
| 1025 | + |
| 1026 | + viewport = document.createElement('div'); |
| 1027 | + viewport.className = 'functions-viewport'; |
| 1028 | + viewport.style.height = (data.length * ROW_HEIGHT) + 'px'; |
| 1029 | + |
| 1030 | + visibleEl = document.createElement('div'); |
| 1031 | + visibleEl.className = 'functions-visible'; |
| 1032 | + |
| 1033 | + viewport.appendChild(visibleEl); |
| 1034 | + container.appendChild(viewport); |
| 1035 | + } |
| 1036 | + |
| 1037 | + function renderVisible() { |
| 1038 | + var scrollTop = container.scrollTop; |
| 1039 | + var startIdx = Math.max(0, Math.floor(scrollTop / ROW_HEIGHT) - BUFFER); |
| 1040 | + var endIdx = Math.min(data.length, startIdx + visibleCount + BUFFER); |
| 1041 | + |
| 1042 | + // Skip re-render if the window hasn't shifted |
| 1043 | + if (startIdx === lastStartIdx) return; |
| 1044 | + lastStartIdx = startIdx; |
| 1045 | + |
| 1046 | + visibleEl.style.top = (startIdx * ROW_HEIGHT) + 'px'; |
| 1047 | + |
| 1048 | + var frag = document.createDocumentFragment(); |
| 1049 | + for (var i = startIdx; i < endIdx; i++) { |
| 1050 | + frag.appendChild(createRow(data[i])); |
| 1051 | + } |
| 1052 | + visibleEl.replaceChildren(frag); |
| 1053 | + } |
| 1054 | + |
| 1055 | + // --- Scroll listener (rAF-throttled) --- |
| 1056 | + var ticking = false; |
| 1057 | + container.addEventListener('scroll', function() { |
| 1058 | + if (!ticking) { |
| 1059 | + requestAnimationFrame(function() { renderVisible(); ticking = false; }); |
| 1060 | + ticking = true; |
| 1061 | + } |
| 1062 | + }); |
| 1063 | + |
| 1064 | + // --- Save state on navigation for back-button restore --- |
| 1065 | + var highlightKey = null; |
| 1066 | + container.addEventListener('click', function(e) { |
| 1067 | + var row = e.target.closest('.function-row'); |
| 1068 | + if (!row) return; |
| 1069 | + var link = row.querySelector('a'); |
| 1070 | + if (!link) return; |
| 1071 | + var nameEl = row.querySelector('.function-name'); |
| 1072 | + var locEl = row.querySelector('.function-location'); |
| 1073 | + if (nameEl && locEl) { |
| 1074 | + sessionStorage.setItem('gcovr-functions-clicked', nameEl.textContent + '|' + locEl.textContent); |
| 1075 | + } |
| 1076 | + sessionStorage.setItem('gcovr-functions-scrollTop', String(container.scrollTop)); |
| 1077 | + }); |
| 1078 | + |
| 1079 | + // --- Data-level sorting --- |
| 1080 | + function sortData(key, ascending) { |
| 1081 | + data.sort(function(a, b) { |
| 1082 | + var aVal, bVal; |
| 1083 | + switch (key) { |
| 1084 | + case 'name': aVal = a.name; bVal = b.name; break; |
| 1085 | + case 'calls': aVal = a.excluded ? -1 : a.execution_count; bVal = b.excluded ? -1 : b.execution_count; break; |
| 1086 | + case 'lines': aVal = parseFloat(a.line_coverage) || 0; bVal = parseFloat(b.line_coverage) || 0; break; |
| 1087 | + case 'conditions': aVal = parseFloat(a.condition_coverage) || 0; bVal = parseFloat(b.condition_coverage) || 0; break; |
| 1088 | + default: aVal = a.name; bVal = b.name; |
| 1089 | + } |
| 1090 | + if (typeof aVal === 'string' && typeof bVal === 'string') { |
| 1091 | + return ascending ? aVal.localeCompare(bVal) : bVal.localeCompare(aVal); |
| 1092 | + } |
| 1093 | + return ascending ? aVal - bVal : bVal - aVal; |
| 1094 | + }); |
| 1095 | + lastStartIdx = -1; // force re-render |
| 1096 | + viewport.style.height = (data.length * ROW_HEIGHT) + 'px'; |
| 1097 | + renderVisible(); |
| 1098 | + } |
| 1099 | + |
| 1100 | + // Intercept sort clicks on functions-header before initSorting runs |
| 1101 | + var funcHeaders = document.querySelectorAll('.functions-header .sortable'); |
| 1102 | + funcHeaders.forEach(function(header) { |
| 1103 | + header.addEventListener('click', function(e) { |
| 1104 | + e.stopPropagation(); |
| 1105 | + var sortKey = this.dataset.sort; |
| 1106 | + var isAscending = this.classList.contains('sorted-ascending'); |
| 1107 | + |
| 1108 | + // Update header classes |
| 1109 | + funcHeaders.forEach(function(h) { |
| 1110 | + h.classList.remove('sorted-ascending', 'sorted-descending'); |
| 1111 | + }); |
| 1112 | + this.classList.add(isAscending ? 'sorted-descending' : 'sorted-ascending'); |
| 1113 | + |
| 1114 | + sortData(sortKey, !isAscending); |
| 1115 | + }, true); // capture phase to beat initSorting |
| 1116 | + }); |
| 1117 | + |
| 1118 | + // --- Restore saved state (scroll + highlight) --- |
| 1119 | + function restoreSavedState() { |
| 1120 | + var saved = sessionStorage.getItem('gcovr-functions-clicked'); |
| 1121 | + if (saved !== null) { |
| 1122 | + sessionStorage.removeItem('gcovr-functions-clicked'); |
| 1123 | + highlightKey = saved; |
| 1124 | + } |
| 1125 | + var scroll = sessionStorage.getItem('gcovr-functions-scrollTop'); |
| 1126 | + if (scroll !== null) { |
| 1127 | + sessionStorage.removeItem('gcovr-functions-scrollTop'); |
| 1128 | + container.scrollTop = parseInt(scroll, 10); |
| 1129 | + } |
| 1130 | + if (saved !== null || scroll !== null) { |
| 1131 | + lastStartIdx = -1; |
| 1132 | + renderVisible(); |
| 1133 | + } |
| 1134 | + } |
| 1135 | + |
| 1136 | + // --- Initialize --- |
| 1137 | + data.sort(function(a, b) { return a.name.localeCompare(b.name); }); |
| 1138 | + |
| 1139 | + setupVirtualScroll(); |
| 1140 | + renderVisible(); |
| 1141 | + restoreSavedState(); |
| 1142 | + |
| 1143 | + // Also restore on bfcache navigation (browser Back button) |
| 1144 | + window.addEventListener('pageshow', function(e) { |
| 1145 | + if (e.persisted) restoreSavedState(); |
| 1146 | + }); |
| 1147 | + |
| 1148 | + // Mark functions page so initSorting can skip it |
| 1149 | + container.dataset.virtualScroll = 'true'; |
| 1150 | + } |
| 1151 | + |
| 1152 | + |
| 1153 | + |
932 | 1154 | // =========================================== |
933 | 1155 | // Sorting |
934 | 1156 | // =========================================== |
|
961 | 1183 | function sortList(key, ascending) { |
962 | 1184 | const container = document.getElementById('file-list') || document.querySelector('.functions-body'); |
963 | 1185 | if (!container) return; |
| 1186 | + // Virtual scroll handles its own sorting |
| 1187 | + if (container.dataset.virtualScroll) return; |
964 | 1188 |
|
965 | 1189 | const rows = Array.from(container.children); |
966 | 1190 |
|
|
0 commit comments