1use db::kvp::Dismissable;
2use editor::Editor;
3use gpui::{Context, EventEmitter, Subscription};
4use ui::{Banner, FluentBuilder as _, prelude::*};
5use workspace::{ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace};
6
7pub struct BasedPyrightBanner {
8 dismissed: bool,
9 have_basedpyright: bool,
10 _subscriptions: [Subscription; 1],
11}
12
13impl Dismissable for BasedPyrightBanner {
14 const KEY: &str = "basedpyright-banner";
15}
16
17impl BasedPyrightBanner {
18 pub fn new(workspace: &Workspace, cx: &mut Context<Self>) -> Self {
19 let subscription = cx.subscribe(workspace.project(), |this, _, event, _| {
20 if let project::Event::LanguageServerAdded(_, name, _) = event
21 && name == "basedpyright"
22 {
23 this.have_basedpyright = true;
24 }
25 });
26 let dismissed = Self::dismissed();
27 Self {
28 dismissed,
29 have_basedpyright: false,
30 _subscriptions: [subscription],
31 }
32 }
33
34 fn onboarding_banner_enabled(&self) -> bool {
35 !self.dismissed && self.have_basedpyright
36 }
37}
38
39impl EventEmitter<ToolbarItemEvent> for BasedPyrightBanner {}
40
41impl Render for BasedPyrightBanner {
42 fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
43 div()
44 .id("basedpyright-banner")
45 .when(self.onboarding_banner_enabled(), |el| {
46 el.child(
47 Banner::new()
48 .child(
49 v_flex()
50 .gap_0p5()
51 .child(Label::new("Basedpyright is now the only default language server for Python").mt_0p5())
52 .child(Label::new("We have disabled PyRight and pylsp by default. They can be re-enabled in your settings.").size(LabelSize::Small).color(Color::Muted))
53 )
54 .action_slot(
55 h_flex()
56 .gap_0p5()
57 .child(
58 Button::new("learn-more", "Learn More")
59 .icon(IconName::ArrowUpRight)
60 .label_size(LabelSize::Small)
61 .icon_size(IconSize::XSmall)
62 .icon_color(Color::Muted)
63 .on_click(|_, _, cx| {
64 cx.open_url("https://zed.dev/docs/languages/python")
65 }),
66 )
67 .child(IconButton::new("dismiss", IconName::Close).icon_size(IconSize::Small).on_click(
68 cx.listener(|this, _, _, cx| {
69 this.dismissed = true;
70 Self::set_dismissed(true, cx);
71 cx.notify();
72 }),
73 ))
74 )
75 .into_any_element(),
76 )
77 })
78 }
79}
80
81impl ToolbarItemView for BasedPyrightBanner {
82 fn set_active_pane_item(
83 &mut self,
84 active_pane_item: Option<&dyn workspace::ItemHandle>,
85 _window: &mut ui::Window,
86 cx: &mut Context<Self>,
87 ) -> ToolbarItemLocation {
88 if !self.onboarding_banner_enabled() {
89 return ToolbarItemLocation::Hidden;
90 }
91 if let Some(item) = active_pane_item
92 && let Some(editor) = item.act_as::<Editor>(cx)
93 && let Some(path) = editor.update(cx, |editor, cx| editor.target_file_abs_path(cx))
94 && let Some(file_name) = path.file_name()
95 && file_name.as_encoded_bytes().ends_with(".py".as_bytes())
96 {
97 return ToolbarItemLocation::Secondary;
98 }
99
100 ToolbarItemLocation::Hidden
101 }
102}