Compare commits

..

1 Commits

Author SHA1 Message Date
Antonio Scandurra
4c4519c870 Start on a new approach to assertions in eval
Co-authored-by: Nathan Sobo <nathan@zed.dev>
2025-04-23 20:19:14 +02:00
92 changed files with 1086 additions and 3297 deletions

411
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -605,7 +605,7 @@ wasmtime-wasi = "29"
which = "6.0.0"
wit-component = "0.221"
workspace-hack = "0.1.0"
zed_llm_client = "0.7.0"
zed_llm_client = "0.6.1"
zstd = "0.11"
metal = "0.29"

View File

@@ -433,39 +433,47 @@ fn render_markdown_code_block(
workspace
.update(cx, {
|workspace, cx| {
let Some(project_path) = workspace
if let Some(project_path) = workspace
.project()
.read(cx)
.find_project_path(&path_range.path, cx)
else {
return;
};
let Some(target) = path_range.range.as_ref().map(|range| {
Point::new(
// Line number is 1-based
range.start.line.saturating_sub(1),
range.start.col.unwrap_or(0),
)
}) else {
return;
};
let open_task =
workspace.open_path(project_path, None, true, window, cx);
window
.spawn(cx, async move |cx| {
let item = open_task.await?;
if let Some(active_editor) = item.downcast::<Editor>() {
active_editor
.update_in(cx, |editor, window, cx| {
editor.go_to_singleton_buffer_point(
target, window, cx,
);
})
.ok();
}
anyhow::Ok(())
})
.detach_and_log_err(cx);
{
let target = path_range.range.as_ref().map(|range| {
Point::new(
// Line number is 1-based
range.start.line.saturating_sub(1),
range.start.col.unwrap_or(0),
)
});
let open_task = workspace.open_path(
project_path,
None,
true,
window,
cx,
);
window
.spawn(cx, async move |cx| {
let item = open_task.await?;
if let Some(target) = target {
if let Some(active_editor) =
item.downcast::<Editor>()
{
active_editor
.downgrade()
.update_in(cx, |editor, window, cx| {
editor
.go_to_singleton_buffer_point(
target, window, cx,
);
})
.log_err();
}
}
anyhow::Ok(())
})
.detach_and_log_err(cx);
}
}
})
.ok();
@@ -799,11 +807,10 @@ impl ActiveThread {
self.thread.read(cx).summary_or_default()
}
pub fn cancel_last_completion(&mut self, window: &mut Window, cx: &mut App) -> bool {
pub fn cancel_last_completion(&mut self, cx: &mut App) -> bool {
self.last_error.take();
self.thread.update(cx, |thread, cx| {
thread.cancel_last_completion(Some(window.window_handle()), cx)
})
self.thread
.update(cx, |thread, cx| thread.cancel_last_completion(cx))
}
pub fn last_error(&self) -> Option<ThreadError> {
@@ -1307,7 +1314,7 @@ impl ActiveThread {
fn confirm_editing_message(
&mut self,
_: &menu::Confirm,
window: &mut Window,
_: &mut Window,
cx: &mut Context<Self>,
) {
let Some((message_id, state)) = self.editing_message.take() else {
@@ -1337,7 +1344,7 @@ impl ActiveThread {
self.thread.update(cx, |thread, cx| {
thread.advance_prompt_id();
thread.send_to_model(model.model, Some(window.window_handle()), cx);
thread.send_to_model(model.model, cx)
});
cx.notify();
}
@@ -1843,9 +1850,11 @@ impl ActiveThread {
.gap_2()
.children(message_content)
.when(has_tool_uses, |parent| {
parent.children(tool_uses.into_iter().map(|tool_use| {
self.render_tool_use(tool_use, window, workspace.clone(), cx)
}))
parent.children(
tool_uses
.into_iter()
.map(|tool_use| self.render_tool_use(tool_use, window, cx)),
)
}),
Role::System => div().id(("message-container", ix)).py_1().px_2().child(
v_flex()
@@ -2438,11 +2447,10 @@ impl ActiveThread {
&self,
tool_use: ToolUse,
window: &mut Window,
workspace: WeakEntity<Workspace>,
cx: &mut Context<Self>,
) -> impl IntoElement + use<> {
if let Some(card) = self.thread.read(cx).card_for_tool(&tool_use.id) {
return card.render(&tool_use.status, window, workspace, cx);
return card.render(&tool_use.status, window, cx);
}
let is_open = self
@@ -3039,7 +3047,7 @@ impl ActiveThread {
&mut self,
tool_use_id: LanguageModelToolUseId,
_: &ClickEvent,
window: &mut Window,
_window: &mut Window,
cx: &mut Context<Self>,
) {
if let Some(PendingToolUseStatus::NeedsConfirmation(c)) = self
@@ -3055,7 +3063,6 @@ impl ActiveThread {
c.input.clone(),
&c.messages,
c.tool.clone(),
Some(window.window_handle()),
cx,
);
});
@@ -3067,12 +3074,11 @@ impl ActiveThread {
tool_use_id: LanguageModelToolUseId,
tool_name: Arc<str>,
_: &ClickEvent,
window: &mut Window,
_window: &mut Window,
cx: &mut Context<Self>,
) {
let window_handle = window.window_handle();
self.thread.update(cx, |thread, cx| {
thread.deny_tool_use(tool_use_id, tool_name, Some(window_handle), cx);
thread.deny_tool_use(tool_use_id, tool_name, cx);
});
}

View File

@@ -356,9 +356,14 @@ impl AssistantPanel {
&self.thread_store
}
fn cancel(&mut self, _: &editor::actions::Cancel, window: &mut Window, cx: &mut Context<Self>) {
fn cancel(
&mut self,
_: &editor::actions::Cancel,
_window: &mut Window,
cx: &mut Context<Self>,
) {
self.thread
.update(cx, |thread, cx| thread.cancel_last_completion(window, cx));
.update(cx, |thread, cx| thread.cancel_last_completion(cx));
}
fn new_thread(&mut self, action: &NewThread, window: &mut Window, cx: &mut Context<Self>) {

View File

@@ -1328,7 +1328,7 @@ impl InlineAssistant {
editor.highlight_rows::<InlineAssist>(
row_range,
cx.theme().status().info_background,
Default::default(),
false,
cx,
);
}
@@ -1393,7 +1393,7 @@ impl InlineAssistant {
editor.highlight_rows::<DeletedLines>(
Anchor::min()..Anchor::max(),
cx.theme().status().deleted_background,
Default::default(),
false,
cx,
);
editor

View File

@@ -195,7 +195,6 @@ impl MessageEditor {
editor.set_mode(EditorMode::Full {
scale_ui_elements_with_buffer_font_size: false,
show_active_line_background: false,
sized_by_content: false,
})
} else {
editor.set_mode(EditorMode::AutoHeight {
@@ -278,7 +277,6 @@ impl MessageEditor {
let context_store = self.context_store.clone();
let git_store = self.project.read(cx).git_store().clone();
let checkpoint = git_store.update(cx, |git_store, cx| git_store.checkpoint(cx));
let window_handle = window.window_handle();
cx.spawn(async move |this, cx| {
let checkpoint = checkpoint.await.ok();
@@ -335,7 +333,7 @@ impl MessageEditor {
thread
.update(cx, |thread, cx| {
thread.advance_prompt_id();
thread.send_to_model(model, Some(window_handle), cx);
thread.send_to_model(model, cx);
})
.log_err();
})
@@ -343,9 +341,9 @@ impl MessageEditor {
}
fn stop_current_and_send_new_message(&mut self, window: &mut Window, cx: &mut Context<Self>) {
let cancelled = self.thread.update(cx, |thread, cx| {
thread.cancel_last_completion(Some(window.window_handle()), cx)
});
let cancelled = self
.thread
.update(cx, |thread, cx| thread.cancel_last_completion(cx));
if cancelled {
self.set_editor_is_expanded(false, cx);

View File

@@ -13,9 +13,7 @@ use feature_flags::{self, FeatureFlagAppExt};
use futures::future::Shared;
use futures::{FutureExt, StreamExt as _};
use git::repository::DiffType;
use gpui::{
AnyWindowHandle, App, AppContext, Context, Entity, EventEmitter, SharedString, Task, WeakEntity,
};
use gpui::{App, AppContext, Context, Entity, EventEmitter, SharedString, Task, WeakEntity};
use language_model::{
ConfiguredModel, LanguageModel, LanguageModelCompletionEvent, LanguageModelId,
LanguageModelImage, LanguageModelKnownError, LanguageModelRegistry, LanguageModelRequest,
@@ -953,12 +951,7 @@ impl Thread {
self.remaining_turns = remaining_turns;
}
pub fn send_to_model(
&mut self,
model: Arc<dyn LanguageModel>,
window: Option<AnyWindowHandle>,
cx: &mut Context<Self>,
) {
pub fn send_to_model(&mut self, model: Arc<dyn LanguageModel>, cx: &mut Context<Self>) {
if self.remaining_turns == 0 {
return;
}
@@ -989,7 +982,7 @@ impl Thread {
};
}
self.stream_completion(request, model, window, cx);
self.stream_completion(request, model, cx);
}
pub fn used_tools_since_last_user_message(&self) -> bool {
@@ -1208,7 +1201,6 @@ impl Thread {
&mut self,
request: LanguageModelRequest,
model: Arc<dyn LanguageModel>,
window: Option<AnyWindowHandle>,
cx: &mut Context<Self>,
) {
let pending_completion_id = post_inc(&mut self.completion_count);
@@ -1390,7 +1382,7 @@ impl Thread {
match result.as_ref() {
Ok(stop_reason) => match stop_reason {
StopReason::ToolUse => {
let tool_uses = thread.use_pending_tools(window, cx);
let tool_uses = thread.use_pending_tools(cx);
cx.emit(ThreadEvent::UsePendingTools { tool_uses });
}
StopReason::EndTurn => {}
@@ -1435,7 +1427,7 @@ impl Thread {
}));
}
thread.cancel_last_completion(window, cx);
thread.cancel_last_completion(cx);
}
}
cx.emit(ThreadEvent::Stopped(result.map_err(Arc::new)));
@@ -1604,11 +1596,7 @@ impl Thread {
)
}
pub fn use_pending_tools(
&mut self,
window: Option<AnyWindowHandle>,
cx: &mut Context<Self>,
) -> Vec<PendingToolUse> {
pub fn use_pending_tools(&mut self, cx: &mut Context<Self>) -> Vec<PendingToolUse> {
self.auto_capture_telemetry(cx);
let request = self.to_completion_request(cx);
let messages = Arc::new(request.messages);
@@ -1640,7 +1628,6 @@ impl Thread {
tool_use.input.clone(),
&messages,
tool,
window,
cx,
);
}
@@ -1657,10 +1644,9 @@ impl Thread {
input: serde_json::Value,
messages: &[LanguageModelRequestMessage],
tool: Arc<dyn Tool>,
window: Option<AnyWindowHandle>,
cx: &mut Context<Thread>,
) {
let task = self.spawn_tool_use(tool_use_id.clone(), messages, input, tool, window, cx);
let task = self.spawn_tool_use(tool_use_id.clone(), messages, input, tool, cx);
self.tool_use
.run_pending_tool(tool_use_id, ui_text.into(), task);
}
@@ -1671,7 +1657,6 @@ impl Thread {
messages: &[LanguageModelRequestMessage],
input: serde_json::Value,
tool: Arc<dyn Tool>,
window: Option<AnyWindowHandle>,
cx: &mut Context<Thread>,
) -> Task<()> {
let tool_name: Arc<str> = tool.name().into();
@@ -1684,7 +1669,6 @@ impl Thread {
messages,
self.project.clone(),
self.action_log.clone(),
window,
cx,
)
};
@@ -1707,7 +1691,7 @@ impl Thread {
output,
cx,
);
thread.tool_finished(tool_use_id, pending_tool_use, false, window, cx);
thread.tool_finished(tool_use_id, pending_tool_use, false, cx);
})
.ok();
}
@@ -1719,7 +1703,6 @@ impl Thread {
tool_use_id: LanguageModelToolUseId,
pending_tool_use: Option<PendingToolUse>,
canceled: bool,
window: Option<AnyWindowHandle>,
cx: &mut Context<Self>,
) {
if self.all_tools_finished() {
@@ -1727,7 +1710,7 @@ impl Thread {
if let Some(ConfiguredModel { model, .. }) = model_registry.default_model() {
self.attach_tool_results(cx);
if !canceled {
self.send_to_model(model, window, cx);
self.send_to_model(model, cx);
}
}
}
@@ -1749,11 +1732,7 @@ impl Thread {
/// Cancels the last pending completion, if there are any pending.
///
/// Returns whether a completion was canceled.
pub fn cancel_last_completion(
&mut self,
window: Option<AnyWindowHandle>,
cx: &mut Context<Self>,
) -> bool {
pub fn cancel_last_completion(&mut self, cx: &mut Context<Self>) -> bool {
let canceled = if self.pending_completions.pop().is_some() {
true
} else {
@@ -1764,7 +1743,6 @@ impl Thread {
pending_tool_use.id.clone(),
Some(pending_tool_use),
true,
window,
cx,
);
}
@@ -2221,7 +2199,6 @@ impl Thread {
&mut self,
tool_use_id: LanguageModelToolUseId,
tool_name: Arc<str>,
window: Option<AnyWindowHandle>,
cx: &mut Context<Self>,
) {
let err = Err(anyhow::anyhow!(
@@ -2230,7 +2207,7 @@ impl Thread {
self.tool_use
.insert_tool_output(tool_use_id.clone(), tool_name, err, cx);
self.tool_finished(tool_use_id.clone(), None, true, window, cx);
self.tool_finished(tool_use_id.clone(), None, true, cx);
}
}

View File

@@ -23,6 +23,7 @@ use gpui::{
use language::LanguageRegistry;
use language_model::{
AuthenticateError, ConfiguredModel, LanguageModelProviderId, LanguageModelRegistry,
ZED_CLOUD_PROVIDER_ID,
};
use project::Project;
use prompt_library::{PromptLibrary, open_prompt_library};
@@ -488,8 +489,8 @@ impl AssistantPanel {
// If we're signed out and don't have a provider configured, or we're signed-out AND Zed.dev is
// the provider, we want to show a nudge to sign in.
let show_zed_ai_notice =
client_status.is_signed_out() && model.map_or(true, |model| model.is_provided_by_zed());
let show_zed_ai_notice = client_status.is_signed_out()
&& model.map_or(true, |model| model.provider.id().0 == ZED_CLOUD_PROVIDER_ID);
self.show_zed_ai_notice = show_zed_ai_notice;
cx.notify();

View File

@@ -1226,7 +1226,7 @@ impl InlineAssistant {
editor.highlight_rows::<InlineAssist>(
row_range,
cx.theme().status().info_background,
Default::default(),
false,
cx,
);
}
@@ -1291,7 +1291,7 @@ impl InlineAssistant {
editor.highlight_rows::<DeletedLines>(
Anchor::min()..Anchor::max(),
cx.theme().status().deleted_background,
Default::default(),
false,
cx,
);
editor

View File

@@ -28,7 +28,6 @@ serde.workspace = true
serde_json.workspace = true
text.workspace = true
util.workspace = true
workspace.workspace = true
workspace-hack.workspace = true
[dev-dependencies]

View File

@@ -10,16 +10,14 @@ use std::sync::Arc;
use anyhow::Result;
use gpui::AnyElement;
use gpui::AnyWindowHandle;
use gpui::Context;
use gpui::IntoElement;
use gpui::Window;
use gpui::{App, Entity, SharedString, Task, WeakEntity};
use gpui::{App, Entity, SharedString, Task};
use icons::IconName;
use language_model::LanguageModelRequestMessage;
use language_model::LanguageModelToolSchemaFormat;
use project::Project;
use workspace::Workspace;
pub use crate::action_log::*;
pub use crate::tool_registry::*;
@@ -67,7 +65,6 @@ pub trait ToolCard: 'static + Sized {
&mut self,
status: &ToolUseStatus,
window: &mut Window,
workspace: WeakEntity<Workspace>,
cx: &mut Context<Self>,
) -> impl IntoElement;
}
@@ -79,7 +76,6 @@ pub struct AnyToolCard {
entity: gpui::AnyEntity,
status: &ToolUseStatus,
window: &mut Window,
workspace: WeakEntity<Workspace>,
cx: &mut App,
) -> AnyElement,
}
@@ -90,14 +86,11 @@ impl<T: ToolCard> From<Entity<T>> for AnyToolCard {
entity: gpui::AnyEntity,
status: &ToolUseStatus,
window: &mut Window,
workspace: WeakEntity<Workspace>,
cx: &mut App,
) -> AnyElement {
let entity = entity.downcast::<T>().unwrap();
entity.update(cx, |entity, cx| {
entity
.render(status, window, workspace, cx)
.into_any_element()
entity.render(status, window, cx).into_any_element()
})
}
@@ -109,14 +102,8 @@ impl<T: ToolCard> From<Entity<T>> for AnyToolCard {
}
impl AnyToolCard {
pub fn render(
&self,
status: &ToolUseStatus,
window: &mut Window,
workspace: WeakEntity<Workspace>,
cx: &mut App,
) -> AnyElement {
(self.render)(self.entity.clone(), status, window, workspace, cx)
pub fn render(&self, status: &ToolUseStatus, window: &mut Window, cx: &mut App) -> AnyElement {
(self.render)(self.entity.clone(), status, window, cx)
}
}
@@ -176,7 +163,6 @@ pub trait Tool: 'static + Send + Sync {
messages: &[LanguageModelRequestMessage],
project: Entity<Project>,
action_log: Entity<ActionLog>,
window: Option<AnyWindowHandle>,
cx: &mut App,
) -> ToolResult;
}

View File

@@ -14,11 +14,10 @@ path = "src/assistant_tools.rs"
[dependencies]
anyhow.workspace = true
assistant_tool.workspace = true
buffer_diff.workspace = true
chrono.workspace = true
collections.workspace = true
component.workspace = true
editor.workspace = true
feature_flags.workspace = true
futures.workspace = true
gpui.workspace = true
html_to_markdown.workspace = true
@@ -37,14 +36,11 @@ serde_json.workspace = true
ui.workspace = true
util.workspace = true
web_search.workspace = true
workspace.workspace = true
workspace-hack.workspace = true
worktree.workspace = true
zed_llm_client.workspace = true
[dev-dependencies]
client = { workspace = true, features = ["test-support"] }
clock = { workspace = true, features = ["test-support"] }
collections = { workspace = true, features = ["test-support"] }
gpui = { workspace = true, features = ["test-support"] }
language = { workspace = true, features = ["test-support"] }

View File

@@ -29,9 +29,9 @@ use std::sync::Arc;
use assistant_tool::ToolRegistry;
use copy_path_tool::CopyPathTool;
use feature_flags::FeatureFlagAppExt;
use gpui::App;
use http_client::HttpClientWithUrl;
use language_model::LanguageModelRegistry;
use move_path_tool::MovePathTool;
use web_search_tool::WebSearchTool;
@@ -56,10 +56,7 @@ use crate::symbol_info_tool::SymbolInfoTool;
use crate::terminal_tool::TerminalTool;
use crate::thinking_tool::ThinkingTool;
pub use create_file_tool::CreateFileToolInput;
pub use edit_file_tool::EditFileToolInput;
pub use path_search_tool::PathSearchToolInput;
pub use read_file_tool::ReadFileToolInput;
pub fn init(http_client: Arc<HttpClientWithUrl>, cx: &mut App) {
assistant_tool::init(cx);
@@ -88,45 +85,34 @@ pub fn init(http_client: Arc<HttpClientWithUrl>, cx: &mut App) {
registry.register_tool(ThinkingTool);
registry.register_tool(FetchTool::new(http_client));
cx.subscribe(
&LanguageModelRegistry::global(cx),
move |registry, event, cx| match event {
language_model::Event::DefaultModelChanged => {
let using_zed_provider = registry
.read(cx)
.default_model()
.map_or(false, |default| default.is_provided_by_zed());
if using_zed_provider {
ToolRegistry::global(cx).register_tool(WebSearchTool);
} else {
ToolRegistry::global(cx).unregister_tool(WebSearchTool);
}
cx.observe_flag::<feature_flags::ZedProWebSearchTool, _>({
move |is_enabled, cx| {
if is_enabled {
ToolRegistry::global(cx).register_tool(WebSearchTool);
} else {
ToolRegistry::global(cx).unregister_tool(WebSearchTool);
}
_ => {}
},
)
}
})
.detach();
}
#[cfg(test)]
mod tests {
use client::Client;
use clock::FakeSystemClock;
use http_client::FakeHttpClient;
use super::*;
#[gpui::test]
fn test_builtin_tool_schema_compatibility(cx: &mut App) {
settings::init(cx);
let client = Client::new(
Arc::new(FakeSystemClock::new()),
FakeHttpClient::with_200_response(),
crate::init(
Arc::new(http_client::HttpClientWithUrl::new(
FakeHttpClient::with_200_response(),
"https://zed.dev",
None,
)),
cx,
);
language_model::init(client.clone(), cx);
crate::init(client.http_client(), cx);
for tool in ToolRegistry::global(cx).tools() {
let actual_schema = tool

View File

@@ -2,7 +2,7 @@ use crate::schema::json_schema_for;
use anyhow::{Result, anyhow};
use assistant_tool::{ActionLog, Tool, ToolResult, ToolWorkingSet};
use futures::future::join_all;
use gpui::{AnyWindowHandle, App, AppContext, Entity, Task};
use gpui::{App, AppContext, Entity, Task};
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
use project::Project;
use schemars::JsonSchema;
@@ -218,7 +218,6 @@ impl Tool for BatchTool {
messages: &[LanguageModelRequestMessage],
project: Entity<Project>,
action_log: Entity<ActionLog>,
window: Option<AnyWindowHandle>,
cx: &mut App,
) -> ToolResult {
let input = match serde_json::from_value::<BatchToolInput>(input) {
@@ -259,9 +258,7 @@ impl Tool for BatchTool {
let action_log = action_log.clone();
let messages = messages.clone();
let tool_result = cx
.update(|cx| {
tool.run(invocation.input, &messages, project, action_log, window, cx)
})
.update(|cx| tool.run(invocation.input, &messages, project, action_log, cx))
.map_err(|err| anyhow!("Failed to start tool '{}': {}", tool_name, err))?;
tasks.push(tool_result.output);

View File

@@ -1,6 +1,6 @@
use anyhow::{Context as _, Result, anyhow};
use assistant_tool::{ActionLog, Tool, ToolResult};
use gpui::{AnyWindowHandle, App, Entity, Task};
use gpui::{App, Entity, Task};
use language::{self, Anchor, Buffer, ToPointUtf16};
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
use project::{self, LspAction, Project};
@@ -140,7 +140,6 @@ impl Tool for CodeActionTool {
_messages: &[LanguageModelRequestMessage],
project: Entity<Project>,
action_log: Entity<ActionLog>,
_window: Option<AnyWindowHandle>,
cx: &mut App,
) -> ToolResult {
let input = match serde_json::from_value::<CodeActionToolInput>(input) {

View File

@@ -6,7 +6,7 @@ use crate::schema::json_schema_for;
use anyhow::{Result, anyhow};
use assistant_tool::{ActionLog, Tool, ToolResult};
use collections::IndexMap;
use gpui::{AnyWindowHandle, App, AsyncApp, Entity, Task};
use gpui::{App, AsyncApp, Entity, Task};
use language::{OutlineItem, ParseStatus, Point};
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
use project::{Project, Symbol};
@@ -128,7 +128,6 @@ impl Tool for CodeSymbolsTool {
_messages: &[LanguageModelRequestMessage],
project: Entity<Project>,
action_log: Entity<ActionLog>,
_window: Option<AnyWindowHandle>,
cx: &mut App,
) -> ToolResult {
let input = match serde_json::from_value::<CodeSymbolsInput>(input) {

View File

@@ -3,7 +3,7 @@ use std::sync::Arc;
use crate::{code_symbols_tool::file_outline, schema::json_schema_for};
use anyhow::{Result, anyhow};
use assistant_tool::{ActionLog, Tool, ToolResult};
use gpui::{AnyWindowHandle, App, Entity, Task};
use gpui::{App, Entity, Task};
use itertools::Itertools;
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
use project::Project;
@@ -102,7 +102,6 @@ impl Tool for ContentsTool {
_messages: &[LanguageModelRequestMessage],
project: Entity<Project>,
action_log: Entity<ActionLog>,
_window: Option<AnyWindowHandle>,
cx: &mut App,
) -> ToolResult {
let input = match serde_json::from_value::<ContentsToolInput>(input) {

View File

@@ -1,7 +1,6 @@
use crate::schema::json_schema_for;
use anyhow::{Result, anyhow};
use assistant_tool::{ActionLog, Tool, ToolResult};
use gpui::AnyWindowHandle;
use gpui::{App, AppContext, Entity, Task};
use language_model::LanguageModelRequestMessage;
use language_model::LanguageModelToolSchemaFormat;
@@ -77,7 +76,6 @@ impl Tool for CopyPathTool {
_messages: &[LanguageModelRequestMessage],
project: Entity<Project>,
_action_log: Entity<ActionLog>,
_window: Option<AnyWindowHandle>,
cx: &mut App,
) -> ToolResult {
let input = match serde_json::from_value::<CopyPathToolInput>(input) {

View File

@@ -1,7 +1,6 @@
use crate::schema::json_schema_for;
use anyhow::{Result, anyhow};
use assistant_tool::{ActionLog, Tool, ToolResult};
use gpui::AnyWindowHandle;
use gpui::{App, Entity, Task};
use language_model::LanguageModelRequestMessage;
use language_model::LanguageModelToolSchemaFormat;
@@ -68,7 +67,6 @@ impl Tool for CreateDirectoryTool {
_messages: &[LanguageModelRequestMessage],
project: Entity<Project>,
_action_log: Entity<ActionLog>,
_window: Option<AnyWindowHandle>,
cx: &mut App,
) -> ToolResult {
let input = match serde_json::from_value::<CreateDirectoryToolInput>(input) {

View File

@@ -1,7 +1,6 @@
use crate::schema::json_schema_for;
use anyhow::{Result, anyhow};
use assistant_tool::{ActionLog, Tool, ToolResult};
use gpui::AnyWindowHandle;
use gpui::{App, Entity, Task};
use language_model::LanguageModelRequestMessage;
use language_model::LanguageModelToolSchemaFormat;
@@ -90,7 +89,6 @@ impl Tool for CreateFileTool {
_messages: &[LanguageModelRequestMessage],
project: Entity<Project>,
action_log: Entity<ActionLog>,
_window: Option<AnyWindowHandle>,
cx: &mut App,
) -> ToolResult {
let input = match serde_json::from_value::<CreateFileToolInput>(input) {

View File

@@ -2,7 +2,7 @@ use crate::schema::json_schema_for;
use anyhow::{Result, anyhow};
use assistant_tool::{ActionLog, Tool, ToolResult};
use futures::{SinkExt, StreamExt, channel::mpsc};
use gpui::{AnyWindowHandle, App, AppContext, Entity, Task};
use gpui::{App, AppContext, Entity, Task};
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
use project::{Project, ProjectPath};
use schemars::JsonSchema;
@@ -62,7 +62,6 @@ impl Tool for DeletePathTool {
_messages: &[LanguageModelRequestMessage],
project: Entity<Project>,
action_log: Entity<ActionLog>,
_window: Option<AnyWindowHandle>,
cx: &mut App,
) -> ToolResult {
let path_str = match serde_json::from_value::<DeletePathToolInput>(input) {

View File

@@ -1,7 +1,7 @@
use crate::schema::json_schema_for;
use anyhow::{Result, anyhow};
use assistant_tool::{ActionLog, Tool, ToolResult};
use gpui::{AnyWindowHandle, App, Entity, Task};
use gpui::{App, Entity, Task};
use language::{DiagnosticSeverity, OffsetRangeExt};
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
use project::Project;
@@ -82,7 +82,6 @@ impl Tool for DiagnosticsTool {
_messages: &[LanguageModelRequestMessage],
project: Entity<Project>,
action_log: Entity<ActionLog>,
_window: Option<AnyWindowHandle>,
cx: &mut App,
) -> ToolResult {
match serde_json::from_value::<DiagnosticsToolInput>(input)

View File

@@ -1,28 +1,15 @@
use crate::{
replace::{replace_exact, replace_with_flexible_indent},
schema::json_schema_for,
};
use crate::{replace::replace_with_flexible_indent, schema::json_schema_for};
use anyhow::{Context as _, Result, anyhow};
use assistant_tool::{ActionLog, AnyToolCard, Tool, ToolCard, ToolResult, ToolUseStatus};
use buffer_diff::{BufferDiff, BufferDiffSnapshot};
use editor::{Editor, EditorMode, MultiBuffer, PathKey};
use gpui::{
AnyWindowHandle, App, AppContext, AsyncApp, Context, Entity, EntityId, Task, WeakEntity,
};
use language::{
Anchor, Buffer, Capability, LanguageRegistry, LineEnding, OffsetRangeExt, Rope, TextBuffer,
};
use assistant_tool::{ActionLog, Tool, ToolResult};
use gpui::{App, AppContext, AsyncApp, Entity, Task};
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
use project::Project;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::{
path::{Path, PathBuf},
sync::Arc,
};
use ui::{Disclosure, Tooltip, Window, prelude::*};
use util::ResultExt;
use workspace::Workspace;
use std::{path::PathBuf, sync::Arc};
use ui::IconName;
use crate::replace::replace_exact;
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
pub struct EditFileToolInput {
@@ -126,7 +113,6 @@ impl Tool for EditFileTool {
_messages: &[LanguageModelRequestMessage],
project: Entity<Project>,
action_log: Entity<ActionLog>,
window: Option<AnyWindowHandle>,
cx: &mut App,
) -> ToolResult {
let input = match serde_json::from_value::<EditFileToolInput>(input) {
@@ -134,18 +120,7 @@ impl Tool for EditFileTool {
Err(err) => return Task::ready(Err(anyhow!(err))).into(),
};
let card = window.and_then(|window| {
window
.update(cx, |_, window, cx| {
cx.new(|cx| {
EditFileToolCard::new(input.path.clone(), project.clone(), window, cx)
})
})
.ok()
});
let card_clone = card.clone();
let task = cx.spawn(async move |cx: &mut AsyncApp| {
cx.spawn(async move |cx: &mut AsyncApp| {
let project_path = project.read_with(cx, |project, cx| {
project
.find_project_path(&input.path, cx)
@@ -153,38 +128,26 @@ impl Tool for EditFileTool {
})??;
let buffer = project
.update(cx, |project, cx| {
project.open_buffer(project_path.clone(), cx)
})?
.update(cx, |project, cx| project.open_buffer(project_path, cx))?
.await?;
let snapshot = buffer.read_with(cx, |buffer, _cx| buffer.snapshot())?;
if input.old_string.is_empty() {
return Err(anyhow!(
"`old_string` can't be empty, use another tool if you want to create a file."
));
return Err(anyhow!("`old_string` cannot be empty. Use a different tool if you want to create a file."));
}
if input.old_string == input.new_string {
return Err(anyhow!(
"The `old_string` and `new_string` are identical, so no changes would be made."
));
return Err(anyhow!("The `old_string` and `new_string` are identical, so no changes would be made."));
}
let result = cx
.background_spawn(async move {
// Try to match exactly
let diff = replace_exact(&input.old_string, &input.new_string, &snapshot)
.await
// If that fails, try being flexible about indentation
.or_else(|| {
replace_with_flexible_indent(
&input.old_string,
&input.new_string,
&snapshot,
)
})?;
.await
// If that fails, try being flexible about indentation
.or_else(|| replace_with_flexible_indent(&input.old_string, &input.new_string, &snapshot))?;
if diff.edits.is_empty() {
return None;
@@ -214,409 +177,41 @@ impl Tool for EditFileTool {
}
})?;
return Err(err);
return Err(err)
};
let snapshot = cx.update(|cx| {
action_log.update(cx, |log, cx| log.track_buffer(buffer.clone(), cx));
action_log.update(cx, |log, cx| {
log.track_buffer(buffer.clone(), cx)
});
let snapshot = buffer.update(cx, |buffer, cx| {
buffer.finalize_last_transaction();
buffer.apply_diff(diff, cx);
buffer.finalize_last_transaction();
buffer.snapshot()
});
action_log.update(cx, |log, cx| log.buffer_edited(buffer.clone(), cx));
action_log.update(cx, |log, cx| {
log.buffer_edited(buffer.clone(), cx)
});
snapshot
})?;
project
.update(cx, |project, cx| project.save_buffer(buffer, cx))?
.await?;
project.update( cx, |project, cx| {
project.save_buffer(buffer, cx)
})?.await?;
let new_text = snapshot.text();
let diff_str = cx
.background_spawn({
let old_text = old_text.clone();
let new_text = new_text.clone();
async move { language::unified_diff(&old_text, &new_text) }
})
.await;
let diff_str = cx.background_spawn(async move {
let new_text = snapshot.text();
language::unified_diff(&old_text, &new_text)
}).await;
if let Some(card) = card_clone {
card.update(cx, |card, cx| {
card.set_diff(project_path.path.clone(), old_text, new_text, cx);
})
.log_err();
}
Ok(format!(
"Edited {}:\n\n```diff\n{}\n```",
input.path.display(),
diff_str
))
});
Ok(format!("Edited {}:\n\n```diff\n{}\n```", input.path.display(), diff_str))
ToolResult {
output: task,
card: card.map(AnyToolCard::from),
}
}).into()
}
}
pub struct EditFileToolCard {
path: PathBuf,
editor: Entity<Editor>,
multibuffer: Entity<MultiBuffer>,
project: Entity<Project>,
diff_task: Option<Task<Result<()>>>,
preview_expanded: bool,
full_height_expanded: bool,
editor_unique_id: EntityId,
}
impl EditFileToolCard {
fn new(path: PathBuf, project: Entity<Project>, window: &mut Window, cx: &mut App) -> Self {
let multibuffer = cx.new(|_| MultiBuffer::without_headers(Capability::ReadOnly));
let editor = cx.new(|cx| {
let mut editor = Editor::new(
EditorMode::Full {
scale_ui_elements_with_buffer_font_size: false,
show_active_line_background: false,
sized_by_content: true,
},
multibuffer.clone(),
Some(project.clone()),
window,
cx,
);
editor.set_show_scrollbars(false, cx);
editor.set_show_gutter(false, cx);
editor.disable_inline_diagnostics();
editor.disable_scrolling(cx);
editor.disable_expand_excerpt_buttons(cx);
editor.set_show_breakpoints(false, cx);
editor.set_show_code_actions(false, cx);
editor.set_show_git_diff_gutter(false, cx);
editor.set_expand_all_diff_hunks(cx);
editor
});
Self {
editor_unique_id: editor.entity_id(),
path,
project,
editor,
multibuffer,
diff_task: None,
preview_expanded: true,
full_height_expanded: false,
}
}
fn set_diff(
&mut self,
path: Arc<Path>,
old_text: String,
new_text: String,
cx: &mut Context<Self>,
) {
let language_registry = self.project.read(cx).languages().clone();
self.diff_task = Some(cx.spawn(async move |this, cx| {
let buffer = build_buffer(new_text, path.clone(), &language_registry, cx).await?;
let buffer_diff = build_buffer_diff(old_text, &buffer, &language_registry, cx).await?;
this.update(cx, |this, cx| {
this.multibuffer.update(cx, |multibuffer, cx| {
let snapshot = buffer.read(cx).snapshot();
let diff = buffer_diff.read(cx);
let diff_hunk_ranges = diff
.hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &snapshot, cx)
.map(|diff_hunk| diff_hunk.buffer_range.to_point(&snapshot))
.collect::<Vec<_>>();
let (_, is_newly_added) = multibuffer.set_excerpts_for_path(
PathKey::for_buffer(&buffer, cx),
buffer,
diff_hunk_ranges,
editor::DEFAULT_MULTIBUFFER_CONTEXT,
cx,
);
debug_assert!(is_newly_added);
multibuffer.add_diff(buffer_diff, cx);
});
cx.notify();
})
}));
}
}
impl ToolCard for EditFileToolCard {
fn render(
&mut self,
status: &ToolUseStatus,
window: &mut Window,
workspace: WeakEntity<Workspace>,
cx: &mut Context<Self>,
) -> impl IntoElement {
let failed = matches!(status, ToolUseStatus::Error(_));
let path_label_button = h_flex()
.id(("edit-tool-path-label-button", self.editor_unique_id))
.w_full()
.max_w_full()
.px_1()
.gap_0p5()
.cursor_pointer()
.rounded_sm()
.opacity(0.8)
.hover(|label| {
label
.opacity(1.)
.bg(cx.theme().colors().element_hover.opacity(0.5))
})
.tooltip(Tooltip::text("Jump to File"))
.child(
h_flex()
.child(
Icon::new(IconName::Pencil)
.size(IconSize::XSmall)
.color(Color::Muted),
)
.child(
div()
.text_size(rems(0.8125))
.child(self.path.display().to_string())
.ml_1p5()
.mr_0p5(),
)
.child(
Icon::new(IconName::ArrowUpRight)
.size(IconSize::XSmall)
.color(Color::Ignored),
),
)
.on_click({
let path = self.path.clone();
let workspace = workspace.clone();
move |_, window, cx| {
workspace
.update(cx, {
|workspace, cx| {
let Some(project_path) =
workspace.project().read(cx).find_project_path(&path, cx)
else {
return;
};
let open_task =
workspace.open_path(project_path, None, true, window, cx);
window
.spawn(cx, async move |cx| {
let item = open_task.await?;
if let Some(active_editor) = item.downcast::<Editor>() {
active_editor
.update_in(cx, |editor, window, cx| {
editor.go_to_singleton_buffer_point(
language::Point::new(0, 0),
window,
cx,
);
})
.log_err();
}
anyhow::Ok(())
})
.detach_and_log_err(cx);
}
})
.ok();
}
})
.into_any_element();
let codeblock_header_bg = cx
.theme()
.colors()
.element_background
.blend(cx.theme().colors().editor_foreground.opacity(0.025));
let codeblock_header = h_flex()
.flex_none()
.p_1()
.gap_1()
.justify_between()
.rounded_t_md()
.when(!failed, |header| header.bg(codeblock_header_bg))
.child(path_label_button)
.map(|container| {
if failed {
container.child(
Icon::new(IconName::Close)
.size(IconSize::Small)
.color(Color::Error),
)
} else {
container.child(
Disclosure::new(
("edit-file-disclosure", self.editor_unique_id),
self.preview_expanded,
)
.opened_icon(IconName::ChevronUp)
.closed_icon(IconName::ChevronDown)
.on_click(cx.listener(
move |this, _event, _window, _cx| {
this.preview_expanded = !this.preview_expanded;
},
)),
)
}
});
let editor = self.editor.update(cx, |editor, cx| {
editor.render(window, cx).into_any_element()
});
let (full_height_icon, full_height_tooltip_label) = if self.full_height_expanded {
(IconName::ChevronUp, "Collapse Code Block")
} else {
(IconName::ChevronDown, "Expand Code Block")
};
let gradient_overlay = div()
.absolute()
.bottom_0()
.left_0()
.w_full()
.h_2_5()
.rounded_b_lg()
.bg(gpui::linear_gradient(
0.,
gpui::linear_color_stop(cx.theme().colors().editor_background, 0.),
gpui::linear_color_stop(cx.theme().colors().editor_background.opacity(0.), 1.),
));
let border_color = cx.theme().colors().border.opacity(0.6);
v_flex()
.mb_2()
.border_1()
.when(failed, |card| card.border_dashed())
.border_color(border_color)
.rounded_lg()
.overflow_hidden()
.child(codeblock_header)
.when(!failed && self.preview_expanded, |card| {
card.child(
v_flex()
.relative()
.overflow_hidden()
.border_t_1()
.border_color(border_color)
.bg(cx.theme().colors().editor_background)
.map(|editor_container| {
if self.full_height_expanded {
editor_container.h_full()
} else {
editor_container.max_h_64()
}
})
.child(div().pl_1().child(editor))
.when(!self.full_height_expanded, |editor_container| {
editor_container.child(gradient_overlay)
}),
)
})
.when(!failed && self.preview_expanded, |card| {
card.child(
h_flex()
.id(("edit-tool-card-inner-hflex", self.editor_unique_id))
.flex_none()
.cursor_pointer()
.h_5()
.justify_center()
.rounded_b_md()
.border_t_1()
.border_color(border_color)
.bg(cx.theme().colors().editor_background)
.hover(|style| style.bg(cx.theme().colors().element_hover.opacity(0.1)))
.child(
Icon::new(full_height_icon)
.size(IconSize::Small)
.color(Color::Muted),
)
.tooltip(Tooltip::text(full_height_tooltip_label))
.on_click(cx.listener(move |this, _event, _window, _cx| {
this.full_height_expanded = !this.full_height_expanded;
})),
)
})
}
}
async fn build_buffer(
mut text: String,
path: Arc<Path>,
language_registry: &Arc<language::LanguageRegistry>,
cx: &mut AsyncApp,
) -> Result<Entity<Buffer>> {
let line_ending = LineEnding::detect(&text);
LineEnding::normalize(&mut text);
let text = Rope::from(text);
let language = cx
.update(|_cx| language_registry.language_for_file_path(&path))?
.await
.ok();
let buffer = cx.new(|cx| {
let buffer = TextBuffer::new_normalized(
0,
cx.entity_id().as_non_zero_u64().into(),
line_ending,
text,
);
let mut buffer = Buffer::build(buffer, None, Capability::ReadWrite);
buffer.set_language(language, cx);
buffer
})?;
Ok(buffer)
}
async fn build_buffer_diff(
mut old_text: String,
buffer: &Entity<Buffer>,
language_registry: &Arc<LanguageRegistry>,
cx: &mut AsyncApp,
) -> Result<Entity<BufferDiff>> {
LineEnding::normalize(&mut old_text);
let buffer = cx.update(|cx| buffer.read(cx).snapshot())?;
let base_buffer = cx
.update(|cx| {
Buffer::build_snapshot(
old_text.clone().into(),
buffer.language().cloned(),
Some(language_registry.clone()),
cx,
)
})?
.await;
let diff_snapshot = cx
.update(|cx| {
BufferDiffSnapshot::new_with_base_buffer(
buffer.text.clone(),
Some(old_text.into()),
base_buffer,
cx,
)
})?
.await;
cx.new(|cx| {
let mut diff = BufferDiff::new(&buffer.text, cx);
diff.set_snapshot(diff_snapshot, &buffer.text, cx);
diff
})
}
#[cfg(test)]
mod tests {
use super::*;

View File

@@ -6,7 +6,7 @@ use crate::schema::json_schema_for;
use anyhow::{Context as _, Result, anyhow, bail};
use assistant_tool::{ActionLog, Tool, ToolResult};
use futures::AsyncReadExt as _;
use gpui::{AnyWindowHandle, App, AppContext as _, Entity, Task};
use gpui::{App, AppContext as _, Entity, Task};
use html_to_markdown::{TagHandler, convert_html_to_markdown, markdown};
use http_client::{AsyncBody, HttpClientWithUrl};
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
@@ -145,7 +145,6 @@ impl Tool for FetchTool {
_messages: &[LanguageModelRequestMessage],
_project: Entity<Project>,
_action_log: Entity<ActionLog>,
_window: Option<AnyWindowHandle>,
cx: &mut App,
) -> ToolResult {
let input = match serde_json::from_value::<FetchToolInput>(input) {

View File

@@ -2,7 +2,7 @@ use crate::schema::json_schema_for;
use anyhow::{Result, anyhow};
use assistant_tool::{ActionLog, Tool, ToolResult};
use futures::StreamExt;
use gpui::{AnyWindowHandle, App, Entity, Task};
use gpui::{App, Entity, Task};
use language::OffsetRangeExt;
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
use project::{
@@ -96,7 +96,6 @@ impl Tool for GrepTool {
_messages: &[LanguageModelRequestMessage],
project: Entity<Project>,
_action_log: Entity<ActionLog>,
_window: Option<AnyWindowHandle>,
cx: &mut App,
) -> ToolResult {
const CONTEXT_LINES: u32 = 2;
@@ -406,7 +405,7 @@ mod tests {
) -> String {
let tool = Arc::new(GrepTool);
let action_log = cx.new(|_cx| ActionLog::new(project.clone()));
let task = cx.update(|cx| tool.run(input, &[], project, action_log, None, cx));
let task = cx.update(|cx| tool.run(input, &[], project, action_log, cx));
match task.output.await {
Ok(result) => result,

View File

@@ -1,7 +1,7 @@
use crate::schema::json_schema_for;
use anyhow::{Result, anyhow};
use assistant_tool::{ActionLog, Tool, ToolResult};
use gpui::{AnyWindowHandle, App, Entity, Task};
use gpui::{App, Entity, Task};
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
use project::Project;
use schemars::JsonSchema;
@@ -76,7 +76,6 @@ impl Tool for ListDirectoryTool {
_messages: &[LanguageModelRequestMessage],
project: Entity<Project>,
_action_log: Entity<ActionLog>,
_window: Option<AnyWindowHandle>,
cx: &mut App,
) -> ToolResult {
let input = match serde_json::from_value::<ListDirectoryToolInput>(input) {

View File

@@ -1,7 +1,7 @@
use crate::schema::json_schema_for;
use anyhow::{Result, anyhow};
use assistant_tool::{ActionLog, Tool, ToolResult};
use gpui::{AnyWindowHandle, App, AppContext, Entity, Task};
use gpui::{App, AppContext, Entity, Task};
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
use project::Project;
use schemars::JsonSchema;
@@ -89,7 +89,6 @@ impl Tool for MovePathTool {
_messages: &[LanguageModelRequestMessage],
project: Entity<Project>,
_action_log: Entity<ActionLog>,
_window: Option<AnyWindowHandle>,
cx: &mut App,
) -> ToolResult {
let input = match serde_json::from_value::<MovePathToolInput>(input) {

View File

@@ -4,7 +4,7 @@ use crate::schema::json_schema_for;
use anyhow::{Result, anyhow};
use assistant_tool::{ActionLog, Tool, ToolResult};
use chrono::{Local, Utc};
use gpui::{AnyWindowHandle, App, Entity, Task};
use gpui::{App, Entity, Task};
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
use project::Project;
use schemars::JsonSchema;
@@ -59,7 +59,6 @@ impl Tool for NowTool {
_messages: &[LanguageModelRequestMessage],
_project: Entity<Project>,
_action_log: Entity<ActionLog>,
_window: Option<AnyWindowHandle>,
_cx: &mut App,
) -> ToolResult {
let input: NowToolInput = match serde_json::from_value(input) {

View File

@@ -1,7 +1,7 @@
use crate::schema::json_schema_for;
use anyhow::{Context as _, Result, anyhow};
use assistant_tool::{ActionLog, Tool, ToolResult};
use gpui::{AnyWindowHandle, App, AppContext, Entity, Task};
use gpui::{App, AppContext, Entity, Task};
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
use project::Project;
use schemars::JsonSchema;
@@ -52,7 +52,6 @@ impl Tool for OpenTool {
_messages: &[LanguageModelRequestMessage],
_project: Entity<Project>,
_action_log: Entity<ActionLog>,
_window: Option<AnyWindowHandle>,
cx: &mut App,
) -> ToolResult {
let input: OpenToolInput = match serde_json::from_value(input) {

View File

@@ -1,7 +1,7 @@
use crate::schema::json_schema_for;
use anyhow::{Result, anyhow};
use assistant_tool::{ActionLog, Tool, ToolResult};
use gpui::{AnyWindowHandle, App, AppContext, Entity, Task};
use gpui::{App, AppContext, Entity, Task};
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
use project::Project;
use schemars::JsonSchema;
@@ -70,7 +70,6 @@ impl Tool for PathSearchTool {
_messages: &[LanguageModelRequestMessage],
project: Entity<Project>,
_action_log: Entity<ActionLog>,
_window: Option<AnyWindowHandle>,
cx: &mut App,
) -> ToolResult {
let (offset, glob) = match serde_json::from_value::<PathSearchToolInput>(input) {

View File

@@ -1,8 +1,7 @@
use crate::{code_symbols_tool::file_outline, schema::json_schema_for};
use anyhow::{Result, anyhow};
use assistant_tool::{ActionLog, Tool, ToolResult};
use gpui::{AnyWindowHandle, App, Entity, Task};
use gpui::{App, Entity, Task};
use indoc::formatdoc;
use itertools::Itertools;
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
@@ -88,7 +87,6 @@ impl Tool for ReadFileTool {
_messages: &[LanguageModelRequestMessage],
project: Entity<Project>,
action_log: Entity<ActionLog>,
_window: Option<AnyWindowHandle>,
cx: &mut App,
) -> ToolResult {
let input = match serde_json::from_value::<ReadFileToolInput>(input) {
@@ -195,7 +193,7 @@ mod test {
"path": "root/nonexistent_file.txt"
});
Arc::new(ReadFileTool)
.run(input, &[], project.clone(), action_log, None, cx)
.run(input, &[], project.clone(), action_log, cx)
.output
})
.await;
@@ -225,7 +223,7 @@ mod test {
"path": "root/small_file.txt"
});
Arc::new(ReadFileTool)
.run(input, &[], project.clone(), action_log, None, cx)
.run(input, &[], project.clone(), action_log, cx)
.output
})
.await;
@@ -255,7 +253,7 @@ mod test {
"path": "root/large_file.rs"
});
Arc::new(ReadFileTool)
.run(input, &[], project.clone(), action_log.clone(), None, cx)
.run(input, &[], project.clone(), action_log.clone(), cx)
.output
})
.await;
@@ -279,7 +277,7 @@ mod test {
"offset": 1
});
Arc::new(ReadFileTool)
.run(input, &[], project.clone(), action_log, None, cx)
.run(input, &[], project.clone(), action_log, cx)
.output
})
.await;
@@ -325,7 +323,7 @@ mod test {
"end_line": 4
});
Arc::new(ReadFileTool)
.run(input, &[], project.clone(), action_log, None, cx)
.run(input, &[], project.clone(), action_log, cx)
.output
})
.await;

View File

@@ -1,6 +1,6 @@
use anyhow::{Context as _, Result, anyhow};
use assistant_tool::{ActionLog, Tool, ToolResult};
use gpui::{AnyWindowHandle, App, Entity, Task};
use gpui::{App, Entity, Task};
use language::{self, Buffer, ToPointUtf16};
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
use project::Project;
@@ -87,7 +87,6 @@ impl Tool for RenameTool {
_messages: &[LanguageModelRequestMessage],
project: Entity<Project>,
action_log: Entity<ActionLog>,
_window: Option<AnyWindowHandle>,
cx: &mut App,
) -> ToolResult {
let input = match serde_json::from_value::<RenameToolInput>(input) {

View File

@@ -1,6 +1,6 @@
use anyhow::{Context as _, Result, anyhow};
use assistant_tool::{ActionLog, Tool, ToolResult};
use gpui::{AnyWindowHandle, App, AsyncApp, Entity, Task};
use gpui::{App, AsyncApp, Entity, Task};
use language::{self, Anchor, Buffer, BufferSnapshot, Location, Point, ToPoint, ToPointUtf16};
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
use project::Project;
@@ -121,7 +121,6 @@ impl Tool for SymbolInfoTool {
_messages: &[LanguageModelRequestMessage],
project: Entity<Project>,
action_log: Entity<ActionLog>,
_window: Option<AnyWindowHandle>,
cx: &mut App,
) -> ToolResult {
let input = match serde_json::from_value::<SymbolInfoToolInput>(input) {

View File

@@ -3,7 +3,7 @@ use anyhow::{Context as _, Result, anyhow};
use assistant_tool::{ActionLog, Tool, ToolResult};
use futures::io::BufReader;
use futures::{AsyncBufReadExt, AsyncReadExt, FutureExt};
use gpui::{AnyWindowHandle, App, AppContext, Entity, Task};
use gpui::{App, AppContext, Entity, Task};
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
use project::Project;
use schemars::JsonSchema;
@@ -78,7 +78,6 @@ impl Tool for TerminalTool {
_messages: &[LanguageModelRequestMessage],
project: Entity<Project>,
_action_log: Entity<ActionLog>,
_window: Option<AnyWindowHandle>,
cx: &mut App,
) -> ToolResult {
let input: TerminalToolInput = match serde_json::from_value(input) {

View File

@@ -3,7 +3,7 @@ use std::sync::Arc;
use crate::schema::json_schema_for;
use anyhow::{Result, anyhow};
use assistant_tool::{ActionLog, Tool, ToolResult};
use gpui::{AnyWindowHandle, App, Entity, Task};
use gpui::{App, Entity, Task};
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
use project::Project;
use schemars::JsonSchema;
@@ -50,7 +50,6 @@ impl Tool for ThinkingTool {
_messages: &[LanguageModelRequestMessage],
_project: Entity<Project>,
_action_log: Entity<ActionLog>,
_window: Option<AnyWindowHandle>,
_cx: &mut App,
) -> ToolResult {
// This tool just "thinks out loud" and doesn't perform any actions.

View File

@@ -5,16 +5,13 @@ use crate::ui::ToolCallCardHeader;
use anyhow::{Context as _, Result, anyhow};
use assistant_tool::{ActionLog, Tool, ToolCard, ToolResult, ToolUseStatus};
use futures::{Future, FutureExt, TryFutureExt};
use gpui::{
AnyWindowHandle, App, AppContext, Context, Entity, IntoElement, Task, WeakEntity, Window,
};
use gpui::{App, AppContext, Context, Entity, IntoElement, Task, Window};
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
use project::Project;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use ui::{IconName, Tooltip, prelude::*};
use web_search::WebSearchRegistry;
use workspace::Workspace;
use zed_llm_client::{WebSearchCitation, WebSearchResponse};
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
@@ -57,7 +54,6 @@ impl Tool for WebSearchTool {
_messages: &[LanguageModelRequestMessage],
_project: Entity<Project>,
_action_log: Entity<ActionLog>,
_window: Option<AnyWindowHandle>,
cx: &mut App,
) -> ToolResult {
let input = match serde_json::from_value::<WebSearchToolInput>(input) {
@@ -115,7 +111,6 @@ impl ToolCard for WebSearchToolCard {
&mut self,
_status: &ToolUseStatus,
_window: &mut Window,
_workspace: WeakEntity<Workspace>,
cx: &mut Context<Self>,
) -> impl IntoElement {
let header = match self.response.as_ref() {
@@ -225,13 +220,8 @@ impl Component for WebSearchTool {
div()
.size_full()
.child(in_progress_search.update(cx, |tool, cx| {
tool.render(
&ToolUseStatus::Pending,
window,
WeakEntity::new_invalid(),
cx,
)
.into_any_element()
tool.render(&ToolUseStatus::Pending, window, cx)
.into_any_element()
}))
.into_any_element(),
),
@@ -240,13 +230,8 @@ impl Component for WebSearchTool {
div()
.size_full()
.child(successful_search.update(cx, |tool, cx| {
tool.render(
&ToolUseStatus::Finished("".into()),
window,
WeakEntity::new_invalid(),
cx,
)
.into_any_element()
tool.render(&ToolUseStatus::Finished("".into()), window, cx)
.into_any_element()
}))
.into_any_element(),
),
@@ -255,13 +240,8 @@ impl Component for WebSearchTool {
div()
.size_full()
.child(error_search.update(cx, |tool, cx| {
tool.render(
&ToolUseStatus::Error("".into()),
window,
WeakEntity::new_invalid(),
cx,
)
.into_any_element()
tool.render(&ToolUseStatus::Error("".into()), window, cx)
.into_any_element()
}))
.into_any_element(),
),

View File

@@ -1,2 +0,0 @@
alter table project_repositories
add column head_commit_details varchar;

View File

@@ -2,7 +2,7 @@ use std::sync::Arc;
use anyhow::{Result, anyhow, bail};
use assistant_tool::{ActionLog, Tool, ToolResult, ToolSource};
use gpui::{AnyWindowHandle, App, Entity, Task};
use gpui::{App, Entity, Task};
use icons::IconName;
use language_model::{LanguageModelRequestMessage, LanguageModelToolSchemaFormat};
use project::Project;
@@ -77,7 +77,6 @@ impl Tool for ContextServerTool {
_messages: &[LanguageModelRequestMessage],
_project: Entity<Project>,
_action_log: Entity<ActionLog>,
_window: Option<AnyWindowHandle>,
cx: &mut App,
) -> ToolResult {
if let Some(server) = self.server_manager.read(cx).get_server(&self.server_id) {

View File

@@ -411,12 +411,12 @@ impl RunningState {
.log_err();
if let Some(thread_id) = thread_id {
this.select_thread(*thread_id, window, cx);
this.select_thread(*thread_id, cx);
}
}
SessionEvent::Threads => {
let threads = this.session.update(cx, |this, cx| this.threads(cx));
this.select_current_thread(&threads, window, cx);
this.select_current_thread(&threads, cx);
}
SessionEvent::CapabilitiesLoaded => {
let capabilities = this.capabilities(cx);
@@ -731,7 +731,6 @@ impl RunningState {
pub fn select_current_thread(
&mut self,
threads: &Vec<(Thread, ThreadStatus)>,
window: &mut Window,
cx: &mut Context<Self>,
) {
let selected_thread = self
@@ -744,7 +743,7 @@ impl RunningState {
};
if Some(ThreadId(selected_thread.id)) != self.thread_id {
self.select_thread(ThreadId(selected_thread.id), window, cx);
self.select_thread(ThreadId(selected_thread.id), cx);
}
}
@@ -757,7 +756,7 @@ impl RunningState {
.map(|id| self.session().read(cx).thread_status(id))
}
fn select_thread(&mut self, thread_id: ThreadId, window: &mut Window, cx: &mut Context<Self>) {
fn select_thread(&mut self, thread_id: ThreadId, cx: &mut Context<Self>) {
if self.thread_id.is_some_and(|id| id == thread_id) {
return;
}
@@ -765,7 +764,8 @@ impl RunningState {
self.thread_id = Some(thread_id);
self.stack_frame_list
.update(cx, |list, cx| list.schedule_refresh(true, window, cx));
.update(cx, |list, cx| list.refresh(cx));
cx.notify();
}
pub fn continue_thread(&mut self, cx: &mut Context<Self>) {
@@ -917,9 +917,9 @@ impl RunningState {
for (thread, _) in threads {
let state = state.clone();
let thread_id = thread.id;
this = this.entry(thread.name, None, move |window, cx| {
this = this.entry(thread.name, None, move |_, cx| {
state.update(cx, |state, cx| {
state.select_thread(ThreadId(thread_id), window, cx);
state.select_thread(ThreadId(thread_id), cx);
});
});
}

View File

@@ -1,6 +1,5 @@
use std::path::Path;
use std::sync::Arc;
use std::time::Duration;
use anyhow::{Result, anyhow};
use dap::StackFrameId;
@@ -29,11 +28,11 @@ pub struct StackFrameList {
_subscription: Subscription,
session: Entity<Session>,
state: WeakEntity<RunningState>,
invalidate: bool,
entries: Vec<StackFrameEntry>,
workspace: WeakEntity<Workspace>,
selected_stack_frame_id: Option<StackFrameId>,
scrollbar_state: ScrollbarState,
_refresh_task: Task<()>,
}
#[allow(clippy::large_enum_variant)]
@@ -69,17 +68,14 @@ impl StackFrameList {
);
let _subscription =
cx.subscribe_in(&session, window, |this, _, event, window, cx| match event {
SessionEvent::Threads => {
this.schedule_refresh(false, window, cx);
}
SessionEvent::Stopped(..) | SessionEvent::StackTrace => {
this.schedule_refresh(true, window, cx);
cx.subscribe_in(&session, window, |this, _, event, _, cx| match event {
SessionEvent::Stopped(_) | SessionEvent::StackTrace | SessionEvent::Threads => {
this.refresh(cx);
}
_ => {}
});
let mut this = Self {
Self {
scrollbar_state: ScrollbarState::new(list.clone()),
list,
session,
@@ -87,12 +83,10 @@ impl StackFrameList {
focus_handle,
state,
_subscription,
invalidate: true,
entries: Default::default(),
selected_stack_frame_id: None,
_refresh_task: Task::ready(()),
};
this.schedule_refresh(true, window, cx);
this
}
}
#[cfg(test)]
@@ -142,32 +136,10 @@ impl StackFrameList {
self.selected_stack_frame_id
}
pub(super) fn schedule_refresh(
&mut self,
select_first: bool,
window: &mut Window,
cx: &mut Context<Self>,
) {
const REFRESH_DEBOUNCE: Duration = Duration::from_millis(20);
self._refresh_task = cx.spawn_in(window, async move |this, cx| {
let debounce = this
.update(cx, |this, cx| {
let new_stack_frames = this.stack_frames(cx);
new_stack_frames.is_empty() && !this.entries.is_empty()
})
.ok()
.unwrap_or_default();
if debounce {
cx.background_executor().timer(REFRESH_DEBOUNCE).await;
}
this.update_in(cx, |this, window, cx| {
this.build_entries(select_first, window, cx);
cx.notify();
})
.ok();
})
pub(super) fn refresh(&mut self, cx: &mut Context<Self>) {
self.invalidate = true;
self.entries.clear();
cx.notify();
}
pub fn build_entries(
@@ -543,7 +515,13 @@ impl StackFrameList {
}
impl Render for StackFrameList {
fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
if self.invalidate {
self.build_entries(self.entries.is_empty(), window, cx);
self.invalidate = false;
cx.notify();
}
div()
.size_full()
.p_1()

View File

@@ -152,7 +152,7 @@ async fn test_fetch_initial_stack_frames_and_go_to_stack_frame(
cx.run_until_parked();
// select first thread
active_debug_session_panel(workspace, cx).update_in(cx, |session, window, cx| {
active_debug_session_panel(workspace, cx).update_in(cx, |session, _, cx| {
session
.mode()
.as_running()
@@ -162,7 +162,6 @@ async fn test_fetch_initial_stack_frames_and_go_to_stack_frame(
&running_state
.session()
.update(cx, |session, cx| session.threads(cx)),
window,
cx,
);
});
@@ -331,7 +330,7 @@ async fn test_select_stack_frame(executor: BackgroundExecutor, cx: &mut TestAppC
cx.run_until_parked();
// select first thread
active_debug_session_panel(workspace, cx).update_in(cx, |session, window, cx| {
active_debug_session_panel(workspace, cx).update_in(cx, |session, _, cx| {
session
.mode()
.as_running()
@@ -341,7 +340,6 @@ async fn test_select_stack_frame(executor: BackgroundExecutor, cx: &mut TestAppC
&running_state
.session()
.update(cx, |session, cx| session.threads(cx)),
window,
cx,
);
});
@@ -706,7 +704,7 @@ async fn test_collapsed_entries(executor: BackgroundExecutor, cx: &mut TestAppCo
cx.run_until_parked();
// select first thread
active_debug_session_panel(workspace, cx).update_in(cx, |session, window, cx| {
active_debug_session_panel(workspace, cx).update_in(cx, |session, _, cx| {
session
.mode()
.as_running()
@@ -716,7 +714,6 @@ async fn test_collapsed_entries(executor: BackgroundExecutor, cx: &mut TestAppCo
&running_state
.session()
.update(cx, |session, cx| session.threads(cx)),
window,
cx,
);
});

View File

@@ -269,12 +269,6 @@ enum DocumentHighlightWrite {}
enum InputComposition {}
enum SelectedTextHighlight {}
pub enum ConflictsOuter {}
pub enum ConflictsOurs {}
pub enum ConflictsTheirs {}
pub enum ConflictsOursMarker {}
pub enum ConflictsTheirsMarker {}
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum Navigated {
Yes,
@@ -433,8 +427,6 @@ pub enum EditorMode {
scale_ui_elements_with_buffer_font_size: bool,
/// When set to `true`, the editor will render a background for the active line.
show_active_line_background: bool,
/// When set to `true`, the editor's height will be determined by its content.
sized_by_content: bool,
},
}
@@ -443,7 +435,6 @@ impl EditorMode {
Self::Full {
scale_ui_elements_with_buffer_font_size: true,
show_active_line_background: true,
sized_by_content: false,
}
}
@@ -703,10 +694,6 @@ pub trait Addon: 'static {
}
fn to_any(&self) -> &dyn std::any::Any;
fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
None
}
}
/// A set of caret positions, registered when the editor was edited.
@@ -801,8 +788,6 @@ pub struct Editor {
show_breadcrumbs: bool,
show_gutter: bool,
show_scrollbars: bool,
disable_scrolling: bool,
disable_expand_excerpt_buttons: bool,
show_line_numbers: Option<bool>,
use_relative_line_numbers: Option<bool>,
show_git_diff_gutter: Option<bool>,
@@ -1098,27 +1083,11 @@ impl SelectionHistory {
}
}
#[derive(Clone, Copy)]
pub struct RowHighlightOptions {
pub autoscroll: bool,
pub include_gutter: bool,
}
impl Default for RowHighlightOptions {
fn default() -> Self {
Self {
autoscroll: Default::default(),
include_gutter: true,
}
}
}
struct RowHighlight {
index: usize,
range: Range<Anchor>,
color: Hsla,
options: RowHighlightOptions,
type_id: TypeId,
should_autoscroll: bool,
}
#[derive(Clone, Debug)]
@@ -1594,13 +1563,11 @@ impl Editor {
blink_manager: blink_manager.clone(),
show_local_selections: true,
show_scrollbars: true,
disable_scrolling: true,
mode,
show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
show_gutter: mode.is_full(),
show_line_numbers: None,
use_relative_line_numbers: None,
disable_expand_excerpt_buttons: false,
show_git_diff_gutter: None,
show_code_actions: None,
show_runnables: None,
@@ -5975,10 +5942,7 @@ impl Editor {
self.highlight_rows::<EditPredictionPreview>(
target..target,
cx.theme().colors().editor_highlighted_line_background,
RowHighlightOptions {
autoscroll: true,
..Default::default()
},
true,
cx,
);
self.request_autoscroll(Autoscroll::fit(), cx);
@@ -11834,7 +11798,6 @@ impl Editor {
fn select_next_match_ranges(
this: &mut Editor,
range: Range<usize>,
reversed: bool,
replace_newest: bool,
auto_scroll: Option<Autoscroll>,
window: &mut Window,
@@ -11845,11 +11808,7 @@ impl Editor {
if replace_newest {
s.delete(s.newest_anchor().id);
}
if reversed {
s.insert_range(range.end..range.start);
} else {
s.insert_range(range);
}
s.insert_range(range.clone());
});
}
@@ -11900,7 +11859,6 @@ impl Editor {
select_next_match_ranges(
self,
next_selected_range,
last_selection.reversed,
replace_newest,
autoscroll,
window,
@@ -11959,7 +11917,6 @@ impl Editor {
select_next_match_ranges(
self,
selection.start..selection.end,
selection.reversed,
replace_newest,
autoscroll,
window,
@@ -12127,11 +12084,7 @@ impl Editor {
if action.replace_newest {
s.delete(s.newest_anchor().id);
}
if last_selection.reversed {
s.insert_range(next_selected_range.end..next_selected_range.start);
} else {
s.insert_range(next_selected_range);
}
s.insert_range(next_selected_range);
});
} else {
select_prev_state.done = true;
@@ -13496,7 +13449,7 @@ impl Editor {
start..end,
highlight_color
.unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
Default::default(),
false,
cx,
);
self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
@@ -16193,21 +16146,11 @@ impl Editor {
cx.notify();
}
pub fn disable_scrolling(&mut self, cx: &mut Context<Self>) {
self.disable_scrolling = true;
cx.notify();
}
pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
self.show_line_numbers = Some(show_line_numbers);
cx.notify();
}
pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
self.disable_expand_excerpt_buttons = true;
cx.notify();
}
pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
self.show_git_diff_gutter = Some(show_git_diff_gutter);
cx.notify();
@@ -16822,7 +16765,7 @@ impl Editor {
&mut self,
range: Range<Anchor>,
color: Hsla,
options: RowHighlightOptions,
should_autoscroll: bool,
cx: &mut Context<Self>,
) {
let snapshot = self.buffer().read(cx).snapshot(cx);
@@ -16854,7 +16797,7 @@ impl Editor {
merged = true;
prev_highlight.index = index;
prev_highlight.color = color;
prev_highlight.options = options;
prev_highlight.should_autoscroll = should_autoscroll;
}
}
@@ -16865,8 +16808,7 @@ impl Editor {
range: range.clone(),
index,
color,
options,
type_id: TypeId::of::<T>(),
should_autoscroll,
},
);
}
@@ -16972,15 +16914,7 @@ impl Editor {
used_highlight_orders.entry(row).or_insert(highlight.index);
if highlight.index >= *used_index {
*used_index = highlight.index;
unique_rows.insert(
DisplayRow(row),
LineHighlight {
include_gutter: highlight.options.include_gutter,
border: None,
background: highlight.color.into(),
type_id: Some(highlight.type_id),
},
);
unique_rows.insert(DisplayRow(row), highlight.color.into());
}
}
unique_rows
@@ -16996,7 +16930,7 @@ impl Editor {
.values()
.flat_map(|highlighted_rows| highlighted_rows.iter())
.filter_map(|highlight| {
if highlight.options.autoscroll {
if highlight.should_autoscroll {
Some(highlight.range.start.to_display_point(snapshot).row())
} else {
None
@@ -17471,19 +17405,13 @@ impl Editor {
});
self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
}
multi_buffer::Event::ExcerptsRemoved {
ids,
removed_buffer_ids,
} => {
multi_buffer::Event::ExcerptsRemoved { ids } => {
self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
let buffer = self.buffer.read(cx);
self.registered_buffers
.retain(|buffer_id, _| buffer.buffer(*buffer_id).is_some());
jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
cx.emit(EditorEvent::ExcerptsRemoved {
ids: ids.clone(),
removed_buffer_ids: removed_buffer_ids.clone(),
})
cx.emit(EditorEvent::ExcerptsRemoved { ids: ids.clone() })
}
multi_buffer::Event::ExcerptsEdited {
excerpt_ids,
@@ -18291,13 +18219,6 @@ impl Editor {
.and_then(|item| item.to_any().downcast_ref::<T>())
}
pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
let type_id = std::any::TypeId::of::<T>();
self.addons
.get_mut(&type_id)
.and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
}
fn character_size(&self, window: &mut Window) -> gpui::Size<Pixels> {
let text_layout_details = self.text_layout_details(window);
let style = &text_layout_details.editor_style;
@@ -19811,7 +19732,6 @@ pub enum EditorEvent {
},
ExcerptsRemoved {
ids: Vec<ExcerptId>,
removed_buffer_ids: Vec<BufferId>,
},
BufferFoldToggled {
ids: Vec<ExcerptId>,
@@ -20752,8 +20672,24 @@ impl Render for MissingEditPredictionKeybindingTooltip {
pub struct LineHighlight {
pub background: Background,
pub border: Option<gpui::Hsla>,
pub include_gutter: bool,
pub type_id: Option<TypeId>,
}
impl From<Hsla> for LineHighlight {
fn from(hsla: Hsla) -> Self {
Self {
background: hsla.into(),
border: None,
}
}
}
impl From<Background> for LineHighlight {
fn from(background: Background) -> Self {
Self {
background,
border: None,
}
}
}
fn render_diff_hunk_controls(

View File

@@ -5825,13 +5825,6 @@ async fn test_select_next(cx: &mut TestAppContext) {
cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
.unwrap();
cx.assert_editor_state("«abcˇ»\n«abcˇ» «abcˇ»\ndefabc\n«abcˇ»");
// Test selection direction should be preserved
cx.set_state("abc\n«ˇabc» abc\ndefabc\nabc");
cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
.unwrap();
cx.assert_editor_state("abc\n«ˇabc» «ˇabc»\ndefabc\nabc");
}
#[gpui::test]
@@ -6018,25 +6011,6 @@ let «fooˇ» = 2;
let foo = 2;
let foo = «2ˇ»;"#,
);
// Test last selection direction should be preserved
cx.set_state(
r#"let foo = 2;
let foo = 2;
let «fooˇ» = 2;
let «ˇfoo» = 2;
let foo = 2;"#,
);
cx.update_editor(|e, window, cx| e.select_next(&SelectNext::default(), window, cx))
.unwrap();
cx.assert_editor_state(
r#"let foo = 2;
let foo = 2;
let «fooˇ» = 2;
let «ˇfoo» = 2;
let «ˇfoo» = 2;"#,
);
}
#[gpui::test]
@@ -6164,26 +6138,25 @@ async fn test_select_previous_with_single_selection(cx: &mut TestAppContext) {
cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
.unwrap();
// selection direction is preserved
cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
.unwrap();
cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
cx.update_editor(|editor, window, cx| editor.undo_selection(&UndoSelection, window, cx));
cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\nabc");
cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\nabc");
cx.update_editor(|editor, window, cx| editor.redo_selection(&RedoSelection, window, cx));
cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndefabc\n«ˇabc»");
cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndefabc\n«abcˇ»");
cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
.unwrap();
cx.assert_editor_state("«ˇabc»\n«ˇabc» abc\ndef«ˇabc»\n«ˇabc»");
cx.assert_editor_state("«abcˇ»\n«ˇabc» abc\ndef«abcˇ»\n«abcˇ»");
cx.update_editor(|e, window, cx| e.select_previous(&SelectPrevious::default(), window, cx))
.unwrap();
cx.assert_editor_state("«ˇabc»\n«ˇabc» «ˇabc»\ndef«ˇabc»\n«ˇabc»");
cx.assert_editor_state("«abcˇ»\n«ˇabc» «abcˇ»\ndef«abcˇ»\n«abcˇ»");
}
#[gpui::test]
@@ -10446,7 +10419,6 @@ async fn test_completion_in_multibuffer_with_replace_range(cx: &mut TestAppConte
EditorMode::Full {
scale_ui_elements_with_buffer_font_size: false,
show_active_line_background: false,
sized_by_content: false,
},
multi_buffer.clone(),
Some(project.clone()),

View File

@@ -1,7 +1,6 @@
use crate::{
ActiveDiagnostic, BlockId, COLUMNAR_SELECTION_MODIFIERS, CURSORS_VISIBLE_FOR,
ChunkRendererContext, ChunkReplacement, ConflictsOurs, ConflictsOursMarker, ConflictsOuter,
ConflictsTheirs, ConflictsTheirsMarker, ContextMenuPlacement, CursorShape, CustomBlockId,
ChunkRendererContext, ChunkReplacement, ContextMenuPlacement, CursorShape, CustomBlockId,
DisplayDiffHunk, DisplayPoint, DisplayRow, DocumentHighlightRead, DocumentHighlightWrite,
EditDisplayMode, Editor, EditorMode, EditorSettings, EditorSnapshot, EditorStyle,
FILE_HEADER_HEIGHT, FocusedBlock, GutterDimensions, HalfPageDown, HalfPageUp, HandleInput,
@@ -2184,10 +2183,6 @@ impl EditorElement {
window: &mut Window,
cx: &mut App,
) -> Vec<Option<(AnyElement, gpui::Point<Pixels>)>> {
if self.editor.read(cx).disable_expand_excerpt_buttons {
return vec![];
}
let editor_font_size = self.style.text.font_size.to_pixels(window.rem_size()) * 1.2;
let scroll_top = scroll_position.y * line_height;
@@ -4041,7 +4036,6 @@ impl EditorElement {
line_height: Pixels,
scroll_pixel_position: gpui::Point<Pixels>,
display_hunks: &[(DisplayDiffHunk, Option<Hitbox>)],
highlighted_rows: &BTreeMap<DisplayRow, LineHighlight>,
editor: Entity<Editor>,
window: &mut Window,
cx: &mut App,
@@ -4070,22 +4064,6 @@ impl EditorElement {
{
continue;
}
if highlighted_rows
.get(&display_row_range.start)
.and_then(|highlight| highlight.type_id)
.is_some_and(|type_id| {
[
TypeId::of::<ConflictsOuter>(),
TypeId::of::<ConflictsOursMarker>(),
TypeId::of::<ConflictsOurs>(),
TypeId::of::<ConflictsTheirs>(),
TypeId::of::<ConflictsTheirsMarker>(),
]
.contains(&type_id)
})
{
continue;
}
let row_ix = (display_row_range.start - row_range.start).0 as usize;
if row_infos[row_ix].diff_status.is_none() {
continue;
@@ -4280,21 +4258,14 @@ impl EditorElement {
highlight_row_end: DisplayRow,
highlight: crate::LineHighlight,
edges| {
let mut origin_x = layout.hitbox.left();
let mut width = layout.hitbox.size.width;
if !highlight.include_gutter {
origin_x += layout.gutter_hitbox.size.width;
width -= layout.gutter_hitbox.size.width;
}
let origin = point(
origin_x,
layout.hitbox.origin.x,
layout.hitbox.origin.y
+ (highlight_row_start.as_f32() - scroll_top)
* layout.position_map.line_height,
);
let size = size(
width,
layout.hitbox.size.width,
layout.position_map.line_height
* highlight_row_end.next_row().minus(highlight_row_start) as f32,
);
@@ -5541,9 +5512,7 @@ impl EditorElement {
}
fn paint_mouse_listeners(&mut self, layout: &EditorLayout, window: &mut Window, cx: &mut App) {
if !self.editor.read(cx).disable_scrolling {
self.paint_scroll_wheel_listener(layout, window, cx);
}
self.paint_scroll_wheel_listener(layout, window, cx);
window.on_mouse_event({
let position_map = layout.position_map.clone();
@@ -6594,21 +6563,10 @@ impl Element for EditorElement {
},
)
}
EditorMode::Full {
sized_by_content, ..
} => {
EditorMode::Full { .. } => {
let mut style = Style::default();
style.size.width = relative(1.).into();
if sized_by_content {
let snapshot = editor.snapshot(window, cx);
let line_height =
self.style.text.line_height_in_pixels(window.rem_size());
let scroll_height =
(snapshot.max_point().row().next_row().0 as f32) * line_height;
style.size.height = scroll_height.into();
} else {
style.size.height = relative(1.).into();
}
style.size.height = relative(1.).into();
window.request_layout(style, None, cx)
}
};
@@ -6831,16 +6789,10 @@ impl Element for EditorElement {
} else {
background_color.opacity(0.36)
}),
include_gutter: true,
type_id: None,
};
let filled_highlight = LineHighlight {
background: solid_background(background_color.opacity(hunk_opacity)),
border: None,
include_gutter: true,
type_id: None,
};
let filled_highlight =
solid_background(background_color.opacity(hunk_opacity)).into();
let background = if Self::diff_hunk_hollow(diff_status, cx) {
hollow_highlight
@@ -7599,7 +7551,6 @@ impl Element for EditorElement {
line_height,
scroll_pixel_position,
&display_hunks,
&highlighted_rows,
self.editor.clone(),
window,
cx,

View File

@@ -288,7 +288,7 @@ impl FollowableItem for Editor {
}
true
}
EditorEvent::ExcerptsRemoved { ids, .. } => {
EditorEvent::ExcerptsRemoved { ids } => {
update
.deleted_excerpts
.extend(ids.iter().map(ExcerptId::to_proto));

View File

@@ -11,7 +11,6 @@ assistant_tool.workspace = true
assistant_tools.workspace = true
async-trait.workspace = true
async-watch.workspace = true
buffer_diff.workspace = true
chrono.workspace = true
clap.workspace = true
client.workspace = true
@@ -24,7 +23,7 @@ fs.workspace = true
futures.workspace = true
gpui.workspace = true
gpui_tokio.workspace = true
handlebars.workspace = true
handlebars = { workspace = true, features = ["rust-embed"] }
language.workspace = true
language_extension.workspace = true
language_model.workspace = true
@@ -37,6 +36,7 @@ prompt_store.workspace = true
regex.workspace = true
release_channel.workspace = true
reqwest_client.workspace = true
rust-embed.workspace = true
serde.workspace = true
serde_json.workspace = true
settings.workspace = true
@@ -48,6 +48,7 @@ unindent.workspace = true
util.workspace = true
uuid = { version = "1.6", features = ["v4"] }
workspace-hack.workspace = true
[[bin]]
name = "eval"
path = "src/eval.rs"

View File

@@ -1,19 +1,16 @@
use serde::{Deserialize, Serialize};
use std::collections::BTreeSet;
use std::fmt::Write;
use std::fmt::{self};
use crate::example::AssertionGroupId;
#[derive(Default, Debug, Serialize, Deserialize, Clone)]
pub struct AssertionsReport {
pub ran: Vec<Assertion>,
pub groups: BTreeSet<AssertionGroupId>,
pub ran: Vec<RanAssertion>,
pub max: Option<usize>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct Assertion {
pub group_id: AssertionGroupId,
pub struct RanAssertion {
pub id: String,
pub result: Result<RanAssertionResult, String>,
}
@@ -24,12 +21,19 @@ pub struct RanAssertionResult {
}
impl AssertionsReport {
pub fn new(max: Option<usize>) -> Self {
AssertionsReport {
ran: Vec::new(),
max,
}
}
pub fn is_empty(&self) -> bool {
self.ran.is_empty()
}
pub fn total_count(&self) -> usize {
self.run_count()
self.run_count().max(self.max.unwrap_or(0))
}
pub fn run_count(&self) -> usize {
@@ -87,7 +91,7 @@ pub fn display_error_row(f: &mut String, round: usize, error: String) -> fmt::Re
)
}
pub fn display_table_row(f: &mut String, round: usize, assertion: &Assertion) -> fmt::Result {
pub fn display_table_row(f: &mut String, round: usize, assertion: &RanAssertion) -> fmt::Result {
let result = match &assertion.result {
Ok(result) if result.passed => "\x1b[32m✔ Passed\x1b[0m",
Ok(_) => "\x1b[31m✗ Failed\x1b[0m",
@@ -98,7 +102,7 @@ pub fn display_table_row(f: &mut String, round: usize, assertion: &Assertion) ->
f,
"│ {:^ROUND_WIDTH$} │ {:<ASSERTIONS_WIDTH$} │ {:>RESULTS_WIDTH$} │",
round,
truncate(&assertion.group_id.to_string(), ASSERTIONS_WIDTH),
truncate(&assertion.id, ASSERTIONS_WIDTH),
result
)
}

View File

@@ -1,14 +1,14 @@
mod assertions;
mod example;
mod example2;
mod examples;
mod ids;
mod instance;
mod templates;
mod tool_metrics;
use assertions::display_error_row;
use example::ExampleMetadata;
use instance::{ExampleInstance, JudgeOutput, RunOutput, run_git};
use serde::Serialize;
pub(crate) use tool_metrics::*;
use ::fs::RealFs;
@@ -621,7 +621,7 @@ pub fn git_branch_for_path(repo_path: &Path) -> String {
}
async fn judge_example(
instance: ExampleInstance,
example: ExampleInstance,
model: Arc<dyn LanguageModel>,
zed_commit_sha: &str,
zed_branch_name: &str,
@@ -630,41 +630,35 @@ async fn judge_example(
enable_telemetry: bool,
cx: &AsyncApp,
) -> JudgeOutput {
let judge_output = instance.judge(model.clone(), &run_output, cx).await;
let evaluated_example = EvaluatedExample {
example: instance.example.meta(),
run: RunMetadata {
zed_commit_sha: zed_commit_sha.to_string(),
zed_branch_name: zed_branch_name.to_string(),
run_id: run_id.to_string(),
},
run_output: run_output.clone(),
judge_output: judge_output.clone(),
};
let judge_output = example.judge(model.clone(), &run_output, cx).await;
if enable_telemetry {
telemetry::event!("Agent Example Evaluated", evaluated_example);
telemetry::event!(
"Agent Example Evaluated",
zed_commit_sha = zed_commit_sha,
zed_branch_name = zed_branch_name,
run_id = run_id,
example_name = example.name.clone(),
example_repetition = example.repetition,
diff_evaluation = judge_output.diff.clone(),
thread_evaluation = judge_output.thread.clone(),
tool_metrics = run_output.tool_metrics,
response_count = run_output.response_count,
token_usage = run_output.token_usage,
model = model.telemetry_id(),
model_provider = model.provider_id().to_string(),
repository_url = example.repo_url(),
repository_revision = example.revision(),
diagnostic_summary_before = run_output.diagnostic_summary_before,
diagnostic_summary_after = run_output.diagnostic_summary_after,
diagnostics_before = run_output.diagnostics_before,
diagnostics_after = run_output.diagnostics_after,
);
}
judge_output
}
#[derive(Serialize)]
struct EvaluatedExample {
example: ExampleMetadata,
run: RunMetadata,
run_output: RunOutput,
judge_output: JudgeOutput,
}
#[derive(Serialize)]
struct RunMetadata {
zed_commit_sha: String,
zed_branch_name: String,
run_id: String,
}
const HEADER_WIDTH: usize = 65;
fn print_h1(header: &str) {

View File

@@ -1,24 +1,20 @@
use std::{
error::Error,
fmt::{self, Debug},
path::Path,
sync::{Arc, Mutex},
time::Duration,
};
use crate::{
ToolMetrics,
assertions::{Assertion, AssertionsReport, RanAssertionResult},
assertions::{AssertionsReport, RanAssertion, RanAssertionResult},
};
use agent::ThreadEvent;
use anyhow::{Result, anyhow};
use async_trait::async_trait;
use buffer_diff::DiffHunkStatus;
use collections::HashMap;
use futures::{FutureExt as _, StreamExt, channel::mpsc, select_biased};
use gpui::{AppContext, AsyncApp, Entity};
use language_model::{LanguageModel, Role, StopReason};
use serde::{Deserialize, Serialize};
pub const THREAD_EVENT_TIMEOUT: Duration = Duration::from_secs(60 * 2);
@@ -36,19 +32,20 @@ pub trait Example {
#[derive(Clone, Debug)]
pub struct JudgeAssertion {
pub group_id: String,
pub id: String,
pub description: String,
}
#[derive(Clone, Debug, Serialize)]
#[derive(Clone, Debug)]
pub struct ExampleMetadata {
pub name: String,
pub url: String,
pub revision: String,
pub language_server: Option<LanguageServer>,
pub max_assertions: Option<usize>,
}
#[derive(Clone, Debug, Serialize)]
#[derive(Clone, Debug)]
pub struct LanguageServer {
pub file_extension: String,
pub allow_preexisting_diagnostics: bool,
@@ -82,6 +79,7 @@ impl fmt::Display for FailedAssertion {
impl Error for FailedAssertion {}
pub struct ExampleContext {
meta: ExampleMetadata,
log_prefix: String,
agent_thread: Entity<agent::Thread>,
app: AsyncApp,
@@ -92,14 +90,16 @@ pub struct ExampleContext {
impl ExampleContext {
pub fn new(
meta: ExampleMetadata,
log_prefix: String,
agent_thread: Entity<agent::Thread>,
model: Arc<dyn LanguageModel>,
app: AsyncApp,
) -> Self {
let assertions = AssertionsReport::default();
let assertions = AssertionsReport::new(meta.max_assertions);
Self {
meta,
log_prefix,
agent_thread,
assertions,
@@ -117,20 +117,60 @@ impl ExampleContext {
.unwrap();
}
pub fn assertion(&mut self, key: impl Into<String>) -> AssertionGroupId {
let group_id = AssertionGroupId(key.into());
self.assertions.groups.insert(group_id.clone());
group_id
pub fn assert(&mut self, expected: bool, message: impl ToString) -> Result<()> {
let message = message.to_string();
self.log_assertion(
if expected {
Ok(())
} else {
Err(anyhow::Error::from(FailedAssertion(message.clone())))
},
message,
)
}
fn log_assertion<T>(
pub fn assert_some<T>(&mut self, option: Option<T>, message: impl ToString) -> Result<T> {
let message = message.to_string();
self.log_assertion(
match option {
Some(value) => Ok(value),
None => Err(anyhow::Error::from(FailedAssertion(message.clone()))),
},
message,
)
}
#[allow(dead_code)]
pub fn assert_eq<T: PartialEq + Debug>(
&mut self,
group_id: AssertionGroupId,
result: Result<T>,
message: String,
) -> Result<T> {
self.assertions.ran.push(Assertion {
group_id,
left: T,
right: T,
message: impl ToString,
) -> Result<()> {
let message = message.to_string();
self.log_assertion(
if left == right {
Ok(())
} else {
println!("{}{:#?} != {:#?}", self.log_prefix, left, right);
Err(anyhow::Error::from(FailedAssertion(message.clone())))
},
message,
)
}
fn log_assertion<T>(&mut self, result: Result<T>, message: String) -> Result<T> {
if let Some(max) = self.meta.max_assertions {
if self.assertions.run_count() > max {
return Err(anyhow!(
"More assertions were run than the stated max_assertions of {}",
max
));
}
}
self.assertions.ran.push(RanAssertion {
id: message.clone(),
result: Ok(RanAssertionResult {
analysis: None,
passed: result.is_ok(),
@@ -194,9 +234,9 @@ impl ExampleContext {
let mut tool_metrics = tool_metrics.lock().unwrap();
if let Some(tool_result) = thread.tool_result(&tool_use_id) {
let message = if tool_result.is_error {
format!("✖︎ {}", tool_use.name)
format!("TOOL FAILED: {}", tool_use.name)
} else {
format!("✔︎ {}", tool_use.name)
format!("TOOL FINISHED: {}", tool_use.name)
};
println!("{log_prefix}{message}");
tool_metrics
@@ -238,7 +278,7 @@ impl ExampleContext {
let message_count_before = self.app.update_entity(&self.agent_thread, |thread, cx| {
thread.set_remaining_turns(iterations);
thread.send_to_model(model, None, cx);
thread.send_to_model(model, cx);
thread.messages().len()
})?;
@@ -280,103 +320,6 @@ impl ExampleContext {
Ok(response)
}
pub fn edits(&self) -> HashMap<Arc<Path>, FileEdits> {
self.app
.read_entity(&self.agent_thread, |thread, cx| {
let action_log = thread.action_log().read(cx);
HashMap::from_iter(action_log.changed_buffers(cx).into_iter().map(
|(buffer, diff)| {
let snapshot = buffer.read(cx).snapshot();
let file = snapshot.file().unwrap();
let diff = diff.read(cx);
let base_text = diff.base_text().text();
let hunks = diff
.hunks(&snapshot, cx)
.map(|hunk| FileEditHunk {
base_text: base_text[hunk.diff_base_byte_range.clone()].to_string(),
text: snapshot
.text_for_range(hunk.range.clone())
.collect::<String>(),
status: hunk.status(),
})
.collect();
(file.path().clone(), FileEdits { hunks })
},
))
})
.unwrap()
}
}
#[derive(Debug, Serialize, Deserialize, Clone, Ord, PartialOrd, PartialEq, Eq)]
pub struct AssertionGroupId(pub String);
impl AssertionGroupId {
pub fn assert(
&self,
expected: bool,
message: impl ToString,
cx: &mut ExampleContext,
) -> Result<()> {
let message = message.to_string();
cx.log_assertion(
self.clone(),
if expected {
Ok(())
} else {
Err(anyhow::Error::from(FailedAssertion(message.clone())))
},
message,
)
}
pub fn assert_some<T>(
&self,
option: Option<T>,
message: impl ToString,
cx: &mut ExampleContext,
) -> Result<T> {
let message = message.to_string();
cx.log_assertion(
self.clone(),
match option {
Some(value) => Ok(value),
None => Err(anyhow::Error::from(FailedAssertion(message.clone()))),
},
message,
)
}
#[allow(dead_code)]
pub fn assert_eq<T: PartialEq + Debug>(
&self,
left: T,
right: T,
message: impl ToString,
cx: &mut ExampleContext,
) -> Result<()> {
let message = message.to_string();
cx.log_assertion(
self.clone(),
if left == right {
Ok(())
} else {
println!("{}{:#?} != {:#?}", cx.log_prefix, left, right);
Err(anyhow::Error::from(FailedAssertion(message.clone())))
},
message,
)
}
}
impl fmt::Display for AssertionGroupId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Display::fmt(&self.0, f)
}
}
#[derive(Debug)]
@@ -391,7 +334,6 @@ impl Response {
pub fn expect_tool(
&self,
group_id: AssertionGroupId,
tool_name: &'static str,
cx: &mut ExampleContext,
) -> Result<&ToolUse> {
@@ -400,11 +342,7 @@ impl Response {
.iter()
.find(|tool_use| tool_use.name == tool_name)
});
group_id.assert_some(result, format!("called `{}`", tool_name), cx)
}
pub fn tool_uses(&self) -> impl Iterator<Item = &ToolUse> {
self.messages.iter().flat_map(|msg| &msg.tool_use)
cx.assert_some(result, format!("called `{}`", tool_name))
}
}
@@ -417,37 +355,17 @@ pub struct Message {
#[derive(Debug)]
pub struct ToolUse {
pub name: String,
name: String,
value: serde_json::Value,
}
impl ToolUse {
pub fn parse_input<Input>(&self) -> Result<Input>
pub fn expect_input<Input>(&self, cx: &mut ExampleContext) -> Result<Input>
where
Input: for<'de> serde::Deserialize<'de>,
{
serde_json::from_value::<Input>(self.value.clone()).map_err(|err| anyhow!(err))
}
}
#[derive(Debug)]
pub struct FileEdits {
hunks: Vec<FileEditHunk>,
}
#[derive(Debug)]
struct FileEditHunk {
base_text: String,
text: String,
status: DiffHunkStatus,
}
impl FileEdits {
pub fn has_added_line(&self, line: &str) -> bool {
self.hunks.iter().any(|hunk| {
hunk.status == DiffHunkStatus::added_none()
&& hunk.base_text.is_empty()
&& hunk.text.contains(line)
})
let result =
serde_json::from_value::<Input>(self.value.clone()).map_err(|err| anyhow!(err));
cx.log_assertion(result, format!("valid `{}` input", &self.name))
}
}

251
crates/eval/src/example2.rs Normal file
View File

@@ -0,0 +1,251 @@
use crate::{example::LanguageServer, examples::ExampleToml};
use collections::BTreeMap;
use futures::future::LocalBoxFuture;
use gpui::SharedString;
use smol::future::FutureExt;
use std::{fs, path::Path, sync::Arc};
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct ExampleId(SharedString);
impl<T: Into<SharedString>> From<T> for ExampleId {
fn from(value: T) -> Self {
Self(value.into())
}
}
#[derive(Clone, Debug)]
pub struct ExampleMetadata {
pub id: ExampleId,
pub url: String,
pub revision: String,
pub language_server: Option<LanguageServer>,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct AssertionId(SharedString);
impl<T: Into<SharedString>> From<T> for AssertionId {
fn from(value: T) -> Self {
Self(value.into())
}
}
#[derive(Default)]
pub struct ExampleSetup {
assertions: BTreeMap<AssertionId, Vec<Assertion>>,
}
impl ExampleSetup {
fn assertion(&mut self, id: impl Into<SharedString>) -> AssertionId {
todo!()
}
}
struct Assertion {
condition: bool,
message: SharedString,
}
struct ExampleRun {
metadata: ExampleMetadata,
example: Option<Arc<dyn ErasedExample>>,
assertions: BTreeMap<AssertionId, Vec<Assertion>>,
}
impl ExampleRun {
pub fn assert(
&mut self,
assertion_id: &AssertionId,
condition: bool,
message: impl Into<SharedString>,
) {
self.assertions
.get_mut(&assertion_id)
.expect("assertion not found")
.push(Assertion {
condition,
message: message.into(),
});
}
pub fn evaluate(&mut self) {
let example = self.example.take().unwrap();
example.evaluate(self);
self.example = Some(example);
}
}
trait Example: 'static {
fn metadata() -> ExampleMetadata
where
Self: Sized;
fn new(cx: &mut ExampleSetup) -> Self
where
Self: Sized;
async fn evaluate(&self, cx: &mut ExampleRun) -> anyhow::Result<()>;
}
trait ErasedExample {
fn evaluate<'a>(&'a self, cx: &'a mut ExampleRun) -> LocalBoxFuture<'a, anyhow::Result<()>>;
}
struct Erased<T>(T);
impl<T: Example> ErasedExample for Erased<T> {
fn evaluate<'a>(&'a self, cx: &'a mut ExampleRun) -> LocalBoxFuture<'a, anyhow::Result<()>> {
self.0.evaluate(cx).boxed_local()
}
}
struct MyExample {
my_assertion: AssertionId,
}
impl Example for MyExample {
fn metadata() -> ExampleMetadata
where
Self: Sized,
{
ExampleMetadata {
id: "my_example".into(),
url: "".into(),
revision: "".into(),
language_server: None,
}
}
fn new(cx: &mut ExampleSetup) -> Self
where
Self: Sized,
{
Self {
my_assertion: cx.assertion("doesnt_do_stupid_thing"),
}
}
async fn evaluate(&self, cx: &mut ExampleRun) -> anyhow::Result<()> {
cx.assert(&self.my_assertion, true, "true was false");
Ok(())
}
}
struct TomlExample {
diff_assertions: BTreeMap<AssertionId, SharedString>,
thread_assertions: BTreeMap<AssertionId, SharedString>,
}
impl ErasedExample for TomlExample {
fn evaluate<'a>(&'a self, cx: &'a mut ExampleRun) -> LocalBoxFuture<'a, anyhow::Result<()>> {
// let sample = cx.sample().await?;
// for (assertion_id, condition) in self.diff_assertions.iter() {
// let prompt = diff_prompt.render(diff, condition);
// cx.judge(assertion_id, prompt).await?;
// }
// for (assertion_id, condition) in self.diff_assertions.iter() {
// cx.judge(assertion_id, condition).await?;
// }
todo!()
}
}
#[derive(Default)]
struct EvalSuite {
example_builders: BTreeMap<ExampleId, Box<dyn Fn() -> ExampleRun>>,
}
impl EvalSuite {
pub fn examples_to_run(&self, trials: usize) -> BTreeMap<ExampleId, Vec<ExampleRun>> {
let mut examples = BTreeMap::new();
for (example_id, builder) in &self.example_builders {
examples.insert(
example_id.clone(),
(0..trials).map(|_| (builder)()).collect(),
);
}
examples
}
pub fn register<T: Example>(&mut self) {
let metadata = T::metadata();
self.example_builders.insert(
metadata.id.clone(),
Box::new(move || {
let mut cx = ExampleSetup::default();
let example = T::new(&mut cx);
ExampleRun {
metadata: metadata.clone(),
example: Some(Arc::new(Erased(example))),
assertions: cx.assertions,
}
}),
);
}
pub fn register_toml(&mut self, toml_abs_path: &Path) {
let name = toml_abs_path
.file_name()
.unwrap()
.to_string_lossy()
.to_string();
let id = ExampleId(name.into());
let base: ExampleToml =
toml::from_str(&fs::read_to_string(&toml_abs_path).unwrap()).unwrap();
let language_server = if base.require_lsp {
Some(crate::example::LanguageServer {
file_extension: base
.language_extension
.expect("Language extension is required when require_lsp = true"),
allow_preexisting_diagnostics: base.allow_preexisting_diagnostics,
})
} else {
None
};
let metadata = ExampleMetadata {
id: id.clone(),
url: base.url,
revision: base.revision,
language_server,
};
self.example_builders.insert(
id,
Box::new(move || {
let mut cx = ExampleSetup::default();
let mut diff_assertions = BTreeMap::new();
let mut thread_assertions = BTreeMap::new();
for (assertion_key, condition) in base.diff_assertions.iter() {
let assertion_id = cx.assertion(assertion_key);
diff_assertions.insert(assertion_id, condition.into());
}
for (assertion_key, condition) in base.thread_assertions.iter() {
let assertion_id = cx.assertion(assertion_key);
thread_assertions.insert(assertion_id.into(), condition.into());
}
ExampleRun {
metadata: metadata.clone(),
example: Some(Arc::new(TomlExample {
diff_assertions,
thread_assertions,
})),
assertions: cx.assertions,
}
}),
);
}
}
fn main() {
// let mut suite = EvalSuite::default();
// suite.register::<MyExample>();
// for path in list_files(..) {
// suite.register_static()
// }
// for (example_id, runs) in suite.examples_to_run(2) {}
}

View File

@@ -1,161 +0,0 @@
use std::{collections::HashSet, path::Path};
use anyhow::Result;
use assistant_tools::{CreateFileToolInput, EditFileToolInput, ReadFileToolInput};
use async_trait::async_trait;
use crate::example::{Example, ExampleContext, ExampleMetadata, JudgeAssertion, LanguageServer};
pub struct AddArgToTraitMethod;
#[async_trait(?Send)]
impl Example for AddArgToTraitMethod {
fn meta(&self) -> ExampleMetadata {
ExampleMetadata {
name: "add_arg_to_trait_method".to_string(),
url: "https://github.com/zed-industries/zed.git".to_string(),
revision: "f69aeb6311dde3c0b8979c293d019d66498d54f2".to_string(),
language_server: Some(LanguageServer {
file_extension: "rs".to_string(),
allow_preexisting_diagnostics: false,
}),
}
}
async fn conversation(&self, cx: &mut ExampleContext) -> Result<()> {
let read_before_edit = cx.assertion("read_before_edit");
let added_any_param = cx.assertion("added_any_param");
let added_unused_param = cx.assertion("added_unused_param");
let added_used_param_to_batch_tool = cx.assertion("added_used_param_to_batch_tool");
const FILENAME: &str = "assistant_tool.rs";
cx.push_user_message(format!(
r#"
Add a `window: Option<gpui::AnyWindowHandle>` argument to the `Tool::run` trait method in {FILENAME},
and update all the implementations of the trait and call sites accordingly.
"#
));
let response = cx.run_to_end().await?;
// Reads files before it edits them
let mut read_files = HashSet::new();
for tool_use in response.tool_uses() {
match tool_use.name.as_str() {
"read_file" => {
if let Ok(input) = tool_use.parse_input::<ReadFileToolInput>() {
read_files.insert(input.path);
}
}
"create_file" => {
if let Ok(input) = tool_use.parse_input::<CreateFileToolInput>() {
read_files.insert(input.path);
}
}
"edit_file" => {
if let Ok(input) = tool_use.parse_input::<EditFileToolInput>() {
read_before_edit
.assert(
read_files.contains(input.path.to_str().unwrap()),
format!(
"Read before edit: {}",
&input.path.file_stem().unwrap().to_str().unwrap()
),
cx,
)
.ok();
}
}
_ => {}
}
}
// Adds ignored argument to all but `batch_tool`
let add_ignored_window_paths = &[
"code_action_tool",
"code_symbols_tool",
"contents_tool",
"copy_path_tool",
"create_directory_tool",
"create_file_tool",
"delete_path_tool",
"diagnostics_tool",
"edit_file_tool",
"fetch_tool",
"grep_tool",
"list_directory_tool",
"move_path_tool",
"now_tool",
"open_tool",
"path_search_tool",
"read_file_tool",
"rename_tool",
"symbol_info_tool",
"terminal_tool",
"thinking_tool",
"web_search_tool",
];
let edits = cx.edits();
for tool_name in add_ignored_window_paths {
let path_str = format!("crates/assistant_tools/src/{}.rs", tool_name);
let edits = edits.get(Path::new(&path_str));
let ignored = edits.map_or(false, |edits| {
edits.has_added_line(" _window: Option<gpui::AnyWindowHandle>,\n")
});
let uningored = edits.map_or(false, |edits| {
edits.has_added_line(" window: Option<gpui::AnyWindowHandle>,\n")
});
added_any_param
.assert(
ignored || uningored,
format!("Argument: {}", tool_name),
cx,
)
.ok();
added_unused_param
.assert(ignored, format!("`_` prefix: {}", tool_name), cx)
.ok();
}
// Adds unignored argument to `batch_tool`
let batch_tool_edits = edits.get(Path::new("crates/assistant_tools/src/batch_tool.rs"));
added_used_param_to_batch_tool
.assert(
batch_tool_edits.map_or(false, |edits| {
edits.has_added_line(" window: Option<gpui::AnyWindowHandle>,\n")
}),
"Argument: batch_tool",
cx,
)
.ok();
Ok(())
}
fn diff_assertions(&self) -> Vec<JudgeAssertion> {
vec![
JudgeAssertion {
group_id: "batch tool passes window to each".to_string(),
description:
"batch_tool is modified to pass a clone of the window to each tool it calls."
.to_string(),
},
JudgeAssertion {
group_id: "tool tests updated".to_string(),
description:
"tool tests are updated to pass the new `window` argument (`None` is ok)."
.to_string(),
},
]
}
}

View File

@@ -15,14 +15,11 @@ impl Example for FileSearchExample {
url: "https://github.com/zed-industries/zed.git".to_string(),
revision: "03ecb88fe30794873f191ddb728f597935b3101c".to_string(),
language_server: None,
max_assertions: Some(4),
}
}
async fn conversation(&self, cx: &mut ExampleContext) -> Result<()> {
let ends_with_filename = cx.assertion("ends_with_filename");
let correct_glob = cx.assertion("correct_glob");
let used_path_search = cx.assertion("used_path_search");
const FILENAME: &str = "find_replace_file_tool.rs";
cx.push_user_message(format!(
r#"
@@ -35,14 +32,13 @@ impl Example for FileSearchExample {
));
let response = cx.run_turn().await?;
let tool_use = response.expect_tool(used_path_search, "path_search", cx)?;
let input = tool_use.parse_input::<PathSearchToolInput>()?;
let tool_use = response.expect_tool("path_search", cx)?;
let input = tool_use.expect_input::<PathSearchToolInput>(cx)?;
let glob = input.glob;
ends_with_filename.assert(
cx.assert(
glob.ends_with(FILENAME),
format!("glob ends with `{FILENAME}`"),
cx,
)?;
let without_filename = glob.replace(FILENAME, "");
@@ -50,7 +46,7 @@ impl Example for FileSearchExample {
.unwrap()
.is_match(&without_filename);
correct_glob.assert(matches, "glob starts with either `**` or `zed`", cx)?;
cx.assert(matches, "glob starts with either `**` or `zed`")?;
Ok(())
}

View File

@@ -11,14 +11,10 @@ use util::serde::default_true;
use crate::example::{Example, ExampleContext, ExampleMetadata, JudgeAssertion};
mod add_arg_to_trait_method;
mod file_search;
pub fn all(examples_dir: &Path) -> Vec<Rc<dyn Example>> {
let mut threads: Vec<Rc<dyn Example>> = vec![
Rc::new(file_search::FileSearchExample),
Rc::new(add_arg_to_trait_method::AddArgToTraitMethod),
];
let mut threads: Vec<Rc<dyn Example>> = vec![Rc::new(file_search::FileSearchExample)];
for example_path in list_declarative_examples(examples_dir).unwrap() {
threads.push(Rc::new(DeclarativeExample::load(&example_path).unwrap()));
@@ -37,14 +33,14 @@ struct DeclarativeExample {
impl DeclarativeExample {
pub fn load(example_path: &Path) -> Result<Self> {
let name = Self::name_from_path(example_path);
let toml: ExampleToml = toml::from_str(&fs::read_to_string(&example_path)?)?;
let base: ExampleToml = toml::from_str(&fs::read_to_string(&example_path)?)?;
let language_server = if toml.require_lsp {
let language_server = if base.require_lsp {
Some(crate::example::LanguageServer {
file_extension: toml
file_extension: base
.language_extension
.expect("Language extension is required when require_lsp = true"),
allow_preexisting_diagnostics: toml.allow_preexisting_diagnostics,
allow_preexisting_diagnostics: base.allow_preexisting_diagnostics,
})
} else {
None
@@ -52,29 +48,24 @@ impl DeclarativeExample {
let metadata = ExampleMetadata {
name,
url: toml.url,
revision: toml.revision,
url: base.url,
revision: base.revision,
language_server,
max_assertions: None,
};
Ok(DeclarativeExample {
metadata,
prompt: toml.prompt,
thread_assertions: toml
prompt: base.prompt,
thread_assertions: base
.thread_assertions
.into_iter()
.map(|(id, description)| JudgeAssertion {
group_id: id,
description,
})
.map(|(id, description)| JudgeAssertion { id, description })
.collect(),
diff_assertions: toml
diff_assertions: base
.diff_assertions
.into_iter()
.map(|(id, description)| JudgeAssertion {
group_id: id,
description,
})
.map(|(id, description)| JudgeAssertion { id, description })
.collect(),
})
}

View File

@@ -29,15 +29,15 @@ use util::ResultExt as _;
use util::command::new_smol_command;
use util::markdown::MarkdownString;
use crate::assertions::{Assertion, AssertionsReport, RanAssertionResult};
use crate::example::{AssertionGroupId, Example, ExampleContext, FailedAssertion, JudgeAssertion};
use crate::assertions::{AssertionsReport, RanAssertion, RanAssertionResult};
use crate::example::{Example, ExampleContext, FailedAssertion, JudgeAssertion};
use crate::{AgentAppState, ToolMetrics};
pub const ZED_REPO_URL: &str = "https://github.com/zed-industries/zed.git";
#[derive(Clone)]
pub struct ExampleInstance {
pub example: Rc<dyn Example>,
pub thread: Rc<dyn Example>,
pub name: String,
pub run_directory: PathBuf,
pub log_prefix: String,
@@ -100,7 +100,7 @@ impl ExampleInstance {
Self {
name,
example: thread,
thread,
log_prefix: String::new(),
run_directory,
repetition,
@@ -110,7 +110,11 @@ impl ExampleInstance {
}
pub fn repo_url(&self) -> String {
self.example.meta().url
self.thread.meta().url
}
pub fn revision(&self) -> String {
self.thread.meta().revision
}
pub fn worktree_name(&self) -> String {
@@ -128,7 +132,7 @@ impl ExampleInstance {
/// Set up the example by checking out the specified Git revision
pub async fn fetch(&mut self) -> Result<()> {
let meta = self.example.meta();
let meta = self.thread.meta();
let revision_exists = run_git(
&self.repo_path,
@@ -151,7 +155,7 @@ impl ExampleInstance {
/// Set up the example by checking out the specified Git revision
pub async fn setup(&mut self) -> Result<()> {
let worktree_path = self.worktree_path();
let meta = self.example.meta();
let meta = self.thread.meta();
if worktree_path.is_dir() {
println!("{}Resetting existing worktree", self.log_prefix);
@@ -190,7 +194,7 @@ impl ExampleInstance {
pub fn worktree_path(&self) -> PathBuf {
self.worktrees_dir
.join(self.worktree_name())
.join(self.example.meta().repo_name())
.join(self.thread.meta().repo_name())
}
pub fn run(
@@ -216,7 +220,7 @@ impl ExampleInstance {
let tools = cx.new(|_| ToolWorkingSet::default());
let thread_store =
ThreadStore::load(project.clone(), tools, app_state.prompt_builder.clone(), cx);
let meta = self.example.meta();
let meta = self.thread.meta();
let this = self.clone();
cx.spawn(async move |cx| {
@@ -349,8 +353,8 @@ impl ExampleInstance {
});
})?;
let mut example_cx = ExampleContext::new(this.log_prefix.clone(), thread.clone(), model.clone(), cx.clone());
let result = this.example.conversation(&mut example_cx).await;
let mut example_cx = ExampleContext::new(meta.clone(), this.log_prefix.clone(), thread.clone(), model.clone(), cx.clone());
let result = this.thread.conversation(&mut example_cx).await;
if let Err(err) = result {
if !err.is::<FailedAssertion>() {
@@ -424,7 +428,7 @@ impl ExampleInstance {
let worktree_path = self.worktree_path();
run_git(&worktree_path, &["add", "."]).await?;
let mut diff_args = vec!["diff", "--staged"];
if self.example.meta().url == ZED_REPO_URL {
if self.thread.meta().url == ZED_REPO_URL {
diff_args.push(":(exclude).rules");
}
run_git(&worktree_path, &diff_args).await
@@ -465,7 +469,7 @@ impl ExampleInstance {
run_output: &RunOutput,
cx: &AsyncApp,
) -> (String, AssertionsReport) {
let diff_assertions = self.example.diff_assertions();
let diff_assertions = self.thread.diff_assertions();
if diff_assertions.is_empty() {
return (
@@ -476,8 +480,8 @@ impl ExampleInstance {
println!("{}Running diff judge", self.log_prefix);
let judge_diff_prompt = include_str!("judge_diff_prompt.hbs");
let judge_diff_prompt_name = "judge_diff_prompt";
let judge_diff_prompt = include_str!("templates/judge_diff.hbs");
let judge_diff_prompt_name = "judge_diff";
let mut hbs = Handlebars::new();
hbs.register_template_string(judge_diff_prompt_name, judge_diff_prompt)
.unwrap();
@@ -512,7 +516,7 @@ impl ExampleInstance {
run_output: &RunOutput,
cx: &AsyncApp,
) -> (String, AssertionsReport) {
let thread_assertions = self.example.thread_assertions();
let thread_assertions = self.thread.thread_assertions();
if thread_assertions.is_empty() {
return (
@@ -521,7 +525,7 @@ impl ExampleInstance {
);
}
let judge_thread_prompt = include_str!("judge_thread_prompt.hbs");
let judge_thread_prompt = include_str!("templates/judge_thread.hbs");
let judge_diff_prompt_name = "judge_thread_prompt";
let mut hbs = Handlebars::new();
hbs.register_template_string(judge_diff_prompt_name, judge_thread_prompt)
@@ -587,15 +591,15 @@ impl ExampleInstance {
};
if result.is_ok() {
println!("{}{}", log_prefix, assertion.group_id);
println!("{}{}", log_prefix, assertion.id);
} else {
println!("{}{}", log_prefix, assertion.group_id);
println!("{}{}", log_prefix, assertion.id);
}
(
response,
Assertion {
group_id: AssertionGroupId(assertion.group_id),
RanAssertion {
id: assertion.id,
result,
},
)
@@ -606,7 +610,7 @@ impl ExampleInstance {
let mut report = AssertionsReport::default();
for (response, assertion) in future::join_all(assertions).await {
writeln!(&mut responses, "# {}", assertion.group_id).unwrap();
writeln!(&mut responses, "# {}", assertion.id).unwrap();
writeln!(&mut responses, "{}\n\n", response).unwrap();
report.ran.push(assertion);
}

View File

@@ -0,0 +1,43 @@
use anyhow::Result;
use handlebars::Handlebars;
use rust_embed::RustEmbed;
use serde::Serialize;
use std::sync::Arc;
#[derive(RustEmbed)]
#[folder = "src/templates"]
#[include = "*.hbs"]
struct Assets;
pub struct Templates(Handlebars<'static>);
impl Templates {
pub fn new() -> Arc<Self> {
let mut handlebars = Handlebars::new();
handlebars.register_embed_templates::<Assets>().unwrap();
// Disable HTML escaping so you don't .
handlebars.register_escape_fn(|text| text.into());
Arc::new(Self(handlebars))
}
}
pub trait Template: Sized {
const TEMPLATE_NAME: &'static str;
fn render(&self, templates: &Templates) -> Result<String>
where
Self: Serialize + Sized,
{
Ok(templates.0.render(Self::TEMPLATE_NAME, self)?)
}
}
#[derive(Serialize)]
pub struct JudgeDiffTemplate {
repository_diff: String,
assertion: String,
}
impl Template for JudgeDiffTemplate {
const TEMPLATE_NAME: &'static str = "judge_diff.hbs";
}

View File

@@ -1,12 +1,7 @@
You are an expert software developer. Your task is to evaluate a diff produced by an AI agent
in response to a prompt. Here is the prompt and the diff:
<prompt>
{{{prompt}}}
</prompt>
You are an expert software developer. Your task is to evaluate a diff produced by an AI agent. Here is the prompt and the diff:
<diff>
{{{repository_diff}}}
{{repository_diff}}
</diff>
Evaluate whether or not the diff passes the following assertion:

View File

@@ -84,6 +84,11 @@ impl FeatureFlag for ZedPro {
const NAME: &'static str = "zed-pro";
}
pub struct ZedProWebSearchTool {}
impl FeatureFlag for ZedProWebSearchTool {
const NAME: &'static str = "zed-pro-web-search-tool";
}
pub struct NotebookFeatureFlag;
impl FeatureFlag for NotebookFeatureFlag {

View File

@@ -34,7 +34,6 @@ pub struct FakeGitRepositoryState {
pub blames: HashMap<RepoPath, Blame>,
pub current_branch_name: Option<String>,
pub branches: HashSet<String>,
pub merge_head_shas: Vec<String>,
pub simulated_index_write_error_message: Option<String>,
}
@@ -48,20 +47,12 @@ impl FakeGitRepositoryState {
blames: Default::default(),
current_branch_name: Default::default(),
branches: Default::default(),
merge_head_shas: Default::default(),
simulated_index_write_error_message: Default::default(),
}
}
}
impl FakeGitRepository {
fn with_state<F, T>(&self, write: bool, f: F) -> Result<T>
where
F: FnOnce(&mut FakeGitRepositoryState) -> T,
{
self.fs.with_git_state(&self.dot_git_path, write, f)
}
fn with_state_async<F, T>(&self, write: bool, f: F) -> BoxFuture<'static, Result<T>>
where
F: 'static + Send + FnOnce(&mut FakeGitRepositoryState) -> Result<T>,
@@ -146,18 +137,11 @@ impl GitRepository for FakeGitRepository {
}
fn merge_head_shas(&self) -> Vec<String> {
self.with_state(false, |state| state.merge_head_shas.clone())
.unwrap()
vec![]
}
fn show(&self, commit: String) -> BoxFuture<Result<CommitDetails>> {
async {
Ok(CommitDetails {
sha: commit.into(),
..Default::default()
})
}
.boxed()
fn show(&self, _commit: String) -> BoxFuture<Result<CommitDetails>> {
unimplemented!()
}
fn reset(

View File

@@ -133,7 +133,7 @@ pub struct CommitSummary {
pub has_parent: bool,
}
#[derive(Clone, Debug, Default, Hash, PartialEq, Eq)]
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct CommitDetails {
pub sha: SharedString,
pub message: SharedString,

View File

@@ -1,473 +0,0 @@
use collections::{HashMap, HashSet};
use editor::{
ConflictsOurs, ConflictsOursMarker, ConflictsOuter, ConflictsTheirs, ConflictsTheirsMarker,
Editor, EditorEvent, ExcerptId, MultiBuffer, RowHighlightOptions,
display_map::{BlockContext, BlockPlacement, BlockProperties, BlockStyle, CustomBlockId},
};
use gpui::{
App, Context, Entity, InteractiveElement as _, ParentElement as _, Subscription, WeakEntity,
};
use language::{Anchor, Buffer, BufferId};
use project::{ConflictRegion, ConflictSet, ConflictSetUpdate};
use std::{ops::Range, sync::Arc};
use ui::{
ActiveTheme, AnyElement, Element as _, StatefulInteractiveElement, Styled,
StyledTypography as _, div, h_flex, rems,
};
pub(crate) struct ConflictAddon {
buffers: HashMap<BufferId, BufferConflicts>,
}
impl ConflictAddon {
pub(crate) fn conflict_set(&self, buffer_id: BufferId) -> Option<Entity<ConflictSet>> {
self.buffers
.get(&buffer_id)
.map(|entry| entry.conflict_set.clone())
}
}
struct BufferConflicts {
block_ids: Vec<(Range<Anchor>, CustomBlockId)>,
conflict_set: Entity<ConflictSet>,
_subscription: Subscription,
}
impl editor::Addon for ConflictAddon {
fn to_any(&self) -> &dyn std::any::Any {
self
}
fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
Some(self)
}
}
pub fn register_editor(editor: &mut Editor, buffer: Entity<MultiBuffer>, cx: &mut Context<Editor>) {
// Only show conflict UI for singletons and in the project diff.
if !editor.buffer().read(cx).is_singleton()
&& !editor.buffer().read(cx).all_diff_hunks_expanded()
{
return;
}
editor.register_addon(ConflictAddon {
buffers: Default::default(),
});
let buffers = buffer.read(cx).all_buffers().clone();
for buffer in buffers {
buffer_added(editor, buffer, cx);
}
cx.subscribe(&cx.entity(), |editor, _, event, cx| match event {
EditorEvent::ExcerptsAdded { buffer, .. } => buffer_added(editor, buffer.clone(), cx),
EditorEvent::ExcerptsExpanded { ids } => {
let multibuffer = editor.buffer().read(cx).snapshot(cx);
for excerpt_id in ids {
let Some(buffer) = multibuffer.buffer_for_excerpt(*excerpt_id) else {
continue;
};
let addon = editor.addon::<ConflictAddon>().unwrap();
let Some(conflict_set) = addon.conflict_set(buffer.remote_id()).clone() else {
return;
};
excerpt_for_buffer_updated(editor, conflict_set, cx);
}
}
EditorEvent::ExcerptsRemoved {
removed_buffer_ids, ..
} => buffers_removed(editor, removed_buffer_ids, cx),
_ => {}
})
.detach();
}
fn excerpt_for_buffer_updated(
editor: &mut Editor,
conflict_set: Entity<ConflictSet>,
cx: &mut Context<Editor>,
) {
let conflicts_len = conflict_set.read(cx).snapshot().conflicts.len();
conflicts_updated(
editor,
conflict_set,
&ConflictSetUpdate {
buffer_range: None,
old_range: 0..conflicts_len,
new_range: 0..conflicts_len,
},
cx,
);
}
fn buffer_added(editor: &mut Editor, buffer: Entity<Buffer>, cx: &mut Context<Editor>) {
let Some(project) = &editor.project else {
return;
};
let git_store = project.read(cx).git_store().clone();
let buffer_conflicts = editor
.addon_mut::<ConflictAddon>()
.unwrap()
.buffers
.entry(buffer.read(cx).remote_id())
.or_insert_with(|| {
let conflict_set = git_store.update(cx, |git_store, cx| {
git_store.open_conflict_set(buffer.clone(), cx)
});
let subscription = cx.subscribe(&conflict_set, conflicts_updated);
BufferConflicts {
block_ids: Vec::new(),
conflict_set: conflict_set.clone(),
_subscription: subscription,
}
});
let conflict_set = buffer_conflicts.conflict_set.clone();
let conflicts_len = conflict_set.read(cx).snapshot().conflicts.len();
let addon_conflicts_len = buffer_conflicts.block_ids.len();
conflicts_updated(
editor,
conflict_set,
&ConflictSetUpdate {
buffer_range: None,
old_range: 0..addon_conflicts_len,
new_range: 0..conflicts_len,
},
cx,
);
}
fn buffers_removed(editor: &mut Editor, removed_buffer_ids: &[BufferId], cx: &mut Context<Editor>) {
let mut removed_block_ids = HashSet::default();
editor
.addon_mut::<ConflictAddon>()
.unwrap()
.buffers
.retain(|buffer_id, buffer| {
if removed_buffer_ids.contains(&buffer_id) {
removed_block_ids.extend(buffer.block_ids.iter().map(|(_, block_id)| *block_id));
false
} else {
true
}
});
editor.remove_blocks(removed_block_ids, None, cx);
}
fn conflicts_updated(
editor: &mut Editor,
conflict_set: Entity<ConflictSet>,
event: &ConflictSetUpdate,
cx: &mut Context<Editor>,
) {
let buffer_id = conflict_set.read(cx).snapshot.buffer_id;
let conflict_set = conflict_set.read(cx).snapshot();
let multibuffer = editor.buffer().read(cx);
let snapshot = multibuffer.snapshot(cx);
let excerpts = multibuffer.excerpts_for_buffer(buffer_id, cx);
let Some(buffer_snapshot) = excerpts
.first()
.and_then(|(excerpt_id, _)| snapshot.buffer_for_excerpt(*excerpt_id))
else {
return;
};
// Remove obsolete highlights and blocks
let conflict_addon = editor.addon_mut::<ConflictAddon>().unwrap();
if let Some(buffer_conflicts) = conflict_addon.buffers.get_mut(&buffer_id) {
let old_conflicts = buffer_conflicts.block_ids[event.old_range.clone()].to_owned();
let mut removed_highlighted_ranges = Vec::new();
let mut removed_block_ids = HashSet::default();
for (conflict_range, block_id) in old_conflicts {
let Some((excerpt_id, _)) = excerpts.iter().find(|(_, range)| {
let precedes_start = range
.context
.start
.cmp(&conflict_range.start, &buffer_snapshot)
.is_le();
let follows_end = range
.context
.end
.cmp(&conflict_range.start, &buffer_snapshot)
.is_ge();
precedes_start && follows_end
}) else {
continue;
};
let excerpt_id = *excerpt_id;
let Some(range) = snapshot
.anchor_in_excerpt(excerpt_id, conflict_range.start)
.zip(snapshot.anchor_in_excerpt(excerpt_id, conflict_range.end))
.map(|(start, end)| start..end)
else {
continue;
};
removed_highlighted_ranges.push(range.clone());
removed_block_ids.insert(block_id);
}
editor.remove_highlighted_rows::<ConflictsOuter>(removed_highlighted_ranges.clone(), cx);
editor.remove_highlighted_rows::<ConflictsOurs>(removed_highlighted_ranges.clone(), cx);
editor
.remove_highlighted_rows::<ConflictsOursMarker>(removed_highlighted_ranges.clone(), cx);
editor.remove_highlighted_rows::<ConflictsTheirs>(removed_highlighted_ranges.clone(), cx);
editor.remove_highlighted_rows::<ConflictsTheirsMarker>(
removed_highlighted_ranges.clone(),
cx,
);
editor.remove_blocks(removed_block_ids, None, cx);
}
// Add new highlights and blocks
let editor_handle = cx.weak_entity();
let new_conflicts = &conflict_set.conflicts[event.new_range.clone()];
let mut blocks = Vec::new();
for conflict in new_conflicts {
let Some((excerpt_id, _)) = excerpts.iter().find(|(_, range)| {
let precedes_start = range
.context
.start
.cmp(&conflict.range.start, &buffer_snapshot)
.is_le();
let follows_end = range
.context
.end
.cmp(&conflict.range.start, &buffer_snapshot)
.is_ge();
precedes_start && follows_end
}) else {
continue;
};
let excerpt_id = *excerpt_id;
update_conflict_highlighting(editor, conflict, &snapshot, excerpt_id, cx);
let Some(anchor) = snapshot.anchor_in_excerpt(excerpt_id, conflict.range.start) else {
continue;
};
let editor_handle = editor_handle.clone();
blocks.push(BlockProperties {
placement: BlockPlacement::Above(anchor),
height: Some(1),
style: BlockStyle::Fixed,
render: Arc::new({
let conflict = conflict.clone();
move |cx| render_conflict_buttons(&conflict, excerpt_id, editor_handle.clone(), cx)
}),
priority: 0,
})
}
let new_block_ids = editor.insert_blocks(blocks, None, cx);
let conflict_addon = editor.addon_mut::<ConflictAddon>().unwrap();
if let Some(buffer_conflicts) = conflict_addon.buffers.get_mut(&buffer_id) {
buffer_conflicts.block_ids.splice(
event.old_range.clone(),
new_conflicts
.iter()
.map(|conflict| conflict.range.clone())
.zip(new_block_ids),
);
}
}
fn update_conflict_highlighting(
editor: &mut Editor,
conflict: &ConflictRegion,
buffer: &editor::MultiBufferSnapshot,
excerpt_id: editor::ExcerptId,
cx: &mut Context<Editor>,
) {
log::debug!("update conflict highlighting for {conflict:?}");
let theme = cx.theme().clone();
let colors = theme.colors();
let outer_start = buffer
.anchor_in_excerpt(excerpt_id, conflict.range.start)
.unwrap();
let outer_end = buffer
.anchor_in_excerpt(excerpt_id, conflict.range.end)
.unwrap();
let our_start = buffer
.anchor_in_excerpt(excerpt_id, conflict.ours.start)
.unwrap();
let our_end = buffer
.anchor_in_excerpt(excerpt_id, conflict.ours.end)
.unwrap();
let their_start = buffer
.anchor_in_excerpt(excerpt_id, conflict.theirs.start)
.unwrap();
let their_end = buffer
.anchor_in_excerpt(excerpt_id, conflict.theirs.end)
.unwrap();
let ours_background = colors.version_control_conflict_ours_background;
let ours_marker = colors.version_control_conflict_ours_marker_background;
let theirs_background = colors.version_control_conflict_theirs_background;
let theirs_marker = colors.version_control_conflict_theirs_marker_background;
let divider_background = colors.version_control_conflict_divider_background;
let options = RowHighlightOptions {
include_gutter: false,
..Default::default()
};
// Prevent diff hunk highlighting within the entire conflict region.
editor.highlight_rows::<ConflictsOuter>(
outer_start..outer_end,
divider_background,
options,
cx,
);
editor.highlight_rows::<ConflictsOurs>(our_start..our_end, ours_background, options, cx);
editor.highlight_rows::<ConflictsOursMarker>(outer_start..our_start, ours_marker, options, cx);
editor.highlight_rows::<ConflictsTheirs>(
their_start..their_end,
theirs_background,
options,
cx,
);
editor.highlight_rows::<ConflictsTheirsMarker>(
their_end..outer_end,
theirs_marker,
options,
cx,
);
}
fn render_conflict_buttons(
conflict: &ConflictRegion,
excerpt_id: ExcerptId,
editor: WeakEntity<Editor>,
cx: &mut BlockContext,
) -> AnyElement {
h_flex()
.h(cx.line_height)
.items_end()
.ml(cx.gutter_dimensions.width)
.id(cx.block_id)
.gap_0p5()
.child(
div()
.id("ours")
.px_1()
.child("Take Ours")
.rounded_t(rems(0.2))
.text_ui_sm(cx)
.hover(|this| this.bg(cx.theme().colors().element_background))
.cursor_pointer()
.on_click({
let editor = editor.clone();
let conflict = conflict.clone();
let ours = conflict.ours.clone();
move |_, _, cx| {
resolve_conflict(editor.clone(), excerpt_id, &conflict, &[ours.clone()], cx)
}
}),
)
.child(
div()
.id("theirs")
.px_1()
.child("Take Theirs")
.rounded_t(rems(0.2))
.text_ui_sm(cx)
.hover(|this| this.bg(cx.theme().colors().element_background))
.cursor_pointer()
.on_click({
let editor = editor.clone();
let conflict = conflict.clone();
let theirs = conflict.theirs.clone();
move |_, _, cx| {
resolve_conflict(
editor.clone(),
excerpt_id,
&conflict,
&[theirs.clone()],
cx,
)
}
}),
)
.child(
div()
.id("both")
.px_1()
.child("Take Both")
.rounded_t(rems(0.2))
.text_ui_sm(cx)
.hover(|this| this.bg(cx.theme().colors().element_background))
.cursor_pointer()
.on_click({
let editor = editor.clone();
let conflict = conflict.clone();
let ours = conflict.ours.clone();
let theirs = conflict.theirs.clone();
move |_, _, cx| {
resolve_conflict(
editor.clone(),
excerpt_id,
&conflict,
&[ours.clone(), theirs.clone()],
cx,
)
}
}),
)
.into_any()
}
fn resolve_conflict(
editor: WeakEntity<Editor>,
excerpt_id: ExcerptId,
resolved_conflict: &ConflictRegion,
ranges: &[Range<Anchor>],
cx: &mut App,
) {
let Some(editor) = editor.upgrade() else {
return;
};
let multibuffer = editor.read(cx).buffer().read(cx);
let snapshot = multibuffer.snapshot(cx);
let Some(buffer) = resolved_conflict
.ours
.end
.buffer_id
.and_then(|buffer_id| multibuffer.buffer(buffer_id))
else {
return;
};
let buffer_snapshot = buffer.read(cx).snapshot();
resolved_conflict.resolve(buffer, ranges, cx);
editor.update(cx, |editor, cx| {
let conflict_addon = editor.addon_mut::<ConflictAddon>().unwrap();
let Some(state) = conflict_addon.buffers.get_mut(&buffer_snapshot.remote_id()) else {
return;
};
let Ok(ix) = state.block_ids.binary_search_by(|(range, _)| {
range
.start
.cmp(&resolved_conflict.range.start, &buffer_snapshot)
}) else {
return;
};
let &(_, block_id) = &state.block_ids[ix];
let start = snapshot
.anchor_in_excerpt(excerpt_id, resolved_conflict.range.start)
.unwrap();
let end = snapshot
.anchor_in_excerpt(excerpt_id, resolved_conflict.range.end)
.unwrap();
editor.remove_highlighted_rows::<ConflictsOuter>(vec![start..end], cx);
editor.remove_highlighted_rows::<ConflictsOurs>(vec![start..end], cx);
editor.remove_highlighted_rows::<ConflictsTheirs>(vec![start..end], cx);
editor.remove_highlighted_rows::<ConflictsOursMarker>(vec![start..end], cx);
editor.remove_highlighted_rows::<ConflictsTheirsMarker>(vec![start..end], cx);
editor.remove_blocks(HashSet::from_iter([block_id]), None, cx);
})
}

View File

@@ -447,7 +447,7 @@ impl GitPanel {
.ok();
}
GitStoreEvent::RepositoryUpdated(_, _, _) => {}
GitStoreEvent::JobsUpdated | GitStoreEvent::ConflictsUpdated => {}
GitStoreEvent::JobsUpdated => {}
},
)
.detach();
@@ -1650,7 +1650,7 @@ impl GitPanel {
if let Some(merge_message) = self
.active_repository
.as_ref()
.and_then(|repo| repo.read(cx).merge.message.as_ref())
.and_then(|repo| repo.read(cx).merge_message.as_ref())
{
return Some(merge_message.to_string());
}

View File

@@ -3,7 +3,6 @@ use std::any::Any;
use ::settings::Settings;
use command_palette_hooks::CommandPaletteFilter;
use commit_modal::CommitModal;
use editor::Editor;
mod blame_ui;
use git::{
repository::{Branch, Upstream, UpstreamTracking, UpstreamTrackingStatus},
@@ -21,7 +20,6 @@ pub mod branch_picker;
mod commit_modal;
pub mod commit_tooltip;
mod commit_view;
mod conflict_view;
pub mod git_panel;
mod git_panel_settings;
pub mod onboarding;
@@ -37,11 +35,6 @@ pub fn init(cx: &mut App) {
editor::set_blame_renderer(blame_ui::GitBlameRenderer, cx);
cx.observe_new(|editor: &mut Editor, _, cx| {
conflict_view::register_editor(editor, editor.buffer().clone(), cx);
})
.detach();
cx.observe_new(|workspace: &mut Workspace, _, cx| {
ProjectDiff::register(workspace, cx);
CommitModal::register(workspace);

View File

@@ -1,5 +1,4 @@
use crate::{
conflict_view::ConflictAddon,
git_panel::{GitPanel, GitPanelAddon, GitStatusEntry},
remote_button::{render_publish_button, render_push_button},
};
@@ -27,10 +26,7 @@ use project::{
Project, ProjectPath,
git_store::{GitStore, GitStoreEvent, RepositoryEvent},
};
use std::{
any::{Any, TypeId},
ops::Range,
};
use std::any::{Any, TypeId};
use theme::ActiveTheme;
use ui::{KeyBinding, Tooltip, prelude::*, vertical_divider};
use util::ResultExt as _;
@@ -52,6 +48,7 @@ pub struct ProjectDiff {
focus_handle: FocusHandle,
update_needed: postage::watch::Sender<()>,
pending_scroll: Option<PathKey>,
current_branch: Option<Branch>,
_task: Task<Result<()>>,
_subscription: Subscription,
}
@@ -64,9 +61,9 @@ struct DiffBuffer {
file_status: FileStatus,
}
const CONFLICT_NAMESPACE: u32 = 1;
const TRACKED_NAMESPACE: u32 = 2;
const NEW_NAMESPACE: u32 = 3;
const CONFLICT_NAMESPACE: u32 = 0;
const TRACKED_NAMESPACE: u32 = 1;
const NEW_NAMESPACE: u32 = 2;
impl ProjectDiff {
pub(crate) fn register(workspace: &mut Workspace, cx: &mut Context<Workspace>) {
@@ -157,8 +154,7 @@ impl ProjectDiff {
window,
move |this, _git_store, event, _window, _cx| match event {
GitStoreEvent::ActiveRepositoryChanged(_)
| GitStoreEvent::RepositoryUpdated(_, RepositoryEvent::Updated { .. }, true)
| GitStoreEvent::ConflictsUpdated => {
| GitStoreEvent::RepositoryUpdated(_, RepositoryEvent::Updated { .. }, true) => {
*this.update_needed.borrow_mut() = ();
}
_ => {}
@@ -182,6 +178,7 @@ impl ProjectDiff {
multibuffer,
pending_scroll: None,
update_needed: send,
current_branch: None,
_task: worker,
_subscription: git_store_subscription,
}
@@ -398,25 +395,11 @@ impl ProjectDiff {
let buffer = diff_buffer.buffer;
let diff = diff_buffer.diff;
let conflict_addon = self
.editor
.read(cx)
.addon::<ConflictAddon>()
.expect("project diff editor should have a conflict addon");
let snapshot = buffer.read(cx).snapshot();
let diff = diff.read(cx);
let diff_hunk_ranges = diff
.hunks_intersecting_range(Anchor::MIN..Anchor::MAX, &snapshot, cx)
.map(|diff_hunk| diff_hunk.buffer_range.clone());
let conflicts = conflict_addon
.conflict_set(snapshot.remote_id())
.map(|conflict_set| conflict_set.read(cx).snapshot().conflicts.clone())
.unwrap_or_default();
let conflicts = conflicts.iter().map(|conflict| conflict.range.clone());
let excerpt_ranges = merge_anchor_ranges(diff_hunk_ranges, conflicts, &snapshot)
.map(|range| range.to_point(&snapshot))
.map(|diff_hunk| diff_hunk.buffer_range.to_point(&snapshot))
.collect::<Vec<_>>();
let (was_empty, is_excerpt_newly_added) = self.multibuffer.update(cx, |multibuffer, cx| {
@@ -424,7 +407,7 @@ impl ProjectDiff {
let (_, is_newly_added) = multibuffer.set_excerpts_for_path(
path_key.clone(),
buffer,
excerpt_ranges,
diff_hunk_ranges,
editor::DEFAULT_MULTIBUFFER_CONTEXT,
cx,
);
@@ -467,6 +450,18 @@ impl ProjectDiff {
cx: &mut AsyncWindowContext,
) -> Result<()> {
while let Some(_) = recv.next().await {
this.update(cx, |this, cx| {
let new_branch = this
.git_store
.read(cx)
.active_repository()
.and_then(|active_repository| active_repository.read(cx).branch.clone());
if new_branch != this.current_branch {
this.current_branch = new_branch;
cx.notify();
}
})?;
let buffers_to_load = this.update(cx, |this, cx| this.load_buffers(cx))?;
for buffer_to_load in buffers_to_load {
if let Some(buffer) = buffer_to_load.await.log_err() {
@@ -1132,6 +1127,47 @@ impl RenderOnce for ProjectDiffEmptyState {
}
}
// .when(self.can_push_and_pull, |this| {
// let remote_button = crate::render_remote_button(
// "project-diff-remote-button",
// &branch,
// self.focus_handle.clone(),
// false,
// );
// match remote_button {
// Some(button) => {
// this.child(h_flex().justify_around().child(button))
// }
// None => this.child(
// h_flex()
// .justify_around()
// .child(Label::new("Remote up to date")),
// ),
// }
// }),
//
// // .map(|this| {
// this.child(h_flex().justify_around().mt_1().child(
// Button::new("project-diff-close-button", "Close").when_some(
// self.focus_handle.clone(),
// |this, focus_handle| {
// this.key_binding(KeyBinding::for_action_in(
// &CloseActiveItem::default(),
// &focus_handle,
// window,
// cx,
// ))
// .on_click(move |_, window, cx| {
// window.focus(&focus_handle);
// window
// .dispatch_action(Box::new(CloseActiveItem::default()), cx);
// })
// },
// ),
// ))
// }),
mod preview {
use git::repository::{
Branch, CommitSummary, Upstream, UpstreamTracking, UpstreamTrackingStatus,
@@ -1257,53 +1293,6 @@ mod preview {
}
}
fn merge_anchor_ranges<'a>(
left: impl 'a + Iterator<Item = Range<Anchor>>,
right: impl 'a + Iterator<Item = Range<Anchor>>,
snapshot: &'a language::BufferSnapshot,
) -> impl 'a + Iterator<Item = Range<Anchor>> {
let mut left = left.fuse().peekable();
let mut right = right.fuse().peekable();
std::iter::from_fn(move || {
let Some(left_range) = left.peek() else {
return right.next();
};
let Some(right_range) = right.peek() else {
return left.next();
};
let mut next_range = if left_range.start.cmp(&right_range.start, snapshot).is_lt() {
left.next().unwrap()
} else {
right.next().unwrap()
};
// Extend the basic range while there's overlap with a range from either stream.
loop {
if let Some(left_range) = left
.peek()
.filter(|range| range.start.cmp(&next_range.end, &snapshot).is_le())
.cloned()
{
left.next();
next_range.end = left_range.end;
} else if let Some(right_range) = right
.peek()
.filter(|range| range.start.cmp(&next_range.end, &snapshot).is_le())
.cloned()
{
right.next();
next_range.end = right_range.end;
} else {
break;
}
}
Some(next_range)
})
}
#[cfg(not(target_os = "windows"))]
#[cfg(test)]
mod tests {

View File

@@ -2,8 +2,7 @@ pub mod cursor_position;
use cursor_position::{LineIndicatorFormat, UserCaretPosition};
use editor::{
Anchor, Editor, MultiBufferSnapshot, RowHighlightOptions, ToOffset, ToPoint, actions::Tab,
scroll::Autoscroll,
Anchor, Editor, MultiBufferSnapshot, ToOffset, ToPoint, actions::Tab, scroll::Autoscroll,
};
use gpui::{
App, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, Render, SharedString, Styled,
@@ -181,10 +180,7 @@ impl GoToLine {
editor.highlight_rows::<GoToLineRowHighlights>(
start..end,
cx.theme().colors().editor_highlighted_line_background,
RowHighlightOptions {
autoscroll: true,
..Default::default()
},
true,
cx,
);
editor.request_autoscroll(Autoscroll::center(), cx);

View File

@@ -15,7 +15,7 @@ use std::{
num::NonZeroU64,
sync::{
Arc, Weak,
atomic::{AtomicU64, AtomicUsize, Ordering::SeqCst},
atomic::{AtomicUsize, Ordering::SeqCst},
},
thread::panicking,
};
@@ -572,30 +572,6 @@ impl AnyWeakEntity {
)
}
}
/// Creates a weak entity that can never be upgraded.
pub fn new_invalid() -> Self {
/// To hold the invariant that all ids are unique, and considering that slotmap
/// increases their IDs from `0`, we can decrease ours from `u64::MAX` so these
/// two will never conflict (u64 is way too large).
static UNIQUE_NON_CONFLICTING_ID_GENERATOR: AtomicU64 = AtomicU64::new(u64::MAX);
let entity_id = UNIQUE_NON_CONFLICTING_ID_GENERATOR.fetch_sub(1, SeqCst);
Self {
// Safety:
// Docs say this is safe but can be unspecified if slotmap changes the representation
// after `1.0.7`, that said, providing a valid entity_id here is not necessary as long
// as we guarantee that that `entity_id` is never used if `entity_ref_counts` equals
// to `Weak::new()` (that is, it's unable to upgrade), that is the invariant that
// actually needs to be hold true.
//
// And there is no sane reason to read an entity slot if `entity_ref_counts` can't be
// read in the first place, so we're good!
entity_id: entity_id.into(),
entity_type: TypeId::of::<()>(),
entity_ref_counts: Weak::new(),
}
}
}
impl std::fmt::Debug for AnyWeakEntity {
@@ -731,14 +707,6 @@ impl<T: 'static> WeakEntity<T> {
.map(|this| cx.read_entity(&this, read)),
)
}
/// Create a new weak entity that can never be upgraded.
pub fn new_invalid() -> Self {
Self {
any_entity: AnyWeakEntity::new_invalid(),
entity_type: PhantomData,
}
}
}
impl<T> Hash for WeakEntity<T> {

View File

@@ -42,10 +42,6 @@ impl ConfiguredModel {
pub fn is_same_as(&self, other: &ConfiguredModel) -> bool {
self.model.id() == other.model.id() && self.provider.id() == other.provider.id()
}
pub fn is_provided_by_zed(&self) -> bool {
self.provider.id().0 == crate::ZED_CLOUD_PROVIDER_ID
}
}
pub enum Event {

View File

@@ -714,32 +714,39 @@ pub fn map_to_language_model_completion_events(
if let Some(tool_use) = state.tool_uses_by_index.get_mut(&index) {
tool_use.input_json.push_str(&partial_json);
// Try to convert invalid (incomplete) JSON into
// valid JSON that serde can accept, e.g. by closing
// unclosed delimiters. This way, we can update the
// UI with whatever has been streamed back so far.
if let Ok(input) = serde_json::Value::from_str(
&partial_json_fixer::fix_json(&tool_use.input_json),
) {
return Some((
vec![Ok(LanguageModelCompletionEvent::ToolUse(
return Some((
vec![maybe!({
Ok(LanguageModelCompletionEvent::ToolUse(
LanguageModelToolUse {
id: tool_use.id.clone().into(),
name: tool_use.name.clone().into(),
is_input_complete: false,
input,
input: if tool_use.input_json.is_empty() {
serde_json::Value::Object(
serde_json::Map::default(),
)
} else {
serde_json::Value::from_str(
// Convert invalid (incomplete) JSON into
// JSON that serde will accept, e.g. by closing
// unclosed delimiters. This way, we can update
// the UI with whatever has been streamed back so far.
&partial_json_fixer::fix_json(
&tool_use.input_json,
),
)
.map_err(|err| anyhow!(err))?
},
},
))],
state,
));
}
))
})],
state,
));
}
}
},
Event::ContentBlockStop { index } => {
if let Some(tool_use) = state.tool_uses_by_index.remove(&index) {
let input_json = tool_use.input_json.trim();
return Some((
vec![maybe!({
Ok(LanguageModelCompletionEvent::ToolUse(
@@ -747,15 +754,15 @@ pub fn map_to_language_model_completion_events(
id: tool_use.id.into(),
name: tool_use.name.into(),
is_input_complete: true,
input: if input_json.is_empty() {
input: if tool_use.input_json.is_empty() {
serde_json::Value::Object(
serde_json::Map::default(),
)
} else {
serde_json::Value::from_str(
input_json
&tool_use.input_json,
)
.map_err(|err| anyhow!("Error parsing tool call input JSON: {err:?} - JSON string was: {input_json:?}"))?
.map_err(|err| anyhow!(err))?
},
},
))

View File

@@ -35,7 +35,7 @@ use strum::IntoEnumIterator;
use thiserror::Error;
use ui::{TintColor, prelude::*};
use zed_llm_client::{
CURRENT_PLAN_HEADER_NAME, CompletionBody, CompletionMode, EXPIRED_LLM_TOKEN_HEADER_NAME,
CURRENT_PLAN_HEADER_NAME, CompletionBody, EXPIRED_LLM_TOKEN_HEADER_NAME,
MAX_LLM_MONTHLY_SPEND_REACHED_HEADER_NAME, MODEL_REQUESTS_RESOURCE_HEADER_VALUE,
SUBSCRIPTION_LIMIT_RESOURCE_HEADER_NAME,
};
@@ -748,7 +748,6 @@ impl LanguageModel for CloudLanguageModel {
CompletionBody {
thread_id,
prompt_id,
mode: Some(CompletionMode::Max),
provider: zed_llm_client::LanguageModelProvider::Anthropic,
model: request.model.clone(),
provider_request: serde_json::to_value(&request)?,
@@ -795,7 +794,6 @@ impl LanguageModel for CloudLanguageModel {
CompletionBody {
thread_id,
prompt_id,
mode: Some(CompletionMode::Max),
provider: zed_llm_client::LanguageModelProvider::OpenAi,
model: request.model.clone(),
provider_request: serde_json::to_value(&request)?,
@@ -826,7 +824,6 @@ impl LanguageModel for CloudLanguageModel {
CompletionBody {
thread_id,
prompt_id,
mode: Some(CompletionMode::Max),
provider: zed_llm_client::LanguageModelProvider::Google,
model: request.model.clone(),
provider_request: serde_json::to_value(&request)?,

View File

@@ -95,7 +95,6 @@ pub enum Event {
},
ExcerptsRemoved {
ids: Vec<ExcerptId>,
removed_buffer_ids: Vec<BufferId>,
},
ExcerptsExpanded {
ids: Vec<ExcerptId>,
@@ -2022,12 +2021,7 @@ impl MultiBuffer {
pub fn clear(&mut self, cx: &mut Context<Self>) {
self.sync(cx);
let ids = self.excerpt_ids();
let removed_buffer_ids = self
.buffers
.borrow_mut()
.drain()
.map(|(id, _)| id)
.collect();
self.buffers.borrow_mut().clear();
self.excerpts_by_path.clear();
self.paths_by_excerpt.clear();
let mut snapshot = self.snapshot.borrow_mut();
@@ -2052,10 +2046,7 @@ impl MultiBuffer {
singleton_buffer_edited: false,
edited_buffer: None,
});
cx.emit(Event::ExcerptsRemoved {
ids,
removed_buffer_ids,
});
cx.emit(Event::ExcerptsRemoved { ids });
cx.notify();
}
@@ -2319,9 +2310,9 @@ impl MultiBuffer {
new_excerpts.append(suffix, &());
drop(cursor);
snapshot.excerpts = new_excerpts;
for buffer_id in &removed_buffer_ids {
self.diffs.remove(buffer_id);
snapshot.diffs.remove(buffer_id);
for buffer_id in removed_buffer_ids {
self.diffs.remove(&buffer_id);
snapshot.diffs.remove(&buffer_id);
}
if changed_trailing_excerpt {
@@ -2334,10 +2325,7 @@ impl MultiBuffer {
singleton_buffer_edited: false,
edited_buffer: None,
});
cx.emit(Event::ExcerptsRemoved {
ids,
removed_buffer_ids,
});
cx.emit(Event::ExcerptsRemoved { ids });
cx.notify();
}

View File

@@ -635,7 +635,7 @@ fn test_excerpt_events(cx: &mut App) {
predecessor,
excerpts,
} => follower.insert_excerpts_with_ids_after(predecessor, buffer, excerpts, cx),
Event::ExcerptsRemoved { ids, .. } => follower.remove_excerpts(ids, cx),
Event::ExcerptsRemoved { ids } => follower.remove_excerpts(ids, cx),
Event::Edited { .. } => {
*follower_edit_event_count.write() += 1;
}

View File

@@ -4,7 +4,6 @@ use std::{
sync::Arc,
};
use editor::RowHighlightOptions;
use editor::{Anchor, AnchorRangeExt, Editor, scroll::Autoscroll};
use fuzzy::StringMatch;
use gpui::{
@@ -172,10 +171,7 @@ impl OutlineViewDelegate {
active_editor.highlight_rows::<OutlineRowHighlights>(
outline_item.range.start..outline_item.range.end,
cx.theme().colors().editor_highlighted_line_background,
RowHighlightOptions {
autoscroll: true,
..Default::default()
},
true,
cx,
);
active_editor.request_autoscroll(Autoscroll::center(), cx);

View File

@@ -5028,7 +5028,7 @@ fn subscribe_for_editor_events(
.extend(excerpts.iter().map(|&(excerpt_id, _)| excerpt_id));
outline_panel.update_fs_entries(editor.clone(), debounce, window, cx);
}
EditorEvent::ExcerptsRemoved { ids, .. } => {
EditorEvent::ExcerptsRemoved { ids } => {
let mut ids = ids.iter().collect::<HashSet<_>>();
for excerpts in outline_panel.excerpts.values_mut() {
excerpts.retain(|excerpt_id, _| !ids.remove(excerpt_id));

View File

@@ -641,7 +641,6 @@ impl CompletionsQuery {
}
}
#[derive(Debug)]
pub enum SessionEvent {
Modules,
LoadedSources,

View File

@@ -1,4 +1,3 @@
mod conflict_set;
pub mod git_traversal;
use crate::{
@@ -11,12 +10,11 @@ use askpass::AskPassDelegate;
use buffer_diff::{BufferDiff, BufferDiffEvent};
use client::ProjectId;
use collections::HashMap;
pub use conflict_set::{ConflictRegion, ConflictSet, ConflictSetSnapshot, ConflictSetUpdate};
use fs::Fs;
use futures::{
FutureExt, StreamExt as _,
FutureExt as _, StreamExt as _,
channel::{mpsc, oneshot},
future::{self, Shared, try_join_all},
future::{self, Shared},
};
use git::{
BuildPermalinkParams, GitHostingProviderRegistry, WORK_DIRECTORY_REPO_PATH,
@@ -76,7 +74,7 @@ pub struct GitStore {
#[allow(clippy::type_complexity)]
loading_diffs:
HashMap<(BufferId, DiffKind), Shared<Task<Result<Entity<BufferDiff>, Arc<anyhow::Error>>>>>,
diffs: HashMap<BufferId, Entity<BufferGitState>>,
diffs: HashMap<BufferId, Entity<BufferDiffState>>,
shared_diffs: HashMap<proto::PeerId, HashMap<BufferId, SharedDiffs>>,
_subscriptions: Vec<Subscription>,
}
@@ -87,15 +85,12 @@ struct SharedDiffs {
uncommitted: Option<Entity<BufferDiff>>,
}
struct BufferGitState {
struct BufferDiffState {
unstaged_diff: Option<WeakEntity<BufferDiff>>,
uncommitted_diff: Option<WeakEntity<BufferDiff>>,
conflict_set: Option<WeakEntity<ConflictSet>>,
recalculate_diff_task: Option<Task<Result<()>>>,
reparse_conflict_markers_task: Option<Task<Result<()>>>,
language: Option<Arc<Language>>,
language_registry: Option<Arc<LanguageRegistry>>,
conflict_updated_futures: Vec<oneshot::Sender<()>>,
recalculating_tx: postage::watch::Sender<bool>,
/// These operation counts are used to ensure that head and index text
@@ -229,26 +224,17 @@ impl sum_tree::KeyedItem for StatusEntry {
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct RepositoryId(pub u64);
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct MergeDetails {
pub conflicted_paths: TreeSet<RepoPath>,
pub message: Option<SharedString>,
pub apply_head: Option<CommitDetails>,
pub cherry_pick_head: Option<CommitDetails>,
pub merge_heads: Vec<CommitDetails>,
pub rebase_head: Option<CommitDetails>,
pub revert_head: Option<CommitDetails>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct RepositorySnapshot {
pub id: RepositoryId,
pub merge_message: Option<SharedString>,
pub statuses_by_path: SumTree<StatusEntry>,
pub work_directory_abs_path: Arc<Path>,
pub branch: Option<Branch>,
pub head_commit: Option<CommitDetails>,
pub merge_conflicts: TreeSet<RepoPath>,
pub merge_head_shas: Vec<SharedString>,
pub scan_id: u64,
pub merge: MergeDetails,
}
type JobId = u64;
@@ -311,7 +297,6 @@ pub enum GitStoreEvent {
RepositoryRemoved(RepositoryId),
IndexWriteError(anyhow::Error),
JobsUpdated,
ConflictsUpdated,
}
impl EventEmitter<RepositoryEvent> for Repository {}
@@ -696,11 +681,10 @@ impl GitStore {
let text_snapshot = buffer.text_snapshot();
this.loading_diffs.remove(&(buffer_id, kind));
let git_store = cx.weak_entity();
let diff_state = this
.diffs
.entry(buffer_id)
.or_insert_with(|| cx.new(|_| BufferGitState::new(git_store)));
.or_insert_with(|| cx.new(|_| BufferDiffState::default()));
let diff = cx.new(|cx| BufferDiff::new(&text_snapshot, cx));
@@ -753,62 +737,6 @@ impl GitStore {
diff_state.read(cx).uncommitted_diff.as_ref()?.upgrade()
}
pub fn open_conflict_set(
&mut self,
buffer: Entity<Buffer>,
cx: &mut Context<Self>,
) -> Entity<ConflictSet> {
log::debug!("open conflict set");
let buffer_id = buffer.read(cx).remote_id();
if let Some(git_state) = self.diffs.get(&buffer_id) {
if let Some(conflict_set) = git_state
.read(cx)
.conflict_set
.as_ref()
.and_then(|weak| weak.upgrade())
{
let conflict_set = conflict_set.clone();
let buffer_snapshot = buffer.read(cx).text_snapshot();
git_state.update(cx, |state, cx| {
let _ = state.reparse_conflict_markers(buffer_snapshot, cx);
});
return conflict_set;
}
}
let is_unmerged = self
.repository_and_path_for_buffer_id(buffer_id, cx)
.map_or(false, |(repo, path)| {
repo.read(cx)
.snapshot
.merge
.conflicted_paths
.contains(&path)
});
let git_store = cx.weak_entity();
let buffer_git_state = self
.diffs
.entry(buffer_id)
.or_insert_with(|| cx.new(|_| BufferGitState::new(git_store)));
let conflict_set = cx.new(|cx| ConflictSet::new(buffer_id, is_unmerged, cx));
self._subscriptions
.push(cx.subscribe(&conflict_set, |_, _, _, cx| {
cx.emit(GitStoreEvent::ConflictsUpdated);
}));
buffer_git_state.update(cx, |state, cx| {
state.conflict_set = Some(conflict_set.downgrade());
let buffer_snapshot = buffer.read(cx).text_snapshot();
let _ = state.reparse_conflict_markers(buffer_snapshot, cx);
});
conflict_set
}
pub fn project_path_git_status(
&self,
project_path: &ProjectPath,
@@ -1151,35 +1079,6 @@ impl GitStore {
cx: &mut Context<Self>,
) {
let id = repo.read(cx).id;
let merge_conflicts = repo.read(cx).snapshot.merge.conflicted_paths.clone();
for (buffer_id, diff) in self.diffs.iter() {
if let Some((buffer_repo, repo_path)) =
self.repository_and_path_for_buffer_id(*buffer_id, cx)
{
if buffer_repo == repo {
diff.update(cx, |diff, cx| {
if let Some(conflict_set) = &diff.conflict_set {
let conflict_status_changed =
conflict_set.update(cx, |conflict_set, cx| {
let has_conflict = merge_conflicts.contains(&repo_path);
conflict_set.set_has_conflict(has_conflict, cx)
})?;
if conflict_status_changed {
let buffer_store = self.buffer_store.read(cx);
if let Some(buffer) = buffer_store.get(*buffer_id) {
let _ = diff.reparse_conflict_markers(
buffer.read(cx).text_snapshot(),
cx,
);
}
}
}
anyhow::Ok(())
})
.ok();
}
}
}
cx.emit(GitStoreEvent::RepositoryUpdated(
id,
event.clone(),
@@ -1319,15 +1218,9 @@ impl GitStore {
if let Some(diff_state) = self.diffs.get_mut(&buffer.read(cx).remote_id()) {
let buffer = buffer.read(cx).text_snapshot();
diff_state.update(cx, |diff_state, cx| {
diff_state.recalculate_diffs(buffer.clone(), cx);
futures.extend(diff_state.wait_for_recalculation().map(FutureExt::boxed));
diff_state.recalculate_diffs(buffer, cx);
futures.extend(diff_state.wait_for_recalculation());
});
futures.push(diff_state.update(cx, |diff_state, cx| {
diff_state
.reparse_conflict_markers(buffer, cx)
.map(|_| {})
.boxed()
}));
}
}
async move {
@@ -2201,86 +2094,13 @@ impl GitStore {
}
}
impl BufferGitState {
fn new(_git_store: WeakEntity<GitStore>) -> Self {
Self {
unstaged_diff: Default::default(),
uncommitted_diff: Default::default(),
recalculate_diff_task: Default::default(),
language: Default::default(),
language_registry: Default::default(),
recalculating_tx: postage::watch::channel_with(false).0,
hunk_staging_operation_count: 0,
hunk_staging_operation_count_as_of_write: 0,
head_text: Default::default(),
index_text: Default::default(),
head_changed: Default::default(),
index_changed: Default::default(),
language_changed: Default::default(),
conflict_updated_futures: Default::default(),
conflict_set: Default::default(),
reparse_conflict_markers_task: Default::default(),
}
}
impl BufferDiffState {
fn buffer_language_changed(&mut self, buffer: Entity<Buffer>, cx: &mut Context<Self>) {
self.language = buffer.read(cx).language().cloned();
self.language_changed = true;
let _ = self.recalculate_diffs(buffer.read(cx).text_snapshot(), cx);
}
fn reparse_conflict_markers(
&mut self,
buffer: text::BufferSnapshot,
cx: &mut Context<Self>,
) -> oneshot::Receiver<()> {
let (tx, rx) = oneshot::channel();
let Some(conflict_set) = self
.conflict_set
.as_ref()
.and_then(|conflict_set| conflict_set.upgrade())
else {
return rx;
};
let old_snapshot = conflict_set.read_with(cx, |conflict_set, _| {
if conflict_set.has_conflict {
Some(conflict_set.snapshot())
} else {
None
}
});
if let Some(old_snapshot) = old_snapshot {
self.conflict_updated_futures.push(tx);
self.reparse_conflict_markers_task = Some(cx.spawn(async move |this, cx| {
let (snapshot, changed_range) = cx
.background_spawn(async move {
let new_snapshot = ConflictSet::parse(&buffer);
let changed_range = old_snapshot.compare(&new_snapshot, &buffer);
(new_snapshot, changed_range)
})
.await;
this.update(cx, |this, cx| {
if let Some(conflict_set) = &this.conflict_set {
conflict_set
.update(cx, |conflict_set, cx| {
conflict_set.set_snapshot(snapshot, changed_range, cx);
})
.ok();
}
let futures = std::mem::take(&mut this.conflict_updated_futures);
for tx in futures {
tx.send(()).ok();
}
})
}))
}
rx
}
fn unstaged_diff(&self) -> Option<Entity<BufferDiff>> {
self.unstaged_diff.as_ref().and_then(|set| set.upgrade())
}
@@ -2515,6 +2335,26 @@ impl BufferGitState {
}
}
impl Default for BufferDiffState {
fn default() -> Self {
Self {
unstaged_diff: Default::default(),
uncommitted_diff: Default::default(),
recalculate_diff_task: Default::default(),
language: Default::default(),
language_registry: Default::default(),
recalculating_tx: postage::watch::channel_with(false).0,
hunk_staging_operation_count: 0,
hunk_staging_operation_count_as_of_write: 0,
head_text: Default::default(),
index_text: Default::default(),
head_changed: Default::default(),
index_changed: Default::default(),
language_changed: Default::default(),
}
}
}
fn make_remote_delegate(
this: Entity<GitStore>,
project_id: u64,
@@ -2557,12 +2397,14 @@ impl RepositorySnapshot {
fn empty(id: RepositoryId, work_directory_abs_path: Arc<Path>) -> Self {
Self {
id,
merge_message: None,
statuses_by_path: Default::default(),
work_directory_abs_path,
branch: None,
head_commit: None,
merge_conflicts: Default::default(),
merge_head_shas: Default::default(),
scan_id: 0,
merge: Default::default(),
}
}
@@ -2577,8 +2419,7 @@ impl RepositorySnapshot {
.collect(),
removed_statuses: Default::default(),
current_merge_conflicts: self
.merge
.conflicted_paths
.merge_conflicts
.iter()
.map(|repo_path| repo_path.to_proto())
.collect(),
@@ -2639,8 +2480,7 @@ impl RepositorySnapshot {
updated_statuses,
removed_statuses,
current_merge_conflicts: self
.merge
.conflicted_paths
.merge_conflicts
.iter()
.map(|path| path.as_ref().to_proto())
.collect(),
@@ -2675,7 +2515,7 @@ impl RepositorySnapshot {
}
pub fn has_conflict(&self, repo_path: &RepoPath) -> bool {
self.merge.conflicted_paths.contains(repo_path)
self.merge_conflicts.contains(repo_path)
}
/// This is the name that will be displayed in the repository selector for this repository.
@@ -2689,77 +2529,7 @@ impl RepositorySnapshot {
}
}
impl MergeDetails {
async fn load(
backend: &Arc<dyn GitRepository>,
status: &SumTree<StatusEntry>,
prev_snapshot: &RepositorySnapshot,
) -> Result<(MergeDetails, bool)> {
fn sha_eq<'a>(
l: impl IntoIterator<Item = &'a CommitDetails>,
r: impl IntoIterator<Item = &'a CommitDetails>,
) -> bool {
l.into_iter()
.map(|commit| &commit.sha)
.eq(r.into_iter().map(|commit| &commit.sha))
}
let merge_heads = try_join_all(
backend
.merge_head_shas()
.into_iter()
.map(|sha| backend.show(sha)),
)
.await?;
let cherry_pick_head = backend.show("CHERRY_PICK_HEAD".into()).await.ok();
let rebase_head = backend.show("REBASE_HEAD".into()).await.ok();
let revert_head = backend.show("REVERT_HEAD".into()).await.ok();
let apply_head = backend.show("APPLY_HEAD".into()).await.ok();
let message = backend.merge_message().await.map(SharedString::from);
let merge_heads_changed = !sha_eq(
merge_heads.as_slice(),
prev_snapshot.merge.merge_heads.as_slice(),
) || !sha_eq(
cherry_pick_head.as_ref(),
prev_snapshot.merge.cherry_pick_head.as_ref(),
) || !sha_eq(
apply_head.as_ref(),
prev_snapshot.merge.apply_head.as_ref(),
) || !sha_eq(
rebase_head.as_ref(),
prev_snapshot.merge.rebase_head.as_ref(),
) || !sha_eq(
revert_head.as_ref(),
prev_snapshot.merge.revert_head.as_ref(),
);
let conflicted_paths = if merge_heads_changed {
TreeSet::from_ordered_entries(
status
.iter()
.filter(|entry| entry.status.is_conflicted())
.map(|entry| entry.repo_path.clone()),
)
} else {
prev_snapshot.merge.conflicted_paths.clone()
};
let details = MergeDetails {
conflicted_paths,
message,
apply_head,
cherry_pick_head,
merge_heads,
rebase_head,
revert_head,
};
Ok((details, merge_heads_changed))
}
}
impl Repository {
pub fn snapshot(&self) -> RepositorySnapshot {
self.snapshot.clone()
}
fn local(
id: RepositoryId,
work_directory_abs_path: Arc<Path>,
@@ -3961,7 +3731,7 @@ impl Repository {
.as_ref()
.map(proto_to_commit_details);
self.snapshot.merge.conflicted_paths = conflicted_paths;
self.snapshot.merge_conflicts = conflicted_paths;
let edits = update
.removed_statuses
@@ -4551,6 +4321,16 @@ async fn compute_snapshot(
let branches = backend.branches().await?;
let branch = branches.into_iter().find(|branch| branch.is_head);
let statuses = backend.status(&[WORK_DIRECTORY_REPO_PATH.clone()]).await?;
let merge_message = backend
.merge_message()
.await
.and_then(|msg| Some(msg.lines().nth(0)?.to_owned().into()));
let merge_head_shas = backend
.merge_head_shas()
.into_iter()
.map(SharedString::from)
.collect();
let statuses_by_path = SumTree::from_iter(
statuses
.entries
@@ -4561,36 +4341,47 @@ async fn compute_snapshot(
}),
&(),
);
let (merge_details, merge_heads_changed) =
MergeDetails::load(&backend, &statuses_by_path, &prev_snapshot).await?;
if merge_heads_changed
let merge_head_shas_changed = merge_head_shas != prev_snapshot.merge_head_shas;
if merge_head_shas_changed
|| branch != prev_snapshot.branch
|| statuses_by_path != prev_snapshot.statuses_by_path
{
events.push(RepositoryEvent::Updated { full_scan: true });
}
let mut current_merge_conflicts = TreeSet::default();
for (repo_path, status) in statuses.entries.iter() {
if status.is_conflicted() {
current_merge_conflicts.insert(repo_path.clone());
}
}
// Cache merge conflict paths so they don't change from staging/unstaging,
// until the merge heads change (at commit time, etc.).
if merge_heads_changed {
let mut merge_conflicts = prev_snapshot.merge_conflicts.clone();
if merge_head_shas_changed {
merge_conflicts = current_merge_conflicts;
events.push(RepositoryEvent::MergeHeadsChanged);
}
// Useful when branch is None in detached head state
let head_commit = match backend.head_sha() {
Some(head_sha) => backend.show(head_sha).await.log_err(),
Some(head_sha) => backend.show(head_sha).await.ok(),
None => None,
};
let snapshot = RepositorySnapshot {
id,
merge_message,
statuses_by_path,
work_directory_abs_path,
scan_id: prev_snapshot.scan_id + 1,
branch,
head_commit,
merge: merge_details,
merge_conflicts,
merge_head_shas,
};
Ok((snapshot, events))

View File

@@ -1,560 +0,0 @@
use gpui::{App, Context, Entity, EventEmitter};
use std::{cmp::Ordering, ops::Range, sync::Arc};
use text::{Anchor, BufferId, OffsetRangeExt as _};
pub struct ConflictSet {
pub has_conflict: bool,
pub snapshot: ConflictSetSnapshot,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ConflictSetUpdate {
pub buffer_range: Option<Range<Anchor>>,
pub old_range: Range<usize>,
pub new_range: Range<usize>,
}
#[derive(Debug, Clone)]
pub struct ConflictSetSnapshot {
pub buffer_id: BufferId,
pub conflicts: Arc<[ConflictRegion]>,
}
impl ConflictSetSnapshot {
pub fn conflicts_in_range(
&self,
range: Range<Anchor>,
buffer: &text::BufferSnapshot,
) -> &[ConflictRegion] {
let start_ix = self
.conflicts
.binary_search_by(|conflict| {
conflict
.range
.end
.cmp(&range.start, buffer)
.then(Ordering::Greater)
})
.unwrap_err();
let end_ix = start_ix
+ self.conflicts[start_ix..]
.binary_search_by(|conflict| {
conflict
.range
.start
.cmp(&range.end, buffer)
.then(Ordering::Less)
})
.unwrap_err();
&self.conflicts[start_ix..end_ix]
}
pub fn compare(&self, other: &Self, buffer: &text::BufferSnapshot) -> ConflictSetUpdate {
let common_prefix_len = self
.conflicts
.iter()
.zip(other.conflicts.iter())
.take_while(|(old, new)| old == new)
.count();
let common_suffix_len = self.conflicts[common_prefix_len..]
.iter()
.rev()
.zip(other.conflicts[common_prefix_len..].iter().rev())
.take_while(|(old, new)| old == new)
.count();
let old_conflicts =
&self.conflicts[common_prefix_len..(self.conflicts.len() - common_suffix_len)];
let new_conflicts =
&other.conflicts[common_prefix_len..(other.conflicts.len() - common_suffix_len)];
let old_range = common_prefix_len..(common_prefix_len + old_conflicts.len());
let new_range = common_prefix_len..(common_prefix_len + new_conflicts.len());
let start = match (old_conflicts.first(), new_conflicts.first()) {
(None, None) => None,
(None, Some(conflict)) => Some(conflict.range.start),
(Some(conflict), None) => Some(conflict.range.start),
(Some(first), Some(second)) => Some(first.range.start.min(&second.range.start, buffer)),
};
let end = match (old_conflicts.last(), new_conflicts.last()) {
(None, None) => None,
(None, Some(conflict)) => Some(conflict.range.end),
(Some(first), None) => Some(first.range.end),
(Some(first), Some(second)) => Some(first.range.end.max(&second.range.end, buffer)),
};
ConflictSetUpdate {
buffer_range: start.zip(end).map(|(start, end)| start..end),
old_range,
new_range,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ConflictRegion {
pub range: Range<Anchor>,
pub ours: Range<Anchor>,
pub theirs: Range<Anchor>,
pub base: Option<Range<Anchor>>,
}
impl ConflictRegion {
pub fn resolve(
&self,
buffer: Entity<language::Buffer>,
ranges: &[Range<Anchor>],
cx: &mut App,
) {
let buffer_snapshot = buffer.read(cx).snapshot();
let mut deletions = Vec::new();
let empty = "";
let outer_range = self.range.to_offset(&buffer_snapshot);
let mut offset = outer_range.start;
for kept_range in ranges {
let kept_range = kept_range.to_offset(&buffer_snapshot);
if kept_range.start > offset {
deletions.push((offset..kept_range.start, empty));
}
offset = kept_range.end;
}
if outer_range.end > offset {
deletions.push((offset..outer_range.end, empty));
}
buffer.update(cx, |buffer, cx| {
buffer.edit(deletions, None, cx);
});
}
}
impl ConflictSet {
pub fn new(buffer_id: BufferId, has_conflict: bool, _: &mut Context<Self>) -> Self {
Self {
has_conflict,
snapshot: ConflictSetSnapshot {
buffer_id,
conflicts: Default::default(),
},
}
}
pub fn set_has_conflict(&mut self, has_conflict: bool, cx: &mut Context<Self>) -> bool {
if has_conflict != self.has_conflict {
self.has_conflict = has_conflict;
if !self.has_conflict {
cx.emit(ConflictSetUpdate {
buffer_range: None,
old_range: 0..self.snapshot.conflicts.len(),
new_range: 0..0,
});
self.snapshot.conflicts = Default::default();
}
true
} else {
false
}
}
pub fn snapshot(&self) -> ConflictSetSnapshot {
self.snapshot.clone()
}
pub fn set_snapshot(
&mut self,
snapshot: ConflictSetSnapshot,
update: ConflictSetUpdate,
cx: &mut Context<Self>,
) {
self.snapshot = snapshot;
cx.emit(update);
}
pub fn parse(buffer: &text::BufferSnapshot) -> ConflictSetSnapshot {
let mut conflicts = Vec::new();
let mut line_pos = 0;
let mut lines = buffer.text_for_range(0..buffer.len()).lines();
let mut conflict_start: Option<usize> = None;
let mut ours_start: Option<usize> = None;
let mut ours_end: Option<usize> = None;
let mut base_start: Option<usize> = None;
let mut base_end: Option<usize> = None;
let mut theirs_start: Option<usize> = None;
while let Some(line) = lines.next() {
let line_end = line_pos + line.len();
if line.starts_with("<<<<<<< ") {
// If we see a new conflict marker while already parsing one,
// abandon the previous one and start a new one
conflict_start = Some(line_pos);
ours_start = Some(line_end + 1);
} else if line.starts_with("||||||| ")
&& conflict_start.is_some()
&& ours_start.is_some()
{
ours_end = Some(line_pos);
base_start = Some(line_end + 1);
} else if line.starts_with("=======")
&& conflict_start.is_some()
&& ours_start.is_some()
{
// Set ours_end if not already set (would be set if we have base markers)
if ours_end.is_none() {
ours_end = Some(line_pos);
} else if base_start.is_some() {
base_end = Some(line_pos);
}
theirs_start = Some(line_end + 1);
} else if line.starts_with(">>>>>>> ")
&& conflict_start.is_some()
&& ours_start.is_some()
&& ours_end.is_some()
&& theirs_start.is_some()
{
let theirs_end = line_pos;
let conflict_end = line_end + 1;
let range = buffer.anchor_after(conflict_start.unwrap())
..buffer.anchor_before(conflict_end);
let ours = buffer.anchor_after(ours_start.unwrap())
..buffer.anchor_before(ours_end.unwrap());
let theirs =
buffer.anchor_after(theirs_start.unwrap())..buffer.anchor_before(theirs_end);
let base = base_start
.zip(base_end)
.map(|(start, end)| buffer.anchor_after(start)..buffer.anchor_before(end));
conflicts.push(ConflictRegion {
range,
ours,
theirs,
base,
});
conflict_start = None;
ours_start = None;
ours_end = None;
base_start = None;
base_end = None;
theirs_start = None;
}
line_pos = line_end + 1;
}
ConflictSetSnapshot {
conflicts: conflicts.into(),
buffer_id: buffer.remote_id(),
}
}
}
impl EventEmitter<ConflictSetUpdate> for ConflictSet {}
#[cfg(test)]
mod tests {
use std::sync::mpsc;
use crate::{Project, project_settings::ProjectSettings};
use super::*;
use fs::FakeFs;
use git::status::{UnmergedStatus, UnmergedStatusCode};
use gpui::{BackgroundExecutor, TestAppContext};
use language::language_settings::AllLanguageSettings;
use serde_json::json;
use settings::Settings as _;
use text::{Buffer, BufferId, ToOffset as _};
use unindent::Unindent as _;
use util::path;
use worktree::WorktreeSettings;
#[test]
fn test_parse_conflicts_in_buffer() {
// Create a buffer with conflict markers
let test_content = r#"
This is some text before the conflict.
<<<<<<< HEAD
This is our version
=======
This is their version
>>>>>>> branch-name
Another conflict:
<<<<<<< HEAD
Our second change
||||||| merged common ancestors
Original content
=======
Their second change
>>>>>>> branch-name
"#
.unindent();
let buffer_id = BufferId::new(1).unwrap();
let buffer = Buffer::new(0, buffer_id, test_content);
let snapshot = buffer.snapshot();
let conflict_snapshot = ConflictSet::parse(&snapshot);
assert_eq!(conflict_snapshot.conflicts.len(), 2);
let first = &conflict_snapshot.conflicts[0];
assert!(first.base.is_none());
let our_text = snapshot
.text_for_range(first.ours.clone())
.collect::<String>();
let their_text = snapshot
.text_for_range(first.theirs.clone())
.collect::<String>();
assert_eq!(our_text, "This is our version\n");
assert_eq!(their_text, "This is their version\n");
let second = &conflict_snapshot.conflicts[1];
assert!(second.base.is_some());
let our_text = snapshot
.text_for_range(second.ours.clone())
.collect::<String>();
let their_text = snapshot
.text_for_range(second.theirs.clone())
.collect::<String>();
let base_text = snapshot
.text_for_range(second.base.as_ref().unwrap().clone())
.collect::<String>();
assert_eq!(our_text, "Our second change\n");
assert_eq!(their_text, "Their second change\n");
assert_eq!(base_text, "Original content\n");
// Test conflicts_in_range
let range = snapshot.anchor_before(0)..snapshot.anchor_before(snapshot.len());
let conflicts_in_range = conflict_snapshot.conflicts_in_range(range, &snapshot);
assert_eq!(conflicts_in_range.len(), 2);
// Test with a range that includes only the first conflict
let first_conflict_end = conflict_snapshot.conflicts[0].range.end;
let range = snapshot.anchor_before(0)..first_conflict_end;
let conflicts_in_range = conflict_snapshot.conflicts_in_range(range, &snapshot);
assert_eq!(conflicts_in_range.len(), 1);
// Test with a range that includes only the second conflict
let second_conflict_start = conflict_snapshot.conflicts[1].range.start;
let range = second_conflict_start..snapshot.anchor_before(snapshot.len());
let conflicts_in_range = conflict_snapshot.conflicts_in_range(range, &snapshot);
assert_eq!(conflicts_in_range.len(), 1);
// Test with a range that doesn't include any conflicts
let range = buffer.anchor_after(first_conflict_end.to_offset(&buffer) + 1)
..buffer.anchor_before(second_conflict_start.to_offset(&buffer) - 1);
let conflicts_in_range = conflict_snapshot.conflicts_in_range(range, &snapshot);
assert_eq!(conflicts_in_range.len(), 0);
}
#[test]
fn test_nested_conflict_markers() {
// Create a buffer with nested conflict markers
let test_content = r#"
This is some text before the conflict.
<<<<<<< HEAD
This is our version
<<<<<<< HEAD
This is a nested conflict marker
=======
This is their version in a nested conflict
>>>>>>> branch-nested
=======
This is their version
>>>>>>> branch-name
"#
.unindent();
let buffer_id = BufferId::new(1).unwrap();
let buffer = Buffer::new(0, buffer_id, test_content.to_string());
let snapshot = buffer.snapshot();
let conflict_snapshot = ConflictSet::parse(&snapshot);
assert_eq!(conflict_snapshot.conflicts.len(), 1);
// The conflict should have our version, their version, but no base
let conflict = &conflict_snapshot.conflicts[0];
assert!(conflict.base.is_none());
// Check that the nested conflict was detected correctly
let our_text = snapshot
.text_for_range(conflict.ours.clone())
.collect::<String>();
assert_eq!(our_text, "This is a nested conflict marker\n");
let their_text = snapshot
.text_for_range(conflict.theirs.clone())
.collect::<String>();
assert_eq!(their_text, "This is their version in a nested conflict\n");
}
#[test]
fn test_conflicts_in_range() {
// Create a buffer with conflict markers
let test_content = r#"
one
<<<<<<< HEAD1
two
=======
three
>>>>>>> branch1
four
five
<<<<<<< HEAD2
six
=======
seven
>>>>>>> branch2
eight
nine
<<<<<<< HEAD3
ten
=======
eleven
>>>>>>> branch3
twelve
<<<<<<< HEAD4
thirteen
=======
fourteen
>>>>>>> branch4
fifteen
"#
.unindent();
let buffer_id = BufferId::new(1).unwrap();
let buffer = Buffer::new(0, buffer_id, test_content.clone());
let snapshot = buffer.snapshot();
let conflict_snapshot = ConflictSet::parse(&snapshot);
assert_eq!(conflict_snapshot.conflicts.len(), 4);
let range = test_content.find("seven").unwrap()..test_content.find("eleven").unwrap();
let range = buffer.anchor_before(range.start)..buffer.anchor_after(range.end);
assert_eq!(
conflict_snapshot.conflicts_in_range(range, &snapshot),
&conflict_snapshot.conflicts[1..=2]
);
let range = test_content.find("one").unwrap()..test_content.find("<<<<<<< HEAD2").unwrap();
let range = buffer.anchor_before(range.start)..buffer.anchor_after(range.end);
assert_eq!(
conflict_snapshot.conflicts_in_range(range, &snapshot),
&conflict_snapshot.conflicts[0..=1]
);
let range =
test_content.find("eight").unwrap() - 1..test_content.find(">>>>>>> branch3").unwrap();
let range = buffer.anchor_before(range.start)..buffer.anchor_after(range.end);
assert_eq!(
conflict_snapshot.conflicts_in_range(range, &snapshot),
&conflict_snapshot.conflicts[1..=2]
);
let range = test_content.find("thirteen").unwrap() - 1..test_content.len();
let range = buffer.anchor_before(range.start)..buffer.anchor_after(range.end);
assert_eq!(
conflict_snapshot.conflicts_in_range(range, &snapshot),
&conflict_snapshot.conflicts[3..=3]
);
}
#[gpui::test]
async fn test_conflict_updates(executor: BackgroundExecutor, cx: &mut TestAppContext) {
env_logger::try_init().ok();
cx.update(|cx| {
settings::init(cx);
WorktreeSettings::register(cx);
ProjectSettings::register(cx);
AllLanguageSettings::register(cx);
});
let initial_text = "
one
two
three
four
five
"
.unindent();
let fs = FakeFs::new(executor);
fs.insert_tree(
path!("/project"),
json!({
".git": {},
"a.txt": initial_text,
}),
)
.await;
let project = Project::test(fs.clone(), [path!("/project").as_ref()], cx).await;
let (git_store, buffer) = project.update(cx, |project, cx| {
(
project.git_store().clone(),
project.open_local_buffer(path!("/project/a.txt"), cx),
)
});
let buffer = buffer.await.unwrap();
let conflict_set = git_store.update(cx, |git_store, cx| {
git_store.open_conflict_set(buffer.clone(), cx)
});
let (events_tx, events_rx) = mpsc::channel::<ConflictSetUpdate>();
let _conflict_set_subscription = cx.update(|cx| {
cx.subscribe(&conflict_set, move |_, event, _| {
events_tx.send(event.clone()).ok();
})
});
let conflicts_snapshot = conflict_set.update(cx, |conflict_set, _| conflict_set.snapshot());
assert!(conflicts_snapshot.conflicts.is_empty());
buffer.update(cx, |buffer, cx| {
buffer.edit(
[
(4..4, "<<<<<<< HEAD\n"),
(14..14, "=======\nTWO\n>>>>>>> branch\n"),
],
None,
cx,
);
});
cx.run_until_parked();
events_rx.try_recv().expect_err(
"no conflicts should be registered as long as the file's status is unchanged",
);
fs.with_git_state(path!("/project/.git").as_ref(), true, |state| {
state.unmerged_paths.insert(
"a.txt".into(),
UnmergedStatus {
first_head: UnmergedStatusCode::Updated,
second_head: UnmergedStatusCode::Updated,
},
);
// Cause the repository to emit MergeHeadsChanged.
state.merge_head_shas = vec!["abc".into(), "def".into()]
})
.unwrap();
cx.run_until_parked();
let update = events_rx
.try_recv()
.expect("status change should trigger conflict parsing");
assert_eq!(update.old_range, 0..0);
assert_eq!(update.new_range, 0..1);
let conflict = conflict_set.update(cx, |conflict_set, _| {
conflict_set.snapshot().conflicts[0].clone()
});
cx.update(|cx| {
conflict.resolve(buffer.clone(), &[conflict.theirs.clone()], cx);
});
cx.run_until_parked();
let update = events_rx
.try_recv()
.expect("conflicts should be removed after resolution");
assert_eq!(update.old_range, 0..1);
assert_eq!(update.new_range, 0..0);
}
}

View File

@@ -29,10 +29,7 @@ pub mod search_history;
mod yarn;
use crate::git_store::GitStore;
pub use git_store::{
ConflictRegion, ConflictSet, ConflictSetSnapshot, ConflictSetUpdate,
git_traversal::{ChildEntriesGitIter, GitEntry, GitEntryRef, GitTraversal},
};
pub use git_store::git_traversal::{ChildEntriesGitIter, GitEntry, GitEntryRef, GitTraversal};
use anyhow::{Context as _, Result, anyhow};
use buffer_store::{BufferStore, BufferStoreEvent};

View File

@@ -83,6 +83,7 @@ impl VsCodeDebugTaskDefinition {
}
}
/// blah
#[derive(Clone, Debug, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct VsCodeDebugTaskFile {

View File

@@ -115,7 +115,6 @@ pub struct TerminalView {
blinking_paused: bool,
blink_epoch: usize,
hover_target_tooltip: Option<String>,
hover_tooltip_update: Task<()>,
workspace_id: Option<WorkspaceId>,
show_breadcrumbs: bool,
block_below_cursor: Option<Rc<BlockProperties>>,
@@ -198,7 +197,6 @@ impl TerminalView {
blinking_paused: false,
blink_epoch: 0,
hover_target_tooltip: None,
hover_tooltip_update: Task::ready(()),
workspace_id,
show_breadcrumbs: TerminalSettings::get_global(cx).toolbar.breadcrumbs,
block_below_cursor: None,
@@ -846,7 +844,7 @@ fn subscribe_for_terminal_events(
let terminal_events_subscription = cx.subscribe_in(
terminal,
window,
move |terminal_view, _, event, window, cx| match event {
move |this, _, event, window, cx| match event {
Event::Wakeup => {
cx.notify();
cx.emit(Event::Wakeup);
@@ -855,7 +853,7 @@ fn subscribe_for_terminal_events(
}
Event::Bell => {
terminal_view.has_bell = true;
this.has_bell = true;
cx.emit(Event::Wakeup);
}
@@ -864,7 +862,7 @@ fn subscribe_for_terminal_events(
TerminalSettings::get_global(cx).blinking,
TerminalBlink::TerminalControlled
) {
terminal_view.blinking_terminal_enabled = *blinking;
this.blinking_terminal_enabled = *blinking;
}
}
@@ -873,46 +871,25 @@ fn subscribe_for_terminal_events(
}
Event::NewNavigationTarget(maybe_navigation_target) => {
match maybe_navigation_target.as_ref() {
None => {
terminal_view.hover_target_tooltip = None;
terminal_view.hover_tooltip_update = Task::ready(());
}
Some(MaybeNavigationTarget::Url(url)) => {
terminal_view.hover_target_tooltip = Some(url.clone());
terminal_view.hover_tooltip_update = Task::ready(());
}
Some(MaybeNavigationTarget::PathLike(path_like_target)) => {
let valid_files_to_open_task = possible_open_target(
&workspace,
&path_like_target.terminal_dir,
&path_like_target.maybe_path,
cx,
);
terminal_view.hover_tooltip_update =
cx.spawn(async move |terminal_view, cx| {
let file_to_open = valid_files_to_open_task.await;
terminal_view
.update(cx, |terminal_view, _| match file_to_open {
Some(
OpenTarget::File(path, _)
| OpenTarget::Worktree(path, _),
) => {
terminal_view.hover_target_tooltip =
Some(path.to_string(|path| {
path.to_string_lossy().to_string()
}));
}
None => {
terminal_view.hover_target_tooltip = None;
}
})
.ok();
});
}
}
this.hover_target_tooltip =
maybe_navigation_target
.as_ref()
.and_then(|navigation_target| match navigation_target {
MaybeNavigationTarget::Url(url) => Some(url.clone()),
MaybeNavigationTarget::PathLike(path_like_target) => {
let valid_files_to_open_task = possible_open_target(
&workspace,
&path_like_target.terminal_dir,
&path_like_target.maybe_path,
cx,
);
Some(match smol::block_on(valid_files_to_open_task)? {
OpenTarget::File(path, _) | OpenTarget::Worktree(path, _) => {
path.to_string(|path| path.to_string_lossy().to_string())
}
})
}
});
cx.notify()
}
@@ -920,7 +897,7 @@ fn subscribe_for_terminal_events(
MaybeNavigationTarget::Url(url) => cx.open_url(url),
MaybeNavigationTarget::PathLike(path_like_target) => {
if terminal_view.hover_target_tooltip.is_none() {
if this.hover_target_tooltip.is_none() {
return;
}
let task_workspace = workspace.clone();
@@ -1230,12 +1207,9 @@ fn possible_open_target(
let fs = workspace.read(cx).project().read(cx).fs().clone();
cx.background_spawn(async move {
for mut path_to_check in fs_paths_to_check {
if let Some(fs_path_to_check) = fs.canonicalize(&path_to_check.path).await.ok() {
if let Some(metadata) = fs.metadata(&fs_path_to_check).await.ok().flatten() {
path_to_check.path = fs_path_to_check;
return Some(OpenTarget::File(path_to_check, metadata));
}
for path_to_check in fs_paths_to_check {
if let Some(metadata) = fs.metadata(&path_to_check.path).await.ok().flatten() {
return Some(OpenTarget::File(path_to_check, metadata));
}
}

View File

@@ -143,11 +143,6 @@ impl ThemeColors {
version_control_renamed: MODIFIED_COLOR,
version_control_conflict: orange().light().step_12(),
version_control_ignored: gray().light().step_12(),
version_control_conflict_ours_background: green().light().step_10().alpha(0.5),
version_control_conflict_theirs_background: blue().light().step_10().alpha(0.5),
version_control_conflict_ours_marker_background: green().light().step_10().alpha(0.7),
version_control_conflict_theirs_marker_background: blue().light().step_10().alpha(0.7),
version_control_conflict_divider_background: Hsla::default(),
}
}
@@ -263,11 +258,6 @@ impl ThemeColors {
version_control_renamed: MODIFIED_COLOR,
version_control_conflict: orange().dark().step_12(),
version_control_ignored: gray().dark().step_12(),
version_control_conflict_ours_background: green().dark().step_10().alpha(0.5),
version_control_conflict_theirs_background: blue().dark().step_10().alpha(0.5),
version_control_conflict_ours_marker_background: green().dark().step_10().alpha(0.7),
version_control_conflict_theirs_marker_background: blue().dark().step_10().alpha(0.7),
version_control_conflict_divider_background: Hsla::default(),
}
}
}

View File

@@ -201,23 +201,6 @@ pub(crate) fn zed_default_dark() -> Theme {
version_control_renamed: MODIFIED_COLOR,
version_control_conflict: crate::orange().light().step_12(),
version_control_ignored: crate::gray().light().step_12(),
version_control_conflict_ours_background: crate::green()
.light()
.step_12()
.alpha(0.5),
version_control_conflict_theirs_background: crate::blue()
.light()
.step_12()
.alpha(0.5),
version_control_conflict_ours_marker_background: crate::green()
.light()
.step_12()
.alpha(0.7),
version_control_conflict_theirs_marker_background: crate::blue()
.light()
.step_12()
.alpha(0.7),
version_control_conflict_divider_background: Hsla::default(),
},
status: StatusColors {
conflict: yellow,

View File

@@ -586,26 +586,6 @@ pub struct ThemeColorsContent {
/// Ignored version control color.
#[serde(rename = "version_control.ignored")]
pub version_control_ignored: Option<String>,
/// Background color for row highlights of "ours" regions in merge conflicts.
#[serde(rename = "version_control.conflict.ours_background")]
pub version_control_conflict_ours_background: Option<String>,
/// Background color for row highlights of "theirs" regions in merge conflicts.
#[serde(rename = "version_control.conflict.theirs_background")]
pub version_control_conflict_theirs_background: Option<String>,
/// Background color for row highlights of "ours" conflict markers in merge conflicts.
#[serde(rename = "version_control.conflict.ours_marker_background")]
pub version_control_conflict_ours_marker_background: Option<String>,
/// Background color for row highlights of "theirs" conflict markers in merge conflicts.
#[serde(rename = "version_control.conflict.theirs_marker_background")]
pub version_control_conflict_theirs_marker_background: Option<String>,
/// Background color for row highlights of the "ours"/"theirs" divider in merge conflicts.
#[serde(rename = "version_control.conflict.divider_background")]
pub version_control_conflict_divider_background: Option<String>,
}
impl ThemeColorsContent {
@@ -1057,26 +1037,6 @@ impl ThemeColorsContent {
.and_then(|color| try_parse_color(color).ok())
// Fall back to `conflict`, for backwards compatibility.
.or(status_colors.ignored),
version_control_conflict_ours_background: self
.version_control_conflict_ours_background
.as_ref()
.and_then(|color| try_parse_color(color).ok()),
version_control_conflict_theirs_background: self
.version_control_conflict_theirs_background
.as_ref()
.and_then(|color| try_parse_color(color).ok()),
version_control_conflict_ours_marker_background: self
.version_control_conflict_ours_marker_background
.as_ref()
.and_then(|color| try_parse_color(color).ok()),
version_control_conflict_theirs_marker_background: self
.version_control_conflict_theirs_marker_background
.as_ref()
.and_then(|color| try_parse_color(color).ok()),
version_control_conflict_divider_background: self
.version_control_conflict_divider_background
.as_ref()
.and_then(|color| try_parse_color(color).ok()),
}
}
}

View File

@@ -261,14 +261,6 @@ pub struct ThemeColors {
pub version_control_conflict: Hsla,
/// Represents an ignored entry in version control systems.
pub version_control_ignored: Hsla,
/// Represents the "ours" region of a merge conflict.
pub version_control_conflict_ours_background: Hsla,
/// Represents the "theirs" region of a merge conflict.
pub version_control_conflict_theirs_background: Hsla,
pub version_control_conflict_ours_marker_background: Hsla,
pub version_control_conflict_theirs_marker_background: Hsla,
pub version_control_conflict_divider_background: Hsla,
}
#[derive(EnumIter, Debug, Clone, Copy, AsRefStr)]

View File

@@ -30,7 +30,7 @@ itertools.workspace = true
language.workspace = true
log.workspace = true
multi_buffer.workspace = true
nvim-rs = { git = "https://github.com/KillTheMule/nvim-rs", rev = "764dd270c642f77f10f3e19d05cc178a6cbe69f3", features = ["use_tokio"], optional = true }
nvim-rs = { git = "https://github.com/KillTheMule/nvim-rs", branch = "master", features = ["use_tokio"], optional = true }
picker.workspace = true
project.workspace = true
regex.workspace = true

View File

@@ -1500,7 +1500,7 @@ impl ShellExec {
editor.highlight_rows::<ShellExec>(
input_range.clone().unwrap(),
cx.theme().status().unreachable_background,
Default::default(),
false,
cx,
);

View File

@@ -61,11 +61,4 @@ impl WebSearchRegistry {
self.active_provider = Some(provider);
}
}
pub fn unregister_provider(&mut self, id: WebSearchProviderId) {
self.providers.remove(&id);
if self.active_provider.as_ref().map(|provider| provider.id()) == Some(id) {
self.active_provider = None;
}
}
}

View File

@@ -14,6 +14,7 @@ path = "src/web_search_providers.rs"
[dependencies]
anyhow.workspace = true
client.workspace = true
feature_flags.workspace = true
futures.workspace = true
gpui.workspace = true
http_client.workspace = true

View File

@@ -50,11 +50,9 @@ impl State {
}
}
pub const ZED_WEB_SEARCH_PROVIDER_ID: &'static str = "zed.dev";
impl WebSearchProvider for CloudWebSearchProvider {
fn id(&self) -> WebSearchProviderId {
WebSearchProviderId(ZED_WEB_SEARCH_PROVIDER_ID.into())
WebSearchProviderId("zed.dev".into())
}
fn search(&self, query: String, cx: &mut App) -> Task<Result<WebSearchResponse>> {

View File

@@ -1,10 +1,10 @@
mod cloud;
use client::Client;
use feature_flags::{FeatureFlagAppExt, ZedProWebSearchTool};
use gpui::{App, Context};
use language_model::LanguageModelRegistry;
use std::sync::Arc;
use web_search::{WebSearchProviderId, WebSearchRegistry};
use web_search::WebSearchRegistry;
pub fn init(client: Arc<Client>, cx: &mut App) {
let registry = WebSearchRegistry::global(cx);
@@ -18,27 +18,18 @@ fn register_web_search_providers(
client: Arc<Client>,
cx: &mut Context<WebSearchRegistry>,
) {
cx.subscribe(
&LanguageModelRegistry::global(cx),
move |this, registry, event, cx| match event {
language_model::Event::DefaultModelChanged => {
let using_zed_provider = registry
.read(cx)
.default_model()
.map_or(false, |default| default.is_provided_by_zed());
if using_zed_provider {
this.register_provider(
cx.observe_flag::<ZedProWebSearchTool, _>({
let client = client.clone();
move |is_enabled, cx| {
if is_enabled {
WebSearchRegistry::global(cx).update(cx, |registry, cx| {
registry.register_provider(
cloud::CloudWebSearchProvider::new(client.clone(), cx),
cx,
)
} else {
this.unregister_provider(WebSearchProviderId(
cloud::ZED_WEB_SEARCH_PROVIDER_ID.into(),
));
}
);
});
}
_ => {}
},
)
}
})
.detach();
}

View File

@@ -5879,8 +5879,7 @@ fn resize_bottom_dock(
window: &mut Window,
cx: &mut App,
) {
let size =
new_size.min(workspace.bounds.bottom() - RESIZE_HANDLE_SIZE - workspace.bounds.top());
let size = new_size.min(workspace.bounds.bottom() - RESIZE_HANDLE_SIZE);
workspace.bottom_dock.update(cx, |bottom_dock, cx| {
bottom_dock.resize_active_panel(Some(size), window, cx);
});

View File

@@ -2,7 +2,7 @@
description = "The fast, collaborative code editor."
edition.workspace = true
name = "zed"
version = "0.185.0"
version = "0.184.0"
publish.workspace = true
license = "GPL-3.0-or-later"
authors = ["Zed Team <hi@zed.dev>"]

View File

@@ -90,7 +90,7 @@ regex = { version = "1" }
regex-automata = { version = "0.4" }
regex-syntax = { version = "0.8" }
rust_decimal = { version = "1", default-features = false, features = ["maths", "serde", "std"] }
rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38", default-features = false, features = ["fs", "net", "std"] }
rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38", features = ["fs", "net"] }
rustls = { version = "0.23", features = ["ring"] }
rustls-webpki = { version = "0.103", default-features = false, features = ["aws-lc-rs", "ring", "std"] }
sea-orm = { version = "1", features = ["runtime-tokio-rustls", "sqlx-postgres", "sqlx-sqlite"] }
@@ -205,7 +205,7 @@ regex = { version = "1" }
regex-automata = { version = "0.4" }
regex-syntax = { version = "0.8" }
rust_decimal = { version = "1", default-features = false, features = ["maths", "serde", "std"] }
rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38", default-features = false, features = ["fs", "net", "std"] }
rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38", features = ["fs", "net"] }
rustls = { version = "0.23", features = ["ring"] }
rustls-webpki = { version = "0.103", default-features = false, features = ["aws-lc-rs", "ring", "std"] }
sea-orm = { version = "1", features = ["runtime-tokio-rustls", "sqlx-postgres", "sqlx-sqlite"] }
@@ -258,8 +258,8 @@ naga = { version = "23", features = ["msl-out", "wgsl-in"] }
nix = { version = "0.29", features = ["fs", "pthread", "signal"] }
object = { version = "0.36", default-features = false, features = ["archive", "read_core", "unaligned", "write"] }
ring = { version = "0.17", features = ["std"] }
rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38", features = ["event", "mm", "param", "pipe", "process", "termios", "time"] }
rustix-dff4ba8e3ae991db = { package = "rustix", version = "1", features = ["fs", "net", "process", "termios", "time"] }
rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38", default-features = false, features = ["event", "mm", "param", "pipe", "process", "procfs", "termios", "time"] }
rustix-dff4ba8e3ae991db = { package = "rustix", version = "1", features = ["fs", "termios", "time"] }
scopeguard = { version = "1" }
security-framework = { version = "3", features = ["OSX_10_14"] }
security-framework-sys = { version = "2", features = ["OSX_10_14"] }
@@ -283,8 +283,8 @@ nix = { version = "0.29", features = ["fs", "pthread", "signal"] }
object = { version = "0.36", default-features = false, features = ["archive", "read_core", "unaligned", "write"] }
proc-macro2 = { version = "1", default-features = false, features = ["span-locations"] }
ring = { version = "0.17", features = ["std"] }
rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38", features = ["event", "mm", "param", "pipe", "process", "termios", "time"] }
rustix-dff4ba8e3ae991db = { package = "rustix", version = "1", features = ["fs", "net", "process", "termios", "time"] }
rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38", default-features = false, features = ["event", "mm", "param", "pipe", "process", "procfs", "termios", "time"] }
rustix-dff4ba8e3ae991db = { package = "rustix", version = "1", features = ["fs", "termios", "time"] }
scopeguard = { version = "1" }
security-framework = { version = "3", features = ["OSX_10_14"] }
security-framework-sys = { version = "2", features = ["OSX_10_14"] }
@@ -306,8 +306,8 @@ naga = { version = "23", features = ["msl-out", "wgsl-in"] }
nix = { version = "0.29", features = ["fs", "pthread", "signal"] }
object = { version = "0.36", default-features = false, features = ["archive", "read_core", "unaligned", "write"] }
ring = { version = "0.17", features = ["std"] }
rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38", features = ["event", "mm", "param", "pipe", "process", "termios", "time"] }
rustix-dff4ba8e3ae991db = { package = "rustix", version = "1", features = ["fs", "net", "process", "termios", "time"] }
rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38", default-features = false, features = ["event", "mm", "param", "pipe", "process", "procfs", "termios", "time"] }
rustix-dff4ba8e3ae991db = { package = "rustix", version = "1", features = ["fs", "termios", "time"] }
scopeguard = { version = "1" }
security-framework = { version = "3", features = ["OSX_10_14"] }
security-framework-sys = { version = "2", features = ["OSX_10_14"] }
@@ -331,8 +331,8 @@ nix = { version = "0.29", features = ["fs", "pthread", "signal"] }
object = { version = "0.36", default-features = false, features = ["archive", "read_core", "unaligned", "write"] }
proc-macro2 = { version = "1", default-features = false, features = ["span-locations"] }
ring = { version = "0.17", features = ["std"] }
rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38", features = ["event", "mm", "param", "pipe", "process", "termios", "time"] }
rustix-dff4ba8e3ae991db = { package = "rustix", version = "1", features = ["fs", "net", "process", "termios", "time"] }
rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38", default-features = false, features = ["event", "mm", "param", "pipe", "process", "procfs", "termios", "time"] }
rustix-dff4ba8e3ae991db = { package = "rustix", version = "1", features = ["fs", "termios", "time"] }
scopeguard = { version = "1" }
security-framework = { version = "3", features = ["OSX_10_14"] }
security-framework-sys = { version = "2", features = ["OSX_10_14"] }
@@ -356,8 +356,7 @@ gimli = { version = "0.31", default-features = false, features = ["read", "std",
hyper-rustls = { version = "0.27", default-features = false, features = ["http1", "http2", "native-tokio", "ring", "tls12"] }
inout = { version = "0.1", default-features = false, features = ["block-padding"] }
itertools-5ef9efb8ec2df382 = { package = "itertools", version = "0.12" }
linux-raw-sys-274715c4dabd11b0 = { package = "linux-raw-sys", version = "0.9", default-features = false, features = ["elf", "errno", "general", "if_ether", "ioctl", "net", "netlink", "no_std", "prctl", "xdp"] }
linux-raw-sys-9fbad63c4bcf4a8f = { package = "linux-raw-sys", version = "0.4", default-features = false, features = ["elf", "errno", "general", "if_ether", "ioctl", "net", "netlink", "no_std", "prctl", "system", "xdp"] }
linux-raw-sys = { version = "0.4", default-features = false, features = ["elf", "errno", "general", "if_ether", "ioctl", "net", "netlink", "no_std", "prctl", "system", "xdp"] }
mio = { version = "1", features = ["net", "os-ext"] }
naga = { version = "23", features = ["spv-out", "wgsl-in"] }
nix = { version = "0.29", features = ["fs", "pthread", "signal", "socket", "uio", "user"] }
@@ -368,8 +367,8 @@ proc-macro2 = { version = "1", features = ["span-locations"] }
quote = { version = "1" }
rand-274715c4dabd11b0 = { package = "rand", version = "0.9" }
ring = { version = "0.17", features = ["std"] }
rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38", features = ["event", "mm", "param", "pipe", "process", "pty", "shm", "stdio", "system", "termios", "time"] }
rustix-dff4ba8e3ae991db = { package = "rustix", version = "1", features = ["fs", "net", "process", "termios", "time"] }
rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38", default-features = false, features = ["event", "mm", "param", "pipe", "process", "procfs", "pty", "shm", "stdio", "system", "termios", "time"] }
rustix-dff4ba8e3ae991db = { package = "rustix", version = "1", features = ["fs", "termios", "time"] }
scopeguard = { version = "1" }
smallvec = { version = "1", default-features = false, features = ["write"] }
syn-f595c2ba2a3f28df = { package = "syn", version = "2", features = ["extra-traits", "fold", "full", "visit", "visit-mut"] }
@@ -397,8 +396,7 @@ gimli = { version = "0.31", default-features = false, features = ["read", "std",
hyper-rustls = { version = "0.27", default-features = false, features = ["http1", "http2", "native-tokio", "ring", "tls12"] }
inout = { version = "0.1", default-features = false, features = ["block-padding"] }
itertools-5ef9efb8ec2df382 = { package = "itertools", version = "0.12" }
linux-raw-sys-274715c4dabd11b0 = { package = "linux-raw-sys", version = "0.9", default-features = false, features = ["elf", "errno", "general", "if_ether", "ioctl", "net", "netlink", "no_std", "prctl", "xdp"] }
linux-raw-sys-9fbad63c4bcf4a8f = { package = "linux-raw-sys", version = "0.4", default-features = false, features = ["elf", "errno", "general", "if_ether", "ioctl", "net", "netlink", "no_std", "prctl", "system", "xdp"] }
linux-raw-sys = { version = "0.4", default-features = false, features = ["elf", "errno", "general", "if_ether", "ioctl", "net", "netlink", "no_std", "prctl", "system", "xdp"] }
mio = { version = "1", features = ["net", "os-ext"] }
naga = { version = "23", features = ["spv-out", "wgsl-in"] }
nix = { version = "0.29", features = ["fs", "pthread", "signal", "socket", "uio", "user"] }
@@ -408,8 +406,8 @@ object = { version = "0.36", default-features = false, features = ["archive", "r
proc-macro2 = { version = "1", default-features = false, features = ["span-locations"] }
rand-274715c4dabd11b0 = { package = "rand", version = "0.9" }
ring = { version = "0.17", features = ["std"] }
rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38", features = ["event", "mm", "param", "pipe", "process", "pty", "shm", "stdio", "system", "termios", "time"] }
rustix-dff4ba8e3ae991db = { package = "rustix", version = "1", features = ["fs", "net", "process", "termios", "time"] }
rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38", default-features = false, features = ["event", "mm", "param", "pipe", "process", "procfs", "pty", "shm", "stdio", "system", "termios", "time"] }
rustix-dff4ba8e3ae991db = { package = "rustix", version = "1", features = ["fs", "termios", "time"] }
scopeguard = { version = "1" }
smallvec = { version = "1", default-features = false, features = ["write"] }
tokio-rustls = { version = "0.26", default-features = false, features = ["ring"] }
@@ -436,8 +434,7 @@ gimli = { version = "0.31", default-features = false, features = ["read", "std",
hyper-rustls = { version = "0.27", default-features = false, features = ["http1", "http2", "native-tokio", "ring", "tls12"] }
inout = { version = "0.1", default-features = false, features = ["block-padding"] }
itertools-5ef9efb8ec2df382 = { package = "itertools", version = "0.12" }
linux-raw-sys-274715c4dabd11b0 = { package = "linux-raw-sys", version = "0.9", default-features = false, features = ["elf", "errno", "general", "if_ether", "ioctl", "net", "netlink", "no_std", "prctl", "xdp"] }
linux-raw-sys-9fbad63c4bcf4a8f = { package = "linux-raw-sys", version = "0.4", default-features = false, features = ["elf", "errno", "general", "if_ether", "ioctl", "net", "netlink", "no_std", "prctl", "system", "xdp"] }
linux-raw-sys = { version = "0.4", default-features = false, features = ["elf", "errno", "general", "if_ether", "ioctl", "net", "netlink", "no_std", "prctl", "system", "xdp"] }
mio = { version = "1", features = ["net", "os-ext"] }
naga = { version = "23", features = ["spv-out", "wgsl-in"] }
nix = { version = "0.29", features = ["fs", "pthread", "signal", "socket", "uio", "user"] }
@@ -448,8 +445,8 @@ proc-macro2 = { version = "1", features = ["span-locations"] }
quote = { version = "1" }
rand-274715c4dabd11b0 = { package = "rand", version = "0.9" }
ring = { version = "0.17", features = ["std"] }
rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38", features = ["event", "mm", "param", "pipe", "process", "pty", "shm", "stdio", "system", "termios", "time"] }
rustix-dff4ba8e3ae991db = { package = "rustix", version = "1", features = ["fs", "net", "process", "termios", "time"] }
rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38", default-features = false, features = ["event", "mm", "param", "pipe", "process", "procfs", "pty", "shm", "stdio", "system", "termios", "time"] }
rustix-dff4ba8e3ae991db = { package = "rustix", version = "1", features = ["fs", "termios", "time"] }
scopeguard = { version = "1" }
smallvec = { version = "1", default-features = false, features = ["write"] }
syn-f595c2ba2a3f28df = { package = "syn", version = "2", features = ["extra-traits", "fold", "full", "visit", "visit-mut"] }
@@ -477,8 +474,7 @@ gimli = { version = "0.31", default-features = false, features = ["read", "std",
hyper-rustls = { version = "0.27", default-features = false, features = ["http1", "http2", "native-tokio", "ring", "tls12"] }
inout = { version = "0.1", default-features = false, features = ["block-padding"] }
itertools-5ef9efb8ec2df382 = { package = "itertools", version = "0.12" }
linux-raw-sys-274715c4dabd11b0 = { package = "linux-raw-sys", version = "0.9", default-features = false, features = ["elf", "errno", "general", "if_ether", "ioctl", "net", "netlink", "no_std", "prctl", "xdp"] }
linux-raw-sys-9fbad63c4bcf4a8f = { package = "linux-raw-sys", version = "0.4", default-features = false, features = ["elf", "errno", "general", "if_ether", "ioctl", "net", "netlink", "no_std", "prctl", "system", "xdp"] }
linux-raw-sys = { version = "0.4", default-features = false, features = ["elf", "errno", "general", "if_ether", "ioctl", "net", "netlink", "no_std", "prctl", "system", "xdp"] }
mio = { version = "1", features = ["net", "os-ext"] }
naga = { version = "23", features = ["spv-out", "wgsl-in"] }
nix = { version = "0.29", features = ["fs", "pthread", "signal", "socket", "uio", "user"] }
@@ -488,8 +484,8 @@ object = { version = "0.36", default-features = false, features = ["archive", "r
proc-macro2 = { version = "1", default-features = false, features = ["span-locations"] }
rand-274715c4dabd11b0 = { package = "rand", version = "0.9" }
ring = { version = "0.17", features = ["std"] }
rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38", features = ["event", "mm", "param", "pipe", "process", "pty", "shm", "stdio", "system", "termios", "time"] }
rustix-dff4ba8e3ae991db = { package = "rustix", version = "1", features = ["fs", "net", "process", "termios", "time"] }
rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38", default-features = false, features = ["event", "mm", "param", "pipe", "process", "procfs", "pty", "shm", "stdio", "system", "termios", "time"] }
rustix-dff4ba8e3ae991db = { package = "rustix", version = "1", features = ["fs", "termios", "time"] }
scopeguard = { version = "1" }
smallvec = { version = "1", default-features = false, features = ["write"] }
tokio-rustls = { version = "0.26", default-features = false, features = ["ring"] }
@@ -510,7 +506,7 @@ hyper-rustls = { version = "0.27", default-features = false, features = ["http1"
itertools-5ef9efb8ec2df382 = { package = "itertools", version = "0.12" }
naga = { version = "23", features = ["spv-out", "wgsl-in"] }
ring = { version = "0.17", features = ["std"] }
rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38", features = ["event"] }
rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38", default-features = false, features = ["event"] }
scopeguard = { version = "1" }
tokio-rustls = { version = "0.26", default-features = false, features = ["ring"] }
tokio-socks = { version = "0.5", features = ["futures-io"] }
@@ -518,7 +514,7 @@ tokio-stream = { version = "0.1", features = ["fs"] }
winapi = { version = "0.3", default-features = false, features = ["cfg", "consoleapi", "errhandlingapi", "evntrace", "fileapi", "handleapi", "in6addr", "inaddr", "knownfolders", "minwinbase", "ntsecapi", "objbase", "processenv", "processthreadsapi", "shlobj", "std", "sysinfoapi", "winbase", "windef", "winerror", "winioctl"] }
windows-core = { version = "0.61" }
windows-numerics = { version = "0.2" }
windows-sys-73dcd821b1037cfd = { package = "windows-sys", version = "0.59", features = ["Wdk_Foundation", "Wdk_Storage_FileSystem", "Win32_Globalization", "Win32_NetworkManagement_IpHelper", "Win32_Networking_WinSock", "Win32_Security_Authentication_Identity", "Win32_Security_Credentials", "Win32_Security_Cryptography", "Win32_Storage_FileSystem", "Win32_System_Com", "Win32_System_Console", "Win32_System_Diagnostics_Debug", "Win32_System_IO", "Win32_System_Ioctl", "Win32_System_Kernel", "Win32_System_LibraryLoader", "Win32_System_Memory", "Win32_System_Performance", "Win32_System_Pipes", "Win32_System_Registry", "Win32_System_SystemInformation", "Win32_System_SystemServices", "Win32_System_Threading", "Win32_System_Time", "Win32_System_WindowsProgramming", "Win32_UI_Input_KeyboardAndMouse", "Win32_UI_Shell", "Win32_UI_WindowsAndMessaging"] }
windows-sys-73dcd821b1037cfd = { package = "windows-sys", version = "0.59", features = ["Wdk_Foundation", "Wdk_Storage_FileSystem", "Win32_NetworkManagement_IpHelper", "Win32_Networking_WinSock", "Win32_Security_Authentication_Identity", "Win32_Security_Credentials", "Win32_Security_Cryptography", "Win32_Storage_FileSystem", "Win32_System_Com", "Win32_System_Console", "Win32_System_Diagnostics_Debug", "Win32_System_IO", "Win32_System_Ioctl", "Win32_System_Kernel", "Win32_System_LibraryLoader", "Win32_System_Memory", "Win32_System_Performance", "Win32_System_Pipes", "Win32_System_Registry", "Win32_System_SystemInformation", "Win32_System_SystemServices", "Win32_System_Threading", "Win32_System_Time", "Win32_System_WindowsProgramming", "Win32_UI_Input_KeyboardAndMouse", "Win32_UI_Shell", "Win32_UI_WindowsAndMessaging"] }
windows-sys-b21d60becc0929df = { package = "windows-sys", version = "0.52", features = ["Wdk_Foundation", "Wdk_Storage_FileSystem", "Wdk_System_IO", "Win32_Foundation", "Win32_Networking_WinSock", "Win32_Security", "Win32_Storage_FileSystem", "Win32_System_Console", "Win32_System_IO", "Win32_System_Pipes", "Win32_System_SystemServices", "Win32_System_Threading", "Win32_System_WindowsProgramming"] }
windows-sys-c8eced492e86ede7 = { package = "windows-sys", version = "0.48", features = ["Win32_Foundation", "Win32_Globalization", "Win32_Networking_WinSock", "Win32_Security", "Win32_Storage_FileSystem", "Win32_System_Com", "Win32_System_Diagnostics_Debug", "Win32_System_IO", "Win32_System_Pipes", "Win32_System_Registry", "Win32_System_Threading", "Win32_System_Time", "Win32_UI_Shell"] }
@@ -533,7 +529,7 @@ itertools-5ef9efb8ec2df382 = { package = "itertools", version = "0.12" }
naga = { version = "23", features = ["spv-out", "wgsl-in"] }
proc-macro2 = { version = "1", default-features = false, features = ["span-locations"] }
ring = { version = "0.17", features = ["std"] }
rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38", features = ["event"] }
rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38", default-features = false, features = ["event"] }
scopeguard = { version = "1" }
tokio-rustls = { version = "0.26", default-features = false, features = ["ring"] }
tokio-socks = { version = "0.5", features = ["futures-io"] }
@@ -541,7 +537,7 @@ tokio-stream = { version = "0.1", features = ["fs"] }
winapi = { version = "0.3", default-features = false, features = ["cfg", "consoleapi", "errhandlingapi", "evntrace", "fileapi", "handleapi", "in6addr", "inaddr", "knownfolders", "minwinbase", "ntsecapi", "objbase", "processenv", "processthreadsapi", "shlobj", "std", "sysinfoapi", "winbase", "windef", "winerror", "winioctl"] }
windows-core = { version = "0.61" }
windows-numerics = { version = "0.2" }
windows-sys-73dcd821b1037cfd = { package = "windows-sys", version = "0.59", features = ["Wdk_Foundation", "Wdk_Storage_FileSystem", "Win32_Globalization", "Win32_NetworkManagement_IpHelper", "Win32_Networking_WinSock", "Win32_Security_Authentication_Identity", "Win32_Security_Credentials", "Win32_Security_Cryptography", "Win32_Storage_FileSystem", "Win32_System_Com", "Win32_System_Console", "Win32_System_Diagnostics_Debug", "Win32_System_IO", "Win32_System_Ioctl", "Win32_System_Kernel", "Win32_System_LibraryLoader", "Win32_System_Memory", "Win32_System_Performance", "Win32_System_Pipes", "Win32_System_Registry", "Win32_System_SystemInformation", "Win32_System_SystemServices", "Win32_System_Threading", "Win32_System_Time", "Win32_System_WindowsProgramming", "Win32_UI_Input_KeyboardAndMouse", "Win32_UI_Shell", "Win32_UI_WindowsAndMessaging"] }
windows-sys-73dcd821b1037cfd = { package = "windows-sys", version = "0.59", features = ["Wdk_Foundation", "Wdk_Storage_FileSystem", "Win32_NetworkManagement_IpHelper", "Win32_Networking_WinSock", "Win32_Security_Authentication_Identity", "Win32_Security_Credentials", "Win32_Security_Cryptography", "Win32_Storage_FileSystem", "Win32_System_Com", "Win32_System_Console", "Win32_System_Diagnostics_Debug", "Win32_System_IO", "Win32_System_Ioctl", "Win32_System_Kernel", "Win32_System_LibraryLoader", "Win32_System_Memory", "Win32_System_Performance", "Win32_System_Pipes", "Win32_System_Registry", "Win32_System_SystemInformation", "Win32_System_SystemServices", "Win32_System_Threading", "Win32_System_Time", "Win32_System_WindowsProgramming", "Win32_UI_Input_KeyboardAndMouse", "Win32_UI_Shell", "Win32_UI_WindowsAndMessaging"] }
windows-sys-b21d60becc0929df = { package = "windows-sys", version = "0.52", features = ["Wdk_Foundation", "Wdk_Storage_FileSystem", "Wdk_System_IO", "Win32_Foundation", "Win32_Networking_WinSock", "Win32_Security", "Win32_Storage_FileSystem", "Win32_System_Console", "Win32_System_IO", "Win32_System_Pipes", "Win32_System_SystemServices", "Win32_System_Threading", "Win32_System_WindowsProgramming"] }
windows-sys-c8eced492e86ede7 = { package = "windows-sys", version = "0.48", features = ["Win32_Foundation", "Win32_Globalization", "Win32_Networking_WinSock", "Win32_Security", "Win32_Storage_FileSystem", "Win32_System_Com", "Win32_System_Diagnostics_Debug", "Win32_System_IO", "Win32_System_Pipes", "Win32_System_Registry", "Win32_System_Threading", "Win32_System_Time", "Win32_UI_Shell"] }
@@ -561,8 +557,7 @@ gimli = { version = "0.31", default-features = false, features = ["read", "std",
hyper-rustls = { version = "0.27", default-features = false, features = ["http1", "http2", "native-tokio", "ring", "tls12"] }
inout = { version = "0.1", default-features = false, features = ["block-padding"] }
itertools-5ef9efb8ec2df382 = { package = "itertools", version = "0.12" }
linux-raw-sys-274715c4dabd11b0 = { package = "linux-raw-sys", version = "0.9", default-features = false, features = ["elf", "errno", "general", "if_ether", "ioctl", "net", "netlink", "no_std", "prctl", "xdp"] }
linux-raw-sys-9fbad63c4bcf4a8f = { package = "linux-raw-sys", version = "0.4", default-features = false, features = ["elf", "errno", "general", "if_ether", "ioctl", "net", "netlink", "no_std", "prctl", "system", "xdp"] }
linux-raw-sys = { version = "0.4", default-features = false, features = ["elf", "errno", "general", "if_ether", "ioctl", "net", "netlink", "no_std", "prctl", "system", "xdp"] }
mio = { version = "1", features = ["net", "os-ext"] }
naga = { version = "23", features = ["spv-out", "wgsl-in"] }
nix = { version = "0.29", features = ["fs", "pthread", "signal", "socket", "uio", "user"] }
@@ -573,8 +568,8 @@ proc-macro2 = { version = "1", features = ["span-locations"] }
quote = { version = "1" }
rand-274715c4dabd11b0 = { package = "rand", version = "0.9" }
ring = { version = "0.17", features = ["std"] }
rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38", features = ["event", "mm", "param", "pipe", "process", "pty", "shm", "stdio", "system", "termios", "time"] }
rustix-dff4ba8e3ae991db = { package = "rustix", version = "1", features = ["fs", "net", "process", "termios", "time"] }
rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38", default-features = false, features = ["event", "mm", "param", "pipe", "process", "procfs", "pty", "shm", "stdio", "system", "termios", "time"] }
rustix-dff4ba8e3ae991db = { package = "rustix", version = "1", features = ["fs", "termios", "time"] }
scopeguard = { version = "1" }
smallvec = { version = "1", default-features = false, features = ["write"] }
syn-f595c2ba2a3f28df = { package = "syn", version = "2", features = ["extra-traits", "fold", "full", "visit", "visit-mut"] }
@@ -602,8 +597,7 @@ gimli = { version = "0.31", default-features = false, features = ["read", "std",
hyper-rustls = { version = "0.27", default-features = false, features = ["http1", "http2", "native-tokio", "ring", "tls12"] }
inout = { version = "0.1", default-features = false, features = ["block-padding"] }
itertools-5ef9efb8ec2df382 = { package = "itertools", version = "0.12" }
linux-raw-sys-274715c4dabd11b0 = { package = "linux-raw-sys", version = "0.9", default-features = false, features = ["elf", "errno", "general", "if_ether", "ioctl", "net", "netlink", "no_std", "prctl", "xdp"] }
linux-raw-sys-9fbad63c4bcf4a8f = { package = "linux-raw-sys", version = "0.4", default-features = false, features = ["elf", "errno", "general", "if_ether", "ioctl", "net", "netlink", "no_std", "prctl", "system", "xdp"] }
linux-raw-sys = { version = "0.4", default-features = false, features = ["elf", "errno", "general", "if_ether", "ioctl", "net", "netlink", "no_std", "prctl", "system", "xdp"] }
mio = { version = "1", features = ["net", "os-ext"] }
naga = { version = "23", features = ["spv-out", "wgsl-in"] }
nix = { version = "0.29", features = ["fs", "pthread", "signal", "socket", "uio", "user"] }
@@ -613,8 +607,8 @@ object = { version = "0.36", default-features = false, features = ["archive", "r
proc-macro2 = { version = "1", default-features = false, features = ["span-locations"] }
rand-274715c4dabd11b0 = { package = "rand", version = "0.9" }
ring = { version = "0.17", features = ["std"] }
rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38", features = ["event", "mm", "param", "pipe", "process", "pty", "shm", "stdio", "system", "termios", "time"] }
rustix-dff4ba8e3ae991db = { package = "rustix", version = "1", features = ["fs", "net", "process", "termios", "time"] }
rustix-d585fab2519d2d1 = { package = "rustix", version = "0.38", default-features = false, features = ["event", "mm", "param", "pipe", "process", "procfs", "pty", "shm", "stdio", "system", "termios", "time"] }
rustix-dff4ba8e3ae991db = { package = "rustix", version = "1", features = ["fs", "termios", "time"] }
scopeguard = { version = "1" }
smallvec = { version = "1", default-features = false, features = ["write"] }
tokio-rustls = { version = "0.26", default-features = false, features = ["ring"] }