Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T06:49:23.928Z Public web read
NIP-34 coordinate30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omega
MaintainersHidden in public view
References2 branches · 1 tag
Read-only clonegit clone https://openagents.com/git/tenant.openagents/omega.git
Browse files

breakpoint_list.rs

1518 lines · 56.6 KB · rust
1use std::{
2    ops::Range,
3    path::{Path, PathBuf},
4    sync::Arc,
5    time::Duration,
6};
7
8use dap::{Capabilities, ExceptionBreakpointsFilter, adapters::DebugAdapterName};
9use db::kvp::KeyValueStore;
10use editor::Editor;
11use gpui::{
12    Action, AppContext, ClickEvent, Entity, FocusHandle, Focusable, MouseButton, ScrollStrategy,
13    Task, UniformListScrollHandle, WeakEntity, actions, uniform_list,
14};
15use itertools::Itertools;
16use language::Point;
17use project::{
18    Project,
19    debugger::{
20        breakpoint_store::{BreakpointEditAction, BreakpointStore, SourceBreakpoint},
21        dap_store::{DapStore, PersistedAdapterOptions},
22        session::Session,
23    },
24    worktree_store::WorktreeStore,
25};
26use ui::{
27    Divider, DividerColor, FluentBuilder as _, Indicator, IntoElement, ListItem, Render,
28    ScrollAxes, StatefulInteractiveElement, Tooltip, WithScrollbar, prelude::*,
29};
30use util::rel_path::RelPath;
31use workspace::Workspace;
32use zed_actions::{ToggleEnableBreakpoint, UnsetBreakpoint};
33
34actions!(
35    debugger,
36    [
37        /// Navigates to the previous breakpoint property in the list.
38        PreviousBreakpointProperty,
39        /// Navigates to the next breakpoint property in the list.
40        NextBreakpointProperty
41    ]
42);
43#[derive(Clone, Copy, PartialEq)]
44pub(crate) enum SelectedBreakpointKind {
45    Source,
46    Exception,
47    Data,
48}
49pub(crate) struct BreakpointList {
50    workspace: WeakEntity<Workspace>,
51    breakpoint_store: Entity<BreakpointStore>,
52    dap_store: Entity<DapStore>,
53    worktree_store: Entity<WorktreeStore>,
54    breakpoints: Vec<BreakpointEntry>,
55    session: Option<Entity<Session>>,
56    focus_handle: FocusHandle,
57    scroll_handle: UniformListScrollHandle,
58    selected_ix: Option<usize>,
59    max_width_index: Option<usize>,
60    input: Entity<Editor>,
61    strip_mode: Option<ActiveBreakpointStripMode>,
62    serialize_exception_breakpoints_task: Option<Task<anyhow::Result<()>>>,
63}
64
65impl Focusable for BreakpointList {
66    fn focus_handle(&self, _: &App) -> gpui::FocusHandle {
67        self.focus_handle.clone()
68    }
69}
70
71#[derive(Clone, Copy, PartialEq)]
72enum ActiveBreakpointStripMode {
73    Log,
74    Condition,
75    HitCondition,
76}
77
78impl BreakpointList {
79    pub(crate) fn new(
80        session: Option<Entity<Session>>,
81        workspace: WeakEntity<Workspace>,
82        project: &Entity<Project>,
83        window: &mut Window,
84        cx: &mut App,
85    ) -> Entity<Self> {
86        let project = project.read(cx);
87        let breakpoint_store = project.breakpoint_store();
88        let worktree_store = project.worktree_store();
89        let dap_store = project.dap_store();
90        let focus_handle = cx.focus_handle();
91        let scroll_handle = UniformListScrollHandle::new();
92
93        let adapter_name = session.as_ref().map(|session| session.read(cx).adapter());
94        cx.new(|cx| {
95            let this = Self {
96                breakpoint_store,
97                dap_store,
98                worktree_store,
99                breakpoints: Default::default(),
100                max_width_index: None,
101                workspace,
102                session,
103                focus_handle,
104                scroll_handle,
105                selected_ix: None,
106                input: cx.new(|cx| Editor::single_line(window, cx)),
107                strip_mode: None,
108                serialize_exception_breakpoints_task: None,
109            };
110            if let Some(name) = adapter_name {
111                _ = this.deserialize_exception_breakpoints(name, cx);
112            }
113            this
114        })
115    }
116
117    fn edit_line_breakpoint(
118        &self,
119        path: Arc<Path>,
120        row: u32,
121        action: BreakpointEditAction,
122        cx: &mut App,
123    ) {
124        Self::edit_line_breakpoint_inner(&self.breakpoint_store, path, row, action, cx);
125    }
126    fn edit_line_breakpoint_inner(
127        breakpoint_store: &Entity<BreakpointStore>,
128        path: Arc<Path>,
129        row: u32,
130        action: BreakpointEditAction,
131        cx: &mut App,
132    ) {
133        breakpoint_store.update(cx, |breakpoint_store, cx| {
134            if let Some((buffer, breakpoint)) = breakpoint_store.breakpoint_at_row(&path, row, cx) {
135                breakpoint_store.toggle_breakpoint(buffer, breakpoint, action, cx);
136            } else {
137                log::error!("Couldn't find breakpoint at row event though it exists: row {row}")
138            }
139        })
140    }
141
142    fn go_to_line_breakpoint(
143        &mut self,
144        path: Arc<Path>,
145        row: u32,
146        window: &mut Window,
147        cx: &mut Context<Self>,
148    ) {
149        let task = self
150            .worktree_store
151            .update(cx, |this, cx| this.find_or_create_worktree(path, false, cx));
152        cx.spawn_in(window, async move |this, cx| {
153            let (worktree, relative_path) = task.await?;
154            let worktree_id = worktree.read_with(cx, |this, _| this.id());
155            let item = this
156                .update_in(cx, |this, window, cx| {
157                    this.workspace.update(cx, |this, cx| {
158                        this.open_path((worktree_id, relative_path), None, true, window, cx)
159                    })
160                })??
161                .await?;
162            if let Some(editor) = item.downcast::<Editor>() {
163                editor
164                    .update_in(cx, |this, window, cx| {
165                        this.go_to_singleton_buffer_point(Point { row, column: 0 }, window, cx);
166                    })
167                    .ok();
168            }
169            anyhow::Ok(())
170        })
171        .detach();
172    }
173
174    pub(crate) fn selection_kind(&self) -> Option<(SelectedBreakpointKind, bool)> {
175        self.selected_ix.and_then(|ix| {
176            self.breakpoints.get(ix).map(|bp| match &bp.kind {
177                BreakpointEntryKind::LineBreakpoint(bp) => (
178                    SelectedBreakpointKind::Source,
179                    bp.breakpoint.state
180                        == project::debugger::breakpoint_store::BreakpointState::Enabled,
181                ),
182                BreakpointEntryKind::ExceptionBreakpoint(bp) => {
183                    (SelectedBreakpointKind::Exception, bp.is_enabled)
184                }
185                BreakpointEntryKind::DataBreakpoint(bp) => {
186                    (SelectedBreakpointKind::Data, bp.0.is_enabled)
187                }
188            })
189        })
190    }
191
192    fn set_active_breakpoint_property(
193        &mut self,
194        prop: ActiveBreakpointStripMode,
195        window: &mut Window,
196        cx: &mut App,
197    ) {
198        self.strip_mode = Some(prop);
199        let placeholder = match prop {
200            ActiveBreakpointStripMode::Log => "Set Log Message",
201            ActiveBreakpointStripMode::Condition => "Set Condition",
202            ActiveBreakpointStripMode::HitCondition => "Set Hit Condition",
203        };
204        let mut is_exception_breakpoint = true;
205        let active_value = self.selected_ix.and_then(|ix| {
206            self.breakpoints.get(ix).and_then(|bp| {
207                if let BreakpointEntryKind::LineBreakpoint(bp) = &bp.kind {
208                    is_exception_breakpoint = false;
209                    match prop {
210                        ActiveBreakpointStripMode::Log => bp.breakpoint.message.clone(),
211                        ActiveBreakpointStripMode::Condition => bp.breakpoint.condition.clone(),
212                        ActiveBreakpointStripMode::HitCondition => {
213                            bp.breakpoint.hit_condition.clone()
214                        }
215                    }
216                } else {
217                    None
218                }
219            })
220        });
221
222        self.input.update(cx, |this, cx| {
223            this.set_placeholder_text(placeholder, window, cx);
224            this.set_read_only(is_exception_breakpoint);
225            this.set_text(active_value.as_deref().unwrap_or(""), window, cx);
226        });
227    }
228
229    fn select_ix(&mut self, ix: Option<usize>, window: &mut Window, cx: &mut Context<Self>) {
230        self.selected_ix = ix;
231        if let Some(ix) = ix {
232            self.scroll_handle
233                .scroll_to_item(ix, ScrollStrategy::Center);
234        }
235        if let Some(mode) = self.strip_mode {
236            self.set_active_breakpoint_property(mode, window, cx);
237        }
238
239        cx.notify();
240    }
241
242    fn select_next(&mut self, _: &menu::SelectNext, window: &mut Window, cx: &mut Context<Self>) {
243        if self.strip_mode.is_some() && self.input.focus_handle(cx).contains_focused(window, cx) {
244            cx.propagate();
245            return;
246        }
247        let ix = match self.selected_ix {
248            _ if self.breakpoints.is_empty() => None,
249            None => Some(0),
250            Some(ix) => {
251                if ix == self.breakpoints.len() - 1 {
252                    Some(0)
253                } else {
254                    Some(ix + 1)
255                }
256            }
257        };
258        self.select_ix(ix, window, cx);
259    }
260
261    fn select_previous(
262        &mut self,
263        _: &menu::SelectPrevious,
264        window: &mut Window,
265        cx: &mut Context<Self>,
266    ) {
267        if self.strip_mode.is_some() && self.input.focus_handle(cx).contains_focused(window, cx) {
268            cx.propagate();
269            return;
270        }
271        let ix = match self.selected_ix {
272            _ if self.breakpoints.is_empty() => None,
273            None => Some(self.breakpoints.len() - 1),
274            Some(ix) => {
275                if ix == 0 {
276                    Some(self.breakpoints.len() - 1)
277                } else {
278                    Some(ix - 1)
279                }
280            }
281        };
282        self.select_ix(ix, window, cx);
283    }
284
285    fn select_first(&mut self, _: &menu::SelectFirst, window: &mut Window, cx: &mut Context<Self>) {
286        if self.strip_mode.is_some() && self.input.focus_handle(cx).contains_focused(window, cx) {
287            cx.propagate();
288            return;
289        }
290        let ix = if !self.breakpoints.is_empty() {
291            Some(0)
292        } else {
293            None
294        };
295        self.select_ix(ix, window, cx);
296    }
297
298    fn select_last(&mut self, _: &menu::SelectLast, window: &mut Window, cx: &mut Context<Self>) {
299        if self.strip_mode.is_some() && self.input.focus_handle(cx).contains_focused(window, cx) {
300            cx.propagate();
301            return;
302        }
303        let ix = if !self.breakpoints.is_empty() {
304            Some(self.breakpoints.len() - 1)
305        } else {
306            None
307        };
308        self.select_ix(ix, window, cx);
309    }
310
311    fn dismiss(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
312        if self.input.focus_handle(cx).contains_focused(window, cx) {
313            self.focus_handle.focus(window, cx);
314        } else if self.strip_mode.is_some() {
315            self.strip_mode.take();
316            cx.notify();
317        } else {
318            cx.propagate();
319        }
320    }
321    fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
322        let Some(entry) = self.selected_ix.and_then(|ix| self.breakpoints.get_mut(ix)) else {
323            return;
324        };
325
326        if let Some(mode) = self.strip_mode {
327            let handle = self.input.focus_handle(cx);
328            if handle.is_focused(window) {
329                // Go back to the main strip. Save the result as well.
330                let text = self.input.read(cx).text(cx);
331
332                match mode {
333                    ActiveBreakpointStripMode::Log => {
334                        if let BreakpointEntryKind::LineBreakpoint(line_breakpoint) = &entry.kind {
335                            Self::edit_line_breakpoint_inner(
336                                &self.breakpoint_store,
337                                line_breakpoint.breakpoint.path.clone(),
338                                line_breakpoint.breakpoint.row,
339                                BreakpointEditAction::EditLogMessage(Arc::from(text)),
340                                cx,
341                            );
342                        }
343                    }
344                    ActiveBreakpointStripMode::Condition => {
345                        if let BreakpointEntryKind::LineBreakpoint(line_breakpoint) = &entry.kind {
346                            Self::edit_line_breakpoint_inner(
347                                &self.breakpoint_store,
348                                line_breakpoint.breakpoint.path.clone(),
349                                line_breakpoint.breakpoint.row,
350                                BreakpointEditAction::EditCondition(Arc::from(text)),
351                                cx,
352                            );
353                        }
354                    }
355                    ActiveBreakpointStripMode::HitCondition => {
356                        if let BreakpointEntryKind::LineBreakpoint(line_breakpoint) = &entry.kind {
357                            Self::edit_line_breakpoint_inner(
358                                &self.breakpoint_store,
359                                line_breakpoint.breakpoint.path.clone(),
360                                line_breakpoint.breakpoint.row,
361                                BreakpointEditAction::EditHitCondition(Arc::from(text)),
362                                cx,
363                            );
364                        }
365                    }
366                }
367                self.focus_handle.focus(window, cx);
368            } else {
369                handle.focus(window, cx);
370            }
371
372            return;
373        }
374        match &mut entry.kind {
375            BreakpointEntryKind::LineBreakpoint(line_breakpoint) => {
376                let path = line_breakpoint.breakpoint.path.clone();
377                let row = line_breakpoint.breakpoint.row;
378                self.go_to_line_breakpoint(path, row, window, cx);
379            }
380            BreakpointEntryKind::DataBreakpoint(_)
381            | BreakpointEntryKind::ExceptionBreakpoint(_) => {}
382        }
383    }
384
385    fn toggle_enable_breakpoint(
386        &mut self,
387        _: &ToggleEnableBreakpoint,
388        window: &mut Window,
389        cx: &mut Context<Self>,
390    ) {
391        let Some(entry) = self.selected_ix.and_then(|ix| self.breakpoints.get_mut(ix)) else {
392            return;
393        };
394        if self.strip_mode.is_some() && self.input.focus_handle(cx).contains_focused(window, cx) {
395            cx.propagate();
396            return;
397        }
398
399        match &mut entry.kind {
400            BreakpointEntryKind::LineBreakpoint(line_breakpoint) => {
401                let path = line_breakpoint.breakpoint.path.clone();
402                let row = line_breakpoint.breakpoint.row;
403                self.edit_line_breakpoint(path, row, BreakpointEditAction::InvertState, cx);
404            }
405            BreakpointEntryKind::ExceptionBreakpoint(exception_breakpoint) => {
406                let id = exception_breakpoint.id.clone();
407                self.toggle_exception_breakpoint(&id, cx);
408            }
409            BreakpointEntryKind::DataBreakpoint(data_breakpoint) => {
410                let id = data_breakpoint.0.dap.data_id.clone();
411                self.toggle_data_breakpoint(&id, cx);
412            }
413        }
414        cx.notify();
415    }
416
417    fn unset_breakpoint(
418        &mut self,
419        _: &UnsetBreakpoint,
420        _window: &mut Window,
421        cx: &mut Context<Self>,
422    ) {
423        let Some(entry) = self.selected_ix.and_then(|ix| self.breakpoints.get_mut(ix)) else {
424            return;
425        };
426
427        if let BreakpointEntryKind::LineBreakpoint(line_breakpoint) = &mut entry.kind {
428            let path = line_breakpoint.breakpoint.path.clone();
429            let row = line_breakpoint.breakpoint.row;
430            self.edit_line_breakpoint(path, row, BreakpointEditAction::Toggle, cx);
431        }
432        cx.notify();
433    }
434
435    fn previous_breakpoint_property(
436        &mut self,
437        _: &PreviousBreakpointProperty,
438        window: &mut Window,
439        cx: &mut Context<Self>,
440    ) {
441        let next_mode = match self.strip_mode {
442            Some(ActiveBreakpointStripMode::Log) => None,
443            Some(ActiveBreakpointStripMode::Condition) => Some(ActiveBreakpointStripMode::Log),
444            Some(ActiveBreakpointStripMode::HitCondition) => {
445                Some(ActiveBreakpointStripMode::Condition)
446            }
447            None => Some(ActiveBreakpointStripMode::HitCondition),
448        };
449        if let Some(mode) = next_mode {
450            self.set_active_breakpoint_property(mode, window, cx);
451        } else {
452            self.strip_mode.take();
453        }
454
455        cx.notify();
456    }
457    fn next_breakpoint_property(
458        &mut self,
459        _: &NextBreakpointProperty,
460        window: &mut Window,
461        cx: &mut Context<Self>,
462    ) {
463        let next_mode = match self.strip_mode {
464            Some(ActiveBreakpointStripMode::Log) => Some(ActiveBreakpointStripMode::Condition),
465            Some(ActiveBreakpointStripMode::Condition) => {
466                Some(ActiveBreakpointStripMode::HitCondition)
467            }
468            Some(ActiveBreakpointStripMode::HitCondition) => None,
469            None => Some(ActiveBreakpointStripMode::Log),
470        };
471        if let Some(mode) = next_mode {
472            self.set_active_breakpoint_property(mode, window, cx);
473        } else {
474            self.strip_mode.take();
475        }
476        cx.notify();
477    }
478
479    fn toggle_data_breakpoint(&mut self, id: &str, cx: &mut Context<Self>) {
480        if let Some(session) = &self.session {
481            session.update(cx, |this, cx| {
482                this.toggle_data_breakpoint(id, cx);
483            });
484        }
485    }
486
487    fn toggle_exception_breakpoint(&mut self, id: &str, cx: &mut Context<Self>) {
488        if let Some(session) = &self.session {
489            session.update(cx, |this, cx| {
490                this.toggle_exception_breakpoint(id, cx);
491            });
492            cx.notify();
493            const EXCEPTION_SERIALIZATION_INTERVAL: Duration = Duration::from_secs(1);
494            self.serialize_exception_breakpoints_task = Some(cx.spawn(async move |this, cx| {
495                cx.background_executor()
496                    .timer(EXCEPTION_SERIALIZATION_INTERVAL)
497                    .await;
498                this.update(cx, |this, cx| this.serialize_exception_breakpoints(cx))?
499                    .await?;
500                Ok(())
501            }));
502        }
503    }
504
505    fn kvp_key(adapter_name: &str) -> String {
506        format!("debug_adapter_`{adapter_name}`_persistence")
507    }
508    fn serialize_exception_breakpoints(
509        &mut self,
510        cx: &mut Context<Self>,
511    ) -> Task<anyhow::Result<()>> {
512        if let Some(session) = self.session.as_ref() {
513            let key = {
514                let session = session.read(cx);
515                let name = session.adapter().0;
516                Self::kvp_key(&name)
517            };
518            let settings = self.dap_store.update(cx, |this, cx| {
519                this.sync_adapter_options(session, cx);
520            });
521            let value = serde_json::to_string(&settings);
522
523            let kvp = KeyValueStore::global(cx);
524            cx.background_executor()
525                .spawn(async move { kvp.write_kvp(key, value?).await })
526        } else {
527            Task::ready(Result::Ok(()))
528        }
529    }
530
531    fn deserialize_exception_breakpoints(
532        &self,
533        adapter_name: DebugAdapterName,
534        cx: &mut Context<Self>,
535    ) -> anyhow::Result<()> {
536        let Some(val) = KeyValueStore::global(cx).read_kvp(&Self::kvp_key(&adapter_name))? else {
537            return Ok(());
538        };
539        let value: PersistedAdapterOptions = serde_json::from_str(&val)?;
540        self.dap_store
541            .update(cx, |this, _| this.set_adapter_options(adapter_name, value));
542
543        Ok(())
544    }
545
546    fn render_list(&mut self, cx: &mut Context<Self>) -> impl IntoElement {
547        let selected_ix = self.selected_ix;
548        let focus_handle = self.focus_handle.clone();
549        let supported_breakpoint_properties = self
550            .session
551            .as_ref()
552            .map(|session| SupportedBreakpointProperties::from(session.read(cx).capabilities()))
553            .unwrap_or_else(SupportedBreakpointProperties::all);
554        let strip_mode = self.strip_mode;
555
556        uniform_list(
557            "breakpoint-list",
558            self.breakpoints.len(),
559            cx.processor(move |this, range: Range<usize>, _, _| {
560                range
561                    .clone()
562                    .zip(&mut this.breakpoints[range])
563                    .map(|(ix, breakpoint)| {
564                        breakpoint
565                            .render(
566                                strip_mode,
567                                supported_breakpoint_properties,
568                                ix,
569                                Some(ix) == selected_ix,
570                                focus_handle.clone(),
571                            )
572                            .into_any_element()
573                    })
574                    .collect()
575            }),
576        )
577        .with_horizontal_sizing_behavior(gpui::ListHorizontalSizingBehavior::Unconstrained)
578        .with_width_from_item(self.max_width_index)
579        .track_scroll(&self.scroll_handle)
580        .flex_1()
581    }
582
583    pub(crate) fn render_control_strip(&self) -> AnyElement {
584        let selection_kind = self.selection_kind();
585        let focus_handle = self.focus_handle.clone();
586
587        let remove_breakpoint_tooltip = selection_kind.map(|(kind, _)| match kind {
588            SelectedBreakpointKind::Source => "Remove breakpoint from a breakpoint list",
589            SelectedBreakpointKind::Exception => {
590                "Exception Breakpoints cannot be removed from the breakpoint list"
591            }
592            SelectedBreakpointKind::Data => "Remove data breakpoint from a breakpoint list",
593        });
594
595        let toggle_label = selection_kind.map(|(_, is_enabled)| {
596            if is_enabled {
597                (
598                    "Disable Breakpoint",
599                    "Disable a breakpoint without removing it from the list",
600                )
601            } else {
602                ("Enable Breakpoint", "Re-enable a breakpoint")
603            }
604        });
605
606        h_flex()
607            .child(
608                IconButton::new(
609                    "disable-breakpoint-breakpoint-list",
610                    IconName::DebugDisabledBreakpoint,
611                )
612                .icon_size(IconSize::Small)
613                .when_some(toggle_label, |this, (label, meta)| {
614                    this.tooltip({
615                        let focus_handle = focus_handle.clone();
616                        move |_window, cx| {
617                            Tooltip::with_meta_in(
618                                label,
619                                Some(&ToggleEnableBreakpoint),
620                                meta,
621                                &focus_handle,
622                                cx,
623                            )
624                        }
625                    })
626                })
627                .disabled(selection_kind.is_none())
628                .on_click({
629                    let focus_handle = focus_handle.clone();
630                    move |_, window, cx| {
631                        focus_handle.focus(window, cx);
632                        window.dispatch_action(ToggleEnableBreakpoint.boxed_clone(), cx)
633                    }
634                }),
635            )
636            .child(
637                IconButton::new("remove-breakpoint-breakpoint-list", IconName::Trash)
638                    .icon_size(IconSize::Small)
639                    .when_some(remove_breakpoint_tooltip, |this, tooltip| {
640                        this.tooltip({
641                            let focus_handle = focus_handle.clone();
642                            move |_window, cx| {
643                                Tooltip::with_meta_in(
644                                    "Remove Breakpoint",
645                                    Some(&UnsetBreakpoint),
646                                    tooltip,
647                                    &focus_handle,
648                                    cx,
649                                )
650                            }
651                        })
652                    })
653                    .disabled(
654                        selection_kind.map(|kind| kind.0) != Some(SelectedBreakpointKind::Source),
655                    )
656                    .on_click({
657                        move |_, window, cx| {
658                            focus_handle.focus(window, cx);
659                            window.dispatch_action(UnsetBreakpoint.boxed_clone(), cx)
660                        }
661                    }),
662            )
663            .into_any_element()
664    }
665}
666
667impl Render for BreakpointList {
668    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl ui::IntoElement {
669        let breakpoints = self.breakpoint_store.read(cx).all_source_breakpoints(cx);
670        self.breakpoints.clear();
671        let path_style = self.worktree_store.read(cx).path_style();
672        let weak = cx.weak_entity();
673        let breakpoints = breakpoints.into_iter().flat_map(|(path, mut breakpoints)| {
674            let relative_worktree_path = self
675                .worktree_store
676                .read(cx)
677                .find_worktree(&path, cx)
678                .and_then(|(worktree, relative_path)| {
679                    worktree
680                        .read(cx)
681                        .is_visible()
682                        .then(|| worktree.read(cx).root_name().join(&relative_path))
683                });
684            breakpoints.sort_by_key(|breakpoint| breakpoint.row);
685            let weak = weak.clone();
686            breakpoints.into_iter().filter_map(move |breakpoint| {
687                debug_assert_eq!(&path, &breakpoint.path);
688                let file_name = breakpoint.path.file_name()?;
689                let breakpoint_path = RelPath::new(&breakpoint.path, path_style).ok();
690
691                let dir = relative_worktree_path
692                    .as_deref()
693                    .or(breakpoint_path.as_deref())?
694                    .parent()
695                    .map(|parent| SharedString::from(parent.display(path_style).to_string()));
696                let name = file_name
697                    .to_str()
698                    .map(ToOwned::to_owned)
699                    .map(SharedString::from)?;
700                let weak = weak.clone();
701                let line = breakpoint.row + 1;
702                Some(BreakpointEntry {
703                    kind: BreakpointEntryKind::LineBreakpoint(LineBreakpoint {
704                        name,
705                        dir,
706                        line,
707                        breakpoint,
708                    }),
709                    weak,
710                })
711            })
712        });
713        let exception_breakpoints = self.session.as_ref().into_iter().flat_map(|session| {
714            session
715                .read(cx)
716                .exception_breakpoints()
717                .map(|(data, is_enabled)| BreakpointEntry {
718                    kind: BreakpointEntryKind::ExceptionBreakpoint(ExceptionBreakpoint {
719                        id: data.filter.clone(),
720                        data: data.clone(),
721                        is_enabled: *is_enabled,
722                    }),
723                    weak: weak.clone(),
724                })
725        });
726        let data_breakpoints = self.session.as_ref().into_iter().flat_map(|session| {
727            session
728                .read(cx)
729                .data_breakpoints()
730                .map(|state| BreakpointEntry {
731                    kind: BreakpointEntryKind::DataBreakpoint(DataBreakpoint(state.clone())),
732                    weak: weak.clone(),
733                })
734        });
735        self.breakpoints.extend(
736            breakpoints
737                .chain(data_breakpoints)
738                .chain(exception_breakpoints),
739        );
740
741        let text_pixels = ui::TextSize::Default.pixels(cx).to_f64() as f32;
742
743        self.max_width_index = self
744            .breakpoints
745            .iter()
746            .map(|entry| match &entry.kind {
747                BreakpointEntryKind::LineBreakpoint(line_bp) => {
748                    let name_and_line = format!("{}:{}", line_bp.name, line_bp.line);
749                    let dir_len = line_bp.dir.as_ref().map(|d| d.len()).unwrap_or(0);
750                    (name_and_line.len() + dir_len) as f32 * text_pixels
751                }
752                BreakpointEntryKind::ExceptionBreakpoint(exc_bp) => {
753                    exc_bp.data.label.len() as f32 * text_pixels
754                }
755                BreakpointEntryKind::DataBreakpoint(data_bp) => {
756                    data_bp.0.context.human_readable_label().len() as f32 * text_pixels
757                }
758            })
759            .position_max_by(|left, right| left.total_cmp(right));
760
761        v_flex()
762            .id("breakpoint-list")
763            .key_context("BreakpointList")
764            .track_focus(&self.focus_handle)
765            .on_action(cx.listener(Self::select_next))
766            .on_action(cx.listener(Self::select_previous))
767            .on_action(cx.listener(Self::select_first))
768            .on_action(cx.listener(Self::select_last))
769            .on_action(cx.listener(Self::dismiss))
770            .on_action(cx.listener(Self::confirm))
771            .on_action(cx.listener(Self::toggle_enable_breakpoint))
772            .on_action(cx.listener(Self::unset_breakpoint))
773            .on_action(cx.listener(Self::next_breakpoint_property))
774            .on_action(cx.listener(Self::previous_breakpoint_property))
775            .size_full()
776            .pt_1()
777            .child(self.render_list(cx))
778            .custom_scrollbars(
779                ui::Scrollbars::new(ScrollAxes::Both)
780                    .tracked_scroll_handle(&self.scroll_handle)
781                    .with_track_along(ScrollAxes::Both, cx.theme().colors().panel_background)
782                    .tracked_entity(cx.entity_id()),
783                window,
784                cx,
785            )
786            .when_some(self.strip_mode, |this, _| {
787                this.child(Divider::horizontal().color(DividerColor::Border))
788                    .child(
789                        h_flex()
790                            .p_1()
791                            .rounded_sm()
792                            .bg(cx.theme().colors().editor_background)
793                            .border_1()
794                            .when(
795                                self.input.focus_handle(cx).contains_focused(window, cx),
796                                |this| {
797                                    let colors = cx.theme().colors();
798
799                                    let border_color = if self.input.read(cx).read_only(cx) {
800                                        colors.border_disabled
801                                    } else {
802                                        colors.border_transparent
803                                    };
804
805                                    this.border_color(border_color)
806                                },
807                            )
808                            .child(self.input.clone()),
809                    )
810            })
811    }
812}
813
814#[derive(Clone, Debug)]
815struct LineBreakpoint {
816    name: SharedString,
817    dir: Option<SharedString>,
818    line: u32,
819    breakpoint: SourceBreakpoint,
820}
821
822impl LineBreakpoint {
823    fn render(
824        &mut self,
825        props: SupportedBreakpointProperties,
826        strip_mode: Option<ActiveBreakpointStripMode>,
827        ix: usize,
828        is_selected: bool,
829        focus_handle: FocusHandle,
830        weak: WeakEntity<BreakpointList>,
831    ) -> ListItem {
832        let icon_name = if self.breakpoint.state.is_enabled() {
833            IconName::DebugBreakpoint
834        } else {
835            IconName::DebugDisabledBreakpoint
836        };
837        let path = self.breakpoint.path.clone();
838        let row = self.breakpoint.row;
839        let is_enabled = self.breakpoint.state.is_enabled();
840
841        let indicator = div()
842            .id(SharedString::from(format!(
843                "breakpoint-ui-toggle-{:?}/{}:{}",
844                self.dir, self.name, self.line
845            )))
846            .child(
847                Icon::new(icon_name)
848                    .color(Color::Debugger)
849                    .size(IconSize::XSmall),
850            )
851            .tooltip({
852                let focus_handle = focus_handle.clone();
853                move |_window, cx| {
854                    Tooltip::for_action_in(
855                        if is_enabled {
856                            "Disable Breakpoint"
857                        } else {
858                            "Enable Breakpoint"
859                        },
860                        &ToggleEnableBreakpoint,
861                        &focus_handle,
862                        cx,
863                    )
864                }
865            })
866            .on_click({
867                let weak = weak.clone();
868                let path = path.clone();
869                move |_, _, cx| {
870                    weak.update(cx, |breakpoint_list, cx| {
871                        breakpoint_list.edit_line_breakpoint(
872                            path.clone(),
873                            row,
874                            BreakpointEditAction::InvertState,
875                            cx,
876                        );
877                    })
878                    .ok();
879                }
880            })
881            .on_mouse_down(MouseButton::Left, move |_, _, _| {});
882
883        ListItem::new(SharedString::from(format!(
884            "breakpoint-ui-item-{:?}/{}:{}",
885            self.dir, self.name, self.line
886        )))
887        .toggle_state(is_selected)
888        .inset(true)
889        .on_click({
890            let weak = weak.clone();
891            move |_, window, cx| {
892                weak.update(cx, |breakpoint_list, cx| {
893                    breakpoint_list.select_ix(Some(ix), window, cx);
894                })
895                .ok();
896            }
897        })
898        .on_secondary_mouse_down(|_, _, cx| {
899            cx.stop_propagation();
900        })
901        .start_slot(indicator)
902        .child(
903            h_flex()
904                .id(SharedString::from(format!(
905                    "breakpoint-ui-on-click-go-to-line-{:?}/{}:{}",
906                    self.dir, self.name, self.line
907                )))
908                .w_full()
909                .gap_1()
910                .min_h(rems_from_px(26.))
911                .justify_between()
912                .on_click({
913                    let weak = weak.clone();
914                    move |_, window, cx| {
915                        weak.update(cx, |breakpoint_list, cx| {
916                            breakpoint_list.select_ix(Some(ix), window, cx);
917                            breakpoint_list.go_to_line_breakpoint(path.clone(), row, window, cx);
918                        })
919                        .ok();
920                    }
921                })
922                .child(
923                    h_flex()
924                        .id("label-container")
925                        .gap_0p5()
926                        .child(
927                            Label::new(format!("{}:{}", self.name, self.line))
928                                .size(LabelSize::Small)
929                                .line_height_style(ui::LineHeightStyle::UiLabel),
930                        )
931                        .children(self.dir.as_ref().and_then(|dir| {
932                            let path_without_root = Path::new(dir.as_ref())
933                                .components()
934                                .skip(1)
935                                .collect::<PathBuf>();
936                            path_without_root.components().next()?;
937                            Some(
938                                Label::new(path_without_root.to_string_lossy().into_owned())
939                                    .color(Color::Muted)
940                                    .size(LabelSize::Small)
941                                    .line_height_style(ui::LineHeightStyle::UiLabel)
942                                    .truncate(),
943                            )
944                        }))
945                        .when_some(self.dir.as_ref(), |this, parent_dir| {
946                            this.tooltip(Tooltip::text(format!(
947                                "Worktree parent path: {parent_dir}"
948                            )))
949                        }),
950                )
951                .child(BreakpointOptionsStrip {
952                    props,
953                    breakpoint: BreakpointEntry {
954                        kind: BreakpointEntryKind::LineBreakpoint(self.clone()),
955                        weak,
956                    },
957                    is_selected,
958                    focus_handle,
959                    strip_mode,
960                    index: ix,
961                }),
962        )
963    }
964}
965
966#[derive(Clone, Debug)]
967struct ExceptionBreakpoint {
968    id: String,
969    data: ExceptionBreakpointsFilter,
970    is_enabled: bool,
971}
972
973#[derive(Clone, Debug)]
974struct DataBreakpoint(project::debugger::session::DataBreakpointState);
975
976impl DataBreakpoint {
977    fn render(
978        &self,
979        props: SupportedBreakpointProperties,
980        strip_mode: Option<ActiveBreakpointStripMode>,
981        ix: usize,
982        is_selected: bool,
983        focus_handle: FocusHandle,
984        list: WeakEntity<BreakpointList>,
985    ) -> ListItem {
986        let color = if self.0.is_enabled {
987            Color::Debugger
988        } else {
989            Color::Muted
990        };
991        let is_enabled = self.0.is_enabled;
992        let id = self.0.dap.data_id.clone();
993
994        ListItem::new(SharedString::from(format!(
995            "data-breakpoint-ui-item-{}",
996            self.0.dap.data_id
997        )))
998        .toggle_state(is_selected)
999        .inset(true)
1000        .start_slot(
1001            div()
1002                .id(SharedString::from(format!(
1003                    "data-breakpoint-ui-item-{}-click-handler",
1004                    self.0.dap.data_id
1005                )))
1006                .child(
1007                    Icon::new(IconName::Binary)
1008                        .color(color)
1009                        .size(IconSize::Small),
1010                )
1011                .tooltip({
1012                    let focus_handle = focus_handle.clone();
1013                    move |_window, cx| {
1014                        Tooltip::for_action_in(
1015                            if is_enabled {
1016                                "Disable Data Breakpoint"
1017                            } else {
1018                                "Enable Data Breakpoint"
1019                            },
1020                            &ToggleEnableBreakpoint,
1021                            &focus_handle,
1022                            cx,
1023                        )
1024                    }
1025                })
1026                .on_click({
1027                    let list = list.clone();
1028                    move |_, _, cx| {
1029                        list.update(cx, |this, cx| {
1030                            this.toggle_data_breakpoint(&id, cx);
1031                        })
1032                        .ok();
1033                    }
1034                }),
1035        )
1036        .child(
1037            h_flex()
1038                .w_full()
1039                .gap_1()
1040                .min_h(rems_from_px(26.))
1041                .justify_between()
1042                .child(
1043                    v_flex()
1044                        .py_1()
1045                        .gap_1()
1046                        .justify_center()
1047                        .id(("data-breakpoint-label", ix))
1048                        .child(
1049                            Label::new(self.0.context.human_readable_label())
1050                                .size(LabelSize::Small)
1051                                .line_height_style(ui::LineHeightStyle::UiLabel),
1052                        ),
1053                )
1054                .child(BreakpointOptionsStrip {
1055                    props,
1056                    breakpoint: BreakpointEntry {
1057                        kind: BreakpointEntryKind::DataBreakpoint(self.clone()),
1058                        weak: list,
1059                    },
1060                    is_selected,
1061                    focus_handle,
1062                    strip_mode,
1063                    index: ix,
1064                }),
1065        )
1066    }
1067}
1068
1069impl ExceptionBreakpoint {
1070    fn render(
1071        &mut self,
1072        props: SupportedBreakpointProperties,
1073        strip_mode: Option<ActiveBreakpointStripMode>,
1074        ix: usize,
1075        is_selected: bool,
1076        focus_handle: FocusHandle,
1077        list: WeakEntity<BreakpointList>,
1078    ) -> ListItem {
1079        let color = if self.is_enabled {
1080            Color::Debugger
1081        } else {
1082            Color::Muted
1083        };
1084        let id = SharedString::from(&self.id);
1085        let is_enabled = self.is_enabled;
1086        let weak = list.clone();
1087
1088        ListItem::new(SharedString::from(format!(
1089            "exception-breakpoint-ui-item-{}",
1090            self.id
1091        )))
1092        .toggle_state(is_selected)
1093        .inset(true)
1094        .on_click({
1095            let list = list.clone();
1096            move |_, window, cx| {
1097                list.update(cx, |list, cx| list.select_ix(Some(ix), window, cx))
1098                    .ok();
1099            }
1100        })
1101        .on_secondary_mouse_down(|_, _, cx| {
1102            cx.stop_propagation();
1103        })
1104        .start_slot(
1105            div()
1106                .id(SharedString::from(format!(
1107                    "exception-breakpoint-ui-item-{}-click-handler",
1108                    self.id
1109                )))
1110                .child(
1111                    Icon::new(IconName::Flame)
1112                        .color(color)
1113                        .size(IconSize::Small),
1114                )
1115                .tooltip({
1116                    let focus_handle = focus_handle.clone();
1117                    move |_window, cx| {
1118                        Tooltip::for_action_in(
1119                            if is_enabled {
1120                                "Disable Exception Breakpoint"
1121                            } else {
1122                                "Enable Exception Breakpoint"
1123                            },
1124                            &ToggleEnableBreakpoint,
1125                            &focus_handle,
1126                            cx,
1127                        )
1128                    }
1129                })
1130                .on_click({
1131                    move |_, _, cx| {
1132                        list.update(cx, |this, cx| {
1133                            this.toggle_exception_breakpoint(&id, cx);
1134                        })
1135                        .ok();
1136                    }
1137                }),
1138        )
1139        .child(
1140            h_flex()
1141                .w_full()
1142                .gap_1()
1143                .min_h(rems_from_px(26.))
1144                .justify_between()
1145                .child(
1146                    v_flex()
1147                        .py_1()
1148                        .gap_1()
1149                        .justify_center()
1150                        .id(("exception-breakpoint-label", ix))
1151                        .child(
1152                            Label::new(self.data.label.clone())
1153                                .size(LabelSize::Small)
1154                                .line_height_style(ui::LineHeightStyle::UiLabel),
1155                        )
1156                        .when_some(self.data.description.clone(), |el, description| {
1157                            el.tooltip(Tooltip::text(description))
1158                        }),
1159                )
1160                .child(BreakpointOptionsStrip {
1161                    props,
1162                    breakpoint: BreakpointEntry {
1163                        kind: BreakpointEntryKind::ExceptionBreakpoint(self.clone()),
1164                        weak,
1165                    },
1166                    is_selected,
1167                    focus_handle,
1168                    strip_mode,
1169                    index: ix,
1170                }),
1171        )
1172    }
1173}
1174#[derive(Clone, Debug)]
1175enum BreakpointEntryKind {
1176    LineBreakpoint(LineBreakpoint),
1177    ExceptionBreakpoint(ExceptionBreakpoint),
1178    DataBreakpoint(DataBreakpoint),
1179}
1180
1181#[derive(Clone, Debug)]
1182struct BreakpointEntry {
1183    kind: BreakpointEntryKind,
1184    weak: WeakEntity<BreakpointList>,
1185}
1186
1187impl BreakpointEntry {
1188    fn render(
1189        &mut self,
1190        strip_mode: Option<ActiveBreakpointStripMode>,
1191        props: SupportedBreakpointProperties,
1192        ix: usize,
1193        is_selected: bool,
1194        focus_handle: FocusHandle,
1195    ) -> ListItem {
1196        match &mut self.kind {
1197            BreakpointEntryKind::LineBreakpoint(line_breakpoint) => line_breakpoint.render(
1198                props,
1199                strip_mode,
1200                ix,
1201                is_selected,
1202                focus_handle,
1203                self.weak.clone(),
1204            ),
1205            BreakpointEntryKind::ExceptionBreakpoint(exception_breakpoint) => exception_breakpoint
1206                .render(
1207                    props.for_exception_breakpoints(),
1208                    strip_mode,
1209                    ix,
1210                    is_selected,
1211                    focus_handle,
1212                    self.weak.clone(),
1213                ),
1214            BreakpointEntryKind::DataBreakpoint(data_breakpoint) => data_breakpoint.render(
1215                props.for_data_breakpoints(),
1216                strip_mode,
1217                ix,
1218                is_selected,
1219                focus_handle,
1220                self.weak.clone(),
1221            ),
1222        }
1223    }
1224
1225    fn id(&self) -> SharedString {
1226        match &self.kind {
1227            BreakpointEntryKind::LineBreakpoint(line_breakpoint) => format!(
1228                "source-breakpoint-control-strip-{:?}:{}",
1229                line_breakpoint.breakpoint.path, line_breakpoint.breakpoint.row
1230            )
1231            .into(),
1232            BreakpointEntryKind::ExceptionBreakpoint(exception_breakpoint) => format!(
1233                "exception-breakpoint-control-strip--{}",
1234                exception_breakpoint.id
1235            )
1236            .into(),
1237            BreakpointEntryKind::DataBreakpoint(data_breakpoint) => format!(
1238                "data-breakpoint-control-strip--{}",
1239                data_breakpoint.0.dap.data_id
1240            )
1241            .into(),
1242        }
1243    }
1244
1245    fn has_log(&self) -> bool {
1246        match &self.kind {
1247            BreakpointEntryKind::LineBreakpoint(line_breakpoint) => {
1248                line_breakpoint.breakpoint.message.is_some()
1249            }
1250            _ => false,
1251        }
1252    }
1253
1254    fn has_condition(&self) -> bool {
1255        match &self.kind {
1256            BreakpointEntryKind::LineBreakpoint(line_breakpoint) => {
1257                line_breakpoint.breakpoint.condition.is_some()
1258            }
1259            // We don't support conditions on exception/data breakpoints
1260            _ => false,
1261        }
1262    }
1263
1264    fn has_hit_condition(&self) -> bool {
1265        match &self.kind {
1266            BreakpointEntryKind::LineBreakpoint(line_breakpoint) => {
1267                line_breakpoint.breakpoint.hit_condition.is_some()
1268            }
1269            _ => false,
1270        }
1271    }
1272}
1273
1274bitflags::bitflags! {
1275    #[derive(Clone, Copy)]
1276    pub struct SupportedBreakpointProperties: u32 {
1277        const LOG = 1 << 0;
1278        const CONDITION = 1 << 1;
1279        const HIT_CONDITION = 1 << 2;
1280        // Conditions for exceptions can be set only when exception filters are supported.
1281        const EXCEPTION_FILTER_OPTIONS = 1 << 3;
1282    }
1283}
1284
1285impl From<&Capabilities> for SupportedBreakpointProperties {
1286    fn from(caps: &Capabilities) -> Self {
1287        let mut this = Self::empty();
1288        for (prop, offset) in [
1289            (caps.supports_log_points, Self::LOG),
1290            (caps.supports_conditional_breakpoints, Self::CONDITION),
1291            (
1292                caps.supports_hit_conditional_breakpoints,
1293                Self::HIT_CONDITION,
1294            ),
1295            (
1296                caps.supports_exception_options,
1297                Self::EXCEPTION_FILTER_OPTIONS,
1298            ),
1299        ] {
1300            if prop.unwrap_or_default() {
1301                this.insert(offset);
1302            }
1303        }
1304        this
1305    }
1306}
1307
1308impl SupportedBreakpointProperties {
1309    fn for_exception_breakpoints(self) -> Self {
1310        // TODO: we don't yet support conditions for exception breakpoints at the data layer, hence all props are disabled here.
1311        Self::empty()
1312    }
1313    fn for_data_breakpoints(self) -> Self {
1314        // TODO: we don't yet support conditions for data breakpoints at the data layer, hence all props are disabled here.
1315        Self::empty()
1316    }
1317}
1318#[derive(IntoElement)]
1319struct BreakpointOptionsStrip {
1320    props: SupportedBreakpointProperties,
1321    breakpoint: BreakpointEntry,
1322    is_selected: bool,
1323    focus_handle: FocusHandle,
1324    strip_mode: Option<ActiveBreakpointStripMode>,
1325    index: usize,
1326}
1327
1328impl BreakpointOptionsStrip {
1329    fn is_toggled(&self, expected_mode: ActiveBreakpointStripMode) -> bool {
1330        self.is_selected && self.strip_mode == Some(expected_mode)
1331    }
1332
1333    fn on_click_callback(
1334        &self,
1335        mode: ActiveBreakpointStripMode,
1336    ) -> impl for<'a> Fn(&ClickEvent, &mut Window, &'a mut App) + use<> {
1337        let list = self.breakpoint.weak.clone();
1338        let ix = self.index;
1339        move |_, window, cx| {
1340            list.update(cx, |this, cx| {
1341                if this.strip_mode != Some(mode) {
1342                    this.set_active_breakpoint_property(mode, window, cx);
1343                } else if this.selected_ix == Some(ix) {
1344                    this.strip_mode.take();
1345                } else {
1346                    cx.propagate();
1347                }
1348            })
1349            .ok();
1350        }
1351    }
1352
1353    fn add_focus_styles(
1354        &self,
1355        kind: ActiveBreakpointStripMode,
1356        available: bool,
1357        window: &Window,
1358        cx: &App,
1359    ) -> impl Fn(Div) -> Div {
1360        move |this: Div| {
1361            // Avoid layout shifts in case there's no colored border
1362            let this = this.border_1().rounded_sm();
1363            let color = cx.theme().colors();
1364
1365            if self.is_selected && self.strip_mode == Some(kind) {
1366                if self.focus_handle.is_focused(window) {
1367                    this.bg(color.editor_background)
1368                        .border_color(color.border_focused)
1369                } else {
1370                    this.border_color(color.border)
1371                }
1372            } else if !available {
1373                this.border_color(color.border_transparent)
1374            } else {
1375                this
1376            }
1377        }
1378    }
1379}
1380
1381impl RenderOnce for BreakpointOptionsStrip {
1382    fn render(self, window: &mut Window, cx: &mut App) -> impl IntoElement {
1383        let id = self.breakpoint.id();
1384        let supports_logs = self.props.contains(SupportedBreakpointProperties::LOG);
1385        let supports_condition = self
1386            .props
1387            .contains(SupportedBreakpointProperties::CONDITION);
1388        let supports_hit_condition = self
1389            .props
1390            .contains(SupportedBreakpointProperties::HIT_CONDITION);
1391        let has_logs = self.breakpoint.has_log();
1392        let has_condition = self.breakpoint.has_condition();
1393        let has_hit_condition = self.breakpoint.has_hit_condition();
1394        let style_for_toggle = |mode, is_enabled| {
1395            if is_enabled && self.strip_mode == Some(mode) && self.is_selected {
1396                ui::ButtonStyle::Filled
1397            } else {
1398                ui::ButtonStyle::Subtle
1399            }
1400        };
1401        let color_for_toggle = |is_enabled| {
1402            if is_enabled {
1403                Color::Default
1404            } else {
1405                Color::Muted
1406            }
1407        };
1408
1409        h_flex()
1410            .gap_px()
1411            .justify_end()
1412            .when(has_logs || self.is_selected, |this| {
1413                this.child(
1414                    div()
1415                    .map(self.add_focus_styles(
1416                        ActiveBreakpointStripMode::Log,
1417                        supports_logs,
1418                        window,
1419                        cx,
1420                    ))
1421                    .child(
1422                        IconButton::new(
1423                            SharedString::from(format!("{id}-log-toggle")),
1424                            IconName::Notepad,
1425                        )
1426                        .shape(ui::IconButtonShape::Square)
1427                        .style(style_for_toggle(ActiveBreakpointStripMode::Log, has_logs))
1428                        .icon_size(IconSize::Small)
1429                        .icon_color(color_for_toggle(has_logs))
1430                        .when(has_logs, |this| this.indicator(Indicator::dot().color(Color::Info)))
1431                        .disabled(!supports_logs)
1432                        .toggle_state(self.is_toggled(ActiveBreakpointStripMode::Log))
1433                        .on_click(self.on_click_callback(ActiveBreakpointStripMode::Log))
1434                        .tooltip(|_window, cx|  {
1435                            Tooltip::with_meta(
1436                                "Set Log Message",
1437                                None,
1438                                "Set log message to display (instead of stopping) when a breakpoint is hit.",
1439                                cx,
1440                            )
1441                        }),
1442                    )
1443                )
1444            })
1445            .when(has_condition || self.is_selected, |this| {
1446                this.child(
1447                    div()
1448                        .map(self.add_focus_styles(
1449                            ActiveBreakpointStripMode::Condition,
1450                            supports_condition,
1451                            window,
1452                            cx,
1453                        ))
1454                        .child(
1455                            IconButton::new(
1456                                SharedString::from(format!("{id}-condition-toggle")),
1457                                IconName::SplitAlt,
1458                            )
1459                            .shape(ui::IconButtonShape::Square)
1460                            .style(style_for_toggle(
1461                                ActiveBreakpointStripMode::Condition,
1462                                has_condition,
1463                            ))
1464                            .icon_size(IconSize::Small)
1465                            .icon_color(color_for_toggle(has_condition))
1466                            .when(has_condition, |this| this.indicator(Indicator::dot().color(Color::Info)))
1467                            .disabled(!supports_condition)
1468                            .toggle_state(self.is_toggled(ActiveBreakpointStripMode::Condition))
1469                            .on_click(self.on_click_callback(ActiveBreakpointStripMode::Condition))
1470                            .tooltip(|_window, cx|  {
1471                                Tooltip::with_meta(
1472                                    "Set Condition",
1473                                    None,
1474                                    "Set condition to evaluate when a breakpoint is hit. Program execution will stop only when the condition is met.",
1475                                    cx,
1476                                )
1477                            }),
1478                        )
1479                )
1480            })
1481            .when(has_hit_condition || self.is_selected, |this| {
1482                this.child(div()
1483                    .map(self.add_focus_styles(
1484                        ActiveBreakpointStripMode::HitCondition,
1485                        supports_hit_condition,
1486                        window,
1487                        cx,
1488                    ))
1489                    .child(
1490                        IconButton::new(
1491                            SharedString::from(format!("{id}-hit-condition-toggle")),
1492                            IconName::ArrowDown10,
1493                        )
1494                        .style(style_for_toggle(
1495                            ActiveBreakpointStripMode::HitCondition,
1496                            has_hit_condition,
1497                        ))
1498                        .shape(ui::IconButtonShape::Square)
1499                        .icon_size(IconSize::Small)
1500                        .icon_color(color_for_toggle(has_hit_condition))
1501                        .when(has_hit_condition, |this| this.indicator(Indicator::dot().color(Color::Info)))
1502                        .disabled(!supports_hit_condition)
1503                        .toggle_state(self.is_toggled(ActiveBreakpointStripMode::HitCondition))
1504                        .on_click(self.on_click_callback(ActiveBreakpointStripMode::HitCondition))
1505                        .tooltip(|_window, cx|  {
1506                            Tooltip::with_meta(
1507                                "Set Hit Condition",
1508                                None,
1509                                "Set expression that controls how many hits of the breakpoint are ignored.",
1510                                cx,
1511                            )
1512                        }),
1513                    ))
1514
1515            })
1516    }
1517}
1518
Served at tenant.openagents/omega Member data and write actions are omitted.