530 lines · 17.3 KB · TypeScript
RawDownload
import { createHighlighterCore, type HighlighterCore } from "@shikijs/core";import { createJavaScriptRegexEngine } from "@shikijs/engine-javascript";import bashLanguage from "@shikijs/langs/bash";import diffLanguage from "@shikijs/langs/diff";import javascriptLanguage from "@shikijs/langs/javascript";import jsonLanguage from "@shikijs/langs/json";import jsxLanguage from "@shikijs/langs/jsx";import tsxLanguage from "@shikijs/langs/tsx";import typescriptLanguage from "@shikijs/langs/typescript";import yamlLanguage from "@shikijs/langs/yaml";import * as Schema from "effect/Schema"; import type { NativeReviewDiffFile, NativeReviewDiffLanguage } from "./nativeReviewDiffTypes";import type { NativeReviewDiffRow, NativeReviewDiffToken } from "./nativeReviewDiffSurface"; export type NativeReviewDiffHighlightScheme = "light" | "dark";export type NativeReviewDiffHighlightEngine = "native" | "javascript"; export class NativeReviewDiffHighlighterUnavailableError extends Schema.TaggedErrorClass<NativeReviewDiffHighlighterUnavailableError>()(  "NativeReviewDiffHighlighterUnavailableError",  {},) {  override get message(): string {    return "The native review diff highlighter is unavailable in this build.";  }} export const isNativeReviewDiffHighlighterUnavailableError = Schema.is(  NativeReviewDiffHighlighterUnavailableError,); export class NativeReviewDiffHighlighterInitializationError extends Schema.TaggedErrorClass<NativeReviewDiffHighlighterInitializationError>()(  "NativeReviewDiffHighlighterInitializationError",  {    requestedEngine: Schema.Literals(["native", "javascript"]),    attemptedEngine: Schema.Literals(["native", "javascript"]),    cause: Schema.Defect(),  },) {  override get message(): string {    return `Failed to initialize the ${this.attemptedEngine} review diff highlighter requested as ${this.requestedEngine}.`;  }} export interface NativeReviewDiffHighlighterHandle {  readonly engine: NativeReviewDiffHighlightEngine;  readonly tokenize: (    code: string,    options: { readonly lang: NativeReviewDiffLanguage; readonly theme: string },  ) => ReadonlyArray<ReadonlyArray<NativeReviewDiffToken>>;} interface NativeReviewDiffLineRow extends NativeReviewDiffRow {  readonly kind: "line";  readonly fileId: string;  readonly content: string;} interface IndexedNativeReviewDiffLineRow {  readonly row: NativeReviewDiffLineRow;  readonly rowIndex: number;} export interface NativeReviewDiffTokenChunk {  readonly chunkIndex: number;  readonly fileId: string;  readonly filePath: string;  readonly language: NativeReviewDiffLanguage;  readonly lineCount: number;  readonly durationMs: number;  readonly tokensByRowId: Record<string, ReadonlyArray<NativeReviewDiffToken>>;} export interface StreamNativeReviewDiffTokenInput {  readonly rows: ReadonlyArray<NativeReviewDiffRow>;  readonly files: ReadonlyArray<NativeReviewDiffFile>;  readonly scheme: NativeReviewDiffHighlightScheme;  readonly engine?: NativeReviewDiffHighlightEngine;  readonly chunkSize?: number;  readonly signal?: AbortSignal;  readonly onChunk: (chunk: NativeReviewDiffTokenChunk) => void;} export interface HighlightNativeReviewDiffVisibleRowsInput {  readonly rows: ReadonlyArray<NativeReviewDiffRow>;  readonly files: ReadonlyArray<NativeReviewDiffFile>;  readonly scheme: NativeReviewDiffHighlightScheme;  readonly engine?: NativeReviewDiffHighlightEngine;  readonly firstRowIndex: number;  readonly lastRowIndex: number;  readonly overscanRows?: number;  readonly maxRows?: number;  readonly alreadyHighlightedRowIds?: ReadonlySet<string>;  readonly signal?: AbortSignal;} const NATIVE_REVIEW_DIFF_HIGHLIGHT_CHUNK_SIZE = 500;const NATIVE_REVIEW_DIFF_VISIBLE_OVERSCAN_ROWS = 160;const NATIVE_REVIEW_DIFF_VISIBLE_MAX_ROWS = 360; const NATIVE_REVIEW_DIFF_THEME_NAME_BY_SCHEME = {  dark: "t3-pierre-dark",  light: "t3-pierre-light",} as const; const PIERRE_LIGHT_SHIKI_THEME = {  name: NATIVE_REVIEW_DIFF_THEME_NAME_BY_SCHEME.light,  type: "light" as const,  fg: "#070707",  bg: "#ffffff",  settings: [    { settings: { foreground: "#070707", background: "#ffffff" } },    { scope: "comment, punctuation.definition.comment", settings: { foreground: "#84848A" } },    {      scope: "keyword, storage, storage.type, keyword.operator.expression",      settings: { foreground: "#FC2B73" },    },    {      scope: "entity.name.function, support.function, meta.function-call",      settings: { foreground: "#7B43F8" },    },    {      scope: "entity.name.type, support.type, support.class",      settings: { foreground: "#C635E4" },    },    {      scope: "string, constant.character, punctuation.definition.string",      settings: { foreground: "#199F43" },    },    {      scope: "constant.numeric, constant.language, constant.other",      settings: { foreground: "#1CA1C7" },    },    {      scope: "variable.parameter, variable.other.readwrite, meta.object-literal.key",      settings: { foreground: "#D47628" },    },    {      scope: "entity.name.tag, support.class.component",      settings: { foreground: "#199F43" },    },    {      scope: "punctuation, meta.brace, meta.delimiter",      settings: { foreground: "#79797F" },    },    { scope: "invalid", settings: { foreground: "#D52C36" } },  ],}; const PIERRE_DARK_SHIKI_THEME = {  name: NATIVE_REVIEW_DIFF_THEME_NAME_BY_SCHEME.dark,  type: "dark" as const,  fg: "#adadb1",  bg: "#0a0a0a",  settings: [    { settings: { foreground: "#adadb1", background: "#0a0a0a" } },    { scope: "comment, punctuation.definition.comment", settings: { foreground: "#84848A" } },    {      scope: "keyword, storage, storage.type, keyword.operator.expression",      settings: { foreground: "#FF678D" },    },    {      scope: "entity.name.function, support.function, meta.function-call",      settings: { foreground: "#9D6AFB" },    },    {      scope: "entity.name.type, support.type, support.class",      settings: { foreground: "#D568EA" },    },    {      scope: "string, constant.character, punctuation.definition.string",      settings: { foreground: "#5ECC71" },    },    {      scope: "constant.numeric, constant.language, constant.other",      settings: { foreground: "#68CDF2" },    },    {      scope: "variable.parameter, variable.other.readwrite, meta.object-literal.key",      settings: { foreground: "#FFA359" },    },    {      scope: "entity.name.tag, support.class.component",      settings: { foreground: "#5ECC71" },    },    {      scope: "punctuation, meta.brace, meta.delimiter",      settings: { foreground: "#79797F" },    },    { scope: "invalid", settings: { foreground: "#FF6762" } },  ],}; const NATIVE_REVIEW_DIFF_SHIKI_THEMES = [  PIERRE_LIGHT_SHIKI_THEME,  PIERRE_DARK_SHIKI_THEME,] satisfies Parameters<typeof createHighlighterCore>[0]["themes"]; const NATIVE_REVIEW_DIFF_LANGUAGES = [  bashLanguage,  diffLanguage,  javascriptLanguage,  jsonLanguage,  jsxLanguage,  tsxLanguage,  typescriptLanguage,  yamlLanguage,] satisfies Parameters<typeof createHighlighterCore>[0]["langs"]; let nativeHighlighterPromise: Promise<NativeReviewDiffHighlighterHandle> | null = null;let javascriptHighlighterPromise: Promise<NativeReviewDiffHighlighterHandle> | null = null; function waitForNextFrame(): Promise<void> {  return new Promise((resolve) => setTimeout(resolve, 0));} function normalizeTokens(  tokenLines: ReadonlyArray<ReadonlyArray<{ content: string; color?: string; fontStyle?: number }>>,): ReadonlyArray<ReadonlyArray<NativeReviewDiffToken>> {  return tokenLines.map((line) =>    line.map((token) => ({      content: token.content,      color: token.color ?? null,      fontStyle: token.fontStyle ?? null,    })),  );} async function createNativeReviewDiffHighlighter(): Promise<NativeReviewDiffHighlighterHandle> {  const nativeEngineModule = await import("react-native-shiki-engine");  if (!nativeEngineModule.isNativeEngineAvailable()) {    throw new NativeReviewDiffHighlighterUnavailableError();  }   const highlighter = await createHighlighterCore({    langs: NATIVE_REVIEW_DIFF_LANGUAGES,    themes: NATIVE_REVIEW_DIFF_SHIKI_THEMES,    engine: nativeEngineModule.createNativeEngine(),  });   return {    engine: "native",    tokenize: (code, options) => normalizeTokens(highlighter.codeToTokensBase(code, options)),  };} async function createJavascriptReviewDiffHighlighter(): Promise<NativeReviewDiffHighlighterHandle> {  const highlighter: HighlighterCore = await createHighlighterCore({    langs: NATIVE_REVIEW_DIFF_LANGUAGES,    themes: NATIVE_REVIEW_DIFF_SHIKI_THEMES,    engine: createJavaScriptRegexEngine(),  });   return {    engine: "javascript",    tokenize: (code, options) => normalizeTokens(highlighter.codeToTokensBase(code, options)),  };} export async function getNativeReviewDiffHighlighter(  engine: NativeReviewDiffHighlightEngine = "native",): Promise<NativeReviewDiffHighlighterHandle> {  if (engine === "javascript") {    try {      javascriptHighlighterPromise ??= createJavascriptReviewDiffHighlighter();      return await javascriptHighlighterPromise;    } catch (cause) {      javascriptHighlighterPromise = null;      throw new NativeReviewDiffHighlighterInitializationError({        requestedEngine: engine,        attemptedEngine: "javascript",        cause,      });    }  }   nativeHighlighterPromise ??= createNativeReviewDiffHighlighter()    .catch(async (cause: unknown) => {      const nativeError = isNativeReviewDiffHighlighterUnavailableError(cause)        ? cause        : new NativeReviewDiffHighlighterInitializationError({            requestedEngine: engine,            attemptedEngine: "native",            cause,          });      console.warn("[debug-native-diff] native highlighter unavailable", {        error: nativeError,      });      try {        javascriptHighlighterPromise ??= createJavascriptReviewDiffHighlighter();        return await javascriptHighlighterPromise;      } catch (fallbackCause) {        javascriptHighlighterPromise = null;        throw new NativeReviewDiffHighlighterInitializationError({          requestedEngine: engine,          attemptedEngine: "javascript",          cause: new AggregateError(            [nativeError, fallbackCause],            "Native and JavaScript review diff highlighter initialization failed.",            { cause: nativeError },          ),        });      }    })    .catch((error) => {      nativeHighlighterPromise = null;      throw error;    });  return await nativeHighlighterPromise;} function isHighlightableLineRow(row: NativeReviewDiffRow): row is NativeReviewDiffLineRow {  return row.kind === "line" && typeof row.fileId === "string" && typeof row.content === "string";} function hasConsecutiveLineNumbers(  previous: number | null | undefined,  next: number | null | undefined,): boolean {  return typeof previous === "number" && typeof next === "number" && next === previous + 1;} function hasOnlyCommentRowsBetween(  rows: ReadonlyArray<NativeReviewDiffRow>,  previousRowIndex: number,  nextRowIndex: number,): boolean {  for (let rowIndex = previousRowIndex + 1; rowIndex < nextRowIndex; rowIndex += 1) {    if (rows[rowIndex]?.kind !== "comment") {      return false;    }  }  return true;} function canShareGrammarContext(  previous: IndexedNativeReviewDiffLineRow,  next: IndexedNativeReviewDiffLineRow,  rows: ReadonlyArray<NativeReviewDiffRow>,): boolean {  if (    next.row.fileId !== previous.row.fileId ||    !hasOnlyCommentRowsBetween(rows, previous.rowIndex, next.rowIndex)  ) {    return false;  }   if (previous.row.change === "delete" || next.row.change === "delete") {    return (      previous.row.change !== "add" &&      next.row.change !== "add" &&      hasConsecutiveLineNumbers(previous.row.oldLineNumber, next.row.oldLineNumber)    );  }   if (previous.row.change === "add" || next.row.change === "add") {    return hasConsecutiveLineNumbers(previous.row.newLineNumber, next.row.newLineNumber);  }   return (    previous.row.change === "context" &&    next.row.change === "context" &&    hasConsecutiveLineNumbers(previous.row.oldLineNumber, next.row.oldLineNumber) &&    hasConsecutiveLineNumbers(previous.row.newLineNumber, next.row.newLineNumber)  );} function groupLineRowsByFileId(rows: ReadonlyArray<NativeReviewDiffRow>) {  const rowsByFileId = new Map<string, NativeReviewDiffLineRow[]>();  for (const row of rows) {    if (!isHighlightableLineRow(row)) {      continue;    }     const fileRows = rowsByFileId.get(row.fileId) ?? [];    fileRows.push(row);    rowsByFileId.set(row.fileId, fileRows);  }  return rowsByFileId;} function createFileMap(files: ReadonlyArray<NativeReviewDiffFile>) {  return new Map(files.map((file) => [file.id, file]));} function clampRowIndex(index: number, rows: ReadonlyArray<NativeReviewDiffRow>) {  if (rows.length === 0) {    return 0;  }   return Math.min(rows.length - 1, Math.max(0, Math.floor(index)));} function makePlainTokenFallback(  row: NativeReviewDiffLineRow,): ReadonlyArray<NativeReviewDiffToken> {  return [{ content: row.content || " ", color: null, fontStyle: null }];} export async function highlightNativeReviewDiffVisibleRows(  input: HighlightNativeReviewDiffVisibleRowsInput,): Promise<{  readonly engine: NativeReviewDiffHighlightEngine;  readonly tokensByRowId: Record<string, ReadonlyArray<NativeReviewDiffToken>>;  readonly rowCount: number;  readonly durationMs: number;}> {  const highlighter = await getNativeReviewDiffHighlighter(input.engine ?? "native");  if (input.signal?.aborted) {    return { engine: highlighter.engine, tokensByRowId: {}, rowCount: 0, durationMs: 0 };  }   const startedAt = performance.now();  const theme = NATIVE_REVIEW_DIFF_THEME_NAME_BY_SCHEME[input.scheme];  const fileMap = createFileMap(input.files);  const overscanRows = input.overscanRows ?? NATIVE_REVIEW_DIFF_VISIBLE_OVERSCAN_ROWS;  const maxRows = input.maxRows ?? NATIVE_REVIEW_DIFF_VISIBLE_MAX_ROWS;  const startIndex = clampRowIndex(input.firstRowIndex - overscanRows, input.rows);  const endIndex = clampRowIndex(input.lastRowIndex + overscanRows, input.rows);  const selectedRows: IndexedNativeReviewDiffLineRow[] = [];   for (    let rowIndex = startIndex;    rowIndex <= endIndex && selectedRows.length < maxRows;    rowIndex += 1  ) {    const row = input.rows[rowIndex];    if (      row &&      isHighlightableLineRow(row) &&      !input.alreadyHighlightedRowIds?.has(row.id) &&      fileMap.has(row.fileId)    ) {      selectedRows.push({ row, rowIndex });    }  }   const tokensByRowId: Record<string, ReadonlyArray<NativeReviewDiffToken>> = {};  let segmentRows: IndexedNativeReviewDiffLineRow[] = [];  let segmentFile: NativeReviewDiffFile | undefined;   const flushSegment = () => {    if (!segmentFile || segmentRows.length === 0 || input.signal?.aborted) {      segmentRows = [];      segmentFile = undefined;      return;    }     const code = segmentRows.map(({ row }) => row.content).join("\n");    const tokenLines = highlighter.tokenize(code, { lang: segmentFile.language, theme });    segmentRows.forEach(({ row }, rowIndex) => {      tokensByRowId[row.id] = tokenLines[rowIndex] ?? makePlainTokenFallback(row);    });    segmentRows = [];    segmentFile = undefined;  };   for (const selectedRow of selectedRows) {    const { row } = selectedRow;    const file = fileMap.get(row.fileId);    if (!file) {      continue;    }     const previousRow = segmentRows.at(-1);    if (      segmentFile &&      (segmentFile.id !== file.id ||        (previousRow !== undefined &&          !canShareGrammarContext(previousRow, selectedRow, input.rows)))    ) {      flushSegment();    }     segmentFile = file;    segmentRows.push(selectedRow);  }  flushSegment();   return {    engine: highlighter.engine,    tokensByRowId,    rowCount: selectedRows.length,    durationMs: Math.round(performance.now() - startedAt),  };} export async function streamNativeReviewDiffTokens(  input: StreamNativeReviewDiffTokenInput,): Promise<NativeReviewDiffHighlightEngine> {  const highlighter = await getNativeReviewDiffHighlighter(input.engine ?? "native");  const rowsByFileId = groupLineRowsByFileId(input.rows);  const theme = NATIVE_REVIEW_DIFF_THEME_NAME_BY_SCHEME[input.scheme];  const chunkSize = input.chunkSize ?? NATIVE_REVIEW_DIFF_HIGHLIGHT_CHUNK_SIZE;  let chunkIndex = 0;   for (const file of input.files) {    const fileRows = rowsByFileId.get(file.id) ?? [];    for (let startIndex = 0; startIndex < fileRows.length; startIndex += chunkSize) {      if (input.signal?.aborted) {

Lines 1–500 of 530.