1use context_server::{ContextServerSettings, ServerCommand, ServerConfig};
2use editor::Editor;
3use gpui::{DismissEvent, Entity, EventEmitter, FocusHandle, Focusable, WeakEntity, prelude::*};
4use serde_json::json;
5use settings::update_settings_file;
6use ui::{Modal, ModalFooter, ModalHeader, Section, Tooltip, prelude::*};
7use workspace::{ModalView, Workspace};
8
9use crate::AddContextServer;
10
11pub struct AddContextServerModal {
12 workspace: WeakEntity<Workspace>,
13 name_editor: Entity<Editor>,
14 command_editor: Entity<Editor>,
15}
16
17impl AddContextServerModal {
18 pub fn register(
19 workspace: &mut Workspace,
20 _window: Option<&mut Window>,
21 _cx: &mut Context<Workspace>,
22 ) {
23 workspace.register_action(|workspace, _: &AddContextServer, window, cx| {
24 let workspace_handle = cx.entity().downgrade();
25 workspace.toggle_modal(window, cx, |window, cx| {
26 Self::new(workspace_handle, window, cx)
27 })
28 });
29 }
30
31 pub fn new(
32 workspace: WeakEntity<Workspace>,
33 window: &mut Window,
34 cx: &mut Context<Self>,
35 ) -> Self {
36 let name_editor = cx.new(|cx| Editor::single_line(window, cx));
37 let command_editor = cx.new(|cx| Editor::single_line(window, cx));
38
39 name_editor.update(cx, |editor, cx| {
40 editor.set_placeholder_text("Context server name", cx);
41 });
42
43 command_editor.update(cx, |editor, cx| {
44 editor.set_placeholder_text("Command to run the context server", cx);
45 });
46
47 Self {
48 name_editor,
49 command_editor,
50 workspace,
51 }
52 }
53
54 fn confirm(&mut self, cx: &mut Context<Self>) {
55 let name = self.name_editor.read(cx).text(cx).trim().to_string();
56 let command = self.command_editor.read(cx).text(cx).trim().to_string();
57
58 if name.is_empty() || command.is_empty() {
59 return;
60 }
61
62 let mut command_parts = command.split(' ').map(|part| part.trim().to_string());
63 let Some(path) = command_parts.next() else {
64 return;
65 };
66 let args = command_parts.collect::<Vec<_>>();
67
68 if let Some(workspace) = self.workspace.upgrade() {
69 workspace.update(cx, |workspace, cx| {
70 let fs = workspace.app_state().fs.clone();
71 update_settings_file::<ContextServerSettings>(fs.clone(), cx, |settings, _| {
72 settings.context_servers.insert(
73 name.into(),
74 ServerConfig {
75 command: Some(ServerCommand {
76 path,
77 args,
78 env: None,
79 }),
80 settings: Some(json!({})),
81 },
82 );
83 });
84 });
85 }
86
87 cx.emit(DismissEvent);
88 }
89
90 fn cancel(&mut self, cx: &mut Context<Self>) {
91 cx.emit(DismissEvent);
92 }
93}
94
95impl ModalView for AddContextServerModal {}
96
97impl Focusable for AddContextServerModal {
98 fn focus_handle(&self, cx: &App) -> FocusHandle {
99 self.name_editor.focus_handle(cx).clone()
100 }
101}
102
103impl EventEmitter<DismissEvent> for AddContextServerModal {}
104
105impl Render for AddContextServerModal {
106 fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
107 let is_name_empty = self.name_editor.read(cx).text(cx).trim().is_empty();
108 let is_command_empty = self.command_editor.read(cx).text(cx).trim().is_empty();
109
110 div()
111 .elevation_3(cx)
112 .w(rems(34.))
113 .key_context("AddContextServerModal")
114 .on_action(cx.listener(|this, _: &menu::Cancel, _window, cx| this.cancel(cx)))
115 .on_action(cx.listener(|this, _: &menu::Confirm, _window, cx| this.confirm(cx)))
116 .capture_any_mouse_down(cx.listener(|this, _, window, cx| {
117 this.focus_handle(cx).focus(window);
118 }))
119 .on_mouse_down_out(cx.listener(|_this, _, _, cx| cx.emit(DismissEvent)))
120 .child(
121 Modal::new("add-context-server", None)
122 .header(ModalHeader::new().headline("Add Context Server"))
123 .section(
124 Section::new()
125 .child(
126 v_flex()
127 .gap_1()
128 .child(Label::new("Name"))
129 .child(self.name_editor.clone()),
130 )
131 .child(
132 v_flex()
133 .gap_1()
134 .child(Label::new("Command"))
135 .child(self.command_editor.clone()),
136 ),
137 )
138 .footer(
139 ModalFooter::new()
140 .start_slot(
141 Button::new("cancel", "Cancel").on_click(
142 cx.listener(|this, _event, _window, cx| this.cancel(cx)),
143 ),
144 )
145 .end_slot(
146 Button::new("add-server", "Add Server")
147 .disabled(is_name_empty || is_command_empty)
148 .map(|button| {
149 if is_name_empty {
150 button.tooltip(Tooltip::text("Name is required"))
151 } else if is_command_empty {
152 button.tooltip(Tooltip::text("Command is required"))
153 } else {
154 button
155 }
156 })
157 .on_click(
158 cx.listener(|this, _event, _window, cx| this.confirm(cx)),
159 ),
160 ),
161 ),
162 )
163 }
164}