1+ import * as vscode from "vscode"
2+ import fs from "fs/promises"
3+ import * as path from "path"
4+
5+ // File size limit: 10MB
6+ const MAX_FILE_SIZE = 10 * 1024 * 1024
7+
8+ // Supported file types with their MIME types
9+ const FILE_TYPE_CATEGORIES = {
10+ images : [ "png" , "jpg" , "jpeg" , "gif" , "bmp" , "webp" , "svg" ] ,
11+ documents : [ "pdf" , "doc" , "docx" , "txt" , "rtf" , "odt" , "md" ] ,
12+ code : [ "js" , "ts" , "py" , "java" , "cpp" , "c" , "h" , "hpp" , "html" , "css" , "json" , "xml" , "yaml" , "yml" , "php" , "rb" , "go" , "rs" , "swift" , "kt" , "scala" , "sh" , "bat" , "ps1" ] ,
13+ data : [ "csv" , "xls" , "xlsx" , "sql" , "db" , "sqlite" ] ,
14+ archives : [ "zip" , "rar" , "tar" , "gz" , "7z" ] ,
15+ config : [ "ini" , "conf" , "config" , "env" , "properties" ] ,
16+ }
17+
18+ // Text file extensions that should be read as content
19+ const TEXT_FILE_EXTENSIONS = [
20+ ...FILE_TYPE_CATEGORIES . code ,
21+ ...FILE_TYPE_CATEGORIES . documents . filter ( ext => [ "txt" , "md" ] . includes ( ext ) ) ,
22+ ...FILE_TYPE_CATEGORIES . config ,
23+ ...FILE_TYPE_CATEGORIES . data . filter ( ext => [ "csv" , "sql" ] . includes ( ext ) ) ,
24+ ]
25+
26+ export interface ProcessedFile {
27+ name : string
28+ path : string
29+ size : number
30+ type : string
31+ category : string
32+ content ?: string // For text files
33+ dataUrl ?: string // For images and binary files
34+ error ?: string
35+ }
36+
37+ export async function selectFiles ( ) : Promise < ProcessedFile [ ] > {
38+ const options : vscode . OpenDialogOptions = {
39+ canSelectMany : true ,
40+ openLabel : "Select" ,
41+ filters : {
42+ "All Files" : [ "*" ] ,
43+ "Images" : FILE_TYPE_CATEGORIES . images ,
44+ "Documents" : FILE_TYPE_CATEGORIES . documents ,
45+ "Code Files" : FILE_TYPE_CATEGORIES . code ,
46+ "Data Files" : FILE_TYPE_CATEGORIES . data ,
47+ "Archives" : FILE_TYPE_CATEGORIES . archives ,
48+ "Config Files" : FILE_TYPE_CATEGORIES . config ,
49+ } ,
50+ }
51+
52+ const fileUris = await vscode . window . showOpenDialog ( options )
53+
54+ if ( ! fileUris || fileUris . length === 0 ) {
55+ return [ ]
56+ }
57+
58+ return await Promise . all (
59+ fileUris . map ( async ( uri : vscode . Uri ) => {
60+ try {
61+ return await processFile ( uri . fsPath )
62+ } catch ( error ) {
63+ const fileName = path . basename ( uri . fsPath )
64+ return {
65+ name : fileName ,
66+ path : uri . fsPath ,
67+ size : 0 ,
68+ type : "unknown" ,
69+ category : "unknown" ,
70+ error : error instanceof Error ? error . message : String ( error ) ,
71+ }
72+ }
73+ } ) ,
74+ )
75+ }
76+
77+ export async function processFile ( filePath : string ) : Promise < ProcessedFile > {
78+ const fileName = path . basename ( filePath )
79+ const fileExt = path . extname ( filePath ) . toLowerCase ( ) . slice ( 1 )
80+
81+ // Check file size
82+ const stats = await fs . stat ( filePath )
83+ if ( stats . size > MAX_FILE_SIZE ) {
84+ throw new Error ( `File size (${ formatFileSize ( stats . size ) } ) exceeds the 10MB limit` )
85+ }
86+
87+ const mimeType = getMimeType ( filePath )
88+ const category = getFileCategory ( fileExt )
89+
90+ const processedFile : ProcessedFile = {
91+ name : fileName ,
92+ path : filePath ,
93+ size : stats . size ,
94+ type : mimeType ,
95+ category,
96+ }
97+
98+ // Handle different file types
99+ if ( category === "images" ) {
100+ // Process images as data URLs (existing behavior)
101+ const buffer = await fs . readFile ( filePath )
102+ const base64 = buffer . toString ( "base64" )
103+ processedFile . dataUrl = `data:${ mimeType } ;base64,${ base64 } `
104+ } else if ( isTextFile ( fileExt ) ) {
105+ // Read text files as content
106+ try {
107+ processedFile . content = await fs . readFile ( filePath , "utf-8" )
108+ } catch ( error ) {
109+ // If UTF-8 reading fails, treat as binary
110+ const buffer = await fs . readFile ( filePath )
111+ const base64 = buffer . toString ( "base64" )
112+ processedFile . dataUrl = `data:${ mimeType } ;base64,${ base64 } `
113+ }
114+ } else {
115+ // Handle binary files as base64
116+ const buffer = await fs . readFile ( filePath )
117+ const base64 = buffer . toString ( "base64" )
118+ processedFile . dataUrl = `data:${ mimeType } ;base64,${ base64 } `
119+ }
120+
121+ return processedFile
122+ }
123+
124+ // Backward compatibility function for existing image functionality
125+ export async function selectImages ( ) : Promise < string [ ] > {
126+ const options : vscode . OpenDialogOptions = {
127+ canSelectMany : true ,
128+ openLabel : "Select" ,
129+ filters : {
130+ Images : FILE_TYPE_CATEGORIES . images ,
131+ } ,
132+ }
133+
134+ const fileUris = await vscode . window . showOpenDialog ( options )
135+
136+ if ( ! fileUris || fileUris . length === 0 ) {
137+ return [ ]
138+ }
139+
140+ return await Promise . all (
141+ fileUris . map ( async ( uri : vscode . Uri ) => {
142+ const filePath = uri . fsPath
143+ const buffer = await fs . readFile ( filePath )
144+ const base64 = buffer . toString ( "base64" )
145+ const mimeType = getMimeType ( filePath )
146+ return `data:${ mimeType } ;base64,${ base64 } `
147+ } ) ,
148+ )
149+ }
150+
151+ function getMimeType ( filePath : string ) : string {
152+ const ext = path . extname ( filePath ) . toLowerCase ( )
153+
154+ // Image types
155+ switch ( ext ) {
156+ case ".png" : return "image/png"
157+ case ".jpeg" :
158+ case ".jpg" : return "image/jpeg"
159+ case ".gif" : return "image/gif"
160+ case ".bmp" : return "image/bmp"
161+ case ".webp" : return "image/webp"
162+ case ".svg" : return "image/svg+xml"
163+
164+ // Document types
165+ case ".pdf" : return "application/pdf"
166+ case ".doc" : return "application/msword"
167+ case ".docx" : return "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
168+ case ".txt" : return "text/plain"
169+ case ".rtf" : return "application/rtf"
170+ case ".odt" : return "application/vnd.oasis.opendocument.text"
171+ case ".md" : return "text/markdown"
172+
173+ // Code types
174+ case ".js" : return "text/javascript"
175+ case ".ts" : return "text/typescript"
176+ case ".py" : return "text/x-python"
177+ case ".java" : return "text/x-java-source"
178+ case ".cpp" :
179+ case ".c" : return "text/x-c"
180+ case ".h" :
181+ case ".hpp" : return "text/x-c"
182+ case ".html" : return "text/html"
183+ case ".css" : return "text/css"
184+ case ".json" : return "application/json"
185+ case ".xml" : return "application/xml"
186+ case ".yaml" :
187+ case ".yml" : return "application/x-yaml"
188+ case ".php" : return "text/x-php"
189+ case ".rb" : return "text/x-ruby"
190+ case ".go" : return "text/x-go"
191+ case ".rs" : return "text/x-rust"
192+ case ".swift" : return "text/x-swift"
193+ case ".kt" : return "text/x-kotlin"
194+ case ".scala" : return "text/x-scala"
195+ case ".sh" : return "text/x-shellscript"
196+ case ".bat" : return "text/x-msdos-batch"
197+ case ".ps1" : return "text/x-powershell"
198+
199+ // Data types
200+ case ".csv" : return "text/csv"
201+ case ".xls" : return "application/vnd.ms-excel"
202+ case ".xlsx" : return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
203+ case ".sql" : return "text/x-sql"
204+ case ".db" :
205+ case ".sqlite" : return "application/x-sqlite3"
206+
207+ // Archive types
208+ case ".zip" : return "application/zip"
209+ case ".rar" : return "application/x-rar-compressed"
210+ case ".tar" : return "application/x-tar"
211+ case ".gz" : return "application/gzip"
212+ case ".7z" : return "application/x-7z-compressed"
213+
214+ // Config types
215+ case ".ini" : return "text/plain"
216+ case ".conf" :
217+ case ".config" : return "text/plain"
218+ case ".env" : return "text/plain"
219+ case ".properties" : return "text/plain"
220+
221+ default : return "application/octet-stream"
222+ }
223+ }
224+
225+ function getFileCategory ( extension : string ) : string {
226+ for ( const [ category , extensions ] of Object . entries ( FILE_TYPE_CATEGORIES ) ) {
227+ if ( extensions . includes ( extension ) ) {
228+ return category
229+ }
230+ }
231+ return "other"
232+ }
233+
234+ function isTextFile ( extension : string ) : boolean {
235+ return TEXT_FILE_EXTENSIONS . includes ( extension )
236+ }
237+
238+ function formatFileSize ( bytes : number ) : string {
239+ if ( bytes === 0 ) return "0 Bytes"
240+
241+ const k = 1024
242+ const sizes = [ "Bytes" , "KB" , "MB" , "GB" ]
243+ const i = Math . floor ( Math . log ( bytes ) / Math . log ( k ) )
244+
245+ return parseFloat ( ( bytes / Math . pow ( k , i ) ) . toFixed ( 2 ) ) + " " + sizes [ i ]
246+ }
0 commit comments