@theia/filesystem 1.65.0-next.6 → 1.65.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (147) hide show
  1. package/lib/browser/download/file-download-command-contribution.d.ts +1 -1
  2. package/lib/browser/download/file-download-command-contribution.d.ts.map +1 -1
  3. package/lib/browser/download/file-download-command-contribution.js +3 -3
  4. package/lib/browser/download/file-download-command-contribution.js.map +1 -1
  5. package/lib/browser/download/file-download-frontend-module.d.ts.map +1 -1
  6. package/lib/browser/download/file-download-frontend-module.js +2 -1
  7. package/lib/browser/download/file-download-frontend-module.js.map +1 -1
  8. package/lib/browser/download/file-download-service.d.ts +2 -10
  9. package/lib/browser/download/file-download-service.d.ts.map +1 -1
  10. package/lib/browser/download/file-download-service.js +8 -7
  11. package/lib/browser/download/file-download-service.js.map +1 -1
  12. package/lib/browser/file-resource.js +1 -1
  13. package/lib/browser/file-resource.js.map +1 -1
  14. package/lib/browser/file-service.d.ts +1 -1
  15. package/lib/browser/file-service.d.ts.map +1 -1
  16. package/lib/browser/file-service.js +1 -1
  17. package/lib/browser/file-service.js.map +1 -1
  18. package/lib/browser/file-tree/file-tree-widget.d.ts +1 -1
  19. package/lib/browser/file-tree/file-tree-widget.d.ts.map +1 -1
  20. package/lib/browser/file-tree/file-tree-widget.js +3 -3
  21. package/lib/browser/file-tree/file-tree-widget.js.map +1 -1
  22. package/lib/browser/filesystem-frontend-contribution.d.ts +5 -4
  23. package/lib/browser/filesystem-frontend-contribution.d.ts.map +1 -1
  24. package/lib/browser/filesystem-frontend-contribution.js +5 -5
  25. package/lib/browser/filesystem-frontend-contribution.js.map +1 -1
  26. package/lib/browser/filesystem-frontend-module.d.ts.map +1 -1
  27. package/lib/browser/filesystem-frontend-module.js +4 -3
  28. package/lib/browser/filesystem-frontend-module.js.map +1 -1
  29. package/lib/browser/filesystem-saveable-service.d.ts.map +1 -1
  30. package/lib/browser/filesystem-saveable-service.js +5 -1
  31. package/lib/browser/filesystem-saveable-service.js.map +1 -1
  32. package/lib/browser/index.d.ts +0 -1
  33. package/lib/browser/index.d.ts.map +1 -1
  34. package/lib/browser/index.js +0 -1
  35. package/lib/browser/index.js.map +1 -1
  36. package/lib/browser/{file-upload-service.d.ts → upload/file-upload-service-impl.d.ts} +16 -52
  37. package/lib/browser/upload/file-upload-service-impl.d.ts.map +1 -0
  38. package/lib/browser/{file-upload-service.js → upload/file-upload-service-impl.js} +27 -27
  39. package/lib/browser/upload/file-upload-service-impl.js.map +1 -0
  40. package/lib/browser-only/browser-only-filesystem-frontend-module.d.ts.map +1 -1
  41. package/lib/browser-only/browser-only-filesystem-frontend-module.js +8 -0
  42. package/lib/browser-only/browser-only-filesystem-frontend-module.js.map +1 -1
  43. package/lib/browser-only/download/file-download-command-contribution.d.ts +15 -0
  44. package/lib/browser-only/download/file-download-command-contribution.d.ts.map +1 -0
  45. package/lib/browser-only/download/file-download-command-contribution.js +55 -0
  46. package/lib/browser-only/download/file-download-command-contribution.js.map +1 -0
  47. package/lib/browser-only/download/file-download-frontend-module.d.ts +4 -0
  48. package/lib/browser-only/download/file-download-frontend-module.d.ts.map +1 -0
  49. package/lib/browser-only/download/file-download-frontend-module.js +27 -0
  50. package/lib/browser-only/download/file-download-frontend-module.js.map +1 -0
  51. package/lib/browser-only/download/file-download-service.d.ts +86 -0
  52. package/lib/browser-only/download/file-download-service.d.ts.map +1 -0
  53. package/lib/browser-only/download/file-download-service.js +551 -0
  54. package/lib/browser-only/download/file-download-service.js.map +1 -0
  55. package/lib/browser-only/file-search.d.ts +38 -0
  56. package/lib/browser-only/file-search.d.ts.map +1 -0
  57. package/lib/browser-only/file-search.js +153 -0
  58. package/lib/browser-only/file-search.js.map +1 -0
  59. package/lib/browser-only/opfs-filesystem-initialization.d.ts +4 -2
  60. package/lib/browser-only/opfs-filesystem-initialization.d.ts.map +1 -1
  61. package/lib/browser-only/opfs-filesystem-initialization.js +4 -1
  62. package/lib/browser-only/opfs-filesystem-initialization.js.map +1 -1
  63. package/lib/browser-only/opfs-filesystem-provider.d.ts +89 -12
  64. package/lib/browser-only/opfs-filesystem-provider.d.ts.map +1 -1
  65. package/lib/browser-only/opfs-filesystem-provider.js +345 -181
  66. package/lib/browser-only/opfs-filesystem-provider.js.map +1 -1
  67. package/lib/browser-only/upload/file-upload-service-impl.d.ts +67 -0
  68. package/lib/browser-only/upload/file-upload-service-impl.d.ts.map +1 -0
  69. package/lib/browser-only/upload/file-upload-service-impl.js +328 -0
  70. package/lib/browser-only/upload/file-upload-service-impl.js.map +1 -0
  71. package/lib/common/download/file-download.d.ts +17 -0
  72. package/lib/common/download/file-download.d.ts.map +1 -0
  73. package/lib/common/download/{file-download-data.js → file-download.js} +3 -2
  74. package/lib/common/download/file-download.js.map +1 -0
  75. package/lib/common/files.d.ts +8 -1
  76. package/lib/common/files.d.ts.map +1 -1
  77. package/lib/common/files.js +35 -1
  78. package/lib/common/files.js.map +1 -1
  79. package/lib/{browser → common}/filesystem-preferences.d.ts +3 -1
  80. package/lib/common/filesystem-preferences.d.ts.map +1 -0
  81. package/lib/{browser → common}/filesystem-preferences.js +17 -11
  82. package/lib/common/filesystem-preferences.js.map +1 -0
  83. package/lib/common/index.d.ts +1 -0
  84. package/lib/common/index.d.ts.map +1 -1
  85. package/lib/common/index.js +1 -0
  86. package/lib/common/index.js.map +1 -1
  87. package/lib/common/io.js +7 -1
  88. package/lib/common/io.js.map +1 -1
  89. package/lib/common/upload/file-upload.d.ts +45 -0
  90. package/lib/common/upload/file-upload.d.ts.map +1 -0
  91. package/{src/common/download/file-download-data.ts → lib/common/upload/file-upload.js} +6 -13
  92. package/lib/common/upload/file-upload.js.map +1 -0
  93. package/lib/node/disk-file-system-provider.d.ts.map +1 -1
  94. package/lib/node/disk-file-system-provider.js +2 -4
  95. package/lib/node/disk-file-system-provider.js.map +1 -1
  96. package/lib/node/download/file-download-handler.js +2 -2
  97. package/lib/node/download/file-download-handler.js.map +1 -1
  98. package/lib/node/filesystem-backend-module.d.ts.map +1 -1
  99. package/lib/node/filesystem-backend-module.js +3 -1
  100. package/lib/node/filesystem-backend-module.js.map +1 -1
  101. package/lib/node/parcel-watcher/parcel-filesystem-service.d.ts +2 -2
  102. package/lib/node/parcel-watcher/parcel-filesystem-service.d.ts.map +1 -1
  103. package/lib/node/parcel-watcher/parcel-filesystem-service.js.map +1 -1
  104. package/lib/node/upload/node-file-upload-service.d.ts.map +1 -0
  105. package/lib/node/{node-file-upload-service.js → upload/node-file-upload-service.js} +1 -1
  106. package/lib/node/upload/node-file-upload-service.js.map +1 -0
  107. package/package.json +11 -5
  108. package/src/browser/download/file-download-command-contribution.ts +1 -1
  109. package/src/browser/download/file-download-frontend-module.ts +3 -2
  110. package/src/browser/download/file-download-service.ts +7 -12
  111. package/src/browser/file-resource.ts +1 -1
  112. package/src/browser/file-service.ts +1 -1
  113. package/src/browser/file-tree/file-tree-widget.tsx +1 -1
  114. package/src/browser/filesystem-frontend-contribution.ts +4 -5
  115. package/src/browser/filesystem-frontend-module.ts +4 -3
  116. package/src/browser/filesystem-saveable-service.ts +5 -1
  117. package/src/browser/index.ts +0 -1
  118. package/src/browser/{file-upload-service.ts → upload/file-upload-service-impl.ts} +31 -72
  119. package/src/browser-only/browser-only-filesystem-frontend-module.ts +10 -0
  120. package/src/browser-only/download/file-download-command-contribution.ts +56 -0
  121. package/src/browser-only/download/file-download-frontend-module.ts +26 -0
  122. package/src/browser-only/download/file-download-service.ts +726 -0
  123. package/src/browser-only/file-search.ts +170 -0
  124. package/src/browser-only/opfs-filesystem-initialization.ts +7 -4
  125. package/src/browser-only/opfs-filesystem-provider.ts +402 -189
  126. package/src/browser-only/upload/file-upload-service-impl.ts +408 -0
  127. package/src/common/download/file-download.ts +40 -0
  128. package/src/common/files.ts +42 -1
  129. package/src/{browser → common}/filesystem-preferences.ts +14 -14
  130. package/src/common/index.ts +1 -0
  131. package/src/common/io.ts +6 -1
  132. package/src/common/upload/file-upload.ts +65 -0
  133. package/src/node/disk-file-system-provider.ts +3 -4
  134. package/src/node/download/file-download-handler.ts +1 -1
  135. package/src/node/filesystem-backend-module.ts +3 -1
  136. package/src/node/parcel-watcher/parcel-filesystem-service.ts +2 -2
  137. package/src/node/{node-file-upload-service.ts → upload/node-file-upload-service.ts} +1 -1
  138. package/lib/browser/file-upload-service.d.ts.map +0 -1
  139. package/lib/browser/file-upload-service.js.map +0 -1
  140. package/lib/browser/filesystem-preferences.d.ts.map +0 -1
  141. package/lib/browser/filesystem-preferences.js.map +0 -1
  142. package/lib/common/download/file-download-data.d.ts +0 -7
  143. package/lib/common/download/file-download-data.d.ts.map +0 -1
  144. package/lib/common/download/file-download-data.js.map +0 -1
  145. package/lib/node/node-file-upload-service.d.ts.map +0 -1
  146. package/lib/node/node-file-upload-service.js.map +0 -1
  147. /package/lib/node/{node-file-upload-service.d.ts → upload/node-file-upload-service.d.ts} +0 -0
