Compare commits
13 Commits
follow-age
...
eval-direc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9147f89257 | ||
|
|
9efc09c5a6 | ||
|
|
e6f6b351b7 | ||
|
|
fde621f0e3 | ||
|
|
c4556e9909 | ||
|
|
7e2de84155 | ||
|
|
d1b35be353 | ||
|
|
49a71ec3b8 | ||
|
|
3bd7ae6e5b | ||
|
|
225deb6785 | ||
|
|
33011f2eaf | ||
|
|
e14d078f8a | ||
|
|
460ac96df4 |
@@ -934,7 +934,7 @@
|
||||
// Shows all diagnostics when not specified.
|
||||
"max_severity": null
|
||||
},
|
||||
"rust": {
|
||||
"cargo": {
|
||||
// When enabled, Zed disables rust-analyzer's check on save and starts to query
|
||||
// Cargo diagnostics separately.
|
||||
"fetch_cargo_diagnostics": false
|
||||
|
||||
@@ -1211,7 +1211,7 @@ impl Component for MessageEditor {
|
||||
}
|
||||
|
||||
impl AgentPreview for MessageEditor {
|
||||
fn create_preview(
|
||||
fn agent_preview(
|
||||
workspace: WeakEntity<Workspace>,
|
||||
active_thread: Entity<ActiveThread>,
|
||||
thread_store: WeakEntity<ThreadStore>,
|
||||
|
||||
@@ -3,7 +3,7 @@ use component::ComponentId;
|
||||
use gpui::{App, Entity, WeakEntity};
|
||||
use linkme::distributed_slice;
|
||||
use std::sync::OnceLock;
|
||||
use ui::{AnyElement, Component, Window};
|
||||
use ui::{AnyElement, Component, ComponentScope, Window};
|
||||
use workspace::Workspace;
|
||||
|
||||
use crate::{ActiveThread, ThreadStore};
|
||||
@@ -22,27 +22,20 @@ pub type PreviewFn = fn(
|
||||
pub static __ALL_AGENT_PREVIEWS: [fn() -> (ComponentId, PreviewFn)] = [..];
|
||||
|
||||
/// Trait that must be implemented by components that provide agent previews.
|
||||
pub trait AgentPreview: Component {
|
||||
/// Get the ID for this component
|
||||
///
|
||||
/// Eventually this will move to the component trait.
|
||||
fn id() -> ComponentId
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
ComponentId(Self::name())
|
||||
pub trait AgentPreview: Component + Sized {
|
||||
#[allow(unused)] // We can't know this is used due to the distributed slice
|
||||
fn scope(&self) -> ComponentScope {
|
||||
ComponentScope::Agent
|
||||
}
|
||||
|
||||
/// Static method to create a preview for this component type
|
||||
fn create_preview(
|
||||
fn agent_preview(
|
||||
workspace: WeakEntity<Workspace>,
|
||||
active_thread: Entity<ActiveThread>,
|
||||
thread_store: WeakEntity<ThreadStore>,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Option<AnyElement>
|
||||
where
|
||||
Self: Sized;
|
||||
) -> Option<AnyElement>;
|
||||
}
|
||||
|
||||
/// Register an agent preview for the given component type
|
||||
@@ -55,8 +48,8 @@ macro_rules! register_agent_preview {
|
||||
$crate::ui::agent_preview::PreviewFn,
|
||||
) = || {
|
||||
(
|
||||
<$type as $crate::ui::agent_preview::AgentPreview>::id(),
|
||||
<$type as $crate::ui::agent_preview::AgentPreview>::create_preview,
|
||||
<$type as component::Component>::id(),
|
||||
<$type as $crate::ui::agent_preview::AgentPreview>::agent_preview,
|
||||
)
|
||||
};
|
||||
};
|
||||
|
||||
@@ -34,7 +34,7 @@ use assistant_settings::AssistantSettings;
|
||||
use assistant_tool::ToolRegistry;
|
||||
use copy_path_tool::CopyPathTool;
|
||||
use feature_flags::{AgentStreamEditsFeatureFlag, FeatureFlagAppExt};
|
||||
use gpui::App;
|
||||
use gpui::{App, Entity};
|
||||
use http_client::HttpClientWithUrl;
|
||||
use language_model::LanguageModelRegistry;
|
||||
use move_path_tool::MovePathTool;
|
||||
@@ -48,27 +48,25 @@ use crate::code_action_tool::CodeActionTool;
|
||||
use crate::code_symbols_tool::CodeSymbolsTool;
|
||||
use crate::contents_tool::ContentsTool;
|
||||
use crate::create_directory_tool::CreateDirectoryTool;
|
||||
use crate::create_file_tool::CreateFileTool;
|
||||
use crate::delete_path_tool::DeletePathTool;
|
||||
use crate::diagnostics_tool::DiagnosticsTool;
|
||||
use crate::edit_file_tool::EditFileTool;
|
||||
use crate::fetch_tool::FetchTool;
|
||||
use crate::find_path_tool::FindPathTool;
|
||||
use crate::grep_tool::GrepTool;
|
||||
use crate::list_directory_tool::ListDirectoryTool;
|
||||
use crate::now_tool::NowTool;
|
||||
use crate::open_tool::OpenTool;
|
||||
use crate::read_file_tool::ReadFileTool;
|
||||
use crate::rename_tool::RenameTool;
|
||||
use crate::streaming_edit_file_tool::StreamingEditFileTool;
|
||||
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 create_file_tool::{CreateFileTool, CreateFileToolInput};
|
||||
pub use edit_file_tool::{EditFileTool, EditFileToolInput};
|
||||
pub use find_path_tool::FindPathToolInput;
|
||||
pub use open_tool::OpenTool;
|
||||
pub use read_file_tool::ReadFileToolInput;
|
||||
pub use terminal_tool::TerminalTool;
|
||||
|
||||
pub fn init(http_client: Arc<HttpClientWithUrl>, cx: &mut App) {
|
||||
assistant_tool::init(cx);
|
||||
@@ -101,19 +99,12 @@ pub fn init(http_client: Arc<HttpClientWithUrl>, cx: &mut App) {
|
||||
cx.observe_global::<SettingsStore>(register_edit_file_tool)
|
||||
.detach();
|
||||
|
||||
register_web_search_tool(&LanguageModelRegistry::global(cx), cx);
|
||||
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);
|
||||
}
|
||||
register_web_search_tool(®istry, cx);
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
@@ -121,6 +112,18 @@ pub fn init(http_client: Arc<HttpClientWithUrl>, cx: &mut App) {
|
||||
.detach();
|
||||
}
|
||||
|
||||
fn register_web_search_tool(registry: &Entity<LanguageModelRegistry>, cx: &mut App) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
fn register_edit_file_tool(cx: &mut App) {
|
||||
let registry = ToolRegistry::global(cx);
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ use futures::{
|
||||
stream::BoxStream,
|
||||
};
|
||||
use gpui::{AppContext, AsyncApp, Entity, SharedString, Task};
|
||||
use language::{Anchor, Bias, Buffer, BufferSnapshot, LineIndent, Point};
|
||||
use language::{Bias, Buffer, BufferSnapshot, LineIndent, Point};
|
||||
use language_model::{
|
||||
LanguageModel, LanguageModelCompletionError, LanguageModelRequest, LanguageModelRequestMessage,
|
||||
MessageContent, Role,
|
||||
@@ -45,7 +45,7 @@ impl Template for EditFilePromptTemplate {
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum EditAgentOutputEvent {
|
||||
Edited { position: Anchor },
|
||||
Edited,
|
||||
OldTextNotFound(SharedString),
|
||||
}
|
||||
|
||||
@@ -139,9 +139,7 @@ impl EditAgent {
|
||||
.update(cx, |log, cx| log.buffer_edited(buffer.clone(), cx));
|
||||
})?;
|
||||
output_events_tx
|
||||
.unbounded_send(EditAgentOutputEvent::Edited {
|
||||
position: Anchor::MAX,
|
||||
})
|
||||
.unbounded_send(EditAgentOutputEvent::Edited)
|
||||
.ok();
|
||||
}
|
||||
|
||||
@@ -277,15 +275,14 @@ impl EditAgent {
|
||||
match op {
|
||||
CharOperation::Insert { text } => {
|
||||
let edit_start = snapshot.anchor_after(edit_start);
|
||||
edits_tx
|
||||
.unbounded_send((edit_start..edit_start, Arc::from(text)))?;
|
||||
edits_tx.unbounded_send((edit_start..edit_start, text))?;
|
||||
}
|
||||
CharOperation::Delete { bytes } => {
|
||||
let edit_end = edit_start + bytes;
|
||||
let edit_range = snapshot.anchor_after(edit_start)
|
||||
..snapshot.anchor_before(edit_end);
|
||||
edit_start = edit_end;
|
||||
edits_tx.unbounded_send((edit_range, Arc::from("")))?;
|
||||
edits_tx.unbounded_send((edit_range, String::new()))?;
|
||||
}
|
||||
CharOperation::Keep { bytes } => edit_start += bytes,
|
||||
}
|
||||
@@ -299,32 +296,16 @@ impl EditAgent {
|
||||
// TODO: group all edits into one transaction
|
||||
let mut edits_rx = edits_rx.ready_chunks(32);
|
||||
while let Some(edits) = edits_rx.next().await {
|
||||
if edits.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Edit the buffer and report edits to the action log as part of the
|
||||
// same effect cycle, otherwise the edit will be reported as if the
|
||||
// user made it.
|
||||
let max_edit_end = cx.update(|cx| {
|
||||
let max_edit_end = buffer.update(cx, |buffer, cx| {
|
||||
buffer.edit(edits.iter().cloned(), None, cx);
|
||||
let max_edit_end = buffer
|
||||
.summaries_for_anchors::<Point, _>(
|
||||
edits.iter().map(|(range, _)| &range.end),
|
||||
)
|
||||
.max()
|
||||
.unwrap();
|
||||
buffer.anchor_before(max_edit_end)
|
||||
});
|
||||
cx.update(|cx| {
|
||||
buffer.update(cx, |buffer, cx| buffer.edit(edits, None, cx));
|
||||
self.action_log
|
||||
.update(cx, |log, cx| log.buffer_edited(buffer.clone(), cx));
|
||||
max_edit_end
|
||||
.update(cx, |log, cx| log.buffer_edited(buffer.clone(), cx))
|
||||
})?;
|
||||
output_events
|
||||
.unbounded_send(EditAgentOutputEvent::Edited {
|
||||
position: max_edit_end,
|
||||
})
|
||||
.unbounded_send(EditAgentOutputEvent::Edited)
|
||||
.ok();
|
||||
}
|
||||
|
||||
@@ -822,12 +803,7 @@ mod tests {
|
||||
|
||||
chunks_tx.unbounded_send("<new_text>abX").unwrap();
|
||||
cx.run_until_parked();
|
||||
assert_eq!(
|
||||
drain_events(&mut events),
|
||||
[EditAgentOutputEvent::Edited {
|
||||
position: buffer.read_with(cx, |buffer, _| buffer.anchor_before(Point::new(0, 3)))
|
||||
}]
|
||||
);
|
||||
assert_eq!(drain_events(&mut events), [EditAgentOutputEvent::Edited]);
|
||||
assert_eq!(
|
||||
buffer.read_with(cx, |buffer, _| buffer.snapshot().text()),
|
||||
"abXc\ndef\nghi"
|
||||
@@ -835,12 +811,7 @@ mod tests {
|
||||
|
||||
chunks_tx.unbounded_send("cY").unwrap();
|
||||
cx.run_until_parked();
|
||||
assert_eq!(
|
||||
drain_events(&mut events),
|
||||
[EditAgentOutputEvent::Edited {
|
||||
position: buffer.read_with(cx, |buffer, _| buffer.anchor_before(Point::new(0, 5)))
|
||||
}]
|
||||
);
|
||||
assert_eq!(drain_events(&mut events), [EditAgentOutputEvent::Edited]);
|
||||
assert_eq!(
|
||||
buffer.read_with(cx, |buffer, _| buffer.snapshot().text()),
|
||||
"abXcY\ndef\nghi"
|
||||
@@ -892,9 +863,7 @@ mod tests {
|
||||
cx.run_until_parked();
|
||||
assert_eq!(
|
||||
drain_events(&mut events),
|
||||
vec![EditAgentOutputEvent::Edited {
|
||||
position: buffer.read_with(cx, |buffer, _| buffer.anchor_before(Point::new(2, 3)))
|
||||
}]
|
||||
vec![EditAgentOutputEvent::Edited]
|
||||
);
|
||||
assert_eq!(
|
||||
buffer.read_with(cx, |buffer, _| buffer.snapshot().text()),
|
||||
|
||||
@@ -7,7 +7,8 @@ use assistant_tool::{ActionLog, AnyToolCard, Tool, ToolCard, ToolResult, ToolUse
|
||||
use buffer_diff::{BufferDiff, BufferDiffSnapshot};
|
||||
use editor::{Editor, EditorMode, MultiBuffer, PathKey};
|
||||
use gpui::{
|
||||
AnyWindowHandle, App, AppContext, AsyncApp, Context, Entity, EntityId, Task, WeakEntity,
|
||||
Animation, AnimationExt, AnyWindowHandle, App, AppContext, AsyncApp, Context, Entity, EntityId,
|
||||
Task, WeakEntity, pulsating_between,
|
||||
};
|
||||
use language::{
|
||||
Anchor, Buffer, Capability, LanguageRegistry, LineEnding, OffsetRangeExt, Rope, TextBuffer,
|
||||
@@ -20,6 +21,7 @@ use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
time::Duration,
|
||||
};
|
||||
use ui::{Disclosure, Tooltip, Window, prelude::*};
|
||||
use util::ResultExt;
|
||||
@@ -323,6 +325,10 @@ impl EditFileToolCard {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn has_diff(&self) -> bool {
|
||||
self.total_lines.is_some()
|
||||
}
|
||||
|
||||
pub fn set_diff(
|
||||
&mut self,
|
||||
path: Arc<Path>,
|
||||
@@ -463,45 +469,44 @@ impl ToolCard for EditFileToolCard {
|
||||
.rounded_t_md()
|
||||
.when(!failed, |header| header.bg(codeblock_header_bg))
|
||||
.child(path_label_button)
|
||||
.map(|container| {
|
||||
if failed {
|
||||
container.child(
|
||||
h_flex()
|
||||
.gap_1()
|
||||
.child(
|
||||
Icon::new(IconName::Close)
|
||||
.size(IconSize::Small)
|
||||
.color(Color::Error),
|
||||
)
|
||||
.child(
|
||||
Disclosure::new(
|
||||
("edit-file-error-disclosure", self.editor_unique_id),
|
||||
self.error_expanded,
|
||||
)
|
||||
.opened_icon(IconName::ChevronUp)
|
||||
.closed_icon(IconName::ChevronDown)
|
||||
.on_click(cx.listener(
|
||||
move |this, _event, _window, _cx| {
|
||||
this.error_expanded = !this.error_expanded;
|
||||
},
|
||||
)),
|
||||
),
|
||||
)
|
||||
} else {
|
||||
container.child(
|
||||
Disclosure::new(
|
||||
("edit-file-disclosure", self.editor_unique_id),
|
||||
self.preview_expanded,
|
||||
.when(failed, |header| {
|
||||
header.child(
|
||||
h_flex()
|
||||
.gap_1()
|
||||
.child(
|
||||
Icon::new(IconName::Close)
|
||||
.size(IconSize::Small)
|
||||
.color(Color::Error),
|
||||
)
|
||||
.opened_icon(IconName::ChevronUp)
|
||||
.closed_icon(IconName::ChevronDown)
|
||||
.on_click(cx.listener(
|
||||
move |this, _event, _window, _cx| {
|
||||
this.preview_expanded = !this.preview_expanded;
|
||||
},
|
||||
)),
|
||||
.child(
|
||||
Disclosure::new(
|
||||
("edit-file-error-disclosure", self.editor_unique_id),
|
||||
self.error_expanded,
|
||||
)
|
||||
.opened_icon(IconName::ChevronUp)
|
||||
.closed_icon(IconName::ChevronDown)
|
||||
.on_click(cx.listener(
|
||||
move |this, _event, _window, _cx| {
|
||||
this.error_expanded = !this.error_expanded;
|
||||
},
|
||||
)),
|
||||
),
|
||||
)
|
||||
})
|
||||
.when(!failed && self.has_diff(), |header| {
|
||||
header.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, editor_line_height) = self.editor.update(cx, |editor, cx| {
|
||||
@@ -538,6 +543,50 @@ impl ToolCard for EditFileToolCard {
|
||||
const DEFAULT_COLLAPSED_LINES: u32 = 10;
|
||||
let is_collapsible = self.total_lines.unwrap_or(0) > DEFAULT_COLLAPSED_LINES;
|
||||
|
||||
let waiting_for_diff = {
|
||||
let styles = [
|
||||
("w_4_5", (0.1, 0.85), 2000),
|
||||
("w_1_4", (0.2, 0.75), 2200),
|
||||
("w_2_4", (0.15, 0.64), 1900),
|
||||
("w_3_5", (0.25, 0.72), 2300),
|
||||
("w_2_5", (0.3, 0.56), 1800),
|
||||
];
|
||||
|
||||
let mut container = v_flex()
|
||||
.p_3()
|
||||
.gap_1p5()
|
||||
.border_t_1()
|
||||
.border_color(border_color)
|
||||
.bg(cx.theme().colors().editor_background);
|
||||
|
||||
for (width_method, pulse_range, duration_ms) in styles.iter() {
|
||||
let (min_opacity, max_opacity) = *pulse_range;
|
||||
let placeholder = match *width_method {
|
||||
"w_4_5" => div().w_3_4(),
|
||||
"w_1_4" => div().w_1_4(),
|
||||
"w_2_4" => div().w_2_4(),
|
||||
"w_3_5" => div().w_3_5(),
|
||||
"w_2_5" => div().w_2_5(),
|
||||
_ => div().w_1_2(),
|
||||
}
|
||||
.id("loading_div")
|
||||
.h_2()
|
||||
.rounded_full()
|
||||
.bg(cx.theme().colors().element_active)
|
||||
.with_animation(
|
||||
"loading_pulsate",
|
||||
Animation::new(Duration::from_millis(*duration_ms))
|
||||
.repeat()
|
||||
.with_easing(pulsating_between(min_opacity, max_opacity)),
|
||||
|label, delta| label.opacity(delta),
|
||||
);
|
||||
|
||||
container = container.child(placeholder);
|
||||
}
|
||||
|
||||
container
|
||||
};
|
||||
|
||||
v_flex()
|
||||
.mb_2()
|
||||
.border_1()
|
||||
@@ -573,50 +622,58 @@ impl ToolCard for EditFileToolCard {
|
||||
),
|
||||
)
|
||||
})
|
||||
.when(!failed && self.preview_expanded, |card| {
|
||||
card.child(
|
||||
v_flex()
|
||||
.relative()
|
||||
.h_full()
|
||||
.when(!self.full_height_expanded, |editor_container| {
|
||||
editor_container
|
||||
.max_h(DEFAULT_COLLAPSED_LINES as f32 * editor_line_height)
|
||||
})
|
||||
.overflow_hidden()
|
||||
.border_t_1()
|
||||
.border_color(border_color)
|
||||
.bg(cx.theme().colors().editor_background)
|
||||
.child(div().pl_1().child(editor))
|
||||
.when(
|
||||
!self.full_height_expanded && is_collapsible,
|
||||
|editor_container| editor_container.child(gradient_overlay),
|
||||
),
|
||||
)
|
||||
.when(is_collapsible, |editor_container| {
|
||||
editor_container.child(
|
||||
h_flex()
|
||||
.id(("expand-button", self.editor_unique_id))
|
||||
.flex_none()
|
||||
.cursor_pointer()
|
||||
.h_5()
|
||||
.justify_center()
|
||||
.rounded_b_md()
|
||||
.when(!self.has_diff() && !failed, |card| {
|
||||
card.child(waiting_for_diff)
|
||||
})
|
||||
.when(
|
||||
!failed && self.preview_expanded && self.has_diff(),
|
||||
|card| {
|
||||
card.child(
|
||||
v_flex()
|
||||
.relative()
|
||||
.h_full()
|
||||
.when(!self.full_height_expanded, |editor_container| {
|
||||
editor_container
|
||||
.max_h(DEFAULT_COLLAPSED_LINES as f32 * editor_line_height)
|
||||
})
|
||||
.overflow_hidden()
|
||||
.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;
|
||||
})),
|
||||
.child(div().pl_1().child(editor))
|
||||
.when(
|
||||
!self.full_height_expanded && is_collapsible,
|
||||
|editor_container| editor_container.child(gradient_overlay),
|
||||
),
|
||||
)
|
||||
})
|
||||
})
|
||||
.when(is_collapsible, |editor_container| {
|
||||
editor_container.child(
|
||||
h_flex()
|
||||
.id(("expand-button", 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;
|
||||
})),
|
||||
)
|
||||
})
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,3 +4,6 @@ This tool opens a file or URL with the default application associated with it on
|
||||
- On Linux, it uses something like `xdg-open`, `gio open`, `gnome-open`, `kde-open`, `wslview` as appropriate
|
||||
|
||||
For example, it can open a web browser with a URL, open a PDF file with the default PDF viewer, etc.
|
||||
|
||||
You MUST ONLY use this tool when the user has explicitly requested opening something. You MUST NEVER assume that
|
||||
the user would like for you to use this tool.
|
||||
|
||||
@@ -205,7 +205,7 @@ impl Tool for StreamingEditFileTool {
|
||||
let mut hallucinated_old_text = false;
|
||||
while let Some(event) = events.next().await {
|
||||
match event {
|
||||
EditAgentOutputEvent::Edited { position } => {
|
||||
EditAgentOutputEvent::Edited => {
|
||||
if let Some(card) = card_clone.as_ref() {
|
||||
let new_snapshot =
|
||||
buffer.read_with(cx, |buffer, _cx| buffer.snapshot())?;
|
||||
|
||||
@@ -624,7 +624,7 @@ struct MigrateToNewBillingBody {
|
||||
#[derive(Debug, Serialize)]
|
||||
struct MigrateToNewBillingResponse {
|
||||
/// The ID of the subscription that was canceled.
|
||||
canceled_subscription_id: String,
|
||||
canceled_subscription_id: Option<String>,
|
||||
}
|
||||
|
||||
async fn migrate_to_new_billing(
|
||||
@@ -650,39 +650,39 @@ async fn migrate_to_new_billing(
|
||||
.get_active_billing_subscriptions(HashSet::from_iter([user.id]))
|
||||
.await?;
|
||||
|
||||
let Some((_billing_customer, billing_subscription)) =
|
||||
let canceled_subscription_id = if let Some((_billing_customer, billing_subscription)) =
|
||||
old_billing_subscriptions_by_user.get(&user.id)
|
||||
else {
|
||||
return Err(Error::http(
|
||||
StatusCode::NOT_FOUND,
|
||||
"No active billing subscriptions to migrate".into(),
|
||||
));
|
||||
{
|
||||
let stripe_subscription_id = billing_subscription
|
||||
.stripe_subscription_id
|
||||
.parse::<stripe::SubscriptionId>()
|
||||
.context("failed to parse Stripe subscription ID from database")?;
|
||||
|
||||
Subscription::cancel(
|
||||
&stripe_client,
|
||||
&stripe_subscription_id,
|
||||
stripe::CancelSubscription {
|
||||
invoice_now: Some(true),
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
|
||||
Some(stripe_subscription_id)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let stripe_subscription_id = billing_subscription
|
||||
.stripe_subscription_id
|
||||
.parse::<stripe::SubscriptionId>()
|
||||
.context("failed to parse Stripe subscription ID from database")?;
|
||||
|
||||
Subscription::cancel(
|
||||
&stripe_client,
|
||||
&stripe_subscription_id,
|
||||
stripe::CancelSubscription {
|
||||
invoice_now: Some(true),
|
||||
..Default::default()
|
||||
},
|
||||
)
|
||||
.await?;
|
||||
|
||||
let feature_flags = app.db.list_feature_flags().await?;
|
||||
let all_feature_flags = app.db.list_feature_flags().await?;
|
||||
let user_feature_flags = app.db.get_user_flags(user.id).await?;
|
||||
|
||||
for feature_flag in ["new-billing", "assistant2"] {
|
||||
let already_in_feature_flag = feature_flags.iter().any(|flag| flag.flag == feature_flag);
|
||||
let already_in_feature_flag = user_feature_flags.iter().any(|flag| flag == feature_flag);
|
||||
if already_in_feature_flag {
|
||||
continue;
|
||||
}
|
||||
|
||||
let feature_flag = feature_flags
|
||||
let feature_flag = all_feature_flags
|
||||
.iter()
|
||||
.find(|flag| flag.flag == feature_flag)
|
||||
.context("failed to find feature flag: {feature_flag:?}")?;
|
||||
@@ -691,7 +691,8 @@ async fn migrate_to_new_billing(
|
||||
}
|
||||
|
||||
Ok(Json(MigrateToNewBillingResponse {
|
||||
canceled_subscription_id: stripe_subscription_id.to_string(),
|
||||
canceled_subscription_id: canceled_subscription_id
|
||||
.map(|subscription_id| subscription_id.to_string()),
|
||||
}))
|
||||
}
|
||||
|
||||
|
||||
@@ -18,6 +18,9 @@ pub trait Component {
|
||||
fn name() -> &'static str {
|
||||
std::any::type_name::<Self>()
|
||||
}
|
||||
fn id() -> ComponentId {
|
||||
ComponentId(Self::name())
|
||||
}
|
||||
/// Returns a name that the component should be sorted by.
|
||||
///
|
||||
/// Implement this if the component should be sorted in an alternate order than its name.
|
||||
@@ -81,7 +84,7 @@ pub fn register_component<T: Component>() {
|
||||
let component_data = (T::scope(), T::name(), T::sort_name(), T::description());
|
||||
let mut data = COMPONENT_DATA.write();
|
||||
data.components.push(component_data);
|
||||
data.previews.insert(T::name(), T::preview);
|
||||
data.previews.insert(T::id().0, T::preview);
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
|
||||
@@ -110,18 +110,7 @@ struct ComponentPreview {
|
||||
active_page: PreviewPage,
|
||||
components: Vec<ComponentMetadata>,
|
||||
component_list: ListState,
|
||||
agent_previews: Vec<
|
||||
Box<
|
||||
dyn Fn(
|
||||
&Self,
|
||||
WeakEntity<Workspace>,
|
||||
Entity<ActiveThread>,
|
||||
WeakEntity<ThreadStore>,
|
||||
&mut Window,
|
||||
&mut App,
|
||||
) -> Option<AnyElement>,
|
||||
>,
|
||||
>,
|
||||
agent_previews: Vec<ComponentId>,
|
||||
cursor_index: usize,
|
||||
language_registry: Arc<LanguageRegistry>,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
@@ -191,38 +180,7 @@ impl ComponentPreview {
|
||||
);
|
||||
|
||||
// Initialize agent previews
|
||||
let agent_previews = agent::all_agent_previews()
|
||||
.into_iter()
|
||||
.map(|id| {
|
||||
Box::new(
|
||||
move |_self: &ComponentPreview,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
active_thread: Entity<ActiveThread>,
|
||||
thread_store: WeakEntity<ThreadStore>,
|
||||
window: &mut Window,
|
||||
cx: &mut App| {
|
||||
agent::get_agent_preview(
|
||||
&id,
|
||||
workspace,
|
||||
active_thread,
|
||||
thread_store,
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
},
|
||||
)
|
||||
as Box<
|
||||
dyn Fn(
|
||||
&ComponentPreview,
|
||||
WeakEntity<Workspace>,
|
||||
Entity<ActiveThread>,
|
||||
WeakEntity<ThreadStore>,
|
||||
&mut Window,
|
||||
&mut App,
|
||||
) -> Option<AnyElement>,
|
||||
>
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let agent_previews = agent::all_agent_previews();
|
||||
|
||||
let mut component_preview = Self {
|
||||
workspace_id: None,
|
||||
@@ -635,44 +593,65 @@ impl ComponentPreview {
|
||||
|
||||
let description = component.description();
|
||||
|
||||
v_flex()
|
||||
.py_2()
|
||||
.child(
|
||||
v_flex()
|
||||
.border_1()
|
||||
.border_color(cx.theme().colors().border)
|
||||
.rounded_sm()
|
||||
.w_full()
|
||||
.gap_4()
|
||||
.py_4()
|
||||
.px_6()
|
||||
.flex_none()
|
||||
.child(
|
||||
v_flex()
|
||||
.gap_1()
|
||||
.child(
|
||||
h_flex().gap_1().text_xl().child(div().child(name)).when(
|
||||
!matches!(scope, ComponentScope::None),
|
||||
|this| {
|
||||
this.child(div().opacity(0.5).child(format!("({})", scope)))
|
||||
},
|
||||
),
|
||||
// Build the content container
|
||||
let mut preview_container = v_flex().py_2().child(
|
||||
v_flex()
|
||||
.border_1()
|
||||
.border_color(cx.theme().colors().border)
|
||||
.rounded_sm()
|
||||
.w_full()
|
||||
.gap_4()
|
||||
.py_4()
|
||||
.px_6()
|
||||
.flex_none()
|
||||
.child(
|
||||
v_flex()
|
||||
.gap_1()
|
||||
.child(
|
||||
h_flex()
|
||||
.gap_1()
|
||||
.text_xl()
|
||||
.child(div().child(name))
|
||||
.when(!matches!(scope, ComponentScope::None), |this| {
|
||||
this.child(div().opacity(0.5).child(format!("({})", scope)))
|
||||
}),
|
||||
)
|
||||
.when_some(description, |this, description| {
|
||||
this.child(
|
||||
div()
|
||||
.text_ui_sm(cx)
|
||||
.text_color(cx.theme().colors().text_muted)
|
||||
.max_w(px(600.0))
|
||||
.child(description),
|
||||
)
|
||||
.when_some(description, |this, description| {
|
||||
this.child(
|
||||
div()
|
||||
.text_ui_sm(cx)
|
||||
.text_color(cx.theme().colors().text_muted)
|
||||
.max_w(px(600.0))
|
||||
.child(description),
|
||||
)
|
||||
}),
|
||||
)
|
||||
.when_some(component.preview(), |this, preview| {
|
||||
this.children(preview(window, cx))
|
||||
}),
|
||||
)
|
||||
.into_any_element()
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
// Check if the component's scope is Agent
|
||||
if scope == ComponentScope::Agent {
|
||||
if let (Some(thread_store), Some(active_thread)) = (
|
||||
self.thread_store.as_ref().map(|ts| ts.downgrade()),
|
||||
self.active_thread.clone(),
|
||||
) {
|
||||
if let Some(element) = agent::get_agent_preview(
|
||||
&component.id(),
|
||||
self.workspace.clone(),
|
||||
active_thread,
|
||||
thread_store,
|
||||
window,
|
||||
cx,
|
||||
) {
|
||||
preview_container = preview_container.child(element);
|
||||
} else if let Some(preview) = component.preview() {
|
||||
preview_container = preview_container.children(preview(window, cx));
|
||||
}
|
||||
}
|
||||
} else if let Some(preview) = component.preview() {
|
||||
preview_container = preview_container.children(preview(window, cx));
|
||||
}
|
||||
|
||||
preview_container.into_any_element()
|
||||
}
|
||||
|
||||
fn render_all_components(&self, cx: &Context<Self>) -> impl IntoElement {
|
||||
@@ -711,7 +690,12 @@ impl ComponentPreview {
|
||||
v_flex()
|
||||
.id("render-component-page")
|
||||
.size_full()
|
||||
.child(ComponentPreviewPage::new(component.clone()))
|
||||
.child(ComponentPreviewPage::new(
|
||||
component.clone(),
|
||||
self.workspace.clone(),
|
||||
self.thread_store.as_ref().map(|ts| ts.downgrade()),
|
||||
self.active_thread.clone(),
|
||||
))
|
||||
.into_any_element()
|
||||
} else {
|
||||
v_flex()
|
||||
@@ -732,13 +716,13 @@ impl ComponentPreview {
|
||||
.id("render-active-thread")
|
||||
.size_full()
|
||||
.child(
|
||||
v_flex().children(self.agent_previews.iter().filter_map(|preview_fn| {
|
||||
v_flex().children(self.agent_previews.iter().filter_map(|component_id| {
|
||||
if let (Some(thread_store), Some(active_thread)) = (
|
||||
self.thread_store.as_ref().map(|ts| ts.downgrade()),
|
||||
self.active_thread.clone(),
|
||||
) {
|
||||
preview_fn(
|
||||
self,
|
||||
agent::get_agent_preview(
|
||||
component_id,
|
||||
self.workspace.clone(),
|
||||
active_thread,
|
||||
thread_store,
|
||||
@@ -894,7 +878,7 @@ impl Default for ActivePageId {
|
||||
|
||||
impl From<ComponentId> for ActivePageId {
|
||||
fn from(id: ComponentId) -> Self {
|
||||
ActivePageId(id.0.to_string())
|
||||
Self(id.0.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1073,16 +1057,25 @@ impl SerializableItem for ComponentPreview {
|
||||
pub struct ComponentPreviewPage {
|
||||
// languages: Arc<LanguageRegistry>,
|
||||
component: ComponentMetadata,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
thread_store: Option<WeakEntity<ThreadStore>>,
|
||||
active_thread: Option<Entity<ActiveThread>>,
|
||||
}
|
||||
|
||||
impl ComponentPreviewPage {
|
||||
pub fn new(
|
||||
component: ComponentMetadata,
|
||||
workspace: WeakEntity<Workspace>,
|
||||
thread_store: Option<WeakEntity<ThreadStore>>,
|
||||
active_thread: Option<Entity<ActiveThread>>,
|
||||
// languages: Arc<LanguageRegistry>
|
||||
) -> Self {
|
||||
Self {
|
||||
// languages,
|
||||
component,
|
||||
workspace,
|
||||
thread_store,
|
||||
active_thread,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1113,12 +1106,32 @@ impl ComponentPreviewPage {
|
||||
}
|
||||
|
||||
fn render_preview(&self, window: &mut Window, cx: &mut App) -> impl IntoElement {
|
||||
// Try to get agent preview first if we have an active thread
|
||||
let maybe_agent_preview = if let (Some(thread_store), Some(active_thread)) =
|
||||
(self.thread_store.as_ref(), self.active_thread.as_ref())
|
||||
{
|
||||
agent::get_agent_preview(
|
||||
&self.component.id(),
|
||||
self.workspace.clone(),
|
||||
active_thread.clone(),
|
||||
thread_store.clone(),
|
||||
window,
|
||||
cx,
|
||||
)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
v_flex()
|
||||
.flex_1()
|
||||
.px_12()
|
||||
.py_6()
|
||||
.bg(cx.theme().colors().editor_background)
|
||||
.child(if let Some(preview) = self.component.preview() {
|
||||
.child(if let Some(element) = maybe_agent_preview {
|
||||
// Use agent preview if available
|
||||
element
|
||||
} else if let Some(preview) = self.component.preview() {
|
||||
// Fall back to component preview
|
||||
preview(window, cx).unwrap_or_else(|| {
|
||||
div()
|
||||
.child("Failed to load preview. This path should be unreachable")
|
||||
|
||||
@@ -6,7 +6,7 @@ use crate::{
|
||||
persistence,
|
||||
};
|
||||
use crate::{new_session_modal::NewSessionModal, session::DebugSession};
|
||||
use anyhow::{Result, anyhow};
|
||||
use anyhow::{Context as _, Result, anyhow};
|
||||
use collections::{HashMap, HashSet};
|
||||
use command_palette_hooks::CommandPaletteFilter;
|
||||
use dap::DebugRequest;
|
||||
@@ -500,7 +500,7 @@ impl DebugPanel {
|
||||
workspace.spawn_in_terminal(task.resolved.clone(), window, cx)
|
||||
})?;
|
||||
|
||||
let exit_status = run_build.await?;
|
||||
let exit_status = run_build.await.transpose()?.context("task cancelled")?;
|
||||
if !exit_status.success() {
|
||||
anyhow::bail!("Build failed");
|
||||
}
|
||||
|
||||
@@ -226,7 +226,7 @@ impl ProjectDiagnosticsEditor {
|
||||
cx.observe_global_in::<IncludeWarnings>(window, |this, window, cx| {
|
||||
this.include_warnings = cx.global::<IncludeWarnings>().0;
|
||||
this.diagnostics.clear();
|
||||
this.update_all_diagnostics(window, cx);
|
||||
this.update_all_diagnostics(false, window, cx);
|
||||
})
|
||||
.detach();
|
||||
cx.observe_release(&cx.entity(), |editor, _, cx| {
|
||||
@@ -254,7 +254,7 @@ impl ProjectDiagnosticsEditor {
|
||||
},
|
||||
_subscription: project_event_subscription,
|
||||
};
|
||||
this.update_all_diagnostics(window, cx);
|
||||
this.update_all_diagnostics(true, window, cx);
|
||||
this
|
||||
}
|
||||
|
||||
@@ -346,13 +346,13 @@ impl ProjectDiagnosticsEditor {
|
||||
if self.cargo_diagnostics_fetch.fetch_task.is_some() {
|
||||
self.stop_cargo_diagnostics_fetch(cx);
|
||||
} else {
|
||||
self.update_all_diagnostics(window, cx);
|
||||
self.update_all_diagnostics(false, window, cx);
|
||||
}
|
||||
} else {
|
||||
if self.update_excerpts_task.is_some() {
|
||||
self.update_excerpts_task = None;
|
||||
} else {
|
||||
self.update_all_diagnostics(window, cx);
|
||||
self.update_all_diagnostics(false, window, cx);
|
||||
}
|
||||
}
|
||||
cx.notify();
|
||||
@@ -371,10 +371,17 @@ impl ProjectDiagnosticsEditor {
|
||||
}
|
||||
}
|
||||
|
||||
fn update_all_diagnostics(&mut self, window: &mut Window, cx: &mut Context<Self>) {
|
||||
fn update_all_diagnostics(
|
||||
&mut self,
|
||||
first_launch: bool,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) {
|
||||
let cargo_diagnostics_sources = self.cargo_diagnostics_sources(cx);
|
||||
if cargo_diagnostics_sources.is_empty() {
|
||||
self.update_all_excerpts(window, cx);
|
||||
} else if first_launch && !self.summary.is_empty() {
|
||||
self.update_all_excerpts(window, cx);
|
||||
} else {
|
||||
self.fetch_cargo_diagnostics(Arc::new(cargo_diagnostics_sources), cx);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ use std::sync::Arc;
|
||||
use crate::Editor;
|
||||
use collections::HashMap;
|
||||
use futures::stream::FuturesUnordered;
|
||||
use gpui::AsyncApp;
|
||||
use gpui::{App, AppContext as _, Entity, Task};
|
||||
use itertools::Itertools;
|
||||
use language::Buffer;
|
||||
@@ -74,6 +75,39 @@ where
|
||||
})
|
||||
}
|
||||
|
||||
async fn lsp_task_context(
|
||||
project: &Entity<Project>,
|
||||
buffer: &Entity<Buffer>,
|
||||
cx: &mut AsyncApp,
|
||||
) -> Option<TaskContext> {
|
||||
let worktree_store = project
|
||||
.update(cx, |project, _| project.worktree_store())
|
||||
.ok()?;
|
||||
|
||||
let worktree_abs_path = cx
|
||||
.update(|cx| {
|
||||
let worktree_id = buffer.read(cx).file().map(|f| f.worktree_id(cx));
|
||||
|
||||
worktree_id
|
||||
.and_then(|worktree_id| worktree_store.read(cx).worktree_for_id(worktree_id, cx))
|
||||
.and_then(|worktree| worktree.read(cx).root_dir())
|
||||
})
|
||||
.ok()?;
|
||||
|
||||
let project_env = project
|
||||
.update(cx, |project, cx| {
|
||||
project.buffer_environment(&buffer, &worktree_store, cx)
|
||||
})
|
||||
.ok()?
|
||||
.await;
|
||||
|
||||
Some(TaskContext {
|
||||
cwd: worktree_abs_path.map(|p| p.to_path_buf()),
|
||||
project_env: project_env.unwrap_or_default(),
|
||||
..TaskContext::default()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn lsp_tasks(
|
||||
project: Entity<Project>,
|
||||
task_sources: &HashMap<LanguageServerName, Vec<BufferId>>,
|
||||
@@ -97,13 +131,16 @@ pub fn lsp_tasks(
|
||||
|
||||
cx.spawn(async move |cx| {
|
||||
let mut lsp_tasks = Vec::new();
|
||||
let lsp_task_context = TaskContext::default();
|
||||
while let Some(server_to_query) = lsp_task_sources.next().await {
|
||||
if let Some((server_id, buffers)) = server_to_query {
|
||||
let source_kind = TaskSourceKind::Lsp(server_id);
|
||||
let id_base = source_kind.to_id_base();
|
||||
let mut new_lsp_tasks = Vec::new();
|
||||
for buffer in buffers {
|
||||
let lsp_buffer_context = lsp_task_context(&project, &buffer, cx)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
|
||||
if let Ok(runnables_task) = project.update(cx, |project, cx| {
|
||||
let buffer_id = buffer.read(cx).remote_id();
|
||||
project.request_lsp(
|
||||
@@ -120,7 +157,7 @@ pub fn lsp_tasks(
|
||||
new_lsp_tasks.extend(new_runnables.runnables.into_iter().filter_map(
|
||||
|(location, runnable)| {
|
||||
let resolved_task =
|
||||
runnable.resolve_task(&id_base, &lsp_task_context)?;
|
||||
runnable.resolve_task(&id_base, &lsp_buffer_context)?;
|
||||
Some((location, resolved_task))
|
||||
},
|
||||
));
|
||||
|
||||
@@ -169,11 +169,14 @@ fn main() {
|
||||
continue;
|
||||
}
|
||||
|
||||
if meta.language_server.map_or(false, |language| {
|
||||
!languages.contains(&language.file_extension)
|
||||
}) {
|
||||
skipped.push(meta.name);
|
||||
continue;
|
||||
if let Some(language) = meta.language_server {
|
||||
if !languages.contains(&language.file_extension) {
|
||||
panic!(
|
||||
"Eval for {:?} could not be run because no language server was found for extension {:?}",
|
||||
meta.name,
|
||||
language.file_extension
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: This creates a worktree per repetition. Ideally these examples should
|
||||
|
||||
@@ -14,12 +14,14 @@ use crate::example::{Example, ExampleContext, ExampleMetadata, JudgeAssertion};
|
||||
mod add_arg_to_trait_method;
|
||||
mod code_block_citations;
|
||||
mod file_search;
|
||||
mod planets;
|
||||
|
||||
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),
|
||||
Rc::new(code_block_citations::CodeBlockCitations),
|
||||
Rc::new(planets::Planets),
|
||||
];
|
||||
|
||||
for example_path in list_declarative_examples(examples_dir).unwrap() {
|
||||
|
||||
73
crates/eval/src/examples/planets.rs
Normal file
73
crates/eval/src/examples/planets.rs
Normal file
@@ -0,0 +1,73 @@
|
||||
use anyhow::Result;
|
||||
use assistant_tool::Tool;
|
||||
use assistant_tools::{OpenTool, TerminalTool};
|
||||
use async_trait::async_trait;
|
||||
|
||||
use crate::example::{Example, ExampleContext, ExampleMetadata, JudgeAssertion};
|
||||
|
||||
pub struct Planets;
|
||||
|
||||
#[async_trait(?Send)]
|
||||
impl Example for Planets {
|
||||
fn meta(&self) -> ExampleMetadata {
|
||||
ExampleMetadata {
|
||||
name: "planets".to_string(),
|
||||
url: "https://github.com/roc-lang/roc".to_string(), // This commit in this repo is just the Apache2 license,
|
||||
revision: "59e49c75214f60b4dc4a45092292061c8c26ce27".to_string(), // so effectively a blank project.
|
||||
language_server: None,
|
||||
max_assertions: None,
|
||||
}
|
||||
}
|
||||
|
||||
async fn conversation(&self, cx: &mut ExampleContext) -> Result<()> {
|
||||
cx.push_user_message(
|
||||
r#"
|
||||
Make a plain JavaScript web page which renders an animated 3D solar system.
|
||||
Let me drag to rotate the camera around.
|
||||
Do not use npm.
|
||||
"#
|
||||
.to_string(),
|
||||
);
|
||||
|
||||
let response = cx.run_to_end().await?;
|
||||
let mut open_tool_uses = 0;
|
||||
let mut terminal_tool_uses = 0;
|
||||
|
||||
for tool_use in response.tool_uses() {
|
||||
if tool_use.name == OpenTool.name() {
|
||||
open_tool_uses += 1;
|
||||
} else if tool_use.name == TerminalTool.name() {
|
||||
terminal_tool_uses += 1;
|
||||
}
|
||||
}
|
||||
|
||||
// The open tool should only be used when requested, which it was not.
|
||||
cx.assert_eq(open_tool_uses, 0, "`open` tool was not used")
|
||||
.ok();
|
||||
// No reason to use the terminal if not using npm.
|
||||
cx.assert_eq(terminal_tool_uses, 0, "`terminal` tool was not used")
|
||||
.ok();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn diff_assertions(&self) -> Vec<JudgeAssertion> {
|
||||
vec![
|
||||
JudgeAssertion {
|
||||
id: "animated solar system".to_string(),
|
||||
description: "This page should render a solar system, and it should be animated."
|
||||
.to_string(),
|
||||
},
|
||||
JudgeAssertion {
|
||||
id: "drag to rotate camera".to_string(),
|
||||
description: "The user can drag to rotate the camera around.".to_string(),
|
||||
},
|
||||
JudgeAssertion {
|
||||
id: "plain JavaScript".to_string(),
|
||||
description:
|
||||
"The code base uses plain JavaScript and no npm, along with HTML and CSS."
|
||||
.to_string(),
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
/// Configuration for a context server.
|
||||
/// Configuration for context server setup and installation.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ContextServerConfiguration {
|
||||
/// Installation instructions for the user.
|
||||
/// Installation instructions in Markdown format.
|
||||
pub installation_instructions: String,
|
||||
/// Default settings for the context server.
|
||||
pub default_settings: String,
|
||||
/// JSON schema describing server settings.
|
||||
/// JSON schema for settings validation.
|
||||
pub settings_schema: serde_json::Value,
|
||||
/// Default settings template.
|
||||
pub default_settings: String,
|
||||
}
|
||||
|
||||
@@ -6,8 +6,7 @@ repository = "https://github.com/zed-industries/zed"
|
||||
documentation = "https://docs.rs/zed_extension_api"
|
||||
keywords = ["zed", "extension"]
|
||||
edition.workspace = true
|
||||
# Change back to `true` when we're ready to publish v0.5.0.
|
||||
publish = false
|
||||
publish = true
|
||||
license = "Apache-2.0"
|
||||
|
||||
[lints]
|
||||
|
||||
@@ -23,7 +23,7 @@ need to set your `crate-type` accordingly:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
zed_extension_api = "0.4.0"
|
||||
zed_extension_api = "0.5.0"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib"]
|
||||
@@ -63,6 +63,7 @@ Here is the compatibility of the `zed_extension_api` with versions of Zed:
|
||||
|
||||
| Zed version | `zed_extension_api` version |
|
||||
| ----------- | --------------------------- |
|
||||
| `0.186.x` | `0.0.1` - `0.5.0` |
|
||||
| `0.184.x` | `0.0.1` - `0.4.0` |
|
||||
| `0.178.x` | `0.0.1` - `0.3.0` |
|
||||
| `0.162.x` | `0.0.1` - `0.2.0` |
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
interface context-server {
|
||||
///
|
||||
/// Configuration for context server setup and installation.
|
||||
record context-server-configuration {
|
||||
///
|
||||
/// Installation instructions in Markdown format.
|
||||
installation-instructions: string,
|
||||
///
|
||||
/// JSON schema for settings validation.
|
||||
settings-schema: string,
|
||||
///
|
||||
/// Default settings template.
|
||||
default-settings: string,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ pub fn wasm_api_version_range(release_channel: ReleaseChannel) -> RangeInclusive
|
||||
|
||||
let max_version = match release_channel {
|
||||
ReleaseChannel::Dev | ReleaseChannel::Nightly => latest::MAX_VERSION,
|
||||
ReleaseChannel::Stable | ReleaseChannel::Preview => since_v0_4_0::MAX_VERSION,
|
||||
ReleaseChannel::Stable | ReleaseChannel::Preview => latest::MAX_VERSION,
|
||||
};
|
||||
|
||||
since_v0_0_1::MIN_VERSION..=max_version
|
||||
@@ -113,8 +113,6 @@ impl Extension {
|
||||
let _ = release_channel;
|
||||
|
||||
if version >= latest::MIN_VERSION {
|
||||
authorize_access_to_unreleased_wasm_api_version(release_channel)?;
|
||||
|
||||
let extension =
|
||||
latest::Extension::instantiate_async(store, component, latest::linker())
|
||||
.await
|
||||
|
||||
@@ -8,7 +8,6 @@ use wasmtime::component::{Linker, Resource};
|
||||
use super::latest;
|
||||
|
||||
pub const MIN_VERSION: SemanticVersion = SemanticVersion::new(0, 4, 0);
|
||||
pub const MAX_VERSION: SemanticVersion = SemanticVersion::new(0, 4, 0);
|
||||
|
||||
wasmtime::component::bindgen!({
|
||||
async: true,
|
||||
|
||||
@@ -242,6 +242,38 @@ async fn test_matching_paths(cx: &mut TestAppContext) {
|
||||
}
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_unicode_paths(cx: &mut TestAppContext) {
|
||||
let app_state = init_test(cx);
|
||||
app_state
|
||||
.fs
|
||||
.as_fake()
|
||||
.insert_tree(
|
||||
path!("/root"),
|
||||
json!({
|
||||
"a": {
|
||||
"İg": " ",
|
||||
}
|
||||
}),
|
||||
)
|
||||
.await;
|
||||
|
||||
let project = Project::test(app_state.fs.clone(), [path!("/root").as_ref()], cx).await;
|
||||
|
||||
let (picker, workspace, cx) = build_find_picker(project, cx);
|
||||
|
||||
cx.simulate_input("g");
|
||||
picker.update(cx, |picker, _| {
|
||||
assert_eq!(picker.delegate.matches.len(), 1);
|
||||
});
|
||||
cx.dispatch_action(SelectNext);
|
||||
cx.dispatch_action(Confirm);
|
||||
cx.read(|cx| {
|
||||
let active_editor = workspace.read(cx).active_item_as::<Editor>(cx).unwrap();
|
||||
assert_eq!(active_editor.read(cx).title(cx), "İg");
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_absolute_paths(cx: &mut TestAppContext) {
|
||||
let app_state = init_test(cx);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
use std::{
|
||||
borrow::{Borrow, Cow},
|
||||
collections::BTreeMap,
|
||||
sync::atomic::{self, AtomicBool},
|
||||
};
|
||||
|
||||
@@ -50,7 +51,7 @@ impl<'a> Matcher<'a> {
|
||||
|
||||
/// Filter and score fuzzy match candidates. Results are returned unsorted, in the same order as
|
||||
/// the input candidates.
|
||||
pub fn match_candidates<C, R, F, T>(
|
||||
pub(crate) fn match_candidates<C, R, F, T>(
|
||||
&mut self,
|
||||
prefix: &[char],
|
||||
lowercase_prefix: &[char],
|
||||
@@ -65,6 +66,7 @@ impl<'a> Matcher<'a> {
|
||||
{
|
||||
let mut candidate_chars = Vec::new();
|
||||
let mut lowercase_candidate_chars = Vec::new();
|
||||
let mut extra_lowercase_chars = BTreeMap::new();
|
||||
|
||||
for candidate in candidates {
|
||||
if !candidate.borrow().has_chars(self.query_char_bag) {
|
||||
@@ -77,9 +79,14 @@ impl<'a> Matcher<'a> {
|
||||
|
||||
candidate_chars.clear();
|
||||
lowercase_candidate_chars.clear();
|
||||
for c in candidate.borrow().to_string().chars() {
|
||||
extra_lowercase_chars.clear();
|
||||
for (i, c) in candidate.borrow().to_string().chars().enumerate() {
|
||||
candidate_chars.push(c);
|
||||
lowercase_candidate_chars.append(&mut c.to_lowercase().collect::<Vec<_>>());
|
||||
let mut char_lowercased = c.to_lowercase().collect::<Vec<_>>();
|
||||
if char_lowercased.len() > 1 {
|
||||
extra_lowercase_chars.insert(i, char_lowercased.len() - 1);
|
||||
}
|
||||
lowercase_candidate_chars.append(&mut char_lowercased);
|
||||
}
|
||||
|
||||
if !self.find_last_positions(lowercase_prefix, &lowercase_candidate_chars) {
|
||||
@@ -97,6 +104,7 @@ impl<'a> Matcher<'a> {
|
||||
&lowercase_candidate_chars,
|
||||
prefix,
|
||||
lowercase_prefix,
|
||||
&extra_lowercase_chars,
|
||||
);
|
||||
|
||||
if score > 0.0 {
|
||||
@@ -131,18 +139,20 @@ impl<'a> Matcher<'a> {
|
||||
fn score_match(
|
||||
&mut self,
|
||||
path: &[char],
|
||||
path_cased: &[char],
|
||||
path_lowercased: &[char],
|
||||
prefix: &[char],
|
||||
lowercase_prefix: &[char],
|
||||
extra_lowercase_chars: &BTreeMap<usize, usize>,
|
||||
) -> f64 {
|
||||
let score = self.recursive_score_match(
|
||||
path,
|
||||
path_cased,
|
||||
path_lowercased,
|
||||
prefix,
|
||||
lowercase_prefix,
|
||||
0,
|
||||
0,
|
||||
self.query.len() as f64,
|
||||
extra_lowercase_chars,
|
||||
) * self.query.len() as f64;
|
||||
|
||||
if score <= 0.0 {
|
||||
@@ -173,12 +183,13 @@ impl<'a> Matcher<'a> {
|
||||
fn recursive_score_match(
|
||||
&mut self,
|
||||
path: &[char],
|
||||
path_cased: &[char],
|
||||
path_lowercased: &[char],
|
||||
prefix: &[char],
|
||||
lowercase_prefix: &[char],
|
||||
query_idx: usize,
|
||||
path_idx: usize,
|
||||
cur_score: f64,
|
||||
extra_lowercase_chars: &BTreeMap<usize, usize>,
|
||||
) -> f64 {
|
||||
use std::path::MAIN_SEPARATOR;
|
||||
|
||||
@@ -200,15 +211,22 @@ impl<'a> Matcher<'a> {
|
||||
|
||||
let mut last_slash = 0;
|
||||
for j in path_idx..=limit {
|
||||
let path_char = if j < prefix.len() {
|
||||
let extra_lowercase_chars_count = extra_lowercase_chars
|
||||
.iter()
|
||||
.take_while(|(i, _)| i < &&j)
|
||||
.map(|(_, increment)| increment)
|
||||
.sum::<usize>();
|
||||
let j_regular = j - extra_lowercase_chars_count;
|
||||
|
||||
let path_char = if j_regular < prefix.len() {
|
||||
lowercase_prefix[j]
|
||||
} else {
|
||||
path_cased[j - prefix.len()]
|
||||
path_lowercased[j - prefix.len()]
|
||||
};
|
||||
let is_path_sep = path_char == MAIN_SEPARATOR;
|
||||
|
||||
if query_idx == 0 && is_path_sep {
|
||||
last_slash = j;
|
||||
last_slash = j_regular;
|
||||
}
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
@@ -218,18 +236,18 @@ impl<'a> Matcher<'a> {
|
||||
#[cfg(target_os = "windows")]
|
||||
let need_to_score = query_char == path_char || (is_path_sep && query_char == '_');
|
||||
if need_to_score {
|
||||
let curr = if j < prefix.len() {
|
||||
prefix[j]
|
||||
let curr = if j_regular < prefix.len() {
|
||||
prefix[j_regular]
|
||||
} else {
|
||||
path[j - prefix.len()]
|
||||
path[j_regular - prefix.len()]
|
||||
};
|
||||
|
||||
let mut char_score = 1.0;
|
||||
if j > path_idx {
|
||||
let last = if j - 1 < prefix.len() {
|
||||
prefix[j - 1]
|
||||
let last = if j_regular - 1 < prefix.len() {
|
||||
prefix[j_regular - 1]
|
||||
} else {
|
||||
path[j - 1 - prefix.len()]
|
||||
path[j_regular - 1 - prefix.len()]
|
||||
};
|
||||
|
||||
if last == MAIN_SEPARATOR {
|
||||
@@ -279,17 +297,18 @@ impl<'a> Matcher<'a> {
|
||||
|
||||
let new_score = self.recursive_score_match(
|
||||
path,
|
||||
path_cased,
|
||||
path_lowercased,
|
||||
prefix,
|
||||
lowercase_prefix,
|
||||
query_idx + 1,
|
||||
j + 1,
|
||||
next_score,
|
||||
extra_lowercase_chars,
|
||||
) * multiplier;
|
||||
|
||||
if new_score > score {
|
||||
score = new_score;
|
||||
best_position = j;
|
||||
best_position = j_regular;
|
||||
// Optimization: can't score better than 1.
|
||||
if new_score == 1.0 {
|
||||
break;
|
||||
|
||||
@@ -59,8 +59,8 @@ impl ProjectEnvironment {
|
||||
|
||||
pub(crate) fn get_buffer_environment(
|
||||
&mut self,
|
||||
buffer: Entity<Buffer>,
|
||||
worktree_store: Entity<WorktreeStore>,
|
||||
buffer: &Entity<Buffer>,
|
||||
worktree_store: &Entity<WorktreeStore>,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Shared<Task<Option<HashMap<String, String>>>> {
|
||||
if cfg!(any(test, feature = "test-support")) {
|
||||
|
||||
@@ -8190,7 +8190,7 @@ impl LspStore {
|
||||
) -> Shared<Task<Option<HashMap<String, String>>>> {
|
||||
if let Some(environment) = &self.as_local().map(|local| local.environment.clone()) {
|
||||
environment.update(cx, |env, cx| {
|
||||
env.get_buffer_environment(buffer.clone(), self.worktree_store.clone(), cx)
|
||||
env.get_buffer_environment(&buffer, &self.worktree_store, cx)
|
||||
})
|
||||
} else {
|
||||
Task::ready(None).shared()
|
||||
|
||||
@@ -56,7 +56,7 @@ use futures::future::join_all;
|
||||
use futures::{
|
||||
StreamExt,
|
||||
channel::mpsc::{self, UnboundedReceiver},
|
||||
future::try_join_all,
|
||||
future::{Shared, try_join_all},
|
||||
};
|
||||
pub use image_store::{ImageItem, ImageStore};
|
||||
use image_store::{ImageItemEvent, ImageStoreEvent};
|
||||
@@ -1605,6 +1605,17 @@ impl Project {
|
||||
self.environment.read(cx).get_cli_environment()
|
||||
}
|
||||
|
||||
pub fn buffer_environment<'a>(
|
||||
&'a self,
|
||||
buffer: &Entity<Buffer>,
|
||||
worktree_store: &Entity<WorktreeStore>,
|
||||
cx: &'a mut App,
|
||||
) -> Shared<Task<Option<HashMap<String, String>>>> {
|
||||
self.environment.update(cx, |environment, cx| {
|
||||
environment.get_buffer_environment(&buffer, &worktree_store, cx)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn shell_environment_errors<'a>(
|
||||
&'a self,
|
||||
cx: &'a App,
|
||||
|
||||
@@ -315,11 +315,7 @@ fn local_task_context_for_location(
|
||||
cx.spawn(async move |cx| {
|
||||
let project_env = environment
|
||||
.update(cx, |environment, cx| {
|
||||
environment.get_buffer_environment(
|
||||
location.buffer.clone(),
|
||||
worktree_store.clone(),
|
||||
cx,
|
||||
)
|
||||
environment.get_buffer_environment(&location.buffer, &worktree_store, cx)
|
||||
})
|
||||
.ok()?
|
||||
.await;
|
||||
|
||||
@@ -46,7 +46,7 @@ use smol::channel::{Receiver, Sender};
|
||||
use task::{HideStrategy, Shell, TaskId};
|
||||
use terminal_settings::{AlternateScroll, CursorShape, TerminalSettings};
|
||||
use theme::{ActiveTheme, Theme};
|
||||
use util::{ResultExt, paths::home_dir, truncate_and_trailoff};
|
||||
use util::{paths::home_dir, truncate_and_trailoff};
|
||||
|
||||
use std::{
|
||||
cmp::{self, min},
|
||||
@@ -1851,8 +1851,7 @@ impl Terminal {
|
||||
if let Some(task) = self.task() {
|
||||
if task.status == TaskStatus::Running {
|
||||
let completion_receiver = task.completion_rx.clone();
|
||||
return cx
|
||||
.spawn(async move |_| completion_receiver.recv().await.log_err().flatten());
|
||||
return cx.spawn(async move |_| completion_receiver.recv().await.ok().flatten());
|
||||
} else if let Ok(status) = task.completion_rx.try_recv() {
|
||||
return Task::ready(status);
|
||||
}
|
||||
|
||||
@@ -483,7 +483,7 @@ impl TerminalPanel {
|
||||
task: &SpawnInTerminal,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Result<Entity<Terminal>>> {
|
||||
) -> Task<Result<WeakEntity<Terminal>>> {
|
||||
let Ok(is_local) = self
|
||||
.workspace
|
||||
.update(cx, |workspace, cx| workspace.project().read(cx).is_local())
|
||||
@@ -552,12 +552,12 @@ impl TerminalPanel {
|
||||
cx.spawn(async move |_, _| rx.await?)
|
||||
}
|
||||
|
||||
pub fn spawn_in_new_terminal(
|
||||
fn spawn_in_new_terminal(
|
||||
&mut self,
|
||||
spawn_task: SpawnInTerminal,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Result<Entity<Terminal>>> {
|
||||
) -> Task<Result<WeakEntity<Terminal>>> {
|
||||
let reveal = spawn_task.reveal;
|
||||
let reveal_target = spawn_task.reveal_target;
|
||||
let kind = TerminalKind::Task(spawn_task);
|
||||
@@ -652,7 +652,7 @@ impl TerminalPanel {
|
||||
kind: TerminalKind,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Workspace>,
|
||||
) -> Task<Result<Entity<Terminal>>> {
|
||||
) -> Task<Result<WeakEntity<Terminal>>> {
|
||||
if !is_enabled_in_workspace(workspace, cx) {
|
||||
return Task::ready(Err(anyhow!(
|
||||
"terminal not yet supported for remote projects"
|
||||
@@ -680,7 +680,7 @@ impl TerminalPanel {
|
||||
});
|
||||
workspace.add_item_to_active_pane(Box::new(terminal_view), None, true, window, cx);
|
||||
})?;
|
||||
Ok(terminal)
|
||||
Ok(terminal.downgrade())
|
||||
})
|
||||
}
|
||||
|
||||
@@ -690,7 +690,7 @@ impl TerminalPanel {
|
||||
reveal_strategy: RevealStrategy,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Result<Entity<Terminal>>> {
|
||||
) -> Task<Result<WeakEntity<Terminal>>> {
|
||||
let workspace = self.workspace.clone();
|
||||
cx.spawn_in(window, async move |terminal_panel, cx| {
|
||||
if workspace.update(cx, |workspace, cx| !is_enabled_in_workspace(workspace, cx))? {
|
||||
@@ -735,11 +735,12 @@ impl TerminalPanel {
|
||||
pane.add_item(terminal_view, true, focus, None, window, cx);
|
||||
});
|
||||
|
||||
Ok(terminal)
|
||||
Ok(terminal.downgrade())
|
||||
})?;
|
||||
terminal_panel.update(cx, |this, cx| {
|
||||
this.pending_terminals_to_add = this.pending_terminals_to_add.saturating_sub(1);
|
||||
this.serialize(cx)
|
||||
terminal_panel.update(cx, |terminal_panel, cx| {
|
||||
terminal_panel.pending_terminals_to_add =
|
||||
terminal_panel.pending_terminals_to_add.saturating_sub(1);
|
||||
terminal_panel.serialize(cx)
|
||||
})?;
|
||||
result
|
||||
})
|
||||
@@ -802,7 +803,7 @@ impl TerminalPanel {
|
||||
terminal_to_replace: Entity<TerminalView>,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Self>,
|
||||
) -> Task<Result<Entity<Terminal>>> {
|
||||
) -> Task<Result<WeakEntity<Terminal>>> {
|
||||
let reveal = spawn_task.reveal;
|
||||
let reveal_target = spawn_task.reveal_target;
|
||||
let window_handle = window.window_handle();
|
||||
@@ -884,7 +885,7 @@ impl TerminalPanel {
|
||||
RevealStrategy::Never => {}
|
||||
}
|
||||
|
||||
Ok(new_terminal)
|
||||
Ok(new_terminal.downgrade())
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1458,22 +1459,25 @@ impl workspace::TerminalProvider for TerminalProvider {
|
||||
task: SpawnInTerminal,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Task<Result<ExitStatus>> {
|
||||
let this = self.0.clone();
|
||||
) -> Task<Option<Result<ExitStatus>>> {
|
||||
let terminal_panel = self.0.clone();
|
||||
window.spawn(cx, async move |cx| {
|
||||
let terminal = this
|
||||
let terminal = terminal_panel
|
||||
.update_in(cx, |terminal_panel, window, cx| {
|
||||
terminal_panel.spawn_task(&task, window, cx)
|
||||
})?
|
||||
.await?;
|
||||
let Some(exit_code) = terminal
|
||||
.read_with(cx, |terminal, cx| terminal.wait_for_completed_task(cx))?
|
||||
.await
|
||||
else {
|
||||
return Err(anyhow!("Task cancelled"));
|
||||
};
|
||||
|
||||
Ok(exit_code)
|
||||
})
|
||||
.ok()?
|
||||
.await;
|
||||
match terminal {
|
||||
Ok(terminal) => {
|
||||
let exit_status = terminal
|
||||
.read_with(cx, |terminal, cx| terminal.wait_for_completed_task(cx))
|
||||
.ok()?
|
||||
.await?;
|
||||
Some(Ok(exit_status))
|
||||
}
|
||||
Err(e) => Some(Err(e)),
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1466,9 +1466,22 @@ impl ShellExec {
|
||||
show_command: false,
|
||||
show_rerun: false,
|
||||
};
|
||||
workspace
|
||||
.spawn_in_terminal(spawn_in_terminal, window, cx)
|
||||
.detach_and_log_err(cx);
|
||||
|
||||
let task_status = workspace.spawn_in_terminal(spawn_in_terminal, window, cx);
|
||||
cx.background_spawn(async move {
|
||||
match task_status.await {
|
||||
Some(Ok(status)) => {
|
||||
if status.success() {
|
||||
log::debug!("Vim shell exec succeeded");
|
||||
} else {
|
||||
log::debug!("Vim shell exec failed, code: {:?}", status.code());
|
||||
}
|
||||
}
|
||||
Some(Err(e)) => log::error!("Vim shell exec failed: {e}"),
|
||||
None => log::debug!("Vim shell exec got cancelled"),
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
});
|
||||
return;
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
mod cloud;
|
||||
|
||||
use client::Client;
|
||||
use gpui::{App, Context};
|
||||
use gpui::{App, Context, Entity};
|
||||
use language_model::LanguageModelRegistry;
|
||||
use std::sync::Arc;
|
||||
use web_search::{WebSearchProviderId, WebSearchRegistry};
|
||||
@@ -14,31 +14,44 @@ pub fn init(client: Arc<Client>, cx: &mut App) {
|
||||
}
|
||||
|
||||
fn register_web_search_providers(
|
||||
_registry: &mut WebSearchRegistry,
|
||||
registry: &mut WebSearchRegistry,
|
||||
client: Arc<Client>,
|
||||
cx: &mut Context<WebSearchRegistry>,
|
||||
) {
|
||||
register_zed_web_search_provider(
|
||||
registry,
|
||||
client.clone(),
|
||||
&LanguageModelRegistry::global(cx),
|
||||
cx,
|
||||
);
|
||||
|
||||
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(
|
||||
cloud::CloudWebSearchProvider::new(client.clone(), cx),
|
||||
cx,
|
||||
)
|
||||
} else {
|
||||
this.unregister_provider(WebSearchProviderId(
|
||||
cloud::ZED_WEB_SEARCH_PROVIDER_ID.into(),
|
||||
));
|
||||
}
|
||||
register_zed_web_search_provider(this, client.clone(), ®istry, cx)
|
||||
}
|
||||
_ => {}
|
||||
},
|
||||
)
|
||||
.detach();
|
||||
}
|
||||
|
||||
fn register_zed_web_search_provider(
|
||||
registry: &mut WebSearchRegistry,
|
||||
client: Arc<Client>,
|
||||
language_model_registry: &Entity<LanguageModelRegistry>,
|
||||
cx: &mut Context<WebSearchRegistry>,
|
||||
) {
|
||||
let using_zed_provider = language_model_registry
|
||||
.read(cx)
|
||||
.default_model()
|
||||
.map_or(false, |default| default.is_provided_by_zed());
|
||||
if using_zed_provider {
|
||||
registry.register_provider(cloud::CloudWebSearchProvider::new(client, cx), cx)
|
||||
} else {
|
||||
registry.unregister_provider(WebSearchProviderId(
|
||||
cloud::ZED_WEB_SEARCH_PROVIDER_ID.into(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use std::process::ExitStatus;
|
||||
|
||||
use anyhow::{Result, anyhow};
|
||||
use gpui::{Context, Entity, Task};
|
||||
use anyhow::Result;
|
||||
use gpui::{AppContext, Context, Entity, Task};
|
||||
use language::Buffer;
|
||||
use project::TaskSourceKind;
|
||||
use remote::ConnectionState;
|
||||
@@ -68,9 +68,21 @@ impl Workspace {
|
||||
}
|
||||
|
||||
if let Some(terminal_provider) = self.terminal_provider.as_ref() {
|
||||
terminal_provider
|
||||
.spawn(spawn_in_terminal, window, cx)
|
||||
.detach_and_log_err(cx);
|
||||
let task_status = terminal_provider.spawn(spawn_in_terminal, window, cx);
|
||||
cx.background_spawn(async move {
|
||||
match task_status.await {
|
||||
Some(Ok(status)) => {
|
||||
if status.success() {
|
||||
log::debug!("Task spawn succeeded");
|
||||
} else {
|
||||
log::debug!("Task spawn failed, code: {:?}", status.code());
|
||||
}
|
||||
}
|
||||
Some(Err(e)) => log::error!("Task spawn failed: {e}"),
|
||||
None => log::debug!("Task spawn got cancelled"),
|
||||
}
|
||||
})
|
||||
.detach();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,11 +104,11 @@ impl Workspace {
|
||||
spawn_in_terminal: SpawnInTerminal,
|
||||
window: &mut Window,
|
||||
cx: &mut Context<Workspace>,
|
||||
) -> Task<Result<ExitStatus>> {
|
||||
) -> Task<Option<Result<ExitStatus>>> {
|
||||
if let Some(terminal_provider) = self.terminal_provider.as_ref() {
|
||||
terminal_provider.spawn(spawn_in_terminal, window, cx)
|
||||
} else {
|
||||
Task::ready(Err(anyhow!("No terminal provider")))
|
||||
Task::ready(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,7 +136,7 @@ pub trait TerminalProvider {
|
||||
task: SpawnInTerminal,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> Task<Result<ExitStatus>>;
|
||||
) -> Task<Option<Result<ExitStatus>>>;
|
||||
}
|
||||
|
||||
pub trait DebuggerProvider {
|
||||
|
||||
Reference in New Issue
Block a user