Compare commits

...
Sign in to create a new pull request.

14 commits

Author SHA1 Message Date
Piotr Osiewicz
575e193ea3 WIP
Co-authored-by: Kirill <kirill@zed.dev>
2023-11-02 10:59:10 +01:00
Piotr Osiewicz
a9e3d8c9a4 Make editor's ContextMenu no longer depend on project in it's api 2023-11-01 00:44:29 +01:00
Piotr Osiewicz
b11d1e2db8 Fix up tests.
Fix circular reference in CodeActions.
2023-11-01 00:26:12 +01:00
Piotr Osiewicz
e6448d7f71 Merge branch 'main' into editor_teardown 2023-10-31 21:55:01 +01:00
Piotr Osiewicz
8996ef9d20 Add FormatProvider 2023-10-31 21:26:51 +01:00
Piotr Osiewicz
c0f6075302 touchup, rename a variable 2023-10-31 15:58:35 +01:00
Piotr Osiewicz
f93b23cfc4 fixup! Add RenameProvider 2023-10-31 11:27:21 +01:00
Piotr Osiewicz
9e60c836e0 Add RenameProvider 2023-10-31 11:25:58 +01:00
Piotr Osiewicz
1a5897e264 Another batch of warning fixes 2023-10-30 21:49:38 +01:00
Piotr Osiewicz
1724d5dde2 Fix warnings 2023-10-30 21:47:07 +01:00
Piotr Osiewicz
ebe8e7d4a0 Add GoToDefinition2 2023-10-30 21:46:57 +01:00
Piotr Osiewicz
722a508c2e Add CodeActionsProvider 2023-10-26 15:37:05 +02:00
Piotr Osiewicz
840309a6b7 editor: extract code completions provider 2023-10-26 14:07:32 +02:00
Piotr Osiewicz
c961a1cd2b Mark a bunch of fns in editor as non-pub 2023-10-26 00:29:31 +02:00
7 changed files with 1555 additions and 1189 deletions

View file

