11use crate :: { config:: ProxyConfig , proxy:: Pair } ;
2+ use memchr:: memmem:: { find, find_iter} ;
23use serde_json:: { Value , json} ;
34use std:: { path:: PathBuf , process:: Stdio , str, sync:: Arc } ;
45use tokio:: {
56 fs:: { File , create_dir_all} ,
67 io:: AsyncWriteExt ,
78 process:: Command ,
89} ;
10+ use tokio_util:: bytes:: Bytes ;
911use tracing:: { debug, error, trace} ;
1012
1113use std:: collections:: HashMap ;
@@ -14,27 +16,41 @@ use tokio::sync::RwLock;
1416/// Redirect the paths from the sender pair to the receiver pair; this is used
1517/// for matching the paths between the container and the host path.
1618pub fn redirect_uri (
17- raw_str : & mut String ,
19+ raw_bytes : & mut Bytes ,
1820 from : & Pair ,
1921 config : & ProxyConfig ,
2022) -> Result < ( ) , Box < dyn std:: error:: Error + Send + Sync > > {
21- let from_path_str : & str ;
22- let to_path_str : & str ;
23+ let from_path : & [ u8 ] ;
24+ let to_path : & [ u8 ] ;
2325
2426 match from {
2527 Pair :: Client => {
26- from_path_str = & config. local_path ;
27- to_path_str = & config. docker_internal_path ;
28+ from_path = & config. local_path . as_bytes ( ) ;
29+ to_path = & config. docker_internal_path . as_bytes ( ) ;
2830 }
2931 Pair :: Server => {
30- from_path_str = & config. docker_internal_path ;
31- to_path_str = & config. local_path ;
32+ from_path = & config. docker_internal_path . as_bytes ( ) ;
33+ to_path = & config. local_path . as_bytes ( ) ;
3234 }
3335 }
3436
35- trace ! ( %from_path_str , %to_path_str ) ;
37+ trace ! ( from=? String :: from_utf8 ( from_path . to_vec ( ) ) , to=? String :: from_utf8 ( to_path . to_vec ( ) ) ) ;
3638
37- * raw_str = raw_str. replace ( from_path_str, to_path_str) ;
39+ let occurrences = find_iter ( & raw_bytes, from_path) ;
40+ let from_n = from_path. len ( ) ;
41+ let mut new_bytes: Bytes = Bytes :: new ( ) ;
42+ let mut last = 0 ;
43+
44+ for occurr in occurrences {
45+ let before = & raw_bytes[ last..occurr] ;
46+ last = occurr + from_n;
47+ // add the new text and join
48+ new_bytes = Bytes :: from ( [ & new_bytes, before, to_path] . concat ( ) ) ;
49+ }
50+ let after = & raw_bytes[ last..] ;
51+ new_bytes = Bytes :: from ( [ & new_bytes, after] . concat ( ) ) ;
52+
53+ * raw_bytes = new_bytes;
3854
3955 Ok ( ( ) )
4056}
@@ -98,94 +114,101 @@ impl RequestTracker {
98114 self . map . write ( ) . await . insert ( id, method. to_string ( ) ) ;
99115 }
100116
101- async fn take_if_match ( & self , id : u64 , expected : & str ) -> bool {
117+ async fn take_if_match ( & self , id : u64 ) -> bool {
102118 let mut map = self . map . write ( ) . await ;
103- let exists = map. get ( & id) . map ( |m| m == expected) . unwrap_or ( false ) ;
104- if exists {
119+ if map. get ( & id) . is_some ( ) {
105120 map. remove ( & id) ;
121+ return true ;
106122 }
107- exists
123+ false
108124 }
109125
110126 pub async fn check_for_methods (
111127 & self ,
112128 methods : & [ & str ] ,
113- raw_str : & mut String ,
129+ raw_bytes : & mut Bytes ,
114130 pair : & Pair ,
115131 ) -> std:: io:: Result < ( ) > {
116132 // If the LSP is not in a container, there is no need to track this.
117133 if !self . config . use_docker {
118134 return Ok ( ( ) ) ;
119135 }
120136
121- //textDocument/declaration
122-
123137 match pair {
124138 Pair :: Server => {
125- let mut v: Value = serde_json:: from_str ( & raw_str) ?;
139+ // Early return
140+ if self . map . read ( ) . await . is_empty ( ) {
141+ trace ! ( "Nothing expecting response, skipping method" ) ;
142+ return Ok ( ( ) ) ;
143+ }
144+
145+ let mut v: Value = serde_json:: from_slice ( raw_bytes. as_ref ( ) ) ?;
126146 trace ! ( server_response=%v, "received" ) ;
127147
128148 // Check if this is a response to a tracked request
129149 if let Some ( id) = v. get ( "id" ) . and_then ( Value :: as_u64) {
130- for method in methods {
131- debug ! ( "Checking for {method} method" ) ;
132-
133- let matches = self . take_if_match ( id, * method) . await ;
134- debug ! ( %matches) ;
135- if matches {
136- trace ! ( %id, "matches" ) ;
137- if let Some ( results) = v. get_mut ( "result" ) . and_then ( Value :: as_array_mut)
138- {
139- trace ! ( ?results) ;
140- for result in results {
141- if let Some ( uri_val) =
142- result. get ( "uri" ) . and_then ( |u| u. as_str ( ) )
143- {
144- if !( uri_val. contains ( & self . config . local_path ) ) {
145- debug ! ( %uri_val) ;
146- let new_uri =
147- self . bind_library ( uri_val. to_string ( ) ) . await ?;
148- debug ! ( "file://{}" , new_uri) ;
149-
150- Self :: modify_uri ( result, & new_uri) ;
151- }
150+ let matches = self . take_if_match ( id) . await ;
151+ debug ! ( %matches) ;
152+ if matches {
153+ trace ! ( %id, "matches" ) ;
154+ if let Some ( results) = v. get_mut ( "result" ) . and_then ( Value :: as_array_mut) {
155+ trace ! ( ?results) ;
156+ for result in results {
157+ if let Some ( uri_val) = result. get ( "uri" ) . and_then ( |u| u. as_str ( ) ) {
158+ if !( uri_val. contains ( & self . config . local_path ) ) {
159+ debug ! ( %uri_val) ;
160+ let new_uri = self . bind_library ( uri_val) . await ?;
161+ debug ! ( "file://{}" , new_uri) ;
162+
163+ Self :: modify_uri ( result, & new_uri) ;
152164 }
153165 }
154- * raw_str = v. to_string ( ) ; // write back the modified JSON
155- } else {
156- trace ! ( "result content not found" ) ;
157166 }
167+
168+ * raw_bytes = Bytes :: from ( serde_json:: to_vec ( & v) ?) ;
169+ } else {
170+ trace ! ( "result content not found" ) ;
158171 }
159172 }
160173 }
161174 }
162175
163176 Pair :: Client => {
164- let v: Value = serde_json:: from_str ( & raw_str) ?;
177+ // Early check to avoid parsing
178+ let mut method_found = "" ;
179+ for method in methods {
180+ debug ! ( "Checking for {method} method" ) ;
181+ let expected = & [ b"\" method\" :\" " , method. as_bytes ( ) , b"\" " ] . concat ( ) ;
182+ if find ( raw_bytes, expected) . is_some ( ) {
183+ method_found = method;
184+ break ;
185+ }
186+ }
187+
188+ if method_found. is_empty ( ) {
189+ debug ! ( "Any method that required redirection was not found, skipping patch" ) ;
190+ return Ok ( ( ) ) ;
191+ }
192+
193+ debug ! ( %method_found) ;
194+
195+ let v: Value = serde_json:: from_slice ( raw_bytes. as_ref ( ) ) ?;
165196 trace ! ( client_request=%v, "received" ) ;
166197
167198 debug ! ( "Checking for id" ) ;
168199 if let Some ( id) = v. get ( "id" ) . and_then ( Value :: as_u64) {
169200 debug ! ( %id) ;
170-
171- if let Some ( req_method) = v. get ( "method" ) . and_then ( Value :: as_str) {
172- trace ! ( %req_method) ;
173- // Only track expected methods if URI matches
174- for method in methods {
175- if req_method == * method {
176- debug ! ( %id, "Storing" ) ;
177- self . track ( id, * method) . await ;
178- }
179- }
180- }
201+ // Only track expected methods if URI matches
202+ self . track ( id, method_found) . await ;
203+ debug ! ( %id, "Storing" ) ;
181204 }
182205 }
183206 }
184207
185208 Ok ( ( ) )
186209 }
187210
188- async fn bind_library ( & self , uri : String ) -> std:: io:: Result < String > {
211+ async fn bind_library ( & self , uri : & str ) -> std:: io:: Result < String > {
189212 let temp_dir = std:: env:: temp_dir ( ) . join ( "lspdock" ) ;
190213 trace ! ( temp_dir=%temp_dir. to_string_lossy( ) ) ;
191214
@@ -200,7 +223,7 @@ impl RequestTracker {
200223 } else {
201224 let relative_path = safe_path. strip_prefix ( "/" ) . unwrap_or ( & safe_path) ;
202225 trace ! ( %relative_path) ;
203- let tmp_file_path = relative_path. to_string ( ) ;
226+ let tmp_file_path = relative_path;
204227 temp_dir. join ( tmp_file_path)
205228 } ;
206229
@@ -216,7 +239,7 @@ impl RequestTracker {
216239 let temp_uri_path = PathBuf :: from ( & temp_uri) ;
217240 debug ! ( %temp_uri) ;
218241 if !temp_uri_path. exists ( ) {
219- self . copy_file ( safe_path. to_string ( ) , & temp_uri) . await ?;
242+ self . copy_file ( & safe_path, & temp_uri) . await ?;
220243 } else {
221244 debug ! ( "File already exists, skipping copy. {}" , temp_uri) ;
222245 }
@@ -225,7 +248,7 @@ impl RequestTracker {
225248 }
226249
227250 /// Copies a file from either the local filesystem or a Docker container.
228- async fn copy_file ( & self , path : String , destination : & str ) -> std:: io:: Result < ( ) > {
251+ async fn copy_file ( & self , path : & str , destination : & str ) -> std:: io:: Result < ( ) > {
229252 // Only copy the file if the LSP is in a container
230253 debug ! ( "Starting file copy from {} to {}" , path, destination) ;
231254 let cmd = Command :: new ( "docker" )
0 commit comments