UNPKG

@pierre/diffs

Version:

265 lines (263 loc) 9.59 kB
import { DEFAULT_COLLAPSED_CONTEXT_THRESHOLD, DEFAULT_THEMES } from "../constants.js"; import { getFiletypeFromFileName } from "./getFiletypeFromFileName.js"; import { cleanLastNewline } from "./cleanLastNewline.js"; import { createTransformerWithState } from "./createTransformerWithState.js"; import { formatCSSVariablePrefix } from "./formatCSSVariablePrefix.js"; import { getHighlighterThemeStyles } from "./getHighlighterThemeStyles.js"; import { getLineNodes } from "./getLineNodes.js"; import { iterateOverDiff } from "./iterateOverDiff.js"; import { createDiffSpanDecoration, pushOrJoinSpan } from "./parseDiffDecorations.js"; import { diffChars, diffWordsWithSpace } from "diff"; //#region src/utils/renderDiffWithHighlighter.ts const DEFAULT_PLAIN_TEXT_OPTIONS = { forcePlainText: false }; function renderDiffWithHighlighter(diff, highlighter, options, { forcePlainText, startingLine, totalLines, expandedHunks, collapsedContextThreshold = DEFAULT_COLLAPSED_CONTEXT_THRESHOLD } = DEFAULT_PLAIN_TEXT_OPTIONS) { if (forcePlainText) { startingLine ??= 0; totalLines ??= Infinity; } else { startingLine = 0; totalLines = Infinity; } const isWindowedHighlight = startingLine > 0 || totalLines < Infinity; const baseThemeType = typeof options.theme === "string" ? highlighter.getTheme(options.theme).type : void 0; const themeStyles = getHighlighterThemeStyles({ theme: options.theme, highlighter }); const lineDiffType = forcePlainText && !isWindowedHighlight && (diff.unifiedLineCount > 1e3 || diff.splitLineCount > 1e3) ? "none" : options.lineDiffType; const code = { deletionLines: [], additionLines: [] }; const { maxLineDiffLength } = options; const shouldGroupAll = !forcePlainText && !diff.isPartial; const expandedHunksForIteration = forcePlainText ? expandedHunks : void 0; const buckets = /* @__PURE__ */ new Map(); function getBucketForHunk(hunkIndex) { const index = shouldGroupAll ? 0 : hunkIndex; const bucket = buckets.get(index) ?? createBucket(); buckets.set(index, bucket); return bucket; } function appendContent(lineContent, lineIndex, segments, contentWrapper) { if (isWindowedHighlight) { let segment = segments.at(-1); if (segment == null || segment.targetIndex + segment.count !== lineIndex) { segment = { targetIndex: lineIndex, originalOffset: contentWrapper.length, count: 0 }; segments.push(segment); } segment.count++; } contentWrapper.push(lineContent); } iterateOverDiff({ diff, diffStyle: "both", startingLine, totalLines, expandedHunks: isWindowedHighlight ? expandedHunksForIteration : true, collapsedContextThreshold, callback: ({ hunkIndex, additionLine, deletionLine, type }) => { const bucket = getBucketForHunk(hunkIndex); const splitLineIndex = additionLine != null ? additionLine.splitLineIndex : deletionLine.splitLineIndex; if (type === "change" && additionLine != null && deletionLine != null) computeLineDiffDecorations({ additionLine: diff.additionLines[additionLine.lineIndex], deletionLine: diff.deletionLines[deletionLine.lineIndex], deletionLineIndex: bucket.deletionContent.length, additionLineIndex: bucket.additionContent.length, deletionDecorations: bucket.deletionDecorations, additionDecorations: bucket.additionDecorations, lineDiffType, maxLineDiffLength }); if (deletionLine != null) { appendContent(diff.deletionLines[deletionLine.lineIndex], deletionLine.lineIndex, bucket.deletionSegments, bucket.deletionContent); bucket.deletionInfo.push({ type: type === "change" ? "change-deletion" : type, lineNumber: deletionLine.lineNumber, altLineNumber: type === "change" ? void 0 : additionLine.lineNumber ?? void 0, lineIndex: `${deletionLine.unifiedLineIndex},${splitLineIndex}` }); } if (additionLine != null) { appendContent(diff.additionLines[additionLine.lineIndex], additionLine.lineIndex, bucket.additionSegments, bucket.additionContent); bucket.additionInfo.push({ type: type === "change" ? "change-addition" : type, lineNumber: additionLine.lineNumber, altLineNumber: type === "change" ? void 0 : deletionLine.lineNumber ?? void 0, lineIndex: `${additionLine.unifiedLineIndex},${splitLineIndex}` }); } } }); for (const bucket of buckets.values()) { if (bucket.deletionContent.length === 0 && bucket.additionContent.length === 0) continue; const deletionFile = { name: diff.prevName ?? diff.name, contents: bucket.deletionContent.value }; const additionFile = { name: diff.name, contents: bucket.additionContent.value }; const { deletionLines, additionLines } = renderTwoFiles({ deletionFile, deletionInfo: bucket.deletionInfo, deletionDecorations: bucket.deletionDecorations, additionFile, additionInfo: bucket.additionInfo, additionDecorations: bucket.additionDecorations, highlighter, options, languageOverride: forcePlainText ? "text" : diff.lang }); if (shouldGroupAll) { code.deletionLines = deletionLines; code.additionLines = additionLines; continue; } if (bucket.deletionSegments.length > 0) for (const seg of bucket.deletionSegments) for (let i = 0; i < seg.count; i++) code.deletionLines[seg.targetIndex + i] = deletionLines[seg.originalOffset + i]; else code.deletionLines.push(...deletionLines); if (bucket.additionSegments.length > 0) for (const seg of bucket.additionSegments) for (let i = 0; i < seg.count; i++) code.additionLines[seg.targetIndex + i] = additionLines[seg.originalOffset + i]; else code.additionLines.push(...additionLines); } return { code, themeStyles, baseThemeType }; } function computeLineDiffDecorations({ deletionLine, additionLine, deletionLineIndex, additionLineIndex, deletionDecorations, additionDecorations, lineDiffType, maxLineDiffLength }) { if (deletionLine == null || additionLine == null || lineDiffType === "none") return; deletionLine = cleanLastNewline(deletionLine); additionLine = cleanLastNewline(additionLine); if (deletionLine.length > maxLineDiffLength || additionLine.length > maxLineDiffLength) return; const lineDiff = lineDiffType === "char" ? diffChars(deletionLine, additionLine) : diffWordsWithSpace(deletionLine, additionLine); const deletionSpans = []; const additionSpans = []; const enableJoin = lineDiffType === "word-alt"; const lastItem = lineDiff.at(-1); for (const item of lineDiff) { const isLastItem = item === lastItem; if (!item.added && !item.removed) { pushOrJoinSpan({ item, arr: deletionSpans, enableJoin, isNeutral: true, isLastItem }); pushOrJoinSpan({ item, arr: additionSpans, enableJoin, isNeutral: true, isLastItem }); } else if (item.removed) pushOrJoinSpan({ item, arr: deletionSpans, enableJoin, isLastItem }); else pushOrJoinSpan({ item, arr: additionSpans, enableJoin, isLastItem }); } let spanIndex = 0; for (const span of deletionSpans) { if (span[0] === 1) deletionDecorations.push(createDiffSpanDecoration({ line: deletionLineIndex, spanStart: spanIndex, spanLength: span[1].length })); spanIndex += span[1].length; } spanIndex = 0; for (const span of additionSpans) { if (span[0] === 1) additionDecorations.push(createDiffSpanDecoration({ line: additionLineIndex, spanStart: spanIndex, spanLength: span[1].length })); spanIndex += span[1].length; } } function createBucket() { return { deletionContent: { push(value) { this.value += value; this.length++; }, value: "", length: 0 }, additionContent: { push(value) { this.value += value; this.length++; }, value: "", length: 0 }, deletionInfo: [], additionInfo: [], deletionDecorations: [], additionDecorations: [], deletionSegments: [], additionSegments: [] }; } function renderTwoFiles({ deletionFile, additionFile, deletionInfo, additionInfo, highlighter, deletionDecorations, additionDecorations, languageOverride, options: { theme: themeOrThemes = DEFAULT_THEMES,...options } }) { const deletionLang = languageOverride ?? getFiletypeFromFileName(deletionFile.name); const additionLang = languageOverride ?? getFiletypeFromFileName(additionFile.name); const { state, transformers } = createTransformerWithState(options.useTokenTransformer); const hastConfig = (() => { return typeof themeOrThemes === "string" ? { ...options, lang: "text", theme: themeOrThemes, transformers, decorations: void 0, defaultColor: false, cssVariablePrefix: formatCSSVariablePrefix("token"), tokenizeTimeLimit: 0 } : { ...options, lang: "text", themes: themeOrThemes, transformers, decorations: void 0, defaultColor: false, cssVariablePrefix: formatCSSVariablePrefix("token"), tokenizeTimeLimit: 0 }; })(); return { deletionLines: (() => { if (deletionFile.contents === "") return []; hastConfig.lang = deletionLang; state.lineInfo = deletionInfo; hastConfig.decorations = deletionDecorations; return getLineNodes(highlighter.codeToHast(cleanLastNewline(deletionFile.contents), hastConfig)); })(), additionLines: (() => { if (additionFile.contents === "") return []; hastConfig.lang = additionLang; hastConfig.decorations = additionDecorations; state.lineInfo = additionInfo; return getLineNodes(highlighter.codeToHast(cleanLastNewline(additionFile.contents), hastConfig)); })() }; } //#endregion export { renderDiffWithHighlighter }; //# sourceMappingURL=renderDiffWithHighlighter.js.map