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