Fix infinite loop in assemble_excerpts (#44195)
Also, expand the number of identifiers fetched. Release Notes: - N/A
This commit is contained in:
@@ -480,16 +480,16 @@ impl EditPredictionStore {
|
||||
shown_predictions: Default::default(),
|
||||
};
|
||||
|
||||
this.enable_or_disable_context_retrieval(cx);
|
||||
this.configure_context_retrieval(cx);
|
||||
let weak_this = cx.weak_entity();
|
||||
cx.on_flags_ready(move |_, cx| {
|
||||
weak_this
|
||||
.update(cx, |this, cx| this.enable_or_disable_context_retrieval(cx))
|
||||
.update(cx, |this, cx| this.configure_context_retrieval(cx))
|
||||
.ok();
|
||||
})
|
||||
.detach();
|
||||
cx.observe_global::<SettingsStore>(|this, cx| {
|
||||
this.enable_or_disable_context_retrieval(cx);
|
||||
this.configure_context_retrieval(cx);
|
||||
})
|
||||
.detach();
|
||||
|
||||
@@ -1770,7 +1770,7 @@ impl EditPredictionStore {
|
||||
cx.notify();
|
||||
}
|
||||
|
||||
fn enable_or_disable_context_retrieval(&mut self, cx: &mut Context<'_, EditPredictionStore>) {
|
||||
fn configure_context_retrieval(&mut self, cx: &mut Context<'_, EditPredictionStore>) {
|
||||
self.use_context = cx.has_flag::<Zeta2FeatureFlag>()
|
||||
&& all_language_settings(None, cx).edit_predictions.use_context;
|
||||
}
|
||||
|
||||
@@ -61,8 +61,8 @@ pub fn assemble_excerpts(
|
||||
buffer,
|
||||
&mut outline_ranges,
|
||||
);
|
||||
child_outline_ix += 1;
|
||||
}
|
||||
child_outline_ix += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -159,166 +159,3 @@ pub fn merge_ranges(ranges: &mut Vec<Range<Point>>) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use gpui::{TestAppContext, prelude::*};
|
||||
use indoc::indoc;
|
||||
use language::{Buffer, Language, LanguageConfig, LanguageMatcher, OffsetRangeExt};
|
||||
use pretty_assertions::assert_eq;
|
||||
use std::{fmt::Write as _, sync::Arc};
|
||||
use util::test::marked_text_ranges;
|
||||
|
||||
#[gpui::test]
|
||||
fn test_rust(cx: &mut TestAppContext) {
|
||||
let table = [
|
||||
(
|
||||
indoc! {r#"
|
||||
struct User {
|
||||
first_name: String,
|
||||
«last_name»: String,
|
||||
age: u32,
|
||||
email: String,
|
||||
create_at: Instant,
|
||||
}
|
||||
|
||||
impl User {
|
||||
pub fn first_name(&self) -> String {
|
||||
self.first_name.clone()
|
||||
}
|
||||
|
||||
pub fn full_name(&self) -> String {
|
||||
« format!("{} {}", self.first_name, self.last_name)
|
||||
» }
|
||||
}
|
||||
"#},
|
||||
indoc! {r#"
|
||||
struct User {
|
||||
first_name: String,
|
||||
last_name: String,
|
||||
…
|
||||
}
|
||||
|
||||
impl User {
|
||||
…
|
||||
pub fn full_name(&self) -> String {
|
||||
format!("{} {}", self.first_name, self.last_name)
|
||||
}
|
||||
}
|
||||
"#},
|
||||
),
|
||||
(
|
||||
indoc! {r#"
|
||||
struct «User» {
|
||||
first_name: String,
|
||||
last_name: String,
|
||||
age: u32,
|
||||
}
|
||||
|
||||
impl User {
|
||||
// methods
|
||||
}
|
||||
"#
|
||||
},
|
||||
indoc! {r#"
|
||||
struct User {
|
||||
first_name: String,
|
||||
last_name: String,
|
||||
age: u32,
|
||||
}
|
||||
…
|
||||
"#},
|
||||
),
|
||||
(
|
||||
indoc! {r#"
|
||||
trait «FooProvider» {
|
||||
const NAME: &'static str;
|
||||
|
||||
fn provide_foo(&self, id: usize) -> Foo;
|
||||
|
||||
fn provide_foo_batched(&self, ids: &[usize]) -> Vec<Foo> {
|
||||
ids.iter()
|
||||
.map(|id| self.provide_foo(*id))
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn sync(&self);
|
||||
}
|
||||
"#
|
||||
},
|
||||
indoc! {r#"
|
||||
trait FooProvider {
|
||||
const NAME: &'static str;
|
||||
|
||||
fn provide_foo(&self, id: usize) -> Foo;
|
||||
|
||||
fn provide_foo_batched(&self, ids: &[usize]) -> Vec<Foo> {
|
||||
…
|
||||
}
|
||||
|
||||
fn sync(&self);
|
||||
}
|
||||
"#},
|
||||
),
|
||||
];
|
||||
|
||||
for (input, expected_output) in table {
|
||||
let (input, ranges) = marked_text_ranges(&input, false);
|
||||
let buffer =
|
||||
cx.new(|cx| Buffer::local(input, cx).with_language(Arc::new(rust_lang()), cx));
|
||||
buffer.read_with(cx, |buffer, _cx| {
|
||||
let ranges: Vec<Range<Point>> = ranges
|
||||
.into_iter()
|
||||
.map(|range| range.to_point(&buffer))
|
||||
.collect();
|
||||
|
||||
let excerpts = assemble_excerpts(&buffer.snapshot(), ranges);
|
||||
|
||||
let output = format_excerpts(buffer, &excerpts);
|
||||
assert_eq!(output, expected_output);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fn format_excerpts(buffer: &Buffer, excerpts: &[RelatedExcerpt]) -> String {
|
||||
let mut output = String::new();
|
||||
let file_line_count = buffer.max_point().row;
|
||||
let mut current_row = 0;
|
||||
for excerpt in excerpts {
|
||||
if excerpt.text.is_empty() {
|
||||
continue;
|
||||
}
|
||||
if current_row < excerpt.point_range.start.row {
|
||||
writeln!(&mut output, "…").unwrap();
|
||||
}
|
||||
current_row = excerpt.point_range.start.row;
|
||||
|
||||
for line in excerpt.text.to_string().lines() {
|
||||
output.push_str(line);
|
||||
output.push('\n');
|
||||
current_row += 1;
|
||||
}
|
||||
}
|
||||
if current_row < file_line_count {
|
||||
writeln!(&mut output, "…").unwrap();
|
||||
}
|
||||
output
|
||||
}
|
||||
|
||||
fn rust_lang() -> Language {
|
||||
Language::new(
|
||||
LanguageConfig {
|
||||
name: "Rust".into(),
|
||||
matcher: LanguageMatcher {
|
||||
path_suffixes: vec!["rs".to_string()],
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
},
|
||||
Some(language::tree_sitter_rust::LANGUAGE.into()),
|
||||
)
|
||||
.with_outline_query(include_str!("../../languages/src/rust/outline.scm"))
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,11 +25,14 @@ mod fake_definition_lsp;
|
||||
pub use cloud_llm_client::predict_edits_v3::Line;
|
||||
pub use excerpt::{EditPredictionExcerpt, EditPredictionExcerptOptions, EditPredictionExcerptText};
|
||||
|
||||
const IDENTIFIER_LINE_COUNT: u32 = 3;
|
||||
|
||||
pub struct RelatedExcerptStore {
|
||||
project: WeakEntity<Project>,
|
||||
related_files: Vec<RelatedFile>,
|
||||
cache: HashMap<Identifier, Arc<CacheEntry>>,
|
||||
update_tx: mpsc::UnboundedSender<(Entity<Buffer>, Anchor)>,
|
||||
identifier_line_count: u32,
|
||||
}
|
||||
|
||||
pub enum RelatedExcerptStoreEvent {
|
||||
@@ -178,9 +181,14 @@ impl RelatedExcerptStore {
|
||||
update_tx,
|
||||
related_files: Vec::new(),
|
||||
cache: Default::default(),
|
||||
identifier_line_count: IDENTIFIER_LINE_COUNT,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_identifier_line_count(&mut self, count: u32) {
|
||||
self.identifier_line_count = count;
|
||||
}
|
||||
|
||||
pub fn refresh(&mut self, buffer: Entity<Buffer>, position: Anchor, _: &mut Context<Self>) {
|
||||
self.update_tx.unbounded_send((buffer, position)).ok();
|
||||
}
|
||||
@@ -195,8 +203,12 @@ impl RelatedExcerptStore {
|
||||
position: Anchor,
|
||||
cx: &mut AsyncApp,
|
||||
) -> Result<()> {
|
||||
let (project, snapshot) = this.read_with(cx, |this, cx| {
|
||||
(this.project.upgrade(), buffer.read(cx).snapshot())
|
||||
let (project, snapshot, identifier_line_count) = this.read_with(cx, |this, cx| {
|
||||
(
|
||||
this.project.upgrade(),
|
||||
buffer.read(cx).snapshot(),
|
||||
this.identifier_line_count,
|
||||
)
|
||||
})?;
|
||||
let Some(project) = project else {
|
||||
return Ok(());
|
||||
@@ -212,7 +224,9 @@ impl RelatedExcerptStore {
|
||||
})?;
|
||||
|
||||
let identifiers = cx
|
||||
.background_spawn(async move { identifiers_for_position(&snapshot, position) })
|
||||
.background_spawn(async move {
|
||||
identifiers_for_position(&snapshot, position, identifier_line_count)
|
||||
})
|
||||
.await;
|
||||
|
||||
let async_cx = cx.clone();
|
||||
@@ -393,14 +407,21 @@ fn process_definition(
|
||||
|
||||
/// Gets all of the identifiers that are present in the given line, and its containing
|
||||
/// outline items.
|
||||
fn identifiers_for_position(buffer: &BufferSnapshot, position: Anchor) -> Vec<Identifier> {
|
||||
fn identifiers_for_position(
|
||||
buffer: &BufferSnapshot,
|
||||
position: Anchor,
|
||||
identifier_line_count: u32,
|
||||
) -> Vec<Identifier> {
|
||||
let offset = position.to_offset(buffer);
|
||||
let point = buffer.offset_to_point(offset);
|
||||
|
||||
let line_range = Point::new(point.row, 0)..Point::new(point.row + 1, 0).min(buffer.max_point());
|
||||
// Search for identifiers on lines adjacent to the cursor.
|
||||
let start = Point::new(point.row.saturating_sub(identifier_line_count), 0);
|
||||
let end = Point::new(point.row + identifier_line_count + 1, 0).min(buffer.max_point());
|
||||
let line_range = start..end;
|
||||
let mut ranges = vec![line_range.to_offset(&buffer)];
|
||||
|
||||
// Include the range of the outline item itself, but not its body.
|
||||
// Search for identifiers mentioned in headers/signatures of containing outline items.
|
||||
let outline_items = buffer.outline_items_as_offsets_containing(offset..offset, false, None);
|
||||
for item in outline_items {
|
||||
if let Some(body_range) = item.body_range(&buffer) {
|
||||
|
||||
@@ -7,8 +7,8 @@ use lsp::FakeLanguageServer;
|
||||
use project::{FakeFs, LocationLink, Project};
|
||||
use serde_json::json;
|
||||
use settings::SettingsStore;
|
||||
use std::sync::Arc;
|
||||
use util::path;
|
||||
use std::{fmt::Write as _, sync::Arc};
|
||||
use util::{path, test::marked_text_ranges};
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_edit_prediction_context(cx: &mut TestAppContext) {
|
||||
@@ -37,6 +37,7 @@ async fn test_edit_prediction_context(cx: &mut TestAppContext) {
|
||||
buffer.anchor_before(offset)
|
||||
};
|
||||
|
||||
store.set_identifier_line_count(0);
|
||||
store.refresh(buffer.clone(), position, cx);
|
||||
});
|
||||
|
||||
@@ -85,6 +86,150 @@ async fn test_edit_prediction_context(cx: &mut TestAppContext) {
|
||||
});
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
fn test_assemble_excerpts(cx: &mut TestAppContext) {
|
||||
let table = [
|
||||
(
|
||||
indoc! {r#"
|
||||
struct User {
|
||||
first_name: String,
|
||||
«last_name»: String,
|
||||
age: u32,
|
||||
email: String,
|
||||
create_at: Instant,
|
||||
}
|
||||
|
||||
impl User {
|
||||
pub fn first_name(&self) -> String {
|
||||
self.first_name.clone()
|
||||
}
|
||||
|
||||
pub fn full_name(&self) -> String {
|
||||
« format!("{} {}", self.first_name, self.last_name)
|
||||
» }
|
||||
}
|
||||
"#},
|
||||
indoc! {r#"
|
||||
struct User {
|
||||
first_name: String,
|
||||
last_name: String,
|
||||
…
|
||||
}
|
||||
|
||||
impl User {
|
||||
…
|
||||
pub fn full_name(&self) -> String {
|
||||
format!("{} {}", self.first_name, self.last_name)
|
||||
}
|
||||
}
|
||||
"#},
|
||||
),
|
||||
(
|
||||
indoc! {r#"
|
||||
struct «User» {
|
||||
first_name: String,
|
||||
last_name: String,
|
||||
age: u32,
|
||||
}
|
||||
|
||||
impl User {
|
||||
// methods
|
||||
}
|
||||
"#},
|
||||
indoc! {r#"
|
||||
struct User {
|
||||
first_name: String,
|
||||
last_name: String,
|
||||
age: u32,
|
||||
}
|
||||
…
|
||||
"#},
|
||||
),
|
||||
(
|
||||
indoc! {r#"
|
||||
trait «FooProvider» {
|
||||
const NAME: &'static str;
|
||||
|
||||
fn provide_foo(&self, id: usize) -> Foo;
|
||||
|
||||
fn provide_foo_batched(&self, ids: &[usize]) -> Vec<Foo> {
|
||||
ids.iter()
|
||||
.map(|id| self.provide_foo(*id))
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn sync(&self);
|
||||
}
|
||||
"#
|
||||
},
|
||||
indoc! {r#"
|
||||
trait FooProvider {
|
||||
const NAME: &'static str;
|
||||
|
||||
fn provide_foo(&self, id: usize) -> Foo;
|
||||
|
||||
fn provide_foo_batched(&self, ids: &[usize]) -> Vec<Foo> {
|
||||
…
|
||||
}
|
||||
|
||||
fn sync(&self);
|
||||
}
|
||||
"#},
|
||||
),
|
||||
(
|
||||
indoc! {r#"
|
||||
trait «Something» {
|
||||
fn method1(&self, id: usize) -> Foo;
|
||||
|
||||
fn method2(&self, ids: &[usize]) -> Vec<Foo> {
|
||||
struct Helper1 {
|
||||
field1: usize,
|
||||
}
|
||||
|
||||
struct Helper2 {
|
||||
field2: usize,
|
||||
}
|
||||
|
||||
struct Helper3 {
|
||||
filed2: usize,
|
||||
}
|
||||
}
|
||||
|
||||
fn sync(&self);
|
||||
}
|
||||
"#
|
||||
},
|
||||
indoc! {r#"
|
||||
trait Something {
|
||||
fn method1(&self, id: usize) -> Foo;
|
||||
|
||||
fn method2(&self, ids: &[usize]) -> Vec<Foo> {
|
||||
…
|
||||
}
|
||||
|
||||
fn sync(&self);
|
||||
}
|
||||
"#},
|
||||
),
|
||||
];
|
||||
|
||||
for (input, expected_output) in table {
|
||||
let (input, ranges) = marked_text_ranges(&input, false);
|
||||
let buffer = cx.new(|cx| Buffer::local(input, cx).with_language(rust_lang(), cx));
|
||||
buffer.read_with(cx, |buffer, _cx| {
|
||||
let ranges: Vec<Range<Point>> = ranges
|
||||
.into_iter()
|
||||
.map(|range| range.to_point(&buffer))
|
||||
.collect();
|
||||
|
||||
let excerpts = assemble_excerpts(&buffer.snapshot(), ranges);
|
||||
|
||||
let output = format_excerpts(buffer, &excerpts);
|
||||
assert_eq!(output, expected_output);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_fake_definition_lsp(cx: &mut TestAppContext) {
|
||||
init_test(cx);
|
||||
@@ -339,6 +484,31 @@ fn assert_definitions(definitions: &[LocationLink], first_lines: &[&str], cx: &m
|
||||
assert_eq!(actual_first_lines, first_lines);
|
||||
}
|
||||
|
||||
fn format_excerpts(buffer: &Buffer, excerpts: &[RelatedExcerpt]) -> String {
|
||||
let mut output = String::new();
|
||||
let file_line_count = buffer.max_point().row;
|
||||
let mut current_row = 0;
|
||||
for excerpt in excerpts {
|
||||
if excerpt.text.is_empty() {
|
||||
continue;
|
||||
}
|
||||
if current_row < excerpt.point_range.start.row {
|
||||
writeln!(&mut output, "…").unwrap();
|
||||
}
|
||||
current_row = excerpt.point_range.start.row;
|
||||
|
||||
for line in excerpt.text.to_string().lines() {
|
||||
output.push_str(line);
|
||||
output.push('\n');
|
||||
current_row += 1;
|
||||
}
|
||||
}
|
||||
if current_row < file_line_count {
|
||||
writeln!(&mut output, "…").unwrap();
|
||||
}
|
||||
output
|
||||
}
|
||||
|
||||
pub(crate) fn rust_lang() -> Arc<Language> {
|
||||
Arc::new(
|
||||
Language::new(
|
||||
|
||||
Reference in New Issue
Block a user