Add support for --lsp [scheme]

This commit is contained in:
Chip Senkbeil 2023-05-23 14:13:25 -05:00
parent 398aff2f12
commit f2bd2f15f5
No known key found for this signature in database
GPG Key ID: 35EF1F8EC72A4131
6 changed files with 175 additions and 31 deletions

View File

@ -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]

View File

@ -22,6 +22,7 @@ pub struct RemoteLspCommand {
pty: Option<PtySize>,
environment: Environment,
current_dir: Option<PathBuf>,
scheme: Option<String>,
}
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<String>) -> &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<Vec<u8>>,
scheme: Option<String>,
}
impl RemoteLspStdin {
pub fn new(inner: RemoteStdin) -> Self {
Self { inner, buf: None }
pub fn new(inner: RemoteStdin, scheme: impl Into<Option<String>>) -> 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<Option<String>>) -> 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<Option<String>>) -> 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<S>(mut stream: S) -> (JoinHandle<()>, mpsc::Receiver<io::Result<Vec<u8>>>)
fn spawn_read_task<S>(
mut stream: S,
scheme: impl Into<Option<String>>,
) -> (JoinHandle<()>, mpsc::Receiver<io::Result<Vec<u8>>>)
where
S: Stream<Item = Vec<u8>> + Send + Unpin + 'static,
{
let mut scheme = scheme.into();
let (tx, rx) = mpsc::channel::<io::Result<Vec<u8>>>(1);
let read_task = tokio::spawn(async move {
let mut task_buf: Option<Vec<u8>> = 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());
}

View File

@ -335,12 +335,22 @@ fn swap_prefix(obj: &mut Map<String, Value>, 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,
})
);
}
}

View File

@ -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!(

View File

@ -20,6 +20,7 @@ impl Lsp {
self,
cmd: impl Into<String>,
current_dir: Option<PathBuf>,
scheme: Option<String>,
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}"))?;

View File

@ -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<Option<String>>,
/// 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")],
}),