Compare commits

...
Sign in to create a new pull request.

6 commits

Author SHA1 Message Date
Cole Miller
b290094f72 finish drafting the banner 2025-08-01 18:44:35 -04:00
Cole Miller
f697429456 wip 2025-08-01 18:09:20 -04:00
Cole Miller
80d7fdb251 default to typeCheckingMode=standard 2025-08-01 15:33:52 -04:00
Cole Miller
182e16d3b3 ensure that builtin basedpyright will override the extension 2025-07-30 17:13:36 -04:00
Cole Miller
573eb8e25d machete 2025-07-30 15:14:17 -04:00
Cole Miller
c82efbac7b use basedpyright by default 2025-07-30 15:06:24 -04:00
15 changed files with 242 additions and 171 deletions

16
Cargo.lock generated
View file

@ -9227,7 +9227,6 @@ dependencies = [
"chrono", "chrono",
"collections", "collections",
"dap", "dap",
"feature_flags",
"futures 0.3.31", "futures 0.3.31",
"gpui", "gpui",
"http_client", "http_client",
@ -12807,6 +12806,20 @@ dependencies = [
"wasmtime-math", "wasmtime-math",
] ]
[[package]]
name = "python_ui"
version = "0.1.0"
dependencies = [
"anyhow",
"db",
"editor",
"gpui",
"project",
"ui",
"util",
"workspace",
]
[[package]] [[package]]
name = "qoi" name = "qoi"
version = "0.4.1" version = "0.4.1"
@ -20307,6 +20320,7 @@ dependencies = [
"project_symbols", "project_symbols",
"prompt_store", "prompt_store",
"proto", "proto",
"python_ui",
"recent_projects", "recent_projects",
"release_channel", "release_channel",
"remote", "remote",

View file

@ -124,6 +124,7 @@ members = [
"crates/project_symbols", "crates/project_symbols",
"crates/prompt_store", "crates/prompt_store",
"crates/proto", "crates/proto",
"crates/python_ui",
"crates/recent_projects", "crates/recent_projects",
"crates/refineable", "crates/refineable",
"crates/refineable/derive_refineable", "crates/refineable/derive_refineable",
@ -347,6 +348,7 @@ project_panel = { path = "crates/project_panel" }
project_symbols = { path = "crates/project_symbols" } project_symbols = { path = "crates/project_symbols" }
prompt_store = { path = "crates/prompt_store" } prompt_store = { path = "crates/prompt_store" }
proto = { path = "crates/proto" } proto = { path = "crates/proto" }
python_ui = { path = "crates/python_ui" }
recent_projects = { path = "crates/recent_projects" } recent_projects = { path = "crates/recent_projects" }
refineable = { path = "crates/refineable" } refineable = { path = "crates/refineable" }
release_channel = { path = "crates/release_channel" } release_channel = { path = "crates/release_channel" }

View file

@ -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" "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>( async fn try_fetch_server_binary<L: LspAdapter + 'static + Send + Sync + ?Sized>(
@ -2273,6 +2278,10 @@ impl LspAdapter for FakeLspAdapter {
let label_for_completion = self.label_for_completion.as_ref()?; let label_for_completion = self.label_for_completion.as_ref()?;
label_for_completion(item, language) label_for_completion(item, language)
} }
fn is_extension(&self) -> bool {
false
}
} }
fn get_capture_indices(query: &Query, captures: &mut [(&str, &mut Option<u32>)]) { fn get_capture_indices(query: &Query, captures: &mut [(&str, &mut Option<u32>)]) {

View file

@ -370,14 +370,23 @@ impl LanguageRegistry {
pub fn register_available_lsp_adapter( pub fn register_available_lsp_adapter(
&self, &self,
name: LanguageServerName, 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, name,
Arc::new(move || { Arc::new(move || CachedLspAdapter::new(adapter.clone())),
let lsp_adapter = load();
CachedLspAdapter::new(lsp_adapter)
}),
); );
} }
@ -392,47 +401,29 @@ impl LanguageRegistry {
Some(load_lsp_adapter()) Some(load_lsp_adapter())
} }
pub fn register_lsp_adapter( pub fn register_lsp_adapter(&self, language_name: LanguageName, adapter: Arc<dyn LspAdapter>) {
&self,
language_name: LanguageName,
adapter: Arc<dyn LspAdapter>,
) -> Arc<CachedLspAdapter> {
let cached = CachedLspAdapter::new(adapter);
let mut state = self.state.write(); 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 state
.lsp_adapters .lsp_adapters
.entry(language_name) .entry(language_name.clone())
.or_default() .or_default()
.push(cached.clone()); .push(cached.clone());
state state
.all_lsp_adapters .all_lsp_adapters
.insert(cached.name.clone(), cached.clone()); .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<dyn LspAdapter> + 'static,
) -> Arc<CachedLspAdapter> {
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 /// Register a fake language server and adapter

View file

@ -397,6 +397,10 @@ impl LspAdapter for ExtensionLspAdapter {
Ok(labels_from_extension(labels, language)) Ok(labels_from_extension(labels, language))
} }
fn is_extension(&self) -> bool {
true
}
} }
fn labels_from_extension( fn labels_from_extension(

View file

@ -41,7 +41,6 @@ async-trait.workspace = true
chrono.workspace = true chrono.workspace = true
collections.workspace = true collections.workspace = true
dap.workspace = true dap.workspace = true
feature_flags.workspace = true
futures.workspace = true futures.workspace = true
gpui.workspace = true gpui.workspace = true
http_client.workspace = true http_client.workspace = true

View file

@ -1,5 +1,4 @@
use anyhow::Context as _; use anyhow::Context as _;
use feature_flags::{FeatureFlag, FeatureFlagAppExt as _};
use gpui::{App, UpdateGlobal}; use gpui::{App, UpdateGlobal};
use node_runtime::NodeRuntime; use node_runtime::NodeRuntime;
use python::PyprojectTomlManifestProvider; use python::PyprojectTomlManifestProvider;
@ -53,11 +52,11 @@ pub static LANGUAGE_GIT_COMMIT: std::sync::LazyLock<Arc<Language>> =
)) ))
}); });
struct BasedPyrightFeatureFlag; // struct BasedPyrightFeatureFlag;
impl FeatureFlag for BasedPyrightFeatureFlag { // impl FeatureFlag for BasedPyrightFeatureFlag {
const NAME: &'static str = "basedpyright"; // const NAME: &'static str = "basedpyright";
} // }
pub fn init(languages: Arc<LanguageRegistry>, node: NodeRuntime, cx: &mut App) { pub fn init(languages: Arc<LanguageRegistry>, node: NodeRuntime, cx: &mut App) {
#[cfg(feature = "load-grammars")] #[cfg(feature = "load-grammars")]
@ -173,7 +172,7 @@ pub fn init(languages: Arc<LanguageRegistry>, node: NodeRuntime, cx: &mut App) {
}, },
LanguageInfo { LanguageInfo {
name: "python", 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), context: Some(python_context_provider),
toolchain: Some(python_toolchain_provider), toolchain: Some(python_toolchain_provider),
}, },
@ -236,19 +235,19 @@ pub fn init(languages: Arc<LanguageRegistry>, node: NodeRuntime, cx: &mut App) {
); );
} }
let mut basedpyright_lsp_adapter = Some(basedpyright_lsp_adapter); // let mut basedpyright_lsp_adapter = Some(basedpyright_lsp_adapter);
cx.observe_flag::<BasedPyrightFeatureFlag, _>({ // cx.observe_flag::<BasedPyrightFeatureFlag, _>({
let languages = languages.clone(); // let languages = languages.clone();
move |enabled, _| { // move |enabled, _| {
if enabled { // if enabled {
if let Some(adapter) = basedpyright_lsp_adapter.take() { // if let Some(adapter) = basedpyright_lsp_adapter.take() {
languages // languages
.register_available_lsp_adapter(adapter.name(), move || adapter.clone()); // .register_available_lsp_adapter(adapter.name(), move || adapter.clone());
} // }
} // }
} // }
}) // })
.detach(); // .detach();
// Register globally available language servers. // Register globally available language servers.
// //
@ -266,26 +265,18 @@ pub fn init(languages: Arc<LanguageRegistry>, node: NodeRuntime, cx: &mut App) {
// ``` // ```
languages.register_available_lsp_adapter( languages.register_available_lsp_adapter(
LanguageServerName("tailwindcss-language-server".into()), LanguageServerName("tailwindcss-language-server".into()),
{ tailwind_adapter.clone(),
let adapter = tailwind_adapter.clone();
move || adapter.clone()
},
); );
languages.register_available_lsp_adapter(LanguageServerName("eslint".into()), { languages.register_available_lsp_adapter(
let adapter = eslint_adapter.clone(); LanguageServerName("eslint".into()),
move || adapter.clone() eslint_adapter.clone(),
}); );
languages.register_available_lsp_adapter(LanguageServerName("vtsls".into()), { languages.register_available_lsp_adapter(LanguageServerName("vtsls".into()), vtsls_adapter);
let adapter = vtsls_adapter.clone();
move || adapter.clone()
});
languages.register_available_lsp_adapter( languages.register_available_lsp_adapter(
LanguageServerName("typescript-language-server".into()), LanguageServerName("typescript-language-server".into()),
{ typescript_lsp_adapter,
let adapter = typescript_lsp_adapter.clone();
move || 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. // Register Tailwind for the existing languages that should have it by default.
// //

View file

@ -1599,23 +1599,32 @@ impl LspAdapter for BasedPyrightLspAdapter {
} }
} }
// Always set the python interpreter path // Set both pythonPath and defaultInterpreterPath for compatibility
// Get or create the python section if let Some(python) = object
let python = object
.entry("python") .entry("python")
.or_insert(Value::Object(serde_json::Map::default())) .or_insert(Value::Object(serde_json::Map::default()))
.as_object_mut() .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 if !object.contains_key("typeCheckingMode")
python.insert( && let Some(analysis) = object
"pythonPath".to_owned(), .entry("basedpyright.analysis")
Value::String(interpreter_path.clone()), .or_insert(Value::Object(serde_json::Map::default()))
); .as_object_mut()
python.insert( {
"defaultInterpreterPath".to_owned(), analysis
Value::String(interpreter_path), .entry("typeCheckingMode")
); .or_insert("standard".into());
}
} }
user_settings user_settings

