ssh lsp completions (#17665)
Release Notes: * ssh-remoting: Fixed shell environment loading for remote shells.
This commit is contained in:
parent
dea85099a2
commit
ca2cce79ed
8 changed files with 74 additions and 78 deletions
|
@ -12,7 +12,7 @@ use collections::{hash_map, HashMap, HashSet};
|
||||||
use futures::{
|
use futures::{
|
||||||
channel::{mpsc, oneshot},
|
channel::{mpsc, oneshot},
|
||||||
future::Shared,
|
future::Shared,
|
||||||
Future, FutureExt as _,
|
Future,
|
||||||
};
|
};
|
||||||
use globset::GlobSet;
|
use globset::GlobSet;
|
||||||
use gpui::{AppContext, BackgroundExecutor, Task};
|
use gpui::{AppContext, BackgroundExecutor, Task};
|
||||||
|
@ -79,7 +79,6 @@ impl<'a> From<&'a str> for LanguageName {
|
||||||
pub struct LanguageRegistry {
|
pub struct LanguageRegistry {
|
||||||
state: RwLock<LanguageRegistryState>,
|
state: RwLock<LanguageRegistryState>,
|
||||||
language_server_download_dir: Option<Arc<Path>>,
|
language_server_download_dir: Option<Arc<Path>>,
|
||||||
login_shell_env_loaded: Shared<Task<()>>,
|
|
||||||
executor: BackgroundExecutor,
|
executor: BackgroundExecutor,
|
||||||
lsp_binary_status_tx: LspBinaryStatusSender,
|
lsp_binary_status_tx: LspBinaryStatusSender,
|
||||||
}
|
}
|
||||||
|
@ -204,7 +203,7 @@ struct LspBinaryStatusSender {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LanguageRegistry {
|
impl LanguageRegistry {
|
||||||
pub fn new(login_shell_env_loaded: Task<()>, executor: BackgroundExecutor) -> Self {
|
pub fn new(executor: BackgroundExecutor) -> Self {
|
||||||
let this = Self {
|
let this = Self {
|
||||||
state: RwLock::new(LanguageRegistryState {
|
state: RwLock::new(LanguageRegistryState {
|
||||||
next_language_server_id: 0,
|
next_language_server_id: 0,
|
||||||
|
@ -224,7 +223,6 @@ impl LanguageRegistry {
|
||||||
fake_server_txs: Default::default(),
|
fake_server_txs: Default::default(),
|
||||||
}),
|
}),
|
||||||
language_server_download_dir: None,
|
language_server_download_dir: None,
|
||||||
login_shell_env_loaded: login_shell_env_loaded.shared(),
|
|
||||||
lsp_binary_status_tx: Default::default(),
|
lsp_binary_status_tx: Default::default(),
|
||||||
executor,
|
executor,
|
||||||
};
|
};
|
||||||
|
@ -234,7 +232,7 @@ impl LanguageRegistry {
|
||||||
|
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
pub fn test(executor: BackgroundExecutor) -> Self {
|
pub fn test(executor: BackgroundExecutor) -> Self {
|
||||||
let mut this = Self::new(Task::ready(()), executor);
|
let mut this = Self::new(executor);
|
||||||
this.language_server_download_dir = Some(Path::new("/the-download-dir").into());
|
this.language_server_download_dir = Some(Path::new("/the-download-dir").into());
|
||||||
this
|
this
|
||||||
}
|
}
|
||||||
|
@ -828,7 +826,7 @@ impl LanguageRegistry {
|
||||||
adapter: Arc<CachedLspAdapter>,
|
adapter: Arc<CachedLspAdapter>,
|
||||||
root_path: Arc<Path>,
|
root_path: Arc<Path>,
|
||||||
delegate: Arc<dyn LspAdapterDelegate>,
|
delegate: Arc<dyn LspAdapterDelegate>,
|
||||||
cli_environment: Option<HashMap<String, String>>,
|
cli_environment: Shared<Task<Option<HashMap<String, String>>>>,
|
||||||
cx: &mut AppContext,
|
cx: &mut AppContext,
|
||||||
) -> Option<PendingLanguageServer> {
|
) -> Option<PendingLanguageServer> {
|
||||||
let server_id = self.state.write().next_language_server_id();
|
let server_id = self.state.write().next_language_server_id();
|
||||||
|
@ -844,15 +842,12 @@ impl LanguageRegistry {
|
||||||
.log_err()?;
|
.log_err()?;
|
||||||
let container_dir: Arc<Path> = Arc::from(download_dir.join(adapter.name.0.as_ref()));
|
let container_dir: Arc<Path> = Arc::from(download_dir.join(adapter.name.0.as_ref()));
|
||||||
let root_path = root_path.clone();
|
let root_path = root_path.clone();
|
||||||
let login_shell_env_loaded = self.login_shell_env_loaded.clone();
|
|
||||||
let this = Arc::downgrade(self);
|
let this = Arc::downgrade(self);
|
||||||
|
|
||||||
let task = cx.spawn({
|
let task = cx.spawn({
|
||||||
let container_dir = container_dir.clone();
|
let container_dir = container_dir.clone();
|
||||||
move |mut cx| async move {
|
move |mut cx| async move {
|
||||||
// If we want to install a binary globally, we need to wait for
|
let cli_environment = cli_environment.await;
|
||||||
// the login shell to be set on our process.
|
|
||||||
login_shell_env_loaded.await;
|
|
||||||
|
|
||||||
let binary_result = adapter
|
let binary_result = adapter
|
||||||
.clone()
|
.clone()
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use assets::Assets;
|
use assets::Assets;
|
||||||
use gpui::{prelude::*, rgb, App, KeyBinding, StyleRefinement, Task, View, WindowOptions};
|
use gpui::{prelude::*, rgb, App, KeyBinding, StyleRefinement, View, WindowOptions};
|
||||||
use language::{language_settings::AllLanguageSettings, LanguageRegistry};
|
use language::{language_settings::AllLanguageSettings, LanguageRegistry};
|
||||||
use markdown::{Markdown, MarkdownStyle};
|
use markdown::{Markdown, MarkdownStyle};
|
||||||
use node_runtime::FakeNodeRuntime;
|
use node_runtime::FakeNodeRuntime;
|
||||||
|
@ -105,8 +105,7 @@ pub fn main() {
|
||||||
let node_runtime = FakeNodeRuntime::new();
|
let node_runtime = FakeNodeRuntime::new();
|
||||||
theme::init(LoadThemes::JustBase, cx);
|
theme::init(LoadThemes::JustBase, cx);
|
||||||
|
|
||||||
let language_registry =
|
let language_registry = LanguageRegistry::new(cx.background_executor().clone());
|
||||||
LanguageRegistry::new(Task::ready(()), cx.background_executor().clone());
|
|
||||||
language_registry.set_theme(cx.theme().clone());
|
language_registry.set_theme(cx.theme().clone());
|
||||||
let language_registry = Arc::new(language_registry);
|
let language_registry = Arc::new(language_registry);
|
||||||
languages::init(language_registry.clone(), node_runtime, cx);
|
languages::init(language_registry.clone(), node_runtime, cx);
|
||||||
|
|
|
@ -29,10 +29,7 @@ pub fn main() {
|
||||||
cx.bind_keys([KeyBinding::new("cmd-c", markdown::Copy, None)]);
|
cx.bind_keys([KeyBinding::new("cmd-c", markdown::Copy, None)]);
|
||||||
|
|
||||||
let node_runtime = FakeNodeRuntime::new();
|
let node_runtime = FakeNodeRuntime::new();
|
||||||
let language_registry = Arc::new(LanguageRegistry::new(
|
let language_registry = Arc::new(LanguageRegistry::new(cx.background_executor().clone()));
|
||||||
Task::ready(()),
|
|
||||||
cx.background_executor().clone(),
|
|
||||||
));
|
|
||||||
languages::init(language_registry.clone(), node_runtime, cx);
|
languages::init(language_registry.clone(), node_runtime, cx);
|
||||||
theme::init(LoadThemes::JustBase, cx);
|
theme::init(LoadThemes::JustBase, cx);
|
||||||
Assets.load_fonts(cx).unwrap();
|
Assets.load_fonts(cx).unwrap();
|
||||||
|
|
|
@ -1,10 +1,7 @@
|
||||||
use anyhow::{anyhow, Context as _, Result};
|
use anyhow::Result;
|
||||||
use futures::{future::Shared, FutureExt};
|
use futures::{future::Shared, FutureExt};
|
||||||
use std::{
|
use std::{path::Path, sync::Arc};
|
||||||
path::{Path, PathBuf},
|
use util::ResultExt;
|
||||||
sync::Arc,
|
|
||||||
};
|
|
||||||
use util::{parse_env_output, ResultExt};
|
|
||||||
|
|
||||||
use collections::HashMap;
|
use collections::HashMap;
|
||||||
use gpui::{AppContext, Context, Model, ModelContext, Task};
|
use gpui::{AppContext, Context, Model, ModelContext, Task};
|
||||||
|
@ -168,10 +165,53 @@ impl From<EnvironmentOrigin> for String {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
|
async fn load_shell_environment(
|
||||||
|
_dir: &Path,
|
||||||
|
_load_direnv: &DirenvSettings,
|
||||||
|
) -> Result<HashMap<String, String>> {
|
||||||
|
Ok([("ZED_FAKE_TEST_ENV".into(), "true".into())]
|
||||||
|
.into_iter()
|
||||||
|
.collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(any(test, feature = "test-support")))]
|
||||||
async fn load_shell_environment(
|
async fn load_shell_environment(
|
||||||
dir: &Path,
|
dir: &Path,
|
||||||
load_direnv: &DirenvSettings,
|
load_direnv: &DirenvSettings,
|
||||||
) -> Result<HashMap<String, String>> {
|
) -> Result<HashMap<String, String>> {
|
||||||
|
use anyhow::{anyhow, Context};
|
||||||
|
use std::path::PathBuf;
|
||||||
|
use util::parse_env_output;
|
||||||
|
|
||||||
|
async fn load_direnv_environment(dir: &Path) -> Result<Option<HashMap<String, String>>> {
|
||||||
|
let Ok(direnv_path) = which::which("direnv") else {
|
||||||
|
return Ok(None);
|
||||||
|
};
|
||||||
|
|
||||||
|
let direnv_output = smol::process::Command::new(direnv_path)
|
||||||
|
.args(["export", "json"])
|
||||||
|
.current_dir(dir)
|
||||||
|
.output()
|
||||||
|
.await
|
||||||
|
.context("failed to spawn direnv to get local environment variables")?;
|
||||||
|
|
||||||
|
anyhow::ensure!(
|
||||||
|
direnv_output.status.success(),
|
||||||
|
"direnv exited with error {:?}",
|
||||||
|
direnv_output.status
|
||||||
|
);
|
||||||
|
|
||||||
|
let output = String::from_utf8_lossy(&direnv_output.stdout);
|
||||||
|
if output.is_empty() {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Some(
|
||||||
|
serde_json::from_str(&output).context("failed to parse direnv output")?,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
let direnv_environment = match load_direnv {
|
let direnv_environment = match load_direnv {
|
||||||
DirenvSettings::ShellHook => None,
|
DirenvSettings::ShellHook => None,
|
||||||
DirenvSettings::Direct => load_direnv_environment(dir).await?,
|
DirenvSettings::Direct => load_direnv_environment(dir).await?,
|
||||||
|
@ -248,31 +288,3 @@ async fn load_shell_environment(
|
||||||
|
|
||||||
Ok(parsed_env)
|
Ok(parsed_env)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn load_direnv_environment(dir: &Path) -> Result<Option<HashMap<String, String>>> {
|
|
||||||
let Ok(direnv_path) = which::which("direnv") else {
|
|
||||||
return Ok(None);
|
|
||||||
};
|
|
||||||
|
|
||||||
let direnv_output = smol::process::Command::new(direnv_path)
|
|
||||||
.args(["export", "json"])
|
|
||||||
.current_dir(dir)
|
|
||||||
.output()
|
|
||||||
.await
|
|
||||||
.context("failed to spawn direnv to get local environment variables")?;
|
|
||||||
|
|
||||||
anyhow::ensure!(
|
|
||||||
direnv_output.status.success(),
|
|
||||||
"direnv exited with error {:?}",
|
|
||||||
direnv_output.status
|
|
||||||
);
|
|
||||||
|
|
||||||
let output = String::from_utf8_lossy(&direnv_output.stdout);
|
|
||||||
if output.is_empty() {
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(Some(
|
|
||||||
serde_json::from_str(&output).context("failed to parse direnv output")?,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
|
@ -4629,14 +4629,13 @@ impl LspStore {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let local = self.as_local().unwrap();
|
||||||
|
|
||||||
let stderr_capture = Arc::new(Mutex::new(Some(String::new())));
|
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 = ProjectLspAdapterDelegate::for_local(self, worktree_handle, cx);
|
||||||
let cli_environment = self
|
let cli_environment = local.environment.update(cx, |environment, cx| {
|
||||||
.as_local()
|
environment.get_environment(Some(worktree_id), Some(worktree_path.clone()), cx)
|
||||||
.unwrap()
|
});
|
||||||
.environment
|
|
||||||
.read(cx)
|
|
||||||
.get_cli_environment();
|
|
||||||
|
|
||||||
let pending_server = match self.languages.create_pending_language_server(
|
let pending_server = match self.languages.create_pending_language_server(
|
||||||
stderr_capture.clone(),
|
stderr_capture.clone(),
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use fs::Fs;
|
use fs::Fs;
|
||||||
use gpui::{AppContext, AsyncAppContext, Context, Model, ModelContext, Task};
|
use gpui::{AppContext, AsyncAppContext, Context, Model, ModelContext};
|
||||||
use language::LanguageRegistry;
|
use language::LanguageRegistry;
|
||||||
use project::{
|
use project::{
|
||||||
buffer_store::BufferStore, project_settings::SettingsObserver, search::SearchQuery,
|
buffer_store::BufferStore, project_settings::SettingsObserver, search::SearchQuery,
|
||||||
|
@ -36,9 +36,7 @@ impl HeadlessProject {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new(session: Arc<SshSession>, fs: Arc<dyn Fs>, cx: &mut ModelContext<Self>) -> Self {
|
pub fn new(session: Arc<SshSession>, fs: Arc<dyn Fs>, cx: &mut ModelContext<Self>) -> Self {
|
||||||
// TODO: we should load the env correctly (as we do in login_shell_env_loaded when stdout is not a pty). Can we re-use the ProjectEnvironment for that?
|
let mut languages = LanguageRegistry::new(cx.background_executor().clone());
|
||||||
let mut languages =
|
|
||||||
LanguageRegistry::new(Task::ready(()), cx.background_executor().clone());
|
|
||||||
languages
|
languages
|
||||||
.set_language_server_download_dir(PathBuf::from("/Users/conrad/what-could-go-wrong"));
|
.set_language_server_download_dir(PathBuf::from("/Users/conrad/what-could-go-wrong"));
|
||||||
|
|
||||||
|
|
|
@ -321,7 +321,7 @@ fn get_language(editor: WeakView<Editor>, cx: &mut AppContext) -> Option<Arc<Lan
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use gpui::{Context, Task};
|
use gpui::Context;
|
||||||
use indoc::indoc;
|
use indoc::indoc;
|
||||||
use language::{Buffer, Language, LanguageConfig, LanguageRegistry};
|
use language::{Buffer, Language, LanguageConfig, LanguageRegistry};
|
||||||
|
|
||||||
|
@ -475,10 +475,7 @@ mod tests {
|
||||||
let typescript =
|
let typescript =
|
||||||
languages::language("typescript", tree_sitter_typescript::language_typescript());
|
languages::language("typescript", tree_sitter_typescript::language_typescript());
|
||||||
let python = languages::language("python", tree_sitter_python::language());
|
let python = languages::language("python", tree_sitter_python::language());
|
||||||
let language_registry = Arc::new(LanguageRegistry::new(
|
let language_registry = Arc::new(LanguageRegistry::new(cx.background_executor().clone()));
|
||||||
Task::ready(()),
|
|
||||||
cx.background_executor().clone(),
|
|
||||||
));
|
|
||||||
language_registry.add(markdown.clone());
|
language_registry.add(markdown.clone());
|
||||||
language_registry.add(typescript.clone());
|
language_registry.add(typescript.clone());
|
||||||
language_registry.add(python.clone());
|
language_registry.add(python.clone());
|
||||||
|
|
|
@ -400,16 +400,16 @@ fn main() {
|
||||||
paths::keymap_file().clone(),
|
paths::keymap_file().clone(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let login_shell_env_loaded = if stdout_is_a_pty() {
|
if !stdout_is_a_pty() {
|
||||||
Task::ready(())
|
app.background_executor()
|
||||||
} else {
|
.spawn(async {
|
||||||
app.background_executor().spawn(async {
|
#[cfg(unix)]
|
||||||
#[cfg(unix)]
|
{
|
||||||
{
|
load_shell_from_passwd().await.log_err();
|
||||||
load_shell_from_passwd().await.log_err();
|
}
|
||||||
}
|
load_login_shell_environment().await.log_err();
|
||||||
load_login_shell_environment().await.log_err();
|
})
|
||||||
})
|
.detach()
|
||||||
};
|
};
|
||||||
|
|
||||||
app.on_open_urls({
|
app.on_open_urls({
|
||||||
|
@ -451,8 +451,7 @@ fn main() {
|
||||||
client::init_settings(cx);
|
client::init_settings(cx);
|
||||||
let client = Client::production(cx);
|
let client = Client::production(cx);
|
||||||
cx.set_http_client(client.http_client().clone());
|
cx.set_http_client(client.http_client().clone());
|
||||||
let mut languages =
|
let mut languages = LanguageRegistry::new(cx.background_executor().clone());
|
||||||
LanguageRegistry::new(login_shell_env_loaded, cx.background_executor().clone());
|
|
||||||
languages.set_language_server_download_dir(paths::languages_dir().clone());
|
languages.set_language_server_download_dir(paths::languages_dir().clone());
|
||||||
let languages = Arc::new(languages);
|
let languages = Arc::new(languages);
|
||||||
let node_runtime = RealNodeRuntime::new(client.http_client());
|
let node_runtime = RealNodeRuntime::new(client.http_client());
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue