WIP
This commit is contained in:
parent
e534c5fdcd
commit
d86da04584
5 changed files with 307 additions and 327 deletions
|
@ -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 {
|
||||||
|
|
|
@ -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(),
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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())
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue