mirror of
http://10.0.2.1:3031/sauer/claude-code.git
synced 2026-06-30 16:06:57 +10:00
487 lines
55 KiB
TypeScript
487 lines
55 KiB
TypeScript
|
|
import { c as _c } from "react/compiler-runtime";
|
||
|
|
import { diffWordsWithSpace, type StructuredPatchHunk } from 'diff';
|
||
|
|
import * as React from 'react';
|
||
|
|
import { useMemo } from 'react';
|
||
|
|
import type { ThemeName } from 'src/utils/theme.js';
|
||
|
|
import { stringWidth } from '../../ink/stringWidth.js';
|
||
|
|
import { Box, NoSelect, Text, useTheme, wrapText } from '../../ink.js';
|
||
|
|
|
||
|
|
/*
|
||
|
|
* StructuredDiffFallback Component: Word-Level Diff Highlighting Example
|
||
|
|
*
|
||
|
|
* This component shows diff changes with word-level highlighting. Here's a walkthrough:
|
||
|
|
*
|
||
|
|
* Example:
|
||
|
|
* ```
|
||
|
|
* // Original code
|
||
|
|
* function oldName(param) {
|
||
|
|
* return param.oldProperty;
|
||
|
|
* }
|
||
|
|
*
|
||
|
|
* // Changed code
|
||
|
|
* function newName(param) {
|
||
|
|
* return param.newProperty;
|
||
|
|
* }
|
||
|
|
* ```
|
||
|
|
*
|
||
|
|
* Processing flow:
|
||
|
|
* 1. Component receives a patch with lines including '+' and '-' prefixes
|
||
|
|
* 2. Lines are transformed into objects with type (add/remove/nochange)
|
||
|
|
* 3. Related add/remove lines are paired (e.g., oldName with newName)
|
||
|
|
* 4. Word-level diffing identifies specific changed parts:
|
||
|
|
* [
|
||
|
|
* { value: 'function ', added: undefined, removed: undefined }, // Common
|
||
|
|
* { value: 'oldName', removed: true }, // Removed
|
||
|
|
* { value: 'newName', added: true }, // Added
|
||
|
|
* { value: '(param) {', added: undefined, removed: undefined } // Common
|
||
|
|
* ]
|
||
|
|
* 5. Renders with enhanced highlighting:
|
||
|
|
* - Common parts are shown normally
|
||
|
|
* - Removed words get a darker red background
|
||
|
|
* - Added words get a darker green background
|
||
|
|
*
|
||
|
|
* This produces a visually clear diff where users can see exactly which words
|
||
|
|
* changed rather than just which lines were modified.
|
||
|
|
*/
|
||
|
|
|
||
|
|
// Define DiffLine interface to be used throughout the file
|
||
|
|
interface DiffLine {
|
||
|
|
code: string;
|
||
|
|
type: 'add' | 'remove' | 'nochange';
|
||
|
|
i: number;
|
||
|
|
originalCode: string;
|
||
|
|
wordDiff?: boolean; // Flag for word-level diffing
|
||
|
|
matchedLine?: DiffLine;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Line object type for internal functions
|
||
|
|
export interface LineObject {
|
||
|
|
code: string;
|
||
|
|
i: number;
|
||
|
|
type: 'add' | 'remove' | 'nochange';
|
||
|
|
originalCode: string;
|
||
|
|
wordDiff?: boolean;
|
||
|
|
matchedLine?: LineObject;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Type for word-level diff parts
|
||
|
|
interface DiffPart {
|
||
|
|
added?: boolean;
|
||
|
|
removed?: boolean;
|
||
|
|
value: string;
|
||
|
|
}
|
||
|
|
type Props = {
|
||
|
|
patch: StructuredPatchHunk;
|
||
|
|
dim: boolean;
|
||
|
|
width: number;
|
||
|
|
};
|
||
|
|
|
||
|
|
// Threshold for when we show a full-line diff instead of word-level diffing
|
||
|
|
const CHANGE_THRESHOLD = 0.4;
|
||
|
|
export function StructuredDiffFallback(t0) {
|
||
|
|
const $ = _c(10);
|
||
|
|
const {
|
||
|
|
patch,
|
||
|
|
dim,
|
||
|
|
width
|
||
|
|
} = t0;
|
||
|
|
const [theme] = useTheme();
|
||
|
|
let t1;
|
||
|
|
if ($[0] !== dim || $[1] !== patch.lines || $[2] !== patch.oldStart || $[3] !== theme || $[4] !== width) {
|
||
|
|
t1 = formatDiff(patch.lines, patch.oldStart, width, dim, theme);
|
||
|
|
$[0] = dim;
|
||
|
|
$[1] = patch.lines;
|
||
|
|
$[2] = patch.oldStart;
|
||
|
|
$[3] = theme;
|
||
|
|
$[4] = width;
|
||
|
|
$[5] = t1;
|
||
|
|
} else {
|
||
|
|
t1 = $[5];
|
||
|
|
}
|
||
|
|
const diff = t1;
|
||
|
|
let t2;
|
||
|
|
if ($[6] !== diff) {
|
||
|
|
t2 = diff.map(_temp);
|
||
|
|
$[6] = diff;
|
||
|
|
$[7] = t2;
|
||
|
|
} else {
|
||
|
|
t2 = $[7];
|
||
|
|
}
|
||
|
|
let t3;
|
||
|
|
if ($[8] !== t2) {
|
||
|
|
t3 = <Box flexDirection="column" flexGrow={1}>{t2}</Box>;
|
||
|
|
$[8] = t2;
|
||
|
|
$[9] = t3;
|
||
|
|
} else {
|
||
|
|
t3 = $[9];
|
||
|
|
}
|
||
|
|
return t3;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Transform lines to line objects with type information
|
||
|
|
function _temp(node, i) {
|
||
|
|
return <Box key={i}>{node}</Box>;
|
||
|
|
}
|
||
|
|
export function transformLinesToObjects(lines: string[]): LineObject[] {
|
||
|
|
return lines.map(code => {
|
||
|
|
if (code.startsWith('+')) {
|
||
|
|
return {
|
||
|
|
code: code.slice(1),
|
||
|
|
i: 0,
|
||
|
|
type: 'add',
|
||
|
|
originalCode: code.slice(1)
|
||
|
|
};
|
||
|
|
}
|
||
|
|
if (code.startsWith('-')) {
|
||
|
|
return {
|
||
|
|
code: code.slice(1),
|
||
|
|
i: 0,
|
||
|
|
type: 'remove',
|
||
|
|
originalCode: code.slice(1)
|
||
|
|
};
|
||
|
|
}
|
||
|
|
return {
|
||
|
|
code: code.slice(1),
|
||
|
|
i: 0,
|
||
|
|
type: 'nochange',
|
||
|
|
originalCode: code.slice(1)
|
||
|
|
};
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
// Group adjacent add/remove lines for word-level diffing
|
||
|
|
export function processAdjacentLines(lineObjects: LineObject[]): LineObject[] {
|
||
|
|
const processedLines: LineObject[] = [];
|
||
|
|
let i = 0;
|
||
|
|
while (i < lineObjects.length) {
|
||
|
|
const current = lineObjects[i];
|
||
|
|
if (!current) {
|
||
|
|
i++;
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Find a sequence of remove followed by add (possible word-level diff candidates)
|
||
|
|
if (current.type === 'remove') {
|
||
|
|
const removeLines: LineObject[] = [current];
|
||
|
|
let j = i + 1;
|
||
|
|
|
||
|
|
// Collect consecutive remove lines
|
||
|
|
while (j < lineObjects.length && lineObjects[j]?.type === 'remove') {
|
||
|
|
const line = lineObjects[j];
|
||
|
|
if (line) {
|
||
|
|
removeLines.push(line);
|
||
|
|
}
|
||
|
|
j++;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Check if there are add lines following the remove lines
|
||
|
|
const addLines: LineObject[] = [];
|
||
|
|
while (j < lineObjects.length && lineObjects[j]?.type === 'add') {
|
||
|
|
const line = lineObjects[j];
|
||
|
|
if (line) {
|
||
|
|
addLines.push(line);
|
||
|
|
}
|
||
|
|
j++;
|
||
|
|
}
|
||
|
|
|
||
|
|
// If we have both remove and add lines, perform word-level diffing
|
||
|
|
if (removeLines.length > 0 && addLines.length > 0) {
|
||
|
|
// For word diffing, we'll compare each pair of lines or the closest available match
|
||
|
|
const pairCount = Math.min(removeLines.length, addLines.length);
|
||
|
|
|
||
|
|
// Add paired lines with word diff info
|
||
|
|
for (let k = 0; k < pairCount; k++) {
|
||
|
|
const removeLine = removeLines[k];
|
||
|
|
const addLine = addLines[k];
|
||
|
|
if (removeLine && addLine) {
|
||
|
|
removeLine.wordDiff = true;
|
||
|
|
addLine.wordDiff = true;
|
||
|
|
|
||
|
|
// Store the matched pair for later word diffing
|
||
|
|
removeLine.matchedLine = addLine;
|
||
|
|
addLine.matchedLine = removeLine;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Add all remove lines (both paired and unpaired)
|
||
|
|
processedLines.push(...removeLines.filter(Boolean));
|
||
|
|
|
||
|
|
// Then add all add lines (both paired and unpaired)
|
||
|
|
processedLines.push(...addLines.filter(Boolean));
|
||
|
|
i = j; // Skip all the lines we've processed
|
||
|
|
} else {
|
||
|
|
// No matching add lines, just add the current remove line
|
||
|
|
processedLines.push(current);
|
||
|
|
i++;
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
// Not a remove line, just add it
|
||
|
|
processedLines.push(current);
|
||
|
|
i++;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return processedLines;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Calculate word-level diffs between two text strings
|
||
|
|
export function calculateWordDiffs(oldText: string, newText: string): DiffPart[] {
|
||
|
|
// Use diffWordsWithSpace instead of diffWords to preserve whitespace
|
||
|
|
// This ensures spaces between tokens like > and { are preserved
|
||
|
|
const result = diffWordsWithSpace(oldText, newText, {
|
||
|
|
ignoreCase: false
|
||
|
|
});
|
||
|
|
return result;
|
||
|
|
}
|
||
|
|
|
||
|
|
// Process word-level diffs with manual wrapping support
|
||
|
|
function generateWordDiffElements(item: DiffLine, width: number, maxWidth: number, dim: boolean, overrideTheme?: ThemeName): React.ReactNode[] | null {
|
||
|
|
const {
|
||
|
|
type,
|
||
|
|
i,
|
||
|
|
wordDiff,
|
||
|
|
matchedLine,
|
||
|
|
originalCode
|
||
|
|
} = item;
|
||
|
|
if (!wordDiff || !matchedLine) {
|
||
|
|
return null; // This function only handles word-level diff rendering
|
||
|
|
}
|
||
|
|
const removedLineText = type === 'remove' ? originalCode : matchedLine.originalCode;
|
||
|
|
const addedLineText = type === 'remove' ? matchedLine.originalCode : originalCode;
|
||
|
|
const wordDiffs = calculateWordDiffs(removedLineText, addedLineText);
|
||
|
|
|
||
|
|
// Check if we should use word-level diffing
|
||
|
|
const totalLength = removedLineText.length + addedLineText.length;
|
||
|
|
const changedLength = wordDiffs.filter(part => part.added || part.removed).reduce((sum, part) => sum + part.value.length, 0);
|
||
|
|
const changeRatio = changedLength / totalLength;
|
||
|
|
if (changeRatio > CHANGE_THRESHOLD || dim) {
|
||
|
|
return null; // Fall back to standard rendering for major changes
|
||
|
|
}
|
||
|
|
|
||
|
|
// Calculate available width for content
|
||
|
|
const diffPrefix = type === 'add' ? '+' : '-';
|
||
|
|
const diffPrefixWidth = diffPrefix.length;
|
||
|
|
const availableContentWidth = Math.max(1, width - maxWidth - 1 - diffPrefixWidth);
|
||
|
|
|
||
|
|
// Manually wrap the word diff parts with better space efficiency
|
||
|
|
const wrappedLines: {
|
||
|
|
content: React.ReactNode[];
|
||
|
|
contentWidth: number;
|
||
|
|
}[] = [];
|
||
|
|
let currentLine: React.ReactNode[] = [];
|
||
|
|
let currentLineWidth = 0;
|
||
|
|
wordDiffs.forEach((part, partIndex) => {
|
||
|
|
// Determine if this part should be shown for this line type
|
||
|
|
let shouldShow = false;
|
||
|
|
let partBgColor: 'diffAddedWord' | 'diffRemovedWord' | undefined;
|
||
|
|
if (type === 'add') {
|
||
|
|
if (part.added) {
|
||
|
|
shouldShow = true;
|
||
|
|
partBgColor = 'diffAddedWord';
|
||
|
|
} else if (!part.removed) {
|
||
|
|
shouldShow = true;
|
||
|
|
}
|
||
|
|
} else if (type === 'remove') {
|
||
|
|
if (part.removed) {
|
||
|
|
shouldShow = true;
|
||
|
|
partBgColor = 'diffRemovedWord';
|
||
|
|
} else if (!part.added) {
|
||
|
|
shouldShow = true;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if (!shouldShow) return;
|
||
|
|
|
||
|
|
// Use wrapText to wrap this individual part if it's long
|
||
|
|
const partWrapped = wrapText(part.value, availableContentWidth, 'wrap');
|
||
|
|
const partLines = partWrapped.split('\n');
|
||
|
|
partLines.forEach((partLine, lineIdx) => {
|
||
|
|
if (!partLine) return;
|
||
|
|
|
||
|
|
// Check if we need to start a new line
|
||
|
|
if (lineIdx > 0 || currentLineWidth + stringWidth(partLine) > availableContentWidth) {
|
||
|
|
if (currentLine.length > 0) {
|
||
|
|
wrappedLines.push({
|
||
|
|
content: [...currentLine],
|
||
|
|
contentWidth: currentLineWidth
|
||
|
|
});
|
||
|
|
currentLine = [];
|
||
|
|
currentLineWidth = 0;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
currentLine.push(<Text key={`part-${partIndex}-${lineIdx}`} backgroundColor={partBgColor}>
|
||
|
|
{partLine}
|
||
|
|
</Text>);
|
||
|
|
currentLineWidth += stringWidth(partLine);
|
||
|
|
});
|
||
|
|
});
|
||
|
|
if (currentLine.length > 0) {
|
||
|
|
wrappedLines.push({
|
||
|
|
content: currentLine,
|
||
|
|
contentWidth: currentLineWidth
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
// Render each wrapped line as a separate Text element
|
||
|
|
return wrappedLines.map(({
|
||
|
|
content,
|
||
|
|
contentWidth
|
||
|
|
}, lineIndex) => {
|
||
|
|
const key = `${type}-${i}-${lineIndex}`;
|
||
|
|
const lineBgColor = type === 'add' ? dim ? 'diffAddedDimmed' : 'diffAdded' : dim ? 'diffRemovedDimmed' : 'diffRemoved';
|
||
|
|
const lineNum = lineIndex === 0 ? i : undefined;
|
||
|
|
const lineNumStr = (lineNum !== undefined ? lineNum.toString().padStart(maxWidth) : ' '.repeat(maxWidth)) + ' ';
|
||
|
|
// Calculate padding to fill the entire terminal width
|
||
|
|
const usedWidth = lineNumStr.length + diffPrefixWidth + contentWidth;
|
||
|
|
const padding = Math.max(0, width - usedWidth);
|
||
|
|
return <Box key={key} flexDirection="row">
|
||
|
|
<NoSelect fromLeftEdge>
|
||
|
|
<Text color={overrideTheme ? 'text' : undefined} backgroundColor={lineBgColor} dimColor={dim}>
|
||
|
|
{lineNumStr}
|
||
|
|
{diffPrefix}
|
||
|
|
</Text>
|
||
|
|
</NoSelect>
|
||
|
|
<Text color={overrideTheme ? 'text' : undefined} backgroundColor={lineBgColor} dimColor={dim}>
|
||
|
|
{content}
|
||
|
|
{' '.repeat(padding)}
|
||
|
|
</Text>
|
||
|
|
</Box>;
|
||
|
|
});
|
||
|
|
}
|
||
|
|
function formatDiff(lines: string[], startingLineNumber: number, width: number, dim: boolean, overrideTheme?: ThemeName): React.ReactNode[] {
|
||
|
|
// Ensure width is at least 1 to prevent rendering issues with very narrow terminals
|
||
|
|
const safeWidth = Math.max(1, Math.floor(width));
|
||
|
|
|
||
|
|
// Step 1: Transform lines to line objects with type information
|
||
|
|
const lineObjects = transformLinesToObjects(lines);
|
||
|
|
|
||
|
|
// Step 2: Group adjacent add/remove lines for word-level diffing
|
||
|
|
const processedLines = processAdjacentLines(lineObjects);
|
||
|
|
|
||
|
|
// Step 3: Number the diff lines
|
||
|
|
const ls = numberDiffLines(processedLines, startingLineNumber);
|
||
|
|
|
||
|
|
// Find max line number width for alignment
|
||
|
|
const maxLineNumber = Math.max(...ls.map(({
|
||
|
|
i
|
||
|
|
}) => i), 0);
|
||
|
|
const maxWidth = Math.max(maxLineNumber.toString().length + 1, 0);
|
||
|
|
|
||
|
|
// Step 4: Render formatting
|
||
|
|
return ls.flatMap((item): React.ReactNode[] => {
|
||
|
|
const {
|
||
|
|
type,
|
||
|
|
code,
|
||
|
|
i,
|
||
|
|
wordDiff,
|
||
|
|
matchedLine
|
||
|
|
} = item;
|
||
|
|
|
||
|
|
// Handle word-level diffing for add/remove pairs
|
||
|
|
if (wordDiff && matchedLine) {
|
||
|
|
const wordDiffElements = generateWordDiffElements(item, safeWidth, maxWidth, dim, overrideTheme);
|
||
|
|
|
||
|
|
// word-diff might refuse (e.g. due to lines being substantially different) in which
|
||
|
|
// case we'll fall through to normal renderin gbelow
|
||
|
|
if (wordDiffElements !== null) {
|
||
|
|
return wordDiffElements;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Standard rendering for lines without word diffing or as fallback
|
||
|
|
// Calculate available width accounting for line number + space + diff prefix
|
||
|
|
const diffPrefixWidth = 2; // " " for unchanged, "+ " or "- " for changes
|
||
|
|
const availableContentWidth = Math.max(1, safeWidth - maxWidth - 1 - diffPrefixWidth); // -1 for space after line number
|
||
|
|
const wrappedText = wrapText(code, availableContentWidth, 'wrap');
|
||
|
|
const wrappedLines = wrappedText.split('\n');
|
||
|
|
return wrappedLines.map((line, lineIndex) => {
|
||
|
|
const key = `${type}-${i}-${lineIndex}`;
|
||
|
|
const lineNum = lineIndex === 0 ? i : undefined;
|
||
|
|
const lineNumStr = (lineNum !== undefined ? lineNum.toString().padStart(maxWidth) : ' '.repeat(maxWidth)) + ' ';
|
||
|
|
const sigil = type === 'add' ? '+' : type === 'remove' ? '-' : ' ';
|
||
|
|
// Calculate padding to fill the entire terminal width
|
||
|
|
const contentWidth = lineNumStr.length + 1 + stringWidth(line); // lineNum + sigil + code
|
||
|
|
const padding = Math.max(0, safeWidth - contentWidth);
|
||
|
|
const bgColor = type === 'add' ? dim ? 'diffAddedDimmed' : 'diffAdded' : type === 'remove' ? dim ? 'diffRemovedDimmed' : 'diffRemoved' : undefined;
|
||
|
|
|
||
|
|
// Gutter (line number + sigil) is wrapped in <NoSelect> so fullscreen
|
||
|
|
// text selection yields clean code. bgColor carries across both boxes
|
||
|
|
// so the visual continuity (solid red/green bar) is unchanged.
|
||
|
|
return <Box key={key} flexDirection="row">
|
||
|
|
<NoSelect fromLeftEdge>
|
||
|
|
<Text color={overrideTheme ? 'text' : undefined} backgroundColor={bgColor} dimColor={dim || type === 'nochange'}>
|
||
|
|
{lineNumStr}
|
||
|
|
{sigil}
|
||
|
|
</Text>
|
||
|
|
</NoSelect>
|
||
|
|
<Text color={overrideTheme ? 'text' : undefined} backgroundColor={bgColor} dimColor={dim}>
|
||
|
|
{line}
|
||
|
|
{' '.repeat(padding)}
|
||
|
|
</Text>
|
||
|
|
</Box>;
|
||
|
|
});
|
||
|
|
});
|
||
|
|
}
|
||
|
|
export function numberDiffLines(diff: LineObject[], startLine: number): DiffLine[] {
|
||
|
|
let i = startLine;
|
||
|
|
const result: DiffLine[] = [];
|
||
|
|
const queue = [...diff];
|
||
|
|
while (queue.length > 0) {
|
||
|
|
const current = queue.shift()!;
|
||
|
|
const {
|
||
|
|
code,
|
||
|
|
type,
|
||
|
|
originalCode,
|
||
|
|
wordDiff,
|
||
|
|
matchedLine
|
||
|
|
} = current;
|
||
|
|
const line = {
|
||
|
|
code,
|
||
|
|
type,
|
||
|
|
i,
|
||
|
|
originalCode,
|
||
|
|
wordDiff,
|
||
|
|
matchedLine
|
||
|
|
};
|
||
|
|
|
||
|
|
// Update counters based on change type
|
||
|
|
switch (type) {
|
||
|
|
case 'nochange':
|
||
|
|
i++;
|
||
|
|
result.push(line);
|
||
|
|
break;
|
||
|
|
case 'add':
|
||
|
|
i++;
|
||
|
|
result.push(line);
|
||
|
|
break;
|
||
|
|
case 'remove':
|
||
|
|
{
|
||
|
|
result.push(line);
|
||
|
|
let numRemoved = 0;
|
||
|
|
while (queue[0]?.type === 'remove') {
|
||
|
|
i++;
|
||
|
|
const current = queue.shift()!;
|
||
|
|
const {
|
||
|
|
code,
|
||
|
|
type,
|
||
|
|
originalCode,
|
||
|
|
wordDiff,
|
||
|
|
matchedLine
|
||
|
|
} = current;
|
||
|
|
const line = {
|
||
|
|
code,
|
||
|
|
type,
|
||
|
|
i,
|
||
|
|
originalCode,
|
||
|
|
wordDiff,
|
||
|
|
matchedLine
|
||
|
|
};
|
||
|
|
result.push(line);
|
||
|
|
numRemoved++;
|
||
|
|
}
|
||
|
|
i -= numRemoved;
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
return result;
|
||
|
|
}
|
||
|
|
//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJuYW1lcyI6WyJkaWZmV29yZHNXaXRoU3BhY2UiLCJTdHJ1Y3R1cmVkUGF0Y2hIdW5rIiwiUmVhY3QiLCJ1c2VNZW1vIiwiVGhlbWVOYW1lIiwic3RyaW5nV2lkdGgiLCJCb3giLCJOb1NlbGVjdCIsIlRleHQiLCJ1c2VUaGVtZSIsIndyYXBUZXh0IiwiRGlmZkxpbmUiLCJjb2RlIiwidHlwZSIsImkiLCJvcmlnaW5hbENvZGUiLCJ3b3JkRGlmZiIsIm1hdGNoZWRMaW5lIiwiTGluZU9iamVjdCIsIkRpZmZQYXJ0IiwiYWRkZWQiLCJyZW1vdmVkIiwidmFsdWUiLCJQcm9wcyIsInBhdGNoIiwiZGltIiwid2lkdGgiLCJDSEFOR0VfVEhSRVNIT0xEIiwiU3RydWN0dXJlZERpZmZGYWxsYmFjayIsInQwIiwiJCIsIl9jIiwidGhlbWUiLCJ0MSIsImxpbmVzIiwib2xkU3RhcnQiLCJmb3JtYXREaWZmIiwiZGlmZiIsInQyIiwibWFwIiwiX3RlbXAiLCJ0MyIsIm5vZGUiLCJ0cmFuc2Zvcm1MaW5lc1RvT2JqZWN0cyIsInN0YXJ0c1dpdGgiLCJzbGljZSIsInByb2Nlc3NBZGphY2VudExpbmVzIiwibGluZU9iamVjdHMiLCJwcm9jZXNzZWRMaW5lcyIsImxlbmd0aCIsImN1cnJlbnQiLCJyZW1vdmVMaW5lcyIsImoiLCJsaW5lIiwicHVzaCIsImFkZExpbmVzIiwicGFpckNvdW50IiwiTWF0aCIsIm1pbiIsImsiLCJyZW1vdmVMaW5lIiwiYWRkTGluZSIsImZpbHRlciIsIkJvb2xlYW4iLCJjYWxjdWxhdGVXb3JkRGlmZnMiLCJvbGRUZXh0IiwibmV3VGV4dCIsInJlc3VsdCIsImlnbm9yZUNhc2UiLCJnZW5lcmF0ZVdvcmREaWZmRWxlbWVudHMiLCJpdGVtIiwibWF4V2lkdGgiLCJvdmVycmlkZVRoZW1lIiwiUmVhY3ROb2RlIiwicmVtb3ZlZExpbmVUZXh0IiwiYWRkZWRMaW5lVGV4dCIsIndvcmREaWZmcyIsInRvdGFsTGVuZ3RoIiwiY2hhbmdlZExlbmd0aCIsInBhcnQiLCJyZWR1Y2UiLCJzdW0iLCJjaGFuZ2VSYXRpbyIsImRpZmZQcmVmaXgiLCJkaWZmUHJlZml4V2lkdGgiLCJhdmFpbGFibGVDb250ZW50V2lkdGgiLCJtYXgiLCJ3cmFwcGVkTGluZXMiLCJjb250ZW50IiwiY29udGVudFdpZHRoIiwiY3VycmVudExpbmUiLCJjdXJyZW50TGluZVdpZHRoIiwiZm9yRWFjaCIsInBhcnRJbmRleCIsInNob3VsZFNob3ciLCJwYXJ0QmdDb2xvciIsInBhcnRXcmFwcGVkIiwicGFydExpbmVzIiwic3BsaXQiLCJwYXJ0TGluZSIsImxpbmVJZHgiLCJsaW5lSW5kZXgiLCJrZXkiLCJsaW5lQmdDb2xvciIsImxpbmVOdW0iLCJ1bmRlZmluZWQiLCJsaW5lTnVtU3RyIiwidG9TdHJpbmciLCJwYWRTdGFydCIsInJlcGVhdCIsInVzZWRXaWR0aCIsInBhZGRpbmciLCJzdGFydGluZ0xpbmVOdW1iZXIiLCJzYWZlV2lkdGgiLCJmbG9vciIsImxzIiwibnVtYmVyRGlmZkxpbmVzIiwibWF4TGluZU51bWJlciIsImZsYXRNYXAiLCJ3b3JkRGlmZkVsZW1lbnRzIiwid3JhcHBlZFRleHQiLCJzaWdpbCIsImJnQ29sb3IiLCJzdGFydExpbmUiLCJxdWV1ZSIsInNoaWZ0IiwibnVtUmVtb3ZlZCJdLCJzb3VyY2VzIjpbIkZhbGxiYWNrLnRzeCJdLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBkaWZmV29yZHNXaXRoU3BhY2UsIHR5cGUgU3RydWN0dXJlZFBhdGNoSHVuayB9IGZyb20gJ2RpZmYnXG5pbXBvcnQgKiBhcyBSZWFjdCBmcm9tICdyZWFjdCdcbmltcG9ydCB7IHVzZU1lbW8gfSBmcm9tICdyZWFjdCdcbmltcG9ydCB0eXBlIHsgVGhlbWVOYW1lIH0gZnJvbSAnc3JjL3V0aWxzL3RoZW1lLmpzJ1xuaW1wb3J0IHsgc3RyaW5nV2lkdGggfSBmcm9tICcuLi8uLi9pbmsvc3RyaW5nV2lkdGguanMnXG5pbXBvcnQgeyBCb3gsIE5vU2VsZWN0LCBUZXh0LCB1c2VUaGVtZSwgd3JhcFRleHQgfSBmcm9tICcuLi8uLi9pbmsuanMnXG5cbi8qXG4gKiBTdHJ1Y3R1cmVkRGlmZkZhbGxiYWNrIENvbXBvbmVudDogV29yZC1MZXZlbCBEaWZmIEhpZ2hsaWdodGluZyBFeGFtcGxlXG4gKlxuICogVGhpcyBjb21wb25lbnQgc2hvd3MgZGlmZiBjaGFuZ2VzIHdpdGggd29yZC1sZXZlbCBoaWdobGlnaHRpbmcuIEhlcmUncyBhIHdhbGt0aHJvdWdoOlxuICpcbiAqIEV4YW1wbGU6XG4gKiBgYGBcbiAqIC8vIE9yaWdpbmFsIGNvZGVcbiAqIGZ1bmN0aW9uIG9sZE5hbWUocGFyYW0pIHtcbiAqICAgcmV0dXJuIHBhcmFtLm9sZFByb3BlcnR5O1xuICogfVxuICpcbiAqIC8vIENoYW5nZWQgY29kZVxuICogZnVuY3Rpb24gbmV3TmFtZShwYXJhbSkge1xuICogICByZXR1cm4gcGFyYW0ubmV3UHJvcGVydHk7XG4gKiB9XG4gKiBgYGBcbiAqXG4gKiBQcm9jZXNzaW5nIGZsb3c6XG4gKiAxLiBDb21wb25lbnQgcmVjZWl2ZXMgYSBwYXRjaCB3aXRoIGxpbmVzIGluY2x1ZGluZyAnKycgYW5kICctJyBwcmVmaXhlc1xuICogMi4gTGluZXMgYXJlIHRyYW5zZm9ybWVkIGludG8gb2JqZWN0cyB3aXRoIHR5cGUgKGFkZC9yZW1vdmUvbm9jaGFuZ2UpXG4gKiAzLiBSZWxhdGVkIGFkZC9yZW1vdmUgbGluZXMgYXJlIHBhaXJlZCAoZS5nLiwgb2xkTmFtZSB3aXRoIG5ld05hbWUpXG4gKiA0LiBXb3JkLWxldmVsIGRpZmZpbmcgaWRlbnRpZmllcyBzcGVjaWZpYyBjaGFuZ2VkIHBhcnRzOlxuICogICAgW1xuICogICAgICB7IHZhbHVlOiAnZnVuY3Rpb24gJywgYWRkZWQ6IHVuZGVmaW5lZCwgcmVtb3ZlZDogdW5kZWZpbmVkIH0sICAvLyBDb21tb25cbiAqICAgICAgeyB2YWx1ZTogJ29sZE5hbWUnLCByZW1vdmVkOiB0cnVlIH0sICAgICAgICAgICAgICAgICAgICAgICAgICAgLy8gUmVtb3ZlZFxuICogICAgICB7IHZhbHVlOiAnbmV3TmFtZScsIGFkZGVkOiB0cnVlIH0sICAgICAgICAgICAgICAgICAgICAgICAgICAgICAvLyBBZGRlZFxuICogICAgICB7IHZhbHVlOiAnKHBhcmFtKSB7JywgYWRkZWQ6IHVuZGVmaW5lZCwgcmVtb3ZlZDogdW5kZWZpbmVkIH0gICAvLyBDb21tb25cbiAqICAgIF1cbiAqIDUuIFJlbmRlcnMgd2l0aCBlbmhhbmNlZCBoaWdobGlnaHRpbmc6XG4gKiAgICAtIENvbW1vbiBwYXJ0cyBhcmUgc2hvd24gbm9ybWFsbHlcbiAqICAgIC0gUmVtb3ZlZCB3b3JkcyBnZXQgYSBkYXJrZXIgcmVkIGJhY2tncm91bmRcbiAqICAgIC0gQWRkZWQgd29
|