diff --git a/Cargo.lock b/Cargo.lock index 61553e7799..1b16956cd6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9227,7 +9227,6 @@ dependencies = [ "chrono", "collections", "dap", - "feature_flags", "futures 0.3.31", "gpui", "http_client", @@ -12807,6 +12806,20 @@ dependencies = [ "wasmtime-math", ] +[[package]] +name = "python_ui" +version = "0.1.0" +dependencies = [ + "anyhow", + "db", + "editor", + "gpui", + "project", + "ui", + "util", + "workspace", +] + [[package]] name = "qoi" version = "0.4.1" @@ -20307,6 +20320,7 @@ dependencies = [ "project_symbols", "prompt_store", "proto", + "python_ui", "recent_projects", "release_channel", "remote", diff --git a/Cargo.toml b/Cargo.toml index cf1ee5956f..6721fcf097 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -124,6 +124,7 @@ members = [ "crates/project_symbols", "crates/prompt_store", "crates/proto", + "crates/python_ui", "crates/recent_projects", "crates/refineable", "crates/refineable/derive_refineable", @@ -347,6 +348,7 @@ project_panel = { path = "crates/project_panel" } project_symbols = { path = "crates/project_symbols" } prompt_store = { path = "crates/prompt_store" } proto = { path = "crates/proto" } +python_ui = { path = "crates/python_ui" } recent_projects = { path = "crates/recent_projects" } refineable = { path = "crates/refineable" } release_channel = { path = "crates/release_channel" } diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 549afc931c..870826d3ca 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -615,6 +615,11 @@ pub trait LspAdapter: 'static + Send + Sync { "Not implemented for this adapter. This method should only be called on the default JSON language server adapter" ); } + + /// True for the extension adapter and false otherwise. + fn is_extension(&self) -> bool { + false + } } async fn try_fetch_server_binary( @@ -2273,6 +2278,10 @@ impl LspAdapter for FakeLspAdapter { let label_for_completion = self.label_for_completion.as_ref()?; label_for_completion(item, language) } + + fn is_extension(&self) -> bool { + false + } } fn get_capture_indices(query: &Query, captures: &mut [(&str, &mut Option)]) { diff --git a/crates/language/src/language_registry.rs b/crates/language/src/language_registry.rs index ab3c0f9b37..608b12f352 100644 --- a/crates/language/src/language_registry.rs +++ b/crates/language/src/language_registry.rs @@ -370,14 +370,23 @@ impl LanguageRegistry { pub fn register_available_lsp_adapter( &self, name: LanguageServerName, - load: impl Fn() -> Arc + 'static + Send + Sync, + adapter: Arc, ) { - self.state.write().available_lsp_adapters.insert( + let mut state = self.state.write(); + + if adapter.is_extension() + && let Some(existing_adapter) = state.all_lsp_adapters.get(&name) + && !existing_adapter.adapter.is_extension() + { + log::warn!( + "not registering extension-provided language server {name:?}, since a builtin language server exists with that name", + ); + return; + } + + state.available_lsp_adapters.insert( name, - Arc::new(move || { - let lsp_adapter = load(); - CachedLspAdapter::new(lsp_adapter) - }), + Arc::new(move || CachedLspAdapter::new(adapter.clone())), ); } @@ -392,47 +401,29 @@ impl LanguageRegistry { Some(load_lsp_adapter()) } - pub fn register_lsp_adapter( - &self, - language_name: LanguageName, - adapter: Arc, - ) -> Arc { - let cached = CachedLspAdapter::new(adapter); + pub fn register_lsp_adapter(&self, language_name: LanguageName, adapter: Arc) { let mut state = self.state.write(); + + if adapter.is_extension() + && let Some(existing_adapter) = state.all_lsp_adapters.get(&adapter.name()) + && !existing_adapter.adapter.is_extension() + { + log::warn!( + "not registering extension-provided language server {:?} for language {language_name:?}, since a builtin language server exists with that name", + adapter.name(), + ); + return; + } + + let cached = CachedLspAdapter::new(adapter); state .lsp_adapters - .entry(language_name) + .entry(language_name.clone()) .or_default() .push(cached.clone()); state .all_lsp_adapters .insert(cached.name.clone(), cached.clone()); - - cached - } - - pub fn get_or_register_lsp_adapter( - &self, - language_name: LanguageName, - server_name: LanguageServerName, - build_adapter: impl FnOnce() -> Arc + 'static, - ) -> Arc { - let registered = self - .state - .write() - .lsp_adapters - .entry(language_name.clone()) - .or_default() - .iter() - .find(|cached_adapter| cached_adapter.name == server_name) - .cloned(); - - if let Some(found) = registered { - found - } else { - let adapter = build_adapter(); - self.register_lsp_adapter(language_name, adapter) - } } /// Register a fake language server and adapter diff --git a/crates/language_extension/src/extension_lsp_adapter.rs b/crates/language_extension/src/extension_lsp_adapter.rs index 58fbe6cda2..8d6dd6eba5 100644 --- a/crates/language_extension/src/extension_lsp_adapter.rs +++ b/crates/language_extension/src/extension_lsp_adapter.rs @@ -397,6 +397,10 @@ impl LspAdapter for ExtensionLspAdapter { Ok(labels_from_extension(labels, language)) } + + fn is_extension(&self) -> bool { + true + } } fn labels_from_extension( diff --git a/crates/languages/Cargo.toml b/crates/languages/Cargo.toml index 260126da63..2e8f007cff 100644 --- a/crates/languages/Cargo.toml +++ b/crates/languages/Cargo.toml @@ -41,7 +41,6 @@ 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 diff --git a/crates/languages/src/lib.rs b/crates/languages/src/lib.rs index 001fd15200..9fb792fc6c 100644 --- a/crates/languages/src/lib.rs +++ b/crates/languages/src/lib.rs @@ -1,5 +1,4 @@ use anyhow::Context as _; -use feature_flags::{FeatureFlag, FeatureFlagAppExt as _}; use gpui::{App, UpdateGlobal}; use node_runtime::NodeRuntime; use python::PyprojectTomlManifestProvider; @@ -53,11 +52,11 @@ pub static LANGUAGE_GIT_COMMIT: std::sync::LazyLock> = )) }); -struct BasedPyrightFeatureFlag; +// struct BasedPyrightFeatureFlag; -impl FeatureFlag for BasedPyrightFeatureFlag { - const NAME: &'static str = "basedpyright"; -} +// impl FeatureFlag for BasedPyrightFeatureFlag { +// const NAME: &'static str = "basedpyright"; +// } pub fn init(languages: Arc, node: NodeRuntime, cx: &mut App) { #[cfg(feature = "load-grammars")] @@ -173,7 +172,7 @@ pub fn init(languages: Arc, node: NodeRuntime, cx: &mut App) { }, LanguageInfo { name: "python", - adapters: vec![python_lsp_adapter.clone(), py_lsp_adapter.clone()], + adapters: vec![basedpyright_lsp_adapter.clone(), py_lsp_adapter.clone()], context: Some(python_context_provider), toolchain: Some(python_toolchain_provider), }, @@ -236,19 +235,19 @@ pub fn init(languages: Arc, node: NodeRuntime, cx: &mut App) { ); } - let mut basedpyright_lsp_adapter = Some(basedpyright_lsp_adapter); - cx.observe_flag::({ - 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(); + // let mut basedpyright_lsp_adapter = Some(basedpyright_lsp_adapter); + // cx.observe_flag::({ + // 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. // @@ -266,26 +265,18 @@ pub fn init(languages: Arc, node: NodeRuntime, cx: &mut App) { // ``` languages.register_available_lsp_adapter( LanguageServerName("tailwindcss-language-server".into()), - { - let adapter = tailwind_adapter.clone(); - move || adapter.clone() - }, + tailwind_adapter.clone(), ); - languages.register_available_lsp_adapter(LanguageServerName("eslint".into()), { - let adapter = eslint_adapter.clone(); - move || adapter.clone() - }); - languages.register_available_lsp_adapter(LanguageServerName("vtsls".into()), { - let adapter = vtsls_adapter.clone(); - move || adapter.clone() - }); + languages.register_available_lsp_adapter( + LanguageServerName("eslint".into()), + eslint_adapter.clone(), + ); + languages.register_available_lsp_adapter(LanguageServerName("vtsls".into()), vtsls_adapter); languages.register_available_lsp_adapter( LanguageServerName("typescript-language-server".into()), - { - let adapter = typescript_lsp_adapter.clone(); - move || adapter.clone() - }, + typescript_lsp_adapter, ); + languages.register_available_lsp_adapter(python_lsp_adapter.name(), python_lsp_adapter); // Register Tailwind for the existing languages that should have it by default. // diff --git a/crates/languages/src/python.rs b/crates/languages/src/python.rs index 0524c02fd5..2133b3c99e 100644 --- a/crates/languages/src/python.rs +++ b/crates/languages/src/python.rs @@ -1599,23 +1599,32 @@ impl LspAdapter for BasedPyrightLspAdapter { } } - // Always set the python interpreter path - // Get or create the python section - let python = object + // Set both pythonPath and defaultInterpreterPath for compatibility + if let Some(python) = object .entry("python") .or_insert(Value::Object(serde_json::Map::default())) .as_object_mut() - .unwrap(); + { + python.insert( + "pythonPath".to_owned(), + Value::String(interpreter_path.clone()), + ); + python.insert( + "defaultInterpreterPath".to_owned(), + Value::String(interpreter_path), + ); + } - // 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), - ); + if !object.contains_key("typeCheckingMode") + && let Some(analysis) = object + .entry("basedpyright.analysis") + .or_insert(Value::Object(serde_json::Map::default())) + .as_object_mut() + { + analysis + .entry("typeCheckingMode") + .or_insert("standard".into()); + } } user_settings diff --git a/crates/lsp/src/lsp.rs b/crates/lsp/src/lsp.rs index b9701a83d2..7ca6bb29bc 100644 --- a/crates/lsp/src/lsp.rs +++ b/crates/lsp/src/lsp.rs @@ -166,6 +166,12 @@ impl<'a> From<&'a str> for LanguageServerName { } } +impl PartialEq for LanguageServerName { + fn eq(&self, other: &str) -> bool { + self.0 == other + } +} + /// Handle to a language server RPC activity subscription. pub enum Subscription { Notification { diff --git a/crates/project/src/lsp_store.rs b/crates/project/src/lsp_store.rs index dd4d0a7f40..9dab6f5527 100644 --- a/crates/project/src/lsp_store.rs +++ b/crates/project/src/lsp_store.rs @@ -81,7 +81,6 @@ use sha2::{Digest, Sha256}; use smol::channel::Sender; use snippet::Snippet; use std::{ - any::Any, borrow::Cow, cell::RefCell, cmp::{Ordering, Reverse}, @@ -12227,87 +12226,6 @@ fn glob_literal_prefix(glob: &Path) -> PathBuf { .collect() } -pub struct SshLspAdapter { - name: LanguageServerName, - binary: LanguageServerBinary, - initialization_options: Option, - code_action_kinds: Option>, -} - -impl SshLspAdapter { - pub fn new( - name: LanguageServerName, - binary: LanguageServerBinary, - initialization_options: Option, - code_action_kinds: Option, - ) -> Self { - Self { - name, - binary, - initialization_options, - code_action_kinds: code_action_kinds - .as_ref() - .and_then(|c| serde_json::from_str(c).ok()), - } - } -} - -#[async_trait(?Send)] -impl LspAdapter for SshLspAdapter { - fn name(&self) -> LanguageServerName { - self.name.clone() - } - - async fn initialization_options( - self: Arc, - _: &dyn Fs, - _: &Arc, - ) -> Result> { - let Some(options) = &self.initialization_options else { - return Ok(None); - }; - let result = serde_json::from_str(options)?; - Ok(result) - } - - fn code_action_kinds(&self) -> Option> { - self.code_action_kinds.clone() - } - - async fn check_if_user_installed( - &self, - _: &dyn LspAdapterDelegate, - _: Arc, - _: &AsyncApp, - ) -> Option { - Some(self.binary.clone()) - } - - async fn cached_server_binary( - &self, - _: PathBuf, - _: &dyn LspAdapterDelegate, - ) -> Option { - None - } - - async fn fetch_latest_server_version( - &self, - _: &dyn LspAdapterDelegate, - ) -> Result> { - anyhow::bail!("SshLspAdapter does not support fetch_latest_server_version") - } - - async fn fetch_server_binary( - &self, - _: Box, - _: PathBuf, - _: &dyn LspAdapterDelegate, - ) -> Result { - anyhow::bail!("SshLspAdapter does not support fetch_server_binary") - } -} - pub fn language_server_settings<'a>( delegate: &'a dyn LspAdapterDelegate, language: &LanguageServerName, diff --git a/crates/python_ui/Cargo.toml b/crates/python_ui/Cargo.toml new file mode 100644 index 0000000000..ca0528dca6 --- /dev/null +++ b/crates/python_ui/Cargo.toml @@ -0,0 +1,31 @@ +[package] +name = "python_ui" +version = "0.1.0" +edition.workspace = true +publish.workspace = true +license = "GPL-3.0-or-later" + +[lints] +workspace = true + +[lib] +path = "src/python_ui.rs" + +[features] +default = [] + +[dependencies] +anyhow.workspace = true +db.workspace = true +editor.workspace = true +gpui.workspace = true +project.workspace = true +ui.workspace = true +util.workspace = true +workspace.workspace = true + +# Uncomment other workspace dependencies as needed +# assistant.workspace = true +# client.workspace = true +# project.workspace = true +# settings.workspace = true diff --git a/crates/python_ui/LICENSE-GPL b/crates/python_ui/LICENSE-GPL new file mode 120000 index 0000000000..89e542f750 --- /dev/null +++ b/crates/python_ui/LICENSE-GPL @@ -0,0 +1 @@ +../../LICENSE-GPL \ No newline at end of file diff --git a/crates/python_ui/src/python_ui.rs b/crates/python_ui/src/python_ui.rs new file mode 100644 index 0000000000..6520f30b3a --- /dev/null +++ b/crates/python_ui/src/python_ui.rs @@ -0,0 +1,92 @@ +use db::kvp::Dismissable; +use editor::Editor; +use gpui::{Context, EventEmitter, Subscription}; +use ui::{ + Banner, Button, Clickable, FluentBuilder as _, IconButton, IconName, InteractiveElement as _, + IntoElement, ParentElement as _, Render, Styled as _, Window, div, h_flex, +}; +use workspace::{ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace}; + +pub struct BasedPyrightBanner { + dismissed: bool, + have_basedpyright: bool, + _subscriptions: [Subscription; 1], +} + +impl Dismissable for BasedPyrightBanner { + const KEY: &str = "basedpyright-banner"; +} + +impl BasedPyrightBanner { + pub fn new(workspace: &Workspace, cx: &mut Context) -> Self { + let subscription = cx.subscribe(workspace.project(), |this, _, event, _| { + if let project::Event::LanguageServerAdded(_, name, _) = event + && name == "basedpyright" + { + this.have_basedpyright = true; + } + }); + let dismissed = Self::dismissed(); + Self { + dismissed, + have_basedpyright: false, + _subscriptions: [subscription], + } + } +} + +impl EventEmitter for BasedPyrightBanner {} + +impl Render for BasedPyrightBanner { + fn render(&mut self, _window: &mut Window, cx: &mut Context) -> impl IntoElement { + div() + .id("basedpyright-banner") + .when(!self.dismissed && self.have_basedpyright, |el| { + el.child( + Banner::new() + .severity(ui::Severity::Info) + .child( + h_flex() + .gap_2() + .child("Basedpyright is now the default language server for Python") + .child( + Button::new("learn-more", "Learn More") + .icon(IconName::ArrowUpRight) + .on_click(|_, _, cx| { + // FIXME more specific link + cx.open_url("https://zed.dev/docs/languages/python") + }), + ), + ) + .action_slot(IconButton::new("dismiss", IconName::Close).on_click( + cx.listener(|this, _, _, cx| { + this.dismissed = true; + Self::set_dismissed(true, cx); + cx.notify(); + }), + )) + .into_any_element(), + ) + }) + } +} + +impl ToolbarItemView for BasedPyrightBanner { + fn set_active_pane_item( + &mut self, + active_pane_item: Option<&dyn workspace::ItemHandle>, + _window: &mut ui::Window, + cx: &mut Context, + ) -> ToolbarItemLocation { + if let Some(item) = active_pane_item + && let Some(editor) = item.act_as::(cx) + && let Some(path) = editor.update(cx, |editor, cx| editor.target_file_abs_path(cx)) + && let Some(file_name) = path.file_name() + && file_name.as_encoded_bytes().ends_with(".py".as_bytes()) + { + return ToolbarItemLocation::Secondary; + } + + ToolbarItemLocation::Hidden + } +} diff --git a/crates/zed/Cargo.toml b/crates/zed/Cargo.toml index 5835ba4db1..036c8f5866 100644 --- a/crates/zed/Cargo.toml +++ b/crates/zed/Cargo.toml @@ -112,6 +112,7 @@ project_panel.workspace = true project_symbols.workspace = true prompt_store.workspace = true proto.workspace = true +python_ui.workspace = true recent_projects.workspace = true release_channel.workspace = true remote.workspace = true diff --git a/crates/zed/src/zed.rs b/crates/zed/src/zed.rs index c72fe39d2d..7b561c9988 100644 --- a/crates/zed/src/zed.rs +++ b/crates/zed/src/zed.rs @@ -43,6 +43,7 @@ use paths::{ use project::{DirectoryLister, ProjectItem}; use project_panel::ProjectPanel; use prompt_store::PromptBuilder; +use python_ui::BasedPyrightBanner; use quick_action_bar::QuickActionBar; use recent_projects::open_ssh_project; use release_channel::{AppCommitSha, ReleaseChannel}; @@ -971,6 +972,8 @@ fn initialize_pane( toolbar.add_item(project_diff_toolbar, window, cx); let agent_diff_toolbar = cx.new(AgentDiffToolbar::new); toolbar.add_item(agent_diff_toolbar, window, cx); + let basedpyright_banner = cx.new(|cx| BasedPyrightBanner::new(workspace, cx)); + toolbar.add_item(basedpyright_banner, window, cx); }) }); }