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}