Cargo.lock 🔗
@@ -469,6 +469,7 @@ dependencies = [
"db",
"editor",
"feature_flags",
+ "file_icons",
"fs",
"futures 0.3.31",
"fuzzy",
Agus Zubiaga , Nathan , and Michael created
https://github.com/user-attachments/assets/d3d6f5f1-23ec-449b-a762-9869b9d4b5a5
Release Notes:
- N/A
---------
Co-authored-by: Nathan <nathan@zed.dev>
Co-authored-by: Michael <michael@zed.dev>
Cargo.lock | 1
crates/assistant2/Cargo.toml | 7
crates/assistant2/src/context.rs | 19 +++
crates/assistant2/src/context_picker/file_context_picker.rs | 6 +
crates/assistant2/src/context_store.rs | 1
crates/assistant2/src/context_strip.rs | 19 +++
crates/assistant2/src/ui/context_pill.rs | 42 ++++--
7 files changed, 78 insertions(+), 17 deletions(-)
@@ -469,6 +469,7 @@ dependencies = [
"db",
"editor",
"feature_flags",
+ "file_icons",
"fs",
"futures 0.3.31",
"fuzzy",
@@ -18,15 +18,16 @@ anyhow.workspace = true
assets.workspace = true
assistant_tool.workspace = true
async-watch.workspace = true
+chrono.workspace = true
client.workspace = true
clock.workspace = true
-chrono.workspace = true
collections.workspace = true
command_palette_hooks.workspace = true
context_server.workspace = true
db.workspace = true
editor.workspace = true
feature_flags.workspace = true
+file_icons.workspace = true
fs.workspace = true
futures.workspace = true
fuzzy.workspace = true
@@ -47,8 +48,8 @@ multi_buffer.workspace = true
ollama = { workspace = true, features = ["schemars"] }
open_ai = { workspace = true, features = ["schemars"] }
ordered-float.workspace = true
-paths.workspace = true
parking_lot.workspace = true
+paths.workspace = true
picker.workspace = true
project.workspace = true
proto.workspace = true
@@ -61,9 +62,9 @@ settings.workspace = true
similar.workspace = true
smol.workspace = true
telemetry_events.workspace = true
+terminal.workspace = true
terminal_view.workspace = true
text.workspace = true
-terminal.workspace = true
theme.workspace = true
time.workspace = true
time_format.workspace = true
@@ -2,11 +2,13 @@ use std::path::Path;
use std::rc::Rc;
use std::sync::Arc;
+use file_icons::FileIcons;
use gpui::{AppContext, Model, SharedString};
use language::Buffer;
use language_model::{LanguageModelRequestMessage, MessageContent};
use serde::{Deserialize, Serialize};
use text::BufferId;
+use ui::IconName;
use util::post_inc;
use crate::thread::Thread;
@@ -27,6 +29,7 @@ pub struct ContextSnapshot {
pub name: SharedString,
pub parent: Option<SharedString>,
pub tooltip: Option<SharedString>,
+ pub icon_path: Option<SharedString>,
pub kind: ContextKind,
/// Concatenating these strings yields text to send to the model. Not refreshed by `snapshot`.
pub text: Box<[SharedString]>,
@@ -40,6 +43,17 @@ pub enum ContextKind {
Thread,
}
+impl ContextKind {
+ pub fn icon(&self) -> IconName {
+ match self {
+ ContextKind::File => IconName::File,
+ ContextKind::Directory => IconName::Folder,
+ ContextKind::FetchedUrl => IconName::Globe,
+ ContextKind::Thread => IconName::MessageCircle,
+ }
+ }
+}
+
#[derive(Debug)]
pub enum Context {
File(FileContext),
@@ -138,11 +152,14 @@ impl FileContext {
.and_then(|p| p.file_name())
.map(|p| p.to_string_lossy().into_owned().into());
+ let icon_path = FileIcons::get_icon(&path, cx);
+
Some(ContextSnapshot {
id: self.id,
name,
parent,
tooltip: Some(full_path),
+ icon_path,
kind: ContextKind::File,
text: Box::new([self.buffer.text.clone()]),
})
@@ -162,6 +179,7 @@ impl FetchedUrlContext {
name: self.url.clone(),
parent: None,
tooltip: None,
+ icon_path: None,
kind: ContextKind::FetchedUrl,
text: Box::new([self.text.clone()]),
}
@@ -176,6 +194,7 @@ impl ThreadContext {
name: thread.summary().unwrap_or("New thread".into()),
parent: None,
tooltip: None,
+ icon_path: None,
kind: ContextKind::Thread,
text: Box::new([self.text.clone()]),
}
@@ -2,6 +2,7 @@ use std::path::Path;
use std::sync::atomic::AtomicBool;
use std::sync::Arc;
+use file_icons::FileIcons;
use fuzzy::PathMatch;
use gpui::{AppContext, DismissEvent, FocusHandle, FocusableView, Task, View, WeakModel, WeakView};
use picker::{Picker, PickerDelegate};
@@ -281,6 +282,10 @@ impl PickerDelegate for FileContextPickerDelegate {
.will_include_file_path(&path_match.path, cx)
});
+ let file_icon = FileIcons::get_icon(&path_match.path.clone(), cx)
+ .map(Icon::from_path)
+ .unwrap_or_else(|| Icon::new(IconName::File));
+
Some(
ListItem::new(ix)
.inset(true)
@@ -288,6 +293,7 @@ impl PickerDelegate for FileContextPickerDelegate {
.child(
h_flex()
.gap_2()
+ .child(file_icon.size(IconSize::Small))
.child(Label::new(file_name))
.children(directory.map(|directory| {
Label::new(directory)
@@ -273,6 +273,7 @@ impl ContextStore {
name,
parent,
tooltip: Some(full_path),
+ icon_path: None,
kind: ContextKind::Directory,
text,
},
@@ -3,6 +3,7 @@ use std::rc::Rc;
use anyhow::Result;
use collections::HashSet;
use editor::Editor;
+use file_icons::FileIcons;
use gpui::{
DismissEvent, EventEmitter, FocusHandle, Model, ModelContext, Subscription, Task, View,
WeakModel, WeakView,
@@ -95,9 +96,12 @@ impl ContextStrip {
None => path.to_string_lossy().into_owned().into(),
};
+ let icon_path = FileIcons::get_icon(path, cx);
+
Some(SuggestedContext::File {
name,
buffer: active_buffer_model.downgrade(),
+ icon_path,
})
}
@@ -228,6 +232,7 @@ impl Render for ContextStrip {
.when_some(suggested_context, |el, suggested| {
el.child(ContextPill::new_suggested(
suggested.name().clone(),
+ suggested.icon_path(),
suggested.kind(),
{
let context_store = self.context_store.clone();
@@ -304,6 +309,7 @@ pub enum SuggestContextKind {
pub enum SuggestedContext {
File {
name: SharedString,
+ icon_path: Option<SharedString>,
buffer: WeakModel<Buffer>,
},
Thread {
@@ -320,13 +326,24 @@ impl SuggestedContext {
}
}
+ pub fn icon_path(&self) -> Option<SharedString> {
+ match self {
+ Self::File { icon_path, .. } => icon_path.clone(),
+ Self::Thread { .. } => None,
+ }
+ }
+
pub fn accept(
&self,
context_store: &mut ContextStore,
cx: &mut ModelContext<ContextStore>,
) -> Task<Result<()>> {
match self {
- Self::File { buffer, name: _ } => {
+ Self::File {
+ buffer,
+ icon_path: _,
+ name: _,
+ } => {
if let Some(buffer) = buffer.upgrade() {
return context_store.add_file_from_buffer(buffer, cx);
};
@@ -14,6 +14,7 @@ pub enum ContextPill {
},
Suggested {
name: SharedString,
+ icon_path: Option<SharedString>,
kind: ContextKind,
on_add: Rc<dyn Fn(&ClickEvent, &mut WindowContext)>,
},
@@ -34,10 +35,16 @@ impl ContextPill {
pub fn new_suggested(
name: SharedString,
+ icon_path: Option<SharedString>,
kind: ContextKind,
on_add: Rc<dyn Fn(&ClickEvent, &mut WindowContext)>,
) -> Self {
- Self::Suggested { name, kind, on_add }
+ Self::Suggested {
+ name,
+ icon_path,
+ kind,
+ on_add,
+ }
}
pub fn id(&self) -> ElementId {
@@ -49,23 +56,27 @@ impl ContextPill {
}
}
- pub fn kind(&self) -> ContextKind {
+ pub fn icon(&self) -> Icon {
match self {
- Self::Added { context, .. } => context.kind,
- Self::Suggested { kind, .. } => *kind,
+ Self::Added { context, .. } => match &context.icon_path {
+ Some(icon_path) => Icon::from_path(icon_path),
+ None => Icon::new(context.kind.icon()),
+ },
+ Self::Suggested {
+ icon_path: Some(icon_path),
+ ..
+ } => Icon::from_path(icon_path),
+ Self::Suggested {
+ kind,
+ icon_path: None,
+ ..
+ } => Icon::new(kind.icon()),
}
}
}
impl RenderOnce for ContextPill {
fn render(self, cx: &mut WindowContext) -> impl IntoElement {
- let icon = match &self.kind() {
- ContextKind::File => IconName::File,
- ContextKind::Directory => IconName::Folder,
- ContextKind::FetchedUrl => IconName::Globe,
- ContextKind::Thread => IconName::MessageCircle,
- };
-
let color = cx.theme().colors();
let base_pill = h_flex()
@@ -75,7 +86,7 @@ impl RenderOnce for ContextPill {
.border_1()
.rounded_md()
.gap_1()
- .child(Icon::new(icon).size(IconSize::XSmall).color(Color::Muted));
+ .child(self.icon().size(IconSize::XSmall).color(Color::Muted));
match &self {
ContextPill::Added {
@@ -118,7 +129,12 @@ impl RenderOnce for ContextPill {
}),
)
}),
- ContextPill::Suggested { name, kind, on_add } => base_pill
+ ContextPill::Suggested {
+ name,
+ icon_path: _,
+ kind,
+ on_add,
+ } => base_pill
.cursor_pointer()
.pr_1()
.border_color(color.border_variant.opacity(0.5))