@@ -2305,8 +2305,7 @@ impl LspStore {
.read(cx)
.worktree_for_id(*worktree_id, cx)?;
let state = this.as_local()?.language_servers.get(server_id)?;
- let delegate =
- ProjectLspAdapterDelegate::for_local(this, &worktree, cx);
+ let delegate = LocalLspAdapterDelegate::for_local(this, &worktree, cx);
match state {
LanguageServerState::Starting(_) => None,
LanguageServerState::Running {
@@ -4368,7 +4367,7 @@ impl LspStore {
let response = this
.update(&mut cx, |this, cx| {
let worktree = this.worktree_for_id(worktree_id, cx)?;
- let delegate = ProjectLspAdapterDelegate::for_local(this, &worktree, cx);
+ let delegate = LocalLspAdapterDelegate::for_local(this, &worktree, cx);
anyhow::Ok(
cx.spawn(|_, _| async move { delegate.which(command.as_os_str()).await }),
)
@@ -4389,7 +4388,7 @@ impl LspStore {
let response = this
.update(&mut cx, |this, cx| {
let worktree = this.worktree_for_id(worktree_id, cx)?;
- let delegate = ProjectLspAdapterDelegate::for_local(this, &worktree, cx);
+ let delegate = LocalLspAdapterDelegate::for_local(this, &worktree, cx);
anyhow::Ok(cx.spawn(|_, _| async move { delegate.shell_env().await }))
})??
.await;
@@ -4398,6 +4397,52 @@ impl LspStore {
env: response.into_iter().collect(),
})
}
+ pub async fn handle_try_exec(
+ this: Model<Self>,
+ envelope: TypedEnvelope<proto::TryExec>,
+ mut cx: AsyncAppContext,
+ ) -> Result<proto::Ack> {
+ let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
+ let binary = envelope
+ .payload
+ .binary
+ .ok_or_else(|| anyhow!("missing binary"))?;
+ let binary = LanguageServerBinary {
+ path: PathBuf::from(binary.path),
+ env: None,
+ arguments: binary.arguments.into_iter().map(Into::into).collect(),
+ };
+ this.update(&mut cx, |this, cx| {
+ let worktree = this.worktree_for_id(worktree_id, cx)?;
+ let delegate = LocalLspAdapterDelegate::for_local(this, &worktree, cx);
+ anyhow::Ok(cx.spawn(|_, _| async move { delegate.try_exec(binary).await }))
+ })??
+ .await?;
+
+ Ok(proto::Ack {})
+ }
+
+ pub async fn handle_read_text_file(
+ this: Model<Self>,
+ envelope: TypedEnvelope<proto::ReadTextFile>,
+ mut cx: AsyncAppContext,
+ ) -> Result<proto::ReadTextFileResponse> {
+ let path = envelope
+ .payload
+ .path
+ .ok_or_else(|| anyhow!("missing path"))?;
+ let worktree_id = WorktreeId::from_proto(path.worktree_id);
+ let path = PathBuf::from(path.path);
+ let response = this
+ .update(&mut cx, |this, cx| {
+ let worktree = this.worktree_for_id(worktree_id, cx)?;
+ let delegate = LocalLspAdapterDelegate::for_local(this, &worktree, cx);
+ anyhow::Ok(cx.spawn(|_, _| async move { delegate.read_text_file(path).await }))
+ })??
+ .await?;
+
+ Ok(proto::ReadTextFileResponse { text: response })
+ }
async fn handle_apply_additional_edits_for_completion(
this: Model<Self>,
@@ -4535,9 +4580,12 @@ impl LspStore {
) {
let ssh = self.as_ssh().unwrap();
- let delegate =
- ProjectLspAdapterDelegate::for_ssh(self, worktree, ssh.upstream_client.clone(), cx)
- as Arc<dyn LspAdapterDelegate>;
+ let delegate = Arc::new(SshLspAdapterDelegate {
+ lsp_store: cx.handle().downgrade(),
+ worktree: worktree.read(cx).snapshot(),
+ upstream_client: ssh.upstream_client.clone(),
+ language_registry: self.languages.clone(),
+ }) as Arc<dyn LspAdapterDelegate>;
// TODO: We should use `adapter` here instead of reaching through the `CachedLspAdapter`.
let lsp_adapter = adapter.adapter.clone();
@@ -4645,7 +4693,7 @@ impl LspStore {
let local = self.as_local().unwrap();
let stderr_capture = Arc::new(Mutex::new(Some(String::new())));
- let lsp_adapter_delegate = ProjectLspAdapterDelegate::for_local(self, worktree_handle, cx);
+ let lsp_adapter_delegate = LocalLspAdapterDelegate::for_local(self, worktree_handle, cx);
let project_environment = local.environment.update(cx, |environment, cx| {
environment.get_environment(Some(worktree_id), Some(worktree_path.clone()), cx)
});
@@ -6938,18 +6986,32 @@ impl LspAdapter for SshLspAdapter {
None
}
}
+pub fn language_server_settings<'a, 'b: 'a>(
+ delegate: &'a dyn LspAdapterDelegate,
+ language: &str,
+ cx: &'b AppContext,
+) -> Option<&'a LspSettings> {
+ ProjectSettings::get(
+ Some(SettingsLocation {
+ worktree_id: delegate.worktree_id(),
+ path: delegate.worktree_root_path(),
+ }),
+ cx,
+ )
+ .lsp
+ .get(language)
+}
-pub struct ProjectLspAdapterDelegate {
+pub struct LocalLspAdapterDelegate {
lsp_store: WeakModel<LspStore>,
worktree: worktree::Snapshot,
- fs: Option<Arc<dyn Fs>>,
+ fs: Arc<dyn Fs>,
http_client: Arc<dyn HttpClient>,
language_registry: Arc<LanguageRegistry>,
load_shell_env_task: Shared<Task<Option<HashMap<String, String>>>>,
- upstream_client: Option<AnyProtoClient>,
}
-impl ProjectLspAdapterDelegate {
+impl LocalLspAdapterDelegate {
fn for_local(
lsp_store: &LspStore,
worktree: &Model<Worktree>,
@@ -6957,45 +7019,37 @@ impl ProjectLspAdapterDelegate {
) -> Arc<Self> {
let local = lsp_store
.as_local()
- .expect("ProjectLspAdapterDelegate cannot be constructed on a remote");
+ .expect("LocalLspAdapterDelegate cannot be constructed on a remote");
let http_client = local
.http_client
.clone()
.unwrap_or_else(|| Arc::new(BlockedHttpClient));
- Self::new(
- lsp_store,
- worktree,
- http_client,
- Some(local.fs.clone()),
- None,
- cx,
- )
- }
-
- fn for_ssh(
- lsp_store: &LspStore,
- worktree: &Model<Worktree>,
- upstream_client: AnyProtoClient,
- cx: &mut ModelContext<LspStore>,
- ) -> Arc<Self> {
- Self::new(
- lsp_store,
- worktree,
- Arc::new(BlockedHttpClient),
- None,
- Some(upstream_client),
- cx,
- )
- }
+ Self::new(lsp_store, worktree, http_client, local.fs.clone(), cx)
+ }
+
+ // fn for_ssh(
+ // lsp_store: &LspStore,
+ // worktree: &Model<Worktree>,
+ // upstream_client: AnyProtoClient,
+ // cx: &mut ModelContext<LspStore>,
+ // ) -> Arc<Self> {
+ // Self::new(
+ // lsp_store,
+ // worktree,
+ // Arc::new(BlockedHttpClient),
+ // None,
+ // Some(upstream_client),
+ // cx,
+ // )
+ // }
pub fn new(
lsp_store: &LspStore,
worktree: &Model<Worktree>,
http_client: Arc<dyn HttpClient>,
- fs: Option<Arc<dyn Fs>>,
- upstream_client: Option<AnyProtoClient>,
+ fs: Arc<dyn Fs>,
cx: &mut ModelContext<LspStore>,
) -> Arc<Self> {
let worktree_id = worktree.read(cx).id();
@@ -7015,52 +7069,14 @@ impl ProjectLspAdapterDelegate {
worktree: worktree.read(cx).snapshot(),
fs,
http_client,
- upstream_client,
language_registry: lsp_store.languages.clone(),
load_shell_env_task,
})
}
}
-struct BlockedHttpClient;
-
-impl HttpClient for BlockedHttpClient {
- fn send(
- &self,
- _req: Request<AsyncBody>,
- ) -> BoxFuture<'static, Result<Response<AsyncBody>, Error>> {
- Box::pin(async {
- Err(std::io::Error::new(
- std::io::ErrorKind::PermissionDenied,
- "ssh host blocked http connection",
- )
- .into())
- })
- }
-
- fn proxy(&self) -> Option<&Uri> {
- None
- }
-}
-
-pub fn language_server_settings<'a, 'b: 'a>(
- delegate: &'a dyn LspAdapterDelegate,
- language: &str,
- cx: &'b AppContext,
-) -> Option<&'a LspSettings> {
- ProjectSettings::get(
- Some(SettingsLocation {
- worktree_id: delegate.worktree_id(),
- path: delegate.worktree_root_path(),
- }),
- cx,
- )
- .lsp
- .get(language)
-}
-
#[async_trait]
-impl LspAdapterDelegate for ProjectLspAdapterDelegate {
+impl LspAdapterDelegate for LocalLspAdapterDelegate {
fn show_notification(&self, message: &str, cx: &mut AppContext) {
self.lsp_store
.update(cx, |_, cx| {
@@ -7082,42 +7098,12 @@ impl LspAdapterDelegate for ProjectLspAdapterDelegate {
}
async fn shell_env(&self) -> HashMap<String, String> {
- if let Some(upstream_client) = &self.upstream_client {
- use rpc::proto::SSH_PROJECT_ID;
-
- return upstream_client
- .request(proto::ShellEnv {
- project_id: SSH_PROJECT_ID,
- worktree_id: self.worktree_id().to_proto(),
- })
- .await
- .map(|response| response.env.into_iter().collect())
- .unwrap_or_default();
- }
-
let task = self.load_shell_env_task.clone();
task.await.unwrap_or_default()
}
#[cfg(not(target_os = "windows"))]
async fn which(&self, command: &OsStr) -> Option<PathBuf> {
- if let Some(upstream_client) = &self.upstream_client {
- use rpc::proto::SSH_PROJECT_ID;
-
- return upstream_client
- .request(proto::WhichCommand {
- project_id: SSH_PROJECT_ID,
- worktree_id: self.worktree_id().to_proto(),
- command: command.to_string_lossy().to_string(),
- })
- .await
- .log_err()
- .and_then(|response| response.path)
- .map(PathBuf::from);
- }
-
- self.fs.as_ref()?;
-
let worktree_abs_path = self.worktree.abs_path();
let shell_path = self.shell_env().await.get("PATH").cloned();
which::which_in(command, shell_path.as_ref(), worktree_abs_path).ok()
@@ -7125,8 +7111,6 @@ impl LspAdapterDelegate for ProjectLspAdapterDelegate {
#[cfg(target_os = "windows")]
async fn which(&self, command: &OsStr) -> Option<PathBuf> {
- self.fs.as_ref()?;
-
// todo(windows) Getting the shell env variables in a current directory on Windows is more complicated than other platforms
// there isn't a 'default shell' necessarily. The closest would be the default profile on the windows terminal
// SEE: https://learn.microsoft.com/en-us/windows/terminal/customize-settings/startup
@@ -7134,10 +7118,6 @@ impl LspAdapterDelegate for ProjectLspAdapterDelegate {
}
async fn try_exec(&self, command: LanguageServerBinary) -> Result<()> {
- if self.fs.is_none() {
- return Ok(());
- }
-
let working_dir = self.worktree_root_path();
let output = smol::process::Command::new(&command.path)
.args(command.arguments)
@@ -7170,12 +7150,127 @@ impl LspAdapterDelegate for ProjectLspAdapterDelegate {
if self.worktree.entry_for_path(&path).is_none() {
return Err(anyhow!("no such path {path:?}"));
};
- if let Some(fs) = &self.fs {
- let content = fs.load(&path).await?;
- Ok(content)
- } else {
- return Err(anyhow!("cannot open {path:?} on ssh host (yet!)"));
- }
+ self.fs.load(&path).await
+ }
+}
+
+struct BlockedHttpClient;
+
+impl HttpClient for BlockedHttpClient {
+ fn send(
+ &self,
+ _req: Request<AsyncBody>,
+ ) -> BoxFuture<'static, Result<Response<AsyncBody>, Error>> {
+ Box::pin(async {
+ Err(std::io::Error::new(
+ std::io::ErrorKind::PermissionDenied,
+ "ssh host blocked http connection",
+ )
+ .into())
+ })
+ }
+
+ fn proxy(&self) -> Option<&Uri> {
+ None
+ }
+}
+
+struct SshLspAdapterDelegate {
+ lsp_store: WeakModel<LspStore>,
+ worktree: worktree::Snapshot,
+ upstream_client: AnyProtoClient,
+ language_registry: Arc<LanguageRegistry>,
+}
+
+#[async_trait]
+impl LspAdapterDelegate for SshLspAdapterDelegate {
+ fn show_notification(&self, message: &str, cx: &mut AppContext) {
+ self.lsp_store
+ .update(cx, |_, cx| {
+ cx.emit(LspStoreEvent::Notification(message.to_owned()))
+ })
+ .ok();
+ }
+
+ fn http_client(&self) -> Arc<dyn HttpClient> {
+ Arc::new(BlockedHttpClient)
+ }
+
+ fn worktree_id(&self) -> WorktreeId {
+ self.worktree.id()
+ }
+
+ fn worktree_root_path(&self) -> &Path {
+ self.worktree.abs_path().as_ref()
+ }
+
+ async fn shell_env(&self) -> HashMap<String, String> {
+ use rpc::proto::SSH_PROJECT_ID;
+
+ self.upstream_client
+ .request(proto::ShellEnv {
+ project_id: SSH_PROJECT_ID,
+ worktree_id: self.worktree_id().to_proto(),
+ })
+ .await
+ .map(|response| response.env.into_iter().collect())
+ .unwrap_or_default()
+ }
+
+ async fn which(&self, command: &OsStr) -> Option<PathBuf> {
+ use rpc::proto::SSH_PROJECT_ID;
+
+ self.upstream_client
+ .request(proto::WhichCommand {
+ project_id: SSH_PROJECT_ID,
+ worktree_id: self.worktree_id().to_proto(),
+ command: command.to_string_lossy().to_string(),
+ })
+ .await
+ .log_err()
+ .and_then(|response| response.path)
+ .map(PathBuf::from)
+ }
+
+ async fn try_exec(&self, command: LanguageServerBinary) -> Result<()> {
+ self.upstream_client
+ .request(proto::TryExec {
+ project_id: rpc::proto::SSH_PROJECT_ID,
+ worktree_id: self.worktree.id().to_proto(),
+ binary: Some(proto::LanguageServerCommand {
+ path: command.path.to_string_lossy().to_string(),
+ arguments: command
+ .arguments
+ .into_iter()
+ .map(|s| s.to_string_lossy().to_string())
+ .collect(),
+ env: command.env.unwrap_or_default().into_iter().collect(),
+ }),
+ })
+ .await?;
+ Ok(())
+ }
+
+ fn update_status(
+ &self,
+ server_name: LanguageServerName,
+ status: language::LanguageServerBinaryStatus,
+ ) {
+ self.language_registry
+ .update_lsp_status(server_name, status);
+ }
+
+ async fn read_text_file(&self, path: PathBuf) -> Result<String> {
+ self.upstream_client
+ .request(proto::ReadTextFile {
+ project_id: rpc::proto::SSH_PROJECT_ID,
+ path: Some(proto::ProjectPath {
+ worktree_id: self.worktree.id().to_proto(),
+ path: path.to_string_lossy().to_string(),
+ }),
+ })
+ .await
+ .map(|r| r.text)
}
}
@@ -15,7 +15,7 @@ use gpui::{AppContext, AsyncAppContext, Model, SemanticVersion};
use parking_lot::Mutex;
use rpc::{
proto::{self, build_typed_envelope, Envelope, EnvelopedMessage, PeerId, RequestMessage},
- EntityMessageSubscriber, ProtoClient, ProtoMessageHandlerSet,
+ EntityMessageSubscriber, ProtoClient, ProtoMessageHandlerSet, RpcError,
};
use smol::{
fs,
@@ -157,8 +157,9 @@ impl SshSession {
let mut remote_server_child = socket
.ssh_command(format!(
- "RUST_LOG={} {:?} run",
+ "RUST_LOG={} RUST_BACKTRACE={} {:?} run",
std::env::var("RUST_LOG").unwrap_or_default(),
+ std::env::var("RUST_BACKTRACE").unwrap_or_default(),
remote_binary_path,
))
.spawn()
@@ -349,7 +350,7 @@ impl SshSession {
}
Err(error) => {
log::error!(
- "error handling message. type:{type_name}, error:{error:?}",
+ "error handling message. type:{type_name}, error:{error}",
);
}
}
@@ -371,7 +372,7 @@ impl SshSession {
payload: T,
) -> impl 'static + Future<Output = Result<T::Response>> {
log::debug!("ssh request start. name:{}", T::NAME);
- let response = self.request_dynamic(payload.into_envelope(0, None, None), "");
+ let response = self.request_dynamic(payload.into_envelope(0, None, None), T::NAME);
async move {
let response = response.await?;
log::debug!("ssh request finish. name:{}", T::NAME);
@@ -388,7 +389,7 @@ impl SshSession {
pub fn request_dynamic(
&self,
mut envelope: proto::Envelope,
- _request_type: &'static str,
+ type_name: &'static str,
) -> impl 'static + Future<Output = Result<proto::Envelope>> {
envelope.id = self.next_message_id.fetch_add(1, SeqCst);
let (tx, rx) = oneshot::channel();
@@ -396,7 +397,13 @@ impl SshSession {
response_channels_lock.insert(MessageId(envelope.id), tx);
drop(response_channels_lock);
self.outgoing_tx.unbounded_send(envelope).ok();
- async move { Ok(rx.await.context("connection lost")?.0) }
+ async move {
+ let response = rx.await.context("connection lost")?.0;
+ if let Some(proto::envelope::Payload::Error(error)) = &response.payload {
+ return Err(RpcError::from_proto(error, type_name));
+ }
+ Ok(response)
+ }
}
pub fn send_dynamic(&self, mut envelope: proto::Envelope) -> Result<()> {