5
5
getDyadRenameTags ,
6
6
getDyadDeleteTags ,
7
7
} from "../ipc/processors/response_processor" ;
8
+ import { normalizePath } from "../ipc/processors/normalizePath" ;
8
9
9
10
import log from "electron-log" ;
10
11
@@ -39,7 +40,39 @@ export abstract class BaseVirtualFileSystem {
39
40
protected baseDir : string ;
40
41
41
42
constructor ( baseDir : string ) {
42
- this . baseDir = baseDir ;
43
+ this . baseDir = path . resolve ( baseDir ) ;
44
+ }
45
+
46
+ /**
47
+ * Normalize path for consistent cross-platform behavior
48
+ */
49
+ private normalizePathForKey ( filePath : string ) : string {
50
+ const absolutePath = path . isAbsolute ( filePath )
51
+ ? filePath
52
+ : path . resolve ( this . baseDir , filePath ) ;
53
+
54
+ // Normalize separators and handle case-insensitive Windows paths
55
+ const normalized = normalizePath ( path . normalize ( absolutePath ) ) ;
56
+
57
+ // Intentionally do NOT lowercase for Windows which is case-insensitive
58
+ // because this avoids issues with path comparison.
59
+ //
60
+ // This is a trade-off and introduces a small edge case where
61
+ // e.g. foo.txt and Foo.txt are treated as different files by the VFS
62
+ // even though Windows treats them as the same file.
63
+ //
64
+ // This should be a pretty rare occurence and it's not worth the extra
65
+ // complexity to handle it.
66
+ return normalized ;
67
+ }
68
+
69
+ /**
70
+ * Convert normalized path back to platform-appropriate format
71
+ */
72
+ private denormalizePath ( normalizedPath : string ) : string {
73
+ return process . platform === "win32"
74
+ ? normalizedPath . replace ( / \/ / g, "\\" )
75
+ : normalizedPath ;
43
76
}
44
77
45
78
/**
@@ -69,43 +102,49 @@ export abstract class BaseVirtualFileSystem {
69
102
/**
70
103
* Write a file to the virtual filesystem
71
104
*/
72
- public writeFile ( relativePath : string , content : string ) : void {
105
+ protected writeFile ( relativePath : string , content : string ) : void {
73
106
const absolutePath = path . resolve ( this . baseDir , relativePath ) ;
74
- this . virtualFiles . set ( absolutePath , content ) ;
107
+ const normalizedKey = this . normalizePathForKey ( absolutePath ) ;
108
+
109
+ this . virtualFiles . set ( normalizedKey , content ) ;
75
110
// Remove from deleted files if it was previously deleted
76
- this . deletedFiles . delete ( absolutePath ) ;
111
+ this . deletedFiles . delete ( normalizedKey ) ;
77
112
}
78
113
79
114
/**
80
115
* Delete a file from the virtual filesystem
81
116
*/
82
- public deleteFile ( relativePath : string ) : void {
117
+ protected deleteFile ( relativePath : string ) : void {
83
118
const absolutePath = path . resolve ( this . baseDir , relativePath ) ;
84
- this . deletedFiles . add ( absolutePath ) ;
119
+ const normalizedKey = this . normalizePathForKey ( absolutePath ) ;
120
+
121
+ this . deletedFiles . add ( normalizedKey ) ;
85
122
// Remove from virtual files if it exists there
86
- this . virtualFiles . delete ( absolutePath ) ;
123
+ this . virtualFiles . delete ( normalizedKey ) ;
87
124
}
88
125
89
126
/**
90
127
* Rename a file in the virtual filesystem
91
128
*/
92
- public renameFile ( fromPath : string , toPath : string ) : void {
129
+ protected renameFile ( fromPath : string , toPath : string ) : void {
93
130
const fromAbsolute = path . resolve ( this . baseDir , fromPath ) ;
94
131
const toAbsolute = path . resolve ( this . baseDir , toPath ) ;
132
+ const fromNormalized = this . normalizePathForKey ( fromAbsolute ) ;
133
+ const toNormalized = this . normalizePathForKey ( toAbsolute ) ;
95
134
96
135
// Mark old file as deleted
97
- this . deletedFiles . add ( fromAbsolute ) ;
136
+ this . deletedFiles . add ( fromNormalized ) ;
98
137
99
138
// If the source file exists in virtual files, move its content
100
- if ( this . virtualFiles . has ( fromAbsolute ) ) {
101
- const content = this . virtualFiles . get ( fromAbsolute ) ! ;
102
- this . virtualFiles . delete ( fromAbsolute ) ;
103
- this . virtualFiles . set ( toAbsolute , content ) ;
139
+ if ( this . virtualFiles . has ( fromNormalized ) ) {
140
+ const content = this . virtualFiles . get ( fromNormalized ) ! ;
141
+ this . virtualFiles . delete ( fromNormalized ) ;
142
+ this . virtualFiles . set ( toNormalized , content ) ;
104
143
} else {
105
144
// Try to read from actual filesystem
106
145
try {
107
146
const content = fs . readFileSync ( fromAbsolute , "utf8" ) ;
108
- this . virtualFiles . set ( toAbsolute , content ) ;
147
+ this . virtualFiles . set ( toNormalized , content ) ;
109
148
} catch ( error ) {
110
149
// If we can't read the source file, we'll let the consumer handle it
111
150
logger . warn (
@@ -116,103 +155,59 @@ export abstract class BaseVirtualFileSystem {
116
155
}
117
156
118
157
// Remove destination from deleted files if it was previously deleted
119
- this . deletedFiles . delete ( toAbsolute ) ;
158
+ this . deletedFiles . delete ( toNormalized ) ;
120
159
}
121
160
122
161
/**
123
162
* Get all virtual files (files that have been written or modified)
124
163
*/
125
164
public getVirtualFiles ( ) : VirtualFile [ ] {
126
165
return Array . from ( this . virtualFiles . entries ( ) ) . map (
127
- ( [ absolutePath , content ] ) => ( {
128
- path : path . relative ( this . baseDir , absolutePath ) ,
129
- content,
130
- } ) ,
166
+ ( [ normalizedKey , content ] ) => {
167
+ // Convert normalized key back to relative path
168
+ const denormalizedPath = this . denormalizePath ( normalizedKey ) ;
169
+
170
+ return {
171
+ path : path . relative ( this . baseDir , denormalizedPath ) ,
172
+ content,
173
+ } ;
174
+ } ,
131
175
) ;
132
176
}
133
177
134
178
/**
135
179
* Get all deleted file paths (relative to base directory)
136
180
*/
137
181
public getDeletedFiles ( ) : string [ ] {
138
- return Array . from ( this . deletedFiles ) . map ( ( absolutePath ) =>
139
- path . relative ( this . baseDir , absolutePath ) ,
140
- ) ;
141
- }
142
-
143
- /**
144
- * Get all files that should be considered (existing + virtual - deleted)
145
- */
146
- public getAllFiles ( ) : string [ ] {
147
- const allFiles = new Set < string > ( ) ;
148
-
149
- // Add virtual files
150
- for ( const [ absolutePath ] of this . virtualFiles . entries ( ) ) {
151
- allFiles . add ( path . relative ( this . baseDir , absolutePath ) ) ;
152
- }
153
-
154
- // Add existing files (this is a simplified version - in practice you might want to scan the directory)
155
- // This method is mainly for getting the current state, consumers can combine with directory scanning
156
-
157
- return Array . from ( allFiles ) ;
158
- }
159
-
160
- /**
161
- * Check if a file has been modified in the virtual filesystem
162
- */
163
- public isFileModified ( filePath : string ) : boolean {
164
- const absolutePath = path . isAbsolute ( filePath )
165
- ? filePath
166
- : path . resolve ( this . baseDir , filePath ) ;
167
-
168
- return (
169
- this . virtualFiles . has ( absolutePath ) || this . deletedFiles . has ( absolutePath )
170
- ) ;
171
- }
172
-
173
- /**
174
- * Clear all virtual changes
175
- */
176
- public clear ( ) : void {
177
- this . virtualFiles . clear ( ) ;
178
- this . deletedFiles . clear ( ) ;
179
- }
180
-
181
- /**
182
- * Get the base directory
183
- */
184
- public getBaseDir ( ) : string {
185
- return this . baseDir ;
182
+ return Array . from ( this . deletedFiles ) . map ( ( normalizedKey ) => {
183
+ // Convert normalized key back to relative path
184
+ const denormalizedPath = this . denormalizePath ( normalizedKey ) ;
185
+ return path . relative ( this . baseDir , denormalizedPath ) ;
186
+ } ) ;
186
187
}
187
188
188
189
/**
189
190
* Check if a file is deleted in the virtual filesystem
190
191
*/
191
192
protected isDeleted ( filePath : string ) : boolean {
192
- const absolutePath = path . isAbsolute ( filePath )
193
- ? filePath
194
- : path . resolve ( this . baseDir , filePath ) ;
195
- return this . deletedFiles . has ( absolutePath ) ;
193
+ const normalizedKey = this . normalizePathForKey ( filePath ) ;
194
+ return this . deletedFiles . has ( normalizedKey ) ;
196
195
}
197
196
198
197
/**
199
198
* Check if a file exists in virtual files
200
199
*/
201
200
protected hasVirtualFile ( filePath : string ) : boolean {
202
- const absolutePath = path . isAbsolute ( filePath )
203
- ? filePath
204
- : path . resolve ( this . baseDir , filePath ) ;
205
- return this . virtualFiles . has ( absolutePath ) ;
201
+ const normalizedKey = this . normalizePathForKey ( filePath ) ;
202
+ return this . virtualFiles . has ( normalizedKey ) ;
206
203
}
207
204
208
205
/**
209
206
* Get virtual file content
210
207
*/
211
208
protected getVirtualFileContent ( filePath : string ) : string | undefined {
212
- const absolutePath = path . isAbsolute ( filePath )
213
- ? filePath
214
- : path . resolve ( this . baseDir , filePath ) ;
215
- return this . virtualFiles . get ( absolutePath ) ;
209
+ const normalizedKey = this . normalizePathForKey ( filePath ) ;
210
+ return this . virtualFiles . get ( normalizedKey ) ;
216
211
}
217
212
}
218
213
0 commit comments