diff --git a/Cargo.lock b/Cargo.lock index 08ffcac7cf..8ab616fbd7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -310,6 +310,7 @@ dependencies = [ "language", "log", "menu", + "multi_buffer", "ordered-float 2.10.0", "parking_lot 0.11.2", "project", @@ -2410,6 +2411,7 @@ dependencies = [ "lazy_static", "log", "lsp", + "multi_buffer", "ordered-float 2.10.0", "parking_lot 0.11.2", "postage", @@ -4600,6 +4602,55 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7843ec2de400bcbc6a6328c958dc38e5359da6e93e72e37bc5246bf1ae776389" +[[package]] +name = "multi_buffer" +version = "0.1.0" +dependencies = [ + "aho-corasick", + "anyhow", + "client", + "clock", + "collections", + "context_menu", + "convert_case 0.6.0", + "copilot", + "ctor", + "env_logger 0.9.3", + "futures 0.3.28", + "git", + "gpui", + "indoc", + "itertools 0.10.5", + "language", + "lazy_static", + "log", + "lsp", + "ordered-float 2.10.0", + "parking_lot 0.11.2", + "postage", + "project", + "pulldown-cmark", + "rand 0.8.5", + "rich_text", + "schemars", + "serde", + "serde_derive", + "settings", + "smallvec", + "smol", + "snippet", + "sum_tree", + "text", + "theme", + "tree-sitter", + "tree-sitter-html", + "tree-sitter-rust", + "tree-sitter-typescript", + "unindent", + "util", + "workspace", +] + [[package]] name = "multimap" version = "0.8.3" diff --git a/Cargo.toml b/Cargo.toml index 836a0bd6b2..1d9da19605 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,6 +46,7 @@ members = [ "crates/lsp", "crates/media", "crates/menu", + "crates/multi_buffer", "crates/node_runtime", "crates/notifications", "crates/outline", diff --git a/crates/assistant/Cargo.toml b/crates/assistant/Cargo.toml index 9cfdd3301a..256f4d8416 100644 --- a/crates/assistant/Cargo.toml +++ b/crates/assistant/Cargo.toml @@ -17,6 +17,7 @@ fs = { path = "../fs" } gpui = { path = "../gpui" } language = { path = "../language" } menu = { path = "../menu" } +multi_buffer = { path = "../multi_buffer" } search = { path = "../search" } settings = { path = "../settings" } theme = { path = "../theme" } diff --git a/crates/assistant/src/assistant_panel.rs b/crates/assistant/src/assistant_panel.rs index ca8c54a285..0dee8be510 100644 --- a/crates/assistant/src/assistant_panel.rs +++ b/crates/assistant/src/assistant_panel.rs @@ -296,7 +296,7 @@ impl AssistantPanel { }; let selection = editor.read(cx).selections.newest_anchor().clone(); - if selection.start.excerpt_id() != selection.end.excerpt_id() { + if selection.start.excerpt_id != selection.end.excerpt_id { return; } let snapshot = editor.read(cx).buffer().read(cx).snapshot(cx); diff --git a/crates/assistant/src/codegen.rs b/crates/assistant/src/codegen.rs index b6ef6b5cfa..6b79daba42 100644 --- a/crates/assistant/src/codegen.rs +++ b/crates/assistant/src/codegen.rs @@ -1,10 +1,11 @@ use crate::streaming_diff::{Hunk, StreamingDiff}; use ai::completion::{CompletionProvider, OpenAIRequest}; use anyhow::Result; -use editor::{multi_buffer, Anchor, MultiBuffer, MultiBufferSnapshot, ToOffset, ToPoint}; +use editor::{Anchor, MultiBuffer, MultiBufferSnapshot, ToOffset, ToPoint}; use futures::{channel::mpsc, SinkExt, Stream, StreamExt}; use gpui::{Entity, ModelContext, ModelHandle, Task}; use language::{Rope, TransactionId}; +use multi_buffer; use std::{cmp, future, ops::Range, sync::Arc}; pub enum Event { diff --git a/crates/editor/Cargo.toml b/crates/editor/Cargo.toml index d03e1c1106..95d7820063 100644 --- a/crates/editor/Cargo.toml +++ b/crates/editor/Cargo.toml @@ -14,6 +14,7 @@ test-support = [ "text/test-support", "language/test-support", "gpui/test-support", + "multi_buffer/test-support", "project/test-support", "util/test-support", "workspace/test-support", @@ -34,6 +35,7 @@ git = { path = "../git" } gpui = { path = "../gpui" } language = { path = "../language" } lsp = { path = "../lsp" } +multi_buffer = { path = "../multi_buffer" } project = { path = "../project" } rpc = { path = "../rpc" } rich_text = { path = "../rich_text" } diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index e54ac04d89..c07625bf9c 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -993,8 +993,8 @@ mod tests { use super::*; use crate::display_map::inlay_map::InlayMap; use crate::display_map::{fold_map::FoldMap, tab_map::TabMap, wrap_map::WrapMap}; - use crate::multi_buffer::MultiBuffer; use gpui::{elements::Empty, Element}; + use multi_buffer::MultiBuffer; use rand::prelude::*; use settings::SettingsStore; use std::env; diff --git a/crates/editor/src/display_map/fold_map.rs b/crates/editor/src/display_map/fold_map.rs index 8faa0c3ec2..4636d9a17f 100644 --- a/crates/editor/src/display_map/fold_map.rs +++ b/crates/editor/src/display_map/fold_map.rs @@ -91,7 +91,7 @@ impl<'a> FoldMapWriter<'a> { // For now, ignore any ranges that span an excerpt boundary. let fold = Fold(buffer.anchor_after(range.start)..buffer.anchor_before(range.end)); - if fold.0.start.excerpt_id() != fold.0.end.excerpt_id() { + if fold.0.start.excerpt_id != fold.0.end.excerpt_id { continue; } diff --git a/crates/editor/src/display_map/inlay_map.rs b/crates/editor/src/display_map/inlay_map.rs index 124b32c234..c0c352453b 100644 --- a/crates/editor/src/display_map/inlay_map.rs +++ b/crates/editor/src/display_map/inlay_map.rs @@ -1,10 +1,8 @@ -use crate::{ - multi_buffer::{MultiBufferChunks, MultiBufferRows}, - Anchor, InlayId, MultiBufferSnapshot, ToOffset, -}; +use crate::{Anchor, InlayId, MultiBufferSnapshot, ToOffset}; use collections::{BTreeMap, BTreeSet}; use gpui::fonts::HighlightStyle; use language::{Chunk, Edit, Point, TextSummary}; +use multi_buffer::{MultiBufferChunks, MultiBufferRows}; use std::{ any::TypeId, cmp, diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 140d79fd33..bfb87afff2 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -11,7 +11,6 @@ pub mod items; mod link_go_to_definition; mod mouse_context_menu; pub mod movement; -pub mod multi_buffer; mod persistence; pub mod scroll; pub mod selections_collection; @@ -7716,8 +7715,8 @@ impl Editor { let mut buffer_highlights = this .document_highlights_for_position(selection.head(), &buffer) .filter(|highlight| { - highlight.start.excerpt_id() == selection.head().excerpt_id() - && highlight.end.excerpt_id() == selection.head().excerpt_id() + highlight.start.excerpt_id == selection.head().excerpt_id + && highlight.end.excerpt_id == selection.head().excerpt_id }); buffer_highlights .next() diff --git a/crates/editor/src/git.rs b/crates/editor/src/git.rs index 0ec7358df7..f8c6ef9a1f 100644 --- a/crates/editor/src/git.rs +++ b/crates/editor/src/git.rs @@ -87,3 +87,196 @@ pub fn diff_hunk_to_display(hunk: DiffHunk, snapshot: &DisplaySnapshot) -> } } } + +#[cfg(any(test, feature = "test_support"))] +mod tests { + use crate::editor_tests::init_test; + use crate::Point; + use gpui::TestAppContext; + use multi_buffer::{ExcerptRange, MultiBuffer}; + use project::{FakeFs, Project}; + use unindent::Unindent; + #[gpui::test] + async fn test_diff_hunks_in_range(cx: &mut TestAppContext) { + use git::diff::DiffHunkStatus; + init_test(cx, |_| {}); + + let fs = FakeFs::new(cx.background()); + let project = Project::test(fs, [], cx).await; + + // buffer has two modified hunks with two rows each + let buffer_1 = project + .update(cx, |project, cx| { + project.create_buffer( + " + 1.zero + 1.ONE + 1.TWO + 1.three + 1.FOUR + 1.FIVE + 1.six + " + .unindent() + .as_str(), + None, + cx, + ) + }) + .unwrap(); + buffer_1.update(cx, |buffer, cx| { + buffer.set_diff_base( + Some( + " + 1.zero + 1.one + 1.two + 1.three + 1.four + 1.five + 1.six + " + .unindent(), + ), + cx, + ); + }); + + // buffer has a deletion hunk and an insertion hunk + let buffer_2 = project + .update(cx, |project, cx| { + project.create_buffer( + " + 2.zero + 2.one + 2.two + 2.three + 2.four + 2.five + 2.six + " + .unindent() + .as_str(), + None, + cx, + ) + }) + .unwrap(); + buffer_2.update(cx, |buffer, cx| { + buffer.set_diff_base( + Some( + " + 2.zero + 2.one + 2.one-and-a-half + 2.two + 2.three + 2.four + 2.six + " + .unindent(), + ), + cx, + ); + }); + + cx.foreground().run_until_parked(); + + let multibuffer = cx.add_model(|cx| { + let mut multibuffer = MultiBuffer::new(0); + multibuffer.push_excerpts( + buffer_1.clone(), + [ + // excerpt ends in the middle of a modified hunk + ExcerptRange { + context: Point::new(0, 0)..Point::new(1, 5), + primary: Default::default(), + }, + // excerpt begins in the middle of a modified hunk + ExcerptRange { + context: Point::new(5, 0)..Point::new(6, 5), + primary: Default::default(), + }, + ], + cx, + ); + multibuffer.push_excerpts( + buffer_2.clone(), + [ + // excerpt ends at a deletion + ExcerptRange { + context: Point::new(0, 0)..Point::new(1, 5), + primary: Default::default(), + }, + // excerpt starts at a deletion + ExcerptRange { + context: Point::new(2, 0)..Point::new(2, 5), + primary: Default::default(), + }, + // excerpt fully contains a deletion hunk + ExcerptRange { + context: Point::new(1, 0)..Point::new(2, 5), + primary: Default::default(), + }, + // excerpt fully contains an insertion hunk + ExcerptRange { + context: Point::new(4, 0)..Point::new(6, 5), + primary: Default::default(), + }, + ], + cx, + ); + multibuffer + }); + + let snapshot = multibuffer.read_with(cx, |b, cx| b.snapshot(cx)); + + assert_eq!( + snapshot.text(), + " + 1.zero + 1.ONE + 1.FIVE + 1.six + 2.zero + 2.one + 2.two + 2.one + 2.two + 2.four + 2.five + 2.six" + .unindent() + ); + + let expected = [ + (DiffHunkStatus::Modified, 1..2), + (DiffHunkStatus::Modified, 2..3), + //TODO: Define better when and where removed hunks show up at range extremities + (DiffHunkStatus::Removed, 6..6), + (DiffHunkStatus::Removed, 8..8), + (DiffHunkStatus::Added, 10..11), + ]; + + assert_eq!( + snapshot + .git_diff_hunks_in_range(0..12) + .map(|hunk| (hunk.status(), hunk.buffer_range)) + .collect::>(), + &expected, + ); + + assert_eq!( + snapshot + .git_diff_hunks_in_range_rev(0..12) + .map(|hunk| (hunk.status(), hunk.buffer_range)) + .collect::>(), + expected + .iter() + .rev() + .cloned() + .collect::>() + .as_slice(), + ); + } +} diff --git a/crates/editor/src/test/editor_lsp_test_context.rs b/crates/editor/src/test/editor_lsp_test_context.rs index 085ce96382..3e2f38a0b6 100644 --- a/crates/editor/src/test/editor_lsp_test_context.rs +++ b/crates/editor/src/test/editor_lsp_test_context.rs @@ -6,18 +6,18 @@ use std::{ use anyhow::Result; +use crate::{Editor, ToPoint}; use collections::HashSet; use futures::Future; use gpui::{json, ViewContext, ViewHandle}; use indoc::indoc; use language::{point_to_lsp, FakeLspAdapter, Language, LanguageConfig, LanguageQueries}; use lsp::{notification, request}; +use multi_buffer::ToPointUtf16; use project::Project; use smol::stream::StreamExt; use workspace::{AppState, Workspace, WorkspaceHandle}; -use crate::{multi_buffer::ToPointUtf16, Editor, ToPoint}; - use super::editor_test_context::EditorTestContext; pub struct EditorLspTestContext<'a> { diff --git a/crates/multi_buffer/Cargo.toml b/crates/multi_buffer/Cargo.toml new file mode 100644 index 0000000000..02c71c734a --- /dev/null +++ b/crates/multi_buffer/Cargo.toml @@ -0,0 +1,80 @@ +[package] +name = "multi_buffer" +version = "0.1.0" +edition = "2021" +publish = false + +[lib] +path = "src/multi_buffer.rs" +doctest = false + +[features] +test-support = [ + "copilot/test-support", + "text/test-support", + "language/test-support", + "gpui/test-support", + "util/test-support", + "tree-sitter-rust", + "tree-sitter-typescript" +] + +[dependencies] +client = { path = "../client" } +clock = { path = "../clock" } +collections = { path = "../collections" } +context_menu = { path = "../context_menu" } +git = { path = "../git" } +gpui = { path = "../gpui" } +language = { path = "../language" } +lsp = { path = "../lsp" } +rich_text = { path = "../rich_text" } +settings = { path = "../settings" } +snippet = { path = "../snippet" } +sum_tree = { path = "../sum_tree" } +text = { path = "../text" } +theme = { path = "../theme" } +util = { path = "../util" } + +aho-corasick = "1.1" +anyhow.workspace = true +convert_case = "0.6.0" +futures.workspace = true +indoc = "1.0.4" +itertools = "0.10" +lazy_static.workspace = true +log.workspace = true +ordered-float.workspace = true +parking_lot.workspace = true +postage.workspace = true +pulldown-cmark = { version = "0.9.2", default-features = false } +rand.workspace = true +schemars.workspace = true +serde.workspace = true +serde_derive.workspace = true +smallvec.workspace = true +smol.workspace = true + +tree-sitter-rust = { workspace = true, optional = true } +tree-sitter-html = { workspace = true, optional = true } +tree-sitter-typescript = { workspace = true, optional = true } + +[dev-dependencies] +copilot = { path = "../copilot", features = ["test-support"] } +text = { path = "../text", features = ["test-support"] } +language = { path = "../language", features = ["test-support"] } +lsp = { path = "../lsp", features = ["test-support"] } +gpui = { path = "../gpui", features = ["test-support"] } +util = { path = "../util", features = ["test-support"] } +project = { path = "../project", features = ["test-support"] } +settings = { path = "../settings", features = ["test-support"] } +workspace = { path = "../workspace", features = ["test-support"] } + +ctor.workspace = true +env_logger.workspace = true +rand.workspace = true +unindent.workspace = true +tree-sitter.workspace = true +tree-sitter-rust.workspace = true +tree-sitter-html.workspace = true +tree-sitter-typescript.workspace = true diff --git a/crates/editor/src/multi_buffer/anchor.rs b/crates/multi_buffer/src/anchor.rs similarity index 95% rename from crates/editor/src/multi_buffer/anchor.rs rename to crates/multi_buffer/src/anchor.rs index 1be4dc2dfb..39a8182da1 100644 --- a/crates/editor/src/multi_buffer/anchor.rs +++ b/crates/multi_buffer/src/anchor.rs @@ -8,9 +8,9 @@ use sum_tree::Bias; #[derive(Clone, Copy, Eq, PartialEq, Debug, Hash)] pub struct Anchor { - pub(crate) buffer_id: Option, - pub(crate) excerpt_id: ExcerptId, - pub(crate) text_anchor: text::Anchor, + pub buffer_id: Option, + pub excerpt_id: ExcerptId, + pub text_anchor: text::Anchor, } impl Anchor { @@ -30,10 +30,6 @@ impl Anchor { } } - pub fn excerpt_id(&self) -> ExcerptId { - self.excerpt_id - } - pub fn cmp(&self, other: &Anchor, snapshot: &MultiBufferSnapshot) -> Ordering { let excerpt_id_cmp = self.excerpt_id.cmp(&other.excerpt_id, snapshot); if excerpt_id_cmp.is_eq() { diff --git a/crates/editor/src/multi_buffer.rs b/crates/multi_buffer/src/multi_buffer.rs similarity index 97% rename from crates/editor/src/multi_buffer.rs rename to crates/multi_buffer/src/multi_buffer.rs index 23a117405c..fc629c653f 100644 --- a/crates/editor/src/multi_buffer.rs +++ b/crates/multi_buffer/src/multi_buffer.rs @@ -303,7 +303,7 @@ impl MultiBuffer { self.snapshot.borrow().clone() } - pub(crate) fn read(&self, cx: &AppContext) -> Ref { + pub fn read(&self, cx: &AppContext) -> Ref { self.sync(cx); self.snapshot.borrow() } @@ -589,7 +589,7 @@ impl MultiBuffer { self.start_transaction_at(Instant::now(), cx) } - pub(crate) fn start_transaction_at( + pub fn start_transaction_at( &mut self, now: Instant, cx: &mut ModelContext, @@ -608,7 +608,7 @@ impl MultiBuffer { self.end_transaction_at(Instant::now(), cx) } - pub(crate) fn end_transaction_at( + pub fn end_transaction_at( &mut self, now: Instant, cx: &mut ModelContext, @@ -1508,7 +1508,7 @@ impl MultiBuffer { "untitled".into() } - #[cfg(test)] + #[cfg(any(test, feature = "test-support"))] pub fn is_parsing(&self, cx: &AppContext) -> bool { self.as_singleton().unwrap().read(cx).is_parsing() } @@ -3198,7 +3198,7 @@ impl MultiBufferSnapshot { theme: Option<&SyntaxTheme>, ) -> Option<(u64, Vec>)> { let anchor = self.anchor_before(offset); - let excerpt_id = anchor.excerpt_id(); + let excerpt_id = anchor.excerpt_id; let excerpt = self.excerpt(excerpt_id)?; Some(( excerpt.buffer_id, @@ -4129,17 +4129,13 @@ where #[cfg(test)] mod tests { - use crate::editor_tests::init_test; - use super::*; use futures::StreamExt; use gpui::{AppContext, TestAppContext}; use language::{Buffer, Rope}; - use project::{FakeFs, Project}; use rand::prelude::*; use settings::SettingsStore; use std::{env, rc::Rc}; - use unindent::Unindent; use util::test::sample_text; #[gpui::test] @@ -4838,190 +4834,6 @@ mod tests { ); } - #[gpui::test] - async fn test_diff_hunks_in_range(cx: &mut TestAppContext) { - use git::diff::DiffHunkStatus; - init_test(cx, |_| {}); - - let fs = FakeFs::new(cx.background()); - let project = Project::test(fs, [], cx).await; - - // buffer has two modified hunks with two rows each - let buffer_1 = project - .update(cx, |project, cx| { - project.create_buffer( - " - 1.zero - 1.ONE - 1.TWO - 1.three - 1.FOUR - 1.FIVE - 1.six - " - .unindent() - .as_str(), - None, - cx, - ) - }) - .unwrap(); - buffer_1.update(cx, |buffer, cx| { - buffer.set_diff_base( - Some( - " - 1.zero - 1.one - 1.two - 1.three - 1.four - 1.five - 1.six - " - .unindent(), - ), - cx, - ); - }); - - // buffer has a deletion hunk and an insertion hunk - let buffer_2 = project - .update(cx, |project, cx| { - project.create_buffer( - " - 2.zero - 2.one - 2.two - 2.three - 2.four - 2.five - 2.six - " - .unindent() - .as_str(), - None, - cx, - ) - }) - .unwrap(); - buffer_2.update(cx, |buffer, cx| { - buffer.set_diff_base( - Some( - " - 2.zero - 2.one - 2.one-and-a-half - 2.two - 2.three - 2.four - 2.six - " - .unindent(), - ), - cx, - ); - }); - - cx.foreground().run_until_parked(); - - let multibuffer = cx.add_model(|cx| { - let mut multibuffer = MultiBuffer::new(0); - multibuffer.push_excerpts( - buffer_1.clone(), - [ - // excerpt ends in the middle of a modified hunk - ExcerptRange { - context: Point::new(0, 0)..Point::new(1, 5), - primary: Default::default(), - }, - // excerpt begins in the middle of a modified hunk - ExcerptRange { - context: Point::new(5, 0)..Point::new(6, 5), - primary: Default::default(), - }, - ], - cx, - ); - multibuffer.push_excerpts( - buffer_2.clone(), - [ - // excerpt ends at a deletion - ExcerptRange { - context: Point::new(0, 0)..Point::new(1, 5), - primary: Default::default(), - }, - // excerpt starts at a deletion - ExcerptRange { - context: Point::new(2, 0)..Point::new(2, 5), - primary: Default::default(), - }, - // excerpt fully contains a deletion hunk - ExcerptRange { - context: Point::new(1, 0)..Point::new(2, 5), - primary: Default::default(), - }, - // excerpt fully contains an insertion hunk - ExcerptRange { - context: Point::new(4, 0)..Point::new(6, 5), - primary: Default::default(), - }, - ], - cx, - ); - multibuffer - }); - - let snapshot = multibuffer.read_with(cx, |b, cx| b.snapshot(cx)); - - assert_eq!( - snapshot.text(), - " - 1.zero - 1.ONE - 1.FIVE - 1.six - 2.zero - 2.one - 2.two - 2.one - 2.two - 2.four - 2.five - 2.six" - .unindent() - ); - - let expected = [ - (DiffHunkStatus::Modified, 1..2), - (DiffHunkStatus::Modified, 2..3), - //TODO: Define better when and where removed hunks show up at range extremities - (DiffHunkStatus::Removed, 6..6), - (DiffHunkStatus::Removed, 8..8), - (DiffHunkStatus::Added, 10..11), - ]; - - assert_eq!( - snapshot - .git_diff_hunks_in_range(0..12) - .map(|hunk| (hunk.status(), hunk.buffer_range)) - .collect::>(), - &expected, - ); - - assert_eq!( - snapshot - .git_diff_hunks_in_range_rev(0..12) - .map(|hunk| (hunk.status(), hunk.buffer_range)) - .collect::>(), - expected - .iter() - .rev() - .cloned() - .collect::>() - .as_slice(), - ); - } - #[gpui::test(iterations = 100)] fn test_random_multibuffer(cx: &mut AppContext, mut rng: StdRng) { let operations = env::var("OPERATIONS")