@@ -304,27 +304,56 @@ pub fn ensure_root(msg: &mut Bytes, config: &ProxyConfig) {
304304 let key = b"\" workspaceFolders\" :[" ;
305305 if let Some ( beg) = find ( msg, key) . map ( |p| p + key. len ( ) )
306306 && let Some ( end) = find ( & msg[ beg..] , b"]" ) . map ( |p| p + beg)
307- && let Some ( ws) = patch_workspace_folders ( & msg[ beg..end] , & docker_uri)
307+ && let Some ( ws) = patch_workspace_folders ( & msg[ beg..end] , & docker_uri, & config . local_path )
308308 {
309309 let before = & msg[ ..beg] ;
310310 let after = & msg[ end..] ;
311311 * msg = Bytes :: from ( [ before, & ws, after] . concat ( ) ) ;
312312 }
313313}
314314
315- fn patch_workspace_folders ( msg : & [ u8 ] , docker_uri : & str ) -> Option < Bytes > {
315+ fn patch_workspace_folders ( msg : & [ u8 ] , docker_uri : & str , local_path : & str ) -> Option < Bytes > {
316316 let key = b"\" uri\" :\" " ;
317- let mut result = None ;
318- for uri_beg in find_iter ( msg, key) {
317+
318+ // Check if we have any matches before allocating
319+ let mut matches = find_iter ( msg, key) . peekable ( ) ;
320+ matches. peek ( ) ?;
321+
322+ let local_path_uri = format ! ( "file://{local_path}" ) ;
323+
324+ let mut new_bytes = Vec :: new ( ) ;
325+ // Cursor to track the position of replacements
326+ let mut last_pos = 0 ;
327+
328+ // Iterate and accumulate
329+ for uri_beg in matches {
319330 let beg = uri_beg + key. len ( ) ;
320331 if let Some ( end) = find ( & msg[ beg..] , b"\" " ) . map ( |p| p + beg) {
321- let before = & msg[ ..beg] ;
322- let after = & msg[ end..] ;
323- result = Some ( Bytes :: from ( [ before, docker_uri. as_bytes ( ) , after] . concat ( ) ) ) ;
332+ // Append everything from the last position up to the start of the URI value
333+ new_bytes. extend_from_slice ( & msg[ last_pos..beg] ) ;
334+
335+ // Force the path to the directory, this occurres in VSCode when in some
336+ // cases the workspaceFolders is set to a relative dir like `file://app`
337+ new_bytes. extend_from_slice ( docker_uri. as_bytes ( ) ) ;
338+
339+ // First try to change the root dir if it is match with the local dir
340+ if let Some ( pattern_init) =
341+ find ( & msg[ beg..end] , local_path_uri. as_bytes ( ) ) . map ( |p| p + beg)
342+ {
343+ let pattern_end = pattern_init + local_path_uri. len ( ) ;
344+ // Fill the gap, when local path is `/my/local/path` and root is `/my/local/path/subdir`
345+ // we add here `/subdir`
346+ new_bytes. extend_from_slice ( & msg[ pattern_end..end] ) ;
347+ }
348+
349+ last_pos = end;
324350 }
325351 }
326352
327- result
353+ // Append the remainder of the message
354+ new_bytes. extend_from_slice ( & msg[ last_pos..] ) ;
355+
356+ Some ( Bytes :: from ( new_bytes) )
328357}
329358
330359#[ cfg( test) ]
@@ -362,8 +391,8 @@ mod tests {
362391
363392 // From Client to Server
364393
365- let rq = lspmsg ! ( "uri" : "/test/path" , "method" : "text/document" , "workspaceFolders" : "[\" name\" : \" something\" , \" uri\" : \" /test/path\" ]" ) ;
366- let ex = lspmsg ! ( "uri" : "/usr/home/app" , "method" : "text/document" , "workspaceFolders" : "[\" name\" : \" something\" , \" uri\" : \" /usr/home/app\" ]" ) ;
394+ let rq = lspmsg ! ( "uri" : "/test/path" , "method" : "text/document" , "workspaceFolders" : "[\" name\" :\" something\" , \" uri\" :\" file:// /test/path\" ]" ) ;
395+ let ex = lspmsg ! ( "uri" : "/usr/home/app" , "method" : "text/document" , "workspaceFolders" : "[\" name\" :\" something\" , \" uri\" :\" file:// /usr/home/app\" ]" ) ;
367396
368397 let mut request = Bytes :: from ( rq. clone ( ) ) ;
369398 let mut expected = Bytes :: from ( ex) ;
@@ -412,6 +441,116 @@ mod tests {
412441 . is_some( )
413442 ) ;
414443 }
444+
445+ #[ test]
446+ fn ensure_root_patches_multiple_workspace_folders ( ) {
447+ let mut config = construct_config ( ) ;
448+ config. docker_internal_path = "/usr/src/app" . to_string ( ) ;
449+
450+ // We simulate a client sending TWO workspace folders
451+ let input_json = json ! ( {
452+ "jsonrpc" : "2.0" ,
453+ "id" : 1 ,
454+ "method" : "initialize" ,
455+ "params" : {
456+ "workspaceFolders" : [
457+ { "uri" : "file:///local/path/one" , "name" : "one" } ,
458+ { "uri" : "file:///local/path/two" , "name" : "two" }
459+ ]
460+ }
461+ } ) ;
462+
463+ let mut request = Bytes :: from ( serde_json:: to_vec ( & input_json) . unwrap ( ) ) ;
464+
465+ // Run the patcher
466+ ensure_root ( & mut request, & config) ;
467+
468+ let body = String :: from_utf8 ( request. to_vec ( ) ) . unwrap ( ) ;
469+
470+ // EXPECTATION:
471+ // Both URIs should be replaced by the docker internal path.
472+ let expected_uri = "file:///usr/src/app" ;
473+
474+ // Check that the first original URI is GONE
475+ assert ! (
476+ !body. contains( "file:///local/path/one" ) ,
477+ "The first workspace folder was NOT patched (or was reverted)!"
478+ ) ;
479+
480+ // Check that the second original URI is GONE
481+ assert ! (
482+ !body. contains( "file:///local/path/two" ) ,
483+ "The second workspace folder was NOT patched!"
484+ ) ;
485+
486+ // Check that the target URI appears twice
487+ let matches = body. matches ( expected_uri) . count ( ) ;
488+ assert_eq ! (
489+ matches, 2 ,
490+ "Expected the docker URI to appear twice, found it {} times" ,
491+ matches
492+ ) ;
493+ }
494+
495+ #[ test]
496+ fn ensure_root_patches_with_subdirs ( ) {
497+ let mut config = construct_config ( ) ;
498+ config. local_path = "/local/path" . to_string ( ) ; // <- this is the root path
499+ config. docker_internal_path = "/usr/src/app" . to_string ( ) ;
500+
501+ // We simulate a client sending TWO workspace folders
502+ let input_json = json ! ( {
503+ "jsonrpc" : "2.0" ,
504+ "id" : 1 ,
505+ "method" : "initialize" ,
506+ "params" : {
507+ "workspaceFolders" : [
508+ { "uri" : "file:///local/path/one" , "name" : "one" } ,
509+ { "uri" : "file:///local/path/two" , "name" : "two" }
510+ ]
511+ }
512+ } ) ;
513+
514+ let mut request = Bytes :: from ( serde_json:: to_vec ( & input_json) . unwrap ( ) ) ;
515+
516+ // Run the patcher
517+ ensure_root ( & mut request, & config) ;
518+
519+ let body = String :: from_utf8 ( request. to_vec ( ) ) . unwrap ( ) ;
520+
521+ // EXPECTATION:
522+ // Both URIs should be replaced by the docker internal path.
523+ let expected_uri_one = "file:///usr/src/app/one" ;
524+ let expected_uri_two = "file:///usr/src/app/two" ;
525+
526+ // Check that the first original URI is GONE
527+ assert ! (
528+ !body. contains( "file:///local/path/one" ) ,
529+ "The first workspace folder was NOT patched (or was reverted)!"
530+ ) ;
531+
532+ // Check that the second original URI is GONE
533+ assert ! (
534+ !body. contains( "file:///local/path/two" ) ,
535+ "The second workspace folder was NOT patched!"
536+ ) ;
537+
538+ // Check that the target URI appears twice
539+ let matches = body. matches ( expected_uri_one) . count ( ) ;
540+ assert_eq ! (
541+ matches, 1 ,
542+ "Expected the docker URI to appear once, found it {} times" ,
543+ matches
544+ ) ;
545+
546+ // Check that the target URI appears twice
547+ let matches = body. matches ( expected_uri_two) . count ( ) ;
548+ assert_eq ! (
549+ matches, 1 ,
550+ "Expected the docker URI to appear once, found it {} times" ,
551+ matches
552+ ) ;
553+ }
415554}
416555
417556#[ cfg( test) ]
0 commit comments