Improve diff rendering pipeline
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
import { parseDiffFromFile, parsePatchFiles, type FileDiffMetadata } from '@pierre/diffs';
|
||||
|
||||
const PATCH_DIFF_CACHE_LIMIT = 64;
|
||||
const patchFileDiffCache = new Map<string, FileDiffMetadata>();
|
||||
|
||||
export const fileDiffFromContent = (file: string, before: string, after: string): FileDiffMetadata => {
|
||||
if (!before && !after) {
|
||||
return emptyFileDiff(file);
|
||||
}
|
||||
|
||||
return parseDiffFromFile(
|
||||
{ name: file, contents: before },
|
||||
{ name: file, contents: after },
|
||||
);
|
||||
};
|
||||
|
||||
export const fileDiffFromPatch = (file: string, patch: string): FileDiffMetadata => {
|
||||
const key = `${file}\0${patch}`;
|
||||
const cached = patchFileDiffCache.get(key);
|
||||
if (cached) {
|
||||
patchFileDiffCache.delete(key);
|
||||
patchFileDiffCache.set(key, cached);
|
||||
return cached;
|
||||
}
|
||||
|
||||
const completeContents = completePatchContents(patch);
|
||||
const value = completeContents
|
||||
? fileDiffFromContent(file, completeContents.before, completeContents.after)
|
||||
: (parsePatchFiles(withPatchHeader(file, patch))[0]?.files[0] ?? emptyFileDiff(file));
|
||||
patchFileDiffCache.set(key, value);
|
||||
|
||||
while (patchFileDiffCache.size > PATCH_DIFF_CACHE_LIMIT) {
|
||||
const firstKey = patchFileDiffCache.keys().next().value;
|
||||
if (!firstKey) break;
|
||||
patchFileDiffCache.delete(firstKey);
|
||||
}
|
||||
|
||||
return value;
|
||||
};
|
||||
|
||||
const withPatchHeader = (file: string, patch: string): string => {
|
||||
if (!patch.trim()) {
|
||||
return patch;
|
||||
}
|
||||
|
||||
if (patch.startsWith('diff --git ') || /^--- [^\n]*\r?\n\+\+\+ /m.test(patch)) {
|
||||
return patch;
|
||||
}
|
||||
|
||||
return `Index: ${file}\n===================================================================\n--- ${file}\t\n+++ ${file}\t\n${patch}`;
|
||||
};
|
||||
|
||||
const completePatchContents = (patch: string): { before: string; after: string } | undefined => {
|
||||
if (!patch.startsWith('diff --git ') && !/^--- [^\n]*\t?\r?\n\+\+\+ [^\n]*\t?(?:\r?\n|$)/m.test(patch)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const hunkMatches = [...patch.matchAll(/^@@\s+-(\d+)(?:,\d+)?\s+\+(\d+)(?:,\d+)?\s+@@[^\n]*(?:\r?\n|$)/gm)];
|
||||
if (hunkMatches.length !== 1) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const hunk = hunkMatches[0];
|
||||
const oldStart = Number.parseInt(hunk[1] ?? '', 10);
|
||||
const newStart = Number.parseInt(hunk[2] ?? '', 10);
|
||||
if (oldStart > 1 || newStart > 1 || !Number.isFinite(oldStart) || !Number.isFinite(newStart)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const hunkStart = (hunk.index ?? 0) + hunk[0].length;
|
||||
const body = patch.slice(hunkStart);
|
||||
const before: Array<{ text: string; newline: boolean }> = [];
|
||||
const after: Array<{ text: string; newline: boolean }> = [];
|
||||
let previous: '-' | '+' | ' ' | undefined;
|
||||
|
||||
for (const rawLine of body.split(/\r?\n/)) {
|
||||
if (rawLine.startsWith('diff --git ') || rawLine.startsWith('@@ ')) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (rawLine.startsWith('\\')) {
|
||||
if (previous === '-' || previous === ' ') {
|
||||
const value = before.at(-1);
|
||||
if (value) value.newline = false;
|
||||
}
|
||||
if (previous === '+' || previous === ' ') {
|
||||
const value = after.at(-1);
|
||||
if (value) value.newline = false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (rawLine.startsWith('-')) {
|
||||
before.push({ text: rawLine.slice(1), newline: true });
|
||||
previous = '-';
|
||||
continue;
|
||||
}
|
||||
|
||||
if (rawLine.startsWith('+')) {
|
||||
after.push({ text: rawLine.slice(1), newline: true });
|
||||
previous = '+';
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!rawLine.startsWith(' ')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
before.push({ text: rawLine.slice(1), newline: true });
|
||||
after.push({ text: rawLine.slice(1), newline: true });
|
||||
previous = ' ';
|
||||
}
|
||||
|
||||
return {
|
||||
before: joinPatchLines(before),
|
||||
after: joinPatchLines(after),
|
||||
};
|
||||
};
|
||||
|
||||
const joinPatchLines = (lines: Array<{ text: string; newline: boolean }>): string =>
|
||||
lines.map((line) => line.text + (line.newline ? '\n' : '')).join('');
|
||||
|
||||
const emptyFileDiff = (file: string): FileDiffMetadata =>
|
||||
parseDiffFromFile({ name: file, contents: '' }, { name: file, contents: '' });
|
||||
@@ -1198,9 +1198,14 @@ export const dict = {
|
||||
'diffView.state.failedToLoadDiff': 'Failed to load diff',
|
||||
'diffView.state.loadingDiff': 'Loading diff...',
|
||||
'diffView.state.loadingChanges': 'Loading changes...',
|
||||
'diffView.state.largeDiff': 'Large diff ({count} changed lines)',
|
||||
'diffView.state.largeDiffDescription': 'Rendering may be slow. You can still view the diff by clicking below.',
|
||||
'diffView.summary.changedFilesSingle': '{count} file changed',
|
||||
'diffView.summary.changedFilesPlural': '{count} files changed',
|
||||
'diffView.actions.retry': 'Retry',
|
||||
'diffView.actions.renderAnyway': 'Render anyway',
|
||||
'diffView.actions.expandAll': 'Expand all',
|
||||
'diffView.actions.collapseAll': 'Collapse all',
|
||||
'diffView.actions.disableLineWrap': 'Disable line wrap',
|
||||
'diffView.actions.enableLineWrap': 'Enable line wrap',
|
||||
'diffView.actions.openFileInEditorAtChange': 'Open this file in editor at change',
|
||||
|
||||
@@ -1164,9 +1164,14 @@ export const dict: Record<I18nKey, string> = {
|
||||
"diffView.state.failedToLoadDiff": "No se pudo cargar la diferencia",
|
||||
"diffView.state.loadingDiff": "Cargando diff...",
|
||||
"diffView.state.loadingChanges": "Cargando cambios...",
|
||||
"diffView.state.largeDiff": "Diff grande ({count} líneas modificadas)",
|
||||
"diffView.state.largeDiffDescription": "El renderizado puede ser lento. Aun así puedes ver el diff con el botón de abajo.",
|
||||
"diffView.summary.changedFilesSingle": "{count} archivo modificado",
|
||||
"diffView.summary.changedFilesPlural": "{count} archivos modificados",
|
||||
"diffView.actions.retry": "Volver a intentar",
|
||||
"diffView.actions.renderAnyway": "Renderizar de todos modos",
|
||||
"diffView.actions.expandAll": "Expandir todo",
|
||||
"diffView.actions.collapseAll": "Contraer todo",
|
||||
"diffView.actions.disableLineWrap": "Desactivar ajuste de línea",
|
||||
"diffView.actions.enableLineWrap": "Activar ajuste de línea",
|
||||
"diffView.actions.openFileInEditorAtChange": "Abrir este archivo en el editor en el cambio",
|
||||
|
||||
@@ -1071,9 +1071,14 @@ export const dict = {
|
||||
'diffView.state.failedToLoadDiff': 'Échec du chargement du différentiel',
|
||||
'diffView.state.loadingDiff': 'Chargement du différentiel...',
|
||||
'diffView.state.loadingChanges': 'Chargement des modifications...',
|
||||
'diffView.state.largeDiff': 'Diff volumineux ({count} lignes modifiées)',
|
||||
'diffView.state.largeDiffDescription': 'Le rendu peut être lent. Vous pouvez tout de même afficher le diff avec le bouton ci-dessous.',
|
||||
'diffView.summary.changedFilesSingle': 'Le fichier {count} a été modifié',
|
||||
'diffView.summary.changedFilesPlural': 'Fichiers {count} modifiés',
|
||||
'diffView.actions.retry': 'Réessayer',
|
||||
'diffView.actions.renderAnyway': 'Afficher quand même',
|
||||
'diffView.actions.expandAll': 'Tout développer',
|
||||
'diffView.actions.collapseAll': 'Tout réduire',
|
||||
'diffView.actions.disableLineWrap': 'Désactiver le retour à la ligne',
|
||||
'diffView.actions.enableLineWrap': 'Activer le retour à la ligne',
|
||||
'diffView.actions.openFileInEditorAtChange': 'Ouvrez ce fichier dans l\'éditeur lors du changement',
|
||||
|
||||
@@ -1201,9 +1201,14 @@ export const dict: Record<I18nKey, string> = {
|
||||
'diffView.state.failedToLoadDiff': '변경사항을 불러오지 못했습니다',
|
||||
'diffView.state.loadingDiff': '변경사항 불러오는 중…',
|
||||
'diffView.state.loadingChanges': '변경 사항 로드 중…',
|
||||
'diffView.state.largeDiff': '큰 diff({count}개 변경 줄)',
|
||||
'diffView.state.largeDiffDescription': '렌더링이 느릴 수 있습니다. 아래 버튼으로 diff를 계속 볼 수 있습니다.',
|
||||
'diffView.summary.changedFilesSingle': '파일 {count}개 변경됨',
|
||||
'diffView.summary.changedFilesPlural': '파일 {count}개 변경됨',
|
||||
'diffView.actions.retry': '다시 시도',
|
||||
'diffView.actions.renderAnyway': '그래도 렌더링',
|
||||
'diffView.actions.expandAll': '모두 펼치기',
|
||||
'diffView.actions.collapseAll': '모두 접기',
|
||||
'diffView.actions.disableLineWrap': '줄 바꿈 끄기',
|
||||
'diffView.actions.enableLineWrap': '줄 바꿈 켜기',
|
||||
'diffView.actions.openFileInEditorAtChange': '변경 위치에서 이 파일을 에디터로 열기',
|
||||
|
||||
@@ -1410,8 +1410,11 @@ export const dict: Record<I18nKey, string> = {
|
||||
'desktopHostSwitcher.toast.sshFailedToConnect': 'Nie udało się połączyć z instancją SSH „{host}”',
|
||||
'diffView.actions.disableLineWrap': 'Wyłącz zawijanie linii',
|
||||
'diffView.actions.enableLineWrap': 'Włącz zawijanie linii',
|
||||
'diffView.actions.expandAll': 'Rozwiń wszystko',
|
||||
'diffView.actions.collapseAll': 'Zwiń wszystko',
|
||||
'diffView.actions.openFileAtFirstChangedLine': 'Otwórz plik na pierwszej zmienionej linii',
|
||||
'diffView.actions.openFileInEditorAtChange': 'Otwórz plik w edytorze na zmianie',
|
||||
'diffView.actions.renderAnyway': 'Renderuj mimo to',
|
||||
'diffView.actions.retry': 'Ponów',
|
||||
'diffView.binary.unavailable': 'Nie można wyświetlić zawartości tego pliku.',
|
||||
'diffView.change.copied': 'Skopiowany plik',
|
||||
@@ -1429,6 +1432,8 @@ export const dict: Record<I18nKey, string> = {
|
||||
'diffView.selector.selectFile': 'Wybierz plik',
|
||||
'diffView.state.cleanWorkingTree': 'Drzewo robocze jest czyste, brak zmian do wyświetlenia',
|
||||
'diffView.state.failedToLoadDiff': 'Nie udało się wczytać diffu',
|
||||
'diffView.state.largeDiff': 'Duży diff ({count} zmienionych linii)',
|
||||
'diffView.state.largeDiffDescription': 'Renderowanie może być wolne. Nadal możesz wyświetlić diff przyciskiem poniżej.',
|
||||
'diffView.state.loadingChanges': 'Ładowanie zmian...',
|
||||
'diffView.state.loadingDiff': 'Ładowanie diffu...',
|
||||
'diffView.state.loadingRepositoryStatus': 'Ładowanie stanu repozytorium...',
|
||||
|
||||
@@ -1164,9 +1164,14 @@ export const dict: Record<I18nKey, string> = {
|
||||
"diffView.state.failedToLoadDiff": "Não foi possível carregar a diferencia",
|
||||
"diffView.state.loadingDiff": "Carregando diff...",
|
||||
"diffView.state.loadingChanges": "Carregando alterações...",
|
||||
"diffView.state.largeDiff": "Diff grande ({count} linhas alteradas)",
|
||||
"diffView.state.largeDiffDescription": "A renderização pode ser lenta. Você ainda pode ver o diff pelo botão abaixo.",
|
||||
"diffView.summary.changedFilesSingle": "{count} arquivo modificado",
|
||||
"diffView.summary.changedFilesPlural": "{count} arquivos modificados",
|
||||
"diffView.actions.retry": "Tentar novamente",
|
||||
"diffView.actions.renderAnyway": "Renderizar mesmo assim",
|
||||
"diffView.actions.expandAll": "Expandir tudo",
|
||||
"diffView.actions.collapseAll": "Recolher tudo",
|
||||
"diffView.actions.disableLineWrap": "Desativar ajuste de linha",
|
||||
"diffView.actions.enableLineWrap": "Ativar ajuste de linha",
|
||||
"diffView.actions.openFileInEditorAtChange": "Abrir este arquivo no editor nesta alteração",
|
||||
|
||||
@@ -1164,9 +1164,14 @@ export const dict: Record<I18nKey, string> = {
|
||||
"diffView.state.failedToLoadDiff": "Не вдалося завантажити diff",
|
||||
"diffView.state.loadingDiff": "Завантаження diff...",
|
||||
"diffView.state.loadingChanges": "Завантаження змін...",
|
||||
"diffView.state.largeDiff": "Великий diff ({count} змінених рядків)",
|
||||
"diffView.state.largeDiffDescription": "Рендеринг може бути повільним. Ви все одно можете переглянути diff кнопкою нижче.",
|
||||
"diffView.summary.changedFilesSingle": "Змінено файл: {count}",
|
||||
"diffView.summary.changedFilesPlural": "Змінено файлів: {count}",
|
||||
"diffView.actions.retry": "Повторити спробу",
|
||||
"diffView.actions.renderAnyway": "Все одно відрендерити",
|
||||
"diffView.actions.expandAll": "Розгорнути все",
|
||||
"diffView.actions.collapseAll": "Згорнути все",
|
||||
"diffView.actions.disableLineWrap": "Вимкнути перенос рядків",
|
||||
"diffView.actions.enableLineWrap": "Увімкнути перенос рядків",
|
||||
"diffView.actions.openFileInEditorAtChange": "Відкрити цей файл у редакторі на зміні",
|
||||
|
||||
@@ -1164,9 +1164,14 @@ export const dict: Record<I18nKey, string> = {
|
||||
'diffView.state.failedToLoadDiff': '加载差异失败',
|
||||
'diffView.state.loadingDiff': '正在加载差异...',
|
||||
'diffView.state.loadingChanges': '正在加载变更...',
|
||||
'diffView.state.largeDiff': '大型差异({count} 行变更)',
|
||||
'diffView.state.largeDiffDescription': '渲染可能较慢。你仍可点击下方按钮查看差异。',
|
||||
'diffView.summary.changedFilesSingle': '{count} 个文件已变更',
|
||||
'diffView.summary.changedFilesPlural': '{count} 个文件已变更',
|
||||
'diffView.actions.retry': '重试',
|
||||
'diffView.actions.renderAnyway': '仍然渲染',
|
||||
'diffView.actions.expandAll': '全部展开',
|
||||
'diffView.actions.collapseAll': '全部折叠',
|
||||
'diffView.actions.disableLineWrap': '关闭自动换行',
|
||||
'diffView.actions.enableLineWrap': '开启自动换行',
|
||||
'diffView.actions.openFileInEditorAtChange': '在编辑器中打开此文件并定位变更',
|
||||
|
||||
@@ -1174,9 +1174,14 @@ export const dict: Record<I18nKey, string> = {
|
||||
'diffView.state.failedToLoadDiff': '載入差異失敗',
|
||||
'diffView.state.loadingDiff': '正在載入差異...',
|
||||
'diffView.state.loadingChanges': '正在載入變更...',
|
||||
'diffView.state.largeDiff': '大型差異({count} 行變更)',
|
||||
'diffView.state.largeDiffDescription': '渲染可能較慢。你仍可點擊下方按鈕查看差異。',
|
||||
'diffView.summary.changedFilesSingle': '{count} 個檔案已變更',
|
||||
'diffView.summary.changedFilesPlural': '{count} 個檔案已變更',
|
||||
'diffView.actions.retry': '重試',
|
||||
'diffView.actions.renderAnyway': '仍然渲染',
|
||||
'diffView.actions.expandAll': '全部展開',
|
||||
'diffView.actions.collapseAll': '全部折疊',
|
||||
'diffView.actions.disableLineWrap': '關閉自動換行',
|
||||
'diffView.actions.enableLineWrap': '開啟自動換行',
|
||||
'diffView.actions.openFileInEditorAtChange': '在編輯器中開啟此檔案並定位變更',
|
||||
|
||||
Reference in New Issue
Block a user