div_inspector.rs

  1use anyhow::{Result, anyhow};
  2use convert_case::{Case, Casing};
  3use editor::{
  4    Bias, CompletionProvider, Editor, EditorEvent, EditorMode, ExcerptId, MinimapVisibility,
  5    MultiBuffer,
  6};
  7use fuzzy::StringMatch;
  8use gpui::Hsla;
  9use gpui::{
 10    AsyncWindowContext, DivInspectorState, Entity, InspectorElementId, IntoElement,
 11    StyleRefinement, Task, Window, inspector_reflection::FunctionReflection, styled_reflection,
 12};
 13use language::language_settings::SoftWrap;
 14use language::{
 15    Anchor, Buffer, BufferSnapshot, CodeLabel, Diagnostic, DiagnosticEntry, DiagnosticSet,
 16    DiagnosticSeverity, LanguageServerId, ToOffset as _,
 17};
 18use project::lsp_store::CompletionDocumentation;
 19use project::{
 20    Completion, CompletionDisplayOptions, CompletionResponse, CompletionSource, Project,
 21    ProjectPath,
 22};
 23use std::fmt::Write as _;
 24use std::ops::Range;
 25use std::path::Path;
 26use std::rc::Rc;
 27use std::sync::LazyLock;
 28use strum::IntoEnumIterator;
 29use theme::{StatusColorField, ThemeColorField};
 30use ui::{Label, LabelSize, Tooltip, prelude::*, styled_ext_reflection, v_flex};
 31use util::{FieldAccessByEnum, TakeUntilExt, split_str_with_ranges};
 32
 33/// Path used for unsaved buffer that contains style json. To support the json language server, this
 34/// matches the name used in the generated schemas.
 35const ZED_INSPECTOR_STYLE_JSON: &str = util_macros::path!("/zed-inspector-style.json");
 36
 37pub(crate) struct DivInspector {
 38    state: State,
 39    project: Entity<Project>,
 40    inspector_id: Option<InspectorElementId>,
 41    inspector_state: Option<DivInspectorState>,
 42    /// Value of `DivInspectorState.base_style` when initially picked.
 43    initial_style: StyleRefinement,
 44    /// Portion of `initial_style` that can't be converted to rust code.
 45    unconvertible_style: StyleRefinement,
 46    /// Edits the user has made to the json buffer: `json_editor - (unconvertible_style + rust_editor)`.
 47    json_style_overrides: StyleRefinement,
 48    /// Error to display from parsing the json, or if serialization errors somehow occur.
 49    json_style_error: Option<SharedString>,
 50    /// Currently selected completion.
 51    rust_completion: Option<String>,
 52    /// Range that will be replaced by the completion if selected.
 53    rust_completion_replace_range: Option<Range<Anchor>>,
 54}
 55
 56enum State {
 57    Loading,
 58    BuffersLoaded {
 59        rust_style_buffer: Entity<Buffer>,
 60        json_style_buffer: Entity<Buffer>,
 61    },
 62    Ready {
 63        rust_style_buffer: Entity<Buffer>,
 64        rust_style_editor: Entity<Editor>,
 65        json_style_buffer: Entity<Buffer>,
 66        json_style_editor: Entity<Editor>,
 67    },
 68    LoadError {
 69        message: SharedString,
 70    },
 71}
 72
 73impl DivInspector {
 74    pub fn new(
 75        project: Entity<Project>,
 76        window: &mut Window,
 77        cx: &mut Context<Self>,
 78    ) -> DivInspector {
 79        // Open the buffers once, so they can then be used for each editor.
 80        cx.spawn_in(window, {
 81            let languages = project.read(cx).languages().clone();
 82            let project = project.clone();
 83            async move |this, cx| {
 84                // Open the JSON style buffer in the inspector-specific project, so that it runs the
 85                // JSON language server.
 86                let json_style_buffer =
 87                    Self::create_buffer_in_project(ZED_INSPECTOR_STYLE_JSON, &project, cx).await;
 88
 89                // Create Rust style buffer without adding it to the project / buffer_store, so that
 90                // Rust Analyzer doesn't get started for it.
 91                let rust_language_result = languages.language_for_name("Rust").await;
 92                let rust_style_buffer = rust_language_result.and_then(|rust_language| {
 93                    cx.new(|cx| Buffer::local("", cx).with_language(rust_language, cx))
 94                });
 95
 96                match json_style_buffer.and_then(|json_style_buffer| {
 97                    rust_style_buffer
 98                        .map(|rust_style_buffer| (json_style_buffer, rust_style_buffer))
 99                }) {
100                    Ok((json_style_buffer, rust_style_buffer)) => {
101                        this.update_in(cx, |this, window, cx| {
102                            this.state = State::BuffersLoaded {
103                                json_style_buffer,
104                                rust_style_buffer,
105                            };
106
107                            // Initialize editors immediately instead of waiting for
108                            // `update_inspected_element`. This avoids continuing to show
109                            // "Loading..." until the user moves the mouse to a different element.
110                            if let Some(id) = this.inspector_id.take() {
111                                let inspector_state =
112                                    window.with_inspector_state(Some(&id), cx, |state, _window| {
113                                        state.clone()
114                                    });
115                                if let Some(inspector_state) = inspector_state {
116                                    this.update_inspected_element(&id, inspector_state, window, cx);
117                                    cx.notify();
118                                }
119                            }
120                        })
121                        .ok();
122                    }
123                    Err(err) => {
124                        this.update(cx, |this, _cx| {
125                            this.state = State::LoadError {
126                                message: format!(
127                                    "Failed to create buffers for style editing: {err}"
128                                )
129                                .into(),
130                            };
131                        })
132                        .ok();
133                    }
134                }
135            }
136        })
137        .detach();
138
139        DivInspector {
140            state: State::Loading,
141            project,
142            inspector_id: None,
143            inspector_state: None,
144            initial_style: StyleRefinement::default(),
145            unconvertible_style: StyleRefinement::default(),
146            json_style_overrides: StyleRefinement::default(),
147            rust_completion: None,
148            rust_completion_replace_range: None,
149            json_style_error: None,
150        }
151    }
152
153    pub fn update_inspected_element(
154        &mut self,
155        id: &InspectorElementId,
156        inspector_state: DivInspectorState,
157        window: &mut Window,
158        cx: &mut Context<Self>,
159    ) {
160        let style = (*inspector_state.base_style).clone();
161        self.inspector_state = Some(inspector_state);
162
163        if self.inspector_id.as_ref() == Some(id) {
164            return;
165        }
166
167        self.inspector_id = Some(id.clone());
168        self.initial_style = style.clone();
169
170        let (rust_style_buffer, json_style_buffer) = match &self.state {
171            State::BuffersLoaded {
172                rust_style_buffer,
173                json_style_buffer,
174            }
175            | State::Ready {
176                rust_style_buffer,
177                json_style_buffer,
178                ..
179            } => (rust_style_buffer.clone(), json_style_buffer.clone()),
180            State::Loading | State::LoadError { .. } => return,
181        };
182
183        let json_style_editor = self.create_editor(json_style_buffer.clone(), window, cx);
184        let rust_style_editor = self.create_editor(rust_style_buffer.clone(), window, cx);
185
186        rust_style_editor.update(cx, {
187            let div_inspector = cx.entity();
188            |rust_style_editor, _cx| {
189                rust_style_editor.set_completion_provider(Some(Rc::new(
190                    RustStyleCompletionProvider { div_inspector },
191                )));
192            }
193        });
194
195        let rust_style = match self.reset_style_editors(&rust_style_buffer, &json_style_buffer, cx)
196        {
197            Ok(rust_style) => {
198                self.json_style_error = None;
199                rust_style
200            }
201            Err(err) => {
202                self.json_style_error = Some(format!("{err}").into());
203                return;
204            }
205        };
206
207        cx.subscribe_in(&json_style_editor, window, {
208            let id = id.clone();
209            let rust_style_buffer = rust_style_buffer.clone();
210            move |this, editor, event: &EditorEvent, window, cx| {
211                if event == &EditorEvent::BufferEdited {
212                    let style_json = editor.read(cx).text(cx);
213                    match serde_json_lenient::from_str_lenient::<StyleRefinement>(&style_json) {
214                        Ok(new_style) => {
215                            let (rust_style, _) = this.style_from_rust_buffer_snapshot(
216                                &rust_style_buffer.read(cx).snapshot(),
217                                cx,
218                            );
219
220                            let mut unconvertible_plus_rust = this.unconvertible_style.clone();
221                            unconvertible_plus_rust.refine(&rust_style);
222
223                            // The serialization of `DefiniteLength::Fraction` does not perfectly
224                            // roundtrip because with f32, `(x / 100.0 * 100.0) == x` is not always
225                            // true (such as for `p_1_3`). This can cause these values to
226                            // erroneously appear in `json_style_overrides` since they are not
227                            // perfectly equal. Roundtripping before `subtract` fixes this.
228                            unconvertible_plus_rust =
229                                serde_json::to_string(&unconvertible_plus_rust)
230                                    .ok()
231                                    .and_then(|json| {
232                                        serde_json_lenient::from_str_lenient(&json).ok()
233                                    })
234                                    .unwrap_or(unconvertible_plus_rust);
235
236                            this.json_style_overrides =
237                                new_style.subtract(&unconvertible_plus_rust);
238
239                            window.with_inspector_state::<DivInspectorState, _>(
240                                Some(&id),
241                                cx,
242                                |inspector_state, _window| {
243                                    if let Some(inspector_state) = inspector_state.as_mut() {
244                                        *inspector_state.base_style = new_style;
245                                    }
246                                },
247                            );
248                            window.refresh();
249                            this.json_style_error = None;
250                        }
251                        Err(err) => this.json_style_error = Some(err.to_string().into()),
252                    }
253                }
254            }
255        })
256        .detach();
257
258        cx.subscribe(&rust_style_editor, {
259            let json_style_buffer = json_style_buffer.clone();
260            let rust_style_buffer = rust_style_buffer.clone();
261            move |this, _editor, event: &EditorEvent, cx| {
262                if let EditorEvent::BufferEdited = event {
263                    this.update_json_style_from_rust(&json_style_buffer, &rust_style_buffer, cx);
264                }
265            }
266        })
267        .detach();
268
269        self.unconvertible_style = style.subtract(&rust_style);
270        self.json_style_overrides = StyleRefinement::default();
271        self.state = State::Ready {
272            rust_style_buffer,
273            rust_style_editor,
274            json_style_buffer,
275            json_style_editor,
276        };
277    }
278
279    fn reset_style(&mut self, cx: &mut App) {
280        if let State::Ready {
281            rust_style_buffer,
282            json_style_buffer,
283            ..
284        } = &self.state
285        {
286            if let Err(err) =
287                self.reset_style_editors(&rust_style_buffer.clone(), &json_style_buffer.clone(), cx)
288            {
289                self.json_style_error = Some(format!("{err}").into());
290            } else {
291                self.json_style_error = None;
292            }
293        }
294    }
295
296    fn reset_style_editors(
297        &self,
298        rust_style_buffer: &Entity<Buffer>,
299        json_style_buffer: &Entity<Buffer>,
300        cx: &mut App,
301    ) -> Result<StyleRefinement> {
302        let json_text = match serde_json::to_string_pretty(&self.initial_style) {
303            Ok(json_text) => json_text,
304            Err(err) => {
305                return Err(anyhow!("Failed to convert style to JSON: {err}"));
306            }
307        };
308
309        let (rust_code, rust_style) = guess_rust_code_from_style(&self.initial_style, cx);
310        rust_style_buffer.update(cx, |rust_style_buffer, cx| {
311            rust_style_buffer.set_text(rust_code, cx);
312            let snapshot = rust_style_buffer.snapshot();
313            let (_, unrecognized_ranges) = self.style_from_rust_buffer_snapshot(&snapshot, cx);
314            Self::set_rust_buffer_diagnostics(
315                unrecognized_ranges,
316                rust_style_buffer,
317                &snapshot,
318                cx,
319            );
320        });
321        json_style_buffer.update(cx, |json_style_buffer, cx| {
322            json_style_buffer.set_text(json_text, cx);
323        });
324
325        Ok(rust_style)
326    }
327
328    fn handle_rust_completion_selection_change(
329        &mut self,
330        rust_completion: Option<String>,
331        cx: &mut Context<Self>,
332    ) {
333        self.rust_completion = rust_completion;
334        if let State::Ready {
335            rust_style_buffer,
336            json_style_buffer,
337            ..
338        } = &self.state
339        {
340            self.update_json_style_from_rust(
341                &json_style_buffer.clone(),
342                &rust_style_buffer.clone(),
343                cx,
344            );
345        }
346    }
347
348    fn update_json_style_from_rust(
349        &mut self,
350        json_style_buffer: &Entity<Buffer>,
351        rust_style_buffer: &Entity<Buffer>,
352        cx: &mut Context<Self>,
353    ) {
354        let rust_style = rust_style_buffer.update(cx, |rust_style_buffer, cx| {
355            let snapshot = rust_style_buffer.snapshot();
356            let (rust_style, unrecognized_ranges) =
357                self.style_from_rust_buffer_snapshot(&snapshot, cx);
358            Self::set_rust_buffer_diagnostics(
359                unrecognized_ranges,
360                rust_style_buffer,
361                &snapshot,
362                cx,
363            );
364            rust_style
365        });
366
367        // Preserve parts of the json style which do not come from the unconvertible style or rust
368        // style. This way user edits to the json style are preserved when they are not overridden
369        // by the rust style.
370        //
371        // This results in a behavior where user changes to the json style that do overlap with the
372        // rust style will get set to the rust style when the user edits the rust style. It would be
373        // possible to update the rust style when the json style changes, but this is undesirable
374        // as the user may be working on the actual code in the rust style.
375        let mut new_style = self.unconvertible_style.clone();
376        new_style.refine(&self.json_style_overrides);
377        let new_style = new_style.refined(rust_style);
378
379        match serde_json::to_string_pretty(&new_style) {
380            Ok(json) => {
381                json_style_buffer.update(cx, |json_style_buffer, cx| {
382                    json_style_buffer.set_text(json, cx);
383                });
384            }
385            Err(err) => {
386                self.json_style_error = Some(err.to_string().into());
387            }
388        }
389    }
390
391    fn style_from_rust_buffer_snapshot(
392        &self,
393        snapshot: &BufferSnapshot,
394        cx: &App,
395    ) -> (StyleRefinement, Vec<Range<Anchor>>) {
396        let method_names = if let Some((completion, completion_range)) = self
397            .rust_completion
398            .as_ref()
399            .zip(self.rust_completion_replace_range.as_ref())
400        {
401            let before_text = snapshot
402                .text_for_range(0..completion_range.start.to_offset(snapshot))
403                .collect::<String>();
404            let after_text = snapshot
405                .text_for_range(
406                    completion_range.end.to_offset(snapshot)
407                        ..snapshot.clip_offset(usize::MAX, Bias::Left),
408                )
409                .collect::<String>();
410            let mut method_names = split_str_with_ranges(&before_text, is_not_identifier_char)
411                .into_iter()
412                .map(|(range, name)| (Some(range), name.to_string()))
413                .collect::<Vec<_>>();
414            method_names.push((None, completion.clone()));
415            method_names.extend(
416                split_str_with_ranges(&after_text, is_not_identifier_char)
417                    .into_iter()
418                    .map(|(range, name)| (Some(range), name.to_string())),
419            );
420            method_names
421        } else {
422            split_str_with_ranges(&snapshot.text(), is_not_identifier_char)
423                .into_iter()
424                .map(|(range, name)| (Some(range), name.to_string()))
425                .collect::<Vec<_>>()
426        };
427
428        guess_style_from_rust_code(method_names, snapshot, cx)
429    }
430
431    fn set_rust_buffer_diagnostics(
432        unrecognized_ranges: Vec<Range<Anchor>>,
433        rust_style_buffer: &mut Buffer,
434        snapshot: &BufferSnapshot,
435        cx: &mut Context<Buffer>,
436    ) {
437        let diagnostic_entries = unrecognized_ranges
438            .into_iter()
439            .enumerate()
440            .map(|(ix, range)| DiagnosticEntry {
441                range,
442                diagnostic: Diagnostic {
443                    message: "unrecognized".to_string(),
444                    severity: DiagnosticSeverity::WARNING,
445                    is_primary: true,
446                    group_id: ix,
447                    ..Default::default()
448                },
449            });
450        let diagnostics = DiagnosticSet::from_sorted_entries(diagnostic_entries, snapshot);
451        rust_style_buffer.update_diagnostics(LanguageServerId(0), diagnostics, cx);
452    }
453
454    async fn create_buffer_in_project(
455        path: impl AsRef<Path>,
456        project: &Entity<Project>,
457        cx: &mut AsyncWindowContext,
458    ) -> Result<Entity<Buffer>> {
459        let worktree = project
460            .update(cx, |project, cx| project.create_worktree(path, false, cx))?
461            .await?;
462
463        let project_path = worktree.read_with(cx, |worktree, _cx| ProjectPath {
464            worktree_id: worktree.id(),
465            path: Path::new("").into(),
466        })?;
467
468        let buffer = project
469            .update(cx, |project, cx| project.open_path(project_path, cx))?
470            .await?
471            .1;
472
473        Ok(buffer)
474    }
475
476    fn create_editor(
477        &self,
478        buffer: Entity<Buffer>,
479        window: &mut Window,
480        cx: &mut Context<Self>,
481    ) -> Entity<Editor> {
482        cx.new(|cx| {
483            let multi_buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
484            let mut editor = Editor::new(
485                EditorMode::full(),
486                multi_buffer,
487                Some(self.project.clone()),
488                window,
489                cx,
490            );
491            editor.set_soft_wrap_mode(SoftWrap::EditorWidth, cx);
492            editor.set_show_line_numbers(false, cx);
493            editor.set_show_code_actions(false, cx);
494            editor.set_show_breakpoints(false, cx);
495            editor.set_show_git_diff_gutter(false, cx);
496            editor.set_show_runnables(false, cx);
497            editor.set_show_edit_predictions(Some(false), window, cx);
498            editor.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
499            editor
500        })
501    }
502}
503
504impl Render for DivInspector {
505    fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
506        v_flex()
507            .size_full()
508            .gap_2()
509            .when_some(self.inspector_state.as_ref(), |this, inspector_state| {
510                this.child(
511                    v_flex()
512                        .child(Label::new("Layout").size(LabelSize::Large))
513                        .child(render_layout_state(inspector_state, cx)),
514                )
515            })
516            .map(|this| match &self.state {
517                State::Loading | State::BuffersLoaded { .. } => {
518                    this.child(Label::new("Loading..."))
519                }
520                State::LoadError { message } => this.child(
521                    div()
522                        .w_full()
523                        .border_1()
524                        .border_color(Color::Error.color(cx))
525                        .child(Label::new(message)),
526                ),
527                State::Ready {
528                    rust_style_editor,
529                    json_style_editor,
530                    ..
531                } => this
532                    .child(
533                        v_flex()
534                            .gap_2()
535                            .child(
536                                h_flex()
537                                    .justify_between()
538                                    .child(Label::new("Rust Style").size(LabelSize::Large))
539                                    .child(
540                                        IconButton::new("reset-style", IconName::Eraser)
541                                            .tooltip(Tooltip::text("Reset style"))
542                                            .on_click(cx.listener(|this, _, _window, cx| {
543                                                this.reset_style(cx);
544                                            })),
545                                    ),
546                            )
547                            .child(div().h_64().child(rust_style_editor.clone())),
548                    )
549                    .child(
550                        v_flex()
551                            .gap_2()
552                            .child(Label::new("JSON Style").size(LabelSize::Large))
553                            .child(div().h_128().child(json_style_editor.clone()))
554                            .when_some(self.json_style_error.as_ref(), |this, last_error| {
555                                this.child(
556                                    div()
557                                        .w_full()
558                                        .border_1()
559                                        .border_color(Color::Error.color(cx))
560                                        .child(Label::new(last_error)),
561                                )
562                            }),
563                    ),
564            })
565            .into_any_element()
566    }
567}
568
569fn render_layout_state(inspector_state: &DivInspectorState, cx: &App) -> Div {
570    v_flex()
571        .child(
572            div()
573                .text_ui(cx)
574                .child(format!("Bounds: {}", inspector_state.bounds)),
575        )
576        .child(
577            div()
578                .id("content-size")
579                .text_ui(cx)
580                .tooltip(Tooltip::text("Size of the element's children"))
581                .child(
582                    if inspector_state.content_size != inspector_state.bounds.size {
583                        format!("Content size: {}", inspector_state.content_size)
584                    } else {
585                        "".to_string()
586                    },
587                ),
588        )
589}
590
591static STYLE_METHODS: LazyLock<Vec<(Box<StyleRefinement>, FunctionReflection<StyleRefinement>)>> =
592    LazyLock::new(|| {
593        // Include StyledExt methods first so that those methods take precedence.
594        styled_ext_reflection::methods::<StyleRefinement>()
595            .into_iter()
596            .chain(styled_reflection::methods::<StyleRefinement>())
597            .map(|method| (Box::new(method.invoke(StyleRefinement::default())), method))
598            .collect()
599    });
600
601static COLOR_METHODS: &[ColorMethod] = &[
602    ColorMethod {
603        name: "border_color",
604        set: |style, color| style.border_color(color),
605        get: |style| style.border_color,
606    },
607    ColorMethod {
608        name: "text_color",
609        set: |style, color| style.text_color(color),
610        get: |style| style.text.as_ref().and_then(|text_style| text_style.color),
611    },
612    ColorMethod {
613        name: "text_decoration_color",
614        set: |style, color| style.text_decoration_color(color),
615        get: |style| {
616            style.text.as_ref().and_then(|text_style| {
617                text_style
618                    .underline
619                    .as_ref()
620                    .and_then(|underline| underline.color)
621            })
622        },
623    },
624    ColorMethod {
625        name: "text_bg",
626        set: |style, color| style.text_bg(color),
627        get: |style| {
628            style
629                .text
630                .as_ref()
631                .and_then(|text_style| text_style.background_color)
632        },
633    },
634    ColorMethod {
635        name: "bg",
636        set: |style, color| style.bg(color),
637        get: |style| {
638            style.background.as_ref().and_then(|background| {
639                background
640                    .color()
641                    .and_then(|background| background.as_solid())
642            })
643        },
644    },
645];
646
647struct ColorMethod {
648    name: &'static str,
649    set: fn(StyleRefinement, Hsla) -> StyleRefinement,
650    get: fn(&StyleRefinement) -> Option<Hsla>,
651}
652
653fn guess_rust_code_from_style(goal_style: &StyleRefinement, cx: &App) -> (String, StyleRefinement) {
654    let mut subset_methods = Vec::new();
655    for (style, method) in STYLE_METHODS.iter() {
656        if goal_style.is_superset_of(style) {
657            subset_methods.push(method);
658        }
659    }
660
661    let mut code = String::new();
662    let mut style = StyleRefinement::default();
663    for method in subset_methods {
664        let before_change = style.clone();
665        style = method.invoke(style);
666        if before_change != style {
667            let _ = write!(code, ".{}()\n", &method.name);
668        }
669    }
670
671    let theme = cx.theme();
672    for color_method in COLOR_METHODS {
673        if let Some(color) = (color_method.get)(&goal_style) {
674            let mut found_match = false;
675            for theme_color_field in ThemeColorField::iter() {
676                // TODO: If proper color provenance information is added, then the
677                // `make_colors_unique` hack in the theme loading code can be removed.
678                if *theme.colors().get_field_by_enum(theme_color_field) == color {
679                    found_match = true;
680                    let _ = write!(
681                        code,
682                        ".{}(colors.{})\n",
683                        color_method.name,
684                        theme_color_field.as_ref().to_case(Case::Snake)
685                    );
686                }
687            }
688            for status_color_field in StatusColorField::iter() {
689                if *theme.status().get_field_by_enum(status_color_field) == color {
690                    found_match = true;
691                    let _ = write!(
692                        code,
693                        ".{}(status.{})\n",
694                        color_method.name,
695                        status_color_field.as_ref().to_case(Case::Snake)
696                    );
697                }
698            }
699            if found_match {
700                style = (color_method.set)(style, color);
701            }
702        }
703    }
704
705    (code, style)
706}
707
708fn guess_style_from_rust_code(
709    method_names: Vec<(Option<Range<usize>>, String)>,
710    snapshot: &BufferSnapshot,
711    cx: &App,
712) -> (StyleRefinement, Vec<Range<Anchor>>) {
713    let theme = cx.theme();
714    let mut style = StyleRefinement::default();
715    let mut unrecognized_ranges = Vec::new();
716    let mut preceded_by_color_method: Option<&ColorMethod> = None;
717    for (range, name) in method_names {
718        if name == "colors" || name == "status" {
719            continue;
720        }
721        if let Some(color_method) = preceded_by_color_method {
722            if let Some(field) = ThemeColorField::iter()
723                .find(|field| name == field.as_ref().to_case(Case::Snake).as_str())
724            {
725                style = (color_method.set)(style, *theme.colors().get_field_by_enum(field));
726                continue;
727            }
728            if let Some(field) = StatusColorField::iter()
729                .find(|field| name == field.as_ref().to_case(Case::Snake).as_str())
730            {
731                style = (color_method.set)(style, *theme.status().get_field_by_enum(field));
732                continue;
733            }
734        }
735        if let Some((_, method)) = STYLE_METHODS.iter().find(|(_, m)| m.name == name) {
736            preceded_by_color_method = None;
737            style = method.invoke(style);
738            continue;
739        }
740        if let Some(color_method) = COLOR_METHODS.iter().find(|m| m.name == name) {
741            preceded_by_color_method = Some(color_method);
742            continue;
743        }
744        if let Some(range) = range {
745            unrecognized_ranges
746                .push(snapshot.anchor_before(range.start)..snapshot.anchor_before(range.end));
747        }
748    }
749
750    (style, unrecognized_ranges)
751}
752
753fn is_not_identifier_char(c: char) -> bool {
754    !c.is_alphanumeric() && c != '_'
755}
756
757struct RustStyleCompletionProvider {
758    div_inspector: Entity<DivInspector>,
759}
760
761impl CompletionProvider for RustStyleCompletionProvider {
762    fn completions(
763        &self,
764        _excerpt_id: ExcerptId,
765        buffer: &Entity<Buffer>,
766        position: Anchor,
767        _: editor::CompletionContext,
768        _window: &mut Window,
769        cx: &mut Context<Editor>,
770    ) -> Task<Result<Vec<CompletionResponse>>> {
771        let snapshot: &text::BufferSnapshot = &buffer.read(cx);
772        let Some(replace_range) = completion_replace_range(snapshot, &position) else {
773            return Task::ready(Ok(Vec::new()));
774        };
775
776        let preceded_by_colors;
777        let preceded_by_status;
778        {
779            let rev_chars_before = snapshot
780                .reversed_chars_at(position)
781                .take(50)
782                .collect::<String>();
783            let mut rev_words_before = rev_chars_before.split(is_not_identifier_char);
784            let rev_first_word_before = rev_words_before.next();
785            let rev_second_word_before = rev_words_before.next();
786            preceded_by_colors =
787                rev_first_word_before == Some("sroloc") || rev_second_word_before == Some("sroloc");
788            preceded_by_status =
789                rev_first_word_before == Some("sutats") || rev_second_word_before == Some("sutats");
790        }
791
792        self.div_inspector.update(cx, |div_inspector, _cx| {
793            div_inspector.rust_completion_replace_range = Some(replace_range.clone());
794        });
795
796        if preceded_by_colors {
797            Task::ready(Ok(vec![CompletionResponse {
798                completions: ThemeColorField::iter()
799                    .map(|color_field| {
800                        let name = color_field.as_ref().to_case(Case::Snake);
801                        Completion {
802                            replace_range: replace_range.clone(),
803                            new_text: format!(".{}", name),
804                            label: CodeLabel::plain(name, None),
805                            icon_path: None,
806                            documentation: None,
807                            source: CompletionSource::Custom,
808                            insert_text_mode: None,
809                            confirm: None,
810                        }
811                    })
812                    .collect(),
813                display_options: CompletionDisplayOptions::default(),
814                is_incomplete: false,
815            }]))
816        } else if preceded_by_status {
817            Task::ready(Ok(vec![CompletionResponse {
818                completions: StatusColorField::iter()
819                    .map(|color_field| {
820                        let name = color_field.as_ref().to_case(Case::Snake);
821                        Completion {
822                            replace_range: replace_range.clone(),
823                            new_text: format!(".{}", name),
824                            label: CodeLabel::plain(name, None),
825                            icon_path: None,
826                            documentation: None,
827                            source: CompletionSource::Custom,
828                            insert_text_mode: None,
829                            confirm: None,
830                        }
831                    })
832                    .collect(),
833                display_options: CompletionDisplayOptions::default(),
834                is_incomplete: false,
835            }]))
836        } else {
837            Task::ready(Ok(vec![CompletionResponse {
838                completions: STYLE_METHODS
839                    .iter()
840                    .map(|(_, method)| Completion {
841                        replace_range: replace_range.clone(),
842                        new_text: format!(".{}()", method.name),
843                        label: CodeLabel::plain(method.name.to_string(), None),
844                        icon_path: None,
845                        documentation: method.documentation.map(|documentation| {
846                            CompletionDocumentation::MultiLineMarkdown(documentation.into())
847                        }),
848                        source: CompletionSource::Custom,
849                        insert_text_mode: None,
850                        confirm: None,
851                    })
852                    .chain(COLOR_METHODS.iter().map(|method| Completion {
853                        replace_range: replace_range.clone(),
854                        new_text: format!(".{}(colors.", method.name),
855                        label: CodeLabel::plain(method.name.to_string(), None),
856                        icon_path: None,
857                        documentation: None,
858                        source: CompletionSource::Custom,
859                        insert_text_mode: None,
860                        confirm: None,
861                    }))
862                    .collect(),
863                display_options: CompletionDisplayOptions::default(),
864                is_incomplete: false,
865            }]))
866        }
867    }
868
869    fn is_completion_trigger(
870        &self,
871        buffer: &Entity<language::Buffer>,
872        position: language::Anchor,
873        _text: &str,
874        _trigger_in_words: bool,
875        _menu_is_open: bool,
876        cx: &mut Context<Editor>,
877    ) -> bool {
878        let snapshot: &text::BufferSnapshot = &buffer.read(cx);
879        completion_replace_range(snapshot, &position).is_some()
880    }
881
882    fn selection_changed(&self, mat: Option<&StringMatch>, _window: &mut Window, cx: &mut App) {
883        let div_inspector = self.div_inspector.clone();
884        let rust_completion = mat.as_ref().map(|mat| mat.string.clone());
885        cx.defer(move |cx| {
886            div_inspector.update(cx, |div_inspector, cx| {
887                div_inspector.handle_rust_completion_selection_change(rust_completion, cx);
888            });
889        });
890    }
891
892    fn sort_completions(&self) -> bool {
893        false
894    }
895}
896
897fn completion_replace_range(
898    snapshot: &text::BufferSnapshot,
899    anchor: &Anchor,
900) -> Option<Range<Anchor>> {
901    let offset = anchor.to_offset(snapshot);
902    let start: usize = snapshot
903        .reversed_chars_at(offset)
904        .take_until(|c| is_not_identifier_char(*c))
905        .map(|char| char.len_utf8())
906        .sum();
907    let end: usize = snapshot
908        .chars_at(offset)
909        .take_until(|c| is_not_identifier_char(*c))
910        .skip(1)
911        .map(|char| char.len_utf8())
912        .sum();
913    Some(snapshot.anchor_after(offset - start)..snapshot.anchor_before(offset + end))
914}