Compare commits
24 Commits
devcontain
...
v0.204.5
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
289a4a6aa7 | ||
|
|
aa8ca12369 | ||
|
|
8eb3952c12 | ||
|
|
529fb5ff30 | ||
|
|
12bc66e6ed | ||
|
|
7dc833d932 | ||
|
|
85cfd726c8 | ||
|
|
9ae63f04f0 | ||
|
|
9bfe7a1798 | ||
|
|
10f172ddac | ||
|
|
a62514df7a | ||
|
|
97d04621de | ||
|
|
4dc0720741 | ||
|
|
8127523d0f | ||
|
|
a1c8e69283 | ||
|
|
3a79ce7312 | ||
|
|
06e58c318b | ||
|
|
64adb6d461 | ||
|
|
69a7c3e252 | ||
|
|
41737efb07 | ||
|
|
608991db39 | ||
|
|
8e0e2a887c | ||
|
|
d900f12473 | ||
|
|
b3729e0b90 |
@@ -32,7 +32,7 @@ jobs:
|
||||
- name: Discord Webhook Action
|
||||
uses: tsickert/discord-webhook@c840d45a03a323fbc3f7507ac7769dbd91bfb164 # v5.3.0
|
||||
with:
|
||||
webhook-url: ${{ secrets.DISCORD_WEBHOOK_URL }}
|
||||
webhook-url: ${{ secrets.DISCORD_WEBHOOK_RELEASE_NOTES }}
|
||||
content: ${{ steps.get-content.outputs.string }}
|
||||
|
||||
send_release_notes_email:
|
||||
|
||||
3
Cargo.lock
generated
3
Cargo.lock
generated
@@ -301,6 +301,7 @@ dependencies = [
|
||||
"futures 0.3.31",
|
||||
"gpui",
|
||||
"gpui_tokio",
|
||||
"http_client",
|
||||
"indoc",
|
||||
"language",
|
||||
"language_model",
|
||||
@@ -20372,7 +20373,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "zed"
|
||||
version = "0.204.0"
|
||||
version = "0.204.5"
|
||||
dependencies = [
|
||||
"acp_tools",
|
||||
"activity_indicator",
|
||||
|
||||
@@ -550,6 +550,8 @@
|
||||
"cmd-ctrl-left": "editor::SelectSmallerSyntaxNode", // Shrink selection
|
||||
"cmd-ctrl-right": "editor::SelectLargerSyntaxNode", // Expand selection
|
||||
"cmd-ctrl-up": "editor::SelectPreviousSyntaxNode", // Move selection up
|
||||
"ctrl-shift-right": "editor::SelectLargerSyntaxNode", // Expand selection (VSCode version)
|
||||
"ctrl-shift-left": "editor::SelectSmallerSyntaxNode", // Shrink selection (VSCode version)
|
||||
"cmd-ctrl-down": "editor::SelectNextSyntaxNode", // Move selection down
|
||||
"cmd-d": ["editor::SelectNext", { "replace_newest": false }], // editor.action.addSelectionToNextFindMatch / find_under_expand
|
||||
"cmd-shift-l": "editor::SelectAllMatches", // Select all occurrences of current selection
|
||||
|
||||
@@ -1769,6 +1769,9 @@ impl AcpThread {
|
||||
reuse_shared_snapshot: bool,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Result<String>> {
|
||||
// Args are 1-based, move to 0-based
|
||||
let line = line.unwrap_or_default().saturating_sub(1);
|
||||
let limit = limit.unwrap_or(u32::MAX);
|
||||
let project = self.project.clone();
|
||||
let action_log = self.action_log.clone();
|
||||
cx.spawn(async move |this, cx| {
|
||||
@@ -1796,44 +1799,37 @@ impl AcpThread {
|
||||
action_log.update(cx, |action_log, cx| {
|
||||
action_log.buffer_read(buffer.clone(), cx);
|
||||
})?;
|
||||
project.update(cx, |project, cx| {
|
||||
let position = buffer
|
||||
.read(cx)
|
||||
.snapshot()
|
||||
.anchor_before(Point::new(line.unwrap_or_default(), 0));
|
||||
project.set_agent_location(
|
||||
Some(AgentLocation {
|
||||
buffer: buffer.downgrade(),
|
||||
position,
|
||||
}),
|
||||
cx,
|
||||
);
|
||||
})?;
|
||||
|
||||
buffer.update(cx, |buffer, _| buffer.snapshot())?
|
||||
let snapshot = buffer.update(cx, |buffer, _| buffer.snapshot())?;
|
||||
this.update(cx, |this, _| {
|
||||
this.shared_buffers.insert(buffer.clone(), snapshot.clone());
|
||||
})?;
|
||||
snapshot
|
||||
};
|
||||
|
||||
this.update(cx, |this, _| {
|
||||
let text = snapshot.text();
|
||||
this.shared_buffers.insert(buffer.clone(), snapshot);
|
||||
if line.is_none() && limit.is_none() {
|
||||
return Ok(text);
|
||||
}
|
||||
let limit = limit.unwrap_or(u32::MAX) as usize;
|
||||
let Some(line) = line else {
|
||||
return Ok(text.lines().take(limit).collect::<String>());
|
||||
};
|
||||
let max_point = snapshot.max_point();
|
||||
if line >= max_point.row {
|
||||
anyhow::bail!(
|
||||
"Attempting to read beyond the end of the file, line {}:{}",
|
||||
max_point.row + 1,
|
||||
max_point.column
|
||||
);
|
||||
}
|
||||
|
||||
let count = text.lines().count();
|
||||
if count < line as usize {
|
||||
anyhow::bail!("There are only {} lines", count);
|
||||
}
|
||||
Ok(text
|
||||
.lines()
|
||||
.skip(line as usize + 1)
|
||||
.take(limit)
|
||||
.collect::<String>())
|
||||
})?
|
||||
let start = snapshot.anchor_before(Point::new(line, 0));
|
||||
let end = snapshot.anchor_before(Point::new(line.saturating_add(limit), 0));
|
||||
|
||||
project.update(cx, |project, cx| {
|
||||
project.set_agent_location(
|
||||
Some(AgentLocation {
|
||||
buffer: buffer.downgrade(),
|
||||
position: start,
|
||||
}),
|
||||
cx,
|
||||
);
|
||||
})?;
|
||||
|
||||
Ok(snapshot.text_for_range(start..end).collect::<String>())
|
||||
})
|
||||
}
|
||||
|
||||
@@ -2378,6 +2374,82 @@ mod tests {
|
||||
request.await.unwrap();
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_reading_from_line(cx: &mut TestAppContext) {
|
||||
init_test(cx);
|
||||
|
||||
let fs = FakeFs::new(cx.executor());
|
||||
fs.insert_tree(path!("/tmp"), json!({"foo": "one\ntwo\nthree\nfour\n"}))
|
||||
.await;
|
||||
let project = Project::test(fs.clone(), [], cx).await;
|
||||
project
|
||||
.update(cx, |project, cx| {
|
||||
project.find_or_create_worktree(path!("/tmp/foo"), true, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let connection = Rc::new(FakeAgentConnection::new());
|
||||
|
||||
let thread = cx
|
||||
.update(|cx| connection.new_thread(project, Path::new(path!("/tmp")), cx))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Whole file
|
||||
let content = thread
|
||||
.update(cx, |thread, cx| {
|
||||
thread.read_text_file(path!("/tmp/foo").into(), None, None, false, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(content, "one\ntwo\nthree\nfour\n");
|
||||
|
||||
// Only start line
|
||||
let content = thread
|
||||
.update(cx, |thread, cx| {
|
||||
thread.read_text_file(path!("/tmp/foo").into(), Some(3), None, false, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(content, "three\nfour\n");
|
||||
|
||||
// Only limit
|
||||
let content = thread
|
||||
.update(cx, |thread, cx| {
|
||||
thread.read_text_file(path!("/tmp/foo").into(), None, Some(2), false, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(content, "one\ntwo\n");
|
||||
|
||||
// Range
|
||||
let content = thread
|
||||
.update(cx, |thread, cx| {
|
||||
thread.read_text_file(path!("/tmp/foo").into(), Some(2), Some(2), false, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(content, "two\nthree\n");
|
||||
|
||||
// Invalid
|
||||
let err = thread
|
||||
.update(cx, |thread, cx| {
|
||||
thread.read_text_file(path!("/tmp/foo").into(), Some(5), Some(2), false, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap_err();
|
||||
|
||||
assert_eq!(
|
||||
err.to_string(),
|
||||
"Attempting to read beyond the end of the file, line 5:0"
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_succeeding_canceled_toolcall(cx: &mut TestAppContext) {
|
||||
init_test(cx);
|
||||
|
||||
@@ -30,6 +30,7 @@ fs.workspace = true
|
||||
futures.workspace = true
|
||||
gpui.workspace = true
|
||||
gpui_tokio = { workspace = true, optional = true }
|
||||
http_client.workspace = true
|
||||
indoc.workspace = true
|
||||
language.workspace = true
|
||||
language_model.workspace = true
|
||||
|
||||
@@ -7,15 +7,19 @@ mod gemini;
|
||||
pub mod e2e_tests;
|
||||
|
||||
pub use claude::*;
|
||||
use client::ProxySettings;
|
||||
use collections::HashMap;
|
||||
pub use custom::*;
|
||||
use fs::Fs;
|
||||
pub use gemini::*;
|
||||
use http_client::read_no_proxy_from_env;
|
||||
use project::agent_server_store::AgentServerStore;
|
||||
|
||||
use acp_thread::AgentConnection;
|
||||
use anyhow::Result;
|
||||
use gpui::{App, Entity, SharedString, Task};
|
||||
use gpui::{App, AppContext, Entity, SharedString, Task};
|
||||
use project::Project;
|
||||
use settings::SettingsStore;
|
||||
use std::{any::Any, path::Path, rc::Rc, sync::Arc};
|
||||
|
||||
pub use acp::AcpConnection;
|
||||
@@ -77,3 +81,25 @@ impl dyn AgentServer {
|
||||
self.into_any().downcast().ok()
|
||||
}
|
||||
}
|
||||
|
||||
/// Load the default proxy environment variables to pass through to the agent
|
||||
pub fn load_proxy_env(cx: &mut App) -> HashMap<String, String> {
|
||||
let proxy_url = cx
|
||||
.read_global(|settings: &SettingsStore, _| settings.get::<ProxySettings>(None).proxy_url());
|
||||
let mut env = HashMap::default();
|
||||
|
||||
if let Some(proxy_url) = &proxy_url {
|
||||
let env_var = if proxy_url.scheme() == "https" {
|
||||
"HTTPS_PROXY"
|
||||
} else {
|
||||
"HTTP_PROXY"
|
||||
};
|
||||
env.insert(env_var.to_owned(), proxy_url.to_string());
|
||||
}
|
||||
|
||||
if let Some(no_proxy) = read_no_proxy_from_env() {
|
||||
env.insert("NO_PROXY".to_owned(), no_proxy);
|
||||
}
|
||||
|
||||
env
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ use anyhow::{Context as _, Result};
|
||||
use gpui::{App, AppContext as _, SharedString, Task};
|
||||
use project::agent_server_store::{AllAgentServersSettings, CLAUDE_CODE_NAME};
|
||||
|
||||
use crate::{AgentServer, AgentServerDelegate};
|
||||
use crate::{AgentServer, AgentServerDelegate, load_proxy_env};
|
||||
use acp_thread::AgentConnection;
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -60,6 +60,7 @@ impl AgentServer for ClaudeCode {
|
||||
let root_dir = root_dir.map(|root_dir| root_dir.to_string_lossy().to_string());
|
||||
let is_remote = delegate.project.read(cx).is_via_remote_server();
|
||||
let store = delegate.store.downgrade();
|
||||
let extra_env = load_proxy_env(cx);
|
||||
let default_mode = self.default_mode(cx);
|
||||
|
||||
cx.spawn(async move |cx| {
|
||||
@@ -70,7 +71,7 @@ impl AgentServer for ClaudeCode {
|
||||
.context("Claude Code is not registered")?;
|
||||
anyhow::Ok(agent.get_command(
|
||||
root_dir.as_deref(),
|
||||
Default::default(),
|
||||
extra_env,
|
||||
delegate.status_tx,
|
||||
delegate.new_version_available,
|
||||
&mut cx.to_async(),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::AgentServerDelegate;
|
||||
use crate::{AgentServerDelegate, load_proxy_env};
|
||||
use acp_thread::AgentConnection;
|
||||
use agent_client_protocol as acp;
|
||||
use anyhow::{Context as _, Result};
|
||||
@@ -65,6 +65,7 @@ impl crate::AgentServer for CustomAgentServer {
|
||||
let is_remote = delegate.project.read(cx).is_via_remote_server();
|
||||
let default_mode = self.default_mode(cx);
|
||||
let store = delegate.store.downgrade();
|
||||
let extra_env = load_proxy_env(cx);
|
||||
|
||||
cx.spawn(async move |cx| {
|
||||
let (command, root_dir, login) = store
|
||||
@@ -76,7 +77,7 @@ impl crate::AgentServer for CustomAgentServer {
|
||||
})?;
|
||||
anyhow::Ok(agent.get_command(
|
||||
root_dir.as_deref(),
|
||||
Default::default(),
|
||||
extra_env,
|
||||
delegate.status_tx,
|
||||
delegate.new_version_available,
|
||||
&mut cx.to_async(),
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
use std::rc::Rc;
|
||||
use std::{any::Any, path::Path};
|
||||
|
||||
use crate::{AgentServer, AgentServerDelegate};
|
||||
use crate::{AgentServer, AgentServerDelegate, load_proxy_env};
|
||||
use acp_thread::AgentConnection;
|
||||
use anyhow::{Context as _, Result};
|
||||
use client::ProxySettings;
|
||||
use collections::HashMap;
|
||||
use gpui::{App, AppContext, SharedString, Task};
|
||||
use gpui::{App, SharedString, Task};
|
||||
use language_models::provider::google::GoogleLanguageModelProvider;
|
||||
use project::agent_server_store::GEMINI_NAME;
|
||||
use settings::SettingsStore;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Gemini;
|
||||
@@ -37,17 +34,15 @@ impl AgentServer for Gemini {
|
||||
let root_dir = root_dir.map(|root_dir| root_dir.to_string_lossy().to_string());
|
||||
let is_remote = delegate.project.read(cx).is_via_remote_server();
|
||||
let store = delegate.store.downgrade();
|
||||
let proxy_url = cx.read_global(|settings: &SettingsStore, _| {
|
||||
settings.get::<ProxySettings>(None).proxy.clone()
|
||||
});
|
||||
let mut extra_env = load_proxy_env(cx);
|
||||
let default_mode = self.default_mode(cx);
|
||||
|
||||
cx.spawn(async move |cx| {
|
||||
let mut extra_env = HashMap::default();
|
||||
extra_env.insert("SURFACE".to_owned(), "zed".to_owned());
|
||||
if let Some(api_key) = cx.update(GoogleLanguageModelProvider::api_key)?.await.ok() {
|
||||
extra_env.insert("GEMINI_API_KEY".into(), api_key.key);
|
||||
}
|
||||
let (mut command, root_dir, login) = store
|
||||
let (command, root_dir, login) = store
|
||||
.update(cx, |store, cx| {
|
||||
let agent = store
|
||||
.get_external_agent(&GEMINI_NAME.into())
|
||||
@@ -62,14 +57,6 @@ impl AgentServer for Gemini {
|
||||
})??
|
||||
.await?;
|
||||
|
||||
// Add proxy flag if proxy settings are configured in Zed and not in the args
|
||||
if let Some(proxy_url_value) = &proxy_url
|
||||
&& !command.args.iter().any(|arg| arg.contains("--proxy"))
|
||||
{
|
||||
command.args.push("--proxy".into());
|
||||
command.args.push(proxy_url_value.clone());
|
||||
}
|
||||
|
||||
let connection = crate::acp::connect(
|
||||
name,
|
||||
command,
|
||||
|
||||
@@ -1605,7 +1605,7 @@ impl AcpThreadView {
|
||||
task.shell = shell;
|
||||
|
||||
let terminal = terminal_panel.update_in(cx, |terminal_panel, window, cx| {
|
||||
terminal_panel.spawn_task(&login, window, cx)
|
||||
terminal_panel.spawn_task(&task, window, cx)
|
||||
})?;
|
||||
|
||||
let terminal = terminal.await?;
|
||||
|
||||
@@ -119,9 +119,11 @@ struct AutoUpdateSetting(bool);
|
||||
///
|
||||
/// Default: true
|
||||
#[derive(Clone, Copy, Default, JsonSchema, Deserialize, Serialize, SettingsUi, SettingsKey)]
|
||||
#[serde(transparent)]
|
||||
#[settings_key(key = "auto_update")]
|
||||
struct AutoUpdateSettingContent(bool);
|
||||
#[settings_key(None)]
|
||||
#[settings_ui(group = "Auto Update")]
|
||||
struct AutoUpdateSettingContent {
|
||||
pub auto_update: Option<bool>,
|
||||
}
|
||||
|
||||
impl Settings for AutoUpdateSetting {
|
||||
type FileContent = AutoUpdateSettingContent;
|
||||
@@ -134,17 +136,22 @@ impl Settings for AutoUpdateSetting {
|
||||
sources.user,
|
||||
]
|
||||
.into_iter()
|
||||
.find_map(|value| value.copied())
|
||||
.unwrap_or(*sources.default);
|
||||
.find_map(|value| value.and_then(|val| val.auto_update))
|
||||
.or(sources.default.auto_update)
|
||||
.ok_or_else(Self::missing_default)?;
|
||||
|
||||
Ok(Self(auto_update.0))
|
||||
Ok(Self(auto_update))
|
||||
}
|
||||
|
||||
fn import_from_vscode(vscode: &settings::VsCodeSettings, current: &mut Self::FileContent) {
|
||||
let mut cur = &mut Some(*current);
|
||||
vscode.enum_setting("update.mode", &mut cur, |s| match s {
|
||||
"none" | "manual" => Some(AutoUpdateSettingContent(false)),
|
||||
_ => Some(AutoUpdateSettingContent(true)),
|
||||
"none" | "manual" => Some(AutoUpdateSettingContent {
|
||||
auto_update: Some(false),
|
||||
}),
|
||||
_ => Some(AutoUpdateSettingContent {
|
||||
auto_update: Some(true),
|
||||
}),
|
||||
});
|
||||
*current = cur.unwrap();
|
||||
}
|
||||
@@ -557,6 +564,7 @@ impl AutoUpdater {
|
||||
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.status = AutoUpdateStatus::Checking;
|
||||
log::info!("Auto Update: checking for updates");
|
||||
cx.notify();
|
||||
})?;
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ use futures::{
|
||||
channel::oneshot, future::BoxFuture,
|
||||
};
|
||||
use gpui::{App, AsyncApp, Entity, Global, Task, WeakEntity, actions};
|
||||
use http_client::{HttpClient, HttpClientWithUrl, http};
|
||||
use http_client::{HttpClient, HttpClientWithUrl, http, read_proxy_from_env};
|
||||
use parking_lot::RwLock;
|
||||
use postage::watch;
|
||||
use proxy::connect_proxy_stream;
|
||||
@@ -132,6 +132,20 @@ pub struct ProxySettings {
|
||||
pub proxy: Option<String>,
|
||||
}
|
||||
|
||||
impl ProxySettings {
|
||||
pub fn proxy_url(&self) -> Option<Url> {
|
||||
self.proxy
|
||||
.as_ref()
|
||||
.and_then(|input| {
|
||||
input
|
||||
.parse::<Url>()
|
||||
.inspect_err(|e| log::error!("Error parsing proxy settings: {}", e))
|
||||
.ok()
|
||||
})
|
||||
.or_else(read_proxy_from_env)
|
||||
}
|
||||
}
|
||||
|
||||
impl Settings for ProxySettings {
|
||||
type FileContent = ProxySettingsContent;
|
||||
|
||||
|
||||
@@ -3006,6 +3006,12 @@ impl EditorElement {
|
||||
.ilog10()
|
||||
+ 1;
|
||||
|
||||
let git_gutter_width = Self::gutter_strip_width(line_height)
|
||||
+ gutter_dimensions
|
||||
.git_blame_entries_width
|
||||
.unwrap_or_default();
|
||||
let available_width = gutter_dimensions.left_padding - git_gutter_width;
|
||||
|
||||
buffer_rows
|
||||
.iter()
|
||||
.enumerate()
|
||||
@@ -3021,9 +3027,6 @@ impl EditorElement {
|
||||
ExpandExcerptDirection::UpAndDown => IconName::ExpandVertical,
|
||||
};
|
||||
|
||||
let git_gutter_width = Self::gutter_strip_width(line_height);
|
||||
let available_width = gutter_dimensions.left_padding - git_gutter_width;
|
||||
|
||||
let editor = self.editor.clone();
|
||||
let is_wide = max_line_number_length
|
||||
>= EditorSettings::get_global(cx).gutter.min_line_number_digits as u32
|
||||
|
||||
@@ -293,7 +293,7 @@ impl StatusItemView for CursorPosition {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Default, PartialEq, JsonSchema, Deserialize, Serialize)]
|
||||
#[derive(Clone, Copy, Default, PartialEq, Debug, JsonSchema, Deserialize, Serialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub(crate) enum LineIndicatorFormat {
|
||||
Short,
|
||||
@@ -301,10 +301,13 @@ pub(crate) enum LineIndicatorFormat {
|
||||
Long,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Default, JsonSchema, Deserialize, Serialize, SettingsUi, SettingsKey)]
|
||||
#[serde(transparent)]
|
||||
#[settings_key(key = "line_indicator_format")]
|
||||
pub(crate) struct LineIndicatorFormatContent(LineIndicatorFormat);
|
||||
#[derive(
|
||||
Clone, Copy, Default, Debug, JsonSchema, Deserialize, Serialize, SettingsUi, SettingsKey,
|
||||
)]
|
||||
#[settings_key(None)]
|
||||
pub(crate) struct LineIndicatorFormatContent {
|
||||
line_indicator_format: Option<LineIndicatorFormat>,
|
||||
}
|
||||
|
||||
impl Settings for LineIndicatorFormat {
|
||||
type FileContent = LineIndicatorFormatContent;
|
||||
@@ -312,14 +315,18 @@ impl Settings for LineIndicatorFormat {
|
||||
fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> anyhow::Result<Self> {
|
||||
let format = [
|
||||
sources.release_channel,
|
||||
sources.operating_system,
|
||||
sources.profile,
|
||||
sources.user,
|
||||
sources.operating_system,
|
||||
Some(sources.default),
|
||||
]
|
||||
.into_iter()
|
||||
.find_map(|value| value.copied())
|
||||
.unwrap_or(*sources.default);
|
||||
.flatten()
|
||||
.filter_map(|val| val.line_indicator_format)
|
||||
.next()
|
||||
.ok_or_else(Self::missing_default)?;
|
||||
|
||||
Ok(format.0)
|
||||
Ok(format)
|
||||
}
|
||||
|
||||
fn import_from_vscode(_vscode: &settings::VsCodeSettings, _current: &mut Self::FileContent) {}
|
||||
|
||||
@@ -82,6 +82,10 @@ unsafe fn build_classes() {
|
||||
APP_DELEGATE_CLASS = unsafe {
|
||||
let mut decl = ClassDecl::new("GPUIApplicationDelegate", class!(NSResponder)).unwrap();
|
||||
decl.add_ivar::<*mut c_void>(MAC_PLATFORM_IVAR);
|
||||
decl.add_method(
|
||||
sel!(applicationWillFinishLaunching:),
|
||||
will_finish_launching as extern "C" fn(&mut Object, Sel, id),
|
||||
);
|
||||
decl.add_method(
|
||||
sel!(applicationDidFinishLaunching:),
|
||||
did_finish_launching as extern "C" fn(&mut Object, Sel, id),
|
||||
@@ -1356,6 +1360,25 @@ unsafe fn get_mac_platform(object: &mut Object) -> &MacPlatform {
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" fn will_finish_launching(_this: &mut Object, _: Sel, _: id) {
|
||||
unsafe {
|
||||
let user_defaults: id = msg_send![class!(NSUserDefaults), standardUserDefaults];
|
||||
let defaults_dict: id = msg_send![class!(NSMutableDictionary), dictionary];
|
||||
|
||||
// The autofill heuristic controller causes slowdown and high CPU usage.
|
||||
// We don't know exactly why. This disables the full heuristic controller.
|
||||
//
|
||||
// Adapted from: https://github.com/ghostty-org/ghostty/pull/8625
|
||||
let false_value: id = msg_send![class!(NSNumber), numberWithBool:false];
|
||||
let _: () = msg_send![defaults_dict,
|
||||
setObject: false_value
|
||||
forKey: ns_string("NSAutoFillHeuristicControllerEnabled")
|
||||
];
|
||||
|
||||
let _: () = msg_send![user_defaults, registerDefaults:defaults_dict];
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" fn did_finish_launching(this: &mut Object, _: Sel, _: id) {
|
||||
unsafe {
|
||||
let app: id = msg_send![APP_CLASS, sharedApplication];
|
||||
|
||||
@@ -318,6 +318,12 @@ pub fn read_proxy_from_env() -> Option<Url> {
|
||||
.and_then(|env| env.parse().ok())
|
||||
}
|
||||
|
||||
pub fn read_no_proxy_from_env() -> Option<String> {
|
||||
const ENV_VARS: &[&str] = &["NO_PROXY", "no_proxy"];
|
||||
|
||||
ENV_VARS.iter().find_map(|var| std::env::var(var).ok())
|
||||
}
|
||||
|
||||
pub struct BlockedHttpClient;
|
||||
|
||||
impl BlockedHttpClient {
|
||||
|
||||
@@ -57,7 +57,7 @@ use std::{
|
||||
str,
|
||||
sync::{
|
||||
Arc, LazyLock,
|
||||
atomic::{AtomicU64, AtomicUsize, Ordering::SeqCst},
|
||||
atomic::{AtomicUsize, Ordering::SeqCst},
|
||||
},
|
||||
};
|
||||
use syntax_map::{QueryCursorHandle, SyntaxSnapshot};
|
||||
@@ -166,7 +166,6 @@ pub struct CachedLspAdapter {
|
||||
pub disk_based_diagnostics_progress_token: Option<String>,
|
||||
language_ids: HashMap<LanguageName, String>,
|
||||
pub adapter: Arc<dyn LspAdapter>,
|
||||
pub reinstall_attempt_count: AtomicU64,
|
||||
cached_binary: futures::lock::Mutex<Option<LanguageServerBinary>>,
|
||||
}
|
||||
|
||||
@@ -183,7 +182,6 @@ impl Debug for CachedLspAdapter {
|
||||
&self.disk_based_diagnostics_progress_token,
|
||||
)
|
||||
.field("language_ids", &self.language_ids)
|
||||
.field("reinstall_attempt_count", &self.reinstall_attempt_count)
|
||||
.finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
@@ -202,7 +200,6 @@ impl CachedLspAdapter {
|
||||
language_ids,
|
||||
adapter,
|
||||
cached_binary: Default::default(),
|
||||
reinstall_attempt_count: AtomicU64::new(0),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -30,6 +30,10 @@ impl BasedPyrightBanner {
|
||||
_subscriptions: [subscription],
|
||||
}
|
||||
}
|
||||
|
||||
fn onboarding_banner_enabled(&self) -> bool {
|
||||
!self.dismissed && self.have_basedpyright
|
||||
}
|
||||
}
|
||||
|
||||
impl EventEmitter<ToolbarItemEvent> for BasedPyrightBanner {}
|
||||
@@ -38,7 +42,7 @@ 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| {
|
||||
.when(self.onboarding_banner_enabled(), |el| {
|
||||
el.child(
|
||||
Banner::new()
|
||||
.child(
|
||||
@@ -81,6 +85,9 @@ impl ToolbarItemView for BasedPyrightBanner {
|
||||
_window: &mut ui::Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> ToolbarItemLocation {
|
||||
if !self.onboarding_banner_enabled() {
|
||||
return ToolbarItemLocation::Hidden;
|
||||
}
|
||||
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))
|
||||
|
||||
@@ -89,7 +89,7 @@ pub fn init(languages: Arc<LanguageRegistry>, node: NodeRuntime, cx: &mut App) {
|
||||
let py_lsp_adapter = Arc::new(python::PyLspAdapter::new());
|
||||
let python_context_provider = Arc::new(python::PythonContextProvider);
|
||||
let python_lsp_adapter = Arc::new(python::PythonLspAdapter::new(node.clone()));
|
||||
let basedpyright_lsp_adapter = Arc::new(BasedPyrightLspAdapter::new());
|
||||
let basedpyright_lsp_adapter = Arc::new(BasedPyrightLspAdapter::new(node.clone()));
|
||||
let python_toolchain_provider = Arc::new(python::PythonToolchainProvider);
|
||||
let rust_context_provider = Arc::new(rust::RustContextProvider);
|
||||
let rust_lsp_adapter = Arc::new(rust::RustLspAdapter);
|
||||
|
||||
@@ -29,7 +29,6 @@ use std::str::FromStr;
|
||||
use std::{
|
||||
any::Any,
|
||||
borrow::Cow,
|
||||
ffi::OsString,
|
||||
fmt::Write,
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
@@ -63,9 +62,6 @@ impl ManifestProvider for PyprojectTomlManifestProvider {
|
||||
}
|
||||
}
|
||||
|
||||
const SERVER_PATH: &str = "node_modules/pyright/langserver.index.js";
|
||||
const NODE_MODULE_RELATIVE_SERVER_PATH: &str = "pyright/langserver.index.js";
|
||||
|
||||
enum TestRunner {
|
||||
UNITTEST,
|
||||
PYTEST,
|
||||
@@ -83,10 +79,6 @@ impl FromStr for TestRunner {
|
||||
}
|
||||
}
|
||||
|
||||
fn server_binary_arguments(server_path: &Path) -> Vec<OsString> {
|
||||
vec![server_path.into(), "--stdio".into()]
|
||||
}
|
||||
|
||||
/// Pyright assigns each completion item a `sortText` of the form `XX.YYYY.name`.
|
||||
/// Where `XX` is the sorting category, `YYYY` is based on most recent usage,
|
||||
/// and `name` is the symbol name itself.
|
||||
@@ -105,10 +97,29 @@ pub struct PythonLspAdapter {
|
||||
|
||||
impl PythonLspAdapter {
|
||||
const SERVER_NAME: LanguageServerName = LanguageServerName::new_static("pyright");
|
||||
const SERVER_PATH: &str = "node_modules/pyright/langserver.index.js";
|
||||
const NODE_MODULE_RELATIVE_SERVER_PATH: &str = "pyright/langserver.index.js";
|
||||
|
||||
pub fn new(node: NodeRuntime) -> Self {
|
||||
PythonLspAdapter { node }
|
||||
}
|
||||
|
||||
async fn get_cached_server_binary(
|
||||
container_dir: PathBuf,
|
||||
node: &NodeRuntime,
|
||||
) -> Option<LanguageServerBinary> {
|
||||
let server_path = container_dir.join(Self::SERVER_PATH);
|
||||
if server_path.exists() {
|
||||
Some(LanguageServerBinary {
|
||||
path: node.binary_path().await.log_err()?,
|
||||
env: None,
|
||||
arguments: vec![server_path.into(), "--stdio".into()],
|
||||
})
|
||||
} else {
|
||||
log::error!("missing executable in directory {:?}", server_path);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait(?Send)]
|
||||
@@ -155,13 +166,13 @@ impl LspAdapter for PythonLspAdapter {
|
||||
.await
|
||||
.log_err()??;
|
||||
|
||||
let path = node_modules_path.join(NODE_MODULE_RELATIVE_SERVER_PATH);
|
||||
let path = node_modules_path.join(Self::NODE_MODULE_RELATIVE_SERVER_PATH);
|
||||
|
||||
let env = delegate.shell_env().await;
|
||||
Some(LanguageServerBinary {
|
||||
path: node,
|
||||
env: Some(env),
|
||||
arguments: server_binary_arguments(&path),
|
||||
arguments: vec![path.into(), "--stdio".into()],
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -185,7 +196,7 @@ impl LspAdapter for PythonLspAdapter {
|
||||
delegate: &dyn LspAdapterDelegate,
|
||||
) -> Result<LanguageServerBinary> {
|
||||
let latest_version = latest_version.downcast::<String>().unwrap();
|
||||
let server_path = container_dir.join(SERVER_PATH);
|
||||
let server_path = container_dir.join(Self::SERVER_PATH);
|
||||
|
||||
self.node
|
||||
.npm_install_packages(
|
||||
@@ -198,7 +209,7 @@ impl LspAdapter for PythonLspAdapter {
|
||||
Ok(LanguageServerBinary {
|
||||
path: self.node.binary_path().await?,
|
||||
env: Some(env),
|
||||
arguments: server_binary_arguments(&server_path),
|
||||
arguments: vec![server_path.into(), "--stdio".into()],
|
||||
})
|
||||
}
|
||||
|
||||
@@ -209,7 +220,7 @@ impl LspAdapter for PythonLspAdapter {
|
||||
delegate: &dyn LspAdapterDelegate,
|
||||
) -> Option<LanguageServerBinary> {
|
||||
let version = version.downcast_ref::<String>().unwrap();
|
||||
let server_path = container_dir.join(SERVER_PATH);
|
||||
let server_path = container_dir.join(Self::SERVER_PATH);
|
||||
|
||||
let should_install_language_server = self
|
||||
.node
|
||||
@@ -228,7 +239,7 @@ impl LspAdapter for PythonLspAdapter {
|
||||
Some(LanguageServerBinary {
|
||||
path: self.node.binary_path().await.ok()?,
|
||||
env: Some(env),
|
||||
arguments: server_binary_arguments(&server_path),
|
||||
arguments: vec![server_path.into(), "--stdio".into()],
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -238,7 +249,7 @@ impl LspAdapter for PythonLspAdapter {
|
||||
container_dir: PathBuf,
|
||||
delegate: &dyn LspAdapterDelegate,
|
||||
) -> Option<LanguageServerBinary> {
|
||||
let mut binary = get_cached_server_binary(container_dir, &self.node).await?;
|
||||
let mut binary = Self::get_cached_server_binary(container_dir, &self.node).await?;
|
||||
binary.env = Some(delegate.shell_env().await);
|
||||
Some(binary)
|
||||
}
|
||||
@@ -392,23 +403,6 @@ impl LspAdapter for PythonLspAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_cached_server_binary(
|
||||
container_dir: PathBuf,
|
||||
node: &NodeRuntime,
|
||||
) -> Option<LanguageServerBinary> {
|
||||
let server_path = container_dir.join(SERVER_PATH);
|
||||
if server_path.exists() {
|
||||
Some(LanguageServerBinary {
|
||||
path: node.binary_path().await.log_err()?,
|
||||
env: None,
|
||||
arguments: server_binary_arguments(&server_path),
|
||||
})
|
||||
} else {
|
||||
log::error!("missing executable in directory {:?}", server_path);
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct PythonContextProvider;
|
||||
|
||||
const PYTHON_TEST_TARGET_TASK_VARIABLE: VariableName =
|
||||
@@ -1181,6 +1175,10 @@ impl LspAdapter for PyLspAdapter {
|
||||
"pylsp-mypy installation failed"
|
||||
);
|
||||
let pylsp = venv.join(BINARY_DIR).join("pylsp");
|
||||
delegate
|
||||
.which(pylsp.as_os_str())
|
||||
.await
|
||||
.context("pylsp installation was incomplete")?;
|
||||
Ok(LanguageServerBinary {
|
||||
path: pylsp,
|
||||
env: None,
|
||||
@@ -1195,6 +1193,7 @@ impl LspAdapter for PyLspAdapter {
|
||||
) -> Option<LanguageServerBinary> {
|
||||
let venv = self.base_venv(delegate).await.ok()?;
|
||||
let pylsp = venv.join(BINARY_DIR).join("pylsp");
|
||||
delegate.which(pylsp.as_os_str()).await?;
|
||||
Some(LanguageServerBinary {
|
||||
path: pylsp,
|
||||
env: None,
|
||||
@@ -1335,62 +1334,34 @@ impl LspAdapter for PyLspAdapter {
|
||||
}
|
||||
|
||||
pub(crate) struct BasedPyrightLspAdapter {
|
||||
python_venv_base: OnceCell<Result<Arc<Path>, String>>,
|
||||
node: NodeRuntime,
|
||||
}
|
||||
|
||||
impl BasedPyrightLspAdapter {
|
||||
const SERVER_NAME: LanguageServerName = LanguageServerName::new_static("basedpyright");
|
||||
const BINARY_NAME: &'static str = "basedpyright-langserver";
|
||||
const SERVER_PATH: &str = "node_modules/basedpyright/langserver.index.js";
|
||||
const NODE_MODULE_RELATIVE_SERVER_PATH: &str = "basedpyright/langserver.index.js";
|
||||
|
||||
pub(crate) fn new() -> Self {
|
||||
Self {
|
||||
python_venv_base: OnceCell::new(),
|
||||
}
|
||||
pub(crate) fn new(node: NodeRuntime) -> Self {
|
||||
BasedPyrightLspAdapter { node }
|
||||
}
|
||||
|
||||
async fn ensure_venv(delegate: &dyn LspAdapterDelegate) -> Result<Arc<Path>> {
|
||||
let python_path = Self::find_base_python(delegate)
|
||||
.await
|
||||
.context("Could not find Python installation for basedpyright")?;
|
||||
let work_dir = delegate
|
||||
.language_server_download_dir(&Self::SERVER_NAME)
|
||||
.await
|
||||
.context("Could not get working directory for basedpyright")?;
|
||||
let mut path = PathBuf::from(work_dir.as_ref());
|
||||
path.push("basedpyright-venv");
|
||||
if !path.exists() {
|
||||
util::command::new_smol_command(python_path)
|
||||
.arg("-m")
|
||||
.arg("venv")
|
||||
.arg("basedpyright-venv")
|
||||
.current_dir(work_dir)
|
||||
.spawn()?
|
||||
.output()
|
||||
.await?;
|
||||
}
|
||||
|
||||
Ok(path.into())
|
||||
}
|
||||
|
||||
// Find "baseline", user python version from which we'll create our own venv.
|
||||
async fn find_base_python(delegate: &dyn LspAdapterDelegate) -> Option<PathBuf> {
|
||||
for path in ["python3", "python"] {
|
||||
if let Some(path) = delegate.which(path.as_ref()).await {
|
||||
return Some(path);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
async fn base_venv(&self, delegate: &dyn LspAdapterDelegate) -> Result<Arc<Path>, String> {
|
||||
self.python_venv_base
|
||||
.get_or_init(move || async move {
|
||||
Self::ensure_venv(delegate)
|
||||
.await
|
||||
.map_err(|e| format!("{e}"))
|
||||
async fn get_cached_server_binary(
|
||||
container_dir: PathBuf,
|
||||
node: &NodeRuntime,
|
||||
) -> Option<LanguageServerBinary> {
|
||||
let server_path = container_dir.join(Self::SERVER_PATH);
|
||||
if server_path.exists() {
|
||||
Some(LanguageServerBinary {
|
||||
path: node.binary_path().await.log_err()?,
|
||||
env: None,
|
||||
arguments: vec![server_path.into(), "--stdio".into()],
|
||||
})
|
||||
.await
|
||||
.clone()
|
||||
} else {
|
||||
log::error!("missing executable in directory {:?}", server_path);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1400,6 +1371,115 @@ impl LspAdapter for BasedPyrightLspAdapter {
|
||||
Self::SERVER_NAME
|
||||
}
|
||||
|
||||
async fn fetch_latest_server_version(
|
||||
&self,
|
||||
_: &dyn LspAdapterDelegate,
|
||||
_: &AsyncApp,
|
||||
) -> Result<Box<dyn 'static + Any + Send>> {
|
||||
Ok(Box::new(
|
||||
self.node
|
||||
.npm_package_latest_version(Self::SERVER_NAME.as_ref())
|
||||
.await?,
|
||||
) as Box<_>)
|
||||
}
|
||||
|
||||
async fn check_if_user_installed(
|
||||
&self,
|
||||
delegate: &dyn LspAdapterDelegate,
|
||||
_: Option<Toolchain>,
|
||||
_: &AsyncApp,
|
||||
) -> Option<LanguageServerBinary> {
|
||||
if let Some(path) = delegate.which(Self::BINARY_NAME.as_ref()).await {
|
||||
let env = delegate.shell_env().await;
|
||||
Some(LanguageServerBinary {
|
||||
path,
|
||||
env: Some(env),
|
||||
arguments: vec!["--stdio".into()],
|
||||
})
|
||||
} else {
|
||||
// TODO shouldn't this be self.node.binary_path()?
|
||||
let node = delegate.which("node".as_ref()).await?;
|
||||
let (node_modules_path, _) = delegate
|
||||
.npm_package_installed_version(Self::SERVER_NAME.as_ref())
|
||||
.await
|
||||
.log_err()??;
|
||||
|
||||
let path = node_modules_path.join(Self::NODE_MODULE_RELATIVE_SERVER_PATH);
|
||||
|
||||
let env = delegate.shell_env().await;
|
||||
Some(LanguageServerBinary {
|
||||
path: node,
|
||||
env: Some(env),
|
||||
arguments: vec![path.into(), "--stdio".into()],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async fn fetch_server_binary(
|
||||
&self,
|
||||
latest_version: Box<dyn 'static + Send + Any>,
|
||||
container_dir: PathBuf,
|
||||
delegate: &dyn LspAdapterDelegate,
|
||||
) -> Result<LanguageServerBinary> {
|
||||
let latest_version = latest_version.downcast::<String>().unwrap();
|
||||
let server_path = container_dir.join(Self::SERVER_PATH);
|
||||
|
||||
self.node
|
||||
.npm_install_packages(
|
||||
&container_dir,
|
||||
&[(Self::SERVER_NAME.as_ref(), latest_version.as_str())],
|
||||
)
|
||||
.await?;
|
||||
|
||||
let env = delegate.shell_env().await;
|
||||
Ok(LanguageServerBinary {
|
||||
path: self.node.binary_path().await?,
|
||||
env: Some(env),
|
||||
arguments: vec![server_path.into(), "--stdio".into()],
|
||||
})
|
||||
}
|
||||
|
||||
async fn check_if_version_installed(
|
||||
&self,
|
||||
version: &(dyn 'static + Send + Any),
|
||||
container_dir: &PathBuf,
|
||||
delegate: &dyn LspAdapterDelegate,
|
||||
) -> Option<LanguageServerBinary> {
|
||||
let version = version.downcast_ref::<String>().unwrap();
|
||||
let server_path = container_dir.join(Self::SERVER_PATH);
|
||||
|
||||
let should_install_language_server = self
|
||||
.node
|
||||
.should_install_npm_package(
|
||||
Self::SERVER_NAME.as_ref(),
|
||||
&server_path,
|
||||
container_dir,
|
||||
VersionStrategy::Latest(version),
|
||||
)
|
||||
.await;
|
||||
|
||||
if should_install_language_server {
|
||||
None
|
||||
} else {
|
||||
let env = delegate.shell_env().await;
|
||||
Some(LanguageServerBinary {
|
||||
path: self.node.binary_path().await.ok()?,
|
||||
env: Some(env),
|
||||
arguments: vec![server_path.into(), "--stdio".into()],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async fn cached_server_binary(
|
||||
&self,
|
||||
container_dir: PathBuf,
|
||||
delegate: &dyn LspAdapterDelegate,
|
||||
) -> Option<LanguageServerBinary> {
|
||||
let mut binary = Self::get_cached_server_binary(container_dir, &self.node).await?;
|
||||
binary.env = Some(delegate.shell_env().await);
|
||||
Some(binary)
|
||||
}
|
||||
|
||||
async fn initialization_options(
|
||||
self: Arc<Self>,
|
||||
_: &dyn Fs,
|
||||
@@ -1418,80 +1498,6 @@ impl LspAdapter for BasedPyrightLspAdapter {
|
||||
})))
|
||||
}
|
||||
|
||||
async fn check_if_user_installed(
|
||||
&self,
|
||||
delegate: &dyn LspAdapterDelegate,
|
||||
toolchain: Option<Toolchain>,
|
||||
_: &AsyncApp,
|
||||
) -> Option<LanguageServerBinary> {
|
||||
if let Some(bin) = delegate.which(Self::BINARY_NAME.as_ref()).await {
|
||||
let env = delegate.shell_env().await;
|
||||
Some(LanguageServerBinary {
|
||||
path: bin,
|
||||
env: Some(env),
|
||||
arguments: vec!["--stdio".into()],
|
||||
})
|
||||
} else {
|
||||
let path = Path::new(toolchain?.path.as_ref())
|
||||
.parent()?
|
||||
.join(Self::BINARY_NAME);
|
||||
path.exists().then(|| LanguageServerBinary {
|
||||
path,
|
||||
arguments: vec!["--stdio".into()],
|
||||
env: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async fn fetch_latest_server_version(
|
||||
&self,
|
||||
_: &dyn LspAdapterDelegate,
|
||||
_: &AsyncApp,
|
||||
) -> Result<Box<dyn 'static + Any + Send>> {
|
||||
Ok(Box::new(()) as Box<_>)
|
||||
}
|
||||
|
||||
async fn fetch_server_binary(
|
||||
&self,
|
||||
_latest_version: Box<dyn 'static + Send + Any>,
|
||||
_container_dir: PathBuf,
|
||||
delegate: &dyn LspAdapterDelegate,
|
||||
) -> Result<LanguageServerBinary> {
|
||||
let venv = self.base_venv(delegate).await.map_err(|e| anyhow!(e))?;
|
||||
let pip_path = venv.join(BINARY_DIR).join("pip3");
|
||||
ensure!(
|
||||
util::command::new_smol_command(pip_path.as_path())
|
||||
.arg("install")
|
||||
.arg("basedpyright")
|
||||
.arg("-U")
|
||||
.output()
|
||||
.await?
|
||||
.status
|
||||
.success(),
|
||||
"basedpyright installation failed"
|
||||
);
|
||||
let pylsp = venv.join(BINARY_DIR).join(Self::BINARY_NAME);
|
||||
Ok(LanguageServerBinary {
|
||||
path: pylsp,
|
||||
env: None,
|
||||
arguments: vec!["--stdio".into()],
|
||||
})
|
||||
}
|
||||
|
||||
async fn cached_server_binary(
|
||||
&self,
|
||||
_container_dir: PathBuf,
|
||||
delegate: &dyn LspAdapterDelegate,
|
||||
) -> Option<LanguageServerBinary> {
|
||||
let venv = self.base_venv(delegate).await.ok()?;
|
||||
let pylsp = venv.join(BINARY_DIR).join(Self::BINARY_NAME);
|
||||
Some(LanguageServerBinary {
|
||||
path: pylsp,
|
||||
env: None,
|
||||
arguments: vec!["--stdio".into()],
|
||||
})
|
||||
}
|
||||
|
||||
async fn process_completions(&self, items: &mut [lsp::CompletionItem]) {
|
||||
process_pyright_completions(items);
|
||||
}
|
||||
|
||||
@@ -234,7 +234,7 @@ impl AgentServerStore {
|
||||
let subscription = cx.observe_global::<SettingsStore>(|this, cx| {
|
||||
this.agent_servers_settings_changed(cx);
|
||||
});
|
||||
let this = Self {
|
||||
let mut this = Self {
|
||||
state: AgentServerStoreState::Local {
|
||||
node_runtime,
|
||||
fs,
|
||||
@@ -245,14 +245,7 @@ impl AgentServerStore {
|
||||
},
|
||||
external_agents: Default::default(),
|
||||
};
|
||||
cx.spawn(async move |this, cx| {
|
||||
cx.background_executor().timer(Duration::from_secs(1)).await;
|
||||
this.update(cx, |this, cx| {
|
||||
this.agent_servers_settings_changed(cx);
|
||||
})
|
||||
.ok();
|
||||
})
|
||||
.detach();
|
||||
this.agent_servers_settings_changed(cx);
|
||||
this
|
||||
}
|
||||
|
||||
@@ -305,22 +298,29 @@ impl AgentServerStore {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn shared(&mut self, project_id: u64, client: AnyProtoClient) {
|
||||
pub fn shared(&mut self, project_id: u64, client: AnyProtoClient, cx: &mut Context<Self>) {
|
||||
match &mut self.state {
|
||||
AgentServerStoreState::Local {
|
||||
downstream_client, ..
|
||||
} => {
|
||||
client
|
||||
.send(proto::ExternalAgentsUpdated {
|
||||
project_id,
|
||||
names: self
|
||||
.external_agents
|
||||
*downstream_client = Some((project_id, client.clone()));
|
||||
// Send the current list of external agents downstream, but only after a delay,
|
||||
// to avoid having the message arrive before the downstream project's agent server store
|
||||
// sets up its handlers.
|
||||
cx.spawn(async move |this, cx| {
|
||||
cx.background_executor().timer(Duration::from_secs(1)).await;
|
||||
let names = this.update(cx, |this, _| {
|
||||
this.external_agents
|
||||
.keys()
|
||||
.map(|name| name.to_string())
|
||||
.collect(),
|
||||
})
|
||||
.log_err();
|
||||
*downstream_client = Some((project_id, client));
|
||||
.collect()
|
||||
})?;
|
||||
client
|
||||
.send(proto::ExternalAgentsUpdated { project_id, names })
|
||||
.log_err();
|
||||
anyhow::Ok(())
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
AgentServerStoreState::Remote { .. } => {
|
||||
debug_panic!(
|
||||
@@ -721,11 +721,6 @@ struct RemoteExternalAgentServer {
|
||||
new_version_available_tx: Option<watch::Sender<Option<String>>>,
|
||||
}
|
||||
|
||||
// new method: status_updated
|
||||
// does nothing in the all-local case
|
||||
// for RemoteExternalAgentServer, sends on the stored tx
|
||||
// etc.
|
||||
|
||||
impl ExternalAgentServer for RemoteExternalAgentServer {
|
||||
fn get_command(
|
||||
&mut self,
|
||||
|
||||
@@ -257,7 +257,7 @@ impl EventEmitter<ConflictSetUpdate> for ConflictSet {}
|
||||
mod tests {
|
||||
use std::{path::Path, sync::mpsc};
|
||||
|
||||
use crate::{Project, project_settings::ProjectSettings};
|
||||
use crate::Project;
|
||||
|
||||
use super::*;
|
||||
use fs::FakeFs;
|
||||
@@ -484,7 +484,7 @@ mod tests {
|
||||
cx.update(|cx| {
|
||||
settings::init(cx);
|
||||
WorktreeSettings::register(cx);
|
||||
ProjectSettings::register(cx);
|
||||
Project::init_settings(cx);
|
||||
AllLanguageSettings::register(cx);
|
||||
});
|
||||
let initial_text = "
|
||||
@@ -585,7 +585,7 @@ mod tests {
|
||||
cx.update(|cx| {
|
||||
settings::init(cx);
|
||||
WorktreeSettings::register(cx);
|
||||
ProjectSettings::register(cx);
|
||||
Project::init_settings(cx);
|
||||
AllLanguageSettings::register(cx);
|
||||
});
|
||||
|
||||
|
||||
@@ -1012,6 +1012,8 @@ impl settings::Settings for DisableAiSettings {
|
||||
.iter()
|
||||
.chain(sources.user.iter())
|
||||
.chain(sources.server.iter())
|
||||
.chain(sources.release_channel.iter())
|
||||
.chain(sources.profile.iter())
|
||||
.any(|disabled| disabled.disable_ai == Some(true));
|
||||
|
||||
Ok(Self { disable_ai })
|
||||
|
||||
@@ -198,7 +198,7 @@ impl HeadlessProject {
|
||||
let agent_server_store = cx.new(|cx| {
|
||||
let mut agent_server_store =
|
||||
AgentServerStore::local(node_runtime.clone(), fs.clone(), environment, cx);
|
||||
agent_server_store.shared(REMOTE_SERVER_PROJECT_ID, session.clone());
|
||||
agent_server_store.shared(REMOTE_SERVER_PROJECT_ID, session.clone(), cx);
|
||||
agent_server_store
|
||||
});
|
||||
|
||||
|
||||
@@ -678,8 +678,7 @@ impl KeymapFile {
|
||||
None,
|
||||
index,
|
||||
tab_size,
|
||||
)
|
||||
.context("Failed to remove keybinding")?;
|
||||
);
|
||||
keymap_contents.replace_range(replace_range, &replace_value);
|
||||
return Ok(keymap_contents);
|
||||
}
|
||||
@@ -699,16 +698,14 @@ impl KeymapFile {
|
||||
// if we are only changing the keybinding (common case)
|
||||
// not the context, etc. Then just update the binding in place
|
||||
|
||||
let (replace_range, replace_value) =
|
||||
replace_top_level_array_value_in_json_text(
|
||||
&keymap_contents,
|
||||
&["bindings", keystrokes_str],
|
||||
Some(&source_action_value),
|
||||
Some(&source.keystrokes_unparsed()),
|
||||
index,
|
||||
tab_size,
|
||||
)
|
||||
.context("Failed to replace keybinding")?;
|
||||
let (replace_range, replace_value) = replace_top_level_array_value_in_json_text(
|
||||
&keymap_contents,
|
||||
&["bindings", keystrokes_str],
|
||||
Some(&source_action_value),
|
||||
Some(&source.keystrokes_unparsed()),
|
||||
index,
|
||||
tab_size,
|
||||
);
|
||||
keymap_contents.replace_range(replace_range, &replace_value);
|
||||
|
||||
return Ok(keymap_contents);
|
||||
@@ -721,28 +718,24 @@ impl KeymapFile {
|
||||
// just update the section in place, updating the context
|
||||
// and the binding
|
||||
|
||||
let (replace_range, replace_value) =
|
||||
replace_top_level_array_value_in_json_text(
|
||||
&keymap_contents,
|
||||
&["bindings", keystrokes_str],
|
||||
Some(&source_action_value),
|
||||
Some(&source.keystrokes_unparsed()),
|
||||
index,
|
||||
tab_size,
|
||||
)
|
||||
.context("Failed to replace keybinding")?;
|
||||
let (replace_range, replace_value) = replace_top_level_array_value_in_json_text(
|
||||
&keymap_contents,
|
||||
&["bindings", keystrokes_str],
|
||||
Some(&source_action_value),
|
||||
Some(&source.keystrokes_unparsed()),
|
||||
index,
|
||||
tab_size,
|
||||
);
|
||||
keymap_contents.replace_range(replace_range, &replace_value);
|
||||
|
||||
let (replace_range, replace_value) =
|
||||
replace_top_level_array_value_in_json_text(
|
||||
&keymap_contents,
|
||||
&["context"],
|
||||
source.context.map(Into::into).as_ref(),
|
||||
None,
|
||||
index,
|
||||
tab_size,
|
||||
)
|
||||
.context("Failed to replace keybinding")?;
|
||||
let (replace_range, replace_value) = replace_top_level_array_value_in_json_text(
|
||||
&keymap_contents,
|
||||
&["context"],
|
||||
source.context.map(Into::into).as_ref(),
|
||||
None,
|
||||
index,
|
||||
tab_size,
|
||||
);
|
||||
keymap_contents.replace_range(replace_range, &replace_value);
|
||||
return Ok(keymap_contents);
|
||||
} else {
|
||||
@@ -751,16 +744,14 @@ impl KeymapFile {
|
||||
// section, then treat this operation as an add operation of the
|
||||
// new binding with the updated context.
|
||||
|
||||
let (replace_range, replace_value) =
|
||||
replace_top_level_array_value_in_json_text(
|
||||
&keymap_contents,
|
||||
&["bindings", keystrokes_str],
|
||||
None,
|
||||
None,
|
||||
index,
|
||||
tab_size,
|
||||
)
|
||||
.context("Failed to replace keybinding")?;
|
||||
let (replace_range, replace_value) = replace_top_level_array_value_in_json_text(
|
||||
&keymap_contents,
|
||||
&["bindings", keystrokes_str],
|
||||
None,
|
||||
None,
|
||||
index,
|
||||
tab_size,
|
||||
);
|
||||
keymap_contents.replace_range(replace_range, &replace_value);
|
||||
operation = KeybindUpdateOperation::Add {
|
||||
source,
|
||||
@@ -811,7 +802,7 @@ impl KeymapFile {
|
||||
&keymap_contents,
|
||||
&value.into(),
|
||||
tab_size,
|
||||
)?;
|
||||
);
|
||||
keymap_contents.replace_range(replace_range, &replace_value);
|
||||
}
|
||||
return Ok(keymap_contents);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -43,12 +43,18 @@ impl Settings for VimModeSetting {
|
||||
|
||||
fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
|
||||
Ok(Self(
|
||||
sources
|
||||
.user
|
||||
.and_then(|mode| mode.vim_mode)
|
||||
.or(sources.server.and_then(|mode| mode.vim_mode))
|
||||
.or(sources.default.vim_mode)
|
||||
.ok_or_else(Self::missing_default)?,
|
||||
[
|
||||
sources.profile,
|
||||
sources.release_channel,
|
||||
sources.user,
|
||||
sources.server,
|
||||
Some(sources.default),
|
||||
]
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.filter_map(|mode| mode.vim_mode)
|
||||
.next()
|
||||
.ok_or_else(Self::missing_default)?,
|
||||
))
|
||||
}
|
||||
|
||||
@@ -86,12 +92,18 @@ impl Settings for HelixModeSetting {
|
||||
|
||||
fn load(sources: SettingsSources<Self::FileContent>, _: &mut App) -> Result<Self> {
|
||||
Ok(Self(
|
||||
sources
|
||||
.user
|
||||
.and_then(|mode| mode.helix_mode)
|
||||
.or(sources.server.and_then(|mode| mode.helix_mode))
|
||||
.or(sources.default.helix_mode)
|
||||
.ok_or_else(Self::missing_default)?,
|
||||
[
|
||||
sources.profile,
|
||||
sources.release_channel,
|
||||
sources.user,
|
||||
sources.server,
|
||||
Some(sources.default),
|
||||
]
|
||||
.into_iter()
|
||||
.flatten()
|
||||
.filter_map(|mode| mode.helix_mode)
|
||||
.next()
|
||||
.ok_or_else(Self::missing_default)?,
|
||||
))
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
description = "The fast, collaborative code editor."
|
||||
edition.workspace = true
|
||||
name = "zed"
|
||||
version = "0.204.0"
|
||||
version = "0.204.5"
|
||||
publish.workspace = true
|
||||
license = "GPL-3.0-or-later"
|
||||
authors = ["Zed Team <hi@zed.dev>"]
|
||||
|
||||
@@ -1 +1 @@
|
||||
dev
|
||||
stable
|
||||
@@ -19,7 +19,6 @@ use git::GitHostingProviderRegistry;
|
||||
use gpui::{App, AppContext, Application, AsyncApp, Focusable as _, UpdateGlobal as _};
|
||||
|
||||
use gpui_tokio::Tokio;
|
||||
use http_client::{Url, read_proxy_from_env};
|
||||
use language::LanguageRegistry;
|
||||
use onboarding::{FIRST_OPEN, show_onboarding_view};
|
||||
use prompt_store::PromptBuilder;
|
||||
@@ -405,16 +404,7 @@ pub fn main() {
|
||||
std::env::consts::OS,
|
||||
std::env::consts::ARCH
|
||||
);
|
||||
let proxy_str = ProxySettings::get_global(cx).proxy.to_owned();
|
||||
let proxy_url = proxy_str
|
||||
.as_ref()
|
||||
.and_then(|input| {
|
||||
input
|
||||
.parse::<Url>()
|
||||
.inspect_err(|e| log::error!("Error parsing proxy settings: {}", e))
|
||||
.ok()
|
||||
})
|
||||
.or_else(read_proxy_from_env);
|
||||
let proxy_url = ProxySettings::get_global(cx).proxy_url();
|
||||
let http = {
|
||||
let _guard = Tokio::handle(cx).enter();
|
||||
|
||||
|
||||
@@ -202,22 +202,48 @@ fn check_pattern(pattern: &[PatternPart], input: &str) -> bool {
|
||||
match_any_chars.end += part.match_any_chars.end;
|
||||
continue;
|
||||
}
|
||||
let search_range_start = input_ix.saturating_sub(match_any_chars.end + part.text.len());
|
||||
let search_range_end = input_ix.saturating_sub(match_any_chars.start);
|
||||
let found_ix = &input[search_range_start..search_range_end].rfind(&part.text);
|
||||
|
||||
let search_range_end = n_chars_before_offset(match_any_chars.start, input_ix, input);
|
||||
let search_range_start = n_chars_before_offset(
|
||||
match_any_chars.len() + part.text.len(),
|
||||
search_range_end,
|
||||
input,
|
||||
);
|
||||
let found_ix = input[search_range_start..search_range_end].rfind(&part.text);
|
||||
|
||||
if let Some(found_ix) = found_ix {
|
||||
input_ix = search_range_start + found_ix;
|
||||
match_any_chars = part.match_any_chars.clone();
|
||||
} else if !part.optional {
|
||||
log::trace!(
|
||||
"Failed to match pattern `...{}` against input `...{}`",
|
||||
&part.text[part.text.len().saturating_sub(128)..],
|
||||
&input[input_ix.saturating_sub(128)..]
|
||||
"Failed to match pattern\n`...{}`\nagainst input\n`...{}`",
|
||||
&part.text[n_chars_before_offset(128, part.text.len(), &part.text)..],
|
||||
&input[n_chars_before_offset(128, search_range_end, input)..search_range_end],
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
match_any_chars.contains(&input_ix)
|
||||
is_char_count_within_range(&input[..input_ix], match_any_chars)
|
||||
}
|
||||
|
||||
fn n_chars_before_offset(char_count: usize, offset: usize, string: &str) -> usize {
|
||||
if char_count == 0 {
|
||||
return offset;
|
||||
}
|
||||
string[..offset]
|
||||
.char_indices()
|
||||
.nth_back(char_count.saturating_sub(1))
|
||||
.map_or(0, |(byte_ix, _)| byte_ix)
|
||||
}
|
||||
|
||||
fn is_char_count_within_range(string: &str, char_count_range: Range<usize>) -> bool {
|
||||
if string.len() >= char_count_range.start * 4 && string.len() < char_count_range.end {
|
||||
return true;
|
||||
}
|
||||
if string.len() < char_count_range.start || string.len() >= char_count_range.end * 4 {
|
||||
return false;
|
||||
}
|
||||
char_count_range.contains(&string.chars().count())
|
||||
}
|
||||
|
||||
/// Canonicalizes license text by removing all non-alphanumeric characters, lowercasing, and turning
|
||||
@@ -360,6 +386,7 @@ impl LicenseDetectionWatcher {
|
||||
mod tests {
|
||||
use fs::FakeFs;
|
||||
use gpui::TestAppContext;
|
||||
use rand::Rng as _;
|
||||
use serde_json::json;
|
||||
use settings::{Settings as _, SettingsStore};
|
||||
use worktree::WorktreeSettings;
|
||||
@@ -578,6 +605,45 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn random_strings_negative_detection() {
|
||||
for _i in 0..20 {
|
||||
let random_string = rand::rng()
|
||||
.sample_iter::<char, _>(rand::distr::StandardUniform)
|
||||
.take(512)
|
||||
.collect::<String>();
|
||||
assert_eq!(detect_license(&random_string), None);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_n_chars_before_offset() {
|
||||
assert_eq!(n_chars_before_offset(2, 4, "hello"), 2);
|
||||
|
||||
let input = "ㄒ乇丂ㄒ";
|
||||
assert_eq!(n_chars_before_offset(2, input.len(), input), "ㄒ乇".len());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_is_char_count_within_range() {
|
||||
// TODO: make this into a proper property test.
|
||||
for _i in 0..20 {
|
||||
let mut rng = rand::rng();
|
||||
let random_char_count = rng.random_range(0..64);
|
||||
let random_string = rand::rng()
|
||||
.sample_iter::<char, _>(rand::distr::StandardUniform)
|
||||
.take(random_char_count)
|
||||
.collect::<String>();
|
||||
let min_chars = rng.random_range(0..10);
|
||||
let max_chars = rng.random_range(min_chars..32);
|
||||
let char_count_range = min_chars..max_chars;
|
||||
assert_eq!(
|
||||
is_char_count_within_range(&random_string, char_count_range.clone()),
|
||||
char_count_range.contains(&random_char_count),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_license_file_name_regex() {
|
||||
// Test basic license file names
|
||||
|
||||
Reference in New Issue
Block a user