parent
7814dd0301
commit
8cc6df573c
8 changed files with 268 additions and 148 deletions
|
@ -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,
|
||||
)
|
||||
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,
|
||||
)
|
||||
}
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue