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",
"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",

View file

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

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"
);
}
/// 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>(
@ -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<u32>)]) {

View file

@ -370,14 +370,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())),
);
}
@ -392,47 +401,29 @@ 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)
.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<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

View file

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

View file

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

View file

@ -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<Arc<Language>> =
))
});
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<LanguageRegistry>, node: NodeRuntime, cx: &mut App) {
#[cfg(feature = "load-grammars")]
@ -173,7 +172,7 @@ pub fn init(languages: Arc<LanguageRegistry>, 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<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 {
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::<BasedPyrightFeatureFlag, _>({
// 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<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.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.
//

View file

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

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.
pub enum Subscription {
Notification {

View file

@ -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<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>(
delegate: &'a dyn LspAdapterDelegate,
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
prompt_store.workspace = true
proto.workspace = true
python_ui.workspace = true
recent_projects.workspace = true
release_channel.workspace = true
remote.workspace = true

View file

@ -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);
})
});
}