mirror of
https://github.com/zed-industries/zed.git
synced 2025-01-24 02:46:43 +00:00
Extract ContextEditor
to assistant_context_editor
(#23433)
This PR extracts the `ContextEditor` to the `assistant_context_editor` crate. As part of this, we have decoupled the `ContextEditor` from the `AssistantPanel`. There is now an `AssistantPanelDelegate` that the `ContextEditor` uses when it needs to interface with the Assistant panel. Release Notes: - N/A
This commit is contained in:
parent
9a7f1d1de4
commit
417760ade7
12 changed files with 263 additions and 166 deletions
10
Cargo.lock
generated
10
Cargo.lock
generated
|
@ -389,7 +389,6 @@ dependencies = [
|
|||
"feature_flags",
|
||||
"fs",
|
||||
"futures 0.3.31",
|
||||
"fuzzy",
|
||||
"gpui",
|
||||
"indexed_docs",
|
||||
"indoc",
|
||||
|
@ -502,6 +501,7 @@ name = "assistant_context_editor"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"assistant_settings",
|
||||
"assistant_slash_command",
|
||||
"assistant_slash_commands",
|
||||
"assistant_tool",
|
||||
|
@ -516,18 +516,24 @@ dependencies = [
|
|||
"futures 0.3.31",
|
||||
"fuzzy",
|
||||
"gpui",
|
||||
"indexed_docs",
|
||||
"language",
|
||||
"language_model",
|
||||
"language_model_selector",
|
||||
"language_models",
|
||||
"languages",
|
||||
"log",
|
||||
"multi_buffer",
|
||||
"open_ai",
|
||||
"parking_lot",
|
||||
"paths",
|
||||
"picker",
|
||||
"pretty_assertions",
|
||||
"project",
|
||||
"prompt_library",
|
||||
"rand 0.8.5",
|
||||
"regex",
|
||||
"rope",
|
||||
"rpc",
|
||||
"serde",
|
||||
"serde_json",
|
||||
|
@ -537,6 +543,8 @@ dependencies = [
|
|||
"strum",
|
||||
"telemetry_events",
|
||||
"text",
|
||||
"theme",
|
||||
"tree-sitter-md",
|
||||
"ui",
|
||||
"unindent",
|
||||
"util",
|
||||
|
|
|
@ -37,7 +37,6 @@ editor.workspace = true
|
|||
feature_flags.workspace = true
|
||||
fs.workspace = true
|
||||
futures.workspace = true
|
||||
fuzzy.workspace = true
|
||||
gpui.workspace = true
|
||||
indexed_docs.workspace = true
|
||||
indoc.workspace = true
|
||||
|
|
|
@ -1,15 +1,11 @@
|
|||
#![cfg_attr(target_os = "windows", allow(unused, dead_code))]
|
||||
|
||||
pub mod assistant_panel;
|
||||
mod context_editor;
|
||||
mod context_history;
|
||||
mod inline_assistant;
|
||||
mod slash_command;
|
||||
pub(crate) mod slash_command_picker;
|
||||
pub mod slash_command_settings;
|
||||
mod terminal_inline_assistant;
|
||||
|
||||
use std::path::PathBuf;
|
||||
use std::sync::Arc;
|
||||
|
||||
use assistant_settings::AssistantSettings;
|
||||
|
@ -19,7 +15,6 @@ use client::Client;
|
|||
use command_palette_hooks::CommandPaletteFilter;
|
||||
use feature_flags::FeatureFlagAppExt;
|
||||
use fs::Fs;
|
||||
use gpui::impl_internal_actions;
|
||||
use gpui::{actions, AppContext, Global, UpdateGlobal};
|
||||
use language_model::{
|
||||
LanguageModelId, LanguageModelProviderId, LanguageModelRegistry, LanguageModelResponseMessage,
|
||||
|
@ -37,33 +32,16 @@ use crate::slash_command_settings::SlashCommandSettings;
|
|||
actions!(
|
||||
assistant,
|
||||
[
|
||||
Assist,
|
||||
Edit,
|
||||
Split,
|
||||
CopyCode,
|
||||
CycleMessageRole,
|
||||
QuoteSelection,
|
||||
InsertIntoEditor,
|
||||
ToggleFocus,
|
||||
InsertActivePrompt,
|
||||
DeployHistory,
|
||||
DeployPromptLibrary,
|
||||
ConfirmCommand,
|
||||
NewContext,
|
||||
ToggleModelSelector,
|
||||
CycleNextInlineAssist,
|
||||
CyclePreviousInlineAssist
|
||||
]
|
||||
);
|
||||
|
||||
#[derive(PartialEq, Clone)]
|
||||
pub enum InsertDraggedFiles {
|
||||
ProjectPaths(Vec<PathBuf>),
|
||||
ExternalFiles(Vec<PathBuf>),
|
||||
}
|
||||
|
||||
impl_internal_actions!(assistant, [InsertDraggedFiles]);
|
||||
|
||||
const DEFAULT_CONTEXT_LINES: usize = 50;
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
|
@ -334,24 +312,6 @@ fn update_slash_commands_from_settings(cx: &mut AppContext) {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn humanize_token_count(count: usize) -> String {
|
||||
match count {
|
||||
0..=999 => count.to_string(),
|
||||
1000..=9999 => {
|
||||
let thousands = count / 1000;
|
||||
let hundreds = (count % 1000 + 50) / 100;
|
||||
if hundreds == 0 {
|
||||
format!("{}k", thousands)
|
||||
} else if hundreds == 10 {
|
||||
format!("{}k", thousands + 1)
|
||||
} else {
|
||||
format!("{}.{}k", thousands, hundreds)
|
||||
}
|
||||
}
|
||||
_ => format!("{}k", (count + 500) / 1000),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[ctor::ctor]
|
||||
fn init_logger() {
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
use crate::context_editor::{
|
||||
ContextEditor, ContextEditorToolbarItem, ContextEditorToolbarItemEvent, DEFAULT_TAB_TITLE,
|
||||
};
|
||||
use crate::context_history::ContextHistory;
|
||||
use crate::{
|
||||
slash_command::SlashCommandCompletionProvider,
|
||||
terminal_inline_assistant::TerminalInlineAssistant, DeployHistory, DeployPromptLibrary,
|
||||
InlineAssistant, InsertDraggedFiles, NewContext, ToggleFocus, ToggleModelSelector,
|
||||
InlineAssistant, NewContext, ToggleFocus,
|
||||
};
|
||||
use anyhow::{anyhow, Result};
|
||||
use assistant_context_editor::{
|
||||
AssistantPanelDelegate, Context, ContextEditor, ContextEditorToolbarItem,
|
||||
ContextEditorToolbarItemEvent, ContextId, ContextStore, ContextStoreEvent, InsertDraggedFiles,
|
||||
SlashCommandCompletionProvider, ToggleModelSelector, DEFAULT_TAB_TITLE,
|
||||
};
|
||||
use anyhow::Result;
|
||||
use assistant_context_editor::{Context, ContextId, ContextStore, ContextStoreEvent};
|
||||
use assistant_settings::{AssistantDockPosition, AssistantSettings};
|
||||
use assistant_slash_command::SlashCommandWorkingSet;
|
||||
use assistant_tool::ToolWorkingSet;
|
||||
|
@ -46,6 +46,8 @@ use workspace::{
|
|||
use zed_actions::InlineAssist;
|
||||
|
||||
pub fn init(cx: &mut AppContext) {
|
||||
<dyn AssistantPanelDelegate>::set_global(Arc::new(ConcreteAssistantPanelDelegate), cx);
|
||||
|
||||
workspace::FollowableViewRegistry::register::<ContextEditor>(cx);
|
||||
cx.observe_new_views(
|
||||
|workspace: &mut Workspace, _cx: &mut ViewContext<Workspace>| {
|
||||
|
@ -438,7 +440,7 @@ impl AssistantPanel {
|
|||
if let Some(context_editor) = self.active_context_editor(cx) {
|
||||
let new_summary = model_summary_editor.read(cx).text(cx);
|
||||
context_editor.update(cx, |context_editor, cx| {
|
||||
context_editor.context.update(cx, |context, cx| {
|
||||
context_editor.context().update(cx, |context, cx| {
|
||||
if context.summary().is_none()
|
||||
&& (new_summary == DEFAULT_TAB_TITLE || new_summary.trim().is_empty())
|
||||
{
|
||||
|
@ -475,7 +477,7 @@ impl AssistantPanel {
|
|||
) {
|
||||
if let Some(context_editor) = self.active_context_editor(cx) {
|
||||
context_editor.update(cx, |context_editor, cx| {
|
||||
context_editor.context.update(cx, |context, cx| {
|
||||
context_editor.context().update(cx, |context, cx| {
|
||||
context.summarize(true, cx);
|
||||
})
|
||||
})
|
||||
|
@ -501,7 +503,6 @@ impl AssistantPanel {
|
|||
.log_err()
|
||||
.flatten();
|
||||
|
||||
let assistant_panel = cx.view().downgrade();
|
||||
let editor = cx.new_view(|cx| {
|
||||
let mut editor = ContextEditor::for_context(
|
||||
context,
|
||||
|
@ -509,7 +510,6 @@ impl AssistantPanel {
|
|||
self.workspace.clone(),
|
||||
self.project.clone(),
|
||||
lsp_adapter_delegate,
|
||||
assistant_panel,
|
||||
cx,
|
||||
);
|
||||
editor.insert_default_prompt(cx);
|
||||
|
@ -523,7 +523,7 @@ impl AssistantPanel {
|
|||
if let Some(editor) = self.active_context_editor(cx) {
|
||||
editor.update(cx, |active_context, cx| {
|
||||
active_context
|
||||
.context
|
||||
.context()
|
||||
.update(cx, |context, cx| context.completion_provider_changed(cx))
|
||||
})
|
||||
}
|
||||
|
@ -716,7 +716,7 @@ impl AssistantPanel {
|
|||
.read(cx)
|
||||
.active_context_editor(cx)
|
||||
.and_then(|editor| {
|
||||
let editor = &editor.read(cx).editor;
|
||||
let editor = &editor.read(cx).editor().clone();
|
||||
if editor.read(cx).is_focused(cx) {
|
||||
Some(editor.clone())
|
||||
} else {
|
||||
|
@ -778,7 +778,6 @@ impl AssistantPanel {
|
|||
|
||||
let fs = this.fs.clone();
|
||||
let project = this.project.clone();
|
||||
let weak_assistant_panel = cx.view().downgrade();
|
||||
|
||||
let editor = cx.new_view(|cx| {
|
||||
ContextEditor::for_context(
|
||||
|
@ -787,7 +786,6 @@ impl AssistantPanel {
|
|||
workspace,
|
||||
project,
|
||||
lsp_adapter_delegate,
|
||||
weak_assistant_panel,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
@ -808,7 +806,6 @@ impl AssistantPanel {
|
|||
.log_err()
|
||||
.flatten();
|
||||
|
||||
let assistant_panel = cx.view().downgrade();
|
||||
let editor = cx.new_view(|cx| {
|
||||
let mut editor = ContextEditor::for_context(
|
||||
context,
|
||||
|
@ -816,7 +813,6 @@ impl AssistantPanel {
|
|||
self.workspace.clone(),
|
||||
self.project.clone(),
|
||||
lsp_adapter_delegate,
|
||||
assistant_panel,
|
||||
cx,
|
||||
);
|
||||
editor.insert_default_prompt(cx);
|
||||
|
@ -1013,7 +1009,7 @@ impl AssistantPanel {
|
|||
}
|
||||
|
||||
pub fn active_context(&self, cx: &AppContext) -> Option<Model<Context>> {
|
||||
Some(self.active_context_editor(cx)?.read(cx).context.clone())
|
||||
Some(self.active_context_editor(cx)?.read(cx).context().clone())
|
||||
}
|
||||
|
||||
pub fn open_saved_context(
|
||||
|
@ -1023,7 +1019,7 @@ impl AssistantPanel {
|
|||
) -> Task<Result<()>> {
|
||||
let existing_context = self.pane.read(cx).items().find_map(|item| {
|
||||
item.downcast::<ContextEditor>()
|
||||
.filter(|editor| editor.read(cx).context.read(cx).path() == Some(&path))
|
||||
.filter(|editor| editor.read(cx).context().read(cx).path() == Some(&path))
|
||||
});
|
||||
if let Some(existing_context) = existing_context {
|
||||
return cx.spawn(|this, mut cx| async move {
|
||||
|
@ -1042,7 +1038,6 @@ impl AssistantPanel {
|
|||
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
let context = context.await?;
|
||||
let assistant_panel = this.clone();
|
||||
this.update(&mut cx, |this, cx| {
|
||||
let editor = cx.new_view(|cx| {
|
||||
ContextEditor::for_context(
|
||||
|
@ -1051,7 +1046,6 @@ impl AssistantPanel {
|
|||
workspace,
|
||||
project,
|
||||
lsp_adapter_delegate,
|
||||
assistant_panel,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
@ -1069,7 +1063,7 @@ impl AssistantPanel {
|
|||
) -> Task<Result<View<ContextEditor>>> {
|
||||
let existing_context = self.pane.read(cx).items().find_map(|item| {
|
||||
item.downcast::<ContextEditor>()
|
||||
.filter(|editor| *editor.read(cx).context.read(cx).id() == id)
|
||||
.filter(|editor| *editor.read(cx).context().read(cx).id() == id)
|
||||
});
|
||||
if let Some(existing_context) = existing_context {
|
||||
return cx.spawn(|this, mut cx| async move {
|
||||
|
@ -1091,7 +1085,6 @@ impl AssistantPanel {
|
|||
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
let context = context.await?;
|
||||
let assistant_panel = this.clone();
|
||||
this.update(&mut cx, |this, cx| {
|
||||
let editor = cx.new_view(|cx| {
|
||||
ContextEditor::for_context(
|
||||
|
@ -1100,7 +1093,6 @@ impl AssistantPanel {
|
|||
workspace,
|
||||
this.project.clone(),
|
||||
lsp_adapter_delegate,
|
||||
assistant_panel,
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
@ -1304,6 +1296,61 @@ impl prompt_library::InlineAssistDelegate for PromptLibraryInlineAssist {
|
|||
}
|
||||
}
|
||||
|
||||
struct ConcreteAssistantPanelDelegate;
|
||||
|
||||
impl AssistantPanelDelegate for ConcreteAssistantPanelDelegate {
|
||||
fn active_context_editor(
|
||||
&self,
|
||||
workspace: &mut Workspace,
|
||||
cx: &mut ViewContext<Workspace>,
|
||||
) -> Option<View<ContextEditor>> {
|
||||
let panel = workspace.panel::<AssistantPanel>(cx)?;
|
||||
panel.read(cx).active_context_editor(cx)
|
||||
}
|
||||
|
||||
fn open_remote_context(
|
||||
&self,
|
||||
workspace: &mut Workspace,
|
||||
context_id: ContextId,
|
||||
cx: &mut ViewContext<Workspace>,
|
||||
) -> Task<Result<View<ContextEditor>>> {
|
||||
let Some(panel) = workspace.panel::<AssistantPanel>(cx) else {
|
||||
return Task::ready(Err(anyhow!("no Assistant panel found")));
|
||||
};
|
||||
|
||||
panel.update(cx, |panel, cx| panel.open_remote_context(context_id, cx))
|
||||
}
|
||||
|
||||
fn quote_selection(
|
||||
&self,
|
||||
workspace: &mut Workspace,
|
||||
creases: Vec<(String, String)>,
|
||||
cx: &mut ViewContext<Workspace>,
|
||||
) {
|
||||
let Some(panel) = workspace.panel::<AssistantPanel>(cx) else {
|
||||
return;
|
||||
};
|
||||
|
||||
// Activate the panel
|
||||
if !panel.focus_handle(cx).contains_focused(cx) {
|
||||
workspace.toggle_panel_focus::<AssistantPanel>(cx);
|
||||
}
|
||||
|
||||
panel.update(cx, |_, cx| {
|
||||
// Wait to create a new context until the workspace is no longer
|
||||
// being updated.
|
||||
cx.defer(move |panel, cx| {
|
||||
if let Some(context) = panel
|
||||
.active_context_editor(cx)
|
||||
.or_else(|| panel.new_context(cx))
|
||||
{
|
||||
context.update(cx, |context, cx| context.quote_creases(creases, cx));
|
||||
};
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
|
||||
pub enum WorkflowAssistStatus {
|
||||
Pending,
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use assistant_context_editor::{ContextStore, RemoteContextMetadata, SavedContextMetadata};
|
||||
use assistant_context_editor::{
|
||||
ContextStore, RemoteContextMetadata, SavedContextMetadata, DEFAULT_TAB_TITLE,
|
||||
};
|
||||
use gpui::{
|
||||
AppContext, EventEmitter, FocusHandle, FocusableView, Model, Subscription, Task, View, WeakView,
|
||||
};
|
||||
|
@ -10,7 +12,6 @@ use ui::utils::{format_distance_from_now, DateTimeType};
|
|||
use ui::{prelude::*, Avatar, ListItem, ListItemSpacing};
|
||||
use workspace::Item;
|
||||
|
||||
use crate::context_editor::DEFAULT_TAB_TITLE;
|
||||
use crate::AssistantPanel;
|
||||
|
||||
#[derive(Clone)]
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
use crate::{
|
||||
humanize_token_count, AssistantPanel, AssistantPanelEvent, CycleNextInlineAssist,
|
||||
CyclePreviousInlineAssist,
|
||||
AssistantPanel, AssistantPanelEvent, CycleNextInlineAssist, CyclePreviousInlineAssist,
|
||||
};
|
||||
use anyhow::{anyhow, Context as _, Result};
|
||||
use assistant_context_editor::RequestType;
|
||||
use assistant_context_editor::{humanize_token_count, RequestType};
|
||||
use assistant_settings::AssistantSettings;
|
||||
use client::{telemetry::Telemetry, ErrorExt};
|
||||
use collections::{hash_map, HashMap, HashSet, VecDeque};
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use crate::{humanize_token_count, AssistantPanel, AssistantPanelEvent, DEFAULT_CONTEXT_LINES};
|
||||
use crate::{AssistantPanel, AssistantPanelEvent, DEFAULT_CONTEXT_LINES};
|
||||
use anyhow::{Context as _, Result};
|
||||
use assistant_context_editor::RequestType;
|
||||
use assistant_context_editor::{humanize_token_count, RequestType};
|
||||
use assistant_settings::AssistantSettings;
|
||||
use client::telemetry::Telemetry;
|
||||
use collections::{HashMap, VecDeque};
|
||||
|
|
|
@ -13,6 +13,7 @@ path = "src/assistant_context_editor.rs"
|
|||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
assistant_settings.workspace = true
|
||||
assistant_slash_command.workspace = true
|
||||
assistant_slash_commands.workspace = true
|
||||
assistant_tool.workspace = true
|
||||
|
@ -27,32 +28,41 @@ fs.workspace = true
|
|||
futures.workspace = true
|
||||
fuzzy.workspace = true
|
||||
gpui.workspace = true
|
||||
indexed_docs.workspace = true
|
||||
language.workspace = true
|
||||
language_model.workspace = true
|
||||
language_model_selector.workspace = true
|
||||
language_models.workspace = true
|
||||
log.workspace = true
|
||||
multi_buffer.workspace = true
|
||||
open_ai.workspace = true
|
||||
parking_lot.workspace = true
|
||||
paths.workspace = true
|
||||
picker.workspace = true
|
||||
project.workspace = true
|
||||
prompt_library.workspace = true
|
||||
regex.workspace = true
|
||||
rope.workspace = true
|
||||
rpc.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
settings.workspace = true
|
||||
smallvec.workspace = true
|
||||
smol.workspace = true
|
||||
strum.workspace = true
|
||||
telemetry_events.workspace = true
|
||||
text.workspace = true
|
||||
theme.workspace = true
|
||||
ui.workspace = true
|
||||
util.workspace = true
|
||||
uuid.workspace = true
|
||||
workspace.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
language_model = { workspace = true, features = ["test-support"] }
|
||||
parking_lot.workspace = true
|
||||
languages = { workspace = true, features = ["test-support"] }
|
||||
pretty_assertions.workspace = true
|
||||
rand.workspace = true
|
||||
settings.workspace = true
|
||||
tree-sitter-md.workspace = true
|
||||
unindent.workspace = true
|
||||
workspace = { workspace = true, features = ["test-support"] }
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
mod context;
|
||||
mod context_editor;
|
||||
mod context_store;
|
||||
mod patch;
|
||||
mod slash_command;
|
||||
mod slash_command_picker;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
|
@ -8,8 +11,10 @@ use client::Client;
|
|||
use gpui::AppContext;
|
||||
|
||||
pub use crate::context::*;
|
||||
pub use crate::context_editor::*;
|
||||
pub use crate::context_store::*;
|
||||
pub use crate::patch::*;
|
||||
pub use crate::slash_command::*;
|
||||
|
||||
pub fn init(client: Arc<Client>, _cx: &mut AppContext) {
|
||||
context_store::init(&client.into());
|
||||
|
|
|
@ -1,9 +1,4 @@
|
|||
use anyhow::Result;
|
||||
use assistant_context_editor::{
|
||||
AssistantPatch, AssistantPatchStatus, CacheStatus, Content, Context, ContextEvent, ContextId,
|
||||
InvokedSlashCommandId, InvokedSlashCommandStatus, Message, MessageId, MessageMetadata,
|
||||
MessageStatus, ParsedSlashCommand, PendingSlashCommandStatus, RequestType,
|
||||
};
|
||||
use assistant_settings::AssistantSettings;
|
||||
use assistant_slash_command::{SlashCommand, SlashCommandOutputSection, SlashCommandWorkingSet};
|
||||
use assistant_slash_commands::{
|
||||
|
@ -27,12 +22,12 @@ use editor::{display_map::CreaseId, FoldPlaceholder};
|
|||
use fs::Fs;
|
||||
use futures::FutureExt;
|
||||
use gpui::{
|
||||
div, img, percentage, point, prelude::*, pulsating_between, size, Animation, AnimationExt,
|
||||
AnyElement, AnyView, AppContext, AsyncWindowContext, ClipboardEntry, ClipboardItem,
|
||||
CursorStyle, Empty, Entity, EventEmitter, FocusHandle, FocusableView, FontWeight,
|
||||
InteractiveElement, IntoElement, Model, ParentElement, Pixels, Render, RenderImage,
|
||||
SharedString, Size, StatefulInteractiveElement, Styled, Subscription, Task, Transformation,
|
||||
View, WeakModel, WeakView,
|
||||
actions, div, img, impl_internal_actions, percentage, point, prelude::*, pulsating_between,
|
||||
size, Animation, AnimationExt, AnyElement, AnyView, AppContext, AsyncWindowContext,
|
||||
ClipboardEntry, ClipboardItem, CursorStyle, Empty, Entity, EventEmitter, FocusHandle,
|
||||
FocusableView, FontWeight, Global, InteractiveElement, IntoElement, Model, ParentElement,
|
||||
Pixels, Render, RenderImage, SharedString, Size, StatefulInteractiveElement, Styled,
|
||||
Subscription, Task, Transformation, View, WeakModel, WeakView,
|
||||
};
|
||||
use indexed_docs::IndexedDocsStore;
|
||||
use language::{language_settings::SoftWrap, BufferSnapshot, LspAdapterDelegate, ToOffset};
|
||||
|
@ -61,10 +56,34 @@ use workspace::{
|
|||
Workspace,
|
||||
};
|
||||
|
||||
actions!(
|
||||
assistant,
|
||||
[
|
||||
Assist,
|
||||
ConfirmCommand,
|
||||
CopyCode,
|
||||
CycleMessageRole,
|
||||
Edit,
|
||||
InsertIntoEditor,
|
||||
QuoteSelection,
|
||||
Split,
|
||||
ToggleModelSelector,
|
||||
]
|
||||
);
|
||||
|
||||
#[derive(PartialEq, Clone)]
|
||||
pub enum InsertDraggedFiles {
|
||||
ProjectPaths(Vec<PathBuf>),
|
||||
ExternalFiles(Vec<PathBuf>),
|
||||
}
|
||||
|
||||
impl_internal_actions!(assistant, [InsertDraggedFiles]);
|
||||
|
||||
use crate::{slash_command::SlashCommandCompletionProvider, slash_command_picker};
|
||||
use crate::{
|
||||
humanize_token_count, slash_command::SlashCommandCompletionProvider, slash_command_picker,
|
||||
Assist, AssistantPanel, ConfirmCommand, CopyCode, CycleMessageRole, Edit, InsertDraggedFiles,
|
||||
InsertIntoEditor, QuoteSelection, Split, ToggleModelSelector,
|
||||
AssistantPatch, AssistantPatchStatus, CacheStatus, Content, Context, ContextEvent, ContextId,
|
||||
InvokedSlashCommandId, InvokedSlashCommandStatus, Message, MessageId, MessageMetadata,
|
||||
MessageStatus, ParsedSlashCommand, PendingSlashCommandStatus, RequestType,
|
||||
};
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
|
@ -94,15 +113,54 @@ enum AssistError {
|
|||
Message(SharedString),
|
||||
}
|
||||
|
||||
pub trait AssistantPanelDelegate {
|
||||
fn active_context_editor(
|
||||
&self,
|
||||
workspace: &mut Workspace,
|
||||
cx: &mut ViewContext<Workspace>,
|
||||
) -> Option<View<ContextEditor>>;
|
||||
|
||||
fn open_remote_context(
|
||||
&self,
|
||||
workspace: &mut Workspace,
|
||||
context_id: ContextId,
|
||||
cx: &mut ViewContext<Workspace>,
|
||||
) -> Task<Result<View<ContextEditor>>>;
|
||||
|
||||
fn quote_selection(
|
||||
&self,
|
||||
workspace: &mut Workspace,
|
||||
creases: Vec<(String, String)>,
|
||||
cx: &mut ViewContext<Workspace>,
|
||||
);
|
||||
}
|
||||
|
||||
impl dyn AssistantPanelDelegate {
|
||||
/// Returns the global [`AssistantPanelDelegate`], if it exists.
|
||||
pub fn try_global(cx: &AppContext) -> Option<Arc<Self>> {
|
||||
cx.try_global::<GlobalAssistantPanelDelegate>()
|
||||
.map(|global| global.0.clone())
|
||||
}
|
||||
|
||||
/// Sets the global [`AssistantPanelDelegate`].
|
||||
pub fn set_global(delegate: Arc<Self>, cx: &mut AppContext) {
|
||||
cx.set_global(GlobalAssistantPanelDelegate(delegate));
|
||||
}
|
||||
}
|
||||
|
||||
struct GlobalAssistantPanelDelegate(Arc<dyn AssistantPanelDelegate>);
|
||||
|
||||
impl Global for GlobalAssistantPanelDelegate {}
|
||||
|
||||
pub struct ContextEditor {
|
||||
pub(crate) context: Model<Context>,
|
||||
context: Model<Context>,
|
||||
fs: Arc<dyn Fs>,
|
||||
slash_commands: Arc<SlashCommandWorkingSet>,
|
||||
tools: Arc<ToolWorkingSet>,
|
||||
workspace: WeakView<Workspace>,
|
||||
project: Model<Project>,
|
||||
lsp_adapter_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
||||
pub(crate) editor: View<Editor>,
|
||||
editor: View<Editor>,
|
||||
blocks: HashMap<MessageId, (MessageHeader, CustomBlockId)>,
|
||||
image_blocks: HashSet<CustomBlockId>,
|
||||
scroll_position: Option<ScrollPosition>,
|
||||
|
@ -113,7 +171,6 @@ pub struct ContextEditor {
|
|||
_subscriptions: Vec<Subscription>,
|
||||
patches: HashMap<Range<language::Anchor>, PatchViewState>,
|
||||
active_patch: Option<Range<language::Anchor>>,
|
||||
assistant_panel: WeakView<AssistantPanel>,
|
||||
last_error: Option<AssistError>,
|
||||
show_accept_terms: bool,
|
||||
pub(crate) slash_menu_handle:
|
||||
|
@ -130,13 +187,12 @@ pub const DEFAULT_TAB_TITLE: &str = "New Chat";
|
|||
const MAX_TAB_TITLE_LEN: usize = 16;
|
||||
|
||||
impl ContextEditor {
|
||||
pub(crate) fn for_context(
|
||||
pub fn for_context(
|
||||
context: Model<Context>,
|
||||
fs: Arc<dyn Fs>,
|
||||
workspace: WeakView<Workspace>,
|
||||
project: Model<Project>,
|
||||
lsp_adapter_delegate: Option<Arc<dyn LspAdapterDelegate>>,
|
||||
assistant_panel: WeakView<AssistantPanel>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Self {
|
||||
let completion_provider = SlashCommandCompletionProvider::new(
|
||||
|
@ -190,7 +246,6 @@ impl ContextEditor {
|
|||
_subscriptions,
|
||||
patches: HashMap::default(),
|
||||
active_patch: None,
|
||||
assistant_panel,
|
||||
last_error: None,
|
||||
show_accept_terms: false,
|
||||
slash_menu_handle: Default::default(),
|
||||
|
@ -203,6 +258,14 @@ impl ContextEditor {
|
|||
this
|
||||
}
|
||||
|
||||
pub fn context(&self) -> &Model<Context> {
|
||||
&self.context
|
||||
}
|
||||
|
||||
pub fn editor(&self) -> &View<Editor> {
|
||||
&self.editor
|
||||
}
|
||||
|
||||
pub fn insert_default_prompt(&mut self, cx: &mut ViewContext<Self>) {
|
||||
let command_name = DefaultSlashCommand.name();
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
|
@ -1523,10 +1586,12 @@ impl ContextEditor {
|
|||
_: &InsertIntoEditor,
|
||||
cx: &mut ViewContext<Workspace>,
|
||||
) {
|
||||
let Some(panel) = workspace.panel::<AssistantPanel>(cx) else {
|
||||
let Some(assistant_panel_delegate) = <dyn AssistantPanelDelegate>::try_global(cx) else {
|
||||
return;
|
||||
};
|
||||
let Some(context_editor_view) = panel.read(cx).active_context_editor(cx) else {
|
||||
let Some(context_editor_view) =
|
||||
assistant_panel_delegate.active_context_editor(workspace, cx)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let Some(active_editor_view) = workspace
|
||||
|
@ -1546,8 +1611,9 @@ impl ContextEditor {
|
|||
|
||||
pub fn copy_code(workspace: &mut Workspace, _: &CopyCode, cx: &mut ViewContext<Workspace>) {
|
||||
let result = maybe!({
|
||||
let panel = workspace.panel::<AssistantPanel>(cx)?;
|
||||
let context_editor_view = panel.read(cx).active_context_editor(cx)?;
|
||||
let assistant_panel_delegate = <dyn AssistantPanelDelegate>::try_global(cx)?;
|
||||
let context_editor_view =
|
||||
assistant_panel_delegate.active_context_editor(workspace, cx)?;
|
||||
Self::get_selection_or_code_block(&context_editor_view, cx)
|
||||
});
|
||||
let Some((text, is_code_block)) = result else {
|
||||
|
@ -1579,10 +1645,12 @@ impl ContextEditor {
|
|||
action: &InsertDraggedFiles,
|
||||
cx: &mut ViewContext<Workspace>,
|
||||
) {
|
||||
let Some(panel) = workspace.panel::<AssistantPanel>(cx) else {
|
||||
let Some(assistant_panel_delegate) = <dyn AssistantPanelDelegate>::try_global(cx) else {
|
||||
return;
|
||||
};
|
||||
let Some(context_editor_view) = panel.read(cx).active_context_editor(cx) else {
|
||||
let Some(context_editor_view) =
|
||||
assistant_panel_delegate.active_context_editor(workspace, cx)
|
||||
else {
|
||||
return;
|
||||
};
|
||||
|
||||
|
@ -1653,7 +1721,7 @@ impl ContextEditor {
|
|||
_: &QuoteSelection,
|
||||
cx: &mut ViewContext<Workspace>,
|
||||
) {
|
||||
let Some(panel) = workspace.panel::<AssistantPanel>(cx) else {
|
||||
let Some(assistant_panel_delegate) = <dyn AssistantPanelDelegate>::try_global(cx) else {
|
||||
return;
|
||||
};
|
||||
|
||||
|
@ -1664,61 +1732,46 @@ impl ContextEditor {
|
|||
if creases.is_empty() {
|
||||
return;
|
||||
}
|
||||
// Activate the panel
|
||||
if !panel.focus_handle(cx).contains_focused(cx) {
|
||||
workspace.toggle_panel_focus::<AssistantPanel>(cx);
|
||||
}
|
||||
|
||||
panel.update(cx, |_, cx| {
|
||||
// Wait to create a new context until the workspace is no longer
|
||||
// being updated.
|
||||
cx.defer(move |panel, cx| {
|
||||
if let Some(context) = panel
|
||||
.active_context_editor(cx)
|
||||
.or_else(|| panel.new_context(cx))
|
||||
{
|
||||
context.update(cx, |context, cx| {
|
||||
context.editor.update(cx, |editor, cx| {
|
||||
editor.insert("\n", cx);
|
||||
for (text, crease_title) in creases {
|
||||
let point = editor.selections.newest::<Point>(cx).head();
|
||||
let start_row = MultiBufferRow(point.row);
|
||||
assistant_panel_delegate.quote_selection(workspace, creases, cx);
|
||||
}
|
||||
|
||||
editor.insert(&text, cx);
|
||||
pub fn quote_creases(&mut self, creases: Vec<(String, String)>, cx: &mut ViewContext<Self>) {
|
||||
self.editor.update(cx, |editor, cx| {
|
||||
editor.insert("\n", cx);
|
||||
for (text, crease_title) in creases {
|
||||
let point = editor.selections.newest::<Point>(cx).head();
|
||||
let start_row = MultiBufferRow(point.row);
|
||||
|
||||
let snapshot = editor.buffer().read(cx).snapshot(cx);
|
||||
let anchor_before = snapshot.anchor_after(point);
|
||||
let anchor_after = editor
|
||||
.selections
|
||||
.newest_anchor()
|
||||
.head()
|
||||
.bias_left(&snapshot);
|
||||
editor.insert(&text, cx);
|
||||
|
||||
editor.insert("\n", cx);
|
||||
let snapshot = editor.buffer().read(cx).snapshot(cx);
|
||||
let anchor_before = snapshot.anchor_after(point);
|
||||
let anchor_after = editor
|
||||
.selections
|
||||
.newest_anchor()
|
||||
.head()
|
||||
.bias_left(&snapshot);
|
||||
|
||||
let fold_placeholder = quote_selection_fold_placeholder(
|
||||
crease_title,
|
||||
cx.view().downgrade(),
|
||||
);
|
||||
let crease = Crease::inline(
|
||||
anchor_before..anchor_after,
|
||||
fold_placeholder,
|
||||
render_quote_selection_output_toggle,
|
||||
|_, _, _| Empty.into_any(),
|
||||
);
|
||||
editor.insert_creases(vec![crease], cx);
|
||||
editor.fold_at(
|
||||
&FoldAt {
|
||||
buffer_row: start_row,
|
||||
},
|
||||
cx,
|
||||
);
|
||||
}
|
||||
})
|
||||
});
|
||||
};
|
||||
});
|
||||
});
|
||||
editor.insert("\n", cx);
|
||||
|
||||
let fold_placeholder =
|
||||
quote_selection_fold_placeholder(crease_title, cx.view().downgrade());
|
||||
let crease = Crease::inline(
|
||||
anchor_before..anchor_after,
|
||||
fold_placeholder,
|
||||
render_quote_selection_output_toggle,
|
||||
|_, _, _| Empty.into_any(),
|
||||
);
|
||||
editor.insert_creases(vec![crease], cx);
|
||||
editor.fold_at(
|
||||
&FoldAt {
|
||||
buffer_row: start_row,
|
||||
},
|
||||
cx,
|
||||
);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn copy(&mut self, _: &editor::actions::Copy, cx: &mut ViewContext<Self>) {
|
||||
|
@ -2154,10 +2207,10 @@ impl ContextEditor {
|
|||
}
|
||||
|
||||
fn render_notice(&self, cx: &mut ViewContext<Self>) -> Option<AnyElement> {
|
||||
use feature_flags::FeatureFlagAppExt;
|
||||
let nudge = self.assistant_panel.upgrade().map(|assistant_panel| {
|
||||
assistant_panel.read(cx).show_zed_ai_notice && cx.has_flag::<feature_flags::ZedPro>()
|
||||
});
|
||||
// This was previously gated behind the `zed-pro` feature flag. Since we
|
||||
// aren't planning to ship that right now, we're just hard-coding this
|
||||
// value to not show the nudge.
|
||||
let nudge = Some(false);
|
||||
|
||||
if nudge.map_or(false, |value| value) {
|
||||
Some(
|
||||
|
@ -3039,18 +3092,15 @@ impl FollowableItem for ContextEditor {
|
|||
let context_id = ContextId::from_proto(state.context_id);
|
||||
let editor_state = state.editor?;
|
||||
|
||||
let (project, panel) = workspace.update(cx, |workspace, cx| {
|
||||
Some((
|
||||
workspace.project().clone(),
|
||||
workspace.panel::<AssistantPanel>(cx)?,
|
||||
))
|
||||
})?;
|
||||
let project = workspace.read(cx).project().clone();
|
||||
let assistant_panel_delegate = <dyn AssistantPanelDelegate>::try_global(cx)?;
|
||||
|
||||
let context_editor =
|
||||
panel.update(cx, |panel, cx| panel.open_remote_context(context_id, cx));
|
||||
let context_editor_task = workspace.update(cx, |workspace, cx| {
|
||||
assistant_panel_delegate.open_remote_context(workspace, context_id, cx)
|
||||
});
|
||||
|
||||
Some(cx.spawn(|mut cx| async move {
|
||||
let context_editor = context_editor.await?;
|
||||
let context_editor = context_editor_task.await?;
|
||||
context_editor
|
||||
.update(&mut cx, |context_editor, cx| {
|
||||
context_editor.remote_id = Some(id);
|
||||
|
@ -3466,6 +3516,24 @@ fn configuration_error(cx: &AppContext) -> Option<ConfigurationError> {
|
|||
None
|
||||
}
|
||||
|
||||
pub fn humanize_token_count(count: usize) -> String {
|
||||
match count {
|
||||
0..=999 => count.to_string(),
|
||||
1000..=9999 => {
|
||||
let thousands = count / 1000;
|
||||
let hundreds = (count % 1000 + 50) / 100;
|
||||
if hundreds == 0 {
|
||||
format!("{}k", thousands)
|
||||
} else if hundreds == 10 {
|
||||
format!("{}k", thousands + 1)
|
||||
} else {
|
||||
format!("{}.{}k", thousands, hundreds)
|
||||
}
|
||||
}
|
||||
_ => format!("{}k", (count + 500) / 1000),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
|
@ -20,7 +20,7 @@ use std::{
|
|||
};
|
||||
use workspace::Workspace;
|
||||
|
||||
pub(crate) struct SlashCommandCompletionProvider {
|
||||
pub struct SlashCommandCompletionProvider {
|
||||
cancel_flag: Mutex<Arc<AtomicBool>>,
|
||||
slash_commands: Arc<SlashCommandWorkingSet>,
|
||||
editor: Option<WeakView<ContextEditor>>,
|
Loading…
Reference in a new issue