@@ -21,36 +21,76 @@ import {
21
21
  FileSystemProviderError,
22
22
  FileSystemProviderErrorCode,
23
23
  FileSystemProviderWithFileReadWriteCapability,
24
- FileType, FileWriteOptions, Stat, WatchOptions, createFileSystemProviderError
24
+ FileSystemProviderWithFileFolderCopyCapability,
25
+ FileSystemProviderWithOpenReadWriteCloseCapability,
26
+ FileType, FileWriteOptions, Stat, WatchOptions, createFileSystemProviderError,
27
+ FileOpenOptions, FileUpdateOptions, FileUpdateResult,
28
+ type FileReadStreamOptions
25
29
  } from '../common/files';
26
- import { Emitter, Event, URI, Disposable, Path } from '@theia/core';
30
+ import { Emitter, Event, URI, Disposable, DisposableCollection, type CancellationToken } from '@theia/core';
31
+ import { EncodingService } from '@theia/core/lib/common/encoding-service';
32
+ import { BinaryBuffer } from '@theia/core/lib/common/buffer';
33
+ import { TextDocumentContentChangeEvent } from '@theia/core/shared/vscode-languageserver-protocol';
34
+ import { TextDocument } from 'vscode-languageserver-textdocument';
35
+ import { OPFSFileSystem, WatchEventType, type FileStat, type OPFSError, type WatchEvent } from 'opfs-worker';
27
36
  import { OPFSInitialization } from './opfs-filesystem-initialization';
