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