use super::*; use crate::assemble_excerpts::assemble_excerpt_ranges; use futures::channel::mpsc::UnboundedReceiver; use gpui::TestAppContext; use indoc::indoc; use language::{Point, ToPoint as _, rust_lang}; use lsp::FakeLanguageServer; use project::{FakeFs, LocationLink, Project, ProjectPath}; use serde_json::json; use settings::SettingsStore; use std::fmt::Write as _; use util::rel_path::rel_path; use util::{path, test::marked_text_ranges}; #[gpui::test] async fn test_edit_prediction_context(cx: &mut TestAppContext) { init_test(cx); let fs = FakeFs::new(cx.executor()); fs.insert_tree(path!("/root"), test_project_1()).await; let project = Project::test(fs.clone(), [path!("/root").as_ref()], cx).await; let mut servers = setup_fake_lsp(&project, cx); let (buffer, _handle) = project .update(cx, |project, cx| { project.open_local_buffer_with_lsp(path!("/root/src/main.rs"), cx) }) .await .unwrap(); let _server = servers.next().await.unwrap(); cx.run_until_parked(); let related_excerpt_store = cx.new(|cx| RelatedExcerptStore::new(&project, cx)); related_excerpt_store.update(cx, |store, cx| { let position = { let buffer = buffer.read(cx); let offset = buffer.text().find("todo").unwrap(); buffer.anchor_before(offset) }; store.set_identifier_line_count(0); store.refresh(buffer.clone(), position, cx); }); cx.executor().advance_clock(DEBOUNCE_DURATION); related_excerpt_store.update(cx, |store, cx| { let excerpts = store.related_files(cx); assert_related_files( &excerpts, &[ ( "root/src/person.rs", &[ indoc! {" pub struct Person { first_name: String, last_name: String, email: String, age: u32, } impl Person { pub fn get_first_name(&self) -> &str { &self.first_name }"}, "}", ], ), ( "root/src/company.rs", &[indoc! {" pub struct Company { owner: Arc, address: Address, }"}], ), ( "root/src/main.rs", &[ indoc! {" pub struct Session { company: Arc, } impl Session { pub fn set_company(&mut self, company: Arc) {"}, indoc! {" } }"}, ], ), ], ); }); let company_buffer = related_excerpt_store.update(cx, |store, cx| { store .related_files_with_buffers(cx) .find(|(file, _)| file.path.to_str() == Some("root/src/company.rs")) .map(|(_, buffer)| buffer) .expect("company.rs buffer not found") }); company_buffer.update(cx, |buffer, cx| { let text = buffer.text(); let insert_pos = text.find("address: Address,").unwrap() + "address: Address,".len(); buffer.edit([(insert_pos..insert_pos, "\n name: String,")], None, cx); }); related_excerpt_store.update(cx, |store, cx| { let excerpts = store.related_files(cx); assert_related_files( &excerpts, &[ ( "root/src/person.rs", &[ indoc! {" pub struct Person { first_name: String, last_name: String, email: String, age: u32, } impl Person { pub fn get_first_name(&self) -> &str { &self.first_name }"}, "}", ], ), ( "root/src/company.rs", &[indoc! {" pub struct Company { owner: Arc, address: Address, name: String, }"}], ), ( "root/src/main.rs", &[ indoc! {" pub struct Session { company: Arc, } impl Session { pub fn set_company(&mut self, company: Arc) {"}, indoc! {" } }"}, ], ), ], ); }); } #[gpui::test] async 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 { 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 { … } fn sync(&self); } "#}, ), ( indoc! {r#" trait «Something» { fn method1(&self, id: usize) -> Foo; fn method2(&self, ids: &[usize]) -> Vec { 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 { … } 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, _| buffer.parsing_idle()) .await; buffer.read_with(cx, |buffer, _cx| { let ranges: Vec<(Range, usize)> = ranges .into_iter() .map(|range| (range.to_point(&buffer), 0)) .collect(); let assembled = assemble_excerpt_ranges(&buffer.snapshot(), ranges); let excerpts: Vec = assembled .into_iter() .map(|(row_range, order)| { let start = Point::new(row_range.start, 0); let end = Point::new(row_range.end, buffer.line_len(row_range.end)); RelatedExcerpt { row_range, text: buffer.text_for_range(start..end).collect::().into(), order, context_source: ContextSource::Lsp, } }) .collect(); 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); let fs = FakeFs::new(cx.executor()); fs.insert_tree(path!("/root"), test_project_1()).await; let project = Project::test(fs.clone(), [path!("/root").as_ref()], cx).await; let mut servers = setup_fake_lsp(&project, cx); let (buffer, _handle) = project .update(cx, |project, cx| { project.open_local_buffer_with_lsp(path!("/root/src/main.rs"), cx) }) .await .unwrap(); let _server = servers.next().await.unwrap(); cx.run_until_parked(); let buffer_text = buffer.read_with(cx, |buffer, _| buffer.text()); let definitions = project .update(cx, |project, cx| { let offset = buffer_text.find("Address {").unwrap(); project.definitions(&buffer, offset, cx) }) .await .unwrap() .unwrap(); assert_definitions(&definitions, &["pub struct Address {"], cx); let definitions = project .update(cx, |project, cx| { let offset = buffer_text.find("State::CA").unwrap(); project.definitions(&buffer, offset, cx) }) .await .unwrap() .unwrap(); assert_definitions(&definitions, &["pub enum State {"], cx); let definitions = project .update(cx, |project, cx| { let offset = buffer_text.find("to_string()").unwrap(); project.definitions(&buffer, offset, cx) }) .await .unwrap() .unwrap(); assert_definitions(&definitions, &["pub fn to_string(&self) -> String {"], cx); } #[gpui::test] async fn test_fake_type_definition_lsp(cx: &mut TestAppContext) { init_test(cx); let fs = FakeFs::new(cx.executor()); fs.insert_tree(path!("/root"), test_project_1()).await; let project = Project::test(fs.clone(), [path!("/root").as_ref()], cx).await; let mut servers = setup_fake_lsp(&project, cx); let (buffer, _handle) = project .update(cx, |project, cx| { project.open_local_buffer_with_lsp(path!("/root/src/main.rs"), cx) }) .await .unwrap(); let _server = servers.next().await.unwrap(); cx.run_until_parked(); let buffer_text = buffer.read_with(cx, |buffer, _| buffer.text()); // Type definition on a type name returns its own definition // (same as regular definition) let type_defs = project .update(cx, |project, cx| { let offset = buffer_text.find("Address {").expect("Address { not found"); project.type_definitions(&buffer, offset, cx) }) .await .unwrap() .unwrap(); assert_definitions(&type_defs, &["pub struct Address {"], cx); // Type definition on a field resolves through the type annotation. // company.rs has `owner: Arc`, so type-def of `owner` → Person. let (company_buffer, _handle) = project .update(cx, |project, cx| { project.open_local_buffer_with_lsp(path!("/root/src/company.rs"), cx) }) .await .unwrap(); cx.run_until_parked(); let company_text = company_buffer.read_with(cx, |buffer, _| buffer.text()); let type_defs = project .update(cx, |project, cx| { let offset = company_text.find("owner").expect("owner not found"); project.type_definitions(&company_buffer, offset, cx) }) .await .unwrap() .unwrap(); assert_definitions(&type_defs, &["pub struct Person {"], cx); // Type definition on another field: `address: Address` → Address. let type_defs = project .update(cx, |project, cx| { let offset = company_text.find("address").expect("address not found"); project.type_definitions(&company_buffer, offset, cx) }) .await .unwrap() .unwrap(); assert_definitions(&type_defs, &["pub struct Address {"], cx); // Type definition on a lowercase name with no type annotation returns empty. let type_defs = project .update(cx, |project, cx| { let offset = buffer_text.find("main").expect("main not found"); project.type_definitions(&buffer, offset, cx) }) .await; let is_empty = match &type_defs { Ok(Some(defs)) => defs.is_empty(), Ok(None) => true, Err(_) => false, }; assert!(is_empty, "expected no type definitions for `main`"); } #[gpui::test] async fn test_type_definitions_in_related_files(cx: &mut TestAppContext) { init_test(cx); let fs = FakeFs::new(cx.executor()); fs.insert_tree( path!("/root"), json!({ "src": { "config.rs": indoc! {r#" pub struct Config { debug: bool, verbose: bool, } "#}, "widget.rs": indoc! {r#" use super::config::Config; pub struct Widget { config: Config, name: String, } impl Widget { pub fn render(&self) { if self.config.debug { println!("debug mode"); } } } "#}, }, }), ) .await; let project = Project::test(fs.clone(), [path!("/root").as_ref()], cx).await; let mut servers = setup_fake_lsp(&project, cx); let (buffer, _handle) = project .update(cx, |project, cx| { project.open_local_buffer_with_lsp(path!("/root/src/widget.rs"), cx) }) .await .unwrap(); let _server = servers.next().await.unwrap(); cx.run_until_parked(); let related_excerpt_store = cx.new(|cx| RelatedExcerptStore::new(&project, cx)); related_excerpt_store.update(cx, |store, cx| { let position = { let buffer = buffer.read(cx); let offset = buffer .text() .find("self.config.debug") .expect("self.config.debug not found"); buffer.anchor_before(offset) }; store.set_identifier_line_count(0); store.refresh(buffer.clone(), position, cx); }); cx.executor().advance_clock(DEBOUNCE_DURATION); // config.rs appears ONLY because the fake LSP resolves the type annotation // `config: Config` to `pub struct Config` via GotoTypeDefinition. // widget.rs appears from regular definitions of Widget / render. related_excerpt_store.update(cx, |store, cx| { let excerpts = store.related_files(cx); assert_related_files( &excerpts, &[ ( "root/src/config.rs", &[indoc! {" pub struct Config { debug: bool, verbose: bool, }"}], ), ( "root/src/widget.rs", &[ indoc! {" pub struct Widget { config: Config, name: String, } impl Widget { pub fn render(&self) {"}, indoc! {" } }"}, ], ), ], ); }); } #[gpui::test] async fn test_type_definition_deduplication(cx: &mut TestAppContext) { init_test(cx); let fs = FakeFs::new(cx.executor()); // In this project the only identifier near the cursor whose type definition // resolves is `TypeA`, and its GotoTypeDefinition returns the exact same // location as GotoDefinition. After the definitions and type definitions are // merged and deduped, the type-definition location is dropped, so it // contributes nothing extra to the related-file output (the target location // appears only once). fs.insert_tree( path!("/root"), json!({ "src": { "types.rs": indoc! {r#" pub struct TypeA { value: i32, } pub struct TypeB { label: String, } "#}, "main.rs": indoc! {r#" use super::types::TypeA; fn work() { let item: TypeA = unimplemented!(); println!("{}", item.value); } "#}, }, }), ) .await; let project = Project::test(fs.clone(), [path!("/root").as_ref()], cx).await; let mut servers = setup_fake_lsp(&project, cx); let (buffer, _handle) = project .update(cx, |project, cx| { project.open_local_buffer_with_lsp(path!("/root/src/main.rs"), cx) }) .await .unwrap(); let _server = servers.next().await.unwrap(); cx.run_until_parked(); let related_excerpt_store = cx.new(|cx| RelatedExcerptStore::new(&project, cx)); related_excerpt_store.update(cx, |store, cx| { let position = { let buffer = buffer.read(cx); let offset = buffer.text().find("let item").expect("let item not found"); buffer.anchor_before(offset) }; store.set_identifier_line_count(0); store.refresh(buffer.clone(), position, cx); }); cx.executor().advance_clock(DEBOUNCE_DURATION); // types.rs appears because `TypeA` has a regular definition there. // `item`'s type definition also resolves to TypeA in types.rs, but // deduplication removes it since it points to the same location. // TypeB should NOT appear because nothing references it. related_excerpt_store.update(cx, |store, cx| { let excerpts = store.related_files(cx); assert_related_files( &excerpts, &[ ( "root/src/types.rs", &[indoc! {" pub struct TypeA { value: i32, }"}], ), ("root/src/main.rs", &["fn work() {", "}"]), ], ); }); } #[gpui::test] async fn test_edit_prediction_filters_raw_definitions_before_opening_buffers( cx: &mut TestAppContext, ) { init_test(cx); let fs = FakeFs::new(cx.executor()); fs.insert_tree( path!("/root"), json!({ "src": { "main.rs": indoc! {" // fake-definition-lsp-extra target /root/src/large.rs 0 0 0 129 // fake-definition-lsp-extra target /root/src/valid.rs 0 3 0 9 // fake-definition-lsp-extra target /outside.rs 0 3 0 10 fn main() { target(); } "}, "valid.rs": "fn target() {}\n", "large.rs": format!("{}\n", "a".repeat(MAX_TARGET_LEN + 16)), }, }), ) .await; fs.insert_file(path!("/outside.rs"), "fn outside() {}\n".into()) .await; let project = Project::test(fs.clone(), [path!("/root").as_ref()], cx).await; let mut servers = setup_fake_lsp(&project, cx); let (buffer, _handle) = project .update(cx, |project, cx| { project.open_local_buffer_with_lsp(path!("/root/src/main.rs"), cx) }) .await .unwrap(); let _fake_language_server = servers.next().await.unwrap(); cx.run_until_parked(); let related_excerpt_store = cx.new(|cx| RelatedExcerptStore::new(&project, cx)); related_excerpt_store.update(cx, |store, cx| { let position = { let buffer = buffer.read(cx); let offset = buffer .text() .find("target();") .expect("target call not found"); buffer.anchor_before(offset) }; store.set_identifier_line_count(0); store.refresh(buffer.clone(), position, cx); }); cx.executor().advance_clock(DEBOUNCE_DURATION); related_excerpt_store.update(cx, |store, cx| { assert_related_files( &store.related_files(cx), &[ ("root/src/valid.rs", &["fn target() {}"]), ("root/src/main.rs", &["fn main() {\n target();\n}"]), ], ); }); let worktree_id = buffer.read_with(cx, |buffer, cx| { buffer.file().expect("buffer has file").worktree_id(cx) }); let valid_path = ProjectPath { worktree_id, path: rel_path("src/valid.rs").into(), }; project.read_with(cx, |project, cx| { assert!(project.get_open_buffer(&valid_path, cx).is_some()); assert_eq!(project.worktrees(cx).count(), 1); }); } #[gpui::test] async fn test_definitions_ranked_by_cursor_proximity(cx: &mut TestAppContext) { init_test(cx); let fs = FakeFs::new(cx.executor()); // helpers.rs has an impl block whose body exceeds the test // MAX_OUTLINE_ITEM_BODY_SIZE (24 bytes), so assemble_excerpt_ranges // splits it into header + individual children + closing brace. main.rs // references two of the three methods on separate lines at varying // distances from the cursor. This exercises: // 1. File ordering by closest identifier rank. // 2. Per-excerpt ordering within a file — child excerpts carry the rank // of the identifier that discovered them. // 3. Parent excerpt (impl header / closing brace) inheriting the minimum // order of its children. fs.insert_tree( path!("/root"), json!({ "src": { "helpers.rs": indoc! {r#" pub struct Helpers { value: i32, } impl Helpers { pub fn alpha(&self) -> i32 { let intermediate = self.value; intermediate + 1 } pub fn beta(&self) -> i32 { let intermediate = self.value; intermediate + 2 } pub fn gamma(&self) -> i32 { let intermediate = self.value; intermediate + 3 } } "#}, "main.rs": indoc! {r#" use super::helpers::Helpers; fn process(h: Helpers) { let a = h.alpha(); let b = h.gamma(); } "#}, }, }), ) .await; let project = Project::test(fs.clone(), [path!("/root").as_ref()], cx).await; let mut servers = setup_fake_lsp(&project, cx); let (buffer, _handle) = project .update(cx, |project, cx| { project.open_local_buffer_with_lsp(path!("/root/src/main.rs"), cx) }) .await .unwrap(); let _server = servers.next().await.unwrap(); cx.run_until_parked(); // Place cursor on "h.alpha()". `alpha` is at distance 0, `gamma` is // farther below. Both resolve to methods inside `impl Helpers` in // helpers.rs. The impl header and closing brace excerpts should inherit // the min order of their children (alpha's order). let related_excerpt_store = cx.new(|cx| RelatedExcerptStore::new(&project, cx)); related_excerpt_store.update(cx, |store, cx| { let position = { let buffer = buffer.read(cx); let offset = buffer.text().find("h.alpha()").unwrap(); buffer.anchor_before(offset) }; store.set_identifier_line_count(1); store.refresh(buffer.clone(), position, cx); }); cx.executor().advance_clock(DEBOUNCE_DURATION); related_excerpt_store.update(cx, |store, cx| { let files = store.related_files(cx); // helpers.rs has 4 excerpts: the struct+impl header merged with // the alpha method header (order 1 from alpha), alpha's closing // brace (order 1), gamma's method header (order 6), and the // gamma+impl closing brace (order 1, inherited from alpha which // is also a child of the impl). let alpha_order = 1; let gamma_order = 6; assert_related_files_with_orders( &files, &[ ( "root/src/helpers.rs", &[ ( indoc! {" pub struct Helpers { value: i32, } impl Helpers { pub fn alpha(&self) -> i32 {"}, alpha_order, ), (" }", alpha_order), (" pub fn gamma(&self) -> i32 {", gamma_order), ( indoc! {" } }"}, alpha_order, ), ], ), ( "root/src/main.rs", &[("fn process(h: Helpers) {", 8), ("}", 8)], ), ], ); }); // Now move cursor to "h.gamma()" — gamma becomes closest, reranking the // excerpts so that the gamma method excerpt has the best order and the // alpha method excerpt has a worse order. related_excerpt_store.update(cx, |store, cx| { let position = { let buffer = buffer.read(cx); let offset = buffer.text().find("h.gamma()").unwrap(); buffer.anchor_before(offset) }; store.set_identifier_line_count(1); store.refresh(buffer.clone(), position, cx); }); cx.executor().advance_clock(DEBOUNCE_DURATION); related_excerpt_store.update(cx, |store, cx| { let files = store.related_files(cx); // Now gamma is closest. The alpha method excerpts carry alpha's // rank (3), and the gamma method excerpts carry gamma's rank (1). // The impl closing brace merges with gamma's closing brace and // inherits gamma's order (the best child). let alpha_order = 3; let gamma_order = 1; assert_related_files_with_orders( &files, &[ ( "root/src/helpers.rs", &[ ( indoc! {" pub struct Helpers { value: i32, } impl Helpers { pub fn alpha(&self) -> i32 {"}, alpha_order, ), (" }", alpha_order), (" pub fn gamma(&self) -> i32 {", gamma_order), ( indoc! {" } }"}, gamma_order, ), ], ), ( "root/src/main.rs", &[("fn process(h: Helpers) {", 8), ("}", 8)], ), ], ); }); } fn init_test(cx: &mut TestAppContext) { let settings_store = cx.update(|cx| SettingsStore::test(cx)); cx.set_global(settings_store); env_logger::try_init().ok(); } fn setup_fake_lsp( project: &Entity, cx: &mut TestAppContext, ) -> UnboundedReceiver { let (language_registry, fs) = project.read_with(cx, |project, _| { (project.languages().clone(), project.fs().clone()) }); let language = rust_lang(); language_registry.add(language.clone()); fake_definition_lsp::register_fake_definition_server(&language_registry, language, fs) } fn test_project_1() -> serde_json::Value { let person_rs = indoc! {r#" pub struct Person { first_name: String, last_name: String, email: String, age: u32, } impl Person { pub fn get_first_name(&self) -> &str { &self.first_name } pub fn get_last_name(&self) -> &str { &self.last_name } pub fn get_email(&self) -> &str { &self.email } pub fn get_age(&self) -> u32 { self.age } } "#}; let address_rs = indoc! {r#" pub struct Address { street: String, city: String, state: State, zip: u32, } pub enum State { CA, OR, WA, TX, // ... } impl Address { pub fn get_street(&self) -> &str { &self.street } pub fn get_city(&self) -> &str { &self.city } pub fn get_state(&self) -> State { self.state } pub fn get_zip(&self) -> u32 { self.zip } } "#}; let company_rs = indoc! {r#" use super::person::Person; use super::address::Address; pub struct Company { owner: Arc, address: Address, } impl Company { pub fn get_owner(&self) -> &Person { &self.owner } pub fn get_address(&self) -> &Address { &self.address } pub fn to_string(&self) -> String { format!("{} ({})", self.owner.first_name, self.address.city) } } "#}; let main_rs = indoc! {r#" use std::sync::Arc; use super::person::Person; use super::address::Address; use super::company::Company; pub struct Session { company: Arc, } impl Session { pub fn set_company(&mut self, company: Arc) { self.company = company; if company.owner != self.company.owner { log("new owner", company.owner.get_first_name()); todo(); } } } fn main() { let company = Company { owner: Arc::new(Person { first_name: "John".to_string(), last_name: "Doe".to_string(), email: "john@example.com".to_string(), age: 30, }), address: Address { street: "123 Main St".to_string(), city: "Anytown".to_string(), state: State::CA, zip: 12345, }, }; println!("Company: {}", company.to_string()); } "#}; json!({ "src": { "person.rs": person_rs, "address.rs": address_rs, "company.rs": company_rs, "main.rs": main_rs, }, }) } fn assert_related_files(actual_files: &[RelatedFile], expected_files: &[(&str, &[&str])]) { let expected_with_orders: Vec<(&str, Vec<(&str, usize)>)> = expected_files .iter() .map(|(path, texts)| (*path, texts.iter().map(|text| (*text, 0)).collect())) .collect(); let expected_refs: Vec<(&str, &[(&str, usize)])> = expected_with_orders .iter() .map(|(path, excerpts)| (*path, excerpts.as_slice())) .collect(); assert_related_files_impl(actual_files, &expected_refs, false) } fn assert_related_files_with_orders( actual_files: &[RelatedFile], expected_files: &[(&str, &[(&str, usize)])], ) { assert_related_files_impl(actual_files, expected_files, true) } fn assert_related_files_impl( actual_files: &[RelatedFile], expected_files: &[(&str, &[(&str, usize)])], check_orders: bool, ) { let actual: Vec<(&str, Vec<(String, usize)>)> = actual_files .iter() .map(|file| { let excerpts = file .excerpts .iter() .map(|excerpt| { let order = if check_orders { excerpt.order } else { 0 }; (excerpt.text.to_string(), order) }) .collect(); (file.path.to_str().unwrap(), excerpts) }) .collect(); let expected: Vec<(&str, Vec<(String, usize)>)> = expected_files .iter() .map(|(path, excerpts)| { ( *path, excerpts .iter() .map(|(text, order)| (text.to_string(), *order)) .collect(), ) }) .collect(); pretty_assertions::assert_eq!(actual, expected) } #[track_caller] fn assert_definitions(definitions: &[LocationLink], first_lines: &[&str], cx: &mut TestAppContext) { let actual_first_lines = definitions .iter() .map(|definition| { definition.target.buffer.read_with(cx, |buffer, _| { let mut start = definition.target.range.start.to_point(&buffer); start.column = 0; let end = Point::new(start.row, buffer.line_len(start.row)); buffer .text_for_range(start..end) .collect::() .trim() .to_string() }) }) .collect::>(); 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.row_range.start { writeln!(&mut output, "…").unwrap(); } current_row = excerpt.row_range.start; 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 }