|
1054 | 1054 | const lastRow = Math.max(0, Math.ceil((scrollY - textPaddingY + height) / rowHeight));
|
1055 | 1055 |
|
1056 | 1056 | // Populate batches for the text
|
1057 |
| - let hoverBox = null; |
| 1057 | + const hoverBoxes = []; |
1058 | 1058 | const hoveredMapping = hover && hover.mapping;
|
1059 | 1059 | const mappingBatches = [];
|
1060 | 1060 | const badMappingBatches = [];
|
|
1112 | 1112 | if (range === null) continue;
|
1113 | 1113 |
|
1114 | 1114 | // Check if this mapping is hovered
|
1115 |
| - const isHovered = hoveredMapping && (sourceIndex === null |
1116 |
| - ? mappings[map] === hoveredMapping.generatedLine && |
1117 |
| - mappings[map + 1] === hoveredMapping.generatedColumn |
1118 |
| - : mappings[map + 2] === hoveredMapping.originalSource && |
1119 |
| - mappings[map + 3] === hoveredMapping.originalLine && |
1120 |
| - mappings[map + 4] === hoveredMapping.originalColumn |
1121 |
| - ); |
| 1115 | + let isHovered = false; |
| 1116 | + if (hoveredMapping) { |
| 1117 | + const isGenerated = sourceIndex === null; |
| 1118 | + const hoverIsGenerated = hover.sourceIndex === null; |
| 1119 | + const matchesGenerated = |
| 1120 | + mappings[map] === hoveredMapping.generatedLine && |
| 1121 | + mappings[map + 1] === hoveredMapping.generatedColumn; |
| 1122 | + const matchesOriginal = |
| 1123 | + mappings[map + 2] === hoveredMapping.originalSource && |
| 1124 | + mappings[map + 3] === hoveredMapping.originalLine && |
| 1125 | + mappings[map + 4] === hoveredMapping.originalColumn; |
| 1126 | + isHovered = hoveredMapping && (isGenerated !== hoverIsGenerated |
| 1127 | + // If this is on the opposite pane from the mouse, show all |
| 1128 | + // mappings that match the hovered mapping instead of showing |
| 1129 | + // an exact match. |
| 1130 | + ? matchesGenerated || matchesOriginal |
| 1131 | + // If this is on the same pane as the mouse, only show the exact |
| 1132 | + // mapping instead of showing everything that matches the target |
| 1133 | + // so hovering isn't confusing. |
| 1134 | + : isGenerated ? matchesGenerated : matchesOriginal); |
| 1135 | + } |
1122 | 1136 |
|
1123 | 1137 | // Add a rectangle to that color's batch
|
1124 | 1138 | const { startColumn, endColumn } = range;
|
1125 | 1139 | const color = mappings[map + 3] % originalLineColors.length;
|
1126 | 1140 | const [x1, y1, x2, y2] = boxForRange(x, y, row, columnWidth, range);
|
1127 | 1141 | if (isHovered) {
|
1128 |
| - hoverBox = { color, rect: [x1 - 2, y1 - 2, x2 - x1 + 4, y2 - y1 + 4] }; |
| 1142 | + hoverBoxes.push({ color, rect: [x1 - 2, y1 - 2, x2 - x1 + 4, y2 - y1 + 4] }); |
1129 | 1143 | } else if (row >= lines.length || startColumn > endOfLineColumn) {
|
1130 | 1144 | badMappingBatches[color].push(x1, y1, x2 - x1, y2 - y1);
|
1131 | 1145 | } else if (endColumn > endOfLineColumn) {
|
|
1164 | 1178 | let status = '';
|
1165 | 1179 |
|
1166 | 1180 | // Draw the hover box for all text areas
|
1167 |
| - if (hoverBox) { |
1168 |
| - const [rx, ry, rw, rh] = hoverBox.rect; |
1169 |
| - c.shadowColor = originalLineColors[hoverBox.color].replace(' 0.3)', ' 1)'); |
| 1181 | + if (hoverBoxes.length > 0) { |
| 1182 | + // Draw the glows |
1170 | 1183 | c.shadowBlur = 20;
|
1171 | 1184 | c.fillStyle = 'black';
|
1172 |
| - c.fillRect(rx - 1, ry - 1, rw + 2, rh + 2); |
| 1185 | + for (const { rect: [rx, ry, rw, rh], color } of hoverBoxes) { |
| 1186 | + c.shadowColor = originalLineColors[color].replace(' 0.3)', ' 1)'); |
| 1187 | + c.fillRect(rx - 1, ry - 1, rw + 2, rh + 2); |
| 1188 | + } |
1173 | 1189 | c.shadowColor = 'transparent';
|
1174 |
| - c.clearRect(rx, ry, rw, rh); |
| 1190 | + |
| 1191 | + // Hollow out the boxes and draw a border around each one |
| 1192 | + for (const { rect: [rx, ry, rw, rh] } of hoverBoxes) { |
| 1193 | + c.clearRect(rx, ry, rw, rh); |
| 1194 | + } |
1175 | 1195 | c.strokeStyle = textColor;
|
1176 | 1196 | c.lineWidth = 2;
|
1177 |
| - c.strokeRect(rx, ry, rw, rh); |
| 1197 | + for (const { rect: [rx, ry, rw, rh] } of hoverBoxes) { |
| 1198 | + c.strokeRect(rx, ry, rw, rh); |
| 1199 | + } |
| 1200 | + |
| 1201 | + // Hollow out the boxes again. This is necessary to remove overlapping |
| 1202 | + // borders from adjacent boxes due to duplicate mappings. |
| 1203 | + for (const { rect: [rx, ry, rw, rh] } of hoverBoxes) { |
| 1204 | + c.clearRect(rx + 2, ry + 1, rw - 4, rh - 2); |
| 1205 | + } |
1178 | 1206 | }
|
1179 | 1207 |
|
1180 | 1208 | // Draw the hover caret, but only for this text area
|
|
1301 | 1329 | const generatedArrowHead = hover.sourceIndex === originalTextArea.sourceIndex;
|
1302 | 1330 | const [ox, oy, ow, oh] = originalHoverRect;
|
1303 | 1331 | const [gx, gy, , gh] = generatedHoverRect;
|
1304 |
| - const x1 = Math.min(ox + ow, originalBounds.x + originalBounds.width) + (originalArrowHead ? 10 : 0); |
1305 |
| - const x2 = Math.max(gx, generatedBounds.x + margin) - (generatedArrowHead ? 10 : 0); |
| 1332 | + const x1 = Math.min(ox + ow, originalBounds.x + originalBounds.width) + (originalArrowHead ? 10 : 2); |
| 1333 | + const x2 = Math.max(gx, generatedBounds.x + margin) - (generatedArrowHead ? 10 : 2); |
1306 | 1334 | const y1 = oy + oh / 2;
|
1307 | 1335 | const y2 = gy + gh / 2;
|
1308 | 1336 |
|
|
0 commit comments