signature_help.rs

  1mod popover;
  2mod state;
  3
  4use crate::actions::ShowSignatureHelp;
  5use crate::{Editor, EditorSettings, ToggleAutoSignatureHelp};
  6use gpui::{AppContext, ViewContext};
  7use language::markdown::parse_markdown;
  8use multi_buffer::{Anchor, ToOffset};
  9use settings::Settings;
 10use std::ops::Range;
 11
 12pub use popover::SignatureHelpPopover;
 13pub use state::SignatureHelpState;
 14
 15// Language-specific settings may define quotes as "brackets", so filter them out separately.
 16const QUOTE_PAIRS: [(&str, &str); 3] = [("'", "'"), ("\"", "\""), ("`", "`")];
 17
 18#[derive(Debug, Clone, Copy, PartialEq)]
 19pub enum SignatureHelpHiddenBy {
 20    AutoClose,
 21    Escape,
 22    Selection,
 23}
 24
 25impl Editor {
 26    pub fn toggle_auto_signature_help_menu(
 27        &mut self,
 28        _: &ToggleAutoSignatureHelp,
 29        cx: &mut ViewContext<Self>,
 30    ) {
 31        self.auto_signature_help = self
 32            .auto_signature_help
 33            .map(|auto_signature_help| !auto_signature_help)
 34            .or_else(|| Some(!EditorSettings::get_global(cx).auto_signature_help));
 35        match self.auto_signature_help {
 36            Some(auto_signature_help) if auto_signature_help => {
 37                self.show_signature_help(&ShowSignatureHelp, cx);
 38            }
 39            Some(_) => {
 40                self.hide_signature_help(cx, SignatureHelpHiddenBy::AutoClose);
 41            }
 42            None => {}
 43        }
 44        cx.notify();
 45    }
 46
 47    pub(super) fn hide_signature_help(
 48        &mut self,
 49        cx: &mut ViewContext<Self>,
 50        signature_help_hidden_by: SignatureHelpHiddenBy,
 51    ) -> bool {
 52        if self.signature_help_state.is_shown() {
 53            self.signature_help_state.kill_task();
 54            self.signature_help_state.hide(signature_help_hidden_by);
 55            cx.notify();
 56            true
 57        } else {
 58            false
 59        }
 60    }
 61
 62    pub fn auto_signature_help_enabled(&self, cx: &AppContext) -> bool {
 63        if let Some(auto_signature_help) = self.auto_signature_help {
 64            auto_signature_help
 65        } else {
 66            EditorSettings::get_global(cx).auto_signature_help
 67        }
 68    }
 69
 70    pub(super) fn should_open_signature_help_automatically(
 71        &mut self,
 72        old_cursor_position: &Anchor,
 73        backspace_pressed: bool,
 74        cx: &mut ViewContext<Self>,
 75    ) -> bool {
 76        if !(self.signature_help_state.is_shown() || self.auto_signature_help_enabled(cx)) {
 77            return false;
 78        }
 79        let newest_selection = self.selections.newest::<usize>(cx);
 80        let head = newest_selection.head();
 81
 82        // There are two cases where the head and tail of a selection are different: selecting multiple ranges and using backspace.
 83        // If we don’t exclude the backspace case, signature_help will blink every time backspace is pressed, so we need to prevent this.
 84        if !newest_selection.is_empty() && !backspace_pressed && head != newest_selection.tail() {
 85            self.signature_help_state
 86                .hide(SignatureHelpHiddenBy::Selection);
 87            return false;
 88        }
 89
 90        let buffer_snapshot = self.buffer().read(cx).snapshot(cx);
 91        let bracket_range = |position: usize| match (position, position + 1) {
 92            (0, b) if b <= buffer_snapshot.len() => 0..b,
 93            (0, b) => 0..b - 1,
 94            (a, b) if b <= buffer_snapshot.len() => a - 1..b,
 95            (a, b) => a - 1..b - 1,
 96        };
 97        let not_quote_like_brackets = |start: Range<usize>, end: Range<usize>| {
 98            let text = buffer_snapshot.text();
 99            let (text_start, text_end) = (text.get(start), text.get(end));
100            QUOTE_PAIRS
101                .into_iter()
102                .all(|(start, end)| text_start != Some(start) && text_end != Some(end))
103        };
104
105        let previous_position = old_cursor_position.to_offset(&buffer_snapshot);
106        let previous_brackets_range = bracket_range(previous_position);
107        let previous_brackets_surround = buffer_snapshot
108            .innermost_enclosing_bracket_ranges(
109                previous_brackets_range,
110                Some(&not_quote_like_brackets),
111            )
112            .filter(|(start_bracket_range, end_bracket_range)| {
113                start_bracket_range.start != previous_position
114                    && end_bracket_range.end != previous_position
115            });
116        let current_brackets_range = bracket_range(head);
117        let current_brackets_surround = buffer_snapshot
118            .innermost_enclosing_bracket_ranges(
119                current_brackets_range,
120                Some(&not_quote_like_brackets),
121            )
122            .filter(|(start_bracket_range, end_bracket_range)| {
123                start_bracket_range.start != head && end_bracket_range.end != head
124            });
125
126        match (previous_brackets_surround, current_brackets_surround) {
127            (None, None) => {
128                self.signature_help_state
129                    .hide(SignatureHelpHiddenBy::AutoClose);
130                false
131            }
132            (Some(_), None) => {
133                self.signature_help_state
134                    .hide(SignatureHelpHiddenBy::AutoClose);
135                false
136            }
137            (None, Some(_)) => true,
138            (Some(previous), Some(current)) => {
139                let condition = self.signature_help_state.hidden_by_selection()
140                    || previous != current
141                    || (previous == current && self.signature_help_state.is_shown());
142                if !condition {
143                    self.signature_help_state
144                        .hide(SignatureHelpHiddenBy::AutoClose);
145                }
146                condition
147            }
148        }
149    }
150
151    pub fn show_signature_help(&mut self, _: &ShowSignatureHelp, cx: &mut ViewContext<Self>) {
152        if self.pending_rename.is_some() || self.has_active_completions_menu() {
153            return;
154        }
155
156        let position = self.selections.newest_anchor().head();
157        let Some((buffer, buffer_position)) =
158            self.buffer.read(cx).text_anchor_for_position(position, cx)
159        else {
160            return;
161        };
162
163        self.signature_help_state
164            .set_task(cx.spawn(move |editor, mut cx| async move {
165                let signature_help = editor
166                    .update(&mut cx, |editor, cx| {
167                        let language = editor.language_at(position, cx);
168                        let project = editor.project.clone()?;
169                        let (markdown, language_registry) = {
170                            project.update(cx, |project, cx| {
171                                let language_registry = project.languages().clone();
172                                (
173                                    project.signature_help(&buffer, buffer_position, cx),
174                                    language_registry,
175                                )
176                            })
177                        };
178                        Some((markdown, language_registry, language))
179                    })
180                    .ok()
181                    .flatten();
182                let signature_help_popover = if let Some((
183                    signature_help_task,
184                    language_registry,
185                    language,
186                )) = signature_help
187                {
188                    // TODO allow multiple signature helps inside the same popover
189                    if let Some(mut signature_help) = signature_help_task.await.into_iter().next() {
190                        let mut parsed_content = parse_markdown(
191                            signature_help.markdown.as_str(),
192                            &language_registry,
193                            language,
194                        )
195                        .await;
196                        parsed_content
197                            .highlights
198                            .append(&mut signature_help.highlights);
199                        Some(SignatureHelpPopover { parsed_content })
200                    } else {
201                        None
202                    }
203                } else {
204                    None
205                };
206                editor
207                    .update(&mut cx, |editor, cx| {
208                        let previous_popover = editor.signature_help_state.popover();
209                        if previous_popover != signature_help_popover.as_ref() {
210                            if let Some(signature_help_popover) = signature_help_popover {
211                                editor
212                                    .signature_help_state
213                                    .set_popover(signature_help_popover);
214                            } else {
215                                editor
216                                    .signature_help_state
217                                    .hide(SignatureHelpHiddenBy::AutoClose);
218                            }
219                            cx.notify();
220                        }
221                    })
222                    .ok();
223            }));
224    }
225}