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}