@@ -82,6 +82,7 @@ export async function checkChangedOnServer(file: CurrentTextFile | CurrentBinary
8282 return mtime ;
8383}
8484
85+ // Synchronize the client version and the server version of the same file
8586export async function importFile (
8687 file : CurrentTextFile | CurrentBinaryFile ,
8788 ignoreConflict ?: boolean ,
@@ -116,78 +117,83 @@ export async function importFile(
116117 mtime < 0 ||
117118 ( notIsfs ( file . uri ) &&
118119 vscode . workspace . getConfiguration ( "objectscript" , file . uri ) . get < boolean > ( "overwriteServerChanges" ) ) ;
119- return api
120- . putDoc (
120+ try {
121+ const data = await api . putDoc (
121122 file . name ,
122123 {
123124 content,
124125 enc,
125126 mtime,
126127 } ,
127128 ignoreConflict
128- )
129- . then ( ( data ) => {
130- // Update cache entry
131- workspaceState . update ( `${ file . uniqueId } :mtime` , Number ( new Date ( data . result . ts + "Z" ) ) ) ;
132-
133- // In case another extension has used an 'objectscript://' uri to load a document read-only from the server,
134- // make it reload with what we just imported to the server.
135- const serverUri = DocumentContentProvider . getUri (
136- file . name ,
137- file . workspaceFolder ,
138- undefined ,
139- false ,
140- undefined ,
141- true
129+ ) ;
130+ workspaceState . update ( `${ file . uniqueId } :mtime` , Number ( new Date ( data . result . ts + "Z" ) ) ) ;
131+ if ( data . result . flag === "1" ) {
132+ // putDoc returns new Storage definitions and the file must be a CLS
133+ let content : string [ ] ;
134+ ( content = new TextDecoder ( "utf-8" ) . decode ( await vscode . workspace . fs . readFile ( file . uri ) ) . split ( / \r ? \n / g) ) ,
135+ ( content = updateStorage ( content , data . result . content ) ) ;
136+ await vscode . workspace . fs . writeFile (
137+ file . uri ,
138+ new TextEncoder ( ) . encode (
139+ content . join ( ( ( < CurrentTextFile > file ) ?. eol ?? vscode . EndOfLine . LF ) == vscode . EndOfLine . CRLF ? "\r\n" : "\n" )
140+ )
142141 ) ;
143- documentContentProvider . update ( serverUri . with ( { scheme : OBJECTSCRIPT_FILE_SCHEMA } ) ) ;
144- } )
145- . catch ( ( error ) => {
146- if ( error ?. statusCode == 409 ) {
147- const choices : string [ ] = [ ] ;
148- if ( ! enc ) {
149- choices . push ( "Compare" ) ;
150- }
151- choices . push ( "Overwrite on Server" , "Pull Server Changes" , "Cancel" ) ;
152- return vscode . window
153- . showErrorMessage (
154- `Failed to import '${ file . name } ': The version of the file on the server is newer.
142+ }
143+ // In case another extension has used an 'objectscript://' uri to load a document read-only from the server,
144+ // make it reload with what we just imported to the server.
145+ const serverUri = DocumentContentProvider . getUri (
146+ file . name ,
147+ file . workspaceFolder ,
148+ undefined ,
149+ false ,
150+ undefined ,
151+ true
152+ ) ;
153+ documentContentProvider . update ( serverUri . with ( { scheme : OBJECTSCRIPT_FILE_SCHEMA } ) ) ;
154+ return ;
155+ } catch ( error ) {
156+ if ( error ?. statusCode == 409 ) {
157+ const choices : string [ ] = [ ] ;
158+ if ( ! enc ) {
159+ choices . push ( "Compare" ) ;
160+ }
161+ choices . push ( "Overwrite on Server" , "Pull Server Changes" , "Cancel" ) ;
162+ const action = await vscode . window . showErrorMessage (
163+ `Failed to import '${ file . name } ': The version of the file on the server is newer.
155164What do you want to do?` ,
156- ...choices
157- )
158- . then ( ( action ) => {
159- switch ( action ) {
160- case "Compare" :
161- return vscode . commands
162- . executeCommand (
163- "vscode.diff" ,
164- vscode . Uri . file ( file . name ) . with ( {
165- scheme : OBJECTSCRIPT_FILE_SCHEMA ,
166- authority : file . workspaceFolder ,
167- query : file . name . includes ( "/" ) ? "csp" : "" ,
168- } ) ,
169- file . uri ,
170- `Server • ${ file . name } ↔ Local • ${ file . fileName } `
171- )
172- . then ( ( ) => Promise . reject ( ) ) ;
173- case "Overwrite on Server" :
174- // Clear cache entry
175- workspaceState . update ( `${ file . uniqueId } :mtime` , undefined ) ;
176- // Overwrite
177- return importFile ( file , true , true ) ;
178- case "Pull Server Changes" :
179- loadChanges ( [ file ] ) ;
180- return Promise . reject ( ) ;
181- case "Cancel" :
182- return Promise . reject ( ) ;
183- }
184- return Promise . reject ( ) ;
185- } ) ;
186- } else {
187- handleError ( error , `Failed to save file '${ file . name } ' on the server.` ) ;
188- return Promise . reject ( ) ;
165+ ...choices
166+ ) ;
167+ switch ( action ) {
168+ case "Compare" :
169+ await vscode . commands . executeCommand (
170+ "vscode.diff" ,
171+ vscode . Uri . file ( file . name ) . with ( {
172+ scheme : OBJECTSCRIPT_FILE_SCHEMA ,
173+ authority : file . workspaceFolder ,
174+ query : file . name . includes ( "/" ) ? "csp" : "" ,
175+ } ) ,
176+ file . uri ,
177+ `Server • ${ file . name } ↔ Local • ${ file . fileName } `
178+ ) ;
179+ return Promise . reject ( ) ;
180+ case "Overwrite on Server" :
181+ // Clear cache entry
182+ workspaceState . update ( `${ file . uniqueId } :mtime` , undefined ) ;
183+ // Overwrite
184+ return importFile ( file , true , true ) ;
185+ case "Pull Server Changes" :
186+ loadChanges ( [ file ] ) ;
187+ return Promise . reject ( ) ;
188+ case "Cancel" :
189+ return Promise . reject ( ) ;
189190 }
190- } ) ;
191+ return Promise . reject ( ) ;
192+ } else {
193+ handleError ( error , `Failed to save file '${ file . name } ' on the server.` ) ;
194+ return Promise . reject ( ) ;
195+ }
196+ }
191197}
192198
193199function updateOthers ( others : string [ ] , baseUri : vscode . Uri ) {
@@ -231,29 +237,9 @@ export async function loadChanges(files: (CurrentTextFile | CurrentBinaryFile)[]
231237 } else {
232238 // Insert/update the storage part of class definition.
233239 content = new TextDecoder ( "utf-8" ) . decode ( await vscode . workspace . fs . readFile ( file . uri ) ) . split ( / \r ? \n / g) ;
234- let storageBegin : number ; // the last "Storage ..." line
235- let storageEnd : number ; // the first " }" after storageBegin
236- let classEnd : number ; // the last " }"
237- for ( let i = 0 ; i < content . length ; i ++ ) {
238- if ( content [ i ] . startsWith ( "Storage " ) ) {
239- storageBegin = i ;
240- } else if ( storageBegin !== undefined && storageEnd === undefined && content [ i ] . startsWith ( "}" ) ) {
241- storageEnd = i ;
242- } else if ( content [ i ] . startsWith ( "}" ) ) {
243- classEnd = i ;
244- }
245- }
246240 let storage = ( await api . getDoc ( file . name , file . uri , undefined , true ) ) . result . content ;
247241 storage = Buffer . isBuffer ( storage ) ? new TextDecoder ( ) . decode ( storage ) . split ( / \r ? \n / g) : storage ;
248- if ( ( storageBegin && storageEnd ) !== undefined ) {
249- // when replacing an existing storage definition, we don't need extra empty lines (if any).
250- while ( storage [ storage . length - 1 ] == "" ) {
251- storage . pop ( ) ;
252- }
253- content . splice ( storageBegin , 1 + storageEnd - storageBegin , ...storage ) ;
254- } else {
255- content . splice ( classEnd , 0 , ...storage ) ;
256- }
242+ content = updateStorage ( content , storage ) ;
257243 }
258244 exportedUris . add ( file . uri . toString ( ) ) ; // Set optimistically
259245 await vscode . workspace . fs
@@ -286,6 +272,51 @@ export async function loadChanges(files: (CurrentTextFile | CurrentBinaryFile)[]
286272 ) ;
287273}
288274
275+ function updateStorage ( content : string [ ] , storage : string [ ] ) : string [ ] {
276+ const storageMap = storageToMap ( storage ) ;
277+ let contentString = content . join ( "\n" ) ;
278+ contentString = contentString
279+ // update existing Storages
280+ . replaceAll ( / \n ( \s * s t o r a g e \s + ( \w + ) \s * { \s * ) ( [ ^ } ] * ?) ( \s * } ) / gim, ( _match , beforeXML , name , _oldXML , afterXML ) => {
281+ const newXML = storageMap . get ( name ) ;
282+ if ( newXML === undefined ) {
283+ return "" ;
284+ }
285+ storageMap . delete ( name ) ;
286+ return "\n" + beforeXML + newXML + afterXML ;
287+ } ) ;
288+ contentString = contentString
289+ // insert remaining Storages
290+ . replace ( / } \s * $ / m, ( m ) => {
291+ for ( const [ name , content ] of storageMap . entries ( ) ) {
292+ m = `Storage ${ name } \n{\n${ content } \n}\n\n${ m } ` ;
293+ }
294+ return m ;
295+ } ) ;
296+ return contentString . split ( "\n" ) ;
297+ }
298+
299+ function storageToMap ( storage : string [ ] ) : Map < string , string > {
300+ const map : Map < string , string > = new Map ( ) ;
301+ let k : string ;
302+ let v = [ ] ;
303+ for ( const line of storage ) {
304+ if ( line . startsWith ( "Storage " ) ) {
305+ k = line . slice ( "Storage " . length , line . length ) ;
306+ v = [ ] ;
307+ } else if ( line === "{" ) {
308+ continue ;
309+ } else if ( line === "}" ) {
310+ map . set ( k , v . join ( "\n" ) ) ;
311+ } else if ( line === "" ) {
312+ continue ;
313+ } else {
314+ v . push ( line ) ;
315+ }
316+ }
317+ return map ;
318+ }
319+
289320export async function compile ( docs : ( CurrentTextFile | CurrentBinaryFile ) [ ] , askFlags = false ) : Promise < any > {
290321 docs = docs . filter ( notNull ) ;
291322 if ( ! docs . length ) return ;
0 commit comments