Skip to content

Commit 507659e

Browse files
committed
refactor(binding): simplify root patching logic
I introduce the plugin concept, this is tought for separating concerns of the method handling and probably expand it to external plugins, the idea is enhancing any LSP with custom method handling defined by the user. - Use concise pattern matching for workspaceFolders patch - Eliminate unused test_hover plugin registration - Streamline conditional checks for URI modification - Update lspmsg macro to handle workspaceFolders lists - Adjust tests to reflect workspaceFolders changes
1 parent ad13d10 commit 507659e

File tree

3 files changed

+81
-114
lines changed

3 files changed

+81
-114
lines changed

src/lsp/binding.rs

Lines changed: 58 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -125,11 +125,6 @@ impl RequestTracker {
125125
raw_bytes: &mut Bytes,
126126
pair: &Pair,
127127
) -> std::io::Result<()> {
128-
// If the LSP is not in a container, there is no need to track this.
129-
if !self.config.use_docker {
130-
return Ok(());
131-
}
132-
133128
match pair {
134129
Pair::Server => {
135130
if self.map.read().await.is_empty() {
@@ -141,11 +136,11 @@ impl RequestTracker {
141136
trace!(server_response=%v, "received");
142137

143138
// Check if this is a response to a tracked request
144-
if let Some(id) = v.get("id").and_then(Value::as_u64) {
145-
if let Some(tracked_method) = self.map.write().await.remove(&id) {
146-
self.plugins.process(self, &mut v, &tracked_method).await?;
147-
*raw_bytes = Bytes::from(serde_json::to_vec(&v)?);
148-
}
139+
if let Some(id) = v.get("id").and_then(Value::as_u64)
140+
&& let Some(tracked_method) = self.map.write().await.remove(&id)
141+
{
142+
self.plugins.process(self, &mut v, &tracked_method).await?;
143+
*raw_bytes = Bytes::from(serde_json::to_vec(&v)?);
149144
}
150145
}
151146

@@ -258,102 +253,65 @@ pub fn redirect_goto_methods<'a>(
258253
v: &'a mut Value,
259254
) -> Pin<Box<dyn Future<Output = std::io::Result<()>> + Send + 'a>> {
260255
Box::pin(async move {
256+
// If the LSP is not in a container, there is no need to track this.
257+
if !tracker.config.use_docker {
258+
return Ok(());
259+
}
261260
if let Some(results) = v.get_mut("result").and_then(Value::as_array_mut) {
262261
for result in results {
263-
if let Some(uri_val) = result.get("uri").and_then(|u| u.as_str()) {
264-
if !uri_val.contains(&tracker.config.local_path) {
265-
let new_uri = tracker.bind_library(uri_val).await?;
266-
RequestTracker::modify_uri(result, &new_uri);
267-
}
268-
}
269-
}
270-
}
271-
Ok(())
272-
})
273-
}
274-
275-
pub fn test_hover<'a>(
276-
_tracker: &'a RequestTracker,
277-
v: &'a mut Value,
278-
) -> Pin<Box<dyn Future<Output = std::io::Result<()>> + Send + 'a>> {
279-
Box::pin(async move {
280-
if let Some(result) = v.get_mut("result").filter(|r| !r.is_null()) {
281-
if let Some(value) = result.get_mut("contents").and_then(|c| c.get_mut("value")) {
282-
if let Some(s) = value.as_str() {
283-
*value = Value::String(s.replace("a", "e"));
262+
if let Some(uri_val) = result.get("uri").and_then(|u| u.as_str())
263+
&& !uri_val.contains(&tracker.config.local_path)
264+
{
265+
let new_uri = tracker.bind_library(uri_val).await?;
266+
RequestTracker::modify_uri(result, &new_uri);
284267
}
285268
}
286269
}
287270
Ok(())
288271
})
289272
}
290273

274+
/// Override any root resolver to the docker internal path,
275+
/// this ensure that the LSP can consume it correctly independent
276+
/// of the IDE
291277
pub fn ensure_root(msg: &mut Bytes, config: &ProxyConfig) {
292278
let docker_uri = format!("file://{}", config.docker_internal_path);
293279

294-
for key in [b"\"rootUri\":\"".as_slice(), b"\"rootPath\":\""] {
295-
if let Some(beg) = find(msg, key).map(|p| p + key.len()) {
296-
if let Some(end) = find(&msg[beg..], b"\"").map(|p| p + beg) {
297-
let before = &msg[..beg];
298-
let after = &msg[end..];
299-
*msg = Bytes::from([before, docker_uri.as_bytes(), after].concat());
300-
}
301-
}
280+
// rootPath is deprecated since 3.0 in favour of rootUri
281+
// but we want to support it anyway, notice that this
282+
// use the path not as a uri (misses the `file://` prefix)
283+
let key = b"\"rootPath\":\"".as_slice();
284+
if let Some(beg) = find(msg, key).map(|p| p + key.len())
285+
&& let Some(end) = find(&msg[beg..], b"\"").map(|p| p + beg)
286+
{
287+
let before = &msg[..beg];
288+
let after = &msg[end..];
289+
*msg = Bytes::from([before, config.docker_internal_path.as_bytes(), after].concat());
290+
}
291+
292+
// rootUri is deprecated since 3.6 in favour of workspaceFolders
293+
// but we want to support it anyway
294+
let key = b"\"rootUri\":\"".as_slice();
295+
if let Some(beg) = find(msg, key).map(|p| p + key.len())
296+
&& let Some(end) = find(&msg[beg..], b"\"").map(|p| p + beg)
297+
{
298+
let before = &msg[..beg];
299+
let after = &msg[end..];
300+
*msg = Bytes::from([before, docker_uri.as_bytes(), after].concat());
302301
}
303302

303+
// Active root resolver, if it is present, then will override `rootPath` and `rootUri`
304304
let key = b"\"workspaceFolders\":[";
305-
if let Some(beg) = find(msg, key).map(|p| p + key.len()) {
306-
if let Some(end) = find(&msg[beg..], b"]").map(|p| p + beg) {
307-
if let Some(ws) = patch_workspace_folders(&msg[beg..end], &docker_uri) {
308-
let before = &msg[..beg];
309-
let after = &msg[end..];
310-
*msg = Bytes::from([before, &ws, after].concat());
311-
}
312-
}
305+
if let Some(beg) = find(msg, key).map(|p| p + key.len())
306+
&& let Some(end) = find(&msg[beg..], b"]").map(|p| p + beg)
307+
&& let Some(ws) = patch_workspace_folders(&msg[beg..end], &docker_uri)
308+
{
309+
let before = &msg[..beg];
310+
let after = &msg[end..];
311+
*msg = Bytes::from([before, &ws, after].concat());
313312
}
314313
}
315314

316-
// pub fn ensure_root(msg: &mut Bytes, config: &ProxyConfig) {
317-
// let docker_uri = format!("file://{}", config.docker_internal_path);
318-
//
319-
// // Patch rootUri
320-
// let key = b"\"rootUri\":\"";
321-
// if let Some(mut beg) = find(msg, key) {
322-
// beg += key.len();
323-
// if let Some(mut end) = find(&msg[beg..], b"\"") {
324-
// end += beg; // Make it absolute position
325-
// let before = &msg[..beg];
326-
// let after = &msg[end..];
327-
// *msg = Bytes::from([before, docker_uri.as_bytes(), after].concat());
328-
// }
329-
// }
330-
//
331-
// // Patch rootPath if present
332-
// let key = b"\"rootPath\":\"";
333-
// if let Some(mut beg) = find(msg, key) {
334-
// beg += key.len();
335-
// if let Some(mut end) = find(&msg[beg..], b"\"") {
336-
// end += beg;
337-
// let before = &msg[..beg];
338-
// let after = &msg[end..];
339-
// *msg = Bytes::from([before, docker_uri.as_bytes(), after].concat());
340-
// }
341-
// }
342-
//
343-
// let key = b"\"workspaceFolders\":[";
344-
// if let Some(mut beg) = find(msg, key) {
345-
// beg += key.len();
346-
// if let Some(mut end) = find(&msg[beg..], b"]") {
347-
// end += beg;
348-
// let before = &msg[..beg];
349-
// let after = &msg[end..];
350-
// if let Some(ws) = patch_workspace_folders(&msg[beg..end], &docker_uri) {
351-
// *msg = Bytes::from([before, &ws, after].concat());
352-
// }
353-
// }
354-
// }
355-
// }
356-
357315
fn patch_workspace_folders(msg: &[u8], docker_uri: &str) -> Option<Bytes> {
358316
let key = b"\"uri\":\"";
359317
let mut result = None;
@@ -404,8 +362,8 @@ mod tests {
404362

405363
// From Client to Server
406364

407-
let rq = lspmsg!("uri": "/test/path", "method": "text/document", "workspaceFolder": "/test/path");
408-
let ex = lspmsg!("uri": "/usr/home/app", "method": "text/document", "workspaceFolder": "/usr/home/app");
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\"]");
409367

410368
let mut request = Bytes::from(rq.clone());
411369
let mut expected = Bytes::from(ex);
@@ -436,15 +394,23 @@ mod tests {
436394
let msg = lspmsg!(
437395
"method": "initialize",
438396
"rootUri": "file:///test/path",
439-
"rootPath": "/test/path"
397+
"rootPath": "/test/path",
398+
"workspaceFolders": "[\"uri\":\"/test/path\",\"name\":\"something\"]"
440399
);
441400

442401
let mut request = Bytes::from(msg);
443402
ensure_root(&mut request, &config);
444403

445404
let body = lspbody!(&request => "string");
446405
assert!(find(body, b"\"rootUri\":\"file:///usr/home/app\"").is_some());
447-
assert!(find(body, b"\"rootPath\":\"file:///usr/home/app\"").is_some());
406+
assert!(find(body, b"\"rootPath\":\"/usr/home/app\"").is_some());
407+
assert!(
408+
find(
409+
body,
410+
b"\"workspaceFolders\":[\"uri\":\"file:///usr/home/app\",\"name\":\"something\"]"
411+
)
412+
.is_some()
413+
);
448414
}
449415
}
450416

src/lsp/parser.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -152,12 +152,17 @@ pub mod lsp_utils {
152152
macro_rules! lspmsg {
153153
($($key:literal: $value:expr),+ $(,)?) => {{
154154
// Build JSON params object
155-
let params = format!(
155+
let mut params = format!(
156156
r#"{{{}}}"#,
157157
vec![$(format!(r#""{}":"{}""#, $key, $value)),+]
158158
.join(",")
159159
);
160160

161+
// Simple approach to handle list in this macro, probably not the most
162+
// resilient option but works for the current tests
163+
params = params.replace("\"[", "[");
164+
params = params.replace("]\"", "]");
165+
161166
let body = format!(
162167
r#"{{"jsonrpc":"2.0","method":"window/logMessage","params":{}}}"#,
163168
params

src/proxy/io.rs

Lines changed: 17 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,7 @@ use std::time::Duration;
33
use tokio_util::sync::CancellationToken;
44

55
use crate::lsp::{
6-
binding::{
7-
PluginRegistry, RequestTracker, ensure_root, redirect_goto_methods, redirect_uri,
8-
test_hover,
9-
},
6+
binding::{PluginRegistry, RequestTracker, ensure_root, redirect_goto_methods, redirect_uri},
107
parser::{LspFramedReader, send_message},
118
pid::PidHandler,
129
};
@@ -59,7 +56,6 @@ where
5956

6057
// Before creating tracker
6158
let mut plugins = PluginRegistry::new();
62-
plugins.register(&["textDocument/hover"], test_hover);
6359
plugins.register(GOTO_METHODS, redirect_goto_methods);
6460
let tracker = RequestTracker::new(config.clone(), plugins);
6561

@@ -176,26 +172,26 @@ where
176172
}
177173
}
178174

179-
tracker_inner.check_for_methods(ALL_METHODS, &mut msg, &pair).await?;
180175
redirect_uri(&mut msg, &pair, &config_clone)?;
181176
}
177+
tracker_inner.check_for_methods(ALL_METHODS, &mut msg, &pair).await?;
182178

183-
send_message(&mut writer, &msg).await.map_err(|e| {
184-
error!("Failed to forward the request: {}", e);
185-
e
186-
})?;
187-
}
188-
}
189-
Ok(None) => {
190-
tokio::time::sleep(Duration::from_millis(30)).await;
191-
debug!("Empty request, connection closed");
192-
break;
193-
}
194-
Err(e) => {
195-
tokio::time::sleep(Duration::from_millis(10)).await;
196-
error!("Error reading message: {}", e);
197-
return Err(e);
179+
send_message(&mut writer, &msg).await.map_err(|e| {
180+
error!("Failed to forward the request: {}", e);
181+
e
182+
})?;
198183
}
184+
}
185+
Ok(None) => {
186+
tokio::time::sleep(Duration::from_millis(30)).await;
187+
debug!("Empty request, connection closed");
188+
break;
189+
}
190+
Err(e) => {
191+
tokio::time::sleep(Duration::from_millis(10)).await;
192+
error!("Error reading message: {}", e);
193+
return Err(e);
194+
}
199195
}
200196
}
201197
Ok(())

0 commit comments

Comments
 (0)