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:
Marshall Bowers 2025-01-21 18:08:34 -05:00 committed by GitHub
parent 9a7f1d1de4
commit 417760ade7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 263 additions and 166 deletions

10
Cargo.lock generated
View file

@ -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",

View file

@ -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

View file

@ -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() {

View file

@ -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,

View file

@ -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)]

View file

@ -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};

View file

@ -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};

View file

@ -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"] }

View file

@ -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());

View file

@ -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::*;

View file

@ -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>>,