From f2bd2f15f58f03a91bba07319ec98cef90fe4ce9 Mon Sep 17 00:00:00 2001 From: Chip Senkbeil Date: Tue, 23 May 2023 14:13:25 -0500 Subject: [PATCH] Add support for --lsp [scheme] --- CHANGELOG.md | 4 ++ distant-core/src/client/lsp.rs | 77 +++++++++++++++------ distant-core/src/client/lsp/msg.rs | 104 ++++++++++++++++++++++++++++- src/cli/commands/client.rs | 4 +- src/cli/commands/client/lsp.rs | 2 + src/options.rs | 15 +++-- 6 files changed, 175 insertions(+), 31 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 391a72a..7927fcb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed - Renamed `distant_core::data` to `distant_core::protocol` +- CLI `--lsp` now accepts an optional `scheme` to be used instead of + `distant://`, which is the default +- `RemoteLspProcess` now takes a second argument, `scheme`, which dictates + whether to translate `distant://` or something else ## [0.20.0-alpha.5] diff --git a/distant-core/src/client/lsp.rs b/distant-core/src/client/lsp.rs index 7ead3ce..512504c 100644 --- a/distant-core/src/client/lsp.rs +++ b/distant-core/src/client/lsp.rs @@ -22,6 +22,7 @@ pub struct RemoteLspCommand { pty: Option, environment: Environment, current_dir: Option, + scheme: Option, } impl Default for RemoteLspCommand { @@ -37,6 +38,7 @@ impl RemoteLspCommand { pty: None, environment: Environment::new(), current_dir: None, + scheme: None, } } @@ -58,6 +60,12 @@ impl RemoteLspCommand { self } + /// Configures the process with a specific scheme to convert rather than `distant://` + pub fn scheme(&mut self, scheme: Option) -> &mut Self { + self.scheme = scheme; + self + } + /// Spawns the specified process on the remote machine using the given session, treating /// the process like an LSP server pub async fn spawn( @@ -71,9 +79,18 @@ impl RemoteLspCommand { command.pty(self.pty); let mut inner = command.spawn(channel, cmd).await?; - let stdin = inner.stdin.take().map(RemoteLspStdin::new); - let stdout = inner.stdout.take().map(RemoteLspStdout::new); - let stderr = inner.stderr.take().map(RemoteLspStderr::new); + let stdin = inner + .stdin + .take() + .map(|x| RemoteLspStdin::new(x, self.scheme.clone())); + let stdout = inner + .stdout + .take() + .map(|x| RemoteLspStdout::new(x, self.scheme.clone())); + let stderr = inner + .stderr + .take() + .map(|x| RemoteLspStderr::new(x, self.scheme.clone())); Ok(RemoteLspProcess { inner, @@ -119,11 +136,16 @@ impl DerefMut for RemoteLspProcess { pub struct RemoteLspStdin { inner: RemoteStdin, buf: Option>, + scheme: Option, } impl RemoteLspStdin { - pub fn new(inner: RemoteStdin) -> Self { - Self { inner, buf: None } + pub fn new(inner: RemoteStdin, scheme: impl Into>) -> Self { + Self { + inner, + buf: None, + scheme: scheme.into(), + } } /// Tries to write data to the stdin of a specific remote process @@ -133,7 +155,10 @@ impl RemoteLspStdin { // Process and then send out each LSP message in our queue for mut data in queue { // Convert distant:// to file:// - data.mut_content().convert_distant_scheme_to_local(); + match self.scheme.as_mut() { + Some(scheme) => data.mut_content().convert_scheme_to_local(scheme), + None => data.mut_content().convert_distant_scheme_to_local(), + } data.refresh_content_length(); self.inner.try_write_str(data.to_string())?; } @@ -152,7 +177,10 @@ impl RemoteLspStdin { // Process and then send out each LSP message in our queue for mut data in queue { // Convert distant:// to file:// - data.mut_content().convert_distant_scheme_to_local(); + match self.scheme.as_mut() { + Some(scheme) => data.mut_content().convert_scheme_to_local(scheme), + None => data.mut_content().convert_distant_scheme_to_local(), + } data.refresh_content_length(); self.inner.write_str(data.to_string()).await?; } @@ -197,16 +225,16 @@ pub struct RemoteLspStdout { } impl RemoteLspStdout { - pub fn new(inner: RemoteStdout) -> Self { - let (read_task, rx) = spawn_read_task(Box::pin(futures::stream::unfold( - inner, - |mut inner| async move { + pub fn new(inner: RemoteStdout, scheme: impl Into>) -> Self { + let (read_task, rx) = spawn_read_task( + Box::pin(futures::stream::unfold(inner, |mut inner| async move { match inner.read().await { Ok(res) => Some((res, inner)), Err(_) => None, } - }, - ))); + })), + scheme, + ); Self { read_task, rx } } @@ -263,16 +291,16 @@ pub struct RemoteLspStderr { } impl RemoteLspStderr { - pub fn new(inner: RemoteStderr) -> Self { - let (read_task, rx) = spawn_read_task(Box::pin(futures::stream::unfold( - inner, - |mut inner| async move { + pub fn new(inner: RemoteStderr, scheme: impl Into>) -> Self { + let (read_task, rx) = spawn_read_task( + Box::pin(futures::stream::unfold(inner, |mut inner| async move { match inner.read().await { Ok(res) => Some((res, inner)), Err(_) => None, } - }, - ))); + })), + scheme, + ); Self { read_task, rx } } @@ -321,10 +349,14 @@ impl Drop for RemoteLspStderr { } } -fn spawn_read_task(mut stream: S) -> (JoinHandle<()>, mpsc::Receiver>>) +fn spawn_read_task( + mut stream: S, + scheme: impl Into>, +) -> (JoinHandle<()>, mpsc::Receiver>>) where S: Stream> + Send + Unpin + 'static, { + let mut scheme = scheme.into(); let (tx, rx) = mpsc::channel::>>(1); let read_task = tokio::spawn(async move { let mut task_buf: Option> = None; @@ -352,7 +384,10 @@ where let mut out = Vec::new(); for mut data in queue { // Convert file:// to distant:// - data.mut_content().convert_local_scheme_to_distant(); + match scheme.as_mut() { + Some(scheme) => data.mut_content().convert_local_scheme_to(scheme), + None => data.mut_content().convert_local_scheme_to_distant(), + } data.refresh_content_length(); out.extend(data.to_bytes()); } diff --git a/distant-core/src/client/lsp/msg.rs b/distant-core/src/client/lsp/msg.rs index 5996df4..d6cdc13 100644 --- a/distant-core/src/client/lsp/msg.rs +++ b/distant-core/src/client/lsp/msg.rs @@ -335,12 +335,22 @@ fn swap_prefix(obj: &mut Map, old: &str, new: &str) { impl LspContent { /// Converts all URIs with `file://` as the scheme to `distant://` instead pub fn convert_local_scheme_to_distant(&mut self) { - swap_prefix(&mut self.0, "file:", "distant:"); + self.convert_local_scheme_to("distant://") + } + + /// Converts all URIs with `file://` as the scheme to `scheme` instead + pub fn convert_local_scheme_to(&mut self, scheme: &str) { + swap_prefix(&mut self.0, "file://", scheme); } /// Converts all URIs with `distant://` as the scheme to `file://` instead pub fn convert_distant_scheme_to_local(&mut self) { - swap_prefix(&mut self.0, "distant:", "file:"); + self.convert_scheme_to_local("distant://") + } + + /// Converts all URIs with `scheme` as the scheme to `file://` instead + pub fn convert_scheme_to_local(&mut self, scheme: &str) { + swap_prefix(&mut self.0, scheme, "file://"); } } @@ -688,6 +698,51 @@ mod tests { ); } + #[test] + fn content_convert_local_scheme_to_should_convert_keys_and_values() { + let mut content = LspContent(make_obj!({ + "distant://key1": "file://value1", + "file://key2": "distant://value2", + "key3": ["file://value3", "distant://value4"], + "key4": { + "distant://key5": "file://value5", + "file://key6": "distant://value6", + "key7": [ + { + "distant://key8": "file://value8", + "file://key9": "distant://value9", + } + ] + }, + "key10": null, + "key11": 123, + "key12": true, + })); + + content.convert_local_scheme_to("custom://"); + assert_eq!( + content.0, + make_obj!({ + "distant://key1": "custom://value1", + "custom://key2": "distant://value2", + "key3": ["custom://value3", "distant://value4"], + "key4": { + "distant://key5": "custom://value5", + "custom://key6": "distant://value6", + "key7": [ + { + "distant://key8": "custom://value8", + "custom://key9": "distant://value9", + } + ] + }, + "key10": null, + "key11": 123, + "key12": true, + }) + ); + } + #[test] fn content_convert_distant_scheme_to_local_should_convert_keys_and_values() { let mut content = LspContent(make_obj!({ @@ -732,4 +787,49 @@ mod tests { }) ); } + + #[test] + fn content_convert_scheme_to_local_should_convert_keys_and_values() { + let mut content = LspContent(make_obj!({ + "custom://key1": "file://value1", + "file://key2": "custom://value2", + "key3": ["file://value3", "custom://value4"], + "key4": { + "custom://key5": "file://value5", + "file://key6": "custom://value6", + "key7": [ + { + "custom://key8": "file://value8", + "file://key9": "custom://value9", + } + ] + }, + "key10": null, + "key11": 123, + "key12": true, + })); + + content.convert_scheme_to_local("custom://"); + assert_eq!( + content.0, + make_obj!({ + "file://key1": "file://value1", + "file://key2": "file://value2", + "key3": ["file://value3", "file://value4"], + "key4": { + "file://key5": "file://value5", + "file://key6": "file://value6", + "key7": [ + { + "file://key8": "file://value8", + "file://key9": "file://value9", + } + ] + }, + "key10": null, + "key11": 123, + "key12": true, + }) + ); + } } diff --git a/src/cli/commands/client.rs b/src/cli/commands/client.rs index 28bc3a4..4589bb9 100644 --- a/src/cli/commands/client.rs +++ b/src/cli/commands/client.rs @@ -433,13 +433,13 @@ async fn async_run(cmd: ClientSubcommand) -> CliResult { // Convert cmd into string let cmd = cmd.join(" "); - if lsp { + if let Some(scheme) = lsp { debug!( "Spawning LSP server (pty = {}, cwd = {:?}): {}", pty, current_dir, cmd ); Lsp::new(channel.into_client().into_channel()) - .spawn(cmd, current_dir, pty, MAX_PIPE_CHUNK_SIZE) + .spawn(cmd, current_dir, scheme, pty, MAX_PIPE_CHUNK_SIZE) .await?; } else if pty { debug!( diff --git a/src/cli/commands/client/lsp.rs b/src/cli/commands/client/lsp.rs index f7cee2b..bce0a41 100644 --- a/src/cli/commands/client/lsp.rs +++ b/src/cli/commands/client/lsp.rs @@ -20,6 +20,7 @@ impl Lsp { self, cmd: impl Into, current_dir: Option, + scheme: Option, pty: bool, max_chunk_size: usize, ) -> CliResult { @@ -33,6 +34,7 @@ impl Lsp { None }) .current_dir(current_dir) + .scheme(scheme) .spawn(self.0, &cmd) .await .with_context(|| format!("Failed to spawn {cmd}"))?; diff --git a/src/options.rs b/src/options.rs index a480dde..052b1d1 100644 --- a/src/options.rs +++ b/src/options.rs @@ -417,9 +417,12 @@ pub enum ClientSubcommand { network: NetworkSettings, /// If specified, will assume the remote process is a LSP server - /// and will translate paths that are local into distant:// and vice versa + /// and will translate paths that are local into distant:// and vice versa. + /// + /// If a scheme is provided, will translate local paths into that scheme! + /// Note that the scheme must be the exact prefix like `distant://`. #[clap(long)] - lsp: bool, + lsp: Option>, /// If specified, will spawn process using a pseudo tty #[clap(long)] @@ -1746,7 +1749,7 @@ mod tests { }, current_dir: None, environment: map!(), - lsp: true, + lsp: Some(None), pty: true, cmd: vec![String::from("cmd")], }), @@ -1784,7 +1787,7 @@ mod tests { }, current_dir: None, environment: map!(), - lsp: true, + lsp: Some(None), pty: true, cmd: vec![String::from("cmd")], }), @@ -1809,7 +1812,7 @@ mod tests { }, current_dir: None, environment: map!(), - lsp: true, + lsp: Some(None), pty: true, cmd: vec![String::from("cmd")], }), @@ -1847,7 +1850,7 @@ mod tests { }, current_dir: None, environment: map!(), - lsp: true, + lsp: Some(None), pty: true, cmd: vec![String::from("cmd")], }),