use crate::scroll::ScrollAmount;use fuzzy::{StringMatch, StringMatchCandidate};use gpui::{ AnyElement, Entity, Focusable, FontWeight, ListSizingBehavior, ScrollHandle, ScrollStrategy, SharedString, Size, StrikethroughStyle, StyledText, Task, UniformListScrollHandle, div, px, uniform_list,};use itertools::Itertools;use language::CodeLabel;use language::{Buffer, LanguageName, LanguageRegistry};use lsp::CompletionItemTag;use markdown::{Markdown, MarkdownElement};use multi_buffer::{Anchor, ExcerptId};use ordered_float::OrderedFloat;use project::lsp_store::CompletionDocumentation;use project::{CodeAction, Completion, TaskSourceKind};use project::{CompletionDisplayOptions, CompletionSource};use task::DebugScenario;use task::TaskContext; 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, ScrollAxes, Scrollbars, Styled, WithScrollbar, prelude::*,};use util::ResultExt; use crate::hover_popover::{hover_markdown_style, open_markdown_url};use crate::{ CodeActionProvider, CompletionId, CompletionProvider, DisplayRow, Editor, EditorStyle, ResolvedTasks, actions::{ConfirmCodeAction, ConfirmCompletion}, split_words, styled_runs_for_code_label,};use crate::{CodeActionSource, EditorSettings};use collections::{HashSet, VecDeque};use settings::{CompletionDetailAlignment, Settings, SnippetSortOrder}; 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.);pub const COMPLETION_MENU_MIN_WIDTH: Pixels = px(280.);pub const COMPLETION_MENU_MAX_WIDTH: Pixels = px(540.);pub const CODE_ACTION_MENU_MIN_WIDTH: Pixels = px(220.);pub const CODE_ACTION_MENU_MAX_WIDTH: Pixels = px(540.); // 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(menu) => menu.render_aside(max_size, window, cx), } } 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 fn scroll_aside( &mut self, scroll_amount: ScrollAmount, window: &mut Window, cx: &mut Context<Editor>, ) { match self { CodeContextMenu::Completions(completions_menu) => { completions_menu.scroll_aside(scroll_amount, window, cx) } CodeContextMenu::CodeActions(_) => (), } } pub fn primary_scroll_handle(&self) -> UniformListScrollHandle { match self { CodeContextMenu::Completions(menu) => menu.scroll_handle.clone(), CodeContextMenu::CodeActions(menu) => menu.scroll_handle.clone(), } }} 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]>>>, /// String match candidate for each completion, grouped by `match_start`. match_candidates: Arc<[(Option<text::Anchor>, Vec<StringMatchCandidate>)]>, /// Entries displayed in the menu, which is a filtered and sorted subset of `match_candidates`. pub entries: Rc<RefCell<Box<[StringMatch]>>>, pub selected_item: usize, filter_task: Task<()>, cancel_filter: Arc<AtomicBool>, scroll_handle: UniformListScrollHandle, // The `ScrollHandle` used on the Markdown documentation rendered on the // side of the completions menu. pub scroll_handle_aside: ScrollHandle, 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>, display_options: CompletionDisplayOptions, 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 { /// Show all completions (words, snippets, LSP) Normal, /// Show only snippets (not words or LSP) /// /// Used after typing a non-word character SnippetsOnly, /// Tab stops within a snippet that have a predefined finite set of choices SnippetChoices, /// Show only words (not snippets or LSP) /// /// Used when word completions are explicitly triggered Words { ignore_threshold: bool },} // 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); }} struct CompletionMenuScrollBarSetting; impl ui::scrollbars::GlobalSetting for CompletionMenuScrollBarSetting { fn get_value(_cx: &App) -> &Self { &Self }} impl ui::scrollbars::ScrollbarVisibility for CompletionMenuScrollBarSetting { fn visibility(&self, cx: &App) -> ui::scrollbars::ShowScrollbar { EditorSettings::get_global(cx).completion_menu_scrollbar }} 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]>, scroll_handle: Option<UniformListScrollHandle>, display_options: CompletionDisplayOptions, 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())) .into_group_map_by(|candidate| completions[candidate.id].match_start) .into_iter() .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: scroll_handle.unwrap_or_else(UniformListScrollHandle::new), scroll_handle_aside: ScrollHandle::new(), resolve_completions: true, last_rendered_range: RefCell::new(None).into(), markdown_cache: RefCell::new(VecDeque::new()).into(), language_registry, language, display_options, 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>, scroll_handle: Option<UniformListScrollHandle>, 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::plain(choice.to_string(), None), match_start: None, snippet_deduplication_key: None, icon_path: None, documentation: None, confirm: None, insert_text_mode: None, source: CompletionSource::Custom, }) .collect(); let match_candidates = Arc::new([( None, 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: scroll_handle.unwrap_or_else(UniformListScrollHandle::new), scroll_handle_aside: ScrollHandle::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, display_options: CompletionDisplayOptions::default(), 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 {Lines 1–500 of 1713. Load more
use crate::scroll::ScrollAmount; use fuzzy::{StringMatch, StringMatchCandidate}; use gpui::{ AnyElement, Entity, Focusable, FontWeight, ListSizingBehavior, ScrollHandle, ScrollStrategy, SharedString, Size, StrikethroughStyle, StyledText, Task, UniformListScrollHandle, div, px, uniform_list, }; use itertools::Itertools; use language::CodeLabel; use language::{Buffer, LanguageName, LanguageRegistry}; use lsp::CompletionItemTag; use markdown::{Markdown, MarkdownElement}; use multi_buffer::{Anchor, ExcerptId}; use ordered_float::OrderedFloat; use project::lsp_store::CompletionDocumentation; use project::{CodeAction, Completion, TaskSourceKind}; use project::{CompletionDisplayOptions, CompletionSource}; use task::DebugScenario; use task::TaskContext; 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, ScrollAxes, Scrollbars, Styled, WithScrollbar, prelude::*, }; use util::ResultExt; use crate::hover_popover::{hover_markdown_style, open_markdown_url}; use crate::{ CodeActionProvider, CompletionId, CompletionProvider, DisplayRow, Editor, EditorStyle, ResolvedTasks, actions::{ConfirmCodeAction, ConfirmCompletion}, split_words, styled_runs_for_code_label, }; use crate::{CodeActionSource, EditorSettings}; use collections::{HashSet, VecDeque}; use settings::{CompletionDetailAlignment, Settings, SnippetSortOrder}; 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.); pub const COMPLETION_MENU_MIN_WIDTH: Pixels = px(280.); pub const COMPLETION_MENU_MAX_WIDTH: Pixels = px(540.); pub const CODE_ACTION_MENU_MIN_WIDTH: Pixels = px(220.); pub const CODE_ACTION_MENU_MAX_WIDTH: Pixels = px(540.); // 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(menu) => menu.render_aside(max_size, window, cx), } } 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 fn scroll_aside( &mut self, scroll_amount: ScrollAmount, window: &mut Window, cx: &mut Context<Editor>, ) { match self { CodeContextMenu::Completions(completions_menu) => { completions_menu.scroll_aside(scroll_amount, window, cx) } CodeContextMenu::CodeActions(_) => (), } } pub fn primary_scroll_handle(&self) -> UniformListScrollHandle { match self { CodeContextMenu::Completions(menu) => menu.scroll_handle.clone(), CodeContextMenu::CodeActions(menu) => menu.scroll_handle.clone(), } } } 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]>>>, /// String match candidate for each completion, grouped by `match_start`. match_candidates: Arc<[(Option<text::Anchor>, Vec<StringMatchCandidate>)]>, /// Entries displayed in the menu, which is a filtered and sorted subset of `match_candidates`. pub entries: Rc<RefCell<Box<[StringMatch]>>>, pub selected_item: usize, filter_task: Task<()>, cancel_filter: Arc<AtomicBool>, scroll_handle: UniformListScrollHandle, // The `ScrollHandle` used on the Markdown documentation rendered on the // side of the completions menu. pub scroll_handle_aside: ScrollHandle, 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>, display_options: CompletionDisplayOptions, 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 { /// Show all completions (words, snippets, LSP) Normal, /// Show only snippets (not words or LSP) /// /// Used after typing a non-word character SnippetsOnly, /// Tab stops within a snippet that have a predefined finite set of choices SnippetChoices, /// Show only words (not snippets or LSP) /// /// Used when word completions are explicitly triggered Words { ignore_threshold: bool }, } // 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); } } struct CompletionMenuScrollBarSetting; impl ui::scrollbars::GlobalSetting for CompletionMenuScrollBarSetting { fn get_value(_cx: &App) -> &Self { &Self } } impl ui::scrollbars::ScrollbarVisibility for CompletionMenuScrollBarSetting { fn visibility(&self, cx: &App) -> ui::scrollbars::ShowScrollbar { EditorSettings::get_global(cx).completion_menu_scrollbar } } 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]>, scroll_handle: Option<UniformListScrollHandle>, display_options: CompletionDisplayOptions, 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())) .into_group_map_by(|candidate| completions[candidate.id].match_start) .into_iter() .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: scroll_handle.unwrap_or_else(UniformListScrollHandle::new), scroll_handle_aside: ScrollHandle::new(), resolve_completions: true, last_rendered_range: RefCell::new(None).into(), markdown_cache: RefCell::new(VecDeque::new()).into(), language_registry, language, display_options, 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>, scroll_handle: Option<UniformListScrollHandle>, 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::plain(choice.to_string(), None), match_start: None, snippet_deduplication_key: None, icon_path: None, documentation: None, confirm: None, insert_text_mode: None, source: CompletionSource::Custom, }) .collect(); let match_candidates = Arc::new([( None, 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: scroll_handle.unwrap_or_else(UniformListScrollHandle::new), scroll_handle_aside: ScrollHandle::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, display_options: CompletionDisplayOptions::default(), 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 {