@@ -10,6 +10,25 @@ import { Infrastructure } from './infrastructure'
1010import { LanguageClientManager } from './languageClient'
1111import { MonacoLanguageClient } from './createLanguageClient'
1212
13+ async function bufferToBase64 ( buffer : ArrayBuffer | Uint8Array ) {
14+ // use a FileReader to generate a base64 data URI:
15+ const base64url = await new Promise < string > ( ( resolve ) => {
16+ const reader = new FileReader ( )
17+ reader . onload = ( ) => resolve ( reader . result as string )
18+ reader . readAsDataURL ( new Blob ( [ buffer ] ) )
19+ } )
20+ // remove the `data:...;base64,` part from the start
21+ return base64url . slice ( base64url . indexOf ( ',' ) + 1 )
22+ }
23+
24+ async function base64ToBufferAsync ( base64 : string ) {
25+ const dataUrl = `data:application/octet-binary;base64,${ base64 } `
26+
27+ const result = await fetch ( dataUrl )
28+ const buffer = await result . arrayBuffer ( )
29+ return new Uint8Array ( buffer )
30+ }
31+
1332interface ResolvedTextDocumentSyncCapabilities {
1433 resolvedTextDocumentSync ?: TextDocumentSyncOptions
1534}
@@ -31,7 +50,7 @@ export class InitializeTextDocumentFeature implements StaticFeature {
3150 const languageClient = this . languageClient
3251 async function saveFile ( textDocument : vscode . TextDocument ) {
3352 if ( documentSelector != null && vscode . languages . match ( documentSelector , textDocument ) > 0 && textDocument . uri . scheme === 'file' ) {
34- await infrastructure . saveFileContent ?.( textDocument . uri , textDocument . getText ( ) , languageClient )
53+ await infrastructure . writeFile ?.( textDocument . uri , btoa ( textDocument . getText ( ) ) , languageClient )
3554
3655 // Always send notification even if the server doesn't support it (because csharp register the didSave feature too late)
3756 await languageClient . sendNotification ( DidSaveTextDocumentNotification . type , {
@@ -76,30 +95,31 @@ export class WillDisposeFeature implements StaticFeature {
7695 clear ( ) : void { }
7796}
7897
79- const encoder = new TextEncoder ( )
80- const decoder = new TextDecoder ( )
81- class InfrastructureTextFileSystemProvider implements IFileSystemProviderWithFileReadWriteCapability {
98+ class InfrastructureFileSystemProvider implements IFileSystemProviderWithFileReadWriteCapability {
8299 capabilities = FileSystemProviderCapabilities . FileReadWrite | FileSystemProviderCapabilities . PathCaseSensitive | FileSystemProviderCapabilities . Readonly
83100 constructor ( private infrastructure : Infrastructure , private languageClientManager : LanguageClientManager ) {
84101 }
85102
86- private cachedContent : Map < string , Promise < string | undefined > > = new Map ( )
87- private async getFileContent ( resource : monaco . Uri ) : Promise < string | undefined > {
103+ private isBlacklisted ( resource : monaco . Uri ) {
88104 const REMOTE_FILE_BLACKLIST = [ '.git/config' , '.vscode' , monaco . Uri . parse ( this . infrastructure . rootUri ) . path ]
89105
90106 const blacklisted = REMOTE_FILE_BLACKLIST . some ( blacklisted => resource . path . endsWith ( blacklisted ) )
91- if ( blacklisted ) {
92- return undefined
93- }
94- if ( ! this . cachedContent . has ( resource . toString ( ) ) ) {
95- this . cachedContent . set ( resource . toString ( ) , this . infrastructure . getFileContent ! ( resource , this . languageClientManager ) )
96- }
97- return await this . cachedContent . get ( resource . toString ( ) )
107+ return blacklisted
98108 }
99109
100110 async readFile ( resource : monaco . Uri ) : Promise < Uint8Array > {
101- const content = await this . getFileContent ( resource )
102- return encoder . encode ( content )
111+ if ( this . isBlacklisted ( resource ) ) {
112+ throw FileSystemProviderError . create ( 'Not allowed' , FileSystemProviderErrorCode . NoPermissions )
113+ }
114+ try {
115+ const file = await this . infrastructure . readFile ! ( resource , this . languageClientManager )
116+ return await base64ToBufferAsync ( file )
117+ } catch ( err ) {
118+ if ( ( err as Error ) . message === 'File not found' ) {
119+ throw FileSystemProviderError . create ( err as Error , FileSystemProviderErrorCode . FileNotFound )
120+ }
121+ throw FileSystemProviderError . create ( err as Error , FileSystemProviderErrorCode . Unknown )
122+ }
103123 }
104124
105125 async writeFile ( ) : Promise < void > {
@@ -116,16 +136,22 @@ class InfrastructureTextFileSystemProvider implements IFileSystemProviderWithFil
116136
117137 async stat ( resource : monaco . Uri ) : Promise < IStat > {
118138 try {
119- const content = await this . getFileContent ( resource )
120- if ( content != null ) {
139+ if ( this . isBlacklisted ( resource ) ) {
140+ throw FileSystemProviderError . create ( 'Not allowed' , FileSystemProviderErrorCode . NoPermissions )
141+ }
142+ const fileStats = await this . infrastructure . getFileStats ?.( resource , this . languageClientManager )
143+ if ( fileStats != null ) {
121144 return {
122- type : FileType . File ,
123- size : encoder . encode ( content ) . length ,
124- mtime : Date . now ( ) ,
125- ctime : Date . now ( )
145+ type : fileStats . type === 'directory' ? FileType . Directory : FileType . File ,
146+ size : fileStats . size ,
147+ mtime : fileStats . mtime ,
148+ ctime : 0
126149 }
127150 }
128151 } catch ( err ) {
152+ if ( ( err as Error ) . message === 'File not found' ) {
153+ throw FileSystemProviderError . create ( err as Error , FileSystemProviderErrorCode . FileNotFound )
154+ }
129155 throw FileSystemProviderError . create ( err as Error , FileSystemProviderErrorCode . Unknown )
130156 }
131157 throw FileSystemProviderError . create ( 'file not found' , FileSystemProviderErrorCode . FileNotFound )
@@ -134,8 +160,20 @@ class InfrastructureTextFileSystemProvider implements IFileSystemProviderWithFil
134160 async mkdir ( ) : Promise < void > {
135161 }
136162
137- async readdir ( ) {
138- return [ ]
163+ async readdir ( resource : monaco . Uri ) {
164+ const result = await this . infrastructure . listFiles ?.( resource , this . languageClientManager )
165+ if ( result == null ) {
166+ return [ ]
167+ }
168+ return result . map ( file => {
169+ let name = file
170+ let type = FileType . File
171+ if ( file . endsWith ( '/' ) ) {
172+ type = FileType . Directory
173+ name = file . slice ( 0 , - 1 )
174+ }
175+ return < [ string , FileType ] > [ name , type ]
176+ } )
139177 }
140178
141179 delete ( ) : Promise < void > {
@@ -156,17 +194,17 @@ export class FileSystemFeature implements StaticFeature {
156194 const disposables = new DisposableStore ( )
157195
158196 // Register readonly file system overlay to access remote files
159- if ( this . infrastructure . getFileContent != null ) {
160- disposables . add ( registerFileSystemOverlay ( - 1 , new InfrastructureTextFileSystemProvider ( this . infrastructure , this . languageClientManager ) ) )
197+ if ( this . infrastructure . readFile != null ) {
198+ disposables . add ( registerFileSystemOverlay ( - 1 , new InfrastructureFileSystemProvider ( this . infrastructure , this . languageClientManager ) ) )
161199 }
162200
163- if ( this . infrastructure . saveFileContent != null ) {
201+ if ( this . infrastructure . writeFile != null ) {
164202 const watcher = vscode . workspace . createFileSystemWatcher ( new vscode . RelativePattern ( vscode . Uri . parse ( this . infrastructure . rootUri ) , '**/*' ) )
165203 disposables . add ( watcher )
166204 const onFileChange = async ( uri : vscode . Uri ) => {
167205 if ( ( await vscode . workspace . fs . stat ( uri ) ) . type === vscode . FileType . File ) {
168206 const content = await vscode . workspace . fs . readFile ( uri )
169- await this . infrastructure . saveFileContent ?.( uri , decoder . decode ( content ) , this . languageClientManager )
207+ await this . infrastructure . writeFile ?.( uri , await bufferToBase64 ( content ) , this . languageClientManager )
170208 }
171209 }
172210 watcher . onDidChange ( onFileChange )
0 commit comments