tool_selector.rs

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