Skip to content

Commit a3bb85a

Browse files
Improve editor binary handling and perf tracing
1 parent a5bb1f3 commit a3bb85a

6 files changed

Lines changed: 82 additions & 11 deletions

File tree

src/features/editor/components/editor.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,7 @@ export function Editor({
279279
const lineHeight = useMemo(() => calculateLineHeight(fontSize), [fontSize]);
280280
const shouldVirtualizeRendering =
281281
lines.length >= EDITOR_CONSTANTS.RENDER_VIRTUALIZATION_THRESHOLD;
282-
const useIncrementalTokenization = hasSyntaxHighlighting;
282+
const useIncrementalTokenization = hasSyntaxHighlighting && shouldVirtualizeRendering;
283283

284284
const {
285285
viewportRange,

src/features/editor/components/layers/highlight-layer.tsx

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -304,12 +304,7 @@ export const HighlightLayer = memo(HighlightLayerComponent, (prev, next) => {
304304
);
305305
}
306306

307-
const tokensUnchanged =
308-
prev.tokens === next.tokens ||
309-
(prev.tokens.length === next.tokens.length &&
310-
(prev.tokens.length === 0 ||
311-
(prev.tokens[0]?.start === next.tokens[0]?.start &&
312-
prev.tokens[prev.tokens.length - 1]?.end === next.tokens[prev.tokens.length - 1]?.end)));
307+
const tokensUnchanged = prev.tokens === next.tokens;
313308

314309
// If only content changed but tokens haven't updated yet, skip the intermediate render.
315310
// The tokenizer will produce new tokens shortly, triggering a proper re-render then.

src/features/editor/hooks/use-performance.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,6 @@ export function usePerformanceMonitor(componentName: string) {
3535
const entries = performance.getEntriesByName(measureName);
3636
const lastEntry = entries[entries.length - 1];
3737
if (lastEntry) {
38-
console.info(
39-
`[Performance] [${componentName}] ${metricName}: ${lastEntry.duration.toFixed(2)}ms`,
40-
);
4138
frontendTrace(
4239
lastEntry.duration >= 250 ? "warn" : "info",
4340
"bench:perf",

src/features/file-system/controllers/file-utils.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,16 @@ export const isPdfFile = (path: string): boolean => {
118118
*/
119119
export const isBinaryFile = (path: string): boolean => {
120120
const lowerPath = path.toLowerCase();
121+
const binarySuffixes = [
122+
".tar.gz",
123+
".tgz",
124+
".tar.bz2",
125+
".tbz2",
126+
".tar.xz",
127+
".txz",
128+
".tar.zst",
129+
".tzst",
130+
];
121131
const binaryExtensions = [
122132
".wasm",
123133
".exe",
@@ -151,9 +161,41 @@ export const isBinaryFile = (path: string): boolean => {
151161
".dmg",
152162
".msi",
153163
];
164+
if (binarySuffixes.some((suffix) => lowerPath.endsWith(suffix))) {
165+
return true;
166+
}
154167
return binaryExtensions.some((ext) => lowerPath.endsWith(ext));
155168
};
156169

170+
/**
171+
* Best-effort binary sniffing for files with unknown extensions.
172+
* Treat null bytes and a high ratio of control bytes as unsupported for the text editor.
173+
*/
174+
export const isBinaryContent = (data: Uint8Array, sampleSize = 8192): boolean => {
175+
const limit = Math.min(data.length, sampleSize);
176+
if (limit === 0) return false;
177+
178+
let suspiciousBytes = 0;
179+
180+
for (let i = 0; i < limit; i++) {
181+
const byte = data[i];
182+
183+
if (byte === 0) {
184+
return true;
185+
}
186+
187+
const isPrintableAscii = byte >= 0x20 && byte <= 0x7e;
188+
const isCommonWhitespace = byte === 0x09 || byte === 0x0a || byte === 0x0d || byte === 0x0c;
189+
const isLikelyUtf8Lead = byte >= 0xc2;
190+
191+
if (!isPrintableAscii && !isCommonWhitespace && !isLikelyUtf8Lead) {
192+
suspiciousBytes++;
193+
}
194+
}
195+
196+
return suspiciousBytes / limit > 0.3;
197+
};
198+
157199
/**
158200
* Extract filename from a path
159201
*/

src/features/file-system/controllers/store.ts

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { invoke } from "@tauri-apps/api/core";
22
import { basename, dirname, extname, join } from "@tauri-apps/api/path";
3-
import { copyFile } from "@tauri-apps/plugin-fs";
3+
import { copyFile, readFile } from "@tauri-apps/plugin-fs";
44
import { revealItemInDir } from "@tauri-apps/plugin-opener";
55
import { create } from "zustand";
66
import { immer } from "zustand/middleware/immer";
@@ -53,6 +53,7 @@ import {
5353
import {
5454
getDatabaseTypeFromPath,
5555
getFilenameFromPath,
56+
isBinaryContent,
5657
isBinaryFile,
5758
isImageFile,
5859
isPdfFile,
@@ -743,6 +744,38 @@ export const useFileSystemStore = createSelectors(
743744
);
744745
fileOpenBenchmark.finish(path, "binary-buffer-opened");
745746
} else {
747+
if (!path.startsWith("remote://")) {
748+
try {
749+
const fileData = await readFile(resolvedPath);
750+
751+
if (isStaleRequest()) return;
752+
753+
if (isBinaryContent(fileData)) {
754+
openBuffer(
755+
path,
756+
fileName,
757+
"",
758+
false,
759+
undefined,
760+
false,
761+
false,
762+
undefined,
763+
false,
764+
false,
765+
false,
766+
undefined,
767+
false,
768+
false,
769+
true,
770+
);
771+
fileOpenBenchmark.finish(path, "binary-sniff-buffer-opened");
772+
return;
773+
}
774+
} catch (error) {
775+
console.error("Failed to inspect file bytes before opening:", error);
776+
}
777+
}
778+
746779
// Check if external editor is enabled for text files
747780
const { settings } = useSettingsStore.getState();
748781
const { openExternalEditorBuffer } = useBufferStore.getState().actions;

src/utils/frontend-trace.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ export function frontendTrace(
2727
message: string,
2828
payload?: Record<string, unknown>,
2929
) {
30+
if (scope.startsWith("bench:")) {
31+
return;
32+
}
33+
3034
void invoke("frontend_trace", {
3135
level,
3236
scope,

0 commit comments

Comments
 (0)