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