tool_selector.rs

  1use std::sync::Arc;
  2
  3use assistant_settings::{AgentProfile, AssistantSettings};
  4use assistant_tool::{ToolSource, ToolWorkingSet};
  5use gpui::{Entity, Subscription};
  6use indexmap::IndexMap;
  7use scripting_tool::ScriptingTool;
  8use settings::{Settings as _, SettingsStore};
  9use ui::{prelude::*, ContextMenu, PopoverMenu, Tooltip};
 10
 11pub struct ToolSelector {
 12    profiles: IndexMap<Arc<str>, AgentProfile>,
 13    tools: Arc<ToolWorkingSet>,
 14    _subscriptions: Vec<Subscription>,
 15}
 16
 17impl ToolSelector {
 18    pub fn new(tools: Arc<ToolWorkingSet>, cx: &mut Context<Self>) -> Self {
 19        let settings_subscription = cx.observe_global::<SettingsStore>(move |this, cx| {
 20            this.refresh_profiles(cx);
 21        });
 22
 23        let mut this = Self {
 24            profiles: IndexMap::default(),
 25            tools,
 26            _subscriptions: vec![settings_subscription],
 27        };
 28        this.refresh_profiles(cx);
 29
 30        this
 31    }
 32
 33    fn refresh_profiles(&mut self, cx: &mut Context<Self>) {
 34        let settings = AssistantSettings::get_global(cx);
 35
 36        self.profiles = settings.profiles.clone();
 37    }
 38
 39    fn build_context_menu(
 40        &self,
 41        window: &mut Window,
 42        cx: &mut Context<Self>,
 43    ) -> Entity<ContextMenu> {
 44        let profiles = self.profiles.clone();
 45        let tool_set = self.tools.clone();
 46        ContextMenu::build_persistent(window, cx, move |mut menu, _window, cx| {
 47            let icon_position = IconPosition::End;
 48
 49            menu = menu.header("Profiles");
 50            for (_id, profile) in profiles.clone() {
 51                menu = menu.toggleable_entry(profile.name.clone(), false, icon_position, None, {
 52                    let tools = tool_set.clone();
 53                    move |_window, cx| {
 54                        tools.disable_source(ToolSource::Native, cx);
 55                        tools.disable_scripting_tool();
 56                        tools.enable(
 57                            ToolSource::Native,
 58                            &profile
 59                                .tools
 60                                .iter()
 61                                .filter_map(|(tool, enabled)| enabled.then(|| tool.clone()))
 62                                .collect::<Vec<_>>(),
 63                        );
 64
 65                        if profile.tools.contains_key(ScriptingTool::NAME) {
 66                            tools.enable_scripting_tool();
 67                        }
 68                    }
 69                });
 70            }
 71
 72            menu = menu.separator();
 73
 74            let tools_by_source = tool_set.tools_by_source(cx);
 75
 76            let all_tools_enabled = tool_set.are_all_tools_enabled();
 77            menu = menu.toggleable_entry("All Tools", all_tools_enabled, icon_position, None, {
 78                let tools = tool_set.clone();
 79                move |_window, cx| {
 80                    if all_tools_enabled {
 81                        tools.disable_all_tools(cx);
 82                    } else {
 83                        tools.enable_all_tools();
 84                    }
 85                }
 86            });
 87
 88            for (source, tools) in tools_by_source {
 89                let mut tools = tools
 90                    .into_iter()
 91                    .map(|tool| {
 92                        let source = tool.source();
 93                        let name = tool.name().into();
 94                        let is_enabled = tool_set.is_enabled(&source, &name);
 95
 96                        (source, name, is_enabled)
 97                    })
 98                    .collect::<Vec<_>>();
 99
100                if ToolSource::Native == source {
101                    tools.push((
102                        ToolSource::Native,
103                        ScriptingTool::NAME.into(),
104                        tool_set.is_scripting_tool_enabled(),
105                    ));
106                    tools.sort_by(|(_, name_a, _), (_, name_b, _)| name_a.cmp(name_b));
107                }
108
109                menu = match &source {
110                    ToolSource::Native => menu.separator().header("Zed Tools"),
111                    ToolSource::ContextServer { id } => {
112                        let all_tools_from_source_enabled =
113                            tool_set.are_all_tools_from_source_enabled(&source);
114
115                        menu.separator().header(id).toggleable_entry(
116                            "All Tools",
117                            all_tools_from_source_enabled,
118                            icon_position,
119                            None,
120                            {
121                                let tools = tool_set.clone();
122                                let source = source.clone();
123                                move |_window, cx| {
124                                    if all_tools_from_source_enabled {
125                                        tools.disable_source(source.clone(), cx);
126                                    } else {
127                                        tools.enable_source(&source);
128                                    }
129                                }
130                            },
131                        )
132                    }
133                };
134
135                for (source, name, is_enabled) in tools {
136                    menu = menu.toggleable_entry(name.clone(), is_enabled, icon_position, None, {
137                        let tools = tool_set.clone();
138                        move |_window, _cx| {
139                            if name.as_ref() == ScriptingTool::NAME {
140                                if is_enabled {
141                                    tools.disable_scripting_tool();
142                                } else {
143                                    tools.enable_scripting_tool();
144                                }
145                            } else {
146                                if is_enabled {
147                                    tools.disable(source.clone(), &[name.clone()]);
148                                } else {
149                                    tools.enable(source.clone(), &[name.clone()]);
150                                }
151                            }
152                        }
153                    });
154                }
155            }
156
157            menu
158        })
159    }
160}
161
162impl Render for ToolSelector {
163    fn render(&mut self, _window: &mut Window, cx: &mut Context<'_, Self>) -> impl IntoElement {
164        let this = cx.entity().clone();
165        PopoverMenu::new("tool-selector")
166            .menu(move |window, cx| {
167                Some(this.update(cx, |this, cx| this.build_context_menu(window, cx)))
168            })
169            .trigger_with_tooltip(
170                IconButton::new("tool-selector-button", IconName::SettingsAlt)
171                    .icon_size(IconSize::Small)
172                    .icon_color(Color::Muted),
173                Tooltip::text("Customize Tools"),
174            )
175            .anchor(gpui::Corner::BottomLeft)
176    }
177}