Overhaul inline assistant (#12846)

This pull request introduces a new diff mechanism that helps users
understand exactly which lines were changed by the LLM.

Release Notes:

- N/A
This commit is contained in:
Antonio Scandurra 2024-06-11 12:39:45 +02:00 committed by GitHub
parent 3722275cfa
commit 98659eabf1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 824 additions and 176 deletions

1
Cargo.lock generated
View File

@ -373,6 +373,7 @@ dependencies = [
"serde", "serde",
"serde_json", "serde_json",
"settings", "settings",
"similar",
"smol", "smol",
"strsim 0.11.1", "strsim 0.11.1",
"strum", "strum",

View File

@ -340,6 +340,7 @@ serde_repr = "0.1"
sha2 = "0.10" sha2 = "0.10"
shellexpand = "2.1.0" shellexpand = "2.1.0"
shlex = "1.3.0" shlex = "1.3.0"
similar = "1.3"
smallvec = { version = "1.6", features = ["union"] } smallvec = { version = "1.6", features = ["union"] }
smol = "1.2" smol = "1.2"
strum = { version = "0.25.0", features = ["derive"] } strum = { version = "0.25.0", features = ["derive"] }

6
assets/icons/context.svg Normal file
View File

@ -0,0 +1,6 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12.6667 2H3.33333C2.59695 2 2 2.59695 2 3.33333V12.6667C2 13.403 2.59695 14 3.33333 14H12.6667C13.403 14 14 13.403 14 12.6667V3.33333C14 2.59695 13.403 2 12.6667 2Z" stroke="#888888" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M9 5H5" stroke="#888888" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M10.5 8H5" stroke="#888888" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M9 10.9502H5" stroke="#888888" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 683 B

View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-rotate-cw"><path d="M21 12a9 9 0 1 1-9-9c2.52 0 4.93 1 6.74 2.74L21 8"/><path d="M21 3v5h-5"/></svg>

After

Width:  |  Height:  |  Size: 303 B

3
assets/icons/stop.svg Normal file
View File

@ -0,0 +1,3 @@
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M9.88889 1H2.11111C1.49746 1 1 1.49746 1 2.11111V9.88889C1 10.5025 1.49746 11 2.11111 11H9.88889C10.5025 11 11 10.5025 11 9.88889V2.11111C11 1.49746 10.5025 1 9.88889 1Z" stroke="#C56757" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 369 B

View File

@ -47,6 +47,7 @@ semantic_index.workspace = true
serde.workspace = true serde.workspace = true
serde_json.workspace = true serde_json.workspace = true
settings.workspace = true settings.workspace = true
similar.workspace = true
smol.workspace = true smol.workspace = true
strsim = "0.11" strsim = "0.11"
strum.workspace = true strum.workspace = true

View File

@ -1222,6 +1222,10 @@ impl Context {
} }
} }
pub(crate) fn token_count(&self) -> Option<usize> {
self.token_count
}
pub(crate) fn count_remaining_tokens(&mut self, cx: &mut ModelContext<Self>) { pub(crate) fn count_remaining_tokens(&mut self, cx: &mut ModelContext<Self>) {
let request = self.to_completion_request(cx); let request = self.to_completion_request(cx);
self.pending_token_count = cx.spawn(|this, mut cx| { self.pending_token_count = cx.spawn(|this, mut cx| {

File diff suppressed because it is too large Load Diff

View File

@ -52,8 +52,14 @@ use multi_buffer::{
ToOffset, ToPoint, ToOffset, ToPoint,
}; };
use serde::Deserialize; use serde::Deserialize;
use std::ops::Add; use std::{
use std::{any::TypeId, borrow::Cow, fmt::Debug, num::NonZeroU32, ops::Range, sync::Arc}; any::TypeId,
borrow::Cow,
fmt::Debug,
num::NonZeroU32,
ops::{Add, Range, Sub},
sync::Arc,
};
use sum_tree::{Bias, TreeMap}; use sum_tree::{Bias, TreeMap};
use tab_map::{TabMap, TabSnapshot}; use tab_map::{TabMap, TabSnapshot};
use text::LineIndent; use text::LineIndent;
@ -1027,6 +1033,14 @@ impl Add for DisplayRow {
} }
} }
impl Sub for DisplayRow {
type Output = Self;
fn sub(self, other: Self) -> Self::Output {
DisplayRow(self.0 - other.0)
}
}
impl DisplayPoint { impl DisplayPoint {
pub fn new(row: DisplayRow, column: u32) -> Self { pub fn new(row: DisplayRow, column: u32) -> Self {
Self(BlockPoint(Point::new(row.0, column))) Self(BlockPoint(Point::new(row.0, column)))

View File

@ -376,6 +376,7 @@ type CompletionId = usize;
// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>; // type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
type BackgroundHighlight = (fn(&ThemeColors) -> Hsla, Arc<[Range<Anchor>]>); type BackgroundHighlight = (fn(&ThemeColors) -> Hsla, Arc<[Range<Anchor>]>);
type GutterHighlight = (fn(&AppContext) -> Hsla, Arc<[Range<Anchor>]>);
struct ScrollbarMarkerState { struct ScrollbarMarkerState {
scrollbar_size: Size<Pixels>, scrollbar_size: Size<Pixels>,
@ -464,6 +465,7 @@ pub struct Editor {
highlight_order: usize, highlight_order: usize,
highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>, highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
background_highlights: TreeMap<TypeId, BackgroundHighlight>, background_highlights: TreeMap<TypeId, BackgroundHighlight>,
gutter_highlights: TreeMap<TypeId, GutterHighlight>,
scrollbar_marker_state: ScrollbarMarkerState, scrollbar_marker_state: ScrollbarMarkerState,
active_indent_guides_state: ActiveIndentGuidesState, active_indent_guides_state: ActiveIndentGuidesState,
nav_history: Option<ItemNavHistory>, nav_history: Option<ItemNavHistory>,
@ -1752,6 +1754,7 @@ impl Editor {
highlight_order: 0, highlight_order: 0,
highlighted_rows: HashMap::default(), highlighted_rows: HashMap::default(),
background_highlights: Default::default(), background_highlights: Default::default(),
gutter_highlights: TreeMap::default(),
scrollbar_marker_state: ScrollbarMarkerState::default(), scrollbar_marker_state: ScrollbarMarkerState::default(),
active_indent_guides_state: ActiveIndentGuidesState::default(), active_indent_guides_state: ActiveIndentGuidesState::default(),
nav_history: None, nav_history: None,
@ -10263,6 +10266,25 @@ impl Editor {
Some(text_highlights) Some(text_highlights)
} }
pub fn highlight_gutter<T: 'static>(
&mut self,
ranges: &[Range<Anchor>],
color_fetcher: fn(&AppContext) -> Hsla,
cx: &mut ViewContext<Self>,
) {
self.gutter_highlights
.insert(TypeId::of::<T>(), (color_fetcher, Arc::from(ranges)));
cx.notify();
}
pub fn clear_gutter_highlights<T: 'static>(
&mut self,
cx: &mut ViewContext<Self>,
) -> Option<GutterHighlight> {
cx.notify();
self.gutter_highlights.remove(&TypeId::of::<T>())
}
#[cfg(feature = "test-support")] #[cfg(feature = "test-support")]
pub fn all_text_background_highlights( pub fn all_text_background_highlights(
&mut self, &mut self,
@ -10452,6 +10474,44 @@ impl Editor {
results results
} }
pub fn gutter_highlights_in_range(
&self,
search_range: Range<Anchor>,
display_snapshot: &DisplaySnapshot,
cx: &AppContext,
) -> Vec<(Range<DisplayPoint>, Hsla)> {
let mut results = Vec::new();
for (color_fetcher, ranges) in self.gutter_highlights.values() {
let color = color_fetcher(cx);
let start_ix = match ranges.binary_search_by(|probe| {
let cmp = probe
.end
.cmp(&search_range.start, &display_snapshot.buffer_snapshot);
if cmp.is_gt() {
Ordering::Greater
} else {
Ordering::Less
}
}) {
Ok(i) | Err(i) => i,
};
for range in &ranges[start_ix..] {
if range
.start
.cmp(&search_range.end, &display_snapshot.buffer_snapshot)
.is_ge()
{
break;
}
let start = range.start.to_display_point(&display_snapshot);
let end = range.end.to_display_point(&display_snapshot);
results.push((start..end, color))
}
}
results
}
/// Get the text ranges corresponding to the redaction query /// Get the text ranges corresponding to the redaction query
pub fn redacted_ranges( pub fn redacted_ranges(
&self, &self,

View File

@ -2837,6 +2837,8 @@ impl EditorElement {
Self::paint_diff_hunks(layout.gutter_hitbox.bounds, layout, cx) Self::paint_diff_hunks(layout.gutter_hitbox.bounds, layout, cx)
} }
self.paint_gutter_highlights(layout, cx);
if layout.blamed_display_rows.is_some() { if layout.blamed_display_rows.is_some() {
self.paint_blamed_display_rows(layout, cx); self.paint_blamed_display_rows(layout, cx);
} }
@ -3006,6 +3008,37 @@ impl EditorElement {
} }
} }
fn paint_gutter_highlights(&self, layout: &EditorLayout, cx: &mut WindowContext) {
let highlight_width = 0.275 * layout.position_map.line_height;
let highlight_corner_radii = Corners::all(0.05 * layout.position_map.line_height);
cx.paint_layer(layout.gutter_hitbox.bounds, |cx| {
for (range, color) in &layout.highlighted_gutter_ranges {
let start_row = if range.start.row() < layout.visible_display_row_range.start {
layout.visible_display_row_range.start - DisplayRow(1)
} else {
range.start.row()
};
let end_row = if range.end.row() > layout.visible_display_row_range.end {
layout.visible_display_row_range.end + DisplayRow(1)
} else {
range.end.row()
};
let start_y = layout.gutter_hitbox.top()
+ start_row.0 as f32 * layout.position_map.line_height
- layout.position_map.scroll_pixel_position.y;
let end_y = layout.gutter_hitbox.top()
+ (end_row.0 + 1) as f32 * layout.position_map.line_height
- layout.position_map.scroll_pixel_position.y;
let bounds = Bounds::from_corners(
point(layout.gutter_hitbox.left(), start_y),
point(layout.gutter_hitbox.left() + highlight_width, end_y),
);
cx.paint_quad(fill(bounds, *color).corner_radii(highlight_corner_radii));
}
});
}
fn paint_blamed_display_rows(&self, layout: &mut EditorLayout, cx: &mut WindowContext) { fn paint_blamed_display_rows(&self, layout: &mut EditorLayout, cx: &mut WindowContext) {
let Some(blamed_display_rows) = layout.blamed_display_rows.take() else { let Some(blamed_display_rows) = layout.blamed_display_rows.take() else {
return; return;
@ -4631,6 +4664,12 @@ impl Element for EditorElement {
&snapshot.display_snapshot, &snapshot.display_snapshot,
cx.theme().colors(), cx.theme().colors(),
); );
let highlighted_gutter_ranges =
self.editor.read(cx).gutter_highlights_in_range(
start_anchor..end_anchor,
&snapshot.display_snapshot,
cx,
);
let redacted_ranges = self.editor.read(cx).redacted_ranges( let redacted_ranges = self.editor.read(cx).redacted_ranges(
start_anchor..end_anchor, start_anchor..end_anchor,
@ -4991,6 +5030,7 @@ impl Element for EditorElement {
active_rows, active_rows,
highlighted_rows, highlighted_rows,
highlighted_ranges, highlighted_ranges,
highlighted_gutter_ranges,
redacted_ranges, redacted_ranges,
line_elements, line_elements,
line_numbers, line_numbers,
@ -5121,6 +5161,7 @@ pub struct EditorLayout {
inline_blame: Option<AnyElement>, inline_blame: Option<AnyElement>,
blocks: Vec<BlockLayout>, blocks: Vec<BlockLayout>,
highlighted_ranges: Vec<(Range<DisplayPoint>, Hsla)>, highlighted_ranges: Vec<(Range<DisplayPoint>, Hsla)>,
highlighted_gutter_ranges: Vec<(Range<DisplayPoint>, Hsla)>,
redacted_ranges: Vec<Range<DisplayPoint>>, redacted_ranges: Vec<Range<DisplayPoint>>,
cursors: Vec<(DisplayPoint, Hsla)>, cursors: Vec<(DisplayPoint, Hsla)>,
visible_cursors: Vec<CursorLayout>, visible_cursors: Vec<CursorLayout>,

View File

@ -49,7 +49,7 @@ schemars.workspace = true
serde.workspace = true serde.workspace = true
serde_json.workspace = true serde_json.workspace = true
settings.workspace = true settings.workspace = true
similar = "1.3" similar.workspace = true
smallvec.workspace = true smallvec.workspace = true
smol.workspace = true smol.workspace = true
sum_tree.workspace = true sum_tree.workspace = true

View File

@ -797,6 +797,10 @@ impl Buffer {
.set_language_registry(language_registry); .set_language_registry(language_registry);
} }
pub fn language_registry(&self) -> Option<Arc<LanguageRegistry>> {
self.syntax_map.lock().language_registry()
}
/// Assign the buffer a new [Capability]. /// Assign the buffer a new [Capability].
pub fn set_capability(&mut self, capability: Capability, cx: &mut ModelContext<Self>) { pub fn set_capability(&mut self, capability: Capability, cx: &mut ModelContext<Self>) {
self.capability = capability; self.capability = capability;

View File

@ -106,6 +106,7 @@ pub enum IconName {
Code, Code,
Collab, Collab,
Command, Command,
Context,
Control, Control,
Copilot, Copilot,
CopilotDisabled, CopilotDisabled,
@ -170,6 +171,7 @@ pub enum IconName {
Rerun, Rerun,
Return, Return,
Reveal, Reveal,
RotateCw,
Save, Save,
Screen, Screen,
SelectAll, SelectAll,
@ -186,6 +188,7 @@ pub enum IconName {
Split, Split,
Star, Star,
StarFilled, StarFilled,
Stop,
Strikethrough, Strikethrough,
Supermaven, Supermaven,
SupermavenDisabled, SupermavenDisabled,
@ -233,6 +236,7 @@ impl IconName {
IconName::Code => "icons/code.svg", IconName::Code => "icons/code.svg",
IconName::Collab => "icons/user_group_16.svg", IconName::Collab => "icons/user_group_16.svg",
IconName::Command => "icons/command.svg", IconName::Command => "icons/command.svg",
IconName::Context => "icons/context.svg",
IconName::Control => "icons/control.svg", IconName::Control => "icons/control.svg",
IconName::Copilot => "icons/copilot.svg", IconName::Copilot => "icons/copilot.svg",
IconName::CopilotDisabled => "icons/copilot_disabled.svg", IconName::CopilotDisabled => "icons/copilot_disabled.svg",
@ -297,6 +301,7 @@ impl IconName {
IconName::ReplyArrowRight => "icons/reply_arrow_right.svg", IconName::ReplyArrowRight => "icons/reply_arrow_right.svg",
IconName::Rerun => "icons/rerun.svg", IconName::Rerun => "icons/rerun.svg",
IconName::Return => "icons/return.svg", IconName::Return => "icons/return.svg",
IconName::RotateCw => "icons/rotate_cw.svg",
IconName::Save => "icons/save.svg", IconName::Save => "icons/save.svg",
IconName::Screen => "icons/desktop.svg", IconName::Screen => "icons/desktop.svg",
IconName::SelectAll => "icons/select_all.svg", IconName::SelectAll => "icons/select_all.svg",
@ -313,6 +318,7 @@ impl IconName {
IconName::Split => "icons/split.svg", IconName::Split => "icons/split.svg",
IconName::Star => "icons/star.svg", IconName::Star => "icons/star.svg",
IconName::StarFilled => "icons/star_filled.svg", IconName::StarFilled => "icons/star_filled.svg",
IconName::Stop => "icons/stop.svg",
IconName::Strikethrough => "icons/strikethrough.svg", IconName::Strikethrough => "icons/strikethrough.svg",
IconName::Supermaven => "icons/supermaven.svg", IconName::Supermaven => "icons/supermaven.svg",
IconName::SupermavenDisabled => "icons/supermaven_disabled.svg", IconName::SupermavenDisabled => "icons/supermaven_disabled.svg",