1431 lines · 56.2 KB · Rust
RawDownload
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, 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::{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 1431.