Switch over to inlay map for Copilot suggestions

Co-Authored-By: Antonio Scandurra <antonio@zed.dev>
This commit is contained in:
Kirill Bulatov 2023-06-15 11:24:25 +03:00
parent 89137e2e83
commit 8cdf1a0faf
4 changed files with 128 additions and 107 deletions

View File

@ -6,12 +6,12 @@ mod tab_map;
mod wrap_map;
use crate::{
display_map::inlay_map::InlayProperties, inlay_cache::InlayId, Anchor, AnchorRangeExt,
MultiBuffer, MultiBufferSnapshot, ToOffset, ToPoint,
inlay_cache::{Inlay, InlayId, InlayProperties},
Anchor, AnchorRangeExt, MultiBuffer, MultiBufferSnapshot, ToOffset, ToPoint,
};
pub use block_map::{BlockMap, BlockPoint};
use collections::{HashMap, HashSet};
use fold_map::{FoldMap, FoldOffset};
use fold_map::FoldMap;
use gpui::{
color::Color,
fonts::{FontId, HighlightStyle},
@ -26,6 +26,7 @@ pub use suggestion_map::Suggestion;
use suggestion_map::SuggestionMap;
use sum_tree::{Bias, TreeMap};
use tab_map::TabMap;
use text::Rope;
use wrap_map::WrapMap;
pub use block_map::{
@ -244,33 +245,6 @@ impl DisplayMap {
self.text_highlights.remove(&Some(type_id))
}
pub fn has_suggestion(&self) -> bool {
self.suggestion_map.has_suggestion()
}
pub fn replace_suggestion<T>(
&mut self,
new_suggestion: Option<Suggestion<T>>,
cx: &mut ModelContext<Self>,
) -> Option<Suggestion<FoldOffset>>
where
T: ToPoint,
{
let snapshot = self.buffer.read(cx).snapshot(cx);
let edits = self.buffer_subscription.consume().into_inner();
let tab_size = Self::tab_size(&self.buffer, cx);
let (snapshot, edits) = self.fold_map.read(snapshot, edits);
let (snapshot, edits, old_suggestion) =
self.suggestion_map.replace(new_suggestion, snapshot, edits);
let (snapshot, edits) = self.inlay_map.sync(snapshot, edits);
let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
let (snapshot, edits) = self
.wrap_map
.update(cx, |map, cx| map.sync(snapshot, edits, cx));
self.block_map.read(snapshot, edits);
old_suggestion
}
pub fn set_font(&self, font_id: FontId, font_size: f32, cx: &mut ModelContext<Self>) -> bool {
self.wrap_map
.update(cx, |map, cx| map.set_font(font_id, font_size, cx))
@ -285,10 +259,10 @@ impl DisplayMap {
.update(cx, |map, cx| map.set_wrap_width(width, cx))
}
pub fn splice_inlays(
pub fn splice_inlays<T: Into<Rope>>(
&mut self,
to_remove: Vec<InlayId>,
to_insert: Vec<(InlayId, Anchor, project::InlayHint)>,
to_insert: Vec<(InlayId, InlayProperties<T>)>,
cx: &mut ModelContext<Self>,
) {
let buffer_snapshot = self.buffer.read(cx).snapshot(cx);
@ -303,28 +277,7 @@ impl DisplayMap {
.update(cx, |map, cx| map.sync(snapshot, edits, cx));
self.block_map.read(snapshot, edits);
let new_inlays: Vec<(InlayId, InlayProperties<String>)> = to_insert
.into_iter()
.map(|(inlay_id, hint_anchor, hint)| {
let mut text = hint.text();
// TODO kb styling instead?
if hint.padding_right {
text.push(' ');
}
if hint.padding_left {
text.insert(0, ' ');
}
(
inlay_id,
InlayProperties {
position: hint_anchor.bias_left(&buffer_snapshot),
text,
},
)
})
.collect();
let (snapshot, edits) = self.inlay_map.splice(to_remove, new_inlays);
let (snapshot, edits) = self.inlay_map.splice(to_remove, to_insert);
let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
let (snapshot, edits) = self
.wrap_map

View File

@ -5,7 +5,10 @@ use super::{
},
TextHighlights,
};
use crate::{inlay_cache::InlayId, Anchor, MultiBufferSnapshot, ToPoint};
use crate::{
inlay_cache::{Inlay, InlayId, InlayProperties},
MultiBufferSnapshot, ToPoint,
};
use collections::{BTreeSet, HashMap};
use gpui::fonts::HighlightStyle;
use language::{Chunk, Edit, Point, Rope, TextSummary};
@ -140,19 +143,6 @@ pub struct InlayChunks<'a> {
highlight_style: Option<HighlightStyle>,
}
#[derive(Debug, Clone)]
pub struct Inlay {
pub(super) id: InlayId,
pub(super) position: Anchor,
pub(super) text: Rope,
}
#[derive(Debug, Clone)]
pub struct InlayProperties<T> {
pub position: Anchor,
pub text: T,
}
impl<'a> Iterator for InlayChunks<'a> {
type Item = Chunk<'a>;
@ -431,6 +421,21 @@ impl InlayMap {
snapshot.version += 1;
let mut edits = BTreeSet::new();
self.inlays.retain(|inlay| !to_remove.contains(&inlay.id));
for inlay_id in to_remove {
if let Some(inlay) = self.inlays_by_id.remove(&inlay_id) {
let buffer_point = inlay.position.to_point(snapshot.buffer_snapshot());
let fold_point = snapshot
.suggestion_snapshot
.fold_snapshot
.to_fold_point(buffer_point, Bias::Left);
let suggestion_point = snapshot.suggestion_snapshot.to_suggestion_point(fold_point);
let suggestion_offset = snapshot.suggestion_snapshot.to_offset(suggestion_point);
edits.insert(suggestion_offset);
}
}
for (id, properties) in to_insert {
let inlay = Inlay {
id,
@ -458,20 +463,6 @@ impl InlayMap {
edits.insert(suggestion_offset);
}
self.inlays.retain(|inlay| !to_remove.contains(&inlay.id));
for inlay_id in to_remove {
if let Some(inlay) = self.inlays_by_id.remove(&inlay_id) {
let buffer_point = inlay.position.to_point(snapshot.buffer_snapshot());
let fold_point = snapshot
.suggestion_snapshot
.fold_snapshot
.to_fold_point(buffer_point, Bias::Left);
let suggestion_point = snapshot.suggestion_snapshot.to_suggestion_point(fold_point);
let suggestion_offset = snapshot.suggestion_snapshot.to_offset(suggestion_point);
edits.insert(suggestion_offset);
}
}
let suggestion_snapshot = snapshot.suggestion_snapshot.clone();
let suggestion_edits = edits
.into_iter()

View File

@ -54,7 +54,9 @@ use gpui::{
};
use highlight_matching_bracket::refresh_matching_bracket_highlights;
use hover_popover::{hide_hover, HoverState};
use inlay_cache::{InlayCache, InlayRefreshReason, InlaySplice, QueryInlaysRange};
use inlay_cache::{
Inlay, InlayCache, InlayId, InlayProperties, InlayRefreshReason, InlaySplice, QueryInlaysRange,
};
pub use items::MAX_TAB_TITLE_LEN;
use itertools::Itertools;
pub use language::{char_kind, CharKind};
@ -95,6 +97,7 @@ use std::{
time::{Duration, Instant},
};
pub use sum_tree::Bias;
use text::Rope;
use theme::{DiagnosticStyle, Theme, ThemeSettings};
use util::{post_inc, RangeExt, ResultExt, TryFutureExt};
use workspace::{ItemNavHistory, ViewId, Workspace};
@ -1061,6 +1064,7 @@ pub struct CopilotState {
cycled: bool,
completions: Vec<copilot::Completion>,
active_completion_index: usize,
suggestion: Option<Inlay>,
}
impl Default for CopilotState {
@ -1072,6 +1076,7 @@ impl Default for CopilotState {
completions: Default::default(),
active_completion_index: 0,
cycled: false,
suggestion: None,
}
}
}
@ -2597,9 +2602,7 @@ impl Editor {
if !settings::get::<EditorSettings>(cx).inlay_hints.enabled {
let to_remove = self.inlay_cache.clear();
self.display_map.update(cx, |display_map, cx| {
display_map.splice_inlays(to_remove, Vec::new(), cx);
});
self.splice_inlay_hints(to_remove, Vec::new(), cx);
return;
}
@ -2609,9 +2612,7 @@ impl Editor {
to_remove,
to_insert,
} = self.inlay_cache.apply_settings(new_settings);
self.display_map.update(cx, |display_map, cx| {
display_map.splice_inlays(to_remove, to_insert, cx);
});
self.splice_inlay_hints(to_remove, to_insert, cx);
}
InlayRefreshReason::Regular => {
let buffer_handle = self.buffer().clone();
@ -2652,9 +2653,7 @@ impl Editor {
.context("inlay cache hint fetch")?;
editor.update(&mut cx, |editor, cx| {
editor.display_map.update(cx, |display_map, cx| {
display_map.splice_inlays(to_remove, to_insert, cx);
});
editor.splice_inlay_hints(to_remove, to_insert, cx)
})
})
.detach_and_log_err(cx);
@ -2662,6 +2661,40 @@ impl Editor {
}
}
fn splice_inlay_hints(
&self,
to_remove: Vec<InlayId>,
to_insert: Vec<(InlayId, Anchor, project::InlayHint)>,
cx: &mut ViewContext<Self>,
) {
let buffer = self.buffer.read(cx).read(cx);
let new_inlays: Vec<(InlayId, InlayProperties<String>)> = to_insert
.into_iter()
.map(|(inlay_id, hint_anchor, hint)| {
let mut text = hint.text();
// TODO kb styling instead?
if hint.padding_right {
text.push(' ');
}
if hint.padding_left {
text.insert(0, ' ');
}
(
inlay_id,
InlayProperties {
position: hint_anchor.bias_left(&buffer),
text,
},
)
})
.collect();
drop(buffer);
self.display_map.update(cx, |display_map, cx| {
display_map.splice_inlays(to_remove, new_inlays, cx);
});
}
fn trigger_on_type_formatting(
&self,
input: String,
@ -3312,10 +3345,7 @@ impl Editor {
}
fn accept_copilot_suggestion(&mut self, cx: &mut ViewContext<Self>) -> bool {
if let Some(suggestion) = self
.display_map
.update(cx, |map, cx| map.replace_suggestion::<usize>(None, cx))
{
if let Some(suggestion) = self.take_active_copilot_suggestion(cx) {
if let Some((copilot, completion)) =
Copilot::global(cx).zip(self.copilot_state.active_completion())
{
@ -3334,7 +3364,7 @@ impl Editor {
}
fn discard_copilot_suggestion(&mut self, cx: &mut ViewContext<Self>) -> bool {
if self.has_active_copilot_suggestion(cx) {
if let Some(suggestion) = self.take_active_copilot_suggestion(cx) {
if let Some(copilot) = Copilot::global(cx) {
copilot
.update(cx, |copilot, cx| {
@ -3345,8 +3375,9 @@ impl Editor {
self.report_copilot_event(None, false, cx)
}
self.display_map
.update(cx, |map, cx| map.replace_suggestion::<usize>(None, cx));
self.display_map.update(cx, |map, cx| {
map.splice_inlays::<&str>(vec![suggestion.id], Vec::new(), cx)
});
cx.notify();
true
} else {
@ -3367,7 +3398,26 @@ impl Editor {
}
fn has_active_copilot_suggestion(&self, cx: &AppContext) -> bool {
self.display_map.read(cx).has_suggestion()
if let Some(suggestion) = self.copilot_state.suggestion.as_ref() {
let buffer = self.buffer.read(cx).read(cx);
suggestion.position.is_valid(&buffer)
} else {
false
}
}
fn take_active_copilot_suggestion(&mut self, cx: &mut ViewContext<Self>) -> Option<Inlay> {
let suggestion = self.copilot_state.suggestion.take()?;
self.display_map.update(cx, |map, cx| {
map.splice_inlays::<&str>(vec![suggestion.id], Default::default(), cx);
});
let buffer = self.buffer.read(cx).read(cx);
if suggestion.position.is_valid(&buffer) {
Some(suggestion)
} else {
None
}
}
fn update_visible_copilot_suggestion(&mut self, cx: &mut ViewContext<Self>) {
@ -3384,14 +3434,28 @@ impl Editor {
.copilot_state
.text_for_active_completion(cursor, &snapshot)
{
self.display_map.update(cx, move |map, cx| {
map.replace_suggestion(
Some(Suggestion {
let text = Rope::from(text);
let mut to_remove = Vec::new();
if let Some(suggestion) = self.copilot_state.suggestion.take() {
to_remove.push(suggestion.id);
}
let to_insert = vec![(
// TODO kb check how can I get the unique id for the suggestion
// Move the generation of the id inside the map
InlayId(usize::MAX),
InlayProperties {
position: cursor,
text: text.trim_end().into(),
}),
cx,
)
text: text.clone(),
},
)];
self.display_map.update(cx, move |map, cx| {
map.splice_inlays(to_remove, to_insert, cx)
});
self.copilot_state.suggestion = Some(Inlay {
id: InlayId(usize::MAX),
position: cursor,
text,
});
cx.notify();
} else {

View File

@ -14,6 +14,19 @@ use util::post_inc;
use collections::{hash_map, BTreeMap, HashMap, HashSet};
#[derive(Debug, Clone)]
pub struct Inlay {
pub id: InlayId,
pub position: Anchor,
pub text: text::Rope,
}
#[derive(Debug, Clone)]
pub struct InlayProperties<T> {
pub position: Anchor,
pub text: T,
}
#[derive(Debug, Copy, Clone)]
pub enum InlayRefreshReason {
Settings(editor_settings::InlayHints),