Detailed changes
@@ -9181,6 +9181,19 @@ dependencies = [
"x_ai",
]
+[[package]]
+name = "language_onboarding"
+version = "0.1.0"
+dependencies = [
+ "db",
+ "editor",
+ "gpui",
+ "project",
+ "ui",
+ "workspace",
+ "workspace-hack",
+]
+
[[package]]
name = "language_selector"
version = "0.1.0"
@@ -9243,7 +9256,6 @@ dependencies = [
"chrono",
"collections",
"dap",
- "feature_flags",
"futures 0.3.31",
"gpui",
"http_client",
@@ -20428,6 +20440,7 @@ dependencies = [
"language_extension",
"language_model",
"language_models",
+ "language_onboarding",
"language_selector",
"language_tools",
"languages",
@@ -94,6 +94,7 @@ members = [
"crates/language_extension",
"crates/language_model",
"crates/language_models",
+ "crates/language_onboarding",
"crates/language_selector",
"crates/language_tools",
"crates/languages",
@@ -320,6 +321,7 @@ language = { path = "crates/language" }
language_extension = { path = "crates/language_extension" }
language_model = { path = "crates/language_model" }
language_models = { path = "crates/language_models" }
+language_onboarding = { path = "crates/language_onboarding" }
language_selector = { path = "crates/language_selector" }
language_tools = { path = "crates/language_tools" }
languages = { path = "crates/languages" }
@@ -590,6 +590,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<L: LspAdapter + 'static + Send + Sync + ?Sized>(
@@ -2270,6 +2275,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<u32>)]) {
@@ -374,14 +374,23 @@ impl LanguageRegistry {
pub fn register_available_lsp_adapter(
&self,
name: LanguageServerName,
- load: impl Fn() -> Arc<dyn LspAdapter> + 'static + Send + Sync,
+ adapter: Arc<dyn LspAdapter>,
) {
- 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())),
);
}
@@ -396,13 +405,21 @@ impl LanguageRegistry {
Some(load_lsp_adapter())
}
- pub fn register_lsp_adapter(
- &self,
- language_name: LanguageName,
- adapter: Arc<dyn LspAdapter>,
- ) -> Arc<CachedLspAdapter> {
- let cached = CachedLspAdapter::new(adapter);
+ pub fn register_lsp_adapter(&self, language_name: LanguageName, adapter: Arc<dyn LspAdapter>) {
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)
@@ -411,8 +428,6 @@ impl LanguageRegistry {
state
.all_lsp_adapters
.insert(cached.name.clone(), cached.clone());
-
- cached
}
/// Register a fake language server and adapter
@@ -398,6 +398,10 @@ impl LspAdapter for ExtensionLspAdapter {
Ok(labels_from_extension(labels, language))
}
+
+ fn is_extension(&self) -> bool {
+ true
+ }
}
fn labels_from_extension(
@@ -0,0 +1,30 @@
+[package]
+name = "language_onboarding"
+version = "0.1.0"
+edition.workspace = true
+publish.workspace = true
+license = "GPL-3.0-or-later"
+
+[lints]
+workspace = true
+
+[lib]
+path = "src/python.rs"
+
+[features]
+default = []
+
+[dependencies]
+db.workspace = true
+editor.workspace = true
+gpui.workspace = true
+project.workspace = true
+ui.workspace = true
+workspace.workspace = true
+workspace-hack.workspace = true
+
+# Uncomment other workspace dependencies as needed
+# assistant.workspace = true
+# client.workspace = true
+# project.workspace = true
+# settings.workspace = true
@@ -0,0 +1 @@
+../../LICENSE-GPL
@@ -0,0 +1,95 @@
+use db::kvp::Dismissable;
+use editor::Editor;
+use gpui::{Context, EventEmitter, Subscription};
+use ui::{
+ Banner, Button, Clickable, Color, FluentBuilder as _, IconButton, IconName,
+ InteractiveElement as _, IntoElement, Label, LabelCommon, LabelSize, ParentElement as _,
+ Render, Styled as _, Window, div, h_flex, v_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>) -> 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<ToolbarItemEvent> for BasedPyrightBanner {}
+
+impl Render for BasedPyrightBanner {
+ fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> 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(v_flex()
+ .child("Basedpyright is now the only default language server for Python")
+ .child(Label::new("We have disabled PyRight and pylsp by default. They can be re-enabled in your settings.").size(LabelSize::XSmall).color(Color::Muted))
+ )
+ .child(
+ Button::new("learn-more", "Learn More")
+ .icon(IconName::ArrowUpRight)
+ .on_click(|_, _, cx| {
+ 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<Self>,
+ ) -> ToolbarItemLocation {
+ if let Some(item) = active_pane_item
+ && let Some(editor) = item.act_as::<Editor>(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
+ }
+}
@@ -42,7 +42,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
@@ -1,5 +1,4 @@
use anyhow::Context as _;
-use feature_flags::{FeatureFlag, FeatureFlagAppExt as _};
use gpui::{App, SharedString, UpdateGlobal};
use node_runtime::NodeRuntime;
use python::PyprojectTomlManifestProvider;
@@ -54,12 +53,6 @@ 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([
@@ -174,7 +167,7 @@ pub fn init(languages: Arc<LanguageRegistry>, node: NodeRuntime, cx: &mut App) {
},
LanguageInfo {
name: "python",
- adapters: vec![python_lsp_adapter, py_lsp_adapter],
+ adapters: vec![basedpyright_lsp_adapter],
context: Some(python_context_provider),
toolchain: Some(python_toolchain_provider),
manifest_name: Some(SharedString::new_static("pyproject.toml").into()),
@@ -240,17 +233,6 @@ 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 && 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)
@@ -267,27 +249,19 @@ pub fn init(languages: Arc<LanguageRegistry>, 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;
- 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;
- move || adapter.clone()
- },
+ typescript_lsp_adapter,
);
-
+ languages.register_available_lsp_adapter(python_lsp_adapter.name(), python_lsp_adapter);
+ languages.register_available_lsp_adapter(py_lsp_adapter.name(), py_lsp_adapter);
// Register Tailwind for the existing languages that should have it by default.
//
// This can be driven by the `language_servers` setting once we have a way for
@@ -35,7 +35,7 @@ use std::{
sync::Arc,
};
use task::{ShellKind, TaskTemplate, TaskTemplates, VariableName};
-use util::ResultExt;
+use util::{ResultExt, maybe};
pub(crate) struct PyprojectTomlManifestProvider;
@@ -1619,23 +1619,37 @@ 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();
-
- // 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),
- );
+ {
+ python.insert(
+ "pythonPath".to_owned(),
+ Value::String(interpreter_path.clone()),
+ );
+ python.insert(
+ "defaultInterpreterPath".to_owned(),
+ Value::String(interpreter_path),
+ );
+ }
+ // Basedpyright by default uses `strict` type checking, we tone it down as to not surpris users
+ maybe!({
+ let basedpyright = object
+ .entry("basedpyright")
+ .or_insert(Value::Object(serde_json::Map::default()));
+ let analysis = basedpyright
+ .as_object_mut()?
+ .entry("analysis")
+ .or_insert(Value::Object(serde_json::Map::default()));
+ if let serde_json::map::Entry::Vacant(v) =
+ analysis.as_object_mut()?.entry("typeCheckingMode")
+ {
+ v.insert(Value::String("standard".to_owned()));
+ }
+ Some(())
+ });
}
user_settings
@@ -166,6 +166,12 @@ impl<'a> From<&'a str> for LanguageServerName {
}
}
+impl PartialEq<str> for LanguageServerName {
+ fn eq(&self, other: &str) -> bool {
+ self.0 == other
+ }
+}
+
/// Handle to a language server RPC activity subscription.
pub enum Subscription {
Notification {
@@ -89,6 +89,7 @@ language.workspace = true
language_extension.workspace = true
language_model.workspace = true
language_models.workspace = true
+language_onboarding.workspace = true
language_selector.workspace = true
language_tools.workspace = true
languages = { workspace = true, features = ["load-grammars"] }
@@ -32,6 +32,7 @@ use gpui::{
};
use image_viewer::ImageInfo;
use language::Capability;
+use language_onboarding::BasedPyrightBanner;
use language_tools::lsp_button::{self, LspButton};
use language_tools::lsp_log_view::LspLogToolbarItemView;
use migrate::{MigrationBanner, MigrationEvent, MigrationNotification, MigrationType};
@@ -1001,6 +1002,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);
})
});
}