From 417760ade7c9c692c6d1cd6494afce5109b17583 Mon Sep 17 00:00:00 2001 From: Marshall Bowers Date: Tue, 21 Jan 2025 18:08:34 -0500 Subject: [PATCH] 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 --- Cargo.lock | 10 +- crates/assistant/Cargo.toml | 1 - crates/assistant/src/assistant.rs | 40 --- crates/assistant/src/assistant_panel.rs | 95 +++++-- crates/assistant/src/context_history.rs | 5 +- crates/assistant/src/inline_assistant.rs | 5 +- .../src/terminal_inline_assistant.rs | 4 +- crates/assistant_context_editor/Cargo.toml | 14 +- .../src/assistant_context_editor.rs | 5 + .../src/context_editor.rs | 248 +++++++++++------- .../src/slash_command.rs | 2 +- .../src/slash_command_picker.rs | 0 12 files changed, 263 insertions(+), 166 deletions(-) rename crates/{assistant => assistant_context_editor}/src/context_editor.rs (95%) rename crates/{assistant => assistant_context_editor}/src/slash_command.rs (99%) rename crates/{assistant => assistant_context_editor}/src/slash_command_picker.rs (100%) diff --git a/Cargo.lock b/Cargo.lock index 263ec38cf3..5f04ebbe3e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", diff --git a/crates/assistant/Cargo.toml b/crates/assistant/Cargo.toml index 79ae691246..50985a51e9 100644 --- a/crates/assistant/Cargo.toml +++ b/crates/assistant/Cargo.toml @@ -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 diff --git a/crates/assistant/src/assistant.rs b/crates/assistant/src/assistant.rs index dcfd15fda8..dbff74b8ba 100644 --- a/crates/assistant/src/assistant.rs +++ b/crates/assistant/src/assistant.rs @@ -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), - ExternalFiles(Vec), -} - -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() { diff --git a/crates/assistant/src/assistant_panel.rs b/crates/assistant/src/assistant_panel.rs index 987a9c492c..4d79d15efd 100644 --- a/crates/assistant/src/assistant_panel.rs +++ b/crates/assistant/src/assistant_panel.rs @@ -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) { + ::set_global(Arc::new(ConcreteAssistantPanelDelegate), cx); + workspace::FollowableViewRegistry::register::(cx); cx.observe_new_views( |workspace: &mut Workspace, _cx: &mut ViewContext| { @@ -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> { - 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> { let existing_context = self.pane.read(cx).items().find_map(|item| { item.downcast::() - .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>> { let existing_context = self.pane.read(cx).items().find_map(|item| { item.downcast::() - .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, + ) -> Option> { + let panel = workspace.panel::(cx)?; + panel.read(cx).active_context_editor(cx) + } + + fn open_remote_context( + &self, + workspace: &mut Workspace, + context_id: ContextId, + cx: &mut ViewContext, + ) -> Task>> { + let Some(panel) = workspace.panel::(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, + ) { + let Some(panel) = workspace.panel::(cx) else { + return; + }; + + // Activate the panel + if !panel.focus_handle(cx).contains_focused(cx) { + workspace.toggle_panel_focus::(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, diff --git a/crates/assistant/src/context_history.rs b/crates/assistant/src/context_history.rs index 5003e638d1..769183651b 100644 --- a/crates/assistant/src/context_history.rs +++ b/crates/assistant/src/context_history.rs @@ -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)] diff --git a/crates/assistant/src/inline_assistant.rs b/crates/assistant/src/inline_assistant.rs index 626ba37158..8239571efc 100644 --- a/crates/assistant/src/inline_assistant.rs +++ b/crates/assistant/src/inline_assistant.rs @@ -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}; diff --git a/crates/assistant/src/terminal_inline_assistant.rs b/crates/assistant/src/terminal_inline_assistant.rs index 85db58aa6a..dfa5342fbb 100644 --- a/crates/assistant/src/terminal_inline_assistant.rs +++ b/crates/assistant/src/terminal_inline_assistant.rs @@ -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}; diff --git a/crates/assistant_context_editor/Cargo.toml b/crates/assistant_context_editor/Cargo.toml index 1b7e4bfacb..8ad036893b 100644 --- a/crates/assistant_context_editor/Cargo.toml +++ b/crates/assistant_context_editor/Cargo.toml @@ -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"] } diff --git a/crates/assistant_context_editor/src/assistant_context_editor.rs b/crates/assistant_context_editor/src/assistant_context_editor.rs index 6f24de83f7..4234e350e1 100644 --- a/crates/assistant_context_editor/src/assistant_context_editor.rs +++ b/crates/assistant_context_editor/src/assistant_context_editor.rs @@ -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, _cx: &mut AppContext) { context_store::init(&client.into()); diff --git a/crates/assistant/src/context_editor.rs b/crates/assistant_context_editor/src/context_editor.rs similarity index 95% rename from crates/assistant/src/context_editor.rs rename to crates/assistant_context_editor/src/context_editor.rs index c39b59aaf0..318c72fa7a 100644 --- a/crates/assistant/src/context_editor.rs +++ b/crates/assistant_context_editor/src/context_editor.rs @@ -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), + ExternalFiles(Vec), +} + +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, + ) -> Option>; + + fn open_remote_context( + &self, + workspace: &mut Workspace, + context_id: ContextId, + cx: &mut ViewContext, + ) -> Task>>; + + fn quote_selection( + &self, + workspace: &mut Workspace, + creases: Vec<(String, String)>, + cx: &mut ViewContext, + ); +} + +impl dyn AssistantPanelDelegate { + /// Returns the global [`AssistantPanelDelegate`], if it exists. + pub fn try_global(cx: &AppContext) -> Option> { + cx.try_global::() + .map(|global| global.0.clone()) + } + + /// Sets the global [`AssistantPanelDelegate`]. + pub fn set_global(delegate: Arc, cx: &mut AppContext) { + cx.set_global(GlobalAssistantPanelDelegate(delegate)); + } +} + +struct GlobalAssistantPanelDelegate(Arc); + +impl Global for GlobalAssistantPanelDelegate {} + pub struct ContextEditor { - pub(crate) context: Model, + context: Model, fs: Arc, slash_commands: Arc, tools: Arc, workspace: WeakView, project: Model, lsp_adapter_delegate: Option>, - pub(crate) editor: View, + editor: View, blocks: HashMap, image_blocks: HashSet, scroll_position: Option, @@ -113,7 +171,6 @@ pub struct ContextEditor { _subscriptions: Vec, patches: HashMap, PatchViewState>, active_patch: Option>, - assistant_panel: WeakView, last_error: Option, 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, fs: Arc, workspace: WeakView, project: Model, lsp_adapter_delegate: Option>, - assistant_panel: WeakView, cx: &mut ViewContext, ) -> 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 { + &self.context + } + + pub fn editor(&self) -> &View { + &self.editor + } + pub fn insert_default_prompt(&mut self, cx: &mut ViewContext) { let command_name = DefaultSlashCommand.name(); self.editor.update(cx, |editor, cx| { @@ -1523,10 +1586,12 @@ impl ContextEditor { _: &InsertIntoEditor, cx: &mut ViewContext, ) { - let Some(panel) = workspace.panel::(cx) else { + let Some(assistant_panel_delegate) = ::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) { let result = maybe!({ - let panel = workspace.panel::(cx)?; - let context_editor_view = panel.read(cx).active_context_editor(cx)?; + let assistant_panel_delegate = ::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, ) { - let Some(panel) = workspace.panel::(cx) else { + let Some(assistant_panel_delegate) = ::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, ) { - let Some(panel) = workspace.panel::(cx) else { + let Some(assistant_panel_delegate) = ::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::(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::(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.editor.update(cx, |editor, cx| { + editor.insert("\n", cx); + for (text, crease_title) in creases { + let point = editor.selections.newest::(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) { @@ -2154,10 +2207,10 @@ impl ContextEditor { } fn render_notice(&self, cx: &mut ViewContext) -> Option { - use feature_flags::FeatureFlagAppExt; - let nudge = self.assistant_panel.upgrade().map(|assistant_panel| { - assistant_panel.read(cx).show_zed_ai_notice && cx.has_flag::() - }); + // 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::(cx)?, - )) - })?; + let project = workspace.read(cx).project().clone(); + let assistant_panel_delegate = ::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 { 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::*; diff --git a/crates/assistant/src/slash_command.rs b/crates/assistant_context_editor/src/slash_command.rs similarity index 99% rename from crates/assistant/src/slash_command.rs rename to crates/assistant_context_editor/src/slash_command.rs index 0963790c9d..d00b3a41a9 100644 --- a/crates/assistant/src/slash_command.rs +++ b/crates/assistant_context_editor/src/slash_command.rs @@ -20,7 +20,7 @@ use std::{ }; use workspace::Workspace; -pub(crate) struct SlashCommandCompletionProvider { +pub struct SlashCommandCompletionProvider { cancel_flag: Mutex>, slash_commands: Arc, editor: Option>, diff --git a/crates/assistant/src/slash_command_picker.rs b/crates/assistant_context_editor/src/slash_command_picker.rs similarity index 100% rename from crates/assistant/src/slash_command_picker.rs rename to crates/assistant_context_editor/src/slash_command_picker.rs