@ -5796,7 +5796,7 @@ async fn test_collaborating_with_renames(
prepare_rename.await.unwrap();
editor_b.update(cx_b, |editor, cx| {
use editor::ToOffset;
let rename = editor.pending_rename().unwrap();
let rename = editor.pending_rename(cx).unwrap();
let buffer = editor.buffer().read(cx).snapshot(cx);
assert_eq!(
rename.range.start.to_offset(&buffer)..rename.range.end.to_offset(&buffer),

File diff suppressed because it is too large Load diff

View file

@ -2,8 +2,9 @@ use super::*;
use crate::{
scroll::scroll_amount::ScrollAmount,
test::{
assert_text_with_selections, build_editor, editor_lsp_test_context::EditorLspTestContext,
editor_test_context::EditorTestContext, select_ranges,
assert_text_with_selections, build_editor, build_editor_with_project,
editor_lsp_test_context::EditorLspTestContext, editor_test_context::EditorTestContext,
select_ranges,
},
JoinLines,
};
@ -4895,7 +4896,9 @@ async fn test_document_format_during_save(cx: &mut gpui::TestAppContext) {
let fake_server = fake_servers.next().await.unwrap();
let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
let editor = cx.add_window(|cx| build_editor(buffer, cx)).root(cx);
let editor = cx
.add_window(|cx| build_editor_with_project(project.clone(), buffer, cx))
.root(cx);
editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
assert!(cx.read(|cx| editor.is_dirty(cx)));
@ -5007,7 +5010,9 @@ async fn test_range_format_during_save(cx: &mut gpui::TestAppContext) {
let fake_server = fake_servers.next().await.unwrap();
let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
let editor = cx.add_window(|cx| build_editor(buffer, cx)).root(cx);
let editor = cx
.add_window(|cx| build_editor_with_project(project.clone(), buffer, cx))
.root(cx);
editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
assert!(cx.read(|cx| editor.is_dirty(cx)));
@ -5128,11 +5133,13 @@ async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) {
let fake_server = fake_servers.next().await.unwrap();
let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
let editor = cx.add_window(|cx| build_editor(buffer, cx)).root(cx);
let editor = cx
.add_window(|cx| build_editor_with_project(project, buffer, cx))
.root(cx);
editor.update(cx, |editor, cx| editor.set_text("one\ntwo\nthree\n", cx));
let format = editor.update(cx, |editor, cx| {
editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
editor.perform_format(FormatTrigger::Manual, cx)
});
fake_server
.handle_request::<lsp::request::Formatting, _, _>(move |params, _| async move {
@ -5149,7 +5156,7 @@ async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) {
.next()
.await;
cx.foreground().start_waiting();
format.await.unwrap();
format.unwrap().await.unwrap();
assert_eq!(
editor.read_with(cx, |editor, cx| editor.text(cx)),
"one, two\nthree\n"
@ -5166,11 +5173,11 @@ async fn test_document_format_manual_trigger(cx: &mut gpui::TestAppContext) {
unreachable!()
});
let format = editor.update(cx, |editor, cx| {
editor.perform_format(project, FormatTrigger::Manual, cx)
editor.perform_format(FormatTrigger::Manual, cx)
});
cx.foreground().advance_clock(super::FORMAT_TIMEOUT);
cx.foreground().start_waiting();
format.await.unwrap();
format.unwrap().await.unwrap();
assert_eq!(
editor.read_with(cx, |editor, cx| editor.text(cx)),
"one\ntwo\nthree\n"
@ -8010,13 +8017,15 @@ async fn test_document_format_with_prettier(cx: &mut gpui::TestAppContext) {
let buffer_text = "one\ntwo\nthree\n";
let buffer = cx.add_model(|cx| MultiBuffer::singleton(buffer, cx));
let editor = cx.add_window(|cx| build_editor(buffer, cx)).root(cx);
let editor = cx
.add_window(|cx| build_editor_with_project(project.clone(), buffer, cx))
.root(cx);
editor.update(cx, |editor, cx| editor.set_text(buffer_text, cx));
let format = editor.update(cx, |editor, cx| {
editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
editor.perform_format(FormatTrigger::Manual, cx)
});
format.await.unwrap();
format.unwrap().await.unwrap();
assert_eq!(
editor.read_with(cx, |editor, cx| editor.text(cx)),
buffer_text.to_string() + prettier_format_suffix,
@ -8027,9 +8036,9 @@ async fn test_document_format_with_prettier(cx: &mut gpui::TestAppContext) {
settings.defaults.formatter = Some(language_settings::Formatter::Auto)
});
let format = editor.update(cx, |editor, cx| {
editor.perform_format(project.clone(), FormatTrigger::Manual, cx)
editor.perform_format(FormatTrigger::Manual, cx)
});
format.await.unwrap();
format.unwrap().await.unwrap();
assert_eq!(
editor.read_with(cx, |editor, cx| editor.text(cx)),
buffer_text.to_string() + prettier_format_suffix + "\n" + prettier_format_suffix,

View file

@ -80,7 +80,7 @@ pub fn find_hovered_hint_part(
pub fn hover_at_inlay(editor: &mut Editor, inlay_hover: InlayHover, cx: &mut ViewContext<Editor>) {
if settings::get::<EditorSettings>(cx).hover_popover_enabled {
if editor.pending_rename.is_some() {
if editor.has_pending_rename(cx) {
return;
}
@ -166,7 +166,7 @@ fn show_hover(
ignore_timeout: bool,
cx: &mut ViewContext<Editor>,
) {
if editor.pending_rename.is_some() {
if editor.has_pending_rename(cx) {
return;
}

View file

@ -11,10 +11,10 @@ use crate::{
use anyhow::Context;
use clock::Global;
use futures::future;
use gpui::{ModelContext, ModelHandle, Task, ViewContext};
use gpui::{Entity, ModelContext, ModelHandle, Task, ViewContext};
use language::{language_settings::InlayHintKind, Buffer, BufferSnapshot};
use parking_lot::RwLock;
use project::{InlayHint, ResolveState};
use project::{InlayHint, Project, ResolveState};
use collections::{hash_map, HashMap, HashSet};
use language::language_settings::InlayHintSettings;
@ -24,9 +24,11 @@ use text::{ToOffset, ToPoint};
use util::post_inc;
pub struct InlayHintCache {
hints: HashMap<ExcerptId, Arc<RwLock<CachedExcerptHints>>>,
// TODO kb consider weak handles
project: ModelHandle<Project>,
pub(super) hints: HashMap<ExcerptId, Arc<RwLock<CachedExcerptHints>>>,
allowed_hint_kinds: HashSet<Option<InlayHintKind>>,
version: usize,
pub(super) version: usize,
pub(super) enabled: bool,
update_tasks: HashMap<ExcerptId, TasksForRanges>,
lsp_request_limiter: Arc<Semaphore>,
@ -236,9 +238,13 @@ impl TasksForRanges {
}
}
impl Entity for InlayHintCache {
type Event = ();
}
impl InlayHintCache {
pub fn new(inlay_hint_settings: InlayHintSettings) -> Self {
pub fn new(project: ModelHandle<Project>, inlay_hint_settings: InlayHintSettings) -> Self {
Self {
project,
allowed_hint_kinds: inlay_hint_settings.enabled_inlay_hint_kinds(),
enabled: inlay_hint_settings.enabled,
hints: HashMap::default(),
@ -304,6 +310,7 @@ impl InlayHintCache {
reason: &'static str,
excerpts_to_query: HashMap<ExcerptId, (ModelHandle<Buffer>, Global, Range<usize>)>,
invalidate: InvalidationStrategy,
cache: ModelHandle<Self>,
cx: &mut ViewContext<Editor>,
) -> Option<InlaySplice> {
if !self.enabled {
@ -336,6 +343,7 @@ impl InlayHintCache {
excerpts_to_query,
invalidate,
cache_version,
cache,
cx,
)
})
@ -538,7 +546,7 @@ impl InlayHintCache {
.read(cx)
.buffer(buffer_id)
.and_then(|buffer| {
let project = editor.project.as_ref()?;
let project = self.project;
Some(project.update(cx, |project, cx| {
project.resolve_inlay_hint(
hint_to_resolve,
@ -582,6 +590,7 @@ fn spawn_new_update_tasks(
excerpts_to_query: HashMap<ExcerptId, (ModelHandle<Buffer>, Global, Range<usize>)>,
invalidate: InvalidationStrategy,
update_cache_version: usize,
cache: ModelHandle<InlayHintCache>,
cx: &mut ViewContext<'_, '_, Editor>,
) {
let visible_hints = Arc::new(editor.visible_inlay_hints(cx));
@ -601,7 +610,8 @@ fn spawn_new_update_tasks(
continue;
}
let cached_excerpt_hints = editor.inlay_hint_cache.hints.get(&excerpt_id).cloned();
let cached_excerpt_hints =
cache.update(cx, |cache, _| cache.hints.get(&excerpt_id).cloned());
if let Some(cached_excerpt_hints) = &cached_excerpt_hints {
let cached_excerpt_hints = cached_excerpt_hints.read();
let cached_buffer_version = &cached_excerpt_hints.buffer_version;
@ -636,35 +646,39 @@ fn spawn_new_update_tasks(
reason,
};
let new_update_task = |query_ranges| {
new_update_task(
query,
query_ranges,
multi_buffer_snapshot,
buffer_snapshot.clone(),
Arc::clone(&visible_hints),
cached_excerpt_hints,
Arc::clone(&editor.inlay_hint_cache.lsp_request_limiter),
cx,
)
};
match editor.inlay_hint_cache.update_tasks.entry(excerpt_id) {
hash_map::Entry::Occupied(mut o) => {
o.get_mut().update_cached_tasks(
&buffer_snapshot,
let cache_handle = cache.clone();
cache.update(cx, |cache, _| {
let new_update_task = |query_ranges| {
new_update_task(
query,
query_ranges,
invalidate,
new_update_task,
);
multi_buffer_snapshot,
buffer_snapshot.clone(),
Arc::clone(&visible_hints),
cached_excerpt_hints,
Arc::clone(&cache.lsp_request_limiter),
cache_handle,
cx,
)
};
match cache.update_tasks.entry(excerpt_id) {
hash_map::Entry::Occupied(mut o) => {
o.get_mut().update_cached_tasks(
&buffer_snapshot,
query_ranges,
invalidate,
new_update_task,
);
}
hash_map::Entry::Vacant(v) => {
v.insert(TasksForRanges::new(
query_ranges.clone(),
new_update_task(query_ranges),
));
}
}
hash_map::Entry::Vacant(v) => {
v.insert(TasksForRanges::new(
query_ranges.clone(),
new_update_task(query_ranges),
));
}
}
})
}
}
@ -760,6 +774,7 @@ fn new_update_task(
visible_hints: Arc<Vec<Inlay>>,
cached_excerpt_hints: Option<Arc<RwLock<CachedExcerptHints>>>,
lsp_request_limiter: Arc<Semaphore>,
cache: ModelHandle<InlayHintCache>,
cx: &mut ViewContext<'_, '_, Editor>,
) -> Task<()> {
cx.spawn(|editor, mut cx| async move {
@ -775,6 +790,7 @@ fn new_update_task(
invalidate,
range,
Arc::clone(&lsp_request_limiter),
cache,
closure_cx.clone(),
)
};
@ -795,13 +811,9 @@ fn new_update_task(
let mut query_range_failed = |range: &Range<language::Anchor>, e: anyhow::Error| {
log::error!("inlay hint update task for range {range:?} failed: {e:#}");
editor
.update(&mut cx, |editor, _| {
if let Some(task_ranges) = editor
.inlay_hint_cache
.update_tasks
.get_mut(&query.excerpt_id)
{
cache
.update(&mut cx, |cache, _| {
if let Some(task_ranges) = cache.update_tasks.get_mut(&query.excerpt_id) {
task_ranges.invalidate_range(&buffer_snapshot, &range);
}
})
@ -846,6 +858,7 @@ async fn fetch_and_update_hints(
invalidate: bool,
fetch_range: Range<language::Anchor>,
lsp_request_limiter: Arc<Semaphore>,
cache: ModelHandle<InlayHintCache>,
mut cx: gpui::AsyncAppContext,
) -> anyhow::Result<()> {
let (lsp_request_guard, got_throttled) = if query.invalidate.should_invalidate() {
@ -880,13 +893,14 @@ async fn fetch_and_update_hints(
};
if query_not_around_visible_range {
log::trace!("Fetching inlay hints for range {fetch_range_to_log:?} got throttled and fell off the current visible range, skipping.");
if let Some(task_ranges) = editor
.inlay_hint_cache
.update_tasks
.get_mut(&query.excerpt_id)
{
task_ranges.invalidate_range(&buffer_snapshot, &fetch_range);
}
cache.update(cx, |cache, _| {
if let Some(task_ranges) = cache
.update_tasks
.get_mut(&query.excerpt_id)
{
task_ranges.invalidate_range(&buffer_snapshot, &fetch_range);
}
});
return None;
}
}
@ -895,7 +909,7 @@ async fn fetch_and_update_hints(
.read(cx)
.buffer(query.buffer_id)
.and_then(|buffer| {
let project = editor.project.as_ref()?;
let project = cache.read(cx).project;
Some(project.update(cx, |project, cx| {
project.inlay_hints(buffer, fetch_range.clone(), cx)
}))
@ -957,6 +971,7 @@ async fn fetch_and_update_hints(
invalidate,
buffer_snapshot,
multi_buffer_snapshot,
cache,
cx,
);
})
@ -1071,13 +1086,11 @@ fn apply_hint_update(
invalidate: bool,
buffer_snapshot: BufferSnapshot,
multi_buffer_snapshot: MultiBufferSnapshot,
cache: ModelHandle<InlayHintsCache>,
cx: &mut ViewContext<'_, '_, Editor>,
) {
let cached_excerpt_hints = editor
.inlay_hint_cache
.hints
.entry(new_update.excerpt_id)
.or_insert_with(|| {
cache.update(cx, |cache, cx| {
let cached_excerpt_hints = cache.hints.entry(new_update.excerpt_id).or_insert_with(|| {
Arc::new(RwLock::new(CachedExcerptHints {
version: query.cache_version,
buffer_version: buffer_snapshot.version().clone(),
@ -1086,112 +1099,111 @@ fn apply_hint_update(
hints_by_id: HashMap::default(),
}))
});
let mut cached_excerpt_hints = cached_excerpt_hints.write();
match query.cache_version.cmp(&cached_excerpt_hints.version) {
cmp::Ordering::Less => return,
cmp::Ordering::Greater | cmp::Ordering::Equal => {
cached_excerpt_hints.version = query.cache_version;
let mut cached_excerpt_hints = cached_excerpt_hints.write();
match query.cache_version.cmp(&cached_excerpt_hints.version) {
cmp::Ordering::Less => return,
cmp::Ordering::Greater | cmp::Ordering::Equal => {
cached_excerpt_hints.version = query.cache_version;
}
}
}
let mut cached_inlays_changed = !new_update.remove_from_cache.is_empty();
cached_excerpt_hints
.ordered_hints
.retain(|hint_id| !new_update.remove_from_cache.contains(hint_id));
cached_excerpt_hints
.hints_by_id
.retain(|hint_id, _| !new_update.remove_from_cache.contains(hint_id));
let mut splice = InlaySplice {
to_remove: new_update.remove_from_visible,
to_insert: Vec::new(),
};
for new_hint in new_update.add_to_cache {
let insert_position = match cached_excerpt_hints
let mut cached_inlays_changed = !new_update.remove_from_cache.is_empty();
cached_excerpt_hints
.ordered_hints
.binary_search_by(|probe| {
cached_excerpt_hints.hints_by_id[probe]
.position
.cmp(&new_hint.position, &buffer_snapshot)
}) {
Ok(i) => {
let mut insert_position = Some(i);
for id in &cached_excerpt_hints.ordered_hints[i..] {
let cached_hint = &cached_excerpt_hints.hints_by_id[id];
if new_hint
.position
.cmp(&cached_hint.position, &buffer_snapshot)
.is_gt()
{
break;
}
if cached_hint.text() == new_hint.text() {
insert_position = None;
break;
}
}
insert_position
}
Err(i) => Some(i),
.retain(|hint_id| !new_update.remove_from_cache.contains(hint_id));
cached_excerpt_hints
.hints_by_id
.retain(|hint_id, _| !new_update.remove_from_cache.contains(hint_id));
let mut splice = InlaySplice {
to_remove: new_update.remove_from_visible,
to_insert: Vec::new(),
};
for new_hint in new_update.add_to_cache {
let insert_position =
match cached_excerpt_hints
.ordered_hints
.binary_search_by(|probe| {
cached_excerpt_hints.hints_by_id[probe]
.position
.cmp(&new_hint.position, &buffer_snapshot)
}) {
Ok(i) => {
let mut insert_position = Some(i);
for id in &cached_excerpt_hints.ordered_hints[i..] {
let cached_hint = &cached_excerpt_hints.hints_by_id[id];
if new_hint
.position
.cmp(&cached_hint.position, &buffer_snapshot)
.is_gt()
{
break;
}
if cached_hint.text() == new_hint.text() {
insert_position = None;
break;
}
}
insert_position
}
Err(i) => Some(i),
};
if let Some(insert_position) = insert_position {
let new_inlay_id = post_inc(&mut editor.next_inlay_id);
if editor
.inlay_hint_cache
.allowed_hint_kinds
.contains(&new_hint.kind)
{
let new_hint_position =
multi_buffer_snapshot.anchor_in_excerpt(query.excerpt_id, new_hint.position);
splice
.to_insert
.push(Inlay::hint(new_inlay_id, new_hint_position, &new_hint));
}
let new_id = InlayId::Hint(new_inlay_id);
cached_excerpt_hints.hints_by_id.insert(new_id, new_hint);
cached_excerpt_hints
.ordered_hints
.insert(insert_position, new_id);
cached_inlays_changed = true;
}
}
cached_excerpt_hints.buffer_version = buffer_snapshot.version().clone();
drop(cached_excerpt_hints);
if invalidate {
let mut outdated_excerpt_caches = HashSet::default();
for (excerpt_id, excerpt_hints) in &editor.inlay_hint_cache().hints {
let excerpt_hints = excerpt_hints.read();
if excerpt_hints.buffer_id == query.buffer_id
&& excerpt_id != &query.excerpt_id
&& buffer_snapshot
.version()
.changed_since(&excerpt_hints.buffer_version)
{
outdated_excerpt_caches.insert(*excerpt_id);
splice
.to_remove
.extend(excerpt_hints.ordered_hints.iter().copied());
if let Some(insert_position) = insert_position {
let new_inlay_id = post_inc(&mut editor.next_inlay_id);
if cache.allowed_hint_kinds.contains(&new_hint.kind) {
let new_hint_position = multi_buffer_snapshot
.anchor_in_excerpt(query.excerpt_id, new_hint.position);
splice
.to_insert
.push(Inlay::hint(new_inlay_id, new_hint_position, &new_hint));
}
let new_id = InlayId::Hint(new_inlay_id);
cached_excerpt_hints.hints_by_id.insert(new_id, new_hint);
cached_excerpt_hints
.ordered_hints
.insert(insert_position, new_id);
cached_inlays_changed = true;
}
}
cached_inlays_changed |= !outdated_excerpt_caches.is_empty();
editor
.inlay_hint_cache
.hints
.retain(|excerpt_id, _| !outdated_excerpt_caches.contains(excerpt_id));
}
cached_excerpt_hints.buffer_version = buffer_snapshot.version().clone();
drop(cached_excerpt_hints);
});
let InlaySplice {
to_remove,
to_insert,
} = splice;
let displayed_inlays_changed = !to_remove.is_empty() || !to_insert.is_empty();
if cached_inlays_changed || displayed_inlays_changed {
editor.inlay_hint_cache.version += 1;
}
if displayed_inlays_changed {
editor.splice_inlay_hints(to_remove, to_insert, cx)
}
cache.update(cx, |cache, _| {
if invalidate {
let mut outdated_excerpt_caches = HashSet::default();
for (excerpt_id, excerpt_hints) in &cache.hints {
let excerpt_hints = excerpt_hints.read();
if excerpt_hints.buffer_id == query.buffer_id
&& excerpt_id != &query.excerpt_id
&& buffer_snapshot
.version()
.changed_since(&excerpt_hints.buffer_version)
{
outdated_excerpt_caches.insert(*excerpt_id);
splice
.to_remove
.extend(excerpt_hints.ordered_hints.iter().copied());
}
}
cached_inlays_changed |= !outdated_excerpt_caches.is_empty();
cache
.hints
.retain(|excerpt_id, _| !outdated_excerpt_caches.contains(excerpt_id));
}
let InlaySplice {
to_remove,
to_insert,
} = splice;
let displayed_inlays_changed = !to_remove.is_empty() || !to_insert.is_empty();
if cached_inlays_changed || displayed_inlays_changed {
cache.version += 1;
}
if displayed_inlays_changed {
editor.splice_inlay_hints(to_remove, to_insert, cx)
}
})
}
#[cfg(test)]
@ -1287,7 +1299,8 @@ pub mod tests {
"Cache should use editor settings to get the allowed hint kinds"
);
assert_eq!(
inlay_cache.version, edits_made,
editor.inlay_hint_cache_version(),
edits_made,
"The editor update the cache version after every cache/view change"
);
});
@ -1312,7 +1325,8 @@ pub mod tests {
"Cache should use editor settings to get the allowed hint kinds"
);
assert_eq!(
inlay_cache.version, edits_made,
editor.inlay_hint_cache_version(),
edits_made,
"The editor update the cache version after every cache/view change"
);
});
@ -1392,7 +1406,7 @@ pub mod tests {
);
assert_eq!(expected_hints, visible_hint_labels(editor, cx));
assert_eq!(
editor.inlay_hint_cache().version,
editor.inlay_hint_cache_version(),
edits_made,
"The editor update the cache version after every cache/view change"
);
@ -1423,7 +1437,7 @@ pub mod tests {
);
assert_eq!(expected_hints, visible_hint_labels(editor, cx));
assert_eq!(
editor.inlay_hint_cache().version,
editor.inlay_hint_cache_version(),
edits_made,
"Should not update the cache while the work task is running"
);
@ -1447,7 +1461,7 @@ pub mod tests {
);
assert_eq!(expected_hints, visible_hint_labels(editor, cx));
assert_eq!(
editor.inlay_hint_cache().version,
editor.inlay_hint_cache_version(),
edits_made,
"Cache version should udpate once after the work task is done"
);
@ -1566,7 +1580,7 @@ pub mod tests {
);
assert_eq!(expected_hints, visible_hint_labels(editor, cx));
assert_eq!(
editor.inlay_hint_cache().version,
editor.inlay_hint_cache_version(),
1,
"Rust editor update the cache version after every cache/view change"
);
@ -1623,7 +1637,7 @@ pub mod tests {
"Markdown editor should have a separate verison, repeating Rust editor rules"
);
assert_eq!(expected_hints, visible_hint_labels(editor, cx));
assert_eq!(editor.inlay_hint_cache().version, 1);
assert_eq!(editor.inlay_hint_cache_version(), 1);
});
rs_editor.update(cx, |editor, cx| {
@ -1640,7 +1654,7 @@ pub mod tests {
);
assert_eq!(expected_hints, visible_hint_labels(editor, cx));
assert_eq!(
editor.inlay_hint_cache().version,
editor.inlay_hint_cache_version(),
2,
"Every time hint cache changes, cache version should be incremented"
);
@ -1653,7 +1667,7 @@ pub mod tests {
"Markdown editor should not be affected by Rust editor changes"
);
assert_eq!(expected_hints, visible_hint_labels(editor, cx));
assert_eq!(editor.inlay_hint_cache().version, 1);
assert_eq!(editor.inlay_hint_cache_version(), 1);
});
md_editor.update(cx, |editor, cx| {
@ -1669,7 +1683,7 @@ pub mod tests {
"Rust editor should not be affected by Markdown editor changes"
);
assert_eq!(expected_hints, visible_hint_labels(editor, cx));
assert_eq!(editor.inlay_hint_cache().version, 2);
assert_eq!(editor.inlay_hint_cache_version(), 2);
});
rs_editor.update(cx, |editor, cx| {
let expected_hints = vec!["1".to_string()];
@ -1679,7 +1693,7 @@ pub mod tests {
"Markdown editor should also change independently"
);
assert_eq!(expected_hints, visible_hint_labels(editor, cx));
assert_eq!(editor.inlay_hint_cache().version, 2);
assert_eq!(editor.inlay_hint_cache_version(), 2);
});
}
@ -1801,7 +1815,7 @@ pub mod tests {
visible_hint_labels(editor, cx)
);
assert_eq!(
editor.inlay_hint_cache().version,
editor.inlay_hint_cache_version(),
edits_made,
"Should not update cache version due to new loaded hints being the same"
);
@ -1936,7 +1950,7 @@ pub mod tests {
assert!(cached_hint_labels(editor).is_empty());
assert!(visible_hint_labels(editor, cx).is_empty());
assert_eq!(
editor.inlay_hint_cache().version, edits_made,
editor.inlay_hint_cache_version(), edits_made,
"The editor should not update the cache version after /refresh query without updates"
);
});
@ -2007,7 +2021,7 @@ pub mod tests {
vec!["parameter hint".to_string()],
visible_hint_labels(editor, cx),
);
assert_eq!(editor.inlay_hint_cache().version, edits_made);
assert_eq!(editor.inlay_hint_cache_version(), edits_made);
});
}
@ -2086,7 +2100,7 @@ pub mod tests {
);
assert_eq!(expected_hints, visible_hint_labels(editor, cx));
assert_eq!(
editor.inlay_hint_cache().version, 1,
editor.inlay_hint_cache_version(), 1,
"Only one update should be registered in the cache after all cancellations"
);
});
@ -2131,7 +2145,7 @@ pub mod tests {
);
assert_eq!(expected_hints, visible_hint_labels(editor, cx));
assert_eq!(
editor.inlay_hint_cache().version,
editor.inlay_hint_cache_version(),
2,
"Should update the cache version once more, for the new change"
);
@ -2301,7 +2315,7 @@ pub mod tests {
);
assert_eq!(expected_hints, visible_hint_labels(editor, cx), "Should display only hints from the visible range");
assert_eq!(
editor.inlay_hint_cache().version, requests_count,
editor.inlay_hint_cache_version(), requests_count,
"LSP queries should've bumped the cache version"
);
});
@ -2363,7 +2377,7 @@ pub mod tests {
);
assert_eq!(expected_hints, visible_hint_labels(editor, cx));
assert_eq!(
editor.inlay_hint_cache().version,
editor.inlay_hint_cache_version(),
lsp_requests,
"Should update the cache for every LSP response with hints added"
);
@ -2427,7 +2441,7 @@ pub mod tests {
assert_eq!(expected_hints, cached_hint_labels(editor),
"Should have hints from the new LSP response after the edit");
assert_eq!(expected_hints, visible_hint_labels(editor, cx));
assert_eq!(editor.inlay_hint_cache().version, lsp_requests, "Should update the cache for every LSP response with hints added");
assert_eq!(editor.inlay_hint_cache_version(), lsp_requests, "Should update the cache for every LSP response with hints added");
});
}
@ -2650,7 +2664,7 @@ pub mod tests {
"When scroll is at the edge of a multibuffer, its visible excerpts only should be queried for inlay hints"
);
assert_eq!(expected_hints, visible_hint_labels(editor, cx));
assert_eq!(editor.inlay_hint_cache().version, expected_hints.len(), "Every visible excerpt hints should bump the verison");
assert_eq!(editor.inlay_hint_cache_version(), expected_hints.len(), "Every visible excerpt hints should bump the verison");
});
editor.update(cx, |editor, cx| {
@ -2680,7 +2694,7 @@ pub mod tests {
assert_eq!(expected_hints, cached_hint_labels(editor),
"With more scrolls of the multibuffer, more hints should be added into the cache and nothing invalidated without edits");
assert_eq!(expected_hints, visible_hint_labels(editor, cx));
assert_eq!(editor.inlay_hint_cache().version, expected_hints.len(),
assert_eq!(editor.inlay_hint_cache_version(), expected_hints.len(),
"Due to every excerpt having one hint, we update cache per new excerpt scrolled");
});
@ -2711,7 +2725,7 @@ pub mod tests {
assert_eq!(expected_hints, cached_hint_labels(editor),
"After multibuffer was scrolled to the end, all hints for all excerpts should be fetched");
assert_eq!(expected_hints, visible_hint_labels(editor, cx));
assert_eq!(editor.inlay_hint_cache().version, expected_hints.len());
assert_eq!(editor.inlay_hint_cache_version(), expected_hints.len());
expected_hints.len()
});
@ -2739,7 +2753,7 @@ pub mod tests {
assert_eq!(expected_hints, cached_hint_labels(editor),
"After multibuffer was scrolled to the end, further scrolls up should not bring more hints");
assert_eq!(expected_hints, visible_hint_labels(editor, cx));
assert_eq!(editor.inlay_hint_cache().version, last_scroll_update_version, "No updates should happen during scrolling already scolled buffer");
assert_eq!(editor.inlay_hint_cache_version(), last_scroll_update_version, "No updates should happen during scrolling already scolled buffer");
});
editor_edited.store(true, Ordering::Release);
@ -2769,7 +2783,7 @@ all hints should be invalidated and requeried for all of its visible excerpts"
);
assert_eq!(expected_hints, visible_hint_labels(editor, cx));
let current_cache_version = editor.inlay_hint_cache().version;
let current_cache_version = editor.inlay_hint_cache_version();
let minimum_expected_version = last_scroll_update_version + expected_hints.len();
assert!(
current_cache_version == minimum_expected_version || current_cache_version == minimum_expected_version + 1,
@ -2953,7 +2967,7 @@ all hints should be invalidated and requeried for all of its visible excerpts"
"All hints are disabled and should not be shown despite being present in the cache"
);
assert_eq!(
editor.inlay_hint_cache().version,
editor.inlay_hint_cache_version(),
2,
"Cache should update once per excerpt query"
);
@ -2976,7 +2990,7 @@ all hints should be invalidated and requeried for all of its visible excerpts"
"All hints are disabled and should not be shown despite being present in the cache"
);
assert_eq!(
editor.inlay_hint_cache().version,
editor.inlay_hint_cache_version(),
3,
"Excerpt removal should trigger a cache update"
);
@ -3004,7 +3018,7 @@ all hints should be invalidated and requeried for all of its visible excerpts"
"Settings change should make cached hints visible"
);
assert_eq!(
editor.inlay_hint_cache().version,
editor.inlay_hint_cache_version(),
4,
"Settings change should trigger a cache update"
);
@ -3114,7 +3128,7 @@ all hints should be invalidated and requeried for all of its visible excerpts"
let expected_hints = vec!["1".to_string()];
assert_eq!(expected_hints, cached_hint_labels(editor));
assert_eq!(expected_hints, visible_hint_labels(editor, cx));
assert_eq!(editor.inlay_hint_cache().version, 1);
assert_eq!(editor.inlay_hint_cache_version(), 1);
});
}
@ -3171,7 +3185,7 @@ all hints should be invalidated and requeried for all of its visible excerpts"
);
assert_eq!(expected_hints, visible_hint_labels(editor, cx));
assert_eq!(
editor.inlay_hint_cache().version,
editor.inlay_hint_cache_version(),
1,
"First toggle should be cache's first update"
);
@ -3187,7 +3201,7 @@ all hints should be invalidated and requeried for all of its visible excerpts"
"Should clear hints after 2nd toggle"
);
assert!(visible_hint_labels(editor, cx).is_empty());
assert_eq!(editor.inlay_hint_cache().version, 2);
assert_eq!(editor.inlay_hint_cache_version(), 2);
});
update_test_language_settings(cx, |settings| {
@ -3207,7 +3221,7 @@ all hints should be invalidated and requeried for all of its visible excerpts"
"Should query LSP hints for the 2nd time after enabling hints in settings"
);
assert_eq!(expected_hints, visible_hint_labels(editor, cx));
assert_eq!(editor.inlay_hint_cache().version, 3);
assert_eq!(editor.inlay_hint_cache_version(), 3);
});
editor.update(cx, |editor, cx| {
@ -3220,7 +3234,7 @@ all hints should be invalidated and requeried for all of its visible excerpts"
"Should clear hints after enabling in settings and a 3rd toggle"
);
assert!(visible_hint_labels(editor, cx).is_empty());
assert_eq!(editor.inlay_hint_cache().version, 4);
assert_eq!(editor.inlay_hint_cache_version(), 4);
});
editor.update(cx, |editor, cx| {
@ -3235,7 +3249,7 @@ all hints should be invalidated and requeried for all of its visible excerpts"
"Should query LSP hints for the 3rd time after enabling hints in settings and toggling them back on"
);
assert_eq!(expected_hints, visible_hint_labels(editor, cx));
assert_eq!(editor.inlay_hint_cache().version, 5);
assert_eq!(editor.inlay_hint_cache_version(), 5);
});
}
@ -3318,7 +3332,7 @@ all hints should be invalidated and requeried for all of its visible excerpts"
editor.update(cx, |editor, cx| {
assert!(cached_hint_labels(editor).is_empty());
assert!(visible_hint_labels(editor, cx).is_empty());
assert_eq!(editor.inlay_hint_cache().version, 0);
assert_eq!(editor.inlay_hint_cache_version(), 0);
});
("/a/main.rs", editor, fake_server)

View file

@ -3,7 +3,7 @@ use crate::{
movement::surrounding_word, persistence::DB, scroll::ScrollAnchor, Anchor, Autoscroll, Editor,
Event, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, NavigationData, ToPoint as _,
};
use anyhow::{Context, Result};
use anyhow::{anyhow, Context, Result};
use collections::HashSet;
use futures::future::try_join_all;
use gpui::{
@ -652,7 +652,9 @@ impl Item for Editor {
cx: &mut ViewContext<Self>,
) -> Task<Result<()>> {
self.report_editor_event("save", None, cx);
let format = self.perform_format(project.clone(), FormatTrigger::Save, cx);
let Some(format) = self.perform_format(FormatTrigger::Save, cx) else {
return Task::ready(Err(anyhow!("Editor does not have a formatter attached")));
};
let buffers = self.buffer().clone().read(cx).all_buffers();
cx.spawn(|_, mut cx| async move {
format.await?;

View file

@ -178,6 +178,9 @@ pub fn update_inlay_link_and_hover_points(
let mut go_to_definition_updated = false;
let mut hover_updated = false;
if let Some(hovered_offset) = hovered_offset {
let Some(inlay_hint_cache) = editor.inlay_hints.clone() else {
return;
};
let buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
let previous_valid_anchor = buffer_snapshot.anchor_at(
point_for_position.previous_valid.to_point(snapshot),
@ -202,82 +205,84 @@ pub fn update_inlay_link_and_hover_points(
})
.max_by_key(|hint| hint.id)
{
let inlay_hint_cache = editor.inlay_hint_cache();
let excerpt_id = previous_valid_anchor.excerpt_id;
if let Some(cached_hint) = inlay_hint_cache.hint_by_id(excerpt_id, hovered_hint.id) {
match cached_hint.resolve_state {
ResolveState::CanResolve(_, _) => {
if let Some(buffer_id) = previous_valid_anchor.buffer_id {
inlay_hint_cache.spawn_hint_resolve(
buffer_id,
excerpt_id,
hovered_hint.id,
cx,
);
}
}
ResolveState::Resolved => {
let mut extra_shift_left = 0;
let mut extra_shift_right = 0;
if cached_hint.padding_left {
extra_shift_left += 1;
extra_shift_right += 1;
}
if cached_hint.padding_right {
extra_shift_right += 1;
}
match cached_hint.label {
project::InlayHintLabel::String(_) => {
if let Some(tooltip) = cached_hint.tooltip {
hover_popover::hover_at_inlay(
editor,
InlayHover {
excerpt: excerpt_id,
tooltip: match tooltip {
InlayHintTooltip::String(text) => HoverBlock {
text,
kind: HoverBlockKind::PlainText,
},
InlayHintTooltip::MarkupContent(content) => {
HoverBlock {
text: content.value,
kind: content.kind,
}
}
},
range: InlayHighlight {
inlay: hovered_hint.id,
inlay_position: hovered_hint.position,
range: extra_shift_left
..hovered_hint.text.len() + extra_shift_right,
},
},
cx,
);
hover_updated = true;
}
inlay_hint_cache.update(cx, |inlay_hint_cache, cx| {
if let Some(cached_hint) = inlay_hint_cache.hint_by_id(excerpt_id, hovered_hint.id)
{
match cached_hint.resolve_state {
ResolveState::CanResolve(_, _) => {
if let Some(buffer_id) = previous_valid_anchor.buffer_id {
inlay_hint_cache.spawn_hint_resolve(
buffer_id,
excerpt_id,
hovered_hint.id,
cx,
);
}
project::InlayHintLabel::LabelParts(label_parts) => {
let hint_start =
snapshot.anchor_to_inlay_offset(hovered_hint.position);
if let Some((hovered_hint_part, part_range)) =
hover_popover::find_hovered_hint_part(
label_parts,
hint_start,
hovered_offset,
)
{
let highlight_start =
(part_range.start - hint_start).0 + extra_shift_left;
let highlight_end =
(part_range.end - hint_start).0 + extra_shift_right;
let highlight = InlayHighlight {
inlay: hovered_hint.id,
inlay_position: hovered_hint.position,
range: highlight_start..highlight_end,
};
if let Some(tooltip) = hovered_hint_part.tooltip {
}
ResolveState::Resolved => {
let mut extra_shift_left = 0;
let mut extra_shift_right = 0;
if cached_hint.padding_left {
extra_shift_left += 1;
extra_shift_right += 1;
}
if cached_hint.padding_right {
extra_shift_right += 1;
}
match cached_hint.label {
project::InlayHintLabel::String(_) => {
if let Some(tooltip) = cached_hint.tooltip {
hover_popover::hover_at_inlay(
editor,
InlayHover {
excerpt: excerpt_id,
tooltip: match tooltip {
InlayHintTooltip::String(text) => HoverBlock {
text,
kind: HoverBlockKind::PlainText,
},
InlayHintTooltip::MarkupContent(content) => {
HoverBlock {
text: content.value,
kind: content.kind,
}
}
},
range: InlayHighlight {
inlay: hovered_hint.id,
inlay_position: hovered_hint.position,
range: extra_shift_left
..hovered_hint.text.len()
+ extra_shift_right,
},
},
cx,
);
hover_updated = true;
}
}
project::InlayHintLabel::LabelParts(label_parts) => {
let hint_start =
snapshot.anchor_to_inlay_offset(hovered_hint.position);
if let Some((hovered_hint_part, part_range)) =
hover_popover::find_hovered_hint_part(
label_parts,
hint_start,
hovered_offset,
)
{
let highlight_start =
(part_range.start - hint_start).0 + extra_shift_left;
let highlight_end =
(part_range.end - hint_start).0 + extra_shift_right;
let highlight = InlayHighlight {
inlay: hovered_hint.id,
inlay_position: hovered_hint.position,
range: highlight_start..highlight_end,
};
if let Some(tooltip) = hovered_hint_part.tooltip {
hover_popover::hover_at_inlay(
editor,
InlayHover {
excerpt: excerpt_id,
@ -299,31 +304,32 @@ pub fn update_inlay_link_and_hover_points(
},
cx,
);
hover_updated = true;
}
if let Some((language_server_id, location)) =
hovered_hint_part.location
{
go_to_definition_updated = true;
update_go_to_definition_link(
editor,
Some(GoToDefinitionTrigger::InlayHint(
highlight,
location,
language_server_id,
)),
cmd_held,
shift_held,
cx,
);
hover_updated = true;
}
if let Some((language_server_id, location)) =
hovered_hint_part.location
{
go_to_definition_updated = true;
update_go_to_definition_link(
editor,
Some(GoToDefinitionTrigger::InlayHint(
highlight,
location,
language_server_id,
)),
cmd_held,
shift_held,
cx,
);
}
}
}
}
};
};
}
ResolveState::Resolving => {}
}
ResolveState::Resolving => {}
}
}
})
}
}
@ -353,7 +359,7 @@ pub fn show_link_definition(
hide_link_definition(editor, cx);
}
if editor.pending_rename.is_some() {
if editor.has_pending_rename(cx) {
return;
}