Cargo.lock 🔗
@@ -9226,6 +9226,7 @@ dependencies = [
"chrono",
"collections",
"dap",
+ "feature_flags",
"futures 0.3.31",
"gpui",
"http_client",
Ben Brandt created
Cargo.lock | 1
assets/icons/cloud_download.svg | 1
crates/editor/src/editor.rs | 11
crates/editor/src/editor_tests.rs | 108 +++++
crates/editor/src/lsp_colors.rs | 6
crates/icons/src/icons.rs | 1
crates/language/src/language.rs | 16
crates/language_tools/src/lsp_log.rs | 2
crates/languages/Cargo.toml | 1
crates/languages/src/lib.rs | 24 +
crates/languages/src/python.rs | 345 ++++++++++++++++++++
crates/languages/src/typescript/runnables.scm | 41 ++
crates/lsp/src/lsp.rs | 64 ++-
crates/onboarding/src/welcome.rs | 3
crates/project/src/lsp_store.rs | 246 +++++++++++---
crates/project/src/project.rs | 27 +
crates/workspace/src/tasks.rs | 8
crates/workspace/src/workspace.rs | 2
crates/zed/src/zed.rs | 33 +
docs/src/configuring-zed.md | 2
docs/src/languages/deno.md | 34 ++
21 files changed, 837 insertions(+), 139 deletions(-)
@@ -9226,6 +9226,7 @@ dependencies = [
"chrono",
"collections",
"dap",
+ "feature_flags",
"futures 0.3.31",
"gpui",
"http_client",
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-cloud-download-icon lucide-cloud-download"><path d="M12 13v8l-4-4"/><path d="m12 21 4-4"/><path d="M4.393 15.269A7 7 0 1 1 15.71 8h1.79a4.5 4.5 0 0 1 2.436 8.284"/></svg>
@@ -1774,7 +1774,7 @@ impl Editor {
) -> Self {
debug_assert!(
display_map.is_none() || mode.is_minimap(),
- "Providing a display map for a new editor is only intended for the minimap and might have unindended side effects otherwise!"
+ "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
);
let full_mode = mode.is_full();
@@ -8235,8 +8235,7 @@ impl Editor {
return;
};
- // Try to find a closest, enclosing node using tree-sitter that has a
- // task
+ // Try to find a closest, enclosing node using tree-sitter that has a task
let Some((buffer, buffer_row, tasks)) = self
.find_enclosing_node_task(cx)
// Or find the task that's closest in row-distance.
@@ -21835,11 +21834,11 @@ impl CodeActionProvider for Entity<Project> {
cx: &mut App,
) -> Task<Result<Vec<CodeAction>>> {
self.update(cx, |project, cx| {
- let code_lens = project.code_lens(buffer, range.clone(), cx);
+ let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
let code_actions = project.code_actions(buffer, range, None, cx);
cx.background_spawn(async move {
- let (code_lens, code_actions) = join(code_lens, code_actions).await;
- Ok(code_lens
+ let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
+ Ok(code_lens_actions
.context("code lens fetch")?
.into_iter()
.chain(code_actions.context("code action fetch")?)
@@ -10072,8 +10072,14 @@ async fn test_autosave_with_dirty_buffers(cx: &mut TestAppContext) {
);
}
-#[gpui::test]
-async fn test_range_format_during_save(cx: &mut TestAppContext) {
+async fn setup_range_format_test(
+ cx: &mut TestAppContext,
+) -> (
+ Entity<Project>,
+ Entity<Editor>,
+ &mut gpui::VisualTestContext,
+ lsp::FakeLanguageServer,
+) {
init_test(cx, |_| {});
let fs = FakeFs::new(cx.executor());
@@ -10088,9 +10094,9 @@ async fn test_range_format_during_save(cx: &mut TestAppContext) {
FakeLspAdapter {
capabilities: lsp::ServerCapabilities {
document_range_formatting_provider: Some(lsp::OneOf::Left(true)),
- ..Default::default()
+ ..lsp::ServerCapabilities::default()
},
- ..Default::default()
+ ..FakeLspAdapter::default()
},
);
@@ -10105,14 +10111,22 @@ async fn test_range_format_during_save(cx: &mut TestAppContext) {
let (editor, cx) = cx.add_window_view(|window, cx| {
build_editor_with_project(project.clone(), buffer, window, cx)
});
+
+ cx.executor().start_waiting();
+ let fake_server = fake_servers.next().await.unwrap();
+
+ (project, editor, cx, fake_server)
+}
+
+#[gpui::test]
+async fn test_range_format_on_save_success(cx: &mut TestAppContext) {
+ let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
+
editor.update_in(cx, |editor, window, cx| {
editor.set_text("one\ntwo\nthree\n", window, cx)
});
assert!(cx.read(|cx| editor.is_dirty(cx)));
- cx.executor().start_waiting();
- let fake_server = fake_servers.next().await.unwrap();
-
let save = editor
.update_in(cx, |editor, window, cx| {
editor.save(
@@ -10147,13 +10161,18 @@ async fn test_range_format_during_save(cx: &mut TestAppContext) {
"one, two\nthree\n"
);
assert!(!cx.read(|cx| editor.is_dirty(cx)));
+}
+
+#[gpui::test]
+async fn test_range_format_on_save_timeout(cx: &mut TestAppContext) {
+ let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
editor.update_in(cx, |editor, window, cx| {
editor.set_text("one\ntwo\nthree\n", window, cx)
});
assert!(cx.read(|cx| editor.is_dirty(cx)));
- // Ensure we can still save even if formatting hangs.
+ // Test that save still works when formatting hangs
fake_server.set_request_handler::<lsp::request::RangeFormatting, _, _>(
move |params, _| async move {
assert_eq!(
@@ -10185,8 +10204,13 @@ async fn test_range_format_during_save(cx: &mut TestAppContext) {
"one\ntwo\nthree\n"
);
assert!(!cx.read(|cx| editor.is_dirty(cx)));
+}
+
+#[gpui::test]
+async fn test_range_format_not_called_for_clean_buffer(cx: &mut TestAppContext) {
+ let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
- // For non-dirty buffer, no formatting request should be sent
+ // Buffer starts clean, no formatting should be requested
let save = editor
.update_in(cx, |editor, window, cx| {
editor.save(
@@ -10207,6 +10231,12 @@ async fn test_range_format_during_save(cx: &mut TestAppContext) {
.next();
cx.executor().start_waiting();
save.await;
+ cx.run_until_parked();
+}
+
+#[gpui::test]
+async fn test_range_format_respects_language_tab_size_override(cx: &mut TestAppContext) {
+ let (project, editor, cx, fake_server) = setup_range_format_test(cx).await;
// Set Rust language override and assert overridden tabsize is sent to language server
update_test_language_settings(cx, |settings| {
@@ -10220,7 +10250,7 @@ async fn test_range_format_during_save(cx: &mut TestAppContext) {
});
editor.update_in(cx, |editor, window, cx| {
- editor.set_text("somehting_new\n", window, cx)
+ editor.set_text("something_new\n", window, cx)
});
assert!(cx.read(|cx| editor.is_dirty(cx)));
let save = editor
@@ -21310,16 +21340,32 @@ async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContex
},
);
- let (buffer, _handle) = project
- .update(cx, |p, cx| {
- p.open_local_buffer_with_lsp(path!("/dir/a.ts"), cx)
+ let editor = workspace
+ .update(cx, |workspace, window, cx| {
+ workspace.open_abs_path(
+ PathBuf::from(path!("/dir/a.ts")),
+ OpenOptions::default(),
+ window,
+ cx,
+ )
})
+ .unwrap()
.await
+ .unwrap()
+ .downcast::<Editor>()
.unwrap();
cx.executor().run_until_parked();
let fake_server = fake_language_servers.next().await.unwrap();
+ let buffer = editor.update(cx, |editor, cx| {
+ editor
+ .buffer()
+ .read(cx)
+ .as_singleton()
+ .expect("have opened a single file by path")
+ });
+
let buffer_snapshot = buffer.update(cx, |buffer, _| buffer.snapshot());
let anchor = buffer_snapshot.anchor_at(0, text::Bias::Left);
drop(buffer_snapshot);
@@ -21377,7 +21423,7 @@ async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContex
assert_eq!(
actions.len(),
1,
- "Should have only one valid action for the 0..0 range"
+ "Should have only one valid action for the 0..0 range, got: {actions:#?}"
);
let action = actions[0].clone();
let apply = project.update(cx, |project, cx| {
@@ -21423,7 +21469,7 @@ async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContex
.into_iter()
.collect(),
),
- ..Default::default()
+ ..lsp::WorkspaceEdit::default()
},
},
)
@@ -21446,6 +21492,38 @@ async fn test_apply_code_lens_actions_with_commands(cx: &mut gpui::TestAppContex
buffer.undo(cx);
assert_eq!(buffer.text(), "a");
});
+
+ let actions_after_edits = cx
+ .update_window(*workspace, |_, window, cx| {
+ project.code_actions(&buffer, anchor..anchor, window, cx)
+ })
+ .unwrap()
+ .await
+ .unwrap();
+ assert_eq!(
+ actions, actions_after_edits,
+ "For the same selection, same code lens actions should be returned"
+ );
+
+ let _responses =
+ fake_server.set_request_handler::<lsp::request::CodeLensRequest, _, _>(|_, _| async move {
+ panic!("No more code lens requests are expected");
+ });
+ editor.update_in(cx, |editor, window, cx| {
+ editor.select_all(&SelectAll, window, cx);
+ });
+ cx.executor().run_until_parked();
+ let new_actions = cx
+ .update_window(*workspace, |_, window, cx| {
+ project.code_actions(&buffer, anchor..anchor, window, cx)
+ })
+ .unwrap()
+ .await
+ .unwrap();
+ assert_eq!(
+ actions, new_actions,
+ "Code lens are queried for the same range and should get the same set back, but without additional LSP queries now"
+ );
}
#[gpui::test]
@@ -6,7 +6,7 @@ use gpui::{Hsla, Rgba};
use itertools::Itertools;
use language::point_from_lsp;
use multi_buffer::Anchor;
-use project::{DocumentColor, lsp_store::ColorFetchStrategy};
+use project::{DocumentColor, lsp_store::LspFetchStrategy};
use settings::Settings as _;
use text::{Bias, BufferId, OffsetRangeExt as _};
use ui::{App, Context, Window};
@@ -180,9 +180,9 @@ impl Editor {
.filter_map(|buffer| {
let buffer_id = buffer.read(cx).remote_id();
let fetch_strategy = if ignore_cache {
- ColorFetchStrategy::IgnoreCache
+ LspFetchStrategy::IgnoreCache
} else {
- ColorFetchStrategy::UseCache {
+ LspFetchStrategy::UseCache {
known_cache_version: self.colors.as_ref().and_then(|colors| {
Some(colors.buffer_colors.get(&buffer_id)?.cache_version_used)
}),
@@ -71,6 +71,7 @@ pub enum IconName {
CircleHelp,
Close,
Cloud,
+ CloudDownload,
Code,
Cog,
Command,
@@ -313,6 +313,15 @@ impl Attach {
}
}
+/// Determines what gets sent out as a workspace folders content
+#[derive(Clone, Copy, Debug, PartialEq)]
+pub enum WorkspaceFoldersContent {
+ /// Send out a single entry with the root of the workspace.
+ WorktreeRoot,
+ /// Send out a list of subproject roots.
+ SubprojectRoots,
+}
+
/// [`LspAdapterDelegate`] allows [`LspAdapter]` implementations to interface with the application
// e.g. to display a notification or fetch data from the web.
#[async_trait]
@@ -606,6 +615,13 @@ pub trait LspAdapter: 'static + Send + Sync {
Attach::Shared
}
+ /// Determines whether a language server supports workspace folders.
+ ///
+ /// And does not trip over itself in the process.
+ fn workspace_folders_content(&self) -> WorkspaceFoldersContent {
+ WorkspaceFoldersContent::SubprojectRoots
+ }
+
fn manifest_name(&self) -> Option<ManifestName> {
None
}
@@ -867,7 +867,7 @@ impl LspLogView {
BINARY = server.binary(),
WORKSPACE_FOLDERS = server
.workspace_folders()
- .iter()
+ .into_iter()
.filter_map(|path| path
.to_file_path()
.ok()
@@ -41,6 +41,7 @@ async-trait.workspace = true
chrono.workspace = true
collections.workspace = true
dap.workspace = true
+feature_flags.workspace = true
futures.workspace = true
gpui.workspace = true
http_client.workspace = true
@@ -1,4 +1,5 @@
use anyhow::Context as _;
+use feature_flags::{FeatureFlag, FeatureFlagAppExt as _};
use gpui::{App, UpdateGlobal};
use node_runtime::NodeRuntime;
use python::PyprojectTomlManifestProvider;
@@ -11,7 +12,7 @@ use util::{ResultExt, asset_str};
pub use language::*;
-use crate::json::JsonTaskProvider;
+use crate::{json::JsonTaskProvider, python::BasedPyrightLspAdapter};
mod bash;
mod c;
@@ -52,6 +53,12 @@ pub static LANGUAGE_GIT_COMMIT: std::sync::LazyLock<Arc<Language>> =
))
});
+struct BasedPyrightFeatureFlag;
+
+impl FeatureFlag for BasedPyrightFeatureFlag {
+ const NAME: &'static str = "basedpyright";
+}
+
pub fn init(languages: Arc<LanguageRegistry>, node: NodeRuntime, cx: &mut App) {
#[cfg(feature = "load-grammars")]
languages.register_native_grammars([
@@ -88,6 +95,7 @@ pub fn init(languages: Arc<LanguageRegistry>, node: NodeRuntime, cx: &mut App) {
let py_lsp_adapter = Arc::new(python::PyLspAdapter::new());
let python_context_provider = Arc::new(python::PythonContextProvider);
let python_lsp_adapter = Arc::new(python::PythonLspAdapter::new(node.clone()));
+ let basedpyright_lsp_adapter = Arc::new(BasedPyrightLspAdapter::new());
let python_toolchain_provider = Arc::new(python::PythonToolchainProvider::default());
let rust_context_provider = Arc::new(rust::RustContextProvider);
let rust_lsp_adapter = Arc::new(rust::RustLspAdapter);
@@ -228,6 +236,20 @@ pub fn init(languages: Arc<LanguageRegistry>, node: NodeRuntime, cx: &mut App) {
);
}
+ let mut basedpyright_lsp_adapter = Some(basedpyright_lsp_adapter);
+ cx.observe_flag::<BasedPyrightFeatureFlag, _>({
+ let languages = languages.clone();
+ move |enabled, _| {
+ if enabled {
+ if let Some(adapter) = basedpyright_lsp_adapter.take() {
+ languages
+ .register_available_lsp_adapter(adapter.name(), move || adapter.clone());
+ }
+ }
+ }
+ })
+ .detach();
+
// Register globally available language servers.
//
// This will allow users to add support for a built-in language server (e.g., Tailwind)
@@ -4,13 +4,13 @@ use async_trait::async_trait;
use collections::HashMap;
use gpui::{App, Task};
use gpui::{AsyncApp, SharedString};
-use language::Toolchain;
use language::ToolchainList;
use language::ToolchainLister;
use language::language_settings::language_settings;
use language::{ContextLocation, LanguageToolchainStore};
use language::{ContextProvider, LspAdapter, LspAdapterDelegate};
use language::{LanguageName, ManifestName, ManifestProvider, ManifestQuery};
+use language::{Toolchain, WorkspaceFoldersContent};
use lsp::LanguageServerBinary;
use lsp::LanguageServerName;
use node_runtime::NodeRuntime;
@@ -400,6 +400,9 @@ impl LspAdapter for PythonLspAdapter {
fn manifest_name(&self) -> Option<ManifestName> {
Some(SharedString::new_static("pyproject.toml").into())
}
+ fn workspace_folders_content(&self) -> WorkspaceFoldersContent {
+ WorkspaceFoldersContent::WorktreeRoot
+ }
}
async fn get_cached_server_binary(
@@ -1282,6 +1285,346 @@ impl LspAdapter for PyLspAdapter {
fn manifest_name(&self) -> Option<ManifestName> {
Some(SharedString::new_static("pyproject.toml").into())
}
+ fn workspace_folders_content(&self) -> WorkspaceFoldersContent {
+ WorkspaceFoldersContent::WorktreeRoot
+ }
+}
+
+pub(crate) struct BasedPyrightLspAdapter {
+ python_venv_base: OnceCell<Result<Arc<Path>, String>>,
+}
+
+impl BasedPyrightLspAdapter {
+ const SERVER_NAME: LanguageServerName = LanguageServerName::new_static("basedpyright");
+ const BINARY_NAME: &'static str = "basedpyright-langserver";
+
+ pub(crate) fn new() -> Self {
+ Self {
+ python_venv_base: OnceCell::new(),
+ }
+ }
+
+ async fn ensure_venv(delegate: &dyn LspAdapterDelegate) -> Result<Arc<Path>> {
+ let python_path = Self::find_base_python(delegate)
+ .await
+ .context("Could not find Python installation for basedpyright")?;
+ let work_dir = delegate
+ .language_server_download_dir(&Self::SERVER_NAME)
+ .await
+ .context("Could not get working directory for basedpyright")?;
+ let mut path = PathBuf::from(work_dir.as_ref());
+ path.push("basedpyright-venv");
+ if !path.exists() {
+ util::command::new_smol_command(python_path)
+ .arg("-m")
+ .arg("venv")
+ .arg("basedpyright-venv")
+ .current_dir(work_dir)
+ .spawn()?
+ .output()
+ .await?;
+ }
+
+ Ok(path.into())
+ }
+
+ // Find "baseline", user python version from which we'll create our own venv.
+ async fn find_base_python(delegate: &dyn LspAdapterDelegate) -> Option<PathBuf> {
+ for path in ["python3", "python"] {
+ if let Some(path) = delegate.which(path.as_ref()).await {
+ return Some(path);
+ }
+ }
+ None
+ }
+
+ async fn base_venv(&self, delegate: &dyn LspAdapterDelegate) -> Result<Arc<Path>, String> {
+ self.python_venv_base
+ .get_or_init(move || async move {
+ Self::ensure_venv(delegate)
+ .await
+ .map_err(|e| format!("{e}"))
+ })
+ .await
+ .clone()
+ }
+}
+
+#[async_trait(?Send)]
+impl LspAdapter for BasedPyrightLspAdapter {
+ fn name(&self) -> LanguageServerName {
+ Self::SERVER_NAME.clone()
+ }
+
+ async fn initialization_options(
+ self: Arc<Self>,
+ _: &dyn Fs,
+ _: &Arc<dyn LspAdapterDelegate>,
+ ) -> Result<Option<Value>> {
+ // Provide minimal initialization options
+ // Virtual environment configuration will be handled through workspace configuration
+ Ok(Some(json!({
+ "python": {
+ "analysis": {
+ "autoSearchPaths": true,
+ "useLibraryCodeForTypes": true,
+ "autoImportCompletions": true
+ }
+ }
+ })))
+ }
+
+ async fn check_if_user_installed(
+ &self,
+ delegate: &dyn LspAdapterDelegate,
+ toolchains: Arc<dyn LanguageToolchainStore>,
+ cx: &AsyncApp,
+ ) -> Option<LanguageServerBinary> {
+ if let Some(bin) = delegate.which(Self::BINARY_NAME.as_ref()).await {
+ let env = delegate.shell_env().await;
+ Some(LanguageServerBinary {
+ path: bin,
+ env: Some(env),
+ arguments: vec!["--stdio".into()],
+ })
+ } else {
+ let venv = toolchains
+ .active_toolchain(
+ delegate.worktree_id(),
+ Arc::from("".as_ref()),
+ LanguageName::new("Python"),
+ &mut cx.clone(),
+ )
+ .await?;
+ let path = Path::new(venv.path.as_ref())
+ .parent()?
+ .join(Self::BINARY_NAME);
+ path.exists().then(|| LanguageServerBinary {
+ path,
+ arguments: vec!["--stdio".into()],
+ env: None,
+ })
+ }
+ }
+
+ async fn fetch_latest_server_version(
+ &self,
+ _: &dyn LspAdapterDelegate,
+ ) -> Result<Box<dyn 'static + Any + Send>> {
+ Ok(Box::new(()) as Box<_>)
+ }
+
+ async fn fetch_server_binary(
+ &self,
+ _latest_version: Box<dyn 'static + Send + Any>,
+ _container_dir: PathBuf,
+ delegate: &dyn LspAdapterDelegate,
+ ) -> Result<LanguageServerBinary> {
+ let venv = self.base_venv(delegate).await.map_err(|e| anyhow!(e))?;
+ let pip_path = venv.join(BINARY_DIR).join("pip3");
+ ensure!(
+ util::command::new_smol_command(pip_path.as_path())
+ .arg("install")
+ .arg("basedpyright")
+ .arg("-U")
+ .output()
+ .await?
+ .status
+ .success(),
+ "basedpyright installation failed"
+ );
+ let pylsp = venv.join(BINARY_DIR).join(Self::BINARY_NAME);
+ Ok(LanguageServerBinary {
+ path: pylsp,
+ env: None,
+ arguments: vec!["--stdio".into()],
+ })
+ }
+
+ async fn cached_server_binary(
+ &self,
+ _container_dir: PathBuf,
+ delegate: &dyn LspAdapterDelegate,
+ ) -> Option<LanguageServerBinary> {
+ let venv = self.base_venv(delegate).await.ok()?;
+ let pylsp = venv.join(BINARY_DIR).join(Self::BINARY_NAME);
+ Some(LanguageServerBinary {
+ path: pylsp,
+ env: None,
+ arguments: vec!["--stdio".into()],
+ })
+ }
+
+ async fn process_completions(&self, items: &mut [lsp::CompletionItem]) {
+ // Pyright assigns each completion item a `sortText` of the form `XX.YYYY.name`.
+ // Where `XX` is the sorting category, `YYYY` is based on most recent usage,
+ // and `name` is the symbol name itself.
+ //
+ // Because the symbol name is included, there generally are not ties when
+ // sorting by the `sortText`, so the symbol's fuzzy match score is not taken
+ // into account. Here, we remove the symbol name from the sortText in order
+ // to allow our own fuzzy score to be used to break ties.
+ //
+ // see https://github.com/microsoft/pyright/blob/95ef4e103b9b2f129c9320427e51b73ea7cf78bd/packages/pyright-internal/src/languageService/completionProvider.ts#LL2873
+ for item in items {
+ let Some(sort_text) = &mut item.sort_text else {
+ continue;
+ };
+ let mut parts = sort_text.split('.');
+ let Some(first) = parts.next() else { continue };
+ let Some(second) = parts.next() else { continue };
+ let Some(_) = parts.next() else { continue };
+ sort_text.replace_range(first.len() + second.len() + 1.., "");
+ }
+ }
+
+ async fn label_for_completion(
+ &self,
+ item: &lsp::CompletionItem,
+ language: &Arc<language::Language>,
+ ) -> Option<language::CodeLabel> {
+ let label = &item.label;
+ let grammar = language.grammar()?;
+ let highlight_id = match item.kind? {
+ lsp::CompletionItemKind::METHOD => grammar.highlight_id_for_name("function.method")?,
+ lsp::CompletionItemKind::FUNCTION => grammar.highlight_id_for_name("function")?,
+ lsp::CompletionItemKind::CLASS => grammar.highlight_id_for_name("type")?,
+ lsp::CompletionItemKind::CONSTANT => grammar.highlight_id_for_name("constant")?,
+ _ => return None,
+ };
+ let filter_range = item
+ .filter_text
+ .as_deref()
+ .and_then(|filter| label.find(filter).map(|ix| ix..ix + filter.len()))
+ .unwrap_or(0..label.len());
+ Some(language::CodeLabel {
+ text: label.clone(),
+ runs: vec![(0..label.len(), highlight_id)],
+ filter_range,
+ })
+ }
+
+ async fn label_for_symbol(
+ &self,
+ name: &str,
+ kind: lsp::SymbolKind,
+ language: &Arc<language::Language>,
+ ) -> Option<language::CodeLabel> {
+ let (text, filter_range, display_range) = match kind {
+ lsp::SymbolKind::METHOD | lsp::SymbolKind::FUNCTION => {
+ let text = format!("def {}():\n", name);
+ let filter_range = 4..4 + name.len();
+ let display_range = 0..filter_range.end;
+ (text, filter_range, display_range)
+ }
+ lsp::SymbolKind::CLASS => {
+ let text = format!("class {}:", name);
+ let filter_range = 6..6 + name.len();
+ let display_range = 0..filter_range.end;
+ (text, filter_range, display_range)
+ }
+ lsp::SymbolKind::CONSTANT => {
+ let text = format!("{} = 0", name);
+ let filter_range = 0..name.len();
+ let display_range = 0..filter_range.end;
+ (text, filter_range, display_range)
+ }
+ _ => return None,
+ };
+
+ Some(language::CodeLabel {
+ runs: language.highlight_text(&text.as_str().into(), display_range.clone()),
+ text: text[display_range].to_string(),
+ filter_range,
+ })
+ }
+
+ async fn workspace_configuration(
+ self: Arc<Self>,
+ _: &dyn Fs,
+ adapter: &Arc<dyn LspAdapterDelegate>,
+ toolchains: Arc<dyn LanguageToolchainStore>,
+ cx: &mut AsyncApp,
+ ) -> Result<Value> {
+ let toolchain = toolchains
+ .active_toolchain(
+ adapter.worktree_id(),
+ Arc::from("".as_ref()),
+ LanguageName::new("Python"),
+ cx,
+ )
+ .await;
+ cx.update(move |cx| {
+ let mut user_settings =
+ language_server_settings(adapter.as_ref(), &Self::SERVER_NAME, cx)
+ .and_then(|s| s.settings.clone())
+ .unwrap_or_default();
+
+ // If we have a detected toolchain, configure Pyright to use it
+ if let Some(toolchain) = toolchain {
+ if user_settings.is_null() {
+ user_settings = Value::Object(serde_json::Map::default());
+ }
+ let object = user_settings.as_object_mut().unwrap();
+
+ let interpreter_path = toolchain.path.to_string();
+
+ // Detect if this is a virtual environment
+ if let Some(interpreter_dir) = Path::new(&interpreter_path).parent() {
+ if let Some(venv_dir) = interpreter_dir.parent() {
+ // Check if this looks like a virtual environment
+ if venv_dir.join("pyvenv.cfg").exists()
+ || venv_dir.join("bin/activate").exists()
+ || venv_dir.join("Scripts/activate.bat").exists()
+ {
+ // Set venvPath and venv at the root level
+ // This matches the format of a pyrightconfig.json file
+ if let Some(parent) = venv_dir.parent() {
+ // Use relative path if the venv is inside the workspace
+ let venv_path = if parent == adapter.worktree_root_path() {
+ ".".to_string()
+ } else {
+ parent.to_string_lossy().into_owned()
+ };
+ object.insert("venvPath".to_string(), Value::String(venv_path));
+ }
+
+ if let Some(venv_name) = venv_dir.file_name() {
+ object.insert(
+ "venv".to_owned(),
+ Value::String(venv_name.to_string_lossy().into_owned()),
+ );
+ }
+ }
+ }
+ }
+
+ // Always set the python interpreter path
+ // Get or create the python section
+ let python = object
+ .entry("python")
+ .or_insert(Value::Object(serde_json::Map::default()))
+ .as_object_mut()
+ .unwrap();
+
+ // Set both pythonPath and defaultInterpreterPath for compatibility
+ python.insert(
+ "pythonPath".to_owned(),
+ Value::String(interpreter_path.clone()),
+ );
+ python.insert(
+ "defaultInterpreterPath".to_owned(),
+ Value::String(interpreter_path),
+ );
+ }
+
+ user_settings
+ })
+ }
+
+ fn manifest_name(&self) -> Option<ManifestName> {
+ Some(SharedString::new_static("pyproject.toml").into())
+ }
}
#[cfg(test)]
@@ -1,4 +1,4 @@
-; Add support for (node:test, bun:test and Jest) runnable
+; Add support for (node:test, bun:test, Jest and Deno.test) runnable
; Function expression that has `it`, `test` or `describe` as the function name
(
(call_expression
@@ -44,3 +44,42 @@
(#set! tag js-test)
)
+
+; Add support for Deno.test with string names
+(
+ (call_expression
+ function: (member_expression
+ object: (identifier) @_namespace
+ property: (property_identifier) @_method
+ )
+ (#eq? @_namespace "Deno")
+ (#eq? @_method "test")
+ arguments: (
+ arguments . [
+ (string (string_fragment) @run @DENO_TEST_NAME)
+ (identifier) @run @DENO_TEST_NAME
+ ]
+ )
+ ) @_js-test
+
+ (#set! tag js-test)
+)
+
+; Add support for Deno.test with named function expressions
+(
+ (call_expression
+ function: (member_expression
+ object: (identifier) @_namespace
+ property: (property_identifier) @_method
+ )
+ (#eq? @_namespace "Deno")
+ (#eq? @_method "test")
+ arguments: (
+ arguments . (function_expression
+ name: (identifier) @run @DENO_TEST_NAME
+ )
+ )
+ ) @_js-test
+
+ (#set! tag js-test)
+)
@@ -29,7 +29,7 @@ use std::{
ffi::{OsStr, OsString},
fmt,
io::Write,
- ops::{Deref, DerefMut},
+ ops::DerefMut,
path::PathBuf,
pin::Pin,
sync::{
@@ -100,7 +100,7 @@ pub struct LanguageServer {
io_tasks: Mutex<Option<(Task<Option<()>>, Task<Option<()>>)>>,
output_done_rx: Mutex<Option<barrier::Receiver>>,
server: Arc<Mutex<Option<Child>>>,
- workspace_folders: Arc<Mutex<BTreeSet<Url>>>,
+ workspace_folders: Option<Arc<Mutex<BTreeSet<Url>>>>,
root_uri: Url,
}
@@ -307,7 +307,7 @@ impl LanguageServer {
binary: LanguageServerBinary,
root_path: &Path,
code_action_kinds: Option<Vec<CodeActionKind>>,
- workspace_folders: Arc<Mutex<BTreeSet<Url>>>,
+ workspace_folders: Option<Arc<Mutex<BTreeSet<Url>>>>,
cx: &mut AsyncApp,
) -> Result<Self> {
let working_dir = if root_path.is_dir() {
@@ -381,7 +381,7 @@ impl LanguageServer {
code_action_kinds: Option<Vec<CodeActionKind>>,
binary: LanguageServerBinary,
root_uri: Url,
- workspace_folders: Arc<Mutex<BTreeSet<Url>>>,
+ workspace_folders: Option<Arc<Mutex<BTreeSet<Url>>>>,
cx: &mut AsyncApp,
on_unhandled_notification: F,
) -> Self
@@ -595,16 +595,26 @@ impl LanguageServer {
}
pub fn default_initialize_params(&self, pull_diagnostics: bool, cx: &App) -> InitializeParams {
- let workspace_folders = self
- .workspace_folders
- .lock()
- .iter()
- .cloned()
- .map(|uri| WorkspaceFolder {
- name: Default::default(),
- uri,
- })
- .collect::<Vec<_>>();
+ let workspace_folders = self.workspace_folders.as_ref().map_or_else(
+ || {
+ vec![WorkspaceFolder {
+ name: Default::default(),
+ uri: self.root_uri.clone(),
+ }]
+ },
+ |folders| {
+ folders
+ .lock()
+ .iter()
+ .cloned()
+ .map(|uri| WorkspaceFolder {
+ name: Default::default(),
+ uri,
+ })
+ .collect()
+ },
+ );
+
#[allow(deprecated)]
InitializeParams {
process_id: None,
@@ -1315,7 +1325,10 @@ impl LanguageServer {
return;
}
- let is_new_folder = self.workspace_folders.lock().insert(uri.clone());
+ let Some(workspace_folders) = self.workspace_folders.as_ref() else {
+ return;
+ };
+ let is_new_folder = workspace_folders.lock().insert(uri.clone());
if is_new_folder {
let params = DidChangeWorkspaceFoldersParams {
event: WorkspaceFoldersChangeEvent {
@@ -1345,7 +1358,10 @@ impl LanguageServer {
{
return;
}
- let was_removed = self.workspace_folders.lock().remove(&uri);
+ let Some(workspace_folders) = self.workspace_folders.as_ref() else {
+ return;
+ };
+ let was_removed = workspace_folders.lock().remove(&uri);
if was_removed {
let params = DidChangeWorkspaceFoldersParams {
event: WorkspaceFoldersChangeEvent {
@@ -1360,7 +1376,10 @@ impl LanguageServer {
}
}
pub fn set_workspace_folders(&self, folders: BTreeSet<Url>) {
- let mut workspace_folders = self.workspace_folders.lock();
+ let Some(workspace_folders) = self.workspace_folders.as_ref() else {
+ return;
+ };
+ let mut workspace_folders = workspace_folders.lock();
let old_workspace_folders = std::mem::take(&mut *workspace_folders);
let added: Vec<_> = folders
@@ -1389,8 +1408,11 @@ impl LanguageServer {
}
}
- pub fn workspace_folders(&self) -> impl Deref<Target = BTreeSet<Url>> + '_ {
- self.workspace_folders.lock()
+ pub fn workspace_folders(&self) -> BTreeSet<Url> {
+ self.workspace_folders.as_ref().map_or_else(
+ || BTreeSet::from_iter([self.root_uri.clone()]),
+ |folders| folders.lock().clone(),
+ )
}
pub fn register_buffer(
@@ -1535,7 +1557,7 @@ impl FakeLanguageServer {
None,
binary.clone(),
root,
- workspace_folders.clone(),
+ Some(workspace_folders.clone()),
cx,
|_| {},
);
@@ -1554,7 +1576,7 @@ impl FakeLanguageServer {
None,
binary,
Self::root_path(),
- workspace_folders,
+ Some(workspace_folders),
cx,
move |msg| {
notifications_tx
@@ -32,8 +32,7 @@ const CONTENT: (Section<4>, Section<3>) = (
action: &Open,
},
SectionEntry {
- // TODO: use proper icon
- icon: IconName::Download,
+ icon: IconName::CloudDownload,
title: "Clone a Repo",
// TODO: use proper action
action: &NoAction,
@@ -46,6 +46,7 @@ use language::{
DiagnosticEntry, DiagnosticSet, DiagnosticSourceKind, Diff, File as _, Language, LanguageName,
LanguageRegistry, LanguageToolchainStore, LocalFile, LspAdapter, LspAdapterDelegate, Patch,
PointUtf16, TextBufferSnapshot, ToOffset, ToPointUtf16, Transaction, Unclipped,
+ WorkspaceFoldersContent,
language_settings::{
FormatOnSave, Formatter, LanguageSettings, SelectedFormatter, language_settings,
},
@@ -217,6 +218,7 @@ impl LocalLspStore {
let binary = self.get_language_server_binary(adapter.clone(), delegate.clone(), true, cx);
let pending_workspace_folders: Arc<Mutex<BTreeSet<Url>>> = Default::default();
+
let pending_server = cx.spawn({
let adapter = adapter.clone();
let server_name = adapter.name.clone();
@@ -242,14 +244,18 @@ impl LocalLspStore {
return Ok(server);
}
+ let code_action_kinds = adapter.code_action_kinds();
lsp::LanguageServer::new(
stderr_capture,
server_id,
server_name,
binary,
&root_path,
- adapter.code_action_kinds(),
- pending_workspace_folders,
+ code_action_kinds,
+ Some(pending_workspace_folders).filter(|_| {
+ adapter.adapter.workspace_folders_content()
+ == WorkspaceFoldersContent::SubprojectRoots
+ }),
cx,
)
}
@@ -575,8 +581,7 @@ impl LocalLspStore {
};
let root = server.workspace_folders();
Ok(Some(
- root.iter()
- .cloned()
+ root.into_iter()
.map(|uri| WorkspaceFolder {
uri,
name: Default::default(),
@@ -3551,7 +3556,8 @@ pub struct LspStore {
_maintain_buffer_languages: Task<()>,
diagnostic_summaries:
HashMap<WorktreeId, HashMap<Arc<Path>, HashMap<LanguageServerId, DiagnosticSummary>>>,
- lsp_data: HashMap<BufferId, DocumentColorData>,
+ lsp_document_colors: HashMap<BufferId, DocumentColorData>,
+ lsp_code_lens: HashMap<BufferId, CodeLensData>,
}
#[derive(Debug, Default, Clone)]
@@ -3561,6 +3567,7 @@ pub struct DocumentColors {
}
type DocumentColorTask = Shared<Task<std::result::Result<DocumentColors, Arc<anyhow::Error>>>>;
+type CodeLensTask = Shared<Task<std::result::Result<Vec<CodeAction>, Arc<anyhow::Error>>>>;
#[derive(Debug, Default)]
struct DocumentColorData {
@@ -3570,8 +3577,15 @@ struct DocumentColorData {
colors_update: Option<(Global, DocumentColorTask)>,
}
+#[derive(Debug, Default)]
+struct CodeLensData {
+ lens_for_version: Global,
+ lens: HashMap<LanguageServerId, Vec<CodeAction>>,
+ update: Option<(Global, CodeLensTask)>,
+}
+
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
-pub enum ColorFetchStrategy {
+pub enum LspFetchStrategy {
IgnoreCache,
UseCache { known_cache_version: Option<usize> },
}
@@ -3804,7 +3818,8 @@ impl LspStore {
language_server_statuses: Default::default(),
nonce: StdRng::from_entropy().r#gen(),
diagnostic_summaries: HashMap::default(),
- lsp_data: HashMap::default(),
+ lsp_document_colors: HashMap::default(),
+ lsp_code_lens: HashMap::default(),
active_entry: None,
_maintain_workspace_config,
_maintain_buffer_languages: Self::maintain_buffer_languages(languages, cx),
@@ -3861,7 +3876,8 @@ impl LspStore {
language_server_statuses: Default::default(),
nonce: StdRng::from_entropy().r#gen(),
diagnostic_summaries: HashMap::default(),
- lsp_data: HashMap::default(),
+ lsp_document_colors: HashMap::default(),
+ lsp_code_lens: HashMap::default(),
active_entry: None,
toolchain_store,
_maintain_workspace_config,
@@ -4162,7 +4178,8 @@ impl LspStore {
*refcount
};
if refcount == 0 {
- lsp_store.lsp_data.remove(&buffer_id);
+ lsp_store.lsp_document_colors.remove(&buffer_id);
+ lsp_store.lsp_code_lens.remove(&buffer_id);
let local = lsp_store.as_local_mut().unwrap();
local.registered_buffers.remove(&buffer_id);
local.buffers_opened_in_servers.remove(&buffer_id);
@@ -5702,69 +5719,168 @@ impl LspStore {
}
}
- pub fn code_lens(
+ pub fn code_lens_actions(
&mut self,
- buffer_handle: &Entity<Buffer>,
+ buffer: &Entity<Buffer>,
cx: &mut Context<Self>,
- ) -> Task<Result<Vec<CodeAction>>> {
+ ) -> CodeLensTask {
+ let version_queried_for = buffer.read(cx).version();
+ let buffer_id = buffer.read(cx).remote_id();
+
+ if let Some(cached_data) = self.lsp_code_lens.get(&buffer_id) {
+ if !version_queried_for.changed_since(&cached_data.lens_for_version) {
+ let has_different_servers = self.as_local().is_some_and(|local| {
+ local
+ .buffers_opened_in_servers
+ .get(&buffer_id)
+ .cloned()
+ .unwrap_or_default()
+ != cached_data.lens.keys().copied().collect()
+ });
+ if !has_different_servers {
+ return Task::ready(Ok(cached_data.lens.values().flatten().cloned().collect()))
+ .shared();
+ }
+ }
+ }
+
+ let lsp_data = self.lsp_code_lens.entry(buffer_id).or_default();
+ if let Some((updating_for, running_update)) = &lsp_data.update {
+ if !version_queried_for.changed_since(&updating_for) {
+ return running_update.clone();
+ }
+ }
+ let buffer = buffer.clone();
+ let query_version_queried_for = version_queried_for.clone();
+ let new_task = cx
+ .spawn(async move |lsp_store, cx| {
+ cx.background_executor()
+ .timer(Duration::from_millis(30))
+ .await;
+ let fetched_lens = lsp_store
+ .update(cx, |lsp_store, cx| lsp_store.fetch_code_lens(&buffer, cx))
+ .map_err(Arc::new)?
+ .await
+ .context("fetching code lens")
+ .map_err(Arc::new);
+ let fetched_lens = match fetched_lens {
+ Ok(fetched_lens) => fetched_lens,
+ Err(e) => {
+ lsp_store
+ .update(cx, |lsp_store, _| {
+ lsp_store.lsp_code_lens.entry(buffer_id).or_default().update = None;
+ })
+ .ok();
+ return Err(e);
+ }
+ };
+
+ lsp_store
+ .update(cx, |lsp_store, _| {
+ let lsp_data = lsp_store.lsp_code_lens.entry(buffer_id).or_default();
+ if lsp_data.lens_for_version == query_version_queried_for {
+ lsp_data.lens.extend(fetched_lens.clone());
+ } else if !lsp_data
+ .lens_for_version
+ .changed_since(&query_version_queried_for)
+ {
+ lsp_data.lens_for_version = query_version_queried_for;
+ lsp_data.lens = fetched_lens.clone();
+ }
+ lsp_data.update = None;
+ lsp_data.lens.values().flatten().cloned().collect()
+ })
+ .map_err(Arc::new)
+ })
+ .shared();
+ lsp_data.update = Some((version_queried_for, new_task.clone()));
+ new_task
+ }
+
+ fn fetch_code_lens(
+ &mut self,
+ buffer: &Entity<Buffer>,
+ cx: &mut Context<Self>,
+ ) -> Task<Result<HashMap<LanguageServerId, Vec<CodeAction>>>> {
if let Some((upstream_client, project_id)) = self.upstream_client() {
let request_task = upstream_client.request(proto::MultiLspQuery {
- buffer_id: buffer_handle.read(cx).remote_id().into(),
- version: serialize_version(&buffer_handle.read(cx).version()),
+ buffer_id: buffer.read(cx).remote_id().into(),
+ version: serialize_version(&buffer.read(cx).version()),
project_id,
strategy: Some(proto::multi_lsp_query::Strategy::All(
proto::AllLanguageServers {},
)),
request: Some(proto::multi_lsp_query::Request::GetCodeLens(
- GetCodeLens.to_proto(project_id, buffer_handle.read(cx)),
+ GetCodeLens.to_proto(project_id, buffer.read(cx)),
)),
});
- let buffer = buffer_handle.clone();
- cx.spawn(async move |weak_project, cx| {
- let Some(project) = weak_project.upgrade() else {
- return Ok(Vec::new());
+ let buffer = buffer.clone();
+ cx.spawn(async move |weak_lsp_store, cx| {
+ let Some(lsp_store) = weak_lsp_store.upgrade() else {
+ return Ok(HashMap::default());
};
let responses = request_task.await?.responses;
- let code_lens = join_all(
+ let code_lens_actions = join_all(
responses
.into_iter()
- .filter_map(|lsp_response| match lsp_response.response? {
- proto::lsp_response::Response::GetCodeLensResponse(response) => {
- Some(response)
- }
- unexpected => {
- debug_panic!("Unexpected response: {unexpected:?}");
- None
- }
+ .filter_map(|lsp_response| {
+ let response = match lsp_response.response? {
+ proto::lsp_response::Response::GetCodeLensResponse(response) => {
+ Some(response)
+ }
+ unexpected => {
+ debug_panic!("Unexpected response: {unexpected:?}");
+ None
+ }
+ }?;
+ let server_id = LanguageServerId::from_proto(lsp_response.server_id);
+ Some((server_id, response))
})
- .map(|code_lens_response| {
- GetCodeLens.response_from_proto(
- code_lens_response,
- project.clone(),
- buffer.clone(),
- cx.clone(),
- )
+ .map(|(server_id, code_lens_response)| {
+ let lsp_store = lsp_store.clone();
+ let buffer = buffer.clone();
+ let cx = cx.clone();
+ async move {
+ (
+ server_id,
+ GetCodeLens
+ .response_from_proto(
+ code_lens_response,
+ lsp_store,
+ buffer,
+ cx,
+ )
+ .await,
+ )
+ }
}),
)
.await;
- Ok(code_lens
+ let mut has_errors = false;
+ let code_lens_actions = code_lens_actions
.into_iter()
- .collect::<Result<Vec<Vec<_>>>>()?
- .into_iter()
- .flatten()
- .collect())
+ .filter_map(|(server_id, code_lens)| match code_lens {
+ Ok(code_lens) => Some((server_id, code_lens)),
+ Err(e) => {
+ has_errors = true;
+ log::error!("{e:#}");
+ None
+ }
+ })
+ .collect::<HashMap<_, _>>();
+ anyhow::ensure!(
+ !has_errors || !code_lens_actions.is_empty(),
+ "Failed to fetch code lens"
+ );
+ Ok(code_lens_actions)
})
} else {
- let code_lens_task =
- self.request_multiple_lsp_locally(buffer_handle, None::<usize>, GetCodeLens, cx);
- cx.spawn(async move |_, _| {
- Ok(code_lens_task
- .await
- .into_iter()
- .flat_map(|(_, code_lens)| code_lens)
- .collect())
- })
+ let code_lens_actions_task =
+ self.request_multiple_lsp_locally(buffer, None::<usize>, GetCodeLens, cx);
+ cx.background_spawn(
+ async move { Ok(code_lens_actions_task.await.into_iter().collect()) },
+ )
}
}
@@ -6597,7 +6713,7 @@ impl LspStore {
pub fn document_colors(
&mut self,
- fetch_strategy: ColorFetchStrategy,
+ fetch_strategy: LspFetchStrategy,
buffer: Entity<Buffer>,
cx: &mut Context<Self>,
) -> Option<DocumentColorTask> {
@@ -6605,11 +6721,11 @@ impl LspStore {
let buffer_id = buffer.read(cx).remote_id();
match fetch_strategy {
- ColorFetchStrategy::IgnoreCache => {}
- ColorFetchStrategy::UseCache {
+ LspFetchStrategy::IgnoreCache => {}
+ LspFetchStrategy::UseCache {
known_cache_version,
} => {
- if let Some(cached_data) = self.lsp_data.get(&buffer_id) {
+ if let Some(cached_data) = self.lsp_document_colors.get(&buffer_id) {
if !version_queried_for.changed_since(&cached_data.colors_for_version) {
let has_different_servers = self.as_local().is_some_and(|local| {
local
@@ -6642,7 +6758,7 @@ impl LspStore {
}
}
- let lsp_data = self.lsp_data.entry(buffer_id).or_default();
+ let lsp_data = self.lsp_document_colors.entry(buffer_id).or_default();
if let Some((updating_for, running_update)) = &lsp_data.colors_update {
if !version_queried_for.changed_since(&updating_for) {
return Some(running_update.clone());
@@ -6656,14 +6772,14 @@ impl LspStore {
.await;
let fetched_colors = lsp_store
.update(cx, |lsp_store, cx| {
- lsp_store.fetch_document_colors_for_buffer(buffer.clone(), cx)
+ lsp_store.fetch_document_colors_for_buffer(&buffer, cx)
})?
.await
.context("fetching document colors")
.map_err(Arc::new);
let fetched_colors = match fetched_colors {
Ok(fetched_colors) => {
- if fetch_strategy != ColorFetchStrategy::IgnoreCache
+ if fetch_strategy != LspFetchStrategy::IgnoreCache
&& Some(true)
== buffer
.update(cx, |buffer, _| {
@@ -6679,7 +6795,7 @@ impl LspStore {
lsp_store
.update(cx, |lsp_store, _| {
lsp_store
- .lsp_data
+ .lsp_document_colors
.entry(buffer_id)
.or_default()
.colors_update = None;
@@ -6691,7 +6807,7 @@ impl LspStore {
lsp_store
.update(cx, |lsp_store, _| {
- let lsp_data = lsp_store.lsp_data.entry(buffer_id).or_default();
+ let lsp_data = lsp_store.lsp_document_colors.entry(buffer_id).or_default();
if lsp_data.colors_for_version == query_version_queried_for {
lsp_data.colors.extend(fetched_colors.clone());
@@ -6725,7 +6841,7 @@ impl LspStore {
fn fetch_document_colors_for_buffer(
&mut self,
- buffer: Entity<Buffer>,
+ buffer: &Entity<Buffer>,
cx: &mut Context<Self>,
) -> Task<anyhow::Result<HashMap<LanguageServerId, HashSet<DocumentColor>>>> {
if let Some((client, project_id)) = self.upstream_client() {
@@ -6740,6 +6856,7 @@ impl LspStore {
GetDocumentColor {}.to_proto(project_id, buffer.read(cx)),
)),
});
+ let buffer = buffer.clone();
cx.spawn(async move |project, cx| {
let Some(project) = project.upgrade() else {
return Ok(HashMap::default());
@@ -6785,7 +6902,7 @@ impl LspStore {
})
} else {
let document_colors_task =
- self.request_multiple_lsp_locally(&buffer, None::<usize>, GetDocumentColor, cx);
+ self.request_multiple_lsp_locally(buffer, None::<usize>, GetDocumentColor, cx);
cx.spawn(async move |_, _| {
Ok(document_colors_task
.await
@@ -11278,9 +11395,12 @@ impl LspStore {
}
fn cleanup_lsp_data(&mut self, for_server: LanguageServerId) {
- for buffer_lsp_data in self.lsp_data.values_mut() {
- buffer_lsp_data.colors.remove(&for_server);
- buffer_lsp_data.cache_version += 1;
+ for buffer_colors in self.lsp_document_colors.values_mut() {
+ buffer_colors.colors.remove(&for_server);
+ buffer_colors.cache_version += 1;
+ }
+ for buffer_lens in self.lsp_code_lens.values_mut() {
+ buffer_lens.lens.remove(&for_server);
}
if let Some(local) = self.as_local_mut() {
local.buffer_pull_diagnostics_result_ids.remove(&for_server);
@@ -113,7 +113,7 @@ use std::{
use task_store::TaskStore;
use terminals::Terminals;
-use text::{Anchor, BufferId, Point};
+use text::{Anchor, BufferId, OffsetRangeExt, Point};
use toolchain_store::EmptyToolchainStore;
use util::{
ResultExt as _,
@@ -590,7 +590,7 @@ pub(crate) struct CoreCompletion {
}
/// A code action provided by a language server.
-#[derive(Clone, Debug)]
+#[derive(Clone, Debug, PartialEq)]
pub struct CodeAction {
/// The id of the language server that produced this code action.
pub server_id: LanguageServerId,
@@ -604,7 +604,7 @@ pub struct CodeAction {
}
/// An action sent back by a language server.
-#[derive(Clone, Debug)]
+#[derive(Clone, Debug, PartialEq)]
pub enum LspAction {
/// An action with the full data, may have a command or may not.
/// May require resolving.
@@ -3607,20 +3607,29 @@ impl Project {
})
}
- pub fn code_lens<T: Clone + ToOffset>(
+ pub fn code_lens_actions<T: Clone + ToOffset>(
&mut self,
- buffer_handle: &Entity<Buffer>,
+ buffer: &Entity<Buffer>,
range: Range<T>,
cx: &mut Context<Self>,
) -> Task<Result<Vec<CodeAction>>> {
- let snapshot = buffer_handle.read(cx).snapshot();
- let range = snapshot.anchor_before(range.start)..snapshot.anchor_after(range.end);
+ let snapshot = buffer.read(cx).snapshot();
+ let range = range.clone().to_owned().to_point(&snapshot);
+ let range_start = snapshot.anchor_before(range.start);
+ let range_end = if range.start == range.end {
+ range_start
+ } else {
+ snapshot.anchor_after(range.end)
+ };
+ let range = range_start..range_end;
let code_lens_actions = self
.lsp_store
- .update(cx, |lsp_store, cx| lsp_store.code_lens(buffer_handle, cx));
+ .update(cx, |lsp_store, cx| lsp_store.code_lens_actions(buffer, cx));
cx.background_spawn(async move {
- let mut code_lens_actions = code_lens_actions.await?;
+ let mut code_lens_actions = code_lens_actions
+ .await
+ .map_err(|e| anyhow!("code lens fetch failed: {e:#}"))?;
code_lens_actions.retain(|code_lens_action| {
range
.start
@@ -73,7 +73,7 @@ impl Workspace {
if let Some(terminal_provider) = self.terminal_provider.as_ref() {
let task_status = terminal_provider.spawn(spawn_in_terminal, window, cx);
- cx.background_spawn(async move {
+ let task = cx.background_spawn(async move {
match task_status.await {
Some(Ok(status)) => {
if status.success() {
@@ -82,11 +82,11 @@ impl Workspace {
log::debug!("Task spawn failed, code: {:?}", status.code());
}
}
- Some(Err(e)) => log::error!("Task spawn failed: {e}"),
+ Some(Err(e)) => log::error!("Task spawn failed: {e:#}"),
None => log::debug!("Task spawn got cancelled"),
}
- })
- .detach();
+ });
+ self.scheduled_tasks.push(task);
}
}
@@ -1104,6 +1104,7 @@ pub struct Workspace {
serialized_ssh_project: Option<SerializedSshProject>,
_items_serializer: Task<Result<()>>,
session_id: Option<String>,
+ scheduled_tasks: Vec<Task<()>>,
}
impl EventEmitter<Event> for Workspace {}
@@ -1435,6 +1436,7 @@ impl Workspace {
_items_serializer,
session_id: Some(session_id),
serialized_ssh_project: None,
+ scheduled_tasks: Vec::new(),
}
}
@@ -126,17 +126,28 @@ pub fn init(cx: &mut App) {
cx.on_action(quit);
cx.on_action(|_: &RestoreBanner, cx| title_bar::restore_banner(cx));
- if ReleaseChannel::global(cx) == ReleaseChannel::Dev || cx.has_flag::<PanicFeatureFlag>() {
- cx.on_action(|_: &TestPanic, _| panic!("Ran the TestPanic action"));
- cx.on_action(|_: &TestCrash, _| {
- unsafe extern "C" {
- fn puts(s: *const i8);
- }
- unsafe {
- puts(0xabad1d3a as *const i8);
- }
- });
- }
+ let flag = cx.wait_for_flag::<PanicFeatureFlag>();
+ cx.spawn(async |cx| {
+ if cx
+ .update(|cx| ReleaseChannel::global(cx) == ReleaseChannel::Dev)
+ .unwrap_or_default()
+ || flag.await
+ {
+ cx.update(|cx| {
+ cx.on_action(|_: &TestPanic, _| panic!("Ran the TestPanic action"));
+ cx.on_action(|_: &TestCrash, _| {
+ unsafe extern "C" {
+ fn puts(s: *const i8);
+ }
+ unsafe {
+ puts(0xabad1d3a as *const i8);
+ }
+ });
+ })
+ .ok();
+ };
+ })
+ .detach();
cx.on_action(|_: &OpenLog, cx| {
with_active_or_new_workspace(cx, |workspace, window, cx| {
open_log_file(workspace, window, cx);
@@ -3390,7 +3390,7 @@ Run the `theme selector: toggle` action in the command palette to see a current
## Agent
-Visit [the Configuration page](/ai/configuration.md) under the AI section to learn more about all the agent-related settings.
+Visit [the Configuration page](./ai/configuration.md) under the AI section to learn more about all the agent-related settings.
## Outline Panel
@@ -57,6 +57,40 @@ See [Configuring supported languages](../configuring-languages.md) in the Zed do
TBD: Deno Typescript REPL instructions [docs/repl#typescript-deno](../repl.md#typescript-deno)
-->
+## DAP support
+
+To debug deno programs, add this to `.zed/debug.json`
+
+```json
+[
+ {
+ "adapter": "JavaScript",
+ "label": "Deno",
+ "request": "launch",
+ "type": "pwa-node",
+ "cwd": "$ZED_WORKTREE_ROOT",
+ "program": "$ZED_FILE",
+ "runtimeExecutable": "deno",
+ "runtimeArgs": ["run", "--allow-all", "--inspect-wait"],
+ "attachSimplePort": 9229
+ }
+]
+```
+
+## Runnable support
+
+To run deno tasks like tests from the ui, add this to `.zed/tasks.json`
+
+```json
+[
+ {
+ "label": "deno test",
+ "command": "deno test -A --filter '/^$ZED_CUSTOM_DENO_TEST_NAME$/' $ZED_FILE",
+ "tags": ["js-test"]
+ }
+]
+```
+
## See also:
- [TypeScript](./typescript.md)