28
-
29
- /** Options to be used when traversing the file system handles */
30
- interface CreateFileSystemHandleOptions {
31
- isDirectory?: boolean;
32
- create?: boolean;
33
- }
37
+ import { ReadableStreamEvents, newWriteableStream } from '@theia/core/lib/common/stream';
38
+ import { readFileIntoStream } from '../common/io';
39
+ import { FileUri } from '@theia/core/lib/common/file-uri';
34
40
 
35
41
  @injectable()
36
- export class OPFSFileSystemProvider implements FileSystemProviderWithFileReadWriteCapability {
37
- capabilities: FileSystemProviderCapabilities = FileSystemProviderCapabilities.FileReadWrite;
42
+ export class OPFSFileSystemProvider implements Disposable,
43
+ FileSystemProviderWithFileReadWriteCapability,
44
+ FileSystemProviderWithOpenReadWriteCloseCapability,
45
+ FileSystemProviderWithFileFolderCopyCapability {
46
+
47
+ private readonly BUFFER_SIZE = 64 * 1024;
48
+
49
+ capabilities: FileSystemProviderCapabilities =
50
+ FileSystemProviderCapabilities.FileReadWrite |
51
+ FileSystemProviderCapabilities.FileOpenReadWriteClose |
52
+ FileSystemProviderCapabilities.FileFolderCopy |
53
+ FileSystemProviderCapabilities.Update;
54
+
38
55
  onDidChangeCapabilities: Event<void> = Event.None;
39
56
 
40
57
  private readonly onDidChangeFileEmitter = new Emitter<readonly FileChange[]>();
58
+
41
59
  readonly onDidChangeFile = this.onDidChangeFileEmitter.event;
42
- onFileWatchError: Event<void> = Event.None;
60
+ readonly onFileWatchError: Event<void> = Event.None;
43
61
 
44
62
  @inject(OPFSInitialization)
45
63
  protected readonly initialization: OPFSInitialization;
46
64
 
47
- private directoryHandle: FileSystemDirectoryHandle;
65
+ @inject(EncodingService)
66
+ protected readonly encodingService: EncodingService;
67
+
68
+ private fs!: OPFSFileSystem;
48
69
  private initialized: Promise<true>;
49
70
 
71
+ protected readonly toDispose = new DisposableCollection(
72
+ this.onDidChangeFileEmitter
73
+ );
74
+
75
+ /**
76
+ * Initializes the OPFS file system provider
77
+ */
50
78
  @postConstruct()
51
79
  protected init(): void {
52
80
  const setup = async (): Promise<true> => {
53
- this.directoryHandle = await this.initialization.getRootDirectory();
81
+ const root = await this.initialization.getRootDirectory();
82
+ const broadcastChannel = this.initialization.getBroadcastChannel();
83
+
84
+ // Set up file change listening via BroadcastChannel
85
+ broadcastChannel.onmessage = this.handleFileSystemChange.bind(this);
86
+
87
+ // Initialize the file system
88
+ this.fs = new OPFSFileSystem({
89
+ root,
90
+ broadcastChannel,
91
+ hashAlgorithm: false,
92
+ });
93
+
54
94
  await this.initialization.initializeFS(new Proxy(this, {
55
95
  get(target, prop, receiver): unknown {
56
96
  if (prop === 'initialized') {
@@ -61,286 +101,459 @@ export class OPFSFileSystemProvider implements FileSystemProviderWithFileReadWri
61
101
  }));
62
102
  return true;
63
103
  };
104
+
64
105
  this.initialized = setup();
65
106
  }
66
107
 
67
- watch(_resource: URI, _opts: WatchOptions): Disposable {
68
- return Disposable.NULL;
108
+ /**
109
+ * Watches a resource for file system changes
110
+ */
111
+ watch(resource: URI, opts: WatchOptions): Disposable {
112
+ if (!resource || !resource.path) {
113
+ return Disposable.NULL;
114
+ }
115
+
116
+ const unwatch = this.fs.watch(formatPath(resource), {
117
+ recursive: opts.recursive,
118
+ exclude: opts.excludes,
119
+ });
120
+
121
+ return Disposable.create(unwatch);
69
122
  }
70
123
 
71
- async exists(resource: URI): Promise<boolean> {
124
+ /**
125
+ * Creates an index from the map of entries
126
+ */
127
+ async createIndex(entries: Map<URI, Uint8Array>): Promise<void> {
128
+ const arrayEntries: [string, Uint8Array][] = [];
129
+ for (const [uri, content] of entries) {
130
+ arrayEntries.push([formatPath(uri), content]);
131
+ }
132
+ await this.fs.createIndex(arrayEntries);
133
+ }
134
+
135
+ /**
136
+ * Retrieves the current file system index
137
+ */
138
+ async index(): Promise<Map<URI, Stat>> {
139
+ const opfsIndex = await this.fs.index();
140
+ const index = new Map<URI, Stat>();
141
+
142
+ for (const [path, stats] of opfsIndex.entries()) {
143
+ const uri = new URI(path);
144
+ index.set(uri, formatStat(stats));
145
+ }
146
+
147
+ return index;
148
+ }
149
+
150
+ /**
151
+ * Clears the file system
152
+ */
153
+ async clear(): Promise<void> {
72
154
  try {
73
- await this.initialized;
74
- await this.toFileSystemHandle(resource);
75
- return true;
155
+ await this.fs.clear();
76
156
  } catch (error) {
157
+ throw toFileSystemProviderError(error as Error | OPFSError);
158
+ }
159
+ }
160
+
161
+ /**
162
+ * Checks if a resource exists
163
+ */
164
+ async exists(resource: URI): Promise<boolean> {
165
+ if (!resource || !resource.path) {
77
166
  return false;
78
167
  }
168
+
169
+ await this.initialized;
170
+
171
+ try {
172
+ return await this.fs.exists(formatPath(resource));
173
+ } catch (error) {
174
+ throw toFileSystemProviderError(error as Error | OPFSError);
175
+ }
79
176
  }
80
177
 
178
+ /**
179
+ * Gets file system statistics for a resource
180
+ */
81
181
  async stat(resource: URI): Promise<Stat> {
182
+ if (!resource || !resource.path) {
183
+ throw createFileSystemProviderError('Invalid resource URI', FileSystemProviderErrorCode.FileNotFound);
184
+ }
185
+
186
+ await this.initialized;
187
+
82
188
  try {
83
- await this.initialized;
84
-
85
- const handle = await this.toFileSystemHandle(resource);
86
-
87
- if (handle.kind === 'file') {
88
- const fileHandle = handle as FileSystemFileHandle;
89
- const file = await fileHandle.getFile();
90
- return {
91
- type: FileType.File,
92
- ctime: file.lastModified,
93
- mtime: file.lastModified,
94
- size: file.size
95
- };
96
- } else if (handle.kind === 'directory') {
97
- return {
98
- type: FileType.Directory,
99
- ctime: 0,
100
- mtime: 0,
101
- size: 0
102
- };
103
- }
189
+ const path = formatPath(resource);
190
+ const stats = await this.fs.stat(path);
104
191
 
105
- throw createFileSystemProviderError('Unknown file handle error', FileSystemProviderErrorCode.Unknown);
192
+ return formatStat(stats);
106
193
  } catch (error) {
107
- throw toFileSystemProviderError(error);
194
+ throw toFileSystemProviderError(error as Error | OPFSError);
108
195
  }
109
196
  }
110
197
 
198
+ /**
199
+ * Creates a directory
200
+ */
111
201
  async mkdir(resource: URI): Promise<void> {
202
+ if (!resource || !resource.path) {
203
+ throw createFileSystemProviderError('Invalid resource URI', FileSystemProviderErrorCode.FileNotFound);
204
+ }
205
+
112
206
  await this.initialized;
207
+
113
208
  try {
114
- await this.toFileSystemHandle(resource, { create: true, isDirectory: true });
209
+ const path = formatPath(resource);
210
+
211
+ await this.fs.mkdir(path, { recursive: true });
115
212
  this.onDidChangeFileEmitter.fire([{ resource, type: FileChangeType.ADDED }]);
116
213
  } catch (error) {
117
- throw toFileSystemProviderError(error, true);
214
+ throw toFileSystemProviderError(error as Error | OPFSError);
118
215
  }
119
216
  }
120
217
 
218
+ /**
219
+ * Reads directory contents
220
+ */
121
221
  async readdir(resource: URI): Promise<[string, FileType][]> {
222
+ if (!resource || !resource.path) {
223
+ throw createFileSystemProviderError('Invalid resource URI', FileSystemProviderErrorCode.FileNotFound);
224
+ }
225
+
122
226
  await this.initialized;
123
227
 
124
228
  try {
125
- // Get the directory handle from the directoryHandle
126
- const directoryHandle = await this.toFileSystemHandle(resource, { create: false, isDirectory: true }) as FileSystemDirectoryHandle;
127
-
128
- const result: [string, FileType][] = [];
129
-
130
- // Iterate through the entries in the directory (files and subdirectories)
131
- for await (const [name, handle] of directoryHandle.entries()) {
132
- // Determine the type of the entry (file or directory)
133
- if (handle.kind === 'file') {
134
- result.push([name, FileType.File]);
135
- } else if (handle.kind === 'directory') {
136
- result.push([name, FileType.Directory]);
137
- }
138
- }
229
+ const path = formatPath(resource);
230
+ const entries = await this.fs.readDir(path);
139
231
 
140
- return result;
232
+ return entries.map(entry => [
233
+ entry.name,
234
+ entry.isFile ? FileType.File : FileType.Directory
235
+ ]);
141
236
  } catch (error) {
142
- throw toFileSystemProviderError(error, true);
237
+ throw toFileSystemProviderError(error as Error | OPFSError);
143
238
  }
144
239
  }
145
240
 
146
- async delete(resource: URI, _opts: FileDeleteOptions): Promise<void> {
241
+ /**
242
+ * Deletes a resource
243
+ */
244
+ async delete(resource: URI, opts: FileDeleteOptions): Promise<void> {
245
+ if (!resource || !resource.path) {
246
+ throw createFileSystemProviderError('Invalid resource URI', FileSystemProviderErrorCode.FileNotFound);
247
+ }
248
+
147
249
  await this.initialized;
250
+
148
251
  try {
149
- const parentURI = resource.parent;
150
- const parentHandle = await this.toFileSystemHandle(parentURI, { create: false, isDirectory: true });
151
- if (parentHandle.kind !== 'directory') {
152
- throw createFileSystemProviderError(new Error('Parent is not a directory'), FileSystemProviderErrorCode.FileNotADirectory);
153
- }
154
- const name = resource.path.base;
155
- return (parentHandle as FileSystemDirectoryHandle).removeEntry(name, { recursive: _opts.recursive });
252
+ const path = formatPath(resource);
253
+
254
+ await this.fs.remove(path, { recursive: opts.recursive });
156
255
  } catch (error) {
157
- throw toFileSystemProviderError(error);
158
- } finally {
159
- this.onDidChangeFileEmitter.fire([{ resource, type: FileChangeType.DELETED }]);
256
+ throw toFileSystemProviderError(error as Error | OPFSError);
160
257
  }
161
258
  }
162
259
 
260
+ /**
261
+ * Renames a resource from one location to another
262
+ */
163
263
  async rename(from: URI, to: URI, opts: FileOverwriteOptions): Promise<void> {
264
+ if (!from || !from.path || !to || !to.path) {
265
+ throw createFileSystemProviderError('Invalid source or destination URI', FileSystemProviderErrorCode.FileNotFound);
266
+ }
267
+
164
268
  await this.initialized;
165
269
 
166
270
  try {
167
- const fromHandle = await this.toFileSystemHandle(from);
168
- // Check whether the source is a file or directory
169
- if (fromHandle.kind === 'directory') {
170
- // Create the new directory and get the handle
171
- await this.mkdir(to);
172
- const toHandle = await this.toFileSystemHandle(to) as FileSystemDirectoryHandle;
173
- await copyDirectoryContents(fromHandle as FileSystemDirectoryHandle, toHandle);
174
-
175
- // Delete the old directory
176
- await this.delete(from, { recursive: true, useTrash: false });
177
- } else {
178
- const content = await this.readFile(from);
179
- await this.writeFile(to, content, { create: true, overwrite: opts.overwrite });
180
- await this.delete(from, { recursive: true, useTrash: false });
181
- }
271
+ const fromPath = formatPath(from);
272
+ const toPath = formatPath(to);
182
273
 
183
- this.onDidChangeFileEmitter.fire([{ resource: to, type: FileChangeType.ADDED }]);
274
+ await this.fs.rename(fromPath, toPath, {
275
+ overwrite: opts.overwrite,
276
+ });
184
277
  } catch (error) {
185
- throw toFileSystemProviderError(error);
278
+ throw toFileSystemProviderError(error as Error | OPFSError);
186
279
  }
187
280
  }
188
281
 
189
- async readFile(resource: URI): Promise<Uint8Array> {
282
+ /**
283
+ * Copies a resource from one location to another
284
+ */
285
+ async copy(from: URI, to: URI, opts: FileOverwriteOptions): Promise<void> {
286
+ if (!from || !from.path || !to || !to.path) {
287
+ throw createFileSystemProviderError('Invalid source or destination URI', FileSystemProviderErrorCode.FileNotFound);
288
+ }
289
+
190
290
  await this.initialized;
191
291
 
192
292
  try {
193
- // Get the file handle from the directoryHandle
194
- const fileHandle = await this.toFileSystemHandle(resource, { create: false, isDirectory: false }) as FileSystemFileHandle;
293
+ const fromPath = formatPath(from);
294
+ const toPath = formatPath(to);
195
295
 
196
- // Get the file itself (which includes the content)
197
- const file = await fileHandle.getFile();
296
+ await this.fs.copy(fromPath, toPath, {
297
+ overwrite: opts.overwrite,
298
+ recursive: true,
299
+ });
300
+ } catch (error) {
301
+ throw toFileSystemProviderError(error as Error | OPFSError);
302
+ }
303
+ }
198
304
 
199
- // Read the file as an ArrayBuffer and convert it to Uint8Array
200
- const arrayBuffer = await file.arrayBuffer();
201
- return new Uint8Array(arrayBuffer);
305
+ /**
306
+ * Reads file content as binary data
307
+ */
308
+ async readFile(resource: URI): Promise<Uint8Array> {
309
+ if (!resource || !resource.path) {
310
+ throw createFileSystemProviderError('Invalid resource URI', FileSystemProviderErrorCode.FileNotFound);
311
+ }
312
+
313
+ await this.initialized;
314
+
315
+ try {
316
+ return await this.fs.readFile(formatPath(resource), 'binary') as Uint8Array;
202
317
  } catch (error) {
203
- throw toFileSystemProviderError(error, false);
318
+ throw toFileSystemProviderError(error as Error | OPFSError);
204
319
  }
205
320
  }
206
321
 
322
+ /**
323
+ * Reads file content as a stream
324
+ */
325
+ readFileStream(resource: URI, opts: FileReadStreamOptions, token: CancellationToken): ReadableStreamEvents<Uint8Array> {
326
+ const stream = newWriteableStream<Uint8Array>(chunks => BinaryBuffer.concat(chunks.map(chunk => BinaryBuffer.wrap(chunk))).buffer);
327
+
328
+ readFileIntoStream(this, resource, stream, data => data.buffer, {
329
+ ...opts,
330
+ bufferSize: this.BUFFER_SIZE
331
+ }, token);
332
+
333
+ return stream;
334
+ }
335
+
336
+ /**
337
+ * Writes binary content to a file
338
+ */
207
339
  async writeFile(resource: URI, content: Uint8Array, opts: FileWriteOptions): Promise<void> {
208
340
  await this.initialized;
209
- let writeableHandle: FileSystemWritableFileStream | undefined = undefined;
341
+
342
+ let handle: number | undefined = undefined;
343
+
344
+ if (!resource || !resource.path) {
345
+ throw createFileSystemProviderError('Invalid resource URI', FileSystemProviderErrorCode.FileNotFound);
346
+ }
347
+
348
+ if (!content || !(content instanceof Uint8Array)) {
349
+ throw createFileSystemProviderError('Invalid content: must be Uint8Array', FileSystemProviderErrorCode.Unknown);
350
+ }
351
+
210
352
  try {
211
- // Validate target unless { create: true, overwrite: true }
353
+ const path = formatPath(resource);
354
+
212
355
  if (!opts.create || !opts.overwrite) {
213
- const fileExists = await this.stat(resource).then(() => true, () => false);
356
+ const fileExists = await this.fs.exists(path);
357
+
214
358
  if (fileExists) {
215
359
  if (!opts.overwrite) {
216
360
  throw createFileSystemProviderError('File already exists', FileSystemProviderErrorCode.FileExists);
217
361
  }
218
- } else {
219
- if (!opts.create) {
220
- throw createFileSystemProviderError('File does not exist', FileSystemProviderErrorCode.FileNotFound);
221
- }
362
+ } else if (!opts.create) {
363
+ throw createFileSystemProviderError('File does not exist', FileSystemProviderErrorCode.FileNotFound);
222
364
  }
223
365
  }
224
366
 
225
- const handle = await this.toFileSystemHandle(resource, { create: true, isDirectory: false }) as FileSystemFileHandle;
226
-
227
367
  // Open
228
- writeableHandle = await handle?.createWritable();
368
+ handle = await this.open(resource, { create: true });
229
369
 
230
370
  // Write content at once
231
- await writeableHandle?.write(content);
232
-
233
- this.onDidChangeFileEmitter.fire([{ resource: resource, type: FileChangeType.UPDATED }]);
371
+ await this.write(handle, 0, content, 0, content.byteLength);
234
372
  } catch (error) {
235
- throw toFileSystemProviderError(error, false);
373
+ throw toFileSystemProviderError(error as Error | OPFSError);
236
374
  } finally {
237
- if (typeof writeableHandle !== 'undefined') {
238
- await writeableHandle.close();
375
+ if (typeof handle === 'number') {
376
+ await this.close(handle);
239
377
  }
240
378
  }
241
379
  }
242
380
 
381
+ // #region Open/Read/Write/Close Operations
382
+
243
383
  /**
244
- * Returns the FileSystemHandle for the given resource given by a URI.
245
- * @param resource URI/path of the resource
246
- * @param options Options for the creation of the handle while traversing the path
247
- * @returns FileSystemHandle for the given resource
384
+ * Opens a file and returns a file descriptor
248
385
  */
249
- private async toFileSystemHandle(resource: URI, options?: CreateFileSystemHandleOptions): Promise<FileSystemHandle> {
250
- const pathParts = resource.path.toString().split(Path.separator).filter(Boolean);
386
+ async open(resource: URI, opts: FileOpenOptions): Promise<number> {
387
+ await this.initialized;
251
388
 
252
- return recursiveFileSystemHandle(this.directoryHandle, pathParts, options);
253
- }
254
- }
389
+ if (!resource || !resource.path) {
390
+ throw createFileSystemProviderError('Invalid resource URI', FileSystemProviderErrorCode.FileNotFound);
391
+ }
392
+
393
+ try {
394
+ const path = formatPath(resource);
395
+ const fileExists = await this.fs.exists(path);
396
+
397
+ if (!opts.create && !fileExists) {
398
+ throw createFileSystemProviderError('File does not exist', FileSystemProviderErrorCode.FileNotFound);
399
+ }
255
400
 
256
- // #region Helper functions
257
- async function recursiveFileSystemHandle(handle: FileSystemHandle, pathParts: string[], options?: CreateFileSystemHandleOptions): Promise<FileSystemHandle> {
258
- // We reached the end of the path, this happens only when not creating
259
- if (pathParts.length === 0) {
260
- return handle;
401
+ const fd = await this.fs.open(path, {
402
+ create: opts.create,
403
+ truncate: opts.create
404
+ });
405
+
406
+ return fd;
407
+ } catch (error) {
408
+ throw toFileSystemProviderError(error as Error | OPFSError);
409
+ }
261
410
  }
262
- // If there are parts left, the handle must be a directory
263
- if (handle.kind !== 'directory') {
264
- throw createFileSystemProviderError('Not a directory', FileSystemProviderErrorCode.FileNotADirectory);
411
+
412
+ /**
413
+ * Closes a file descriptor
414
+ */
415
+ async close(fd: number): Promise<void> {
416
+ try {
417
+ await this.fs.close(fd);
418
+ } catch (error) {
419
+ throw toFileSystemProviderError(error as Error | OPFSError);
420
+ }
265
421
  }
266
- const dirHandle = handle as FileSystemDirectoryHandle;
267
- // We need to create it and thus we need to stop early to create the file or directory
268
- if (pathParts.length === 1 && options?.create) {
269
- if (options?.isDirectory) {
270
- return dirHandle.getDirectoryHandle(pathParts[0], { create: options.create });
271
- } else {
272
- return dirHandle.getFileHandle(pathParts[0], { create: options.create });
422
+
423
+ /**
424
+ * Reads data from a file descriptor
425
+ */
426
+ async read(fd: number, pos: number, data: Uint8Array, offset: number, length: number): Promise<number> {
427
+ try {
428
+ const result = await this.fs.read(fd, data, offset, length, pos);
429
+
430
+ return result.bytesRead;
431
+ } catch (error) {
432
+ throw toFileSystemProviderError(error as Error | OPFSError);
273
433
  }
274
434
  }
275
435
 
276
- // Continue to resolve the path
277
- const part = pathParts.shift()!;
278
- for await (const entry of dirHandle.entries()) {
279
- // Check the entry name in the current directory
280
- if (entry[0] === part) {
281
- return recursiveFileSystemHandle(entry[1], pathParts, options);
436
+ /**
437
+ * Writes data to a file descriptor
438
+ */
439
+ async write(fd: number, pos: number, data: Uint8Array, offset: number, length: number): Promise<number> {
440
+ try {
441
+ return await this.fs.write(fd, data, offset, length, pos, true);
442
+ } catch (error) {
443
+ throw toFileSystemProviderError(error as Error | OPFSError);
282
444
  }
283
445
  }
284
446
 
285
- // If we haven't found the part, we need to create it along the way
286
- if (options?.create) {
287
- const newHandle = await dirHandle.getDirectoryHandle(part, { create: true });
288
- return recursiveFileSystemHandle(newHandle, pathParts, options);
447
+ // #endregion
448
+
449
+ // #region Text File Updates
450
+
451
+ /**
452
+ * Updates a text file with content changes
453
+ */
454
+ async updateFile(resource: URI, changes: TextDocumentContentChangeEvent[], opts: FileUpdateOptions): Promise<FileUpdateResult> {
455
+ try {
456
+ const content = await this.readFile(resource);
457
+ const decoded = this.encodingService.decode(BinaryBuffer.wrap(content), opts.readEncoding);
458
+ const newContent = TextDocument.update(TextDocument.create('', '', 1, decoded), changes, 2).getText();
459
+ const encoding = await this.encodingService.toResourceEncoding(opts.writeEncoding, {
460
+ overwriteEncoding: opts.overwriteEncoding,
461
+ read: async length => {
462
+ const fd = await this.open(resource, { create: false });
463
+ try {
464
+ const data = new Uint8Array(length);
465
+ await this.read(fd, 0, data, 0, length);
466
+ return data;
467
+ } finally {
468
+ await this.close(fd);
469
+ }
470
+ }
471
+ });
472
+
473
+ const encoded = this.encodingService.encode(newContent, encoding);
474
+
475
+ await this.writeFile(resource, encoded.buffer, { create: false, overwrite: true });
476
+
477
+ const stat = await this.stat(resource);
478
+
479
+ return Object.assign(stat, { encoding: encoding.encoding });
480
+ } catch (error) {
481
+ throw toFileSystemProviderError(error as Error | OPFSError);
482
+ }
289
483
  }
290
484
 
291
- throw createFileSystemProviderError('File not found', FileSystemProviderErrorCode.FileNotFound);
292
- }
485
+ // #endregion
293
486
 
294
- // Function to copy directory contents recursively
295
- async function copyDirectoryContents(sourceHandle: FileSystemDirectoryHandle, destinationHandle: FileSystemDirectoryHandle): Promise<void> {
296
- for await (const [name, handle] of sourceHandle.entries()) {
297
- if (handle.kind === 'file') {
298
- const file = await (handle as FileSystemFileHandle).getFile();
299
- const newFileHandle = await destinationHandle.getFileHandle(name, { create: true });
300
- const writable = await newFileHandle.createWritable();
301
- try {
302
- await writable.write(await file.arrayBuffer());
303
- } finally {
304
- await writable.close();
305
- }
306
- } else if (handle.kind === 'directory') {
307
- const newSubDirHandle = await destinationHandle.getDirectoryHandle(name, { create: true });
308
- await copyDirectoryContents(handle as FileSystemDirectoryHandle, newSubDirHandle);
487
+ /**
488
+ * Handles file system change events from BroadcastChannel
489
+ */
490
+ private async handleFileSystemChange(event: MessageEvent<WatchEvent>): Promise<void> {
491
+ if (!event.data?.path) {
492
+ return;
493
+ }
494
+
495
+ const resource = new URI('file://' + event.data.path);
496
+ let changeType: FileChangeType;
497
+
498
+ if (event.data.type === WatchEventType.Added) {
499
+ changeType = FileChangeType.ADDED;
500
+ } else if (event.data.type === WatchEventType.Removed) {
501
+ changeType = FileChangeType.DELETED;
502
+ } else {
503
+ changeType = FileChangeType.UPDATED;
309
504
  }
505
+
506
+ this.onDidChangeFileEmitter.fire([{ resource, type: changeType }]);
507
+ }
508
+
509
+ /**
510
+ * Disposes the file system provider
511
+ */
512
+ dispose(): void {
513
+ this.toDispose.dispose();
310
514
  }
311
515
  }
312
516
 
313
- function toFileSystemProviderError(error: DOMException, is_dir?: boolean): FileSystemProviderError {
517
+ /**
518
+ * Formats a URI or string resource to a file system path
519
+ */
520
+ function formatPath(resource: URI | string): string {
521
+ return FileUri.fsPath(resource);
522
+ }
523
+
524
+ /**
525
+ * Creates a Stat object from OPFS stats
526
+ */
527
+ function formatStat(stats: FileStat): Stat {
528
+ return {
529
+ type: stats.isDirectory ? FileType.Directory : FileType.File,
530
+ ctime: new Date(stats.ctime).getTime(),
531
+ mtime: new Date(stats.mtime).getTime(),
532
+ size: stats.size
533
+ };
534
+ }
535
+
536
+ /**
537
+ * Converts OPFS errors to file system provider errors
538
+ */
539
+ function toFileSystemProviderError(error: OPFSError | Error): FileSystemProviderError {
314
540
  if (error instanceof FileSystemProviderError) {
315
- return error; // avoid double conversion
541
+ return error;
316
542
  }
317
543
 
318
544
  let code: FileSystemProviderErrorCode;
319
- switch (error.name) {
320
- case 'NotFoundError':
321
- code = FileSystemProviderErrorCode.FileNotFound;
322
- break;
323
- case 'InvalidModificationError':
324
- code = FileSystemProviderErrorCode.FileExists;
325
- break;
326
- case 'NotAllowedError':
327
- code = FileSystemProviderErrorCode.NoPermissions;
328
- break;
329
- case 'TypeMismatchError':
330
- if (!is_dir) {
331
- code = FileSystemProviderErrorCode.FileIsADirectory;
332
- } else {
333
- code = FileSystemProviderErrorCode.FileNotADirectory;
334
- }
335
545
 
336
- break;
337
- case 'QuotaExceededError':
338
- code = FileSystemProviderErrorCode.FileTooLarge;
339
- break;
340
- default:
341
- code = FileSystemProviderErrorCode.Unknown;
546
+ if (error.name === 'NotFoundError' || error.name === 'ENOENT') {
547
+ code = FileSystemProviderErrorCode.FileNotFound;
548
+ } else if (error.name === 'NotAllowedError' || error.name === 'SecurityError' || error.name === 'EACCES') {
549
+ code = FileSystemProviderErrorCode.NoPermissions;
550
+ } else if (error.name === 'QuotaExceededError' || error.name === 'ENOSPC') {
551
+ code = FileSystemProviderErrorCode.FileTooLarge;
552
+ } else if (error.name === 'PathError' || error.name === 'INVALID_PATH') {
553
+ code = FileSystemProviderErrorCode.FileNotADirectory;
554
+ } else {
555
+ code = FileSystemProviderErrorCode.Unknown;
342
556
  }
343
557
 
344
558
  return createFileSystemProviderError(error, code);
345
559
  }
346
- // #endregion