mirror of
https://github.com/stablyai/orca.git
synced 2026-09-22 08:02:28 +00:00
* fix(diff): close large-diff deferral review findings from #17521 Deferral keyed "no line counts" off the untracked area, which both prompted ordinary untracked binaries and silently auto-loaded every tracked row when a status pass skipped counting (entry cap hit, numstat failed) — the freeze case the deferral exists for. Decide from the path instead: rows that render as a preview or a binary stub stay automatic, everything Monaco would open as text defers. Also give all three combined-diff virtualizers one shared row estimate, so the PR-review viewers stop estimating a deferred/in-flight large row at 88px while DiffSectionItem renders it at 188px, and drop the dead isLoadOnDemand parameter that estimate covered. * fix(diff): stop deferring cheap uncounted rows the extension list misses The path-only rule relocated friction rather than removing it: every uncounted row deferred unless its extension was in BINARY_FILE_EXTENSIONS, so two classes of tracked row flipped to a "Large diffs are not rendered by default" prompt they had never shown. Tracked binaries outside the list (this repo's own resources/build/icon.icns, plus .tiff/.avif/.psd/.parquet and every extensionless binary) get '-\t-' from `git diff --numstat`, and a submodule whose only change is untracked content inside it gets no numstat row at all while porcelain v2 still reports `1 .M S..U ... sub`. Both are cheap, and both are unreachable from a hardcoded extension list — verified against real git. OR the extension check with two signals already on the entry. A submodule row diffs to a "Subproject commit" line or two whatever it contains, so it is always cheap. And an uncounted row whose siblings in the same pass DID get counts is uncounted for a reason of its own: for a tracked row that reason can only be numstat's binary marker. Untracked rows keep deferring either way, since the scan also skips them past MAX_UNTRACKED_LINE_COUNT_BYTES and their size is exactly what is unknown. No new field crosses git status, the wire, or the section cache; `submodule` and the sibling counts are already there. Fan-out, accepted deliberately: when a pass counts nothing at all — didHitLimit at DEFAULT_GIT_STATUS_LIMIT, or runNumstat returning null — no row has a counted sibling, so the whole combined diff renders as Load prompts. Keeping it. Over 1000 changed entries is precisely the freeze this deferral exists for, and auto-loading that many unbounded Monaco models is the bug, not the mitigation; a numstat failure leaves every size genuinely unknown. Each row still has its own Load diff button, so nothing is unreachable — the only thing missing is a bulk "load all", which would reinstate the freeze on demand. * fix(diff): scope the counted-siblings signal to one counting pass hasCountedSiblings was one boolean over the whole entries array, but that array is not one counting pass. combined-all — the default whenever a branch compare exists — concatenates uncommitted rows with branch-compare rows, and even within the uncommitted set staged and unstaged are separate numstat calls that fail separately. So a single counted branch row vouched for an uncommitted pass that counted nothing (numstat null, or didHitLimit at DEFAULT_GIT_STATUS_LIMIT), and every uncounted row in it auto-loaded into exactly the Monaco freeze the deferral exists to prevent: the guard was off in the default view. Collect the passes that actually counted something, keyed by staging area for status rows and 'compare' for branch/commit rows, and ask that set per row. Untracked rows are unaffected — they never consult the signal. Class 1 of the charter (tracked binaries outside BINARY_FILE_EXTENSIONS) stays open, deliberately. Porcelain v2 reports a modified binary as `1 .M N... 100644` — indistinguishable from text — so only `git diff --numstat`'s `-\t-` knows, and that stdout is parsed on the host (shared/git-uncommitted-line-stats.ts) for both the local and relay status paths. The renderer sees entries, not numstat, so surfacing it per row means a new field on GitStatusEntry and GitBranchChangeEntry that also has to be re-applied in two attachLineStats copies and in the line-stats reuse cache, which persists only {added, removed} and would silently drop it. The one existing field that could carry it — added/removed set to 0 — changes what the host publishes to old clients and mobile, contradicts the documented "undefined for binary files" contract, and collapses the undefined-vs-zero distinction the virtualizer's height estimate reads. So a lone tracked .icns still shows the load prompt; not worth a wire field, and not worth another hardcoded extension. * fix(diff): stop calling an uncounted diff large in the load prompt The deferral prompt had one sentence for two different reasons. A row over MAX_AUTOMATIC_DIFF_CHANGED_LINES really is large. A row with no counts at all — numstat's binary marker, a pass that skipped counting — is deferred because its size is unknown, and "Large diffs are not rendered by default." is simply false for it: a lone tracked resources/build/icon.icns with no counted sibling in its own pass is 4 KB and still says large. Split the copy on the counts the section already carries. No new field on the entry, nothing across the wire, no change to attachLineStats or the line-stats cache — the predicate is renderer-local and mirrors the uncounted branch of shouldLoadCombinedDiffOnDemand, so the two stay in step.
91 lines
1.8 KiB
TypeScript
91 lines
1.8 KiB
TypeScript
import { IMAGE_FILE_EXTENSIONS } from './image-file-extensions'
|
|
|
|
// SVG is an image format that editors open as source text, so it stays out of
|
|
// the binary set even though it lives in IMAGE_FILE_EXTENSIONS.
|
|
const TEXT_IMAGE_EXTENSIONS = new Set(['.svg'])
|
|
|
|
const NON_IMAGE_BINARY_EXTENSIONS = [
|
|
// Archives
|
|
'.7z',
|
|
'.bz2',
|
|
'.gz',
|
|
'.jar',
|
|
'.rar',
|
|
'.tar',
|
|
'.tgz',
|
|
'.war',
|
|
'.xz',
|
|
'.zip',
|
|
'.zst',
|
|
// Audio and video
|
|
'.aac',
|
|
'.avi',
|
|
'.flac',
|
|
'.m4a',
|
|
'.mkv',
|
|
'.mov',
|
|
'.mp3',
|
|
'.mp4',
|
|
'.ogg',
|
|
'.wav',
|
|
'.webm',
|
|
// Documents
|
|
'.doc',
|
|
'.docx',
|
|
'.pdf',
|
|
'.ppt',
|
|
'.pptx',
|
|
'.xls',
|
|
'.xlsx',
|
|
// Fonts
|
|
'.eot',
|
|
'.otf',
|
|
'.ttc',
|
|
'.ttf',
|
|
'.woff',
|
|
'.woff2',
|
|
// Compiled artifacts and datastores
|
|
'.a',
|
|
'.bin',
|
|
'.class',
|
|
'.dll',
|
|
'.dylib',
|
|
'.exe',
|
|
'.idx',
|
|
'.lockb',
|
|
'.node',
|
|
'.o',
|
|
'.pack',
|
|
'.pyc',
|
|
'.pyd',
|
|
'.so',
|
|
'.sqlite',
|
|
'.sqlite3',
|
|
'.wasm'
|
|
]
|
|
|
|
export const BINARY_FILE_EXTENSIONS: readonly string[] = Object.freeze([
|
|
...IMAGE_FILE_EXTENSIONS.filter((extension) => !TEXT_IMAGE_EXTENSIONS.has(extension)),
|
|
...NON_IMAGE_BINARY_EXTENSIONS
|
|
])
|
|
|
|
const BINARY_FILE_EXTENSION_SET = new Set(BINARY_FILE_EXTENSIONS)
|
|
|
|
/**
|
|
* Extension-only guess at "this file is not text". Content-based detection
|
|
* lives in `isBinaryBuffer`; use this only where the bytes are unavailable.
|
|
*/
|
|
export function hasBinaryFileExtension(filePath: string | undefined): boolean {
|
|
if (filePath === undefined) {
|
|
return false
|
|
}
|
|
const lowerPath = filePath.toLowerCase()
|
|
const dotIndex = lowerPath.lastIndexOf('.')
|
|
const separatorIndex = Math.max(lowerPath.lastIndexOf('/'), lowerPath.lastIndexOf('\\'))
|
|
// A leading dot is a dotfile (.gitignore), not an extension.
|
|
if (dotIndex <= separatorIndex + 1) {
|
|
return false
|
|
}
|
|
return BINARY_FILE_EXTENSION_SET.has(lowerPath.slice(dotIndex))
|
|
}
|