1486 lines · 52.9 KB · Rust
RawDownload
use fuzzy::{StringMatch, StringMatchCandidate};use gpui::{    AnyElement, Entity, Focusable, FontWeight, ListSizingBehavior, ScrollStrategy, SharedString,    Size, StrikethroughStyle, StyledText, Task, UniformListScrollHandle, div, px, uniform_list,};use itertools::Itertools;use language::CodeLabel;use language::{Buffer, LanguageName, LanguageRegistry};use markdown::{Markdown, MarkdownElement};use multi_buffer::{Anchor, ExcerptId};use ordered_float::OrderedFloat;use project::CompletionSource;use project::lsp_store::CompletionDocumentation;use project::{CodeAction, Completion, TaskSourceKind};use task::DebugScenario;use task::TaskContext; use std::collections::VecDeque;use std::sync::Arc;use std::sync::atomic::{AtomicBool, Ordering};use std::{    cell::RefCell,    cmp::{Reverse, min},    iter,    ops::Range,    rc::Rc,};use task::ResolvedTask;use ui::{Color, IntoElement, ListItem, Pixels, Popover, Styled, prelude::*};use util::ResultExt; use crate::CodeActionSource;use crate::editor_settings::SnippetSortOrder;use crate::hover_popover::{hover_markdown_style, open_markdown_url};use crate::{    CodeActionProvider, CompletionId, CompletionItemKind, CompletionProvider, DisplayRow, Editor,    EditorStyle, ResolvedTasks,    actions::{ConfirmCodeAction, ConfirmCompletion},    split_words, styled_runs_for_code_label,}; pub const MENU_GAP: Pixels = px(4.);pub const MENU_ASIDE_X_PADDING: Pixels = px(16.);pub const MENU_ASIDE_MIN_WIDTH: Pixels = px(260.);pub const MENU_ASIDE_MAX_WIDTH: Pixels = px(500.); // Constants for the markdown cache. The purpose of this cache is to reduce flickering due to// documentation not yet being parsed.//// The size of the cache is set to 16, which is roughly 3 times more than the number of items// fetched around the current selection. This way documentation is more often ready for render when// revisiting previous entries, such as when pressing backspace.const MARKDOWN_CACHE_MAX_SIZE: usize = 16;const MARKDOWN_CACHE_BEFORE_ITEMS: usize = 2;const MARKDOWN_CACHE_AFTER_ITEMS: usize = 2; // Number of items beyond the visible items to resolve documentation.const RESOLVE_BEFORE_ITEMS: usize = 4;const RESOLVE_AFTER_ITEMS: usize = 4; pub enum CodeContextMenu {    Completions(CompletionsMenu),    CodeActions(CodeActionsMenu),} impl CodeContextMenu {    pub fn select_first(        &mut self,        provider: Option<&dyn CompletionProvider>,        window: &mut Window,        cx: &mut Context<Editor>,    ) -> bool {        if self.visible() {            match self {                CodeContextMenu::Completions(menu) => menu.select_first(provider, window, cx),                CodeContextMenu::CodeActions(menu) => menu.select_first(cx),            }            true        } else {            false        }    }     pub fn select_prev(        &mut self,        provider: Option<&dyn CompletionProvider>,        window: &mut Window,        cx: &mut Context<Editor>,    ) -> bool {        if self.visible() {            match self {                CodeContextMenu::Completions(menu) => menu.select_prev(provider, window, cx),                CodeContextMenu::CodeActions(menu) => menu.select_prev(cx),            }            true        } else {            false        }    }     pub fn select_next(        &mut self,        provider: Option<&dyn CompletionProvider>,        window: &mut Window,        cx: &mut Context<Editor>,    ) -> bool {        if self.visible() {            match self {                CodeContextMenu::Completions(menu) => menu.select_next(provider, window, cx),                CodeContextMenu::CodeActions(menu) => menu.select_next(cx),            }            true        } else {            false        }    }     pub fn select_last(        &mut self,        provider: Option<&dyn CompletionProvider>,        window: &mut Window,        cx: &mut Context<Editor>,    ) -> bool {        if self.visible() {            match self {                CodeContextMenu::Completions(menu) => menu.select_last(provider, window, cx),                CodeContextMenu::CodeActions(menu) => menu.select_last(cx),            }            true        } else {            false        }    }     pub fn visible(&self) -> bool {        match self {            CodeContextMenu::Completions(menu) => menu.visible(),            CodeContextMenu::CodeActions(menu) => menu.visible(),        }    }     pub fn origin(&self) -> ContextMenuOrigin {        match self {            CodeContextMenu::Completions(menu) => menu.origin(),            CodeContextMenu::CodeActions(menu) => menu.origin(),        }    }     pub fn render(        &self,        style: &EditorStyle,        max_height_in_lines: u32,        window: &mut Window,        cx: &mut Context<Editor>,    ) -> AnyElement {        match self {            CodeContextMenu::Completions(menu) => {                menu.render(style, max_height_in_lines, window, cx)            }            CodeContextMenu::CodeActions(menu) => {                menu.render(style, max_height_in_lines, window, cx)            }        }    }     pub fn render_aside(        &mut self,        max_size: Size<Pixels>,        window: &mut Window,        cx: &mut Context<Editor>,    ) -> Option<AnyElement> {        match self {            CodeContextMenu::Completions(menu) => menu.render_aside(max_size, window, cx),            CodeContextMenu::CodeActions(_) => None,        }    }     pub fn focused(&self, window: &mut Window, cx: &mut Context<Editor>) -> bool {        match self {            CodeContextMenu::Completions(completions_menu) => completions_menu                .get_or_create_entry_markdown(completions_menu.selected_item, cx)                .as_ref()                .is_some_and(|markdown| markdown.focus_handle(cx).contains_focused(window, cx)),            CodeContextMenu::CodeActions(_) => false,        }    }} pub enum ContextMenuOrigin {    Cursor,    GutterIndicator(DisplayRow),    QuickActionBar,} pub struct CompletionsMenu {    pub id: CompletionId,    pub source: CompletionsMenuSource,    sort_completions: bool,    pub initial_position: Anchor,    pub initial_query: Option<Arc<String>>,    pub is_incomplete: bool,    pub buffer: Entity<Buffer>,    pub completions: Rc<RefCell<Box<[Completion]>>>,    match_candidates: Arc<[StringMatchCandidate]>,    pub entries: Rc<RefCell<Box<[StringMatch]>>>,    pub selected_item: usize,    filter_task: Task<()>,    cancel_filter: Arc<AtomicBool>,    scroll_handle: UniformListScrollHandle,    resolve_completions: bool,    show_completion_documentation: bool,    last_rendered_range: Rc<RefCell<Option<Range<usize>>>>,    markdown_cache: Rc<RefCell<VecDeque<(MarkdownCacheKey, Entity<Markdown>)>>>,    language_registry: Option<Arc<LanguageRegistry>>,    language: Option<LanguageName>,    snippet_sort_order: SnippetSortOrder,} #[derive(Clone, Debug, PartialEq)]enum MarkdownCacheKey {    ForCandidate {        candidate_id: usize,    },    ForCompletionMatch {        new_text: String,        markdown_source: SharedString,    },} #[derive(Copy, Clone, Debug, PartialEq, Eq)]pub enum CompletionsMenuSource {    Normal,    SnippetChoices,    Words,} // TODO: There should really be a wrapper around fuzzy match tasks that does this.impl Drop for CompletionsMenu {    fn drop(&mut self) {        self.cancel_filter.store(true, Ordering::Relaxed);    }} impl CompletionsMenu {    pub fn new(        id: CompletionId,        source: CompletionsMenuSource,        sort_completions: bool,        show_completion_documentation: bool,        initial_position: Anchor,        initial_query: Option<Arc<String>>,        is_incomplete: bool,        buffer: Entity<Buffer>,        completions: Box<[Completion]>,        snippet_sort_order: SnippetSortOrder,        language_registry: Option<Arc<LanguageRegistry>>,        language: Option<LanguageName>,        cx: &mut Context<Editor>,    ) -> Self {        let match_candidates = completions            .iter()            .enumerate()            .map(|(id, completion)| StringMatchCandidate::new(id, completion.label.filter_text()))            .collect();         let completions_menu = Self {            id,            source,            sort_completions,            initial_position,            initial_query,            is_incomplete,            buffer,            show_completion_documentation,            completions: RefCell::new(completions).into(),            match_candidates,            entries: Rc::new(RefCell::new(Box::new([]))),            selected_item: 0,            filter_task: Task::ready(()),            cancel_filter: Arc::new(AtomicBool::new(false)),            scroll_handle: UniformListScrollHandle::new(),            resolve_completions: true,            last_rendered_range: RefCell::new(None).into(),            markdown_cache: RefCell::new(VecDeque::new()).into(),            language_registry,            language,            snippet_sort_order,        };         completions_menu.start_markdown_parse_for_nearby_entries(cx);         completions_menu    }     pub fn new_snippet_choices(        id: CompletionId,        sort_completions: bool,        choices: &Vec<String>,        selection: Range<Anchor>,        buffer: Entity<Buffer>,        snippet_sort_order: SnippetSortOrder,    ) -> Self {        let completions = choices            .iter()            .map(|choice| Completion {                replace_range: selection.start.text_anchor..selection.end.text_anchor,                new_text: choice.to_string(),                label: CodeLabel {                    text: choice.to_string(),                    runs: Default::default(),                    filter_range: Default::default(),                },                icon_path: None,                documentation: None,                confirm: None,                insert_text_mode: None,                source: CompletionSource::Custom,            })            .collect();         let match_candidates = choices            .iter()            .enumerate()            .map(|(id, completion)| StringMatchCandidate::new(id, &completion))            .collect();        let entries = choices            .iter()            .enumerate()            .map(|(id, completion)| StringMatch {                candidate_id: id,                score: 1.,                positions: vec![],                string: completion.clone(),            })            .collect();        Self {            id,            source: CompletionsMenuSource::SnippetChoices,            sort_completions,            initial_position: selection.start,            initial_query: None,            is_incomplete: false,            buffer,            completions: RefCell::new(completions).into(),            match_candidates,            entries: RefCell::new(entries).into(),            selected_item: 0,            filter_task: Task::ready(()),            cancel_filter: Arc::new(AtomicBool::new(false)),            scroll_handle: UniformListScrollHandle::new(),            resolve_completions: false,            show_completion_documentation: false,            last_rendered_range: RefCell::new(None).into(),            markdown_cache: RefCell::new(VecDeque::new()).into(),            language_registry: None,            language: None,            snippet_sort_order,        }    }     fn select_first(        &mut self,        provider: Option<&dyn CompletionProvider>,        window: &mut Window,        cx: &mut Context<Editor>,    ) {        let index = if self.scroll_handle.y_flipped() {            self.entries.borrow().len() - 1        } else {            0        };        self.update_selection_index(index, provider, window, cx);    }     fn select_last(        &mut self,        provider: Option<&dyn CompletionProvider>,        window: &mut Window,        cx: &mut Context<Editor>,    ) {        let index = if self.scroll_handle.y_flipped() {            0        } else {            self.entries.borrow().len() - 1        };        self.update_selection_index(index, provider, window, cx);    }     fn select_prev(        &mut self,        provider: Option<&dyn CompletionProvider>,        window: &mut Window,        cx: &mut Context<Editor>,    ) {        let index = if self.scroll_handle.y_flipped() {            self.next_match_index()        } else {            self.prev_match_index()        };        self.update_selection_index(index, provider, window, cx);    }     fn select_next(        &mut self,        provider: Option<&dyn CompletionProvider>,        window: &mut Window,        cx: &mut Context<Editor>,    ) {        let index = if self.scroll_handle.y_flipped() {            self.prev_match_index()        } else {            self.next_match_index()        };        self.update_selection_index(index, provider, window, cx);    }     fn update_selection_index(        &mut self,        match_index: usize,        provider: Option<&dyn CompletionProvider>,        window: &mut Window,        cx: &mut Context<Editor>,    ) {        if self.selected_item != match_index {            self.selected_item = match_index;            self.handle_selection_changed(provider, window, cx);        }    }     fn prev_match_index(&self) -> usize {        if self.selected_item > 0 {            self.selected_item - 1        } else {            self.entries.borrow().len() - 1        }    }     fn next_match_index(&self) -> usize {        if self.selected_item + 1 < self.entries.borrow().len() {            self.selected_item + 1        } else {            0        }    }     fn handle_selection_changed(        &mut self,        provider: Option<&dyn CompletionProvider>,        window: &mut Window,        cx: &mut Context<Editor>,    ) {        self.scroll_handle            .scroll_to_item(self.selected_item, ScrollStrategy::Top);        if let Some(provider) = provider {            let entries = self.entries.borrow();            let entry = if self.selected_item < entries.len() {                Some(&entries[self.selected_item])            } else {                None            };            provider.selection_changed(entry, window, cx);        }        self.resolve_visible_completions(provider, cx);        self.start_markdown_parse_for_nearby_entries(cx);        cx.notify();    }     pub fn resolve_visible_completions(        &mut self,        provider: Option<&dyn CompletionProvider>,        cx: &mut Context<Editor>,    ) {        if !self.resolve_completions {            return;        }        let Some(provider) = provider else {            return;        };         let entries = self.entries.borrow();        if entries.is_empty() {            return;        }        if self.selected_item >= entries.len() {            log::error!(                "bug: completion selected_item >= entries.len(): {} >= {}",                self.selected_item,                entries.len()            );            self.selected_item = entries.len() - 1;        }         // Attempt to resolve completions for every item that will be displayed. This matters        // because single line documentation may be displayed inline with the completion.        //        // When navigating to the very beginning or end of completions, `last_rendered_range` may        // have no overlap with the completions that will be displayed, so instead use a range based        // on the last rendered count.        const APPROXIMATE_VISIBLE_COUNT: usize = 12;        let last_rendered_range = self.last_rendered_range.borrow().clone();

Lines 1–500 of 1486.