div_inspector.rs

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