use anyhow::Result;use collections::{HashMap, HashSet};use editor::{CompletionProvider, SelectionEffects};use editor::{CurrentLineHighlight, Editor, EditorElement, EditorEvent, EditorStyle, actions::Tab};use gpui::{ App, Bounds, DEFAULT_ADDITIONAL_WINDOW_SIZE, Entity, EventEmitter, Focusable, PromptLevel, Subscription, Task, TextStyle, Tiling, TitlebarOptions, WindowBounds, WindowHandle, WindowOptions, actions, point, size, transparent_black,};use language::{Buffer, LanguageRegistry, language_settings::SoftWrap};use language_model::{ ConfiguredModel, LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage, Role,};use picker::{Picker, PickerDelegate};use platform_title_bar::PlatformTitleBar;use release_channel::ReleaseChannel;use rope::Rope;use settings::Settings;use std::rc::Rc;use std::sync::Arc;use std::sync::atomic::AtomicBool;use std::time::Duration;use theme::ThemeSettings;use ui::{Divider, ListItem, ListItemSpacing, ListSubHeader, Tooltip, prelude::*};use ui_input::ErasedEditor;use util::{ResultExt, TryFutureExt};use workspace::{MultiWorkspace, Workspace, WorkspaceSettings, client_side_decorations};use zed_actions::assistant::InlineAssist; use prompt_store::*; pub fn init(cx: &mut App) { prompt_store::init(cx);} actions!( rules_library, [ /// Creates a new rule in the rules library. NewRule, /// Deletes the selected rule. DeleteRule, /// Duplicates the selected rule. DuplicateRule, /// Toggles whether the selected rule is a default rule. ToggleDefaultRule, /// Restores a built-in rule to its default content. RestoreDefaultContent ]); pub trait InlineAssistDelegate { fn assist( &self, prompt_editor: &Entity<Editor>, initial_prompt: Option<String>, window: &mut Window, cx: &mut Context<RulesLibrary>, ); /// Returns whether the Agent panel was focused. fn focus_agent_panel( &self, workspace: &mut Workspace, window: &mut Window, cx: &mut Context<Workspace>, ) -> bool;} /// This function opens a new rules library window if one doesn't exist already./// If one exists, it brings it to the foreground.////// Note that, when opening a new window, this waits for the PromptStore to be/// initialized. If it was initialized successfully, it returns a window handle/// to a rules library.pub fn open_rules_library( language_registry: Arc<LanguageRegistry>, inline_assist_delegate: Box<dyn InlineAssistDelegate>, make_completion_provider: Rc<dyn Fn() -> Rc<dyn CompletionProvider>>, prompt_to_select: Option<PromptId>, cx: &mut App,) -> Task<Result<WindowHandle<RulesLibrary>>> { let store = PromptStore::global(cx); cx.spawn(async move |cx| { // We query windows in spawn so that all windows have been returned to GPUI let existing_window = cx.update(|cx| { let existing_window = cx .windows() .into_iter() .find_map(|window| window.downcast::<RulesLibrary>()); if let Some(existing_window) = existing_window { existing_window .update(cx, |rules_library, window, cx| { if let Some(prompt_to_select) = prompt_to_select { rules_library.load_rule(prompt_to_select, true, window, cx); } window.activate_window() }) .ok(); Some(existing_window) } else { None } }); if let Some(existing_window) = existing_window { return Ok(existing_window); } let store = store.await?; cx.update(|cx| { let app_id = ReleaseChannel::global(cx).app_id(); let bounds = Bounds::centered(None, size(px(1024.0), px(768.0)), cx); let window_decorations = match std::env::var("ZED_WINDOW_DECORATIONS") { Ok(val) if val == "server" => gpui::WindowDecorations::Server, Ok(val) if val == "client" => gpui::WindowDecorations::Client, _ => match WorkspaceSettings::get_global(cx).window_decorations { settings::WindowDecorations::Server => gpui::WindowDecorations::Server, settings::WindowDecorations::Client => gpui::WindowDecorations::Client, }, }; cx.open_window( WindowOptions { titlebar: Some(TitlebarOptions { title: Some("Rules Library".into()), appears_transparent: true, traffic_light_position: Some(point(px(12.0), px(12.0))), }), app_id: Some(app_id.to_owned()), window_bounds: Some(WindowBounds::Windowed(bounds)), window_background: cx.theme().window_background_appearance(), window_decorations: Some(window_decorations), window_min_size: Some(DEFAULT_ADDITIONAL_WINDOW_SIZE), kind: gpui::WindowKind::Floating, ..Default::default() }, |window, cx| { cx.new(|cx| { RulesLibrary::new( store, language_registry, inline_assist_delegate, make_completion_provider, prompt_to_select, window, cx, ) }) }, ) }) })} pub struct RulesLibrary { title_bar: Option<Entity<PlatformTitleBar>>, store: Entity<PromptStore>, language_registry: Arc<LanguageRegistry>, rule_editors: HashMap<PromptId, RuleEditor>, active_rule_id: Option<PromptId>, picker: Entity<Picker<RulePickerDelegate>>, pending_load: Task<()>, inline_assist_delegate: Box<dyn InlineAssistDelegate>, make_completion_provider: Rc<dyn Fn() -> Rc<dyn CompletionProvider>>, _subscriptions: Vec<Subscription>,} struct RuleEditor { title_editor: Entity<Editor>, body_editor: Entity<Editor>, token_count: Option<u64>, pending_token_count: Task<Option<()>>, next_title_and_body_to_save: Option<(String, Rope)>, pending_save: Option<Task<Option<()>>>, _subscriptions: Vec<Subscription>,} enum RulePickerEntry { Header(SharedString), Rule(PromptMetadata), Separator,} struct RulePickerDelegate { store: Entity<PromptStore>, selected_index: usize, filtered_entries: Vec<RulePickerEntry>,} enum RulePickerEvent { Selected { prompt_id: PromptId }, Confirmed { prompt_id: PromptId }, Deleted { prompt_id: PromptId }, ToggledDefault { prompt_id: PromptId },} impl EventEmitter<RulePickerEvent> for Picker<RulePickerDelegate> {} impl PickerDelegate for RulePickerDelegate { type ListItem = AnyElement; fn match_count(&self) -> usize { self.filtered_entries.len() } fn no_matches_text(&self, _window: &mut Window, _cx: &mut App) -> Option<SharedString> { Some("No rules found matching your search.".into()) } fn selected_index(&self) -> usize { self.selected_index } fn set_selected_index(&mut self, ix: usize, _: &mut Window, cx: &mut Context<Picker<Self>>) { self.selected_index = ix.min(self.filtered_entries.len().saturating_sub(1)); if let Some(RulePickerEntry::Rule(rule)) = self.filtered_entries.get(self.selected_index) { cx.emit(RulePickerEvent::Selected { prompt_id: rule.id }); } cx.notify(); } fn can_select(&mut self, ix: usize, _: &mut Window, _: &mut Context<Picker<Self>>) -> bool { match self.filtered_entries.get(ix) { Some(RulePickerEntry::Rule(_)) => true, Some(RulePickerEntry::Header(_)) | Some(RulePickerEntry::Separator) | None => false, } } fn placeholder_text(&self, _window: &mut Window, _cx: &mut App) -> Arc<str> { "Search…".into() } fn update_matches( &mut self, query: String, window: &mut Window, cx: &mut Context<Picker<Self>>, ) -> Task<()> { let cancellation_flag = Arc::new(AtomicBool::default()); let search = self.store.read(cx).search(query, cancellation_flag, cx); let prev_prompt_id = self .filtered_entries .get(self.selected_index) .and_then(|entry| { if let RulePickerEntry::Rule(rule) = entry { Some(rule.id) } else { None } }); cx.spawn_in(window, async move |this, cx| { let (filtered_entries, selected_index) = cx .background_spawn(async move { let matches = search.await; let (built_in_rules, user_rules): (Vec<_>, Vec<_>) = matches.into_iter().partition(|rule| rule.id.is_built_in()); let (default_rules, other_rules): (Vec<_>, Vec<_>) = user_rules.into_iter().partition(|rule| rule.default); let mut filtered_entries = Vec::new(); if !built_in_rules.is_empty() { filtered_entries.push(RulePickerEntry::Header("Built-in Rules".into())); for rule in built_in_rules { filtered_entries.push(RulePickerEntry::Rule(rule)); } filtered_entries.push(RulePickerEntry::Separator); } if !default_rules.is_empty() { filtered_entries.push(RulePickerEntry::Header("Default Rules".into())); for rule in default_rules { filtered_entries.push(RulePickerEntry::Rule(rule)); } filtered_entries.push(RulePickerEntry::Separator); } for rule in other_rules { filtered_entries.push(RulePickerEntry::Rule(rule)); } let selected_index = prev_prompt_id .and_then(|prev_prompt_id| { filtered_entries.iter().position(|entry| { if let RulePickerEntry::Rule(rule) = entry { rule.id == prev_prompt_id } else { false } }) }) .unwrap_or_else(|| { filtered_entries .iter() .position(|entry| matches!(entry, RulePickerEntry::Rule(_))) .unwrap_or(0) }); (filtered_entries, selected_index) }) .await; this.update_in(cx, |this, window, cx| { this.delegate.filtered_entries = filtered_entries; this.set_selected_index( selected_index, Some(picker::Direction::Down), true, window, cx, ); cx.notify(); }) .ok(); }) } fn confirm(&mut self, _secondary: bool, _: &mut Window, cx: &mut Context<Picker<Self>>) { if let Some(RulePickerEntry::Rule(rule)) = self.filtered_entries.get(self.selected_index) { cx.emit(RulePickerEvent::Confirmed { prompt_id: rule.id }); } } fn dismissed(&mut self, _window: &mut Window, _cx: &mut Context<Picker<Self>>) {} fn render_match( &self, ix: usize, selected: bool, _: &mut Window, cx: &mut Context<Picker<Self>>, ) -> Option<Self::ListItem> { match self.filtered_entries.get(ix)? { RulePickerEntry::Header(title) => { let tooltip_text = if title.as_ref() == "Built-in Rules" { "Built-in rules are those included out of the box with Zed." } else { "Default Rules are attached by default with every new thread." }; Some( ListSubHeader::new(title.clone()) .end_slot( IconButton::new("info", IconName::Info) .style(ButtonStyle::Transparent) .icon_size(IconSize::Small) .icon_color(Color::Muted) .tooltip(Tooltip::text(tooltip_text)) .into_any_element(), ) .inset(true) .into_any_element(), ) } RulePickerEntry::Separator => Some( h_flex() .py_1() .child(Divider::horizontal()) .into_any_element(), ), RulePickerEntry::Rule(rule) => { let default = rule.default; let prompt_id = rule.id; Some( ListItem::new(ix) .inset(true) .spacing(ListItemSpacing::Sparse) .toggle_state(selected) .child( Label::new(rule.title.clone().unwrap_or("Untitled".into())) .truncate() .mr_10(), ) .end_slot::<IconButton>((default && !prompt_id.is_built_in()).then(|| { IconButton::new("toggle-default-rule", IconName::Paperclip) .toggle_state(true) .icon_color(Color::Accent) .icon_size(IconSize::Small) .tooltip(Tooltip::text("Remove from Default Rules")) .on_click(cx.listener(move |_, _, _, cx| { cx.emit(RulePickerEvent::ToggledDefault { prompt_id }) })) })) .when(!prompt_id.is_built_in(), |this| { this.end_hover_slot( h_flex() .child( IconButton::new("delete-rule", IconName::Trash) .icon_color(Color::Muted) .icon_size(IconSize::Small) .tooltip(Tooltip::text("Delete Rule")) .on_click(cx.listener(move |_, _, _, cx| { cx.emit(RulePickerEvent::Deleted { prompt_id }) })), ) .child( IconButton::new("toggle-default-rule", IconName::Plus) .selected_icon(IconName::Dash) .toggle_state(default) .icon_size(IconSize::Small) .icon_color(if default { Color::Accent } else { Color::Muted }) .map(|this| { if default { this.tooltip(Tooltip::text( "Remove from Default Rules", )) } else { this.tooltip(move |_window, cx| { Tooltip::with_meta( "Add to Default Rules", None, "Always included in every thread.", cx, ) }) } }) .on_click(cx.listener(move |_, _, _, cx| { cx.emit(RulePickerEvent::ToggledDefault { prompt_id, }) })), ), ) }) .into_any_element(), ) } } } fn render_editor( &self, editor: &Arc<dyn ErasedEditor>, _: &mut Window, cx: &mut Context<Picker<Self>>, ) -> Div { let editor = editor.as_any().downcast_ref::<Entity<Editor>>().unwrap(); h_flex() .py_1() .px_1p5() .mx_1() .gap_1p5() .rounded_sm() .bg(cx.theme().colors().editor_background) .border_1() .border_color(cx.theme().colors().border) .child(Icon::new(IconName::MagnifyingGlass).color(Color::Muted)) .child(editor.clone()) }} impl RulesLibrary { fn new( store: Entity<PromptStore>, language_registry: Arc<LanguageRegistry>, inline_assist_delegate: Box<dyn InlineAssistDelegate>, make_completion_provider: Rc<dyn Fn() -> Rc<dyn CompletionProvider>>, rule_to_select: Option<PromptId>, window: &mut Window, cx: &mut Context<Self>, ) -> Self { let (_selected_index, _matches) = if let Some(rule_to_select) = rule_to_select { let matches = store.read(cx).all_prompt_metadata(); let selected_index = matches .iter() .enumerate() .find(|(_, metadata)| metadata.id == rule_to_select) .map_or(0, |(ix, _)| ix); (selected_index, matches) } else { (0, vec![]) }; let picker_delegate = RulePickerDelegate { store: store.clone(), selected_index: 0, filtered_entries: Vec::new(), }; let picker = cx.new(|cx| { let picker = Picker::list(picker_delegate, window, cx) .modal(false) .max_height(None);Lines 1–500 of 1435. Load more
use anyhow::Result; use collections::{HashMap, HashSet}; use editor::{CompletionProvider, SelectionEffects}; use editor::{CurrentLineHighlight, Editor, EditorElement, EditorEvent, EditorStyle, actions::Tab}; use gpui::{ App, Bounds, DEFAULT_ADDITIONAL_WINDOW_SIZE, Entity, EventEmitter, Focusable, PromptLevel, Subscription, Task, TextStyle, Tiling, TitlebarOptions, WindowBounds, WindowHandle, WindowOptions, actions, point, size, transparent_black, }; use language::{Buffer, LanguageRegistry, language_settings::SoftWrap}; use language_model::{ ConfiguredModel, LanguageModelRegistry, LanguageModelRequest, LanguageModelRequestMessage, Role, }; use picker::{Picker, PickerDelegate}; use platform_title_bar::PlatformTitleBar; use release_channel::ReleaseChannel; use rope::Rope; use settings::Settings; use std::rc::Rc; use std::sync::Arc; use std::sync::atomic::AtomicBool; use std::time::Duration; use theme::ThemeSettings; use ui::{Divider, ListItem, ListItemSpacing, ListSubHeader, Tooltip, prelude::*}; use ui_input::ErasedEditor; use util::{ResultExt, TryFutureExt}; use workspace::{MultiWorkspace, Workspace, WorkspaceSettings, client_side_decorations}; use zed_actions::assistant::InlineAssist; use prompt_store::*; pub fn init(cx: &mut App) { prompt_store::init(cx); } actions!( rules_library, [ /// Creates a new rule in the rules library. NewRule, /// Deletes the selected rule. DeleteRule, /// Duplicates the selected rule. DuplicateRule, /// Toggles whether the selected rule is a default rule. ToggleDefaultRule, /// Restores a built-in rule to its default content. RestoreDefaultContent ] ); pub trait InlineAssistDelegate { fn assist( &self, prompt_editor: &Entity<Editor>, initial_prompt: Option<String>, window: &mut Window, cx: &mut Context<RulesLibrary>, ); /// Returns whether the Agent panel was focused. fn focus_agent_panel( &self, workspace: &mut Workspace, window: &mut Window, cx: &mut Context<Workspace>, ) -> bool; } /// This function opens a new rules library window if one doesn't exist already. /// If one exists, it brings it to the foreground. /// /// Note that, when opening a new window, this waits for the PromptStore to be /// initialized. If it was initialized successfully, it returns a window handle /// to a rules library. pub fn open_rules_library( language_registry: Arc<LanguageRegistry>, inline_assist_delegate: Box<dyn InlineAssistDelegate>, make_completion_provider: Rc<dyn Fn() -> Rc<dyn CompletionProvider>>, prompt_to_select: Option<PromptId>, cx: &mut App, ) -> Task<Result<WindowHandle<RulesLibrary>>> { let store = PromptStore::global(cx); cx.spawn(async move |cx| { // We query windows in spawn so that all windows have been returned to GPUI let existing_window = cx.update(|cx| { let existing_window = cx .windows() .into_iter() .find_map(|window| window.downcast::<RulesLibrary>()); if let Some(existing_window) = existing_window { existing_window .update(cx, |rules_library, window, cx| { if let Some(prompt_to_select) = prompt_to_select { rules_library.load_rule(prompt_to_select, true, window, cx); } window.activate_window() }) .ok(); Some(existing_window) } else { None } }); if let Some(existing_window) = existing_window { return Ok(existing_window); } let store = store.await?; cx.update(|cx| { let app_id = ReleaseChannel::global(cx).app_id(); let bounds = Bounds::centered(None, size(px(1024.0), px(768.0)), cx); let window_decorations = match std::env::var("ZED_WINDOW_DECORATIONS") { Ok(val) if val == "server" => gpui::WindowDecorations::Server, Ok(val) if val == "client" => gpui::WindowDecorations::Client, _ => match WorkspaceSettings::get_global(cx).window_decorations { settings::WindowDecorations::Server => gpui::WindowDecorations::Server, settings::WindowDecorations::Client => gpui::WindowDecorations::Client, }, }; cx.open_window( WindowOptions { titlebar: Some(TitlebarOptions { title: Some("Rules Library".into()), appears_transparent: true, traffic_light_position: Some(point(px(12.0), px(12.0))), }), app_id: Some(app_id.to_owned()), window_bounds: Some(WindowBounds::Windowed(bounds)), window_background: cx.theme().window_background_appearance(), window_decorations: Some(window_decorations), window_min_size: Some(DEFAULT_ADDITIONAL_WINDOW_SIZE), kind: gpui::WindowKind::Floating, ..Default::default() }, |window, cx| { cx.new(|cx| { RulesLibrary::new( store, language_registry, inline_assist_delegate, make_completion_provider, prompt_to_select, window, cx, ) }) }, ) }) }) } pub struct RulesLibrary { title_bar: Option<Entity<PlatformTitleBar>>, store: Entity<PromptStore>, language_registry: Arc<LanguageRegistry>, rule_editors: HashMap<PromptId, RuleEditor>, active_rule_id: Option<PromptId>, picker: Entity<Picker<RulePickerDelegate>>, pending_load: Task<()>, inline_assist_delegate: Box<dyn InlineAssistDelegate>, make_completion_provider: Rc<dyn Fn() -> Rc<dyn CompletionProvider>>, _subscriptions: Vec<Subscription>, } struct RuleEditor { title_editor: Entity<Editor>, body_editor: Entity<Editor>, token_count: Option<u64>, pending_token_count: Task<Option<()>>, next_title_and_body_to_save: Option<(String, Rope)>, pending_save: Option<Task<Option<()>>>, _subscriptions: Vec<Subscription>, } enum RulePickerEntry { Header(SharedString), Rule(PromptMetadata), Separator, } struct RulePickerDelegate { store: Entity<PromptStore>, selected_index: usize, filtered_entries: Vec<RulePickerEntry>, } enum RulePickerEvent { Selected { prompt_id: PromptId }, Confirmed { prompt_id: PromptId }, Deleted { prompt_id: PromptId }, ToggledDefault { prompt_id: PromptId }, } impl EventEmitter<RulePickerEvent> for Picker<RulePickerDelegate> {} impl PickerDelegate for RulePickerDelegate { type ListItem = AnyElement; fn match_count(&self) -> usize { self.filtered_entries.len() } fn no_matches_text(&self, _window: &mut Window, _cx: &mut App) -> Option<SharedString> { Some("No rules found matching your search.".into()) } fn selected_index(&self) -> usize { self.selected_index } fn set_selected_index(&mut self, ix: usize, _: &mut Window, cx: &mut Context<Picker<Self>>) { self.selected_index = ix.min(self.filtered_entries.len().saturating_sub(1)); if let Some(RulePickerEntry::Rule(rule)) = self.filtered_entries.get(self.selected_index) { cx.emit(RulePickerEvent::Selected { prompt_id: rule.id }); } cx.notify(); } fn can_select(&mut self, ix: usize, _: &mut Window, _: &mut Context<Picker<Self>>) -> bool { match self.filtered_entries.get(ix) { Some(RulePickerEntry::Rule(_)) => true, Some(RulePickerEntry::Header(_)) | Some(RulePickerEntry::Separator) | None => false, } } fn placeholder_text(&self, _window: &mut Window, _cx: &mut App) -> Arc<str> { "Search…".into() } fn update_matches( &mut self, query: String, window: &mut Window, cx: &mut Context<Picker<Self>>, ) -> Task<()> { let cancellation_flag = Arc::new(AtomicBool::default()); let search = self.store.read(cx).search(query, cancellation_flag, cx); let prev_prompt_id = self .filtered_entries .get(self.selected_index) .and_then(|entry| { if let RulePickerEntry::Rule(rule) = entry { Some(rule.id) } else { None } }); cx.spawn_in(window, async move |this, cx| { let (filtered_entries, selected_index) = cx .background_spawn(async move { let matches = search.await; let (built_in_rules, user_rules): (Vec<_>, Vec<_>) = matches.into_iter().partition(|rule| rule.id.is_built_in()); let (default_rules, other_rules): (Vec<_>, Vec<_>) = user_rules.into_iter().partition(|rule| rule.default); let mut filtered_entries = Vec::new(); if !built_in_rules.is_empty() { filtered_entries.push(RulePickerEntry::Header("Built-in Rules".into())); for rule in built_in_rules { filtered_entries.push(RulePickerEntry::Rule(rule)); } filtered_entries.push(RulePickerEntry::Separator); } if !default_rules.is_empty() { filtered_entries.push(RulePickerEntry::Header("Default Rules".into())); for rule in default_rules { filtered_entries.push(RulePickerEntry::Rule(rule)); } filtered_entries.push(RulePickerEntry::Separator); } for rule in other_rules { filtered_entries.push(RulePickerEntry::Rule(rule)); } let selected_index = prev_prompt_id .and_then(|prev_prompt_id| { filtered_entries.iter().position(|entry| { if let RulePickerEntry::Rule(rule) = entry { rule.id == prev_prompt_id } else { false } }) }) .unwrap_or_else(|| { filtered_entries .iter() .position(|entry| matches!(entry, RulePickerEntry::Rule(_))) .unwrap_or(0) }); (filtered_entries, selected_index) }) .await; this.update_in(cx, |this, window, cx| { this.delegate.filtered_entries = filtered_entries; this.set_selected_index( selected_index, Some(picker::Direction::Down), true, window, cx, ); cx.notify(); }) .ok(); }) } fn confirm(&mut self, _secondary: bool, _: &mut Window, cx: &mut Context<Picker<Self>>) { if let Some(RulePickerEntry::Rule(rule)) = self.filtered_entries.get(self.selected_index) { cx.emit(RulePickerEvent::Confirmed { prompt_id: rule.id }); } } fn dismissed(&mut self, _window: &mut Window, _cx: &mut Context<Picker<Self>>) {} fn render_match( &self, ix: usize, selected: bool, _: &mut Window, cx: &mut Context<Picker<Self>>, ) -> Option<Self::ListItem> { match self.filtered_entries.get(ix)? { RulePickerEntry::Header(title) => { let tooltip_text = if title.as_ref() == "Built-in Rules" { "Built-in rules are those included out of the box with Zed." } else { "Default Rules are attached by default with every new thread." }; Some( ListSubHeader::new(title.clone()) .end_slot( IconButton::new("info", IconName::Info) .style(ButtonStyle::Transparent) .icon_size(IconSize::Small) .icon_color(Color::Muted) .tooltip(Tooltip::text(tooltip_text)) .into_any_element(), ) .inset(true) .into_any_element(), ) } RulePickerEntry::Separator => Some( h_flex() .py_1() .child(Divider::horizontal()) .into_any_element(), ), RulePickerEntry::Rule(rule) => { let default = rule.default; let prompt_id = rule.id; Some( ListItem::new(ix) .inset(true) .spacing(ListItemSpacing::Sparse) .toggle_state(selected) .child( Label::new(rule.title.clone().unwrap_or("Untitled".into())) .truncate() .mr_10(), ) .end_slot::<IconButton>((default && !prompt_id.is_built_in()).then(|| { IconButton::new("toggle-default-rule", IconName::Paperclip) .toggle_state(true) .icon_color(Color::Accent) .icon_size(IconSize::Small) .tooltip(Tooltip::text("Remove from Default Rules")) .on_click(cx.listener(move |_, _, _, cx| { cx.emit(RulePickerEvent::ToggledDefault { prompt_id }) })) })) .when(!prompt_id.is_built_in(), |this| { this.end_hover_slot( h_flex() .child( IconButton::new("delete-rule", IconName::Trash) .icon_color(Color::Muted) .icon_size(IconSize::Small) .tooltip(Tooltip::text("Delete Rule")) .on_click(cx.listener(move |_, _, _, cx| { cx.emit(RulePickerEvent::Deleted { prompt_id }) })), ) .child( IconButton::new("toggle-default-rule", IconName::Plus) .selected_icon(IconName::Dash) .toggle_state(default) .icon_size(IconSize::Small) .icon_color(if default { Color::Accent } else { Color::Muted }) .map(|this| { if default { this.tooltip(Tooltip::text( "Remove from Default Rules", )) } else { this.tooltip(move |_window, cx| { Tooltip::with_meta( "Add to Default Rules", None, "Always included in every thread.", cx, ) }) } }) .on_click(cx.listener(move |_, _, _, cx| { cx.emit(RulePickerEvent::ToggledDefault { prompt_id, }) })), ), ) }) .into_any_element(), ) } } } fn render_editor( &self, editor: &Arc<dyn ErasedEditor>, _: &mut Window, cx: &mut Context<Picker<Self>>, ) -> Div { let editor = editor.as_any().downcast_ref::<Entity<Editor>>().unwrap(); h_flex() .py_1() .px_1p5() .mx_1() .gap_1p5() .rounded_sm() .bg(cx.theme().colors().editor_background) .border_1() .border_color(cx.theme().colors().border) .child(Icon::new(IconName::MagnifyingGlass).color(Color::Muted)) .child(editor.clone()) } } impl RulesLibrary { fn new( store: Entity<PromptStore>, language_registry: Arc<LanguageRegistry>, inline_assist_delegate: Box<dyn InlineAssistDelegate>, make_completion_provider: Rc<dyn Fn() -> Rc<dyn CompletionProvider>>, rule_to_select: Option<PromptId>, window: &mut Window, cx: &mut Context<Self>, ) -> Self { let (_selected_index, _matches) = if let Some(rule_to_select) = rule_to_select { let matches = store.read(cx).all_prompt_metadata(); let selected_index = matches .iter() .enumerate() .find(|(_, metadata)| metadata.id == rule_to_select) .map_or(0, |(ix, _)| ix); (selected_index, matches) } else { (0, vec![]) }; let picker_delegate = RulePickerDelegate { store: store.clone(), selected_index: 0, filtered_entries: Vec::new(), }; let picker = cx.new(|cx| { let picker = Picker::list(picker_delegate, window, cx) .modal(false) .max_height(None);