@@ -37,6 +37,53 @@ import { createFileSearchIndexService } from "./fileSearchIndexService";
3737
3838const MAX_EDITOR_READ_BYTES = 5 * 1024 * 1024 ;
3939const GIT_STATUS_CACHE_TTL_MS = 5_000 ;
40+ const TEXT_EXTENSIONS = new Set ( [
41+ ".bash" ,
42+ ".c" ,
43+ ".cc" ,
44+ ".cfg" ,
45+ ".cjs" ,
46+ ".conf" ,
47+ ".cpp" ,
48+ ".cs" ,
49+ ".css" ,
50+ ".csv" ,
51+ ".cts" ,
52+ ".env" ,
53+ ".fish" ,
54+ ".go" ,
55+ ".h" ,
56+ ".hpp" ,
57+ ".html" ,
58+ ".ini" ,
59+ ".java" ,
60+ ".js" ,
61+ ".json" ,
62+ ".jsonc" ,
63+ ".jsx" ,
64+ ".less" ,
65+ ".log" ,
66+ ".md" ,
67+ ".mdx" ,
68+ ".mjs" ,
69+ ".mts" ,
70+ ".py" ,
71+ ".rb" ,
72+ ".rs" ,
73+ ".sass" ,
74+ ".scss" ,
75+ ".sh" ,
76+ ".sql" ,
77+ ".swift" ,
78+ ".toml" ,
79+ ".ts" ,
80+ ".tsx" ,
81+ ".txt" ,
82+ ".xml" ,
83+ ".yaml" ,
84+ ".yml" ,
85+ ".zsh" ,
86+ ] ) ;
4087
4188function containsDotGit ( absPath : string ) : boolean {
4289 const parts = absPath . split ( path . sep ) ;
@@ -45,6 +92,7 @@ function containsDotGit(absPath: string): boolean {
4592
4693function languageIdFromPath ( relPath : string ) : string {
4794 const ext = path . extname ( relPath ) . toLowerCase ( ) ;
95+ if ( isImagePath ( relPath ) ) return "image" ;
4896 if ( ext === ".ts" || ext === ".tsx" ) return "typescript" ;
4997 if ( ext === ".js" || ext === ".jsx" || ext === ".mjs" || ext === ".cjs" ) return "javascript" ;
5098 if ( ext === ".json" ) return "json" ;
@@ -61,6 +109,62 @@ function languageIdFromPath(relPath: string): string {
61109 return "plaintext" ;
62110}
63111
112+ function isImagePath ( relPath : string ) : boolean {
113+ return inferImageMimeType ( relPath ) !== null ;
114+ }
115+
116+ function inferImageMimeType ( relPath : string ) : string | null {
117+ const ext = path . extname ( relPath ) . toLowerCase ( ) ;
118+ switch ( ext ) {
119+ case ".avif" :
120+ return "image/avif" ;
121+ case ".bmp" :
122+ return "image/bmp" ;
123+ case ".gif" :
124+ return "image/gif" ;
125+ case ".ico" :
126+ case ".cur" :
127+ return "image/x-icon" ;
128+ case ".jpg" :
129+ case ".jpeg" :
130+ case ".jfif" :
131+ case ".pjpeg" :
132+ case ".pjp" :
133+ return "image/jpeg" ;
134+ case ".png" :
135+ return "image/png" ;
136+ case ".svg" :
137+ return "image/svg+xml" ;
138+ case ".webp" :
139+ return "image/webp" ;
140+ default :
141+ return null ;
142+ }
143+ }
144+
145+ function looksLikeBinary ( buf : Buffer , relPath : string ) : boolean {
146+ if ( hasNullByte ( buf ) ) return true ;
147+ if ( TEXT_EXTENSIONS . has ( path . extname ( relPath ) . toLowerCase ( ) ) ) return false ;
148+
149+ const sample = buf . subarray ( 0 , Math . min ( buf . length , 8192 ) ) ;
150+ if ( sample . length === 0 ) return false ;
151+
152+ const decoded = sample . toString ( "utf8" ) ;
153+ const replacementChars = decoded . match ( / \uFFFD / g) ?. length ?? 0 ;
154+ if ( replacementChars > 0 ) {
155+ return replacementChars / decoded . length > 0.01 ;
156+ }
157+
158+ let suspiciousControlChars = 0 ;
159+ for ( const byte of sample ) {
160+ const isAllowedWhitespace = byte === 9 || byte === 10 || byte === 12 || byte === 13 ;
161+ if ( byte < 32 && ! isAllowedWhitespace ) {
162+ suspiciousControlChars += 1 ;
163+ }
164+ }
165+ return suspiciousControlChars / sample . length > 0.3 ;
166+ }
167+
64168function isAlwaysIgnoredPath ( normalized : string ) : boolean {
65169 return (
66170 normalized . startsWith ( ".git/" ) ||
@@ -366,7 +470,6 @@ export function createFileService({
366470 if ( await isIgnoredPath ( rootPath , rel , includeIgnored ) ) continue ;
367471 if ( entry . name === ".git" ) continue ;
368472
369- const childAbs = path . join ( dirPath , entry . name ) ;
370473 const node : FileTreeNode = {
371474 name : entry . name ,
372475 path : rel ,
@@ -443,13 +546,29 @@ export function createFileService({
443546 ) ;
444547 }
445548 const buf = fs . readFileSync ( absPath ) ;
446- const isBinary = hasNullByte ( buf ) ;
549+ const imageMimeType = inferImageMimeType ( normalizedRel ) ;
550+ if ( imageMimeType ) {
551+ const base64 = buf . toString ( "base64" ) ;
552+ return {
553+ content : base64 ,
554+ encoding : "base64" ,
555+ size : stat . size ,
556+ languageId : languageIdFromPath ( normalizedRel ) ,
557+ isBinary : true ,
558+ previewKind : "image" ,
559+ mimeType : imageMimeType ,
560+ dataUrl : `data:${ imageMimeType } ;base64,${ base64 } ` ,
561+ } ;
562+ }
563+ const isBinary = looksLikeBinary ( buf , normalizedRel ) ;
447564 return {
448- content : isBinary ? "" : buf . toString ( "utf8" ) ,
449- encoding : "utf-8" ,
565+ content : isBinary ? buf . toString ( "base64" ) : buf . toString ( "utf8" ) ,
566+ encoding : isBinary ? "base64" : "utf-8" ,
450567 size : stat . size ,
451568 languageId : languageIdFromPath ( normalizedRel ) ,
452- isBinary
569+ isBinary,
570+ previewKind : isBinary ? "binary" : "text" ,
571+ mimeType : null ,
453572 } ;
454573 } ,
455574
0 commit comments