This commit is contained in:
Antonio Scandurra 2023-12-05 19:27:15 +01:00
parent e534c5fdcd
commit d86da04584
5 changed files with 307 additions and 327 deletions

View file

@ -104,7 +104,7 @@ pub struct OpenAIResponseStreamEvent {
pub async fn stream_completion( pub async fn stream_completion(
credential: ProviderCredential, credential: ProviderCredential,
executor: Arc<BackgroundExecutor>, executor: BackgroundExecutor,
request: Box<dyn CompletionRequest>, request: Box<dyn CompletionRequest>,
) -> Result<impl Stream<Item = Result<OpenAIResponseStreamEvent>>> { ) -> Result<impl Stream<Item = Result<OpenAIResponseStreamEvent>>> {
let api_key = match credential { let api_key = match credential {
@ -197,11 +197,11 @@ pub async fn stream_completion(
pub struct OpenAICompletionProvider { pub struct OpenAICompletionProvider {
model: OpenAILanguageModel, model: OpenAILanguageModel,
credential: Arc<RwLock<ProviderCredential>>, credential: Arc<RwLock<ProviderCredential>>,
executor: Arc<BackgroundExecutor>, executor: BackgroundExecutor,
} }
impl OpenAICompletionProvider { impl OpenAICompletionProvider {
pub fn new(model_name: &str, executor: Arc<BackgroundExecutor>) -> Self { pub fn new(model_name: &str, executor: BackgroundExecutor) -> Self {
let model = OpenAILanguageModel::load(model_name); let model = OpenAILanguageModel::load(model_name);
let credential = Arc::new(RwLock::new(ProviderCredential::NoCredentials)); let credential = Arc::new(RwLock::new(ProviderCredential::NoCredentials));
Self { Self {

View file

@ -27,8 +27,8 @@ use editor::{
use fs::Fs; use fs::Fs;
use futures::StreamExt; use futures::StreamExt;
use gpui::{ use gpui::{
actions, div, point, uniform_list, Action, AnyElement, AppContext, AsyncAppContext, actions, div, point, uniform_list, Action, AnyElement, AppContext, AsyncWindowContext,
ClipboardItem, Div, Element, Entity, EventEmitter, FocusHandle, Focusable, FocusableView, ClipboardItem, Context, Div, EventEmitter, FocusHandle, Focusable, FocusableView,
HighlightStyle, InteractiveElement, IntoElement, Model, ModelContext, ParentElement, Pixels, HighlightStyle, InteractiveElement, IntoElement, Model, ModelContext, ParentElement, Pixels,
PromptLevel, Render, StatefulInteractiveElement, Styled, Subscription, Task, PromptLevel, Render, StatefulInteractiveElement, Styled, Subscription, Task,
UniformListScrollHandle, View, ViewContext, VisualContext, WeakModel, WeakView, WindowContext, UniformListScrollHandle, View, ViewContext, VisualContext, WeakModel, WeakView, WindowContext,
@ -51,7 +51,7 @@ use std::{
}; };
use ui::{ use ui::{
h_stack, v_stack, Button, ButtonCommon, ButtonLike, Clickable, Color, Icon, IconButton, h_stack, v_stack, Button, ButtonCommon, ButtonLike, Clickable, Color, Icon, IconButton,
IconElement, Label, Selectable, StyledExt, Tooltip, IconElement, Label, Selectable, Tooltip,
}; };
use util::{paths::CONVERSATIONS_DIR, post_inc, ResultExt, TryFutureExt}; use util::{paths::CONVERSATIONS_DIR, post_inc, ResultExt, TryFutureExt};
use uuid::Uuid; use uuid::Uuid;
@ -76,49 +76,18 @@ actions!(
pub fn init(cx: &mut AppContext) { pub fn init(cx: &mut AppContext) {
AssistantSettings::register(cx); AssistantSettings::register(cx);
cx.add_action( cx.observe_new_views(
|this: &mut AssistantPanel, |workspace: &mut Workspace, cx: &mut ViewContext<Workspace>| {
_: &workspace::NewFile, workspace
cx: &mut ViewContext<AssistantPanel>| { .register_action(|workspace, _: &ToggleFocus, cx| {
this.new_conversation(cx);
},
);
cx.add_action(ConversationEditor::assist);
cx.capture_action(ConversationEditor::cancel_last_assist);
cx.capture_action(ConversationEditor::save);
cx.add_action(ConversationEditor::quote_selection);
cx.capture_action(ConversationEditor::copy);
cx.add_action(ConversationEditor::split);
cx.capture_action(ConversationEditor::cycle_message_role);
cx.add_action(AssistantPanel::save_credentials);
cx.add_action(AssistantPanel::reset_credentials);
cx.add_action(AssistantPanel::toggle_zoom);
cx.add_action(AssistantPanel::deploy);
cx.add_action(AssistantPanel::select_next_match);
cx.add_action(AssistantPanel::select_prev_match);
cx.add_action(AssistantPanel::handle_editor_cancel);
cx.add_action(
|workspace: &mut Workspace, _: &ToggleFocus, cx: &mut ViewContext<Workspace>| {
workspace.toggle_panel_focus::<AssistantPanel>(cx); workspace.toggle_panel_focus::<AssistantPanel>(cx);
})
.register_action(AssistantPanel::inline_assist)
.register_action(AssistantPanel::cancel_last_inline_assist)
.register_action(ConversationEditor::quote_selection);
}, },
); )
cx.add_action(AssistantPanel::inline_assist); .detach();
cx.add_action(AssistantPanel::cancel_last_inline_assist);
cx.add_action(InlineAssistant::confirm);
cx.add_action(InlineAssistant::cancel);
cx.add_action(InlineAssistant::toggle_include_conversation);
cx.add_action(InlineAssistant::toggle_retrieve_context);
cx.add_action(InlineAssistant::move_up);
cx.add_action(InlineAssistant::move_down);
}
#[derive(Debug)]
pub enum AssistantPanelEvent {
ZoomIn,
ZoomOut,
Focus,
Close,
DockPositionChanged,
} }
pub struct AssistantPanel { pub struct AssistantPanel {
@ -131,7 +100,6 @@ pub struct AssistantPanel {
saved_conversations: Vec<SavedConversationMetadata>, saved_conversations: Vec<SavedConversationMetadata>,
saved_conversations_scroll_handle: UniformListScrollHandle, saved_conversations_scroll_handle: UniformListScrollHandle,
zoomed: bool, zoomed: bool,
// todo!("remove has_focus field")
focus_handle: FocusHandle, focus_handle: FocusHandle,
toolbar: View<Toolbar>, toolbar: View<Toolbar>,
completion_provider: Arc<dyn CompletionProvider>, completion_provider: Arc<dyn CompletionProvider>,
@ -152,9 +120,12 @@ pub struct AssistantPanel {
impl AssistantPanel { impl AssistantPanel {
const INLINE_PROMPT_HISTORY_MAX_LEN: usize = 20; const INLINE_PROMPT_HISTORY_MAX_LEN: usize = 20;
pub fn load(workspace: WeakView<Workspace>, cx: AsyncAppContext) -> Task<Result<View<Self>>> { pub fn load(
workspace: WeakView<Workspace>,
mut cx: AsyncWindowContext,
) -> Task<Result<View<Self>>> {
cx.spawn(|mut cx| async move { cx.spawn(|mut cx| async move {
let fs = workspace.read_with(&cx, |workspace, _| workspace.app_state().fs.clone())?; let fs = workspace.update(&mut cx, |workspace, _| workspace.app_state().fs.clone())?;
let saved_conversations = SavedConversationMetadata::list(fs.clone()) let saved_conversations = SavedConversationMetadata::list(fs.clone())
.await .await
.log_err() .log_err()
@ -163,7 +134,7 @@ impl AssistantPanel {
// TODO: deserialize state. // TODO: deserialize state.
let workspace_handle = workspace.clone(); let workspace_handle = workspace.clone();
workspace.update(&mut cx, |workspace, cx| { workspace.update(&mut cx, |workspace, cx| {
cx.add_view::<Self, _>(|cx| { cx.build_view::<Self>(|cx| {
const CONVERSATION_WATCH_DURATION: Duration = Duration::from_millis(100); const CONVERSATION_WATCH_DURATION: Duration = Duration::from_millis(100);
let _watch_saved_conversations = cx.spawn(move |this, mut cx| async move { let _watch_saved_conversations = cx.spawn(move |this, mut cx| async move {
let mut events = fs let mut events = fs
@ -184,10 +155,10 @@ impl AssistantPanel {
anyhow::Ok(()) anyhow::Ok(())
}); });
let toolbar = cx.add_view(|cx| { let toolbar = cx.build_view(|cx| {
let mut toolbar = Toolbar::new(); let mut toolbar = Toolbar::new();
toolbar.set_can_navigate(false, cx); toolbar.set_can_navigate(false, cx);
toolbar.add_item(cx.add_view(|cx| BufferSearchBar::new(cx)), cx); toolbar.add_item(cx.build_view(|cx| BufferSearchBar::new(cx)), cx);
toolbar toolbar
}); });
@ -199,8 +170,8 @@ impl AssistantPanel {
)); ));
let focus_handle = cx.focus_handle(); let focus_handle = cx.focus_handle();
cx.on_focus_in(Self::focus_in).detach(); cx.on_focus_in(&focus_handle, Self::focus_in).detach();
cx.on_focus_out(Self::focus_out).detach(); cx.on_focus_out(&focus_handle, Self::focus_out).detach();
let mut this = Self { let mut this = Self {
workspace: workspace_handle, workspace: workspace_handle,
@ -231,11 +202,11 @@ impl AssistantPanel {
let mut old_dock_position = this.position(cx); let mut old_dock_position = this.position(cx);
this.subscriptions = this.subscriptions =
vec![cx.observe_global::<SettingsStore, _>(move |this, cx| { vec![cx.observe_global::<SettingsStore>(move |this, cx| {
let new_dock_position = this.position(cx); let new_dock_position = this.position(cx);
if new_dock_position != old_dock_position { if new_dock_position != old_dock_position {
old_dock_position = new_dock_position; old_dock_position = new_dock_position;
cx.emit(AssistantPanelEvent::DockPositionChanged); cx.emit(PanelEvent::ChangePosition);
} }
cx.notify(); cx.notify();
})]; })];
@ -343,7 +314,7 @@ impl AssistantPanel {
// Retrieve Credentials Authenticates the Provider // Retrieve Credentials Authenticates the Provider
provider.retrieve_credentials(cx); provider.retrieve_credentials(cx);
let codegen = cx.add_model(|cx| { let codegen = cx.build_model(|cx| {
Codegen::new(editor.read(cx).buffer().clone(), codegen_kind, provider, cx) Codegen::new(editor.read(cx).buffer().clone(), codegen_kind, provider, cx)
}); });
@ -353,14 +324,14 @@ impl AssistantPanel {
let previously_indexed = semantic_index let previously_indexed = semantic_index
.update(&mut cx, |index, cx| { .update(&mut cx, |index, cx| {
index.project_previously_indexed(&project, cx) index.project_previously_indexed(&project, cx)
}) })?
.await .await
.unwrap_or(false); .unwrap_or(false);
if previously_indexed { if previously_indexed {
let _ = semantic_index let _ = semantic_index
.update(&mut cx, |index, cx| { .update(&mut cx, |index, cx| {
index.index_project(project.clone(), cx) index.index_project(project.clone(), cx)
}) })?
.await; .await;
} }
anyhow::Ok(()) anyhow::Ok(())
@ -369,7 +340,7 @@ impl AssistantPanel {
} }
let measurements = Rc::new(Cell::new(BlockMeasurements::default())); let measurements = Rc::new(Cell::new(BlockMeasurements::default()));
let inline_assistant = cx.add_view(|cx| { let inline_assistant = cx.build_view(|cx| {
let assistant = InlineAssistant::new( let assistant = InlineAssistant::new(
inline_assist_id, inline_assist_id,
measurements.clone(), measurements.clone(),
@ -382,7 +353,7 @@ impl AssistantPanel {
self.semantic_index.clone(), self.semantic_index.clone(),
project.clone(), project.clone(),
); );
cx.focus_self(); assistant.focus_handle.focus(cx);
assistant assistant
}); });
let block_id = editor.update(cx, |editor, cx| { let block_id = editor.update(cx, |editor, cx| {
@ -429,8 +400,13 @@ impl AssistantPanel {
move |_, editor, event, cx| { move |_, editor, event, cx| {
if let Some(inline_assistant) = inline_assistant.upgrade() { if let Some(inline_assistant) = inline_assistant.upgrade() {
if let EditorEvent::SelectionsChanged { local } = event { if let EditorEvent::SelectionsChanged { local } = event {
if *local && inline_assistant.read(cx).has_focus { if *local
cx.focus(&editor); && inline_assistant
.read(cx)
.focus_handle
.contains_focused(cx)
{
cx.focus_view(&editor);
} }
} }
} }
@ -555,7 +531,7 @@ impl AssistantPanel {
} }
} }
cx.propagate_action(); cx.propagate();
} }
fn finish_inline_assist(&mut self, assist_id: usize, undo: bool, cx: &mut ViewContext<Self>) { fn finish_inline_assist(&mut self, assist_id: usize, undo: bool, cx: &mut ViewContext<Self>) {
@ -709,13 +685,17 @@ impl AssistantPanel {
let snippets = cx.spawn(|_, mut cx| async move { let snippets = cx.spawn(|_, mut cx| async move {
let mut snippets = Vec::new(); let mut snippets = Vec::new();
for result in search_results.await { for result in search_results.await {
snippets.push(PromptCodeSnippet::new(result.buffer, result.range, &mut cx)); snippets.push(PromptCodeSnippet::new(
result.buffer,
result.range,
&mut cx,
)?);
} }
snippets anyhow::Ok(snippets)
}); });
snippets snippets
} else { } else {
Task::ready(Vec::new()) Task::ready(Ok(Vec::new()))
}; };
let mut model = AssistantSettings::get_global(cx) let mut model = AssistantSettings::get_global(cx)
@ -724,7 +704,7 @@ impl AssistantPanel {
let model_name = model.full_name(); let model_name = model.full_name();
let prompt = cx.background_executor().spawn(async move { let prompt = cx.background_executor().spawn(async move {
let snippets = snippets.await; let snippets = snippets.await?;
let language_name = language_name.as_deref(); let language_name = language_name.as_deref();
generate_content_prompt( generate_content_prompt(
@ -799,7 +779,7 @@ impl AssistantPanel {
} else { } else {
editor.highlight_background::<PendingInlineAssist>( editor.highlight_background::<PendingInlineAssist>(
background_ranges, background_ranges,
|theme| theme.assistant.inline.pending_edit_background, |theme| gpui::red(), // todo!("use the appropriate color")
cx, cx,
); );
} }
@ -820,7 +800,7 @@ impl AssistantPanel {
} }
fn new_conversation(&mut self, cx: &mut ViewContext<Self>) -> View<ConversationEditor> { fn new_conversation(&mut self, cx: &mut ViewContext<Self>) -> View<ConversationEditor> {
let editor = cx.add_view(|cx| { let editor = cx.build_view(|cx| {
ConversationEditor::new( ConversationEditor::new(
self.completion_provider.clone(), self.completion_provider.clone(),
self.languages.clone(), self.languages.clone(),
@ -854,8 +834,8 @@ impl AssistantPanel {
self.toolbar.update(cx, |toolbar, cx| { self.toolbar.update(cx, |toolbar, cx| {
toolbar.set_active_item(Some(&editor), cx); toolbar.set_active_item(Some(&editor), cx);
}); });
if self.has_focus(cx) { if self.focus_handle.contains_focused(cx) {
cx.focus(&editor); cx.focus_view(&editor);
} }
} else { } else {
self.toolbar.update(cx, |toolbar, cx| { self.toolbar.update(cx, |toolbar, cx| {
@ -891,31 +871,31 @@ impl AssistantPanel {
self.completion_provider.save_credentials(cx, credential); self.completion_provider.save_credentials(cx, credential);
self.api_key_editor.take(); self.api_key_editor.take();
cx.focus_self(); self.focus_handle.focus(cx);
cx.notify(); cx.notify();
} }
} else { } else {
cx.propagate_action(); cx.propagate();
} }
} }
fn reset_credentials(&mut self, _: &ResetKey, cx: &mut ViewContext<Self>) { fn reset_credentials(&mut self, _: &ResetKey, cx: &mut ViewContext<Self>) {
self.completion_provider.delete_credentials(cx); self.completion_provider.delete_credentials(cx);
self.api_key_editor = Some(build_api_key_editor(cx)); self.api_key_editor = Some(build_api_key_editor(cx));
cx.focus_self(); self.focus_handle.focus(cx);
cx.notify(); cx.notify();
} }
fn toggle_zoom(&mut self, _: &workspace::ToggleZoom, cx: &mut ViewContext<Self>) { fn toggle_zoom(&mut self, _: &workspace::ToggleZoom, cx: &mut ViewContext<Self>) {
if self.zoomed { if self.zoomed {
cx.emit(AssistantPanelEvent::ZoomOut) cx.emit(PanelEvent::ZoomOut)
} else { } else {
cx.emit(AssistantPanelEvent::ZoomIn) cx.emit(PanelEvent::ZoomIn)
} }
} }
fn deploy(&mut self, action: &search::buffer_search::Deploy, cx: &mut ViewContext<Self>) { fn deploy(&mut self, action: &search::buffer_search::Deploy, cx: &mut ViewContext<Self>) {
let mut propagate_action = true; let mut propagate = true;
if let Some(search_bar) = self.toolbar.read(cx).item_of_type::<BufferSearchBar>() { if let Some(search_bar) = self.toolbar.read(cx).item_of_type::<BufferSearchBar>() {
search_bar.update(cx, |search_bar, cx| { search_bar.update(cx, |search_bar, cx| {
if search_bar.show(cx) { if search_bar.show(cx) {
@ -924,12 +904,12 @@ impl AssistantPanel {
search_bar.select_query(cx); search_bar.select_query(cx);
cx.focus_self(); cx.focus_self();
} }
propagate_action = false propagate = false
} }
}); });
} }
if propagate_action { if propagate {
cx.propagate_action(); cx.propagate();
} }
} }
@ -942,7 +922,7 @@ impl AssistantPanel {
return; return;
} }
} }
cx.propagate_action(); cx.propagate();
} }
fn select_next_match(&mut self, _: &search::SelectNextMatch, cx: &mut ViewContext<Self>) { fn select_next_match(&mut self, _: &search::SelectNextMatch, cx: &mut ViewContext<Self>) {
@ -976,9 +956,9 @@ impl AssistantPanel {
fn render_editor_tools(&self, cx: &mut ViewContext<Self>) -> Vec<AnyElement> { fn render_editor_tools(&self, cx: &mut ViewContext<Self>) -> Vec<AnyElement> {
if self.active_editor().is_some() { if self.active_editor().is_some() {
vec![ vec![
Self::render_split_button(cx).into_any(), Self::render_split_button(cx).into_any_element(),
Self::render_quote_button(cx).into_any(), Self::render_quote_button(cx).into_any_element(),
Self::render_assist_button(cx).into_any(), Self::render_assist_button(cx).into_any_element(),
] ]
} else { } else {
Default::default() Default::default()
@ -1028,16 +1008,13 @@ impl AssistantPanel {
} }
fn render_zoom_button(&self, cx: &mut ViewContext<Self>) -> impl IntoElement { fn render_zoom_button(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
let zoomed = self.zoomed;
IconButton::new("zoom_button", Icon::Menu) IconButton::new("zoom_button", Icon::Menu)
.on_click(cx.listener(|this, _event, cx| { .on_click(cx.listener(|this, _event, cx| {
this.toggle_zoom(&ToggleZoom, cx); this.toggle_zoom(&ToggleZoom, cx);
})) }))
.tooltip(|cx| { .tooltip(move |cx| {
Tooltip::for_action( Tooltip::for_action(if zoomed { "Zoom Out" } else { "Zoom In" }, &ToggleZoom, cx)
if self.zoomed { "Zoom Out" } else { "Zoom In" },
&ToggleZoom,
cx,
)
}) })
} }
@ -1072,16 +1049,16 @@ impl AssistantPanel {
cx.spawn(|this, mut cx| async move { cx.spawn(|this, mut cx| async move {
let saved_conversation = fs.load(&path).await?; let saved_conversation = fs.load(&path).await?;
let saved_conversation = serde_json::from_str(&saved_conversation)?; let saved_conversation = serde_json::from_str(&saved_conversation)?;
let conversation = cx.add_model(|cx| { let conversation = cx.build_model(|cx| {
Conversation::deserialize(saved_conversation, path.clone(), languages, cx) Conversation::deserialize(saved_conversation, path.clone(), languages, cx)
}); })?;
this.update(&mut cx, |this, cx| { this.update(&mut cx, |this, cx| {
// If, by the time we've loaded the conversation, the user has already opened // If, by the time we've loaded the conversation, the user has already opened
// the same conversation, we don't want to open it again. // the same conversation, we don't want to open it again.
if let Some(ix) = this.editor_index_for_path(&path, cx) { if let Some(ix) = this.editor_index_for_path(&path, cx) {
this.set_active_editor_index(Some(ix), cx); this.set_active_editor_index(Some(ix), cx);
} else { } else {
let editor = cx.add_view(|cx| { let editor = cx.build_view(|cx| {
ConversationEditor::for_conversation(conversation, fs, workspace, cx) ConversationEditor::for_conversation(conversation, fs, workspace, cx)
}); });
this.add_conversation(editor, cx); this.add_conversation(editor, cx);
@ -1120,6 +1097,7 @@ impl Render for AssistantPanel {
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element { fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
if let Some(api_key_editor) = self.api_key_editor.clone() { if let Some(api_key_editor) = self.api_key_editor.clone() {
v_stack() v_stack()
.on_action(cx.listener(AssistantPanel::save_credentials))
.track_focus(&self.focus_handle) .track_focus(&self.focus_handle)
.child(Label::new( .child(Label::new(
"To use the assistant panel or inline assistant, you need to add your OpenAI api key.", "To use the assistant panel or inline assistant, you need to add your OpenAI api key.",
@ -1159,6 +1137,15 @@ impl Render for AssistantPanel {
} }
v_stack() v_stack()
.on_action(cx.listener(|this, _: &workspace::NewFile, cx| {
this.new_conversation(cx);
}))
.on_action(cx.listener(AssistantPanel::reset_credentials))
.on_action(cx.listener(AssistantPanel::toggle_zoom))
.on_action(cx.listener(AssistantPanel::deploy))
.on_action(cx.listener(AssistantPanel::select_next_match))
.on_action(cx.listener(AssistantPanel::select_prev_match))
.on_action(cx.listener(AssistantPanel::handle_editor_cancel))
.track_focus(&self.focus_handle) .track_focus(&self.focus_handle)
.child(header) .child(header)
.children(if self.toolbar.read(cx).hidden() { .children(if self.toolbar.read(cx).hidden() {
@ -1175,7 +1162,7 @@ impl Render for AssistantPanel {
self.saved_conversations.len(), self.saved_conversations.len(),
|this, range, cx| { |this, range, cx| {
range range
.map(|ix| this.render_saved_conversation(ix, cx).into_any()) .map(|ix| this.render_saved_conversation(ix, cx))
.collect() .collect()
}, },
) )
@ -1311,17 +1298,14 @@ impl Conversation {
completion_provider: Arc<dyn CompletionProvider>, completion_provider: Arc<dyn CompletionProvider>,
) -> Self { ) -> Self {
let markdown = language_registry.language_for_name("Markdown"); let markdown = language_registry.language_for_name("Markdown");
let buffer = cx.add_model(|cx| { let buffer = cx.build_model(|cx| {
let mut buffer = Buffer::new(0, cx.model_id() as u64, ""); let mut buffer = Buffer::new(0, cx.entity_id().as_u64(), "");
buffer.set_language_registry(language_registry); buffer.set_language_registry(language_registry);
cx.spawn_weak(|buffer, mut cx| async move { cx.spawn(|buffer, mut cx| async move {
let markdown = markdown.await?; let markdown = markdown.await?;
let buffer = buffer
.upgrade(&cx)
.ok_or_else(|| anyhow!("buffer was dropped"))?;
buffer.update(&mut cx, |buffer: &mut Buffer, cx| { buffer.update(&mut cx, |buffer: &mut Buffer, cx| {
buffer.set_language(Some(markdown), cx) buffer.set_language(Some(markdown), cx)
}); })?;
anyhow::Ok(()) anyhow::Ok(())
}) })
.detach_and_log_err(cx); .detach_and_log_err(cx);
@ -1409,8 +1393,8 @@ impl Conversation {
let markdown = language_registry.language_for_name("Markdown"); let markdown = language_registry.language_for_name("Markdown");
let mut message_anchors = Vec::new(); let mut message_anchors = Vec::new();
let mut next_message_id = MessageId(0); let mut next_message_id = MessageId(0);
let buffer = cx.add_model(|cx| { let buffer = cx.build_model(|cx| {
let mut buffer = Buffer::new(0, cx.model_id() as u64, saved_conversation.text); let mut buffer = Buffer::new(0, cx.entity_id().as_u64(), saved_conversation.text);
for message in saved_conversation.messages { for message in saved_conversation.messages {
message_anchors.push(MessageAnchor { message_anchors.push(MessageAnchor {
id: message.id, id: message.id,
@ -1419,14 +1403,11 @@ impl Conversation {
next_message_id = cmp::max(next_message_id, MessageId(message.id.0 + 1)); next_message_id = cmp::max(next_message_id, MessageId(message.id.0 + 1));
} }
buffer.set_language_registry(language_registry); buffer.set_language_registry(language_registry);
cx.spawn_weak(|buffer, mut cx| async move { cx.spawn(|buffer, mut cx| async move {
let markdown = markdown.await?; let markdown = markdown.await?;
let buffer = buffer
.upgrade(&cx)
.ok_or_else(|| anyhow!("buffer was dropped"))?;
buffer.update(&mut cx, |buffer: &mut Buffer, cx| { buffer.update(&mut cx, |buffer: &mut Buffer, cx| {
buffer.set_language(Some(markdown), cx) buffer.set_language(Some(markdown), cx)
}); })?;
anyhow::Ok(()) anyhow::Ok(())
}) })
.detach_and_log_err(cx); .detach_and_log_err(cx);
@ -1497,26 +1478,24 @@ impl Conversation {
}) })
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let model = self.model.clone(); let model = self.model.clone();
self.pending_token_count = cx.spawn_weak(|this, mut cx| { self.pending_token_count = cx.spawn(|this, mut cx| {
async move { async move {
cx.background_executor() cx.background_executor()
.timer(Duration::from_millis(200)) .timer(Duration::from_millis(200))
.await; .await;
let token_count = cx let token_count = cx
.background() .background_executor()
.spawn(async move { .spawn(async move {
tiktoken_rs::num_tokens_from_messages(&model.full_name(), &messages) tiktoken_rs::num_tokens_from_messages(&model.full_name(), &messages)
}) })
.await?; .await?;
this.upgrade(&cx) this.update(&mut cx, |this, cx| {
.ok_or_else(|| anyhow!("conversation was dropped"))?
.update(&mut cx, |this, cx| {
this.max_token_count = this.max_token_count =
tiktoken_rs::model::get_context_size(&this.model.full_name()); tiktoken_rs::model::get_context_size(&this.model.full_name());
this.token_count = Some(token_count); this.token_count = Some(token_count);
cx.notify() cx.notify()
}); })?;
anyhow::Ok(()) anyhow::Ok(())
} }
.log_err() .log_err()
@ -1603,7 +1582,7 @@ impl Conversation {
.unwrap(); .unwrap();
user_messages.push(user_message); user_messages.push(user_message);
let task = cx.spawn_weak({ let task = cx.spawn({
|this, mut cx| async move { |this, mut cx| async move {
let assistant_message_id = assistant_message.id; let assistant_message_id = assistant_message.id;
let stream_completion = async { let stream_completion = async {
@ -1612,9 +1591,7 @@ impl Conversation {
while let Some(message) = messages.next().await { while let Some(message) = messages.next().await {
let text = message?; let text = message?;
this.upgrade(&cx) this.update(&mut cx, |this, cx| {
.ok_or_else(|| anyhow!("conversation was dropped"))?
.update(&mut cx, |this, cx| {
let message_ix = this let message_ix = this
.message_anchors .message_anchors
.iter() .iter()
@ -1631,23 +1608,21 @@ impl Conversation {
cx.emit(ConversationEvent::StreamedCompletion); cx.emit(ConversationEvent::StreamedCompletion);
Some(()) Some(())
}); })?;
smol::future::yield_now().await; smol::future::yield_now().await;
} }
this.upgrade(&cx) this.update(&mut cx, |this, cx| {
.ok_or_else(|| anyhow!("conversation was dropped"))?
.update(&mut cx, |this, cx| {
this.pending_completions this.pending_completions
.retain(|completion| completion.id != this.completion_count); .retain(|completion| completion.id != this.completion_count);
this.summarize(cx); this.summarize(cx);
}); })?;
anyhow::Ok(()) anyhow::Ok(())
}; };
let result = stream_completion.await; let result = stream_completion.await;
if let Some(this) = this.upgrade(&cx) {
this.update(&mut cx, |this, cx| { this.update(&mut cx, |this, cx| {
if let Some(metadata) = if let Some(metadata) =
this.messages_metadata.get_mut(&assistant_message.id) this.messages_metadata.get_mut(&assistant_message.id)
@ -1663,8 +1638,8 @@ impl Conversation {
} }
cx.notify(); cx.notify();
} }
}); })
} .ok();
} }
}); });
@ -1999,10 +1974,10 @@ impl Conversation {
None None
}; };
(path, summary) (path, summary)
}); })?;
if let Some(summary) = summary { if let Some(summary) = summary {
let conversation = this.read_with(&cx, |this, cx| this.serialize(cx)); let conversation = this.read_with(&cx, |this, cx| this.serialize(cx))?;
let path = if let Some(old_path) = old_path { let path = if let Some(old_path) = old_path {
old_path old_path
} else { } else {
@ -2026,7 +2001,7 @@ impl Conversation {
fs.create_dir(CONVERSATIONS_DIR.as_ref()).await?; fs.create_dir(CONVERSATIONS_DIR.as_ref()).await?;
fs.atomic_write(path.clone(), serde_json::to_string(&conversation).unwrap()) fs.atomic_write(path.clone(), serde_json::to_string(&conversation).unwrap())
.await?; .await?;
this.update(&mut cx, |this, _| this.path = Some(path)); this.update(&mut cx, |this, _| this.path = Some(path))?;
} }
Ok(()) Ok(())
@ -2069,7 +2044,7 @@ impl ConversationEditor {
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
) -> Self { ) -> Self {
let conversation = let conversation =
cx.add_model(|cx| Conversation::new(language_registry, cx, completion_provider)); cx.build_model(|cx| Conversation::new(language_registry, cx, completion_provider));
Self::for_conversation(conversation, fs, workspace, cx) Self::for_conversation(conversation, fs, workspace, cx)
} }
@ -2079,7 +2054,7 @@ impl ConversationEditor {
workspace: WeakView<Workspace>, workspace: WeakView<Workspace>,
cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
) -> Self { ) -> Self {
let editor = cx.add_view(|cx| { let editor = cx.build_view(|cx| {
let mut editor = Editor::for_buffer(conversation.read(cx).buffer.clone(), None, cx); let mut editor = Editor::for_buffer(conversation.read(cx).buffer.clone(), None, cx);
editor.set_soft_wrap_mode(SoftWrap::EditorWidth, cx); editor.set_soft_wrap_mode(SoftWrap::EditorWidth, cx);
editor.set_show_gutter(false, cx); editor.set_show_gutter(false, cx);
@ -2093,7 +2068,7 @@ impl ConversationEditor {
cx.observe(&conversation, |_, _, cx| cx.notify()), cx.observe(&conversation, |_, _, cx| cx.notify()),
cx.subscribe(&conversation, Self::handle_conversation_event), cx.subscribe(&conversation, Self::handle_conversation_event),
cx.subscribe(&editor, Self::handle_editor_event), cx.subscribe(&editor, Self::handle_editor_event),
cx.on_focus(&focus_handle, |this, _, cx| cx.focus(&this.editor)), cx.on_focus(&focus_handle, |this, cx| cx.focus_view(&this.editor)),
]; ];
let mut this = Self { let mut this = Self {
@ -2155,7 +2130,7 @@ impl ConversationEditor {
.conversation .conversation
.update(cx, |conversation, _| conversation.cancel_last_assist()) .update(cx, |conversation, _| conversation.cancel_last_assist())
{ {
cx.propagate_action(); cx.propagate();
} }
} }
@ -2247,8 +2222,8 @@ impl ConversationEditor {
.anchor() .anchor()
.scroll_position(&snapshot.display_snapshot); .scroll_position(&snapshot.display_snapshot);
let scroll_bottom = scroll_position.y() + editor.visible_line_count().unwrap_or(0.); let scroll_bottom = scroll_position.y + editor.visible_line_count().unwrap_or(0.);
if (scroll_position.y()..scroll_bottom).contains(&cursor_row) { if (scroll_position.y..scroll_bottom).contains(&cursor_row) {
Some(ScrollPosition { Some(ScrollPosition {
cursor, cursor,
offset_before_cursor: point(scroll_position.x, cursor_row - scroll_position.y), offset_before_cursor: point(scroll_position.x, cursor_row - scroll_position.y),
@ -2286,7 +2261,7 @@ impl ConversationEditor {
}) })
.on_click({ .on_click({
let conversation = conversation.clone(); let conversation = conversation.clone();
move |_, _, cx| { move |_, cx| {
conversation.update(cx, |conversation, cx| { conversation.update(cx, |conversation, cx| {
conversation.cycle_message_roles( conversation.cycle_message_roles(
HashSet::from_iter(Some(message_id)), HashSet::from_iter(Some(message_id)),
@ -2302,8 +2277,7 @@ impl ConversationEditor {
.border_color(gpui::red()) .border_color(gpui::red())
.child(sender) .child(sender)
.child(Label::new(message.sent_at.format("%I:%M%P").to_string())) .child(Label::new(message.sent_at.format("%I:%M%P").to_string()))
.with_children( .children(if let MessageStatus::Error(error) = &message.status {
if let MessageStatus::Error(error) = &message.status {
Some( Some(
div() div()
.id("error") .id("error")
@ -2312,8 +2286,7 @@ impl ConversationEditor {
) )
} else { } else {
None None
}, })
)
.into_any_element() .into_any_element()
} }
}), }),
@ -2342,7 +2315,7 @@ impl ConversationEditor {
return; return;
}; };
let text = editor.read_with(cx, |editor, cx| { let editor = editor.read(cx);
let range = editor.selections.newest::<usize>(cx).range(); let range = editor.selections.newest::<usize>(cx).range();
let buffer = editor.buffer().read(cx).snapshot(cx); let buffer = editor.buffer().read(cx).snapshot(cx);
let start_language = buffer.language_at(range.start); let start_language = buffer.language_at(range.start);
@ -2355,7 +2328,7 @@ impl ConversationEditor {
let language_name = language_name.as_deref().unwrap_or("").to_lowercase(); let language_name = language_name.as_deref().unwrap_or("").to_lowercase();
let selected_text = buffer.text_for_range(range).collect::<String>(); let selected_text = buffer.text_for_range(range).collect::<String>();
if selected_text.is_empty() { let text = if selected_text.is_empty() {
None None
} else { } else {
Some(if language_name == "markdown" { Some(if language_name == "markdown" {
@ -2367,11 +2340,10 @@ impl ConversationEditor {
} else { } else {
format!("```{language_name}\n{selected_text}\n```") format!("```{language_name}\n{selected_text}\n```")
}) })
} };
});
// Activate the panel // Activate the panel
if !panel.read(cx).has_focus(cx) { if !panel.focus_handle(cx).contains_focused(cx) {
workspace.toggle_panel_focus::<AssistantPanel>(cx); workspace.toggle_panel_focus::<AssistantPanel>(cx);
} }
@ -2415,13 +2387,12 @@ impl ConversationEditor {
} }
if spanned_messages > 1 { if spanned_messages > 1 {
cx.platform() cx.write_to_clipboard(ClipboardItem::new(copied_text));
.write_to_clipboard(ClipboardItem::new(copied_text));
return; return;
} }
} }
cx.propagate_action(); cx.propagate();
} }
fn split(&mut self, _: &Split, cx: &mut ViewContext<Self>) { fn split(&mut self, _: &Split, cx: &mut ViewContext<Self>) {
@ -2492,7 +2463,16 @@ impl Render for ConversationEditor {
type Element = Div; type Element = Div;
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element { fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
div().relative().child(self.editor.clone()).child( div()
.relative()
.capture_action(cx.listener(ConversationEditor::cancel_last_assist))
.capture_action(cx.listener(ConversationEditor::save))
.capture_action(cx.listener(ConversationEditor::copy))
.capture_action(cx.listener(ConversationEditor::cycle_message_role))
.on_action(cx.listener(ConversationEditor::assist))
.on_action(cx.listener(ConversationEditor::split))
.child(self.editor.clone())
.child(
h_stack() h_stack()
.absolute() .absolute()
.gap_1() .gap_1()
@ -2504,6 +2484,12 @@ impl Render for ConversationEditor {
} }
} }
impl FocusableView for ConversationEditor {
fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
self.focus_handle.clone()
}
}
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
struct MessageAnchor { struct MessageAnchor {
id: MessageId, id: MessageId,
@ -2577,30 +2563,40 @@ impl Render for InlineAssistant {
fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element { fn render(&mut self, cx: &mut ViewContext<Self>) -> Self::Element {
let measurements = self.measurements.get(); let measurements = self.measurements.get();
h_stack() h_stack()
.on_action(cx.listener(Self::confirm))
.on_action(cx.listener(Self::cancel))
.on_action(cx.listener(Self::toggle_include_conversation))
.on_action(cx.listener(Self::toggle_retrieve_context))
.on_action(cx.listener(Self::move_up))
.on_action(cx.listener(Self::move_down))
.child( .child(
h_stack() h_stack()
.justify_center() .justify_center()
.w(measurements.gutter_width) .w(measurements.gutter_width)
.child( .child(
IconButton::new("include_conversation", Icon::Ai) IconButton::new("include_conversation", Icon::Ai)
.action(ToggleIncludeConversation) .action(Box::new(ToggleIncludeConversation))
.selected(self.include_conversation) .selected(self.include_conversation)
.tooltip(Tooltip::for_action( .tooltip(|cx| {
Tooltip::for_action(
"Include Conversation", "Include Conversation",
&ToggleIncludeConversation, &ToggleIncludeConversation,
cx, cx,
)), )
}),
) )
.children(if SemanticIndex::enabled(cx) { .children(if SemanticIndex::enabled(cx) {
Some( Some(
IconButton::new("retrieve_context", Icon::MagnifyingGlass) IconButton::new("retrieve_context", Icon::MagnifyingGlass)
.action(ToggleRetrieveContext) .action(Box::new(ToggleRetrieveContext))
.selected(self.retrieve_context) .selected(self.retrieve_context)
.tooltip(Tooltip::for_action( .tooltip(|cx| {
Tooltip::for_action(
"Retrieve Context", "Retrieve Context",
&ToggleRetrieveContext, &ToggleRetrieveContext,
cx, cx,
)), )
}),
) )
} else { } else {
None None
@ -2629,6 +2625,12 @@ impl Render for InlineAssistant {
} }
} }
impl FocusableView for InlineAssistant {
fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
self.focus_handle.clone()
}
}
impl InlineAssistant { impl InlineAssistant {
fn new( fn new(
id: usize, id: usize,
@ -2656,10 +2658,7 @@ impl InlineAssistant {
let mut subscriptions = vec![ let mut subscriptions = vec![
cx.observe(&codegen, Self::handle_codegen_changed), cx.observe(&codegen, Self::handle_codegen_changed),
cx.subscribe(&prompt_editor, Self::handle_prompt_editor_events), cx.subscribe(&prompt_editor, Self::handle_prompt_editor_events),
cx.on_focus( cx.on_focus(&focus_handle, |this, cx| cx.focus_view(&this.prompt_editor)),
&focus_handle,
cx.listener(|this, _, cx| cx.focus(&this.prompt_editor)),
),
]; ];
if let Some(semantic_index) = semantic_index.clone() { if let Some(semantic_index) = semantic_index.clone() {
@ -2939,42 +2938,17 @@ impl InlineAssistant {
div() div()
.id("update") .id("update")
.tooltip(|cx| Tooltip::text(status_text, cx)) .tooltip(|cx| Tooltip::text(status_text, cx))
.child(IconElement::new(Icon::Update).color(color)) .child(IconElement::new(Icon::Update).color(Color::Info))
.into_any_element() .into_any_element()
Svg::new("icons/update.svg")
.with_color(theme.assistant.inline.context_status.in_progress_icon.color)
.constrained()
.with_width(theme.assistant.inline.context_status.in_progress_icon.width)
.contained()
.with_style(theme.assistant.inline.context_status.in_progress_icon.container)
.with_tooltip::<ContextStatusIcon>(
self.id,
status_text,
None,
theme.tooltip.clone(),
cx,
)
.aligned()
.into_any(),
) )
} }
SemanticIndexStatus::Indexed {} => Some( SemanticIndexStatus::Indexed {} => Some(
Svg::new("icons/check.svg") div()
.with_color(theme.assistant.inline.context_status.complete_icon.color) .id("check")
.constrained() .tooltip(|cx| Tooltip::text("Index up to date", cx))
.with_width(theme.assistant.inline.context_status.complete_icon.width) .child(IconElement::new(Icon::Check).color(Color::Success))
.contained() .into_any_element()
.with_style(theme.assistant.inline.context_status.complete_icon.container)
.with_tooltip::<ContextStatusIcon>(
self.id,
"Index up to date",
None,
theme.tooltip.clone(),
cx,
)
.aligned()
.into_any(),
), ),
} }
} else { } else {
@ -3083,7 +3057,8 @@ mod tests {
let registry = Arc::new(LanguageRegistry::test()); let registry = Arc::new(LanguageRegistry::test());
let completion_provider = Arc::new(FakeCompletionProvider::new()); let completion_provider = Arc::new(FakeCompletionProvider::new());
let conversation = cx.add_model(|cx| Conversation::new(registry, cx, completion_provider)); let conversation =
cx.build_model(|cx| Conversation::new(registry, cx, completion_provider));
let buffer = conversation.read(cx).buffer.clone(); let buffer = conversation.read(cx).buffer.clone();
let message_1 = conversation.read(cx).message_anchors[0].clone(); let message_1 = conversation.read(cx).message_anchors[0].clone();
@ -3213,7 +3188,8 @@ mod tests {
let registry = Arc::new(LanguageRegistry::test()); let registry = Arc::new(LanguageRegistry::test());
let completion_provider = Arc::new(FakeCompletionProvider::new()); let completion_provider = Arc::new(FakeCompletionProvider::new());
let conversation = cx.add_model(|cx| Conversation::new(registry, cx, completion_provider)); let conversation =
cx.build_model(|cx| Conversation::new(registry, cx, completion_provider));
let buffer = conversation.read(cx).buffer.clone(); let buffer = conversation.read(cx).buffer.clone();
let message_1 = conversation.read(cx).message_anchors[0].clone(); let message_1 = conversation.read(cx).message_anchors[0].clone();
@ -3310,7 +3286,8 @@ mod tests {
init(cx); init(cx);
let registry = Arc::new(LanguageRegistry::test()); let registry = Arc::new(LanguageRegistry::test());
let completion_provider = Arc::new(FakeCompletionProvider::new()); let completion_provider = Arc::new(FakeCompletionProvider::new());
let conversation = cx.add_model(|cx| Conversation::new(registry, cx, completion_provider)); let conversation =
cx.build_model(|cx| Conversation::new(registry, cx, completion_provider));
let buffer = conversation.read(cx).buffer.clone(); let buffer = conversation.read(cx).buffer.clone();
let message_1 = conversation.read(cx).message_anchors[0].clone(); let message_1 = conversation.read(cx).message_anchors[0].clone();
@ -3394,7 +3371,7 @@ mod tests {
let registry = Arc::new(LanguageRegistry::test()); let registry = Arc::new(LanguageRegistry::test());
let completion_provider = Arc::new(FakeCompletionProvider::new()); let completion_provider = Arc::new(FakeCompletionProvider::new());
let conversation = let conversation =
cx.add_model(|cx| Conversation::new(registry.clone(), cx, completion_provider)); cx.build_model(|cx| Conversation::new(registry.clone(), cx, completion_provider));
let buffer = conversation.read(cx).buffer.clone(); let buffer = conversation.read(cx).buffer.clone();
let message_0 = conversation.read(cx).message_anchors[0].id; let message_0 = conversation.read(cx).message_anchors[0].id;
let message_1 = conversation.update(cx, |conversation, cx| { let message_1 = conversation.update(cx, |conversation, cx| {
@ -3427,7 +3404,7 @@ mod tests {
] ]
); );
let deserialized_conversation = cx.add_model(|cx| { let deserialized_conversation = cx.build_model(|cx| {
Conversation::deserialize( Conversation::deserialize(
conversation.read(cx).serialize(cx), conversation.read(cx).serialize(cx),
Default::default(), Default::default(),

View file

@ -1961,14 +1961,14 @@ impl Editor {
cx.notify(); cx.notify();
} }
// pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut ViewContext<Self>) { pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut ViewContext<Self>) {
// self.cursor_shape = cursor_shape; self.cursor_shape = cursor_shape;
// cx.notify(); cx.notify();
// } }
// pub fn set_collapse_matches(&mut self, collapse_matches: bool) { pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
// self.collapse_matches = collapse_matches; self.collapse_matches = collapse_matches;
// } }
pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> { pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
if self.collapse_matches { if self.collapse_matches {
@ -1977,56 +1977,47 @@ impl Editor {
range.clone() range.clone()
} }
// pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut ViewContext<Self>) { pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut ViewContext<Self>) {
// if self.display_map.read(cx).clip_at_line_ends != clip { if self.display_map.read(cx).clip_at_line_ends != clip {
// self.display_map self.display_map
// .update(cx, |map, _| map.clip_at_line_ends = clip); .update(cx, |map, _| map.clip_at_line_ends = clip);
// } }
// } }
// pub fn set_keymap_context_layer<Tag: 'static>( pub fn set_keymap_context_layer<Tag: 'static>(
// &mut self, &mut self,
// context: KeymapContext, context: KeyContext,
// cx: &mut ViewContext<Self>, cx: &mut ViewContext<Self>,
// ) { ) {
// self.keymap_context_layers self.keymap_context_layers
// .insert(TypeId::of::<Tag>(), context); .insert(TypeId::of::<Tag>(), context);
// cx.notify(); cx.notify();
// } }
// pub fn remove_keymap_context_layer<Tag: 'static>(&mut self, cx: &mut ViewContext<Self>) { pub fn remove_keymap_context_layer<Tag: 'static>(&mut self, cx: &mut ViewContext<Self>) {
// self.keymap_context_layers.remove(&TypeId::of::<Tag>()); self.keymap_context_layers.remove(&TypeId::of::<Tag>());
// cx.notify(); cx.notify();
// } }
// pub fn set_input_enabled(&mut self, input_enabled: bool) { pub fn set_input_enabled(&mut self, input_enabled: bool) {
// self.input_enabled = input_enabled; self.input_enabled = input_enabled;
// } }
// pub fn set_autoindent(&mut self, autoindent: bool) { pub fn set_autoindent(&mut self, autoindent: bool) {
// if autoindent { if autoindent {
// self.autoindent_mode = Some(AutoindentMode::EachLine); self.autoindent_mode = Some(AutoindentMode::EachLine);
// } else { } else {
// self.autoindent_mode = None; self.autoindent_mode = None;
// } }
// } }
// pub fn read_only(&self) -> bool { pub fn read_only(&self) -> bool {
// self.read_only self.read_only
// } }
// pub fn set_read_only(&mut self, read_only: bool) { pub fn set_read_only(&mut self, read_only: bool) {
// self.read_only = read_only; self.read_only = read_only;
// } }
// pub fn set_field_editor_style(
// &mut self,
// style: Option<Arc<GetFieldEditorTheme>>,
// cx: &mut ViewContext<Self>,
// ) {
// self.get_field_editor_theme = style;
// cx.notify();
// }
fn selections_did_change( fn selections_did_change(
&mut self, &mut self,

View file

@ -2816,3 +2816,9 @@ impl From<(&'static str, EntityId)> for ElementId {
ElementId::NamedInteger(name.into(), id.as_u64() as usize) ElementId::NamedInteger(name.into(), id.as_u64() as usize)
} }
} }
impl From<(&'static str, usize)> for ElementId {
fn from((name, id): (&'static str, usize)) -> Self {
ElementId::NamedInteger(name.into(), id)
}
}

View file

@ -44,12 +44,18 @@ impl<'a, T: ?Sized> From<&'a T> for ArcCow<'a, T> {
} }
} }
impl<T> From<Arc<T>> for ArcCow<'_, T> { impl<T: ?Sized> From<Arc<T>> for ArcCow<'_, T> {
fn from(s: Arc<T>) -> Self { fn from(s: Arc<T>) -> Self {
Self::Owned(s) Self::Owned(s)
} }
} }
impl<T: ?Sized> From<&'_ Arc<T>> for ArcCow<'_, T> {
fn from(s: &'_ Arc<T>) -> Self {
Self::Owned(s.clone())
}
}
impl From<String> for ArcCow<'_, str> { impl From<String> for ArcCow<'_, str> {
fn from(value: String) -> Self { fn from(value: String) -> Self {
Self::Owned(value.into()) Self::Owned(value.into())