Detailed changes
@@ -62,6 +62,9 @@
// Whether to display inline and alongside documentation for items in the
// completions menu
"show_completion_documentation": true,
+ // The debounce delay before re-querying the language server for completion
+ // documentation when not included in original completion list.
+ "completion_documentation_secondary_query_debounce": 300,
// Whether to show wrap guides in the editor. Setting this to true will
// show a guide at the 'preferred_line_length' value if softwrap is set to
// 'preferred_line_length', and will show any additional guides as specified
@@ -0,0 +1,49 @@
+use std::time::Duration;
+
+use futures::{channel::oneshot, FutureExt};
+use gpui::{Task, ViewContext};
+
+use crate::Editor;
+
+pub struct DebouncedDelay {
+ task: Option<Task<()>>,
+ cancel_channel: Option<oneshot::Sender<()>>,
+}
+
+impl DebouncedDelay {
+ pub fn new() -> DebouncedDelay {
+ DebouncedDelay {
+ task: None,
+ cancel_channel: None,
+ }
+ }
+
+ pub fn fire_new<F>(&mut self, delay: Duration, cx: &mut ViewContext<Editor>, func: F)
+ where
+ F: 'static + Send + FnOnce(&mut Editor, &mut ViewContext<Editor>) -> Task<()>,
+ {
+ if let Some(channel) = self.cancel_channel.take() {
+ _ = channel.send(());
+ }
+
+ let (sender, mut receiver) = oneshot::channel::<()>();
+ self.cancel_channel = Some(sender);
+
+ let previous_task = self.task.take();
+ self.task = Some(cx.spawn(move |model, mut cx| async move {
+ let mut timer = cx.background_executor().timer(delay).fuse();
+ if let Some(previous_task) = previous_task {
+ previous_task.await;
+ }
+
+ futures::select_biased! {
+ _ = receiver => return,
+ _ = timer => {}
+ }
+
+ if let Ok(task) = model.update(&mut cx, |project, cx| (func)(project, cx)) {
+ task.await;
+ }
+ }));
+ }
+}
@@ -19,6 +19,7 @@ mod editor_settings;
mod element;
mod inlay_hint_cache;
+mod debounced_delay;
mod git;
mod highlight_matching_bracket;
mod hover_popover;
@@ -45,6 +46,7 @@ use clock::ReplicaId;
use collections::{BTreeMap, Bound, HashMap, HashSet, VecDeque};
use convert_case::{Case, Casing};
use copilot::Copilot;
+use debounced_delay::DebouncedDelay;
pub use display_map::DisplayPoint;
use display_map::*;
pub use editor_settings::EditorSettings;
@@ -85,7 +87,7 @@ pub use multi_buffer::{
ToPoint,
};
use ordered_float::OrderedFloat;
-use parking_lot::RwLock;
+use parking_lot::{Mutex, RwLock};
use project::{FormatTrigger, Location, Project, ProjectPath, ProjectTransaction};
use rand::prelude::*;
use rpc::proto::*;
@@ -383,6 +385,7 @@ pub struct Editor {
mouse_context_menu: Option<MouseContextMenu>,
completion_tasks: Vec<(CompletionId, Task<Option<()>>)>,
next_completion_id: CompletionId,
+ completion_documentation_pre_resolve_debounce: DebouncedDelay,
available_code_actions: Option<(Model<Buffer>, Arc<[CodeAction]>)>,
code_actions_task: Option<Task<()>>,
document_highlights_task: Option<Task<()>>,
@@ -701,6 +704,7 @@ struct CompletionsMenu {
matches: Arc<[StringMatch]>,
selected_item: usize,
scroll_handle: UniformListScrollHandle,
+ selected_completion_documentation_resolve_debounce: Arc<Mutex<DebouncedDelay>>,
}
impl CompletionsMenu {
@@ -741,30 +745,31 @@ impl CompletionsMenu {
}
fn pre_resolve_completion_documentation(
- &self,
+ completions: Arc<RwLock<Box<[Completion]>>>,
+ matches: Arc<[StringMatch]>,
editor: &Editor,
cx: &mut ViewContext<Editor>,
- ) -> Option<Task<()>> {
+ ) -> Task<()> {
let settings = EditorSettings::get_global(cx);
if !settings.show_completion_documentation {
- return None;
+ return Task::ready(());
}
let Some(provider) = editor.completion_provider.as_ref() else {
- return None;
+ return Task::ready(());
};
let resolve_task = provider.resolve_completions(
- self.matches.iter().map(|m| m.candidate_id).collect(),
- self.completions.clone(),
+ matches.iter().map(|m| m.candidate_id).collect(),
+ completions.clone(),
cx,
);
- return Some(cx.spawn(move |this, mut cx| async move {
+ return cx.spawn(move |this, mut cx| async move {
if let Some(true) = resolve_task.await.log_err() {
this.update(&mut cx, |_, cx| cx.notify()).ok();
}
- }));
+ });
}
fn attempt_resolve_selected_completion_documentation(
@@ -785,12 +790,20 @@ impl CompletionsMenu {
let resolve_task = project.update(cx, |project, cx| {
project.resolve_completions(vec![completion_index], self.completions.clone(), cx)
});
- cx.spawn(move |this, mut cx| async move {
- if let Some(true) = resolve_task.await.log_err() {
- this.update(&mut cx, |_, cx| cx.notify()).ok();
- }
- })
- .detach();
+
+ let delay_ms =
+ EditorSettings::get_global(cx).completion_documentation_secondary_query_debounce;
+ let delay = Duration::from_millis(delay_ms);
+
+ self.selected_completion_documentation_resolve_debounce
+ .lock()
+ .fire_new(delay, cx, |_, cx| {
+ cx.spawn(move |this, mut cx| async move {
+ if let Some(true) = resolve_task.await.log_err() {
+ this.update(&mut cx, |_, cx| cx.notify()).ok();
+ }
+ })
+ });
}
fn visible(&self) -> bool {
@@ -1434,6 +1447,7 @@ impl Editor {
mouse_context_menu: None,
completion_tasks: Default::default(),
next_completion_id: 0,
+ completion_documentation_pre_resolve_debounce: DebouncedDelay::new(),
next_inlay_id: 0,
available_code_actions: Default::default(),
code_actions_task: Default::default(),
@@ -3143,7 +3157,7 @@ impl Editor {
let task = cx.spawn(|this, mut cx| {
async move {
let completions = completions.await.log_err();
- let (menu, pre_resolve_task) = if let Some(completions) = completions {
+ let menu = if let Some(completions) = completions {
let mut menu = CompletionsMenu {
id,
initial_position: position,
@@ -3163,23 +3177,40 @@ impl Editor {
matches: Vec::new().into(),
selected_item: 0,
scroll_handle: UniformListScrollHandle::new(),
+ selected_completion_documentation_resolve_debounce: Arc::new(Mutex::new(
+ DebouncedDelay::new(),
+ )),
};
menu.filter(query.as_deref(), cx.background_executor().clone())
.await;
if menu.matches.is_empty() {
- (None, None)
+ None
} else {
- let pre_resolve_task = this
- .update(&mut cx, |editor, cx| {
- menu.pre_resolve_completion_documentation(editor, cx)
- })
- .ok()
- .flatten();
- (Some(menu), pre_resolve_task)
+ this.update(&mut cx, |editor, cx| {
+ let completions = menu.completions.clone();
+ let matches = menu.matches.clone();
+
+ let delay_ms = EditorSettings::get_global(cx)
+ .completion_documentation_secondary_query_debounce;
+ let delay = Duration::from_millis(delay_ms);
+
+ editor
+ .completion_documentation_pre_resolve_debounce
+ .fire_new(delay, cx, |editor, cx| {
+ CompletionsMenu::pre_resolve_completion_documentation(
+ completions,
+ matches,
+ editor,
+ cx,
+ )
+ });
+ })
+ .ok();
+ Some(menu)
}
} else {
- (None, None)
+ None
};
this.update(&mut cx, |this, cx| {
@@ -3215,10 +3246,6 @@ impl Editor {
}
})?;
- if let Some(pre_resolve_task) = pre_resolve_task {
- pre_resolve_task.await;
- }
-
Ok::<_, anyhow::Error>(())
}
.log_err()
@@ -8,6 +8,7 @@ pub struct EditorSettings {
pub hover_popover_enabled: bool,
pub show_completions_on_input: bool,
pub show_completion_documentation: bool,
+ pub completion_documentation_secondary_query_debounce: u64,
pub use_on_type_format: bool,
pub scrollbar: Scrollbar,
pub relative_line_numbers: bool,
@@ -72,6 +73,11 @@ pub struct EditorSettingsContent {
///
/// Default: true
pub show_completion_documentation: Option<bool>,
+ /// The debounce delay before re-querying the language server for completion
+ /// documentation when not included in original completion list.
+ ///
+ /// Default: 300 ms
+ pub completion_documentation_secondary_query_debounce: Option<u64>,
/// Whether to use additional LSP queries to format (and amend) the code after
/// every "trigger" symbol input, defined by LSP server capabilities.
///
@@ -0,0 +1,49 @@
+use std::time::Duration;
+
+use futures::{channel::oneshot, FutureExt};
+use gpui::{ModelContext, Task};
+
+use crate::Project;
+
+pub struct DebouncedDelay {
+ task: Option<Task<()>>,
+ cancel_channel: Option<oneshot::Sender<()>>,
+}
+
+impl DebouncedDelay {
+ pub fn new() -> DebouncedDelay {
+ DebouncedDelay {
+ task: None,
+ cancel_channel: None,
+ }
+ }
+
+ pub fn fire_new<F>(&mut self, delay: Duration, cx: &mut ModelContext<Project>, func: F)
+ where
+ F: 'static + Send + FnOnce(&mut Project, &mut ModelContext<Project>) -> Task<()>,
+ {
+ if let Some(channel) = self.cancel_channel.take() {
+ _ = channel.send(());
+ }
+
+ let (sender, mut receiver) = oneshot::channel::<()>();
+ self.cancel_channel = Some(sender);
+
+ let previous_task = self.task.take();
+ self.task = Some(cx.spawn(move |model, mut cx| async move {
+ let mut timer = cx.background_executor().timer(delay).fuse();
+ if let Some(previous_task) = previous_task {
+ previous_task.await;
+ }
+
+ futures::select_biased! {
+ _ = receiver => return,
+ _ = timer => {}
+ }
+
+ if let Ok(task) = model.update(&mut cx, |project, cx| (func)(project, cx)) {
+ task.await;
+ }
+ }));
+ }
+}
@@ -1,3 +1,4 @@
+pub mod debounced_delay;
mod ignore;
pub mod lsp_command;
pub mod lsp_ext_command;
@@ -17,11 +18,9 @@ use client::{proto, Client, Collaborator, TypedEnvelope, UserStore};
use clock::ReplicaId;
use collections::{hash_map, BTreeMap, HashMap, HashSet, VecDeque};
use copilot::Copilot;
+use debounced_delay::DebouncedDelay;
use futures::{
- channel::{
- mpsc::{self, UnboundedReceiver},
- oneshot,
- },
+ channel::mpsc::{self, UnboundedReceiver},
future::{try_join_all, Shared},
stream::FuturesUnordered,
AsyncWriteExt, Future, FutureExt, StreamExt, TryFutureExt,
@@ -140,7 +139,7 @@ pub struct Project {
buffer_snapshots: HashMap<BufferId, HashMap<LanguageServerId, Vec<LspBufferSnapshot>>>, // buffer_id -> server_id -> vec of snapshots
buffers_being_formatted: HashSet<BufferId>,
buffers_needing_diff: HashSet<WeakModel<Buffer>>,
- git_diff_debouncer: DelayedDebounced,
+ git_diff_debouncer: DebouncedDelay,
nonce: u128,
_maintain_buffer_languages: Task<()>,
_maintain_workspace_config: Task<Result<()>>,
@@ -154,54 +153,11 @@ pub struct Project {
prettier_instances: HashMap<PathBuf, PrettierInstance>,
}
-struct DelayedDebounced {
- task: Option<Task<()>>,
- cancel_channel: Option<oneshot::Sender<()>>,
-}
-
pub enum LanguageServerToQuery {
Primary,
Other(LanguageServerId),
}
-impl DelayedDebounced {
- fn new() -> DelayedDebounced {
- DelayedDebounced {
- task: None,
- cancel_channel: None,
- }
- }
-
- fn fire_new<F>(&mut self, delay: Duration, cx: &mut ModelContext<Project>, func: F)
- where
- F: 'static + Send + FnOnce(&mut Project, &mut ModelContext<Project>) -> Task<()>,
- {
- if let Some(channel) = self.cancel_channel.take() {
- _ = channel.send(());
- }
-
- let (sender, mut receiver) = oneshot::channel::<()>();
- self.cancel_channel = Some(sender);
-
- let previous_task = self.task.take();
- self.task = Some(cx.spawn(move |project, mut cx| async move {
- let mut timer = cx.background_executor().timer(delay).fuse();
- if let Some(previous_task) = previous_task {
- previous_task.await;
- }
-
- futures::select_biased! {
- _ = receiver => return,
- _ = timer => {}
- }
-
- if let Ok(task) = project.update(&mut cx, |project, cx| (func)(project, cx)) {
- task.await;
- }
- }));
- }
-}
-
struct LspBufferSnapshot {
version: i32,
snapshot: TextBufferSnapshot,
@@ -670,7 +626,7 @@ impl Project {
last_workspace_edits_by_language_server: Default::default(),
buffers_being_formatted: Default::default(),
buffers_needing_diff: Default::default(),
- git_diff_debouncer: DelayedDebounced::new(),
+ git_diff_debouncer: DebouncedDelay::new(),
nonce: StdRng::from_entropy().gen(),
terminals: Terminals {
local_handles: Vec::new(),
@@ -774,7 +730,7 @@ impl Project {
opened_buffers: Default::default(),
buffers_being_formatted: Default::default(),
buffers_needing_diff: Default::default(),
- git_diff_debouncer: DelayedDebounced::new(),
+ git_diff_debouncer: DebouncedDelay::new(),
buffer_snapshots: Default::default(),
nonce: StdRng::from_entropy().gen(),
terminals: Terminals {
@@ -622,6 +622,16 @@ These values take in the same options as the root-level settings with the same n
`boolean` values
+## Completion Documentation Debounce Delay
+
+- Description: The debounce delay before re-querying the language server for completion documentation when not included in original completion list.
+- Setting: `completion_documentation_secondary_query_debounce`
+- Default: `300` ms
+
+**Options**
+
+`integer` values
+
## Show Copilot Suggestions
- Description: Whether or not to show Copilot suggestions as you type or wait for a `copilot::Toggle`.