inert.rs

  1use std::path::PathBuf;
  2
  3use dap::{DebugAdapterConfig, DebugAdapterKind, DebugRequestType};
  4use editor::{Editor, EditorElement, EditorStyle};
  5use gpui::{App, AppContext, Entity, EventEmitter, FocusHandle, Focusable, TextStyle, WeakEntity};
  6use settings::Settings as _;
  7use task::TCPHost;
  8use theme::ThemeSettings;
  9use ui::{
 10    h_flex, relative, v_flex, ActiveTheme as _, Button, ButtonCommon, ButtonStyle, Clickable,
 11    Context, ContextMenu, Disableable, DropdownMenu, InteractiveElement, IntoElement,
 12    ParentElement, Render, SharedString, Styled, Window,
 13};
 14use workspace::Workspace;
 15
 16use crate::attach_modal::AttachModal;
 17
 18pub(crate) struct InertState {
 19    focus_handle: FocusHandle,
 20    selected_debugger: Option<SharedString>,
 21    program_editor: Entity<Editor>,
 22    cwd_editor: Entity<Editor>,
 23    workspace: WeakEntity<Workspace>,
 24}
 25
 26impl InertState {
 27    pub(super) fn new(
 28        workspace: WeakEntity<Workspace>,
 29        default_cwd: &str,
 30        window: &mut Window,
 31        cx: &mut Context<Self>,
 32    ) -> Self {
 33        let program_editor = cx.new(|cx| {
 34            let mut editor = Editor::single_line(window, cx);
 35            editor.set_placeholder_text("Program path", cx);
 36            editor
 37        });
 38        let cwd_editor = cx.new(|cx| {
 39            let mut editor = Editor::single_line(window, cx);
 40            editor.insert(default_cwd, window, cx);
 41            editor.set_placeholder_text("Working directory", cx);
 42            editor
 43        });
 44        Self {
 45            workspace,
 46            cwd_editor,
 47            program_editor,
 48            selected_debugger: None,
 49            focus_handle: cx.focus_handle(),
 50        }
 51    }
 52}
 53impl Focusable for InertState {
 54    fn focus_handle(&self, _cx: &App) -> FocusHandle {
 55        self.focus_handle.clone()
 56    }
 57}
 58
 59pub(crate) enum InertEvent {
 60    Spawned { config: DebugAdapterConfig },
 61}
 62
 63impl EventEmitter<InertEvent> for InertState {}
 64
 65static SELECT_DEBUGGER_LABEL: SharedString = SharedString::new_static("Select Debugger");
 66
 67impl Render for InertState {
 68    fn render(
 69        &mut self,
 70        window: &mut ui::Window,
 71        cx: &mut ui::Context<'_, Self>,
 72    ) -> impl ui::IntoElement {
 73        let weak = cx.weak_entity();
 74        let disable_buttons = self.selected_debugger.is_none();
 75        v_flex()
 76            .track_focus(&self.focus_handle)
 77            .size_full()
 78            .gap_1()
 79            .p_2()
 80
 81            .child(
 82                v_flex().gap_1()
 83                    .child(
 84                        h_flex()
 85                            .w_full()
 86                            .gap_2()
 87
 88                            .child(Self::render_editor(&self.program_editor, cx))
 89                            .child(
 90                                h_flex().child(DropdownMenu::new(
 91                                    "dap-adapter-picker",
 92                                    self.selected_debugger
 93                                        .as_ref()
 94                                        .unwrap_or_else(|| &SELECT_DEBUGGER_LABEL)
 95                                        .clone(),
 96                                    ContextMenu::build(window, cx, move |this, _, _| {
 97                                        let setter_for_name = |name: &'static str| {
 98                                            let weak = weak.clone();
 99                                            move |_: &mut Window, cx: &mut App| {
100                                                let name = name;
101                                                (&weak)
102                                                    .update(cx, move |this, _| {
103                                                        this.selected_debugger = Some(name.into());
104                                                    })
105                                                    .ok();
106                                            }
107                                        };
108                                        this.entry("GDB", None, setter_for_name("GDB"))
109                                            .entry("Delve", None, setter_for_name("Delve"))
110                                            .entry("LLDB", None, setter_for_name("LLDB"))
111                                            .entry("PHP", None, setter_for_name("PHP"))
112                                            .entry("JavaScript", None, setter_for_name("JavaScript"))
113                                            .entry("Debugpy", None, setter_for_name("Debugpy"))
114                                    }),
115                                )),
116                            )
117                    )
118                    .child(
119                        h_flex().gap_2().child(
120                            Self::render_editor(&self.cwd_editor, cx),
121                        ).child(h_flex()
122                            .gap_4()
123                            .pl_2()
124                            .child(
125                                Button::new("launch-dap", "Launch")
126                                    .style(ButtonStyle::Filled)
127                                    .disabled(disable_buttons)
128                                    .on_click(cx.listener(|this, _, _, cx| {
129                                        let program = this.program_editor.read(cx).text(cx);
130                                        let cwd = PathBuf::from(this.cwd_editor.read(cx).text(cx));
131                                        let kind = kind_for_label(this.selected_debugger.as_deref().unwrap_or_else(|| unimplemented!("Automatic selection of a debugger based on users project")));
132                                        cx.emit(InertEvent::Spawned {
133                                            config: DebugAdapterConfig {
134                                                label: "hard coded".into(),
135                                                kind,
136                                                request: DebugRequestType::Launch,
137                                                program: Some(program),
138                                                cwd: Some(cwd),
139                                                initialize_args: None,
140                                                supports_attach: false,
141                                            },
142                                        });
143                                    })),
144                            )
145                            .child(Button::new("attach-dap", "Attach")
146                                .style(ButtonStyle::Filled)
147                                .disabled(disable_buttons)
148                                .on_click(cx.listener(|this, _, window, cx| this.attach(window, cx)))
149                            ))
150                    )
151            )
152    }
153}
154
155fn kind_for_label(label: &str) -> DebugAdapterKind {
156    match label {
157        "LLDB" => DebugAdapterKind::Lldb,
158        "Debugpy" => DebugAdapterKind::Python(TCPHost::default()),
159        "JavaScript" => DebugAdapterKind::Javascript(TCPHost::default()),
160        "PHP" => DebugAdapterKind::Php(TCPHost::default()),
161        "Delve" => DebugAdapterKind::Go(TCPHost::default()),
162        _ => {
163            unimplemented!()
164        } // Maybe we should set a toast notification here
165    }
166}
167impl InertState {
168    fn render_editor(editor: &Entity<Editor>, cx: &Context<Self>) -> impl IntoElement {
169        let settings = ThemeSettings::get_global(cx);
170        let text_style = TextStyle {
171            color: cx.theme().colors().text,
172            font_family: settings.buffer_font.family.clone(),
173            font_features: settings.buffer_font.features.clone(),
174            font_size: settings.buffer_font_size(cx).into(),
175            font_weight: settings.buffer_font.weight,
176            line_height: relative(settings.buffer_line_height.value()),
177            ..Default::default()
178        };
179
180        EditorElement::new(
181            editor,
182            EditorStyle {
183                background: cx.theme().colors().editor_background,
184                local_player: cx.theme().players().local(),
185                text: text_style,
186                ..Default::default()
187            },
188        )
189    }
190
191    fn attach(&self, window: &mut Window, cx: &mut Context<Self>) {
192        let process_id = self.program_editor.read(cx).text(cx).parse::<u32>().ok();
193        let cwd = PathBuf::from(self.cwd_editor.read(cx).text(cx));
194        let kind = kind_for_label(self.selected_debugger.as_deref().unwrap_or_else(|| {
195            unimplemented!("Automatic selection of a debugger based on users project")
196        }));
197
198        let config = DebugAdapterConfig {
199            label: "hard coded attach".into(),
200            kind,
201            request: DebugRequestType::Attach(task::AttachConfig { process_id }),
202            program: None,
203            cwd: Some(cwd),
204            initialize_args: None,
205            supports_attach: true,
206        };
207
208        if process_id.is_some() {
209            cx.emit(InertEvent::Spawned { config });
210        } else {
211            let _ = self.workspace.update(cx, |workspace, cx| {
212                let project = workspace.project().clone();
213                workspace.toggle_modal(window, cx, |window, cx| {
214                    AttachModal::new(project, config, window, cx)
215                });
216            });
217        }
218    }
219}