From c82efbac7b41791293ceabd02139b09d63639847 Mon Sep 17 00:00:00 2001 From: Cole Miller Date: Wed, 30 Jul 2025 15:06:24 -0400 Subject: [PATCH 1/6] use basedpyright by default --- crates/languages/src/lib.rs | 40 +++++++++++++++++++------------------ 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/crates/languages/src/lib.rs b/crates/languages/src/lib.rs index 001fd15200..06f285fccc 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. // @@ -286,6 +285,9 @@ pub fn init(languages: Arc, node: NodeRuntime, cx: &mut App) { move || adapter.clone() }, ); + languages.register_available_lsp_adapter(python_lsp_adapter.name(), move || { + python_lsp_adapter.clone() + }); // Register Tailwind for the existing languages that should have it by default. // From 573eb8e25d7cd3b63a85eed3710c9fc5bde53c1c Mon Sep 17 00:00:00 2001 From: Cole Miller Date: Wed, 30 Jul 2025 15:14:17 -0400 Subject: [PATCH 2/6] machete --- Cargo.lock | 1 - crates/languages/Cargo.toml | 1 - 2 files changed, 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 61553e7799..69876ef92d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -9227,7 +9227,6 @@ dependencies = [ "chrono", "collections", "dap", - "feature_flags", "futures 0.3.31", "gpui", "http_client", 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 From 182e16d3b31b88e80c05b6fdce70eda21e7973ab Mon Sep 17 00:00:00 2001 From: Cole Miller Date: Wed, 30 Jul 2025 17:13:36 -0400 Subject: [PATCH 3/6] ensure that builtin basedpyright will override the extension --- crates/language/src/language.rs | 9 ++ crates/language/src/language_registry.rs | 69 +++++++--------- .../src/extension_lsp_adapter.rs | 4 + crates/languages/src/lib.rs | 27 ++---- crates/project/src/lsp_store.rs | 82 ------------------- 5 files changed, 51 insertions(+), 140 deletions(-) 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/src/lib.rs b/crates/languages/src/lib.rs index 06f285fccc..9fb792fc6c 100644 --- a/crates/languages/src/lib.rs +++ b/crates/languages/src/lib.rs @@ -265,29 +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(), move || { - python_lsp_adapter.clone() - }); + 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/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, From 80d7fdb251d36e19d5af878794339d666eb2a7b0 Mon Sep 17 00:00:00 2001 From: Cole Miller Date: Fri, 1 Aug 2025 15:33:52 -0400 Subject: [PATCH 4/6] default to typeCheckingMode=standard --- crates/languages/src/python.rs | 35 +++++++++++++++++++++------------- 1 file changed, 22 insertions(+), 13 deletions(-) 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 From f6974294563253f27cf83508eed3e2227c3eb839 Mon Sep 17 00:00:00 2001 From: Cole Miller Date: Fri, 1 Aug 2025 18:09:20 -0400 Subject: [PATCH 5/6] wip --- Cargo.lock | 15 +++ Cargo.toml | 2 + crates/lsp/src/lsp.rs | 6 ++ crates/python_ui/Cargo.toml | 31 ++++++ crates/python_ui/LICENSE-GPL | 1 + crates/python_ui/src/python_ui.rs | 150 ++++++++++++++++++++++++++++++ crates/zed/Cargo.toml | 1 + crates/zed/src/zed.rs | 3 + 8 files changed, 209 insertions(+) create mode 100644 crates/python_ui/Cargo.toml create mode 120000 crates/python_ui/LICENSE-GPL create mode 100644 crates/python_ui/src/python_ui.rs diff --git a/Cargo.lock b/Cargo.lock index 69876ef92d..1b16956cd6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -12806,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" @@ -20306,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/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/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..7ada5b8293 --- /dev/null +++ b/crates/python_ui/src/python_ui.rs @@ -0,0 +1,150 @@ +use db::kvp::Dismissable; +use editor::Editor; +use gpui::{App, AppContext as _, Context, EventEmitter, Subscription}; +use ui::{ + Banner, Button, Clickable, FluentBuilder as _, IconButton, IconName, InteractiveElement as _, + IntoElement, ParentElement as _, Render, Window, div, h_flex, +}; +use workspace::{ + ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace, + notifications::{NotificationId, simple_message_notification::MessageNotification}, +}; + +impl Dismissable for BasedPyrightNote { + const KEY: &str = "basedpyright-note"; +} + +// pub fn init(cx: &mut App) { +// cx.observe_new(move |workspace: &mut Workspace, window, cx| { +// let Some(window) = window else { +// return; +// }; + +// cx.subscribe_in(workspace.project(), window, |_, _, event, window, cx| { +// if let project::Event::LanguageServerAdded(_, name, _) = event +// && name == "basedpyright" +// { +// if BasedPyrightNote::dismissed() { +// return; +// } + +// cx.on_next_frame(window, move |workspace, _, cx| { +// workspace.show_notification( +// NotificationId::unique::(), +// cx, +// |cx| { +// cx.new(move |cx| { +// MessageNotification::new( +// "basedpyright is now the default language server for Python", +// cx, +// ) +// .more_info_message("Learn More") +// .more_info_url("https://zed.dev/FIXME") +// // .primary_message("Yes, install extension") +// // .primary_icon(IconName::Check) +// // .primary_icon_color(Color::Success) +// // .primary_on_click({ +// // let extension_id = extension_id.clone(); +// // move |_window, cx| { +// // let extension_id = extension_id.clone(); +// // let extension_store = ExtensionStore::global(cx); +// // extension_store.update(cx, move |store, cx| { +// // store.install_latest_extension(extension_id, cx); +// // }); +// // } +// // }) +// // .secondary_message("No, don't install it") +// // .secondary_icon(IconName::Close) +// // .secondary_icon_color(Color::Error) +// // .secondary_on_click(move |_window, cx| { +// // let key = language_extension_key(&extension_id); +// // db::write_and_log(cx, move || { +// // KEY_VALUE_STORE.write_kvp(key, "dismissed".to_string()) +// // }); +// // }) +// }) +// }, +// ); +// }) +// } +// }) +// .detach(); +// }) +// .detach(); +// } + +pub struct BasedPyrightBanner { + dismissed: bool, + have_basedpyright: bool, + _subscriptions: [Subscription; 1], +} + +impl BasedPyrightBanner { + pub fn new(workspace: &Workspace, cx: &mut Context) -> Self { + let subscription = cx.subscribe(workspace.project(), |this, _, event, cx| { + if let project::Event::LanguageServerAdded(_, name, _) = event + && name == "basedpyright" + { + this.have_basedpyright = true; + } + }); + Self { + dismissed: false, + 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() + .child("Basedpyright is now the default language server for Python") + .child( + Button::new("learn-more", "Learn More") + .icon(IconName::ArrowUpRight), + ), + ) + .action_slot(IconButton::new("dismiss", IconName::Close).on_click( + cx.listener(|this, _, _, cx| { + this.dismissed = true; + 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.downcast::() + && let Some(buffer) = editor.read(cx).buffer().read(cx).as_singleton() + && let Some(file) = buffer.read(cx).file() + && file + .file_name(cx) + .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); }) }); } From b290094f72d467106a0e44e4873d5bb3f1a714ba Mon Sep 17 00:00:00 2001 From: Cole Miller Date: Fri, 1 Aug 2025 18:44:35 -0400 Subject: [PATCH 6/6] finish drafting the banner --- crates/python_ui/src/python_ui.rs | 102 +++++++----------------------- 1 file changed, 22 insertions(+), 80 deletions(-) diff --git a/crates/python_ui/src/python_ui.rs b/crates/python_ui/src/python_ui.rs index 7ada5b8293..6520f30b3a 100644 --- a/crates/python_ui/src/python_ui.rs +++ b/crates/python_ui/src/python_ui.rs @@ -1,77 +1,11 @@ use db::kvp::Dismissable; use editor::Editor; -use gpui::{App, AppContext as _, Context, EventEmitter, Subscription}; +use gpui::{Context, EventEmitter, Subscription}; use ui::{ Banner, Button, Clickable, FluentBuilder as _, IconButton, IconName, InteractiveElement as _, - IntoElement, ParentElement as _, Render, Window, div, h_flex, + IntoElement, ParentElement as _, Render, Styled as _, Window, div, h_flex, }; -use workspace::{ - ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace, - notifications::{NotificationId, simple_message_notification::MessageNotification}, -}; - -impl Dismissable for BasedPyrightNote { - const KEY: &str = "basedpyright-note"; -} - -// pub fn init(cx: &mut App) { -// cx.observe_new(move |workspace: &mut Workspace, window, cx| { -// let Some(window) = window else { -// return; -// }; - -// cx.subscribe_in(workspace.project(), window, |_, _, event, window, cx| { -// if let project::Event::LanguageServerAdded(_, name, _) = event -// && name == "basedpyright" -// { -// if BasedPyrightNote::dismissed() { -// return; -// } - -// cx.on_next_frame(window, move |workspace, _, cx| { -// workspace.show_notification( -// NotificationId::unique::(), -// cx, -// |cx| { -// cx.new(move |cx| { -// MessageNotification::new( -// "basedpyright is now the default language server for Python", -// cx, -// ) -// .more_info_message("Learn More") -// .more_info_url("https://zed.dev/FIXME") -// // .primary_message("Yes, install extension") -// // .primary_icon(IconName::Check) -// // .primary_icon_color(Color::Success) -// // .primary_on_click({ -// // let extension_id = extension_id.clone(); -// // move |_window, cx| { -// // let extension_id = extension_id.clone(); -// // let extension_store = ExtensionStore::global(cx); -// // extension_store.update(cx, move |store, cx| { -// // store.install_latest_extension(extension_id, cx); -// // }); -// // } -// // }) -// // .secondary_message("No, don't install it") -// // .secondary_icon(IconName::Close) -// // .secondary_icon_color(Color::Error) -// // .secondary_on_click(move |_window, cx| { -// // let key = language_extension_key(&extension_id); -// // db::write_and_log(cx, move || { -// // KEY_VALUE_STORE.write_kvp(key, "dismissed".to_string()) -// // }); -// // }) -// }) -// }, -// ); -// }) -// } -// }) -// .detach(); -// }) -// .detach(); -// } +use workspace::{ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace}; pub struct BasedPyrightBanner { dismissed: bool, @@ -79,17 +13,22 @@ pub struct BasedPyrightBanner { _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, cx| { + 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: false, + dismissed, have_basedpyright: false, _subscriptions: [subscription], } @@ -108,15 +47,21 @@ impl Render for BasedPyrightBanner { .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), + .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(); }), )) @@ -130,17 +75,14 @@ impl ToolbarItemView for BasedPyrightBanner { fn set_active_pane_item( &mut self, active_pane_item: Option<&dyn workspace::ItemHandle>, - window: &mut ui::Window, + _window: &mut ui::Window, cx: &mut Context, ) -> ToolbarItemLocation { if let Some(item) = active_pane_item - && let Some(editor) = item.downcast::() - && let Some(buffer) = editor.read(cx).buffer().read(cx).as_singleton() - && let Some(file) = buffer.read(cx).file() - && file - .file_name(cx) - .as_encoded_bytes() - .ends_with(".py".as_bytes()) + && 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; }