parent
94ed0b7767
commit
fcadcbb510
5 changed files with 189 additions and 53 deletions
|
@ -1,3 +1,4 @@
|
||||||
|
use crate::context::{AssistantContext, ContextId};
|
||||||
use crate::thread::{
|
use crate::thread::{
|
||||||
LastRestoreCheckpoint, MessageId, MessageSegment, RequestKind, Thread, ThreadError,
|
LastRestoreCheckpoint, MessageId, MessageSegment, RequestKind, Thread, ThreadError,
|
||||||
ThreadEvent, ThreadFeedback,
|
ThreadEvent, ThreadFeedback,
|
||||||
|
@ -19,9 +20,12 @@ use gpui::{
|
||||||
use language::{Buffer, LanguageRegistry};
|
use language::{Buffer, LanguageRegistry};
|
||||||
use language_model::{LanguageModelRegistry, LanguageModelToolUseId, Role};
|
use language_model::{LanguageModelRegistry, LanguageModelToolUseId, Role};
|
||||||
use markdown::{Markdown, MarkdownStyle};
|
use markdown::{Markdown, MarkdownStyle};
|
||||||
|
use project::ProjectItem as _;
|
||||||
use settings::Settings as _;
|
use settings::Settings as _;
|
||||||
|
use std::rc::Rc;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
use text::ToPoint;
|
||||||
use theme::ThemeSettings;
|
use theme::ThemeSettings;
|
||||||
use ui::{prelude::*, Disclosure, IconButton, KeyBinding, Scrollbar, ScrollbarState, Tooltip};
|
use ui::{prelude::*, Disclosure, IconButton, KeyBinding, Scrollbar, ScrollbarState, Tooltip};
|
||||||
use util::ResultExt as _;
|
use util::ResultExt as _;
|
||||||
|
@ -778,6 +782,9 @@ impl ActiveThread {
|
||||||
return Empty.into_any();
|
return Empty.into_any();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let context_store = self.context_store.clone();
|
||||||
|
let workspace = self.workspace.clone();
|
||||||
|
|
||||||
let thread = self.thread.read(cx);
|
let thread = self.thread.read(cx);
|
||||||
// Get all the data we need from thread before we start using it in closures
|
// Get all the data we need from thread before we start using it in closures
|
||||||
let checkpoint = thread.checkpoint_for_message(message_id);
|
let checkpoint = thread.checkpoint_for_message(message_id);
|
||||||
|
@ -901,36 +908,53 @@ impl ActiveThread {
|
||||||
.into_any_element(),
|
.into_any_element(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let message_content = v_flex()
|
let message_content =
|
||||||
.gap_1p5()
|
v_flex()
|
||||||
.child(
|
.gap_1p5()
|
||||||
if let Some(edit_message_editor) = edit_message_editor.clone() {
|
.child(
|
||||||
div()
|
if let Some(edit_message_editor) = edit_message_editor.clone() {
|
||||||
.key_context("EditMessageEditor")
|
div()
|
||||||
.on_action(cx.listener(Self::cancel_editing_message))
|
.key_context("EditMessageEditor")
|
||||||
.on_action(cx.listener(Self::confirm_editing_message))
|
.on_action(cx.listener(Self::cancel_editing_message))
|
||||||
.min_h_6()
|
.on_action(cx.listener(Self::confirm_editing_message))
|
||||||
.child(edit_message_editor)
|
.min_h_6()
|
||||||
} else {
|
.child(edit_message_editor)
|
||||||
div()
|
} else {
|
||||||
.min_h_6()
|
div()
|
||||||
.text_ui(cx)
|
.min_h_6()
|
||||||
.child(self.render_message_content(message_id, rendered_message, cx))
|
.text_ui(cx)
|
||||||
},
|
.child(self.render_message_content(message_id, rendered_message, cx))
|
||||||
)
|
},
|
||||||
.when_some(context, |parent, context| {
|
)
|
||||||
if !context.is_empty() {
|
.when_some(context, |parent, context| {
|
||||||
parent.child(
|
if !context.is_empty() {
|
||||||
h_flex().flex_wrap().gap_1().children(
|
parent.child(h_flex().flex_wrap().gap_1().children(
|
||||||
context
|
context.into_iter().map(|context| {
|
||||||
.into_iter()
|
let context_id = context.id;
|
||||||
.map(|context| ContextPill::added(context, false, false, None)),
|
ContextPill::added(context, false, false, None).on_click(Rc::new(
|
||||||
),
|
cx.listener({
|
||||||
)
|
let workspace = workspace.clone();
|
||||||
} else {
|
let context_store = context_store.clone();
|
||||||
parent
|
move |_, _, window, cx| {
|
||||||
}
|
if let Some(workspace) = workspace.upgrade() {
|
||||||
});
|
open_context(
|
||||||
|
context_id,
|
||||||
|
context_store.clone(),
|
||||||
|
workspace,
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
cx.notify();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
))
|
||||||
|
}),
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
parent
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
let styled_message = match message.role {
|
let styled_message = match message.role {
|
||||||
Role::User => v_flex()
|
Role::User => v_flex()
|
||||||
|
@ -1823,3 +1847,93 @@ impl Render for ActiveThread {
|
||||||
.child(self.render_vertical_scrollbar(cx))
|
.child(self.render_vertical_scrollbar(cx))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn open_context(
|
||||||
|
id: ContextId,
|
||||||
|
context_store: Entity<ContextStore>,
|
||||||
|
workspace: Entity<Workspace>,
|
||||||
|
window: &mut Window,
|
||||||
|
cx: &mut App,
|
||||||
|
) {
|
||||||
|
let Some(context) = context_store.read(cx).context_for_id(id) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
match context {
|
||||||
|
AssistantContext::File(file_context) => {
|
||||||
|
if let Some(project_path) = file_context.context_buffer.buffer.read(cx).project_path(cx)
|
||||||
|
{
|
||||||
|
workspace.update(cx, |workspace, cx| {
|
||||||
|
workspace
|
||||||
|
.open_path(project_path, None, true, window, cx)
|
||||||
|
.detach_and_log_err(cx);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AssistantContext::Directory(directory_context) => {
|
||||||
|
let path = directory_context.path.clone();
|
||||||
|
workspace.update(cx, |workspace, cx| {
|
||||||
|
workspace.project().update(cx, |project, cx| {
|
||||||
|
if let Some(entry) = project.entry_for_path(&path, cx) {
|
||||||
|
cx.emit(project::Event::RevealInProjectPanel(entry.id));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
AssistantContext::Symbol(symbol_context) => {
|
||||||
|
if let Some(project_path) = symbol_context
|
||||||
|
.context_symbol
|
||||||
|
.buffer
|
||||||
|
.read(cx)
|
||||||
|
.project_path(cx)
|
||||||
|
{
|
||||||
|
let snapshot = symbol_context.context_symbol.buffer.read(cx).snapshot();
|
||||||
|
let target_position = symbol_context
|
||||||
|
.context_symbol
|
||||||
|
.id
|
||||||
|
.range
|
||||||
|
.start
|
||||||
|
.to_point(&snapshot);
|
||||||
|
|
||||||
|
let open_task = workspace.update(cx, |workspace, cx| {
|
||||||
|
workspace.open_path(project_path, None, true, window, cx)
|
||||||
|
});
|
||||||
|
window
|
||||||
|
.spawn(cx, async move |cx| {
|
||||||
|
if let Some(active_editor) = open_task
|
||||||
|
.await
|
||||||
|
.log_err()
|
||||||
|
.and_then(|item| item.downcast::<Editor>())
|
||||||
|
{
|
||||||
|
active_editor
|
||||||
|
.downgrade()
|
||||||
|
.update_in(cx, |editor, window, cx| {
|
||||||
|
editor.go_to_singleton_buffer_point(
|
||||||
|
target_position,
|
||||||
|
window,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.log_err();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
AssistantContext::FetchedUrl(fetched_url_context) => {
|
||||||
|
cx.open_url(&fetched_url_context.url);
|
||||||
|
}
|
||||||
|
AssistantContext::Thread(thread_context) => {
|
||||||
|
let thread_id = thread_context.thread.read(cx).id().clone();
|
||||||
|
workspace.update(cx, |workspace, cx| {
|
||||||
|
if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {
|
||||||
|
panel.update(cx, |panel, cx| {
|
||||||
|
panel
|
||||||
|
.open_thread(&thread_id, window, cx)
|
||||||
|
.detach_and_log_err(cx)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
use std::rc::Rc;
|
use std::ops::Range;
|
||||||
use std::{ops::Range, path::Path};
|
|
||||||
|
|
||||||
use file_icons::FileIcons;
|
use file_icons::FileIcons;
|
||||||
use gpui::{App, Entity, SharedString};
|
use gpui::{App, Entity, SharedString};
|
||||||
|
@ -85,7 +84,7 @@ pub struct FileContext {
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct DirectoryContext {
|
pub struct DirectoryContext {
|
||||||
pub path: Rc<Path>,
|
pub path: ProjectPath,
|
||||||
pub context_buffers: Vec<ContextBuffer>,
|
pub context_buffers: Vec<ContextBuffer>,
|
||||||
pub snapshot: ContextSnapshot,
|
pub snapshot: ContextSnapshot,
|
||||||
}
|
}
|
||||||
|
@ -185,17 +184,18 @@ impl FileContext {
|
||||||
impl DirectoryContext {
|
impl DirectoryContext {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
id: ContextId,
|
id: ContextId,
|
||||||
path: &Path,
|
project_path: ProjectPath,
|
||||||
context_buffers: Vec<ContextBuffer>,
|
context_buffers: Vec<ContextBuffer>,
|
||||||
) -> DirectoryContext {
|
) -> DirectoryContext {
|
||||||
let full_path: SharedString = path.to_string_lossy().into_owned().into();
|
let full_path: SharedString = project_path.path.to_string_lossy().into_owned().into();
|
||||||
|
|
||||||
let name = match path.file_name() {
|
let name = match project_path.path.file_name() {
|
||||||
Some(name) => name.to_string_lossy().into_owned().into(),
|
Some(name) => name.to_string_lossy().into_owned().into(),
|
||||||
None => full_path.clone(),
|
None => full_path.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let parent = path
|
let parent = project_path
|
||||||
|
.path
|
||||||
.parent()
|
.parent()
|
||||||
.and_then(|p| p.file_name())
|
.and_then(|p| p.file_name())
|
||||||
.map(|p| p.to_string_lossy().into_owned().into());
|
.map(|p| p.to_string_lossy().into_owned().into());
|
||||||
|
@ -208,7 +208,7 @@ impl DirectoryContext {
|
||||||
.into();
|
.into();
|
||||||
|
|
||||||
DirectoryContext {
|
DirectoryContext {
|
||||||
path: path.into(),
|
path: project_path,
|
||||||
context_buffers,
|
context_buffers,
|
||||||
snapshot: ContextSnapshot {
|
snapshot: ContextSnapshot {
|
||||||
id,
|
id,
|
||||||
|
|
|
@ -60,6 +60,10 @@ impl ContextStore {
|
||||||
&self.context
|
&self.context
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn context_for_id(&self, id: ContextId) -> Option<&AssistantContext> {
|
||||||
|
self.context().iter().find(|context| context.id() == id)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn clear(&mut self) {
|
pub fn clear(&mut self) {
|
||||||
self.context.clear();
|
self.context.clear();
|
||||||
self.files.clear();
|
self.files.clear();
|
||||||
|
@ -253,21 +257,21 @@ impl ContextStore {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.update(cx, |this, _| {
|
this.update(cx, |this, _| {
|
||||||
this.insert_directory(&project_path.path, context_buffers);
|
this.insert_directory(project_path, context_buffers);
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
anyhow::Ok(())
|
anyhow::Ok(())
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn insert_directory(&mut self, path: &Path, context_buffers: Vec<ContextBuffer>) {
|
fn insert_directory(&mut self, project_path: ProjectPath, context_buffers: Vec<ContextBuffer>) {
|
||||||
let id = self.next_context_id.post_inc();
|
let id = self.next_context_id.post_inc();
|
||||||
self.directories.insert(path.to_path_buf(), id);
|
self.directories.insert(project_path.path.to_path_buf(), id);
|
||||||
|
|
||||||
self.context
|
self.context
|
||||||
.push(AssistantContext::Directory(DirectoryContext::new(
|
.push(AssistantContext::Directory(DirectoryContext::new(
|
||||||
id,
|
id,
|
||||||
path,
|
project_path,
|
||||||
context_buffers,
|
context_buffers,
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
@ -704,8 +708,9 @@ pub fn refresh_context_store_text(
|
||||||
|| changed_buffers.iter().any(|buffer| {
|
|| changed_buffers.iter().any(|buffer| {
|
||||||
let buffer = buffer.read(cx);
|
let buffer = buffer.read(cx);
|
||||||
|
|
||||||
buffer_path_log_err(&buffer)
|
buffer_path_log_err(&buffer).map_or(false, |path| {
|
||||||
.map_or(false, |path| path.starts_with(&directory_context.path))
|
path.starts_with(&directory_context.path.path)
|
||||||
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
if should_refresh {
|
if should_refresh {
|
||||||
|
@ -797,7 +802,7 @@ fn refresh_directory_text(
|
||||||
let context_buffers = context_buffers.await;
|
let context_buffers = context_buffers.await;
|
||||||
context_store
|
context_store
|
||||||
.update(cx, |context_store, _| {
|
.update(cx, |context_store, _| {
|
||||||
let new_directory_context = DirectoryContext::new(id, &path, context_buffers);
|
let new_directory_context = DirectoryContext::new(id, path, context_buffers);
|
||||||
context_store.replace_context(AssistantContext::Directory(new_directory_context));
|
context_store.replace_context(AssistantContext::Directory(new_directory_context));
|
||||||
})
|
})
|
||||||
.ok();
|
.ok();
|
||||||
|
|
|
@ -4,15 +4,15 @@ use collections::HashSet;
|
||||||
use editor::Editor;
|
use editor::Editor;
|
||||||
use file_icons::FileIcons;
|
use file_icons::FileIcons;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
App, Bounds, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, Subscription,
|
App, Bounds, ClickEvent, DismissEvent, Entity, EventEmitter, FocusHandle, Focusable,
|
||||||
WeakEntity,
|
Subscription, WeakEntity,
|
||||||
};
|
};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use language::Buffer;
|
use language::Buffer;
|
||||||
use ui::{prelude::*, KeyBinding, PopoverMenu, PopoverMenuHandle, Tooltip};
|
use ui::{prelude::*, KeyBinding, PopoverMenu, PopoverMenuHandle, Tooltip};
|
||||||
use workspace::{notifications::NotifyResultExt, Workspace};
|
use workspace::{notifications::NotifyResultExt, Workspace};
|
||||||
|
|
||||||
use crate::context::ContextKind;
|
use crate::context::{ContextId, ContextKind};
|
||||||
use crate::context_picker::{ConfirmBehavior, ContextPicker};
|
use crate::context_picker::{ConfirmBehavior, ContextPicker};
|
||||||
use crate::context_store::ContextStore;
|
use crate::context_store::ContextStore;
|
||||||
use crate::thread::Thread;
|
use crate::thread::Thread;
|
||||||
|
@ -277,6 +277,14 @@ impl ContextStrip {
|
||||||
best.map(|(index, _, _)| index)
|
best.map(|(index, _, _)| index)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn open_context(&mut self, id: ContextId, window: &mut Window, cx: &mut App) {
|
||||||
|
let Some(workspace) = self.workspace.upgrade() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
crate::active_thread::open_context(id, self.context_store.clone(), workspace, window, cx);
|
||||||
|
}
|
||||||
|
|
||||||
fn remove_focused_context(
|
fn remove_focused_context(
|
||||||
&mut self,
|
&mut self,
|
||||||
_: &RemoveFocusedContext,
|
_: &RemoveFocusedContext,
|
||||||
|
@ -458,6 +466,7 @@ impl Render for ContextStrip {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.children(context.iter().enumerate().map(|(i, context)| {
|
.children(context.iter().enumerate().map(|(i, context)| {
|
||||||
|
let id = context.id;
|
||||||
ContextPill::added(
|
ContextPill::added(
|
||||||
context.clone(),
|
context.clone(),
|
||||||
dupe_names.contains(&context.name),
|
dupe_names.contains(&context.name),
|
||||||
|
@ -473,10 +482,16 @@ impl Render for ContextStrip {
|
||||||
}))
|
}))
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.on_click(Rc::new(cx.listener(move |this, _, _window, cx| {
|
.on_click(Rc::new(cx.listener(
|
||||||
this.focused_index = Some(i);
|
move |this, event: &ClickEvent, window, cx| {
|
||||||
cx.notify();
|
if event.down.click_count > 1 {
|
||||||
})))
|
this.open_context(id, window, cx);
|
||||||
|
} else {
|
||||||
|
this.focused_index = Some(i);
|
||||||
|
}
|
||||||
|
cx.notify();
|
||||||
|
},
|
||||||
|
)))
|
||||||
}))
|
}))
|
||||||
.when_some(suggested_context, |el, suggested| {
|
.when_some(suggested_context, |el, suggested| {
|
||||||
el.child(
|
el.child(
|
||||||
|
|
|
@ -162,7 +162,9 @@ impl RenderOnce for ContextPill {
|
||||||
})
|
})
|
||||||
.when_some(on_click.as_ref(), |element, on_click| {
|
.when_some(on_click.as_ref(), |element, on_click| {
|
||||||
let on_click = on_click.clone();
|
let on_click = on_click.clone();
|
||||||
element.on_click(move |event, window, cx| on_click(event, window, cx))
|
element
|
||||||
|
.cursor_pointer()
|
||||||
|
.on_click(move |event, window, cx| on_click(event, window, cx))
|
||||||
}),
|
}),
|
||||||
ContextPill::Suggested {
|
ContextPill::Suggested {
|
||||||
name,
|
name,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue