Compare commits

...

24 Commits

Author SHA1 Message Date
Joseph T. Lyons
289a4a6aa7 zed 0.204.5 2025-09-22 14:05:00 -04:00
Conrad Irwin
aa8ca12369 Move my keybinding fixes to the right platform (#38654)
In cffb883108 I put the fixed keybindings
on the wrong platform

Release Notes:

- Fix syntax node shortcuts
2025-09-22 10:26:38 -06:00
Finn Evers
8eb3952c12 editor: Properly layout expand toggles with git blame enabled (#38349)
Release Notes:

- Fixed an issue where expand toggles were too large with the git blame
deployed.
2025-09-22 11:24:20 -04:00
Cole Miller
529fb5ff30 zed 0.204.4 2025-09-20 12:55:44 -04:00
Cole Miller
12bc66e6ed Fix spawning ACP login task (for v0.204.x) (#38565)
Release Notes:

- Fixed a bug that prevented authenticating in the terminal with Gemini
and Claude Code.
2025-09-20 12:54:45 -04:00
Cole Miller
7dc833d932 zed 0.204.3 2025-09-19 10:13:44 -04:00
Cole Miller
85cfd726c8 python: Install basedpyright with npm instead of pip (#38471)
Closes #ISSUE

Release Notes:

- N/A *or* Added/Fixed/Improved ...
2025-09-19 10:05:09 -04:00
Joseph T. Lyons
9ae63f04f0 zed 0.204.2 2025-09-18 16:00:22 -04:00
Cole Miller
9bfe7a1798 acp: Fix agent servers sometimes not being registered when Zed starts (#38330)
In local projects, initialize the list of agents in the agent server
store immediately. Previously we were initializing the list only after a
delay, in an attempt to avoid sending the `ExternalAgentsUpdated`
message to the downstream client (if any) before its handlers were
initialized. But we already have a separate codepath for that situation,
in the `AgentServerStore::shared`, and we can insert the delay in that
place instead.

Release Notes:

- acp: Fixed a bug where starting an external agent thread soon after
Zed starts up would show a "not registered" error.

---------

Co-authored-by: Michael <michael@zed.dev>
Co-authored-by: Agus <agus@zed.dev>
2025-09-18 15:56:39 -04:00
Cole Miller
10f172ddac python: Install basedpyright if the basedpyright-langserver binary is missing (#38426)
Potential fix for #38377

Release Notes:

- N/A

---------

Co-authored-by: Peter Tripp <petertripp@gmail.com>
2025-09-18 15:52:13 -04:00
Ben Brandt
a62514df7a acp: Fix behavior of read_text_file for ACP agents (#38401)
We were incorrectly handling the line number as well as stripping out
line breaks when returning portions of files.

It also makes sure following is updated even when we load a snapshot
from cache, which wasn't the case before.

We also are able to load the text via a range in the snapshot, rather
than allocating a string for the entire file and then another after
iterating over lines in the file.

Release Notes:

- acp: Fix incorrect behavior when ACP agents requested to read portions
of files.
2025-09-18 11:55:45 +02:00
Vladimir Varankin
97d04621de Hide BasedPyright banner in toolbar when dismissed (#38135)
This PR fixes the `BasedPyrightBanner`, making sure the banner is
completely hidden in the toolbar, when it was dismissed, or it's not
installed.

Without the fix, the banner still occupies some space in the toolbar,
making the UI looks inconsistent when editing a Python file. The bug is
**especially prominent** when the toolbar is hidden in the user's
settings (see below).

_Banner is shown_
<img width="1470" height="254" alt="Screenshot 2025-09-14 at 11 36 37"
src="https://github.com/user-attachments/assets/1415b075-0660-41ed-8069-c2318ac3a7cf"
/>

_Banner dismissed_
<img width="1470" height="207" alt="Screenshot 2025-09-14 at 11 36 44"
src="https://github.com/user-attachments/assets/828a3fba-5c50-4aba-832c-3e0cc6ed464b"
/>

_Banner dismissed (and the toolbar is hidden)_
<img width="1470" height="177" alt="Screenshot 2025-09-14 at 12 07 25"
src="https://github.com/user-attachments/assets/41aa5861-87df-491f-ac7e-09fc1558dd84"
/>

Closes n/a

Release Notes:

- Fixed the basedpyright onboarding banner
2025-09-18 00:40:13 +02:00
Joseph T. Lyons
4dc0720741 Adjust release notes Discord webhook name (#37997)
Release Notes:

- N/A
2025-09-17 11:23:33 -04:00
Joseph T. Lyons
8127523d0f v0.204.x stable 2025-09-17 10:10:04 -04:00
Smit Barmase
a1c8e69283 macOS: Disable NSAutoFillHeuristicController on macOS 26 (#38179)
Closes #33182

From
https://github.com/zed-industries/zed/issues/33182#issuecomment-3289846957,
thanks @mitchellh.

Release Notes:

- Fixed an issue where scrolling could sometimes feel choppy on macOS
26.
2025-09-16 21:53:09 +05:30
Ben Brandt
3a79ce7312 agent_servers: Set proxy env for all ACP agents (#38247)
- Use ProxySettings::proxy_url to read from settings or env
- Export HTTP(S)_PROXY and NO_PROXY for agent CLIs
- Add read_no_proxy_from_env and move parsing from main

Closes https://github.com/zed-industries/claude-code-acp/issues/46

Release Notes:

- acp: Pass proxy settings through to all ACP agents
2025-09-16 12:28:05 +02:00
Ben Brandt
06e58c318b agent_servers: Let Gemini CLI know it is running in Zed (#38058)
By passing through Zed as the surface, Gemini can know which editor it
is running in.

Release Notes:

- N/A
2025-09-16 12:26:56 +02:00
Conrad Irwin
64adb6d461 Re-add VSCode syntax node motions (#38208)
Closes #ISSUE

Release Notes:

- (preview only) restored ctrl-shift-{left,right} for Larger/Smaller
syntax node. This is VSCode's default and avoids the breaking change
from #37874
2025-09-15 09:18:34 -06:00
Ben Kunkle
69a7c3e252 Ability to update JSON arrays (#38087)
Closes #ISSUE

Adds the ability to our JSON updating code to update arrays within other
objects. Previously updating of arrays was limited to just top level
arrays (i.e. `keymap.json`) however this PR makes it so nested arrays
are supported as well using `#{index}` syntax as a key.

This PR also fixes an issue with the array updating code that meant that
updating empty json values `""` or an empty `keymap.json` file in the
case of the Keymap Editor would fail instead of creating a new array.

Release Notes:

- Fixed an issue where keybindings would fail to save in the Keymap
Editor if the `keymap.json` file was completely empty
2025-09-14 11:55:59 -05:00
Ben Kunkle
41737efb07 Fix line indicator format setting (#38071)
Closes #ISSUE

Release Notes:

- Fixed an issue where the `line_indicator_format` setting would not
update based on the value in `settings.json`
2025-09-12 16:17:22 -04:00
Anthony
608991db39 zed 0.204.1 2025-09-11 19:14:58 -04:00
Anthony Eid
8e0e2a887c Fix auto update not defaulting to true (#38022)
#37337 Made `AutoUpdateSetting` `FileContent =
AutoUpdateSettingsContent` which caused a deserialization bug to occur
because the field it was wrapping wasn't optional. Thus serde would
deserialize the wrapped type `bool` to its default value `false`
stopping the settings load function from reading the correct default
value from `default.json`

I also added a log message that states when the auto updater struct is
checking for updates to make this easier to test.

Release Notes:

- fix auto update defaulting to false
2025-09-11 19:11:33 -04:00
Michael Sloan
d900f12473 Fix panics from unicode slicing in license detection 2025-09-11 14:44:07 -06:00
Joseph T. Lyons
b3729e0b90 v0.204.x preview 2025-09-10 10:08:23 -04:00
31 changed files with 1425 additions and 386 deletions

View File

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

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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(),

View File

@@ -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(),

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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)?,
))
}

View File

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

View File

@@ -1 +1 @@
dev
stable

View File

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

View File

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