|
|
|
@@ -4,6 +4,7 @@ use crate::{
|
|
|
|
|
Event, git_store::StatusEntry, task_inventory::TaskContexts, task_store::TaskSettingsLocation,
|
|
|
|
|
*,
|
|
|
|
|
};
|
|
|
|
|
use async_trait::async_trait;
|
|
|
|
|
use buffer_diff::{
|
|
|
|
|
BufferDiffEvent, CALCULATE_DIFF_TASK, DiffHunkSecondaryStatus, DiffHunkStatus,
|
|
|
|
|
DiffHunkStatusKind, assert_hunks,
|
|
|
|
@@ -21,7 +22,8 @@ use http_client::Url;
|
|
|
|
|
use itertools::Itertools;
|
|
|
|
|
use language::{
|
|
|
|
|
Diagnostic, DiagnosticEntry, DiagnosticSet, DiagnosticSourceKind, DiskState, FakeLspAdapter,
|
|
|
|
|
LanguageConfig, LanguageMatcher, LanguageName, LineEnding, OffsetRangeExt, Point, ToPoint,
|
|
|
|
|
LanguageConfig, LanguageMatcher, LanguageName, LineEnding, ManifestName, ManifestProvider,
|
|
|
|
|
ManifestQuery, OffsetRangeExt, Point, ToPoint, ToolchainLister,
|
|
|
|
|
language_settings::{AllLanguageSettings, LanguageSettingsContent, language_settings},
|
|
|
|
|
tree_sitter_rust, tree_sitter_typescript,
|
|
|
|
|
};
|
|
|
|
@@ -596,6 +598,203 @@ async fn test_fallback_to_single_worktree_tasks(cx: &mut gpui::TestAppContext) {
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[gpui::test]
|
|
|
|
|
async fn test_running_multiple_instances_of_a_single_server_in_one_worktree(
|
|
|
|
|
cx: &mut gpui::TestAppContext,
|
|
|
|
|
) {
|
|
|
|
|
pub(crate) struct PyprojectTomlManifestProvider;
|
|
|
|
|
|
|
|
|
|
impl ManifestProvider for PyprojectTomlManifestProvider {
|
|
|
|
|
fn name(&self) -> ManifestName {
|
|
|
|
|
SharedString::new_static("pyproject.toml").into()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn search(
|
|
|
|
|
&self,
|
|
|
|
|
ManifestQuery {
|
|
|
|
|
path,
|
|
|
|
|
depth,
|
|
|
|
|
delegate,
|
|
|
|
|
}: ManifestQuery,
|
|
|
|
|
) -> Option<Arc<Path>> {
|
|
|
|
|
for path in path.ancestors().take(depth) {
|
|
|
|
|
let p = path.join("pyproject.toml");
|
|
|
|
|
if delegate.exists(&p, Some(false)) {
|
|
|
|
|
return Some(path.into());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
init_test(cx);
|
|
|
|
|
let fs = FakeFs::new(cx.executor());
|
|
|
|
|
|
|
|
|
|
fs.insert_tree(
|
|
|
|
|
path!("/the-root"),
|
|
|
|
|
json!({
|
|
|
|
|
".zed": {
|
|
|
|
|
"settings.json": r#"
|
|
|
|
|
{
|
|
|
|
|
"languages": {
|
|
|
|
|
"Python": {
|
|
|
|
|
"language_servers": ["ty"]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}"#
|
|
|
|
|
},
|
|
|
|
|
"project-a": {
|
|
|
|
|
".venv": {},
|
|
|
|
|
"file.py": "",
|
|
|
|
|
"pyproject.toml": ""
|
|
|
|
|
},
|
|
|
|
|
"project-b": {
|
|
|
|
|
".venv": {},
|
|
|
|
|
"source_file.py":"",
|
|
|
|
|
"another_file.py": "",
|
|
|
|
|
"pyproject.toml": ""
|
|
|
|
|
}
|
|
|
|
|
}),
|
|
|
|
|
)
|
|
|
|
|
.await;
|
|
|
|
|
cx.update(|cx| {
|
|
|
|
|
ManifestProvidersStore::global(cx).register(Arc::new(PyprojectTomlManifestProvider))
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
let project = Project::test(fs.clone(), [path!("/the-root").as_ref()], cx).await;
|
|
|
|
|
let language_registry = project.read_with(cx, |project, _| project.languages().clone());
|
|
|
|
|
let _fake_python_server = language_registry.register_fake_lsp(
|
|
|
|
|
"Python",
|
|
|
|
|
FakeLspAdapter {
|
|
|
|
|
name: "ty",
|
|
|
|
|
capabilities: lsp::ServerCapabilities {
|
|
|
|
|
..Default::default()
|
|
|
|
|
},
|
|
|
|
|
..Default::default()
|
|
|
|
|
},
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
language_registry.add(python_lang(fs.clone()));
|
|
|
|
|
let (first_buffer, _handle) = project
|
|
|
|
|
.update(cx, |project, cx| {
|
|
|
|
|
project.open_local_buffer_with_lsp(path!("/the-root/project-a/file.py"), cx)
|
|
|
|
|
})
|
|
|
|
|
.await
|
|
|
|
|
.unwrap();
|
|
|
|
|
cx.executor().run_until_parked();
|
|
|
|
|
let servers = project.update(cx, |project, cx| {
|
|
|
|
|
project.lsp_store.update(cx, |this, cx| {
|
|
|
|
|
first_buffer.update(cx, |buffer, cx| {
|
|
|
|
|
this.language_servers_for_local_buffer(buffer, cx)
|
|
|
|
|
.map(|(adapter, server)| (adapter.clone(), server.clone()))
|
|
|
|
|
.collect::<Vec<_>>()
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
});
|
|
|
|
|
cx.executor().run_until_parked();
|
|
|
|
|
assert_eq!(servers.len(), 1);
|
|
|
|
|
let (adapter, server) = servers.into_iter().next().unwrap();
|
|
|
|
|
assert_eq!(adapter.name(), LanguageServerName::new_static("ty"));
|
|
|
|
|
assert_eq!(server.server_id(), LanguageServerId(0));
|
|
|
|
|
// `workspace_folders` are set to the rooting point.
|
|
|
|
|
assert_eq!(
|
|
|
|
|
server.workspace_folders(),
|
|
|
|
|
BTreeSet::from_iter(
|
|
|
|
|
[Url::from_file_path(path!("/the-root/project-a")).unwrap()].into_iter()
|
|
|
|
|
)
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
let (second_project_buffer, _other_handle) = project
|
|
|
|
|
.update(cx, |project, cx| {
|
|
|
|
|
project.open_local_buffer_with_lsp(path!("/the-root/project-b/source_file.py"), cx)
|
|
|
|
|
})
|
|
|
|
|
.await
|
|
|
|
|
.unwrap();
|
|
|
|
|
cx.executor().run_until_parked();
|
|
|
|
|
let servers = project.update(cx, |project, cx| {
|
|
|
|
|
project.lsp_store.update(cx, |this, cx| {
|
|
|
|
|
second_project_buffer.update(cx, |buffer, cx| {
|
|
|
|
|
this.language_servers_for_local_buffer(buffer, cx)
|
|
|
|
|
.map(|(adapter, server)| (adapter.clone(), server.clone()))
|
|
|
|
|
.collect::<Vec<_>>()
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
});
|
|
|
|
|
cx.executor().run_until_parked();
|
|
|
|
|
assert_eq!(servers.len(), 1);
|
|
|
|
|
let (adapter, server) = servers.into_iter().next().unwrap();
|
|
|
|
|
assert_eq!(adapter.name(), LanguageServerName::new_static("ty"));
|
|
|
|
|
// We're not using venvs at all here, so both folders should fall under the same root.
|
|
|
|
|
assert_eq!(server.server_id(), LanguageServerId(0));
|
|
|
|
|
// Now, let's select a different toolchain for one of subprojects.
|
|
|
|
|
let (available_toolchains_for_b, root_path) = project
|
|
|
|
|
.update(cx, |this, cx| {
|
|
|
|
|
let worktree_id = this.worktrees(cx).next().unwrap().read(cx).id();
|
|
|
|
|
this.available_toolchains(
|
|
|
|
|
ProjectPath {
|
|
|
|
|
worktree_id,
|
|
|
|
|
path: Arc::from("project-b/source_file.py".as_ref()),
|
|
|
|
|
},
|
|
|
|
|
LanguageName::new("Python"),
|
|
|
|
|
cx,
|
|
|
|
|
)
|
|
|
|
|
})
|
|
|
|
|
.await
|
|
|
|
|
.expect("A toolchain to be discovered");
|
|
|
|
|
assert_eq!(root_path.as_ref(), Path::new("project-b"));
|
|
|
|
|
assert_eq!(available_toolchains_for_b.toolchains().len(), 1);
|
|
|
|
|
let currently_active_toolchain = project
|
|
|
|
|
.update(cx, |this, cx| {
|
|
|
|
|
let worktree_id = this.worktrees(cx).next().unwrap().read(cx).id();
|
|
|
|
|
this.active_toolchain(
|
|
|
|
|
ProjectPath {
|
|
|
|
|
worktree_id,
|
|
|
|
|
path: Arc::from("project-b/source_file.py".as_ref()),
|
|
|
|
|
},
|
|
|
|
|
LanguageName::new("Python"),
|
|
|
|
|
cx,
|
|
|
|
|
)
|
|
|
|
|
})
|
|
|
|
|
.await;
|
|
|
|
|
|
|
|
|
|
assert!(currently_active_toolchain.is_none());
|
|
|
|
|
let _ = project
|
|
|
|
|
.update(cx, |this, cx| {
|
|
|
|
|
let worktree_id = this.worktrees(cx).next().unwrap().read(cx).id();
|
|
|
|
|
this.activate_toolchain(
|
|
|
|
|
ProjectPath {
|
|
|
|
|
worktree_id,
|
|
|
|
|
path: root_path,
|
|
|
|
|
},
|
|
|
|
|
available_toolchains_for_b
|
|
|
|
|
.toolchains
|
|
|
|
|
.into_iter()
|
|
|
|
|
.next()
|
|
|
|
|
.unwrap(),
|
|
|
|
|
cx,
|
|
|
|
|
)
|
|
|
|
|
})
|
|
|
|
|
.await
|
|
|
|
|
.unwrap();
|
|
|
|
|
cx.run_until_parked();
|
|
|
|
|
let servers = project.update(cx, |project, cx| {
|
|
|
|
|
project.lsp_store.update(cx, |this, cx| {
|
|
|
|
|
second_project_buffer.update(cx, |buffer, cx| {
|
|
|
|
|
this.language_servers_for_local_buffer(buffer, cx)
|
|
|
|
|
.map(|(adapter, server)| (adapter.clone(), server.clone()))
|
|
|
|
|
.collect::<Vec<_>>()
|
|
|
|
|
})
|
|
|
|
|
})
|
|
|
|
|
});
|
|
|
|
|
cx.executor().run_until_parked();
|
|
|
|
|
assert_eq!(servers.len(), 1);
|
|
|
|
|
let (adapter, server) = servers.into_iter().next().unwrap();
|
|
|
|
|
assert_eq!(adapter.name(), LanguageServerName::new_static("ty"));
|
|
|
|
|
// There's a new language server in town.
|
|
|
|
|
assert_eq!(server.server_id(), LanguageServerId(1));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[gpui::test]
|
|
|
|
|
async fn test_managing_language_servers(cx: &mut gpui::TestAppContext) {
|
|
|
|
|
init_test(cx);
|
|
|
|
@@ -8982,6 +9181,65 @@ fn rust_lang() -> Arc<Language> {
|
|
|
|
|
))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn python_lang(fs: Arc<FakeFs>) -> Arc<Language> {
|
|
|
|
|
struct PythonMootToolchainLister(Arc<FakeFs>);
|
|
|
|
|
#[async_trait]
|
|
|
|
|
impl ToolchainLister for PythonMootToolchainLister {
|
|
|
|
|
async fn list(
|
|
|
|
|
&self,
|
|
|
|
|
worktree_root: PathBuf,
|
|
|
|
|
subroot_relative_path: Option<Arc<Path>>,
|
|
|
|
|
_: Option<HashMap<String, String>>,
|
|
|
|
|
) -> ToolchainList {
|
|
|
|
|
// This lister will always return a path .venv directories within ancestors
|
|
|
|
|
let ancestors = subroot_relative_path
|
|
|
|
|
.into_iter()
|
|
|
|
|
.flat_map(|path| path.ancestors().map(ToOwned::to_owned).collect::<Vec<_>>());
|
|
|
|
|
let mut toolchains = vec![];
|
|
|
|
|
for ancestor in ancestors {
|
|
|
|
|
let venv_path = worktree_root.join(ancestor).join(".venv");
|
|
|
|
|
if self.0.is_dir(&venv_path).await {
|
|
|
|
|
toolchains.push(Toolchain {
|
|
|
|
|
name: SharedString::new("Python Venv"),
|
|
|
|
|
path: venv_path.to_string_lossy().into_owned().into(),
|
|
|
|
|
language_name: LanguageName(SharedString::new_static("Python")),
|
|
|
|
|
as_json: serde_json::Value::Null,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
ToolchainList {
|
|
|
|
|
toolchains,
|
|
|
|
|
..Default::default()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// Returns a term which we should use in UI to refer to a toolchain.
|
|
|
|
|
fn term(&self) -> SharedString {
|
|
|
|
|
SharedString::new_static("virtual environment")
|
|
|
|
|
}
|
|
|
|
|
/// Returns the name of the manifest file for this toolchain.
|
|
|
|
|
fn manifest_name(&self) -> ManifestName {
|
|
|
|
|
SharedString::new_static("pyproject.toml").into()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Arc::new(
|
|
|
|
|
Language::new(
|
|
|
|
|
LanguageConfig {
|
|
|
|
|
name: "Python".into(),
|
|
|
|
|
matcher: LanguageMatcher {
|
|
|
|
|
path_suffixes: vec!["py".to_string()],
|
|
|
|
|
..Default::default()
|
|
|
|
|
},
|
|
|
|
|
..Default::default()
|
|
|
|
|
},
|
|
|
|
|
None, // We're not testing Python parsing with this language.
|
|
|
|
|
)
|
|
|
|
|
.with_manifest(Some(ManifestName::from(SharedString::new_static(
|
|
|
|
|
"pyproject.toml",
|
|
|
|
|
))))
|
|
|
|
|
.with_toolchain_lister(Some(Arc::new(PythonMootToolchainLister(fs)))),
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn typescript_lang() -> Arc<Language> {
|
|
|
|
|
Arc::new(Language::new(
|
|
|
|
|
LanguageConfig {
|
|
|
|
|