View file

@ -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. /// Handle to a language server RPC activity subscription.
pub enum Subscription { pub enum Subscription {
Notification { Notification {

View file

@ -81,7 +81,6 @@ use sha2::{Digest, Sha256};
use smol::channel::Sender; use smol::channel::Sender;
use snippet::Snippet; use snippet::Snippet;
use std::{ use std::{
any::Any,
borrow::Cow, borrow::Cow,
cell::RefCell, cell::RefCell,
cmp::{Ordering, Reverse}, cmp::{Ordering, Reverse},
@ -12227,87 +12226,6 @@ fn glob_literal_prefix(glob: &Path) -> PathBuf {
.collect() .collect()
} }
pub struct SshLspAdapter {
name: LanguageServerName,
binary: LanguageServerBinary,
initialization_options: Option<String>,
code_action_kinds: Option<Vec<CodeActionKind>>,
}
impl SshLspAdapter {
pub fn new(
name: LanguageServerName,
binary: LanguageServerBinary,
initialization_options: Option<String>,
code_action_kinds: Option<String>,
) -> 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<Self>,
_: &dyn Fs,
_: &Arc<dyn LspAdapterDelegate>,
) -> Result<Option<serde_json::Value>> {
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<Vec<CodeActionKind>> {
self.code_action_kinds.clone()
}
async fn check_if_user_installed(
&self,
_: &dyn LspAdapterDelegate,
_: Arc<dyn LanguageToolchainStore>,
_: &AsyncApp,
) -> Option<LanguageServerBinary> {
Some(self.binary.clone())
}
async fn cached_server_binary(
&self,
_: PathBuf,
_: &dyn LspAdapterDelegate,
) -> Option<LanguageServerBinary> {
None
}
async fn fetch_latest_server_version(
&self,
_: &dyn LspAdapterDelegate,
) -> Result<Box<dyn 'static + Send + Any>> {
anyhow::bail!("SshLspAdapter does not support fetch_latest_server_version")
}
async fn fetch_server_binary(
&self,
_: Box<dyn 'static + Send + Any>,
_: PathBuf,
_: &dyn LspAdapterDelegate,
) -> Result<LanguageServerBinary> {
anyhow::bail!("SshLspAdapter does not support fetch_server_binary")
}
}
pub fn language_server_settings<'a>( pub fn language_server_settings<'a>(
delegate: &'a dyn LspAdapterDelegate, delegate: &'a dyn LspAdapterDelegate,
language: &LanguageServerName, language: &LanguageServerName,

View file

@ -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

View file

@ -0,0 +1 @@
../../LICENSE-GPL

View file

@ -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>) -> 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("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<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
}
}

View file

@ -112,6 +112,7 @@ project_panel.workspace = true
project_symbols.workspace = true project_symbols.workspace = true
prompt_store.workspace = true prompt_store.workspace = true
proto.workspace = true proto.workspace = true
python_ui.workspace = true
recent_projects.workspace = true recent_projects.workspace = true
release_channel.workspace = true release_channel.workspace = true
remote.workspace = true remote.workspace = true

View file

@ -43,6 +43,7 @@ use paths::{
use project::{DirectoryLister, ProjectItem}; use project::{DirectoryLister, ProjectItem};
use project_panel::ProjectPanel; use project_panel::ProjectPanel;
use prompt_store::PromptBuilder; use prompt_store::PromptBuilder;
use python_ui::BasedPyrightBanner;
use quick_action_bar::QuickActionBar; use quick_action_bar::QuickActionBar;
use recent_projects::open_ssh_project; use recent_projects::open_ssh_project;
use release_channel::{AppCommitSha, ReleaseChannel}; use release_channel::{AppCommitSha, ReleaseChannel};
@ -971,6 +972,8 @@ fn initialize_pane(
toolbar.add_item(project_diff_toolbar, window, cx); toolbar.add_item(project_diff_toolbar, window, cx);
let agent_diff_toolbar = cx.new(AgentDiffToolbar::new); let agent_diff_toolbar = cx.new(AgentDiffToolbar::new);
toolbar.add_item(agent_diff_toolbar, window, cx); 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);
}) })
}); });
} }