add_context_server_modal.rs

  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}