| ... | @@ -17,7 +17,7 @@ import { | ... | @@ -17,7 +17,7 @@ import { |
| 17 | resolveReviewHighlighterEnginePreference, | 17 | resolveReviewHighlighterEnginePreference, |
| 18 | type ReviewHighlighterEngine, | 18 | type ReviewHighlighterEngine, |
| 19 | } from "./reviewHighlighterEngine"; | 19 | } from "./reviewHighlighterEngine"; |
| 20 | import type { ReviewRenderableFile, ReviewRenderableLineRow } from "./reviewModel"; | 20 | import type { ReviewRenderableLineRow } from "./reviewModel"; |
| 21 | import { applyDiffRangesToTokens, computeWordAltDiffRanges } from "./reviewWordDiffs"; | 21 | import { applyDiffRangesToTokens, computeWordAltDiffRanges } from "./reviewWordDiffs"; |
| 22 | | 22 | |
| 23 | export type ReviewDiffTheme = "light" | "dark"; | 23 | export type ReviewDiffTheme = "light" | "dark"; |
| ... | @@ -43,17 +43,6 @@ export interface ReviewHighlightedToken { | ... | @@ -43,17 +43,6 @@ export interface ReviewHighlightedToken { |
| 43 | readonly diffHighlight?: boolean; | 43 | readonly diffHighlight?: boolean; |
| 44 | } | 44 | } |
| 45 | | 45 | |
| 46 | export interface ReviewHighlightedFile { | | |
| 47 | readonly additionLines: ReadonlyArray<ReadonlyArray<ReviewHighlightedToken>>; | | |
| 48 | readonly deletionLines: ReadonlyArray<ReadonlyArray<ReviewHighlightedToken>>; | | |
| 49 | } | | |
| 50 | | | |
| 51 | export interface ReviewHighlightFileProgress { | | |
| 52 | readonly highlightedFile: ReviewHighlightedFile; | | |
| 53 | readonly complete: boolean; | | |
| 54 | readonly highlightedLineCount: number; | | |
| 55 | } | | |
| 56 | | | |
| 57 | const SHIKI_THEME_NAME_BY_SCHEME = { | 46 | const SHIKI_THEME_NAME_BY_SCHEME = { |
| 58 | light: "github-light-default", | 47 | light: "github-light-default", |
| 59 | dark: "github-dark-default", | 48 | dark: "github-dark-default", |
| ... | @@ -64,16 +53,9 @@ const REVIEW_HIGHLIGHTER_ENGINE_ENV_VALUE = | ... | @@ -64,16 +53,9 @@ const REVIEW_HIGHLIGHTER_ENGINE_ENV_VALUE = |
| 64 | const REVIEW_HIGHLIGHTER_ENGINE_PREFERENCE = resolveReviewHighlighterEnginePreference( | 53 | const REVIEW_HIGHLIGHTER_ENGINE_PREFERENCE = resolveReviewHighlighterEnginePreference( |
| 65 | REVIEW_HIGHLIGHTER_ENGINE_ENV_VALUE, | 54 | REVIEW_HIGHLIGHTER_ENGINE_ENV_VALUE, |
| 66 | ); | 55 | ); |
| 67 | const REVIEW_HIGHLIGHTER_DISABLE_RESULT_CACHE = resolveReviewHighlighterBooleanFlag( | | |
| 68 | process.env.EXPO_PUBLIC_REVIEW_HIGHLIGHTER_DISABLE_CACHE, | | |
| 69 | false, | | |
| 70 | ); | | |
| 71 | const REVIEW_HIGHLIGHT_RESULT_CACHE_LIMIT = 8; | | |
| 72 | const REVIEW_HIGHLIGHT_CHUNK_LINE_THRESHOLD = 8; | 56 | const REVIEW_HIGHLIGHT_CHUNK_LINE_THRESHOLD = 8; |
| 73 | const REVIEW_HIGHLIGHT_CHUNK_SIZE = 200; | 57 | const REVIEW_HIGHLIGHT_CHUNK_SIZE = 200; |
| 74 | const REVIEW_TOKENIZE_MAX_LINE_LENGTH = 1_000; | 58 | const REVIEW_TOKENIZE_MAX_LINE_LENGTH = 1_000; |
| 75 | const highlightCache = new Map<string, Promise<ReviewHighlightedFile>>(); | | |
| 76 | const resolvedHighlightCache = new Map<string, ReviewHighlightedFile>(); | | |
| 77 | const REVIEW_INITIAL_LANGUAGE_MODULES = [ | 59 | const REVIEW_INITIAL_LANGUAGE_MODULES = [ |
| 78 | bashLanguage, | 60 | bashLanguage, |
| 79 | javascriptLanguage, | 61 | javascriptLanguage, |
| ... | @@ -204,22 +186,6 @@ type LoadedLanguageModule = { | ... | @@ -204,22 +186,6 @@ type LoadedLanguageModule = { |
| 204 | default: Parameters<HighlighterCore["loadLanguage"]>[0]; | 186 | default: Parameters<HighlighterCore["loadLanguage"]>[0]; |
| 205 | }; | 187 | }; |
| 206 | | 188 | |
| 207 | function resolveReviewHighlighterBooleanFlag( | | |
| 208 | value: string | undefined, | | |
| 209 | defaultValue: boolean, | | |
| 210 | ): boolean { | | |
| 211 | switch (value) { | | |
| 212 | case "1": | | |
| 213 | case "true": | | |
| 214 | return true; | | |
| 215 | case "0": | | |
| 216 | case "false": | | |
| 217 | return false; | | |
| 218 | default: | | |
| 219 | return defaultValue; | | |
| 220 | } | | |
| 221 | } | | |
| 222 | | | |
| 223 | function isReviewHighlighterDebugLoggingEnabled(): boolean { | 189 | function isReviewHighlighterDebugLoggingEnabled(): boolean { |
| 224 | return typeof __DEV__ !== "undefined" ? __DEV__ : false; | 190 | return typeof __DEV__ !== "undefined" ? __DEV__ : false; |
| 225 | } | 191 | } |
| ... | @@ -267,7 +233,6 @@ async function getHighlighter(): Promise<HighlighterCore> { | ... | @@ -267,7 +233,6 @@ async function getHighlighter(): Promise<HighlighterCore> { |
| 267 | logReviewHighlighterDiagnostic("initializing", { | 233 | logReviewHighlighterDiagnostic("initializing", { |
| 268 | configuredPreference: REVIEW_HIGHLIGHTER_ENGINE_ENV_VALUE, | 234 | configuredPreference: REVIEW_HIGHLIGHTER_ENGINE_ENV_VALUE, |
| 269 | preference: REVIEW_HIGHLIGHTER_ENGINE_PREFERENCE, | 235 | preference: REVIEW_HIGHLIGHTER_ENGINE_PREFERENCE, |
| 270 | resultCacheDisabled: REVIEW_HIGHLIGHTER_DISABLE_RESULT_CACHE, | | |
| 271 | }); | 236 | }); |
| 272 | | 237 | |
| 273 | const themes = [githubLightDefault, githubDarkDefault]; | 238 | const themes = [githubLightDefault, githubDarkDefault]; |
| ... | @@ -488,13 +453,6 @@ async function resolveLanguageFromPath( | ... | @@ -488,13 +453,6 @@ async function resolveLanguageFromPath( |
| 488 | return candidate; | 453 | return candidate; |
| 489 | } | 454 | } |
| 490 | | 455 | |
| 491 | async function resolveLanguage(file: ReviewRenderableFile): Promise<string> { | | |
| 492 | return ( | | |
| 493 | resolveLoadedLanguageFromPath(file.path, file.languageHint) ?? | | |
| 494 | (await resolveLanguageFromPath(file.path, file.languageHint)) | | |
| 495 | ); | | |
| 496 | } | | |
| 497 | | | |
| 498 | function normalizeHighlightedLines( | 456 | function normalizeHighlightedLines( |
| 499 | tokenLines: ReadonlyArray<ReadonlyArray<{ content: string; color?: string; fontStyle?: number }>>, | 457 | tokenLines: ReadonlyArray<ReadonlyArray<{ content: string; color?: string; fontStyle?: number }>>, |
| 500 | ): ReadonlyArray<ReadonlyArray<ReviewHighlightedToken>> { | 458 | ): ReadonlyArray<ReadonlyArray<ReviewHighlightedToken>> { |
| ... | @@ -507,84 +465,6 @@ function normalizeHighlightedLines( | ... | @@ -507,84 +465,6 @@ function normalizeHighlightedLines( |
| 507 | ); | 465 | ); |
| 508 | } | 466 | } |
| 509 | | 467 | |
| 510 | function makePlainHighlightedLines( | | |
| 511 | lines: ReadonlyArray<string>, | | |
| 512 | ): ReadonlyArray<ReadonlyArray<ReviewHighlightedToken>> { | | |
| 513 | return lines.map((line) => [ | | |
| 514 | { | | |
| 515 | content: stripTrailingNewline(line), | | |
| 516 | color: null, | | |
| 517 | fontStyle: null, | | |
| 518 | }, | | |
| 519 | ]); | | |
| 520 | } | | |
| 521 | | | |
| 522 | function applyWordAltDiffHighlightsToFile( | | |
| 523 | file: ReviewRenderableFile, | | |
| 524 | highlighted: ReviewHighlightedFile, | | |
| 525 | ): ReviewHighlightedFile { | | |
| 526 | const nextAdditionLines = [...highlighted.additionLines]; | | |
| 527 | const nextDeletionLines = [...highlighted.deletionLines]; | | |
| 528 | const processedPairs = new Set<string>(); | | |
| 529 | let changed = false; | | |
| 530 | | | |
| 531 | file.rows.forEach((row) => { | | |
| 532 | if (row.kind !== "line" || row.change === "context" || !row.comparison) { | | |
| 533 | return; | | |
| 534 | } | | |
| 535 | | | |
| 536 | const deletionTokenIndex = | | |
| 537 | row.change === "delete" | | |
| 538 | ? row.deletionTokenIndex | | |
| 539 | : row.comparison.change === "delete" | | |
| 540 | ? row.comparison.tokenIndex | | |
| 541 | : null; | | |
| 542 | const additionTokenIndex = | | |
| 543 | row.change === "add" | | |
| 544 | ? row.additionTokenIndex | | |
| 545 | : row.comparison.change === "add" | | |
| 546 | ? row.comparison.tokenIndex | | |
| 547 | : null; | | |
| 548 | | | |
| 549 | if (deletionTokenIndex === null || additionTokenIndex === null) { | | |
| 550 | return; | | |
| 551 | } | | |
| 552 | | | |
| 553 | const pairKey = `${deletionTokenIndex}:${additionTokenIndex}`; | | |
| 554 | if (processedPairs.has(pairKey)) { | | |
| 555 | return; | | |
| 556 | } | | |
| 557 | processedPairs.add(pairKey); | | |
| 558 | | | |
| 559 | const deletionLine = stripTrailingNewline(file.deletionLines[deletionTokenIndex] ?? ""); | | |
| 560 | const additionLine = stripTrailingNewline(file.additionLines[additionTokenIndex] ?? ""); | | |
| 561 | const ranges = computeWordAltDiffRanges({ deletionLine, additionLine }); | | |
| 562 | | | |
| 563 | if (ranges.deletion.length > 0) { | | |
| 564 | nextDeletionLines[deletionTokenIndex] = applyDiffRangesToTokens( | | |
| 565 | nextDeletionLines[deletionTokenIndex] ?? [], | | |
| 566 | ranges.deletion, | | |
| 567 | ); | | |
| 568 | changed = true; | | |
| 569 | } | | |
| 570 | | | |
| 571 | if (ranges.addition.length > 0) { | | |
| 572 | nextAdditionLines[additionTokenIndex] = applyDiffRangesToTokens( | | |
| 573 | nextAdditionLines[additionTokenIndex] ?? [], | | |
| 574 | ranges.addition, | | |
| 575 | ); | | |
| 576 | changed = true; | | |
| 577 | } | | |
| 578 | }); | | |
| 579 | | | |
| 580 | return changed | | |
| 581 | ? { | | |
| 582 | additionLines: nextAdditionLines, | | |
| 583 | deletionLines: nextDeletionLines, | | |
| 584 | } | | |
| 585 | : highlighted; | | |
| 586 | } | | |
| 587 | | | |
| 588 | function applyWordAltDiffHighlightsToSelectedLines(input: { | 468 | function applyWordAltDiffHighlightsToSelectedLines(input: { |
| 589 | readonly lines: ReadonlyArray<ReviewRenderableLineRow>; | 469 | readonly lines: ReadonlyArray<ReviewRenderableLineRow>; |
| 590 | readonly tokenMap: Record<string, ReadonlyArray<ReviewHighlightedToken>>; | 470 | readonly tokenMap: Record<string, ReadonlyArray<ReviewHighlightedToken>>; |
| ... | @@ -733,292 +613,6 @@ export async function highlightSourceFile(input: { | ... | @@ -733,292 +613,6 @@ export async function highlightSourceFile(input: { |
| 733 | return highlightLines(input.contents, language, SHIKI_THEME_NAME_BY_SCHEME[input.theme]); | 613 | return highlightLines(input.contents, language, SHIKI_THEME_NAME_BY_SCHEME[input.theme]); |
| 734 | } | 614 | } |
| 735 | | 615 | |
| 736 | async function highlightPatchLinesInChunks(input: { | | |
| 737 | readonly lines: ReadonlyArray<string>; | | |
| 738 | readonly language: string; | | |
| 739 | readonly theme: string; | | |
| 740 | readonly onChunk: ( | | |
| 741 | startIndex: number, | | |
| 742 | tokens: ReadonlyArray<ReadonlyArray<ReviewHighlightedToken>>, | | |
| 743 | ) => void; | | |
| 744 | }): Promise<ReadonlyArray<ReadonlyArray<ReviewHighlightedToken>>> { | | |
| 745 | if (input.lines.length === 0) { | | |
| 746 | return []; | | |
| 747 | } | | |
| 748 | | | |
| 749 | const highlighter = await getHighlighter(); | | |
| 750 | const highlightedLines: Array<ReadonlyArray<ReviewHighlightedToken>> = []; | | |
| 751 | | | |
| 752 | for ( | | |
| 753 | let startIndex = 0; | | |
| 754 | startIndex < input.lines.length; | | |
| 755 | startIndex += REVIEW_HIGHLIGHT_CHUNK_SIZE | | |
| 756 | ) { | | |
| 757 | const lineChunk = input.lines.slice(startIndex, startIndex + REVIEW_HIGHLIGHT_CHUNK_SIZE); | | |
| 758 | const chunkTokens: Array<ReadonlyArray<ReviewHighlightedToken>> = []; | | |
| 759 | const tokenizableLines: string[] = []; | | |
| 760 | const tokenizableIndexes: number[] = []; | | |
| 761 | | | |
| 762 | lineChunk.forEach((line, index) => { | | |
| 763 | const strippedLine = stripTrailingNewline(line); | | |
| 764 | if (strippedLine.length > REVIEW_TOKENIZE_MAX_LINE_LENGTH) { | | |
| 765 | chunkTokens[index] = [{ content: strippedLine, color: null, fontStyle: null }]; | | |
| 766 | return; | | |
| 767 | } | | |
| 768 | | | |
| 769 | tokenizableIndexes.push(index); | | |
| 770 | tokenizableLines.push(strippedLine); | | |
| 771 | }); | | |
| 772 | | | |
| 773 | if (tokenizableLines.length > 0) { | | |
| 774 | const tokenLines = highlighter.codeToTokensBase(tokenizableLines.join("\n"), { | | |
| 775 | lang: input.language, | | |
| 776 | theme: input.theme, | | |
| 777 | }); | | |
| 778 | const normalizedTokenLines = normalizeHighlightedLines(tokenLines); | | |
| 779 | | | |
| 780 | tokenizableIndexes.forEach((chunkIndex, tokenIndex) => { | | |
| 781 | chunkTokens[chunkIndex] = normalizedTokenLines[tokenIndex] ?? []; | | |
| 782 | }); | | |
| 783 | } | | |
| 784 | | | |
| 785 | const completedChunk = lineChunk.map((_, index) => chunkTokens[index] ?? []); | | |
| 786 | highlightedLines.push(...completedChunk); | | |
| 787 | input.onChunk(startIndex, completedChunk); | | |
| 788 | | | |
| 789 | if (startIndex + REVIEW_HIGHLIGHT_CHUNK_SIZE < input.lines.length) { | | |
| 790 | await waitForNextFrame(); | | |
| 791 | } | | |
| 792 | } | | |
| 793 | | | |
| 794 | return highlightedLines; | | |
| 795 | } | | |
| 796 | | | |
| 797 | function getHighlightCacheKey(file: ReviewRenderableFile, theme: ReviewDiffTheme): string { | | |
| 798 | return `${SHIKI_THEME_NAME_BY_SCHEME[theme]}:${file.cacheKey}`; | | |
| 799 | } | | |
| 800 | | | |
| 801 | function storeResolvedHighlightedFile(cacheKey: string, highlighted: ReviewHighlightedFile): void { | | |
| 802 | if (resolvedHighlightCache.has(cacheKey)) { | | |
| 803 | resolvedHighlightCache.delete(cacheKey); | | |
| 804 | } | | |
| 805 | | | |
| 806 | resolvedHighlightCache.set(cacheKey, highlighted); | | |
| 807 | | | |
| 808 | while (resolvedHighlightCache.size > REVIEW_HIGHLIGHT_RESULT_CACHE_LIMIT) { | | |
| 809 | const oldestKey = resolvedHighlightCache.keys().next().value; | | |
| 810 | if (oldestKey === undefined) { | | |
| 811 | break; | | |
| 812 | } | | |
| 813 | resolvedHighlightCache.delete(oldestKey); | | |
| 814 | } | | |
| 815 | } | | |
| 816 | | | |
| 817 | export async function highlightReviewFile( | | |
| 818 | file: ReviewRenderableFile, | | |
| 819 | theme: ReviewDiffTheme, | | |
| 820 | ): Promise<ReviewHighlightedFile> { | | |
| 821 | const shikiTheme = SHIKI_THEME_NAME_BY_SCHEME[theme]; | | |
| 822 | const cacheKey = getHighlightCacheKey(file, theme); | | |
| 823 | if (!REVIEW_HIGHLIGHTER_DISABLE_RESULT_CACHE) { | | |
| 824 | const resolved = resolvedHighlightCache.get(cacheKey); | | |
| 825 | if (resolved) { | | |
| 826 | logReviewHighlighterDiagnostic("file highlight cache hit (resolved)", { | | |
| 827 | fileId: file.id, | | |
| 828 | filePath: file.path, | | |
| 829 | theme, | | |
| 830 | }); | | |
| 831 | return resolved; | | |
| 832 | } | | |
| 833 | const cached = highlightCache.get(cacheKey); | | |
| 834 | if (cached) { | | |
| 835 | logReviewHighlighterDiagnostic("file highlight cache hit (pending)", { | | |
| 836 | fileId: file.id, | | |
| 837 | filePath: file.path, | | |
| 838 | theme, | | |
| 839 | }); | | |
| 840 | return cached; | | |
| 841 | } | | |
| 842 | } | | |
| 843 | | | |
| 844 | const promise = (async () => { | | |
| 845 | const startedAt = Date.now(); | | |
| 846 | logReviewHighlighterDiagnostic("file highlight start", { | | |
| 847 | fileId: file.id, | | |
| 848 | filePath: file.path, | | |
| 849 | theme, | | |
| 850 | additionLineCount: file.additionLines.length, | | |
| 851 | deletionLineCount: file.deletionLines.length, | | |
| 852 | rowCount: file.rows.length, | | |
| 853 | }); | | |
| 854 | const loadedLanguage = resolveLoadedLanguageFromPath(file.path, file.languageHint); | | |
| 855 | const language = loadedLanguage ?? (await resolveLanguage(file)); | | |
| 856 | if (language === "text") { | | |
| 857 | const highlighted = applyWordAltDiffHighlightsToFile(file, { | | |
| 858 | additionLines: makePlainHighlightedLines(file.additionLines), | | |
| 859 | deletionLines: makePlainHighlightedLines(file.deletionLines), | | |
| 860 | }); | | |
| 861 | if (!REVIEW_HIGHLIGHTER_DISABLE_RESULT_CACHE) { | | |
| 862 | storeResolvedHighlightedFile(cacheKey, highlighted); | | |
| 863 | } | | |
| 864 | logReviewHighlighterDiagnostic("file highlight complete", { | | |
| 865 | fileId: file.id, | | |
| 866 | filePath: file.path, | | |
| 867 | theme, | | |
| 868 | language, | | |
| 869 | highlightedAdditionLineCount: highlighted.additionLines.length, | | |
| 870 | highlightedDeletionLineCount: highlighted.deletionLines.length, | | |
| 871 | durationMs: Date.now() - startedAt, | | |
| 872 | }); | | |
| 873 | return highlighted; | | |
| 874 | } | | |
| 875 | | | |
| 876 | const additionLines = await highlightLines( | | |
| 877 | joinPatchLines(file.additionLines), | | |
| 878 | language, | | |
| 879 | shikiTheme, | | |
| 880 | ); | | |
| 881 | await waitForNextFrame(); | | |
| 882 | const deletionLines = await highlightLines( | | |
| 883 | joinPatchLines(file.deletionLines), | | |
| 884 | language, | | |
| 885 | shikiTheme, | | |
| 886 | ); | | |
| 887 | await waitForNextFrame(); | | |
| 888 | | | |
| 889 | const highlighted = applyWordAltDiffHighlightsToFile(file, { additionLines, deletionLines }); | | |
| 890 | if (!REVIEW_HIGHLIGHTER_DISABLE_RESULT_CACHE) { | | |
| 891 | storeResolvedHighlightedFile(cacheKey, highlighted); | | |
| 892 | } | | |
| 893 | logReviewHighlighterDiagnostic("file highlight complete", { | | |
| 894 | fileId: file.id, | | |
| 895 | filePath: file.path, | | |
| 896 | theme, | | |
| 897 | language, | | |
| 898 | highlightedAdditionLineCount: highlighted.additionLines.length, | | |
| 899 | highlightedDeletionLineCount: highlighted.deletionLines.length, | | |
| 900 | durationMs: Date.now() - startedAt, | | |
| 901 | }); | | |
| 902 | return highlighted; | | |
| 903 | })(); | | |
| 904 | | | |
| 905 | if (!REVIEW_HIGHLIGHTER_DISABLE_RESULT_CACHE) { | | |
| 906 | highlightCache.set(cacheKey, promise); | | |
| 907 | } | | |
| 908 | return promise.finally(() => { | | |
| 909 | if (!REVIEW_HIGHLIGHTER_DISABLE_RESULT_CACHE) { | | |
| 910 | highlightCache.delete(cacheKey); | | |
| 911 | } | | |
| 912 | }); | | |
| 913 | } | | |
| 914 | | | |
| 915 | export async function streamHighlightReviewFile( | | |
| 916 | file: ReviewRenderableFile, | | |
| 917 | theme: ReviewDiffTheme, | | |
| 918 | onProgress: (progress: ReviewHighlightFileProgress) => void, | | |
| 919 | ): Promise<ReviewHighlightedFile> { | | |
| 920 | const shikiTheme = SHIKI_THEME_NAME_BY_SCHEME[theme]; | | |
| 921 | const cacheKey = getHighlightCacheKey(file, theme); | | |
| 922 | if (!REVIEW_HIGHLIGHTER_DISABLE_RESULT_CACHE) { | | |
| 923 | const resolved = resolvedHighlightCache.get(cacheKey); | | |
| 924 | if (resolved) { | | |
| 925 | onProgress({ | | |
| 926 | highlightedFile: resolved, | | |
| 927 | complete: true, | | |
| 928 | highlightedLineCount: resolved.additionLines.length + resolved.deletionLines.length, | | |
| 929 | }); | | |
| 930 | return resolved; | | |
| 931 | } | | |
| 932 | } | | |
| 933 | | | |
| 934 | const startedAt = Date.now(); | | |
| 935 | logReviewHighlighterDiagnostic("file stream highlight start", { | | |
| 936 | fileId: file.id, | | |
| 937 | filePath: file.path, | | |
| 938 | theme, | | |
| 939 | additionLineCount: file.additionLines.length, | | |
| 940 | deletionLineCount: file.deletionLines.length, | | |
| 941 | rowCount: file.rows.length, | | |
| 942 | }); | | |
| 943 | | | |
| 944 | const loadedLanguage = resolveLoadedLanguageFromPath(file.path, file.languageHint); | | |
| 945 | const language = loadedLanguage ?? (await resolveLanguage(file)); | | |
| 946 | if (language === "text") { | | |
| 947 | const highlighted = applyWordAltDiffHighlightsToFile(file, { | | |
| 948 | additionLines: makePlainHighlightedLines(file.additionLines), | | |
| 949 | deletionLines: makePlainHighlightedLines(file.deletionLines), | | |
| 950 | }); | | |
| 951 | if (!REVIEW_HIGHLIGHTER_DISABLE_RESULT_CACHE) { | | |
| 952 | storeResolvedHighlightedFile(cacheKey, highlighted); | | |
| 953 | } | | |
| 954 | onProgress({ | | |
| 955 | highlightedFile: highlighted, | | |
| 956 | complete: true, | | |
| 957 | highlightedLineCount: highlighted.additionLines.length + highlighted.deletionLines.length, | | |
| 958 | }); | | |
| 959 | logReviewHighlighterDiagnostic("file stream highlight complete", { | | |
| 960 | fileId: file.id, | | |
| 961 | filePath: file.path, | | |
| 962 | theme, | | |
| 963 | language, | | |
| 964 | highlightedAdditionLineCount: highlighted.additionLines.length, | | |
| 965 | highlightedDeletionLineCount: highlighted.deletionLines.length, | | |
| 966 | highlightedLineCount: highlighted.additionLines.length + highlighted.deletionLines.length, | | |
| 967 | durationMs: Date.now() - startedAt, | | |
| 968 | }); | | |
| 969 | return highlighted; | | |
| 970 | } | | |
| 971 | | | |
| 972 | const additionLines: Array<ReadonlyArray<ReviewHighlightedToken>> = []; | | |
| 973 | const deletionLines: Array<ReadonlyArray<ReviewHighlightedToken>> = []; | | |
| 974 | let highlightedLineCount = 0; | | |
| 975 | | | |
| 976 | await highlightPatchLinesInChunks({ | | |
| 977 | lines: file.additionLines, | | |
| 978 | language, | | |
| 979 | theme: shikiTheme, | | |
| 980 | onChunk: (startIndex, tokens) => { | | |
| 981 | tokens.forEach((lineTokens, index) => { | | |
| 982 | additionLines[startIndex + index] = lineTokens; | | |
| 983 | }); | | |
| 984 | highlightedLineCount += tokens.length; | | |
| 985 | }, | | |
| 986 | }); | | |
| 987 | await waitForNextFrame(); | | |
| 988 | await highlightPatchLinesInChunks({ | | |
| 989 | lines: file.deletionLines, | | |
| 990 | language, | | |
| 991 | theme: shikiTheme, | | |
| 992 | onChunk: (startIndex, tokens) => { | | |
| 993 | tokens.forEach((lineTokens, index) => { | | |
| 994 | deletionLines[startIndex + index] = lineTokens; | | |
| 995 | }); | | |
| 996 | highlightedLineCount += tokens.length; | | |
| 997 | }, | | |
| 998 | }); | | |
| 999 | | | |
| 1000 | const highlighted = applyWordAltDiffHighlightsToFile(file, { additionLines, deletionLines }); | | |
| 1001 | if (!REVIEW_HIGHLIGHTER_DISABLE_RESULT_CACHE) { | | |
| 1002 | storeResolvedHighlightedFile(cacheKey, highlighted); | | |
| 1003 | } | | |
| 1004 | onProgress({ | | |
| 1005 | highlightedFile: highlighted, | | |
| 1006 | complete: true, | | |
| 1007 | highlightedLineCount, | | |
| 1008 | }); | | |
| 1009 | logReviewHighlighterDiagnostic("file stream highlight complete", { | | |
| 1010 | fileId: file.id, | | |
| 1011 | filePath: file.path, | | |
| 1012 | theme, | | |
| 1013 | language, | | |
| 1014 | highlightedAdditionLineCount: highlighted.additionLines.length, | | |
| 1015 | highlightedDeletionLineCount: highlighted.deletionLines.length, | | |
| 1016 | highlightedLineCount, | | |
| 1017 | durationMs: Date.now() - startedAt, | | |
| 1018 | }); | | |
| 1019 | return highlighted; | | |
| 1020 | } | | |
| 1021 | | | |
| 1022 | export async function highlightReviewSelectedLines(input: { | 616 | export async function highlightReviewSelectedLines(input: { |
| 1023 | readonly filePath: string; | 617 | readonly filePath: string; |
| 1024 | readonly lines: ReadonlyArray<ReviewRenderableLineRow>; | 618 | readonly lines: ReadonlyArray<ReviewRenderableLineRow>; |