Compare commits
1 Commits
assertion-
...
example2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4c4519c870 |
411
Cargo.lock
generated
411
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -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"
|
||||
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -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>) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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"] }
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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::*;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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(),
|
||||
),
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
alter table project_repositories
|
||||
add column head_commit_details varchar;
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
});
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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()),
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
251
crates/eval/src/example2.rs
Normal 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) {}
|
||||
}
|
||||
@@ -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(),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -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(())
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
43
crates/eval/src/templates.rs
Normal file
43
crates/eval/src/templates.rs
Normal 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";
|
||||
}
|
||||
@@ -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:
|
||||
@@ -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 {
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
})
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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> {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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))?
|
||||
},
|
||||
},
|
||||
))
|
||||
|
||||
@@ -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)?,
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -641,7 +641,6 @@ impl CompletionsQuery {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum SessionEvent {
|
||||
Modules,
|
||||
LoadedSources,
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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};
|
||||
|
||||
@@ -83,6 +83,7 @@ impl VsCodeDebugTaskDefinition {
|
||||
}
|
||||
}
|
||||
|
||||
/// blah
|
||||
#[derive(Clone, Debug, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct VsCodeDebugTaskFile {
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1500,7 +1500,7 @@ impl ShellExec {
|
||||
editor.highlight_rows::<ShellExec>(
|
||||
input_range.clone().unwrap(),
|
||||
cx.theme().status().unreachable_background,
|
||||
Default::default(),
|
||||
false,
|
||||
cx,
|
||||
);
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>> {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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>"]
|
||||
|
||||
@@ -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"] }
|
||||
|
||||
Reference in New Issue
Block a user