signature_help.rs

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