From 2d6285a6e1b675210ebb231811432bf77f850adb Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 3 Nov 2021 18:47:18 -0600 Subject: [PATCH 001/109] Start on a test for grouped diagnostics --- crates/language/src/tests.rs | 110 +++++++++++++++++++++++++++++++++++ 1 file changed, 110 insertions(+) diff --git a/crates/language/src/tests.rs b/crates/language/src/tests.rs index b83fb4c671ac6ddc3cb87bd89fc29c8215e7353f..4e718220465b3dd66e597a94ab23e730d9c898a3 100644 --- a/crates/language/src/tests.rs +++ b/crates/language/src/tests.rs @@ -694,6 +694,116 @@ async fn test_empty_diagnostic_ranges(mut cx: gpui::TestAppContext) { }); } +#[gpui::test] +async fn test_grouped_diagnostics(mut cx: gpui::TestAppContext) { + cx.add_model(|cx| { + let text = " + fn foo(mut v: Vec) { + for x in &v { + v.push(1); + } + } + " + .unindent(); + + let mut buffer = Buffer::new(0, text, cx); + buffer.set_language(Some(Arc::new(rust_lang())), None, cx); + let diagnostics = vec![ + lsp::Diagnostic { + range: lsp::Range::new(lsp::Position::new(1, 8), lsp::Position::new(1, 9)), + severity: Some(DiagnosticSeverity::WARNING), + message: "unused variable: `x`\n`#[warn(unused_variables)]` on by default" + .to_string(), + related_information: Some(vec![lsp::DiagnosticRelatedInformation { + location: lsp::Location { + uri: lsp::Url::from_file_path("/example.rs").unwrap(), + range: lsp::Range::new(lsp::Position::new(1, 8), lsp::Position::new(1, 9)), + }, + message: "if this is intentional, prefix it with an underscore: `_x`" + .to_string(), + }]), + ..Default::default() + }, + lsp::Diagnostic { + range: lsp::Range::new(lsp::Position::new(1, 8), lsp::Position::new(1, 9)), + severity: Some(DiagnosticSeverity::HINT), + message: "if this is intentional, prefix it with an underscore: `_x`".to_string(), + related_information: Some(vec![lsp::DiagnosticRelatedInformation { + location: lsp::Location { + uri: lsp::Url::from_file_path("/example.rs").unwrap(), + range: lsp::Range::new(lsp::Position::new(1, 8), lsp::Position::new(1, 9)), + }, + message: "original diagnostic".to_string(), + }]), + ..Default::default() + }, + lsp::Diagnostic { + range: lsp::Range::new( lsp::Position::new(2, 8), lsp::Position::new(2, 17)), + severity: Some(DiagnosticSeverity::ERROR), + message: "cannot borrow `v` as mutable because it is also borrowed as immutable\nmutable borrow occurs here".to_string(), + related_information: Some( + vec![ + lsp::DiagnosticRelatedInformation { + location: lsp::Location { + uri: lsp::Url::from_file_path("/example.rs").unwrap(), + range: lsp::Range::new(lsp::Position::new( 1, 13, ), lsp::Position::new(1, 15)), + }, + message: "immutable borrow occurs here".to_string(), + }, + lsp::DiagnosticRelatedInformation { + location: lsp::Location { + uri: lsp::Url::from_file_path("/example.rs").unwrap(), + range: lsp::Range::new(lsp::Position::new( 1, 13, ), lsp::Position::new(1, 15)), + }, + message: "immutable borrow later used here".to_string(), + }, + ], + ), + ..Default::default() + }, + lsp::Diagnostic { + range: lsp::Range::new( lsp::Position::new(1, 13), lsp::Position::new(1, 15)), + severity: Some( DiagnosticSeverity::HINT), + message: "immutable borrow occurs here".to_string(), + related_information: Some( + vec![ + lsp::DiagnosticRelatedInformation { + location: lsp::Location { + uri: lsp::Url::from_file_path("/example.rs").unwrap(), + range: lsp::Range::new(lsp::Position::new( 2, 8, ), lsp::Position::new(2, 17)), + }, + message: "original diagnostic".to_string(), + }, + ], + ), + ..Default::default() + }, + lsp::Diagnostic { + range: lsp::Range::new( lsp::Position::new(1, 13), lsp::Position::new(1, 15)), + severity: Some(DiagnosticSeverity::HINT), + message: "immutable borrow later used here".to_string(), + related_information: Some( + vec![ + lsp::DiagnosticRelatedInformation { + location: lsp::Location { + uri: lsp::Url::from_file_path("/example.rs").unwrap(), + range: lsp::Range::new(lsp::Position::new( 2, 8, ), lsp::Position::new(2, 17)), + }, + message: "original diagnostic".to_string(), + }, + ], + ), + ..Default::default() + }, + ]; + buffer.update_diagnostics(None, diagnostics, cx).unwrap(); + + // TODO: Group these diagnostics somehow. + + buffer + }); +} + fn chunks_with_diagnostics( buffer: &Buffer, range: Range, From 78bbb83448f73ba2a28e5505f1eaa75d07dbee2c Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 4 Nov 2021 14:52:34 +0100 Subject: [PATCH 002/109] Assign diagnostics a `group_id` based on their `related_information` Co-Authored-By: Nathan Sobo --- crates/language/src/lib.rs | 68 +++++++-- crates/language/src/proto.rs | 2 + crates/language/src/tests.rs | 245 +++++++++++++++++++++++++-------- crates/project/src/worktree.rs | 3 +- crates/rpc/proto/zed.proto | 1 + 5 files changed, 249 insertions(+), 70 deletions(-) diff --git a/crates/language/src/lib.rs b/crates/language/src/lib.rs index 58f0ecad6455edbffb54760f3d5fec84cb7e1f24..bae0b0ee5e258991ea94871853fb50026b973337 100644 --- a/crates/language/src/lib.rs +++ b/crates/language/src/lib.rs @@ -85,6 +85,7 @@ pub struct Snapshot { pub struct Diagnostic { pub severity: DiagnosticSeverity, pub message: String, + pub group_id: usize, } struct LanguageServerState { @@ -699,6 +700,7 @@ impl Buffer { } else { self.content() }; + let abs_path = self.file.as_ref().and_then(|f| f.abs_path()); let empty_set = HashSet::new(); let disk_based_sources = self @@ -714,17 +716,28 @@ impl Buffer { .peekable(); let mut last_edit_old_end = PointUtf16::zero(); let mut last_edit_new_end = PointUtf16::zero(); + let mut groups = HashMap::new(); + let mut next_group_id = 0; content.anchor_range_multimap( Bias::Left, Bias::Right, - diagnostics.into_iter().filter_map(|diagnostic| { - let mut start = PointUtf16::new( - diagnostic.range.start.line, - diagnostic.range.start.character, - ); - let mut end = - PointUtf16::new(diagnostic.range.end.line, diagnostic.range.end.character); + diagnostics.iter().filter_map(|diagnostic| { + let mut start = diagnostic.range.start.to_point_utf16(); + let mut end = diagnostic.range.end.to_point_utf16(); + let source = diagnostic.source.as_ref(); + let code = diagnostic.code.as_ref(); + let group_id = diagnostic_ranges(&diagnostic, abs_path.as_deref()) + .find_map(|range| groups.get(&(source, code, range))) + .copied() + .unwrap_or_else(|| { + let group_id = post_inc(&mut next_group_id); + for range in diagnostic_ranges(&diagnostic, abs_path.as_deref()) { + groups.insert((source, code, range), group_id); + } + group_id + }); + if diagnostic .source .as_ref() @@ -760,7 +773,8 @@ impl Buffer { range, Diagnostic { severity: diagnostic.severity.unwrap_or(DiagnosticSeverity::ERROR), - message: diagnostic.message, + message: diagnostic.message.clone(), + group_id, }, )) }), @@ -1888,6 +1902,44 @@ impl ToTreeSitterPoint for Point { } } +trait ToPointUtf16 { + fn to_point_utf16(self) -> PointUtf16; +} + +impl ToPointUtf16 for lsp::Position { + fn to_point_utf16(self) -> PointUtf16 { + PointUtf16::new(self.line, self.character) + } +} + +fn diagnostic_ranges<'a>( + diagnostic: &'a lsp::Diagnostic, + abs_path: Option<&'a Path>, +) -> impl 'a + Iterator> { + diagnostic + .related_information + .iter() + .flatten() + .filter_map(move |info| { + if info.location.uri.to_file_path().ok()? == abs_path? { + let info_start = PointUtf16::new( + info.location.range.start.line, + info.location.range.start.character, + ); + let info_end = PointUtf16::new( + info.location.range.end.line, + info.location.range.end.character, + ); + Some(info_start..info_end) + } else { + None + } + }) + .chain(Some( + diagnostic.range.start.to_point_utf16()..diagnostic.range.end.to_point_utf16(), + )) +} + fn contiguous_ranges( values: impl IntoIterator, max_len: usize, diff --git a/crates/language/src/proto.rs b/crates/language/src/proto.rs index 4e6a8316c2391034cdd7e66b1594de0d0c1c2417..bccb965a55ac537536ede541caf19a9ab69e550c 100644 --- a/crates/language/src/proto.rs +++ b/crates/language/src/proto.rs @@ -141,6 +141,7 @@ pub fn serialize_diagnostics(map: &AnchorRangeMultimap) -> proto::Di DiagnosticSeverity::HINT => proto::diagnostic::Severity::Hint, _ => proto::diagnostic::Severity::None, } as i32, + group_id: diagnostic.group_id as u64, }) .collect(), } @@ -308,6 +309,7 @@ pub fn deserialize_diagnostics(message: proto::DiagnosticSet) -> AnchorRangeMult proto::diagnostic::Severity::None => return None, }, message: diagnostic.message, + group_id: diagnostic.group_id as usize, }, )) }), diff --git a/crates/language/src/tests.rs b/crates/language/src/tests.rs index 4e718220465b3dd66e597a94ab23e730d9c898a3..776d4943d965dd3d1ac8fb5c107a5babccb37518 100644 --- a/crates/language/src/tests.rs +++ b/crates/language/src/tests.rs @@ -482,14 +482,16 @@ async fn test_diagnostics(mut cx: gpui::TestAppContext) { Point::new(3, 9)..Point::new(3, 11), &Diagnostic { severity: DiagnosticSeverity::ERROR, - message: "undefined variable 'BB'".to_string() + message: "undefined variable 'BB'".to_string(), + group_id: 0, }, ), ( Point::new(4, 9)..Point::new(4, 12), &Diagnostic { severity: DiagnosticSeverity::ERROR, - message: "undefined variable 'CCC'".to_string() + message: "undefined variable 'CCC'".to_string(), + group_id: 0, } ) ] @@ -545,14 +547,16 @@ async fn test_diagnostics(mut cx: gpui::TestAppContext) { Point::new(2, 9)..Point::new(2, 12), &Diagnostic { severity: DiagnosticSeverity::WARNING, - message: "unreachable statement".to_string() + message: "unreachable statement".to_string(), + group_id: 0, } ), ( Point::new(2, 9)..Point::new(2, 10), &Diagnostic { severity: DiagnosticSeverity::ERROR, - message: "undefined variable 'A'".to_string() + message: "undefined variable 'A'".to_string(), + group_id: 0, }, ) ] @@ -620,14 +624,16 @@ async fn test_diagnostics(mut cx: gpui::TestAppContext) { Point::new(2, 21)..Point::new(2, 22), &Diagnostic { severity: DiagnosticSeverity::ERROR, - message: "undefined variable 'A'".to_string() + message: "undefined variable 'A'".to_string(), + group_id: 0, } ), ( Point::new(3, 9)..Point::new(3, 11), &Diagnostic { severity: DiagnosticSeverity::ERROR, - message: "undefined variable 'BB'".to_string() + message: "undefined variable 'BB'".to_string(), + group_id: 0, }, ) ] @@ -706,31 +712,30 @@ async fn test_grouped_diagnostics(mut cx: gpui::TestAppContext) { " .unindent(); - let mut buffer = Buffer::new(0, text, cx); + let file = FakeFile::new("/example.rs"); + let mut buffer = Buffer::from_file(0, text, Box::new(file.clone()), cx); buffer.set_language(Some(Arc::new(rust_lang())), None, cx); let diagnostics = vec![ lsp::Diagnostic { range: lsp::Range::new(lsp::Position::new(1, 8), lsp::Position::new(1, 9)), severity: Some(DiagnosticSeverity::WARNING), - message: "unused variable: `x`\n`#[warn(unused_variables)]` on by default" - .to_string(), + message: "error 1".to_string(), related_information: Some(vec![lsp::DiagnosticRelatedInformation { location: lsp::Location { - uri: lsp::Url::from_file_path("/example.rs").unwrap(), + uri: lsp::Url::from_file_path(&file.abs_path).unwrap(), range: lsp::Range::new(lsp::Position::new(1, 8), lsp::Position::new(1, 9)), }, - message: "if this is intentional, prefix it with an underscore: `_x`" - .to_string(), + message: "error 1 hint 1".to_string(), }]), ..Default::default() }, lsp::Diagnostic { range: lsp::Range::new(lsp::Position::new(1, 8), lsp::Position::new(1, 9)), severity: Some(DiagnosticSeverity::HINT), - message: "if this is intentional, prefix it with an underscore: `_x`".to_string(), + message: "error 1 hint 1".to_string(), related_information: Some(vec![lsp::DiagnosticRelatedInformation { location: lsp::Location { - uri: lsp::Url::from_file_path("/example.rs").unwrap(), + uri: lsp::Url::from_file_path(&file.abs_path).unwrap(), range: lsp::Range::new(lsp::Position::new(1, 8), lsp::Position::new(1, 9)), }, message: "original diagnostic".to_string(), @@ -738,67 +743,108 @@ async fn test_grouped_diagnostics(mut cx: gpui::TestAppContext) { ..Default::default() }, lsp::Diagnostic { - range: lsp::Range::new( lsp::Position::new(2, 8), lsp::Position::new(2, 17)), + range: lsp::Range::new(lsp::Position::new(2, 8), lsp::Position::new(2, 17)), severity: Some(DiagnosticSeverity::ERROR), - message: "cannot borrow `v` as mutable because it is also borrowed as immutable\nmutable borrow occurs here".to_string(), - related_information: Some( - vec![ - lsp::DiagnosticRelatedInformation { - location: lsp::Location { - uri: lsp::Url::from_file_path("/example.rs").unwrap(), - range: lsp::Range::new(lsp::Position::new( 1, 13, ), lsp::Position::new(1, 15)), - }, - message: "immutable borrow occurs here".to_string(), + message: "error 2".to_string(), + related_information: Some(vec![ + lsp::DiagnosticRelatedInformation { + location: lsp::Location { + uri: lsp::Url::from_file_path(&file.abs_path).unwrap(), + range: lsp::Range::new( + lsp::Position::new(1, 13), + lsp::Position::new(1, 15), + ), }, - lsp::DiagnosticRelatedInformation { - location: lsp::Location { - uri: lsp::Url::from_file_path("/example.rs").unwrap(), - range: lsp::Range::new(lsp::Position::new( 1, 13, ), lsp::Position::new(1, 15)), - }, - message: "immutable borrow later used here".to_string(), + message: "error 2 hint 1".to_string(), + }, + lsp::DiagnosticRelatedInformation { + location: lsp::Location { + uri: lsp::Url::from_file_path(&file.abs_path).unwrap(), + range: lsp::Range::new( + lsp::Position::new(1, 13), + lsp::Position::new(1, 15), + ), }, - ], - ), + message: "error 2 hint 2".to_string(), + }, + ]), ..Default::default() }, lsp::Diagnostic { - range: lsp::Range::new( lsp::Position::new(1, 13), lsp::Position::new(1, 15)), - severity: Some( DiagnosticSeverity::HINT), - message: "immutable borrow occurs here".to_string(), - related_information: Some( - vec![ - lsp::DiagnosticRelatedInformation { - location: lsp::Location { - uri: lsp::Url::from_file_path("/example.rs").unwrap(), - range: lsp::Range::new(lsp::Position::new( 2, 8, ), lsp::Position::new(2, 17)), - }, - message: "original diagnostic".to_string(), - }, - ], - ), + range: lsp::Range::new(lsp::Position::new(1, 13), lsp::Position::new(1, 15)), + severity: Some(DiagnosticSeverity::HINT), + message: "error 2 hint 1".to_string(), + related_information: Some(vec![lsp::DiagnosticRelatedInformation { + location: lsp::Location { + uri: lsp::Url::from_file_path(&file.abs_path).unwrap(), + range: lsp::Range::new(lsp::Position::new(2, 8), lsp::Position::new(2, 17)), + }, + message: "original diagnostic".to_string(), + }]), ..Default::default() }, lsp::Diagnostic { - range: lsp::Range::new( lsp::Position::new(1, 13), lsp::Position::new(1, 15)), + range: lsp::Range::new(lsp::Position::new(1, 13), lsp::Position::new(1, 15)), severity: Some(DiagnosticSeverity::HINT), - message: "immutable borrow later used here".to_string(), - related_information: Some( - vec![ - lsp::DiagnosticRelatedInformation { - location: lsp::Location { - uri: lsp::Url::from_file_path("/example.rs").unwrap(), - range: lsp::Range::new(lsp::Position::new( 2, 8, ), lsp::Position::new(2, 17)), - }, - message: "original diagnostic".to_string(), - }, - ], - ), + message: "error 2 hint 2".to_string(), + related_information: Some(vec![lsp::DiagnosticRelatedInformation { + location: lsp::Location { + uri: lsp::Url::from_file_path(&file.abs_path).unwrap(), + range: lsp::Range::new(lsp::Position::new(2, 8), lsp::Position::new(2, 17)), + }, + message: "original diagnostic".to_string(), + }]), ..Default::default() }, ]; buffer.update_diagnostics(None, diagnostics, cx).unwrap(); - - // TODO: Group these diagnostics somehow. + assert_eq!( + buffer + .diagnostics_in_range::<_, Point>(0..buffer.len()) + .collect::>(), + &[ + ( + Point::new(1, 8)..Point::new(1, 9), + &Diagnostic { + severity: DiagnosticSeverity::WARNING, + message: "error 1".to_string(), + group_id: 0 + } + ), + ( + Point::new(1, 8)..Point::new(1, 9), + &Diagnostic { + severity: DiagnosticSeverity::HINT, + message: "error 1 hint 1".to_string(), + group_id: 0 + } + ), + ( + Point::new(1, 13)..Point::new(1, 15), + &Diagnostic { + severity: DiagnosticSeverity::HINT, + message: "error 2 hint 1".to_string(), + group_id: 1 + } + ), + ( + Point::new(1, 13)..Point::new(1, 15), + &Diagnostic { + severity: DiagnosticSeverity::HINT, + message: "error 2 hint 2".to_string(), + group_id: 1 + } + ), + ( + Point::new(2, 8)..Point::new(2, 17), + &Diagnostic { + severity: DiagnosticSeverity::ERROR, + message: "error 2".to_string(), + group_id: 1 + } + ) + ] + ); buffer }); @@ -875,3 +921,80 @@ fn rust_lang() -> Language { fn empty(point: Point) -> Range { point..point } + +#[derive(Clone)] +struct FakeFile { + abs_path: PathBuf, +} + +impl FakeFile { + fn new(abs_path: impl Into) -> Self { + Self { + abs_path: abs_path.into(), + } + } +} + +impl File for FakeFile { + fn worktree_id(&self) -> usize { + todo!() + } + + fn entry_id(&self) -> Option { + todo!() + } + + fn mtime(&self) -> SystemTime { + SystemTime::now() + } + + fn path(&self) -> &Arc { + todo!() + } + + fn abs_path(&self) -> Option { + Some(self.abs_path.clone()) + } + + fn full_path(&self) -> PathBuf { + todo!() + } + + fn file_name(&self) -> Option { + todo!() + } + + fn is_deleted(&self) -> bool { + todo!() + } + + fn save( + &self, + _: u64, + _: Rope, + _: clock::Global, + _: &mut MutableAppContext, + ) -> Task> { + todo!() + } + + fn load_local(&self, _: &AppContext) -> Option>> { + todo!() + } + + fn buffer_updated(&self, _: u64, _: super::Operation, _: &mut MutableAppContext) { + todo!() + } + + fn buffer_removed(&self, _: u64, _: &mut MutableAppContext) { + todo!() + } + + fn boxed_clone(&self) -> Box { + todo!() + } + + fn as_any(&self) -> &dyn Any { + todo!() + } +} diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 10025b305431e042f251f5dd61bbfeb30091083d..5a9b7d205ed5acf12ba15c6c024ae33dc4917df3 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -3633,7 +3633,8 @@ mod tests { Point::new(0, 9)..Point::new(0, 10), &Diagnostic { severity: lsp::DiagnosticSeverity::ERROR, - message: "undefined variable 'A'".to_string() + message: "undefined variable 'A'".to_string(), + group_id: 0, } )] ) diff --git a/crates/rpc/proto/zed.proto b/crates/rpc/proto/zed.proto index 8753f27dbac619f372257da92c1d1aab7eabc5da..f826c4b03f55b4b46927496b23e0e4f535fbabc8 100644 --- a/crates/rpc/proto/zed.proto +++ b/crates/rpc/proto/zed.proto @@ -256,6 +256,7 @@ message Diagnostic { uint64 end = 2; Severity severity = 3; string message = 4; + uint64 group_id = 5; enum Severity { None = 0; Error = 1; From 2f4d8932dc0dc8aa13f18c75116a8b35cea168bc Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 4 Nov 2021 15:16:59 +0100 Subject: [PATCH 003/109] Allow querying a diagnostic group by its id Co-Authored-By: Nathan Sobo --- crates/buffer/src/anchor.rs | 32 ++++++++++++++++++++++ crates/language/src/lib.rs | 13 +++++++++ crates/language/src/tests.rs | 51 ++++++++++++++++++++++++++++++++++++ 3 files changed, 96 insertions(+) diff --git a/crates/buffer/src/anchor.rs b/crates/buffer/src/anchor.rs index bb0e7b386a92fc53de013fa2187e406e1ea01438..ceee746c6a9ce9d00c54a6965fe81d010ed68042 100644 --- a/crates/buffer/src/anchor.rs +++ b/crates/buffer/src/anchor.rs @@ -354,6 +354,38 @@ impl AnchorRangeMultimap { .cursor::<()>() .map(|entry| (entry.range.start..entry.range.end, &entry.value)) } + + pub fn filter<'a, O, F>( + &'a self, + content: Content<'a>, + mut f: F, + ) -> impl 'a + Iterator, &T)> + where + O: FromAnchor, + F: 'a + FnMut(&'a T) -> bool, + { + let mut endpoint = Anchor { + full_offset: FullOffset(0), + bias: Bias::Left, + version: self.version.clone(), + }; + self.entries + .cursor::<()>() + .enumerate() + .filter_map(move |(ix, entry)| { + if f(&entry.value) { + endpoint.full_offset = entry.range.start; + endpoint.bias = self.start_bias; + let start = O::from_anchor(&endpoint, &content); + endpoint.full_offset = entry.range.end; + endpoint.bias = self.end_bias; + let end = O::from_anchor(&endpoint, &content); + Some((ix, start..end, &entry.value)) + } else { + None + } + }) + } } impl sum_tree::Item for AnchorRangeMultimapEntry { diff --git a/crates/language/src/lib.rs b/crates/language/src/lib.rs index bae0b0ee5e258991ea94871853fb50026b973337..f7e5016fc9b23bcc1cb97e057349887097d4bd05 100644 --- a/crates/language/src/lib.rs +++ b/crates/language/src/lib.rs @@ -812,6 +812,19 @@ impl Buffer { .map(move |(_, range, diagnostic)| (range, diagnostic)) } + pub fn diagnostic_group<'a, O>( + &'a self, + group_id: usize, + ) -> impl Iterator, &Diagnostic)> + 'a + where + O: 'a + FromAnchor, + { + let content = self.content(); + self.diagnostics + .filter(content, move |diagnostic| diagnostic.group_id == group_id) + .map(move |(_, range, diagnostic)| (range, diagnostic)) + } + pub fn diagnostics_update_count(&self) -> usize { self.diagnostics_update_count } diff --git a/crates/language/src/tests.rs b/crates/language/src/tests.rs index 776d4943d965dd3d1ac8fb5c107a5babccb37518..4044c81d730239194e2b9bfcae3d0833b7b3ecce 100644 --- a/crates/language/src/tests.rs +++ b/crates/language/src/tests.rs @@ -846,6 +846,57 @@ async fn test_grouped_diagnostics(mut cx: gpui::TestAppContext) { ] ); + assert_eq!( + buffer.diagnostic_group(0).collect::>(), + &[ + ( + Point::new(1, 8)..Point::new(1, 9), + &Diagnostic { + severity: DiagnosticSeverity::WARNING, + message: "error 1".to_string(), + group_id: 0 + } + ), + ( + Point::new(1, 8)..Point::new(1, 9), + &Diagnostic { + severity: DiagnosticSeverity::HINT, + message: "error 1 hint 1".to_string(), + group_id: 0 + } + ), + ] + ); + assert_eq!( + buffer.diagnostic_group(1).collect::>(), + &[ + ( + Point::new(1, 13)..Point::new(1, 15), + &Diagnostic { + severity: DiagnosticSeverity::HINT, + message: "error 2 hint 1".to_string(), + group_id: 1 + } + ), + ( + Point::new(1, 13)..Point::new(1, 15), + &Diagnostic { + severity: DiagnosticSeverity::HINT, + message: "error 2 hint 2".to_string(), + group_id: 1 + } + ), + ( + Point::new(2, 8)..Point::new(2, 17), + &Diagnostic { + severity: DiagnosticSeverity::ERROR, + message: "error 2".to_string(), + group_id: 1 + } + ) + ] + ); + buffer }); } From 8b9488bacbc11854d41bf33a497f4b25e34702f0 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 4 Nov 2021 17:34:33 -0700 Subject: [PATCH 004/109] Add missing group_id fields in rpc test --- crates/server/src/rpc.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/crates/server/src/rpc.rs b/crates/server/src/rpc.rs index aebebc589177910c7dd31a7992f3d7c084b39bfb..d9d45207d054c63f52fe722e4f0e65957a49cf48 100644 --- a/crates/server/src/rpc.rs +++ b/crates/server/src/rpc.rs @@ -1713,6 +1713,7 @@ mod tests { ( Point::new(0, 4)..Point::new(0, 7), &Diagnostic { + group_id: 0, message: "message 1".to_string(), severity: lsp::DiagnosticSeverity::ERROR, } @@ -1720,6 +1721,7 @@ mod tests { ( Point::new(0, 10)..Point::new(0, 13), &Diagnostic { + group_id: 1, severity: lsp::DiagnosticSeverity::WARNING, message: "message 2".to_string() } From 62ec105bffa9dc243d122cabb4f61728e37a3b8e Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 10 Nov 2021 14:00:40 -0700 Subject: [PATCH 005/109] WIP --- crates/editor/src/display_map.rs | 1 + .../editor/src/display_map/injection_map.rs | 252 ++++++++++++++++++ 2 files changed, 253 insertions(+) create mode 100644 crates/editor/src/display_map/injection_map.rs diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 596dc9507f66eb4130247ea0992b15267d55d984..537c4f8ee8973820971f359810d323c9c4f79215 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -1,4 +1,5 @@ mod fold_map; +mod injection_map; mod tab_map; mod wrap_map; diff --git a/crates/editor/src/display_map/injection_map.rs b/crates/editor/src/display_map/injection_map.rs new file mode 100644 index 0000000000000000000000000000000000000000..3bd142fe78a17e18902a71342afb8bdedcb8e2c6 --- /dev/null +++ b/crates/editor/src/display_map/injection_map.rs @@ -0,0 +1,252 @@ +use std::{ + cmp, mem, + sync::atomic::{AtomicUsize, Ordering::SeqCst}, +}; + +use buffer::{Anchor, Bias, Edit, Point, Rope, TextSummary}; +use gpui::{fonts::HighlightStyle, AppContext, ModelHandle}; +use language::Buffer; +use parking_lot::Mutex; +use sum_tree::SumTree; + +#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Ord, PartialOrd)] +pub struct InjectionId(usize); + +pub struct InjectionMap { + buffer: ModelHandle, + transforms: Mutex>, + injections: SumTree, + injection_contents: SumTree, + version: AtomicUsize, + last_sync: Mutex, +} + +pub struct InjectionSnapshot { + transforms: SumTree, + injection_contents: SumTree, + buffer_snapshot: language::Snapshot, + pub version: usize, +} + +pub struct InjectionMapWriter<'a>(&'a mut InjectionMap); + +#[derive(Clone)] +struct SyncState { + version: clock::Global, + parse_count: usize, + diagnostics_update_count: usize, +} + +#[derive(Clone, Debug)] +struct Injection { + id: InjectionId, + position: Anchor, + is_block: bool, +} + +#[derive(Clone, Debug)] +struct InjectionSummary { + min_id: InjectionId, + max_id: InjectionId, + min_position: Anchor, + max_position: Anchor, +} + +#[derive(Clone, Debug)] +struct InjectionContent { + injection_id: InjectionId, + runs: Vec<(usize, HighlightStyle)>, + text: Rope, +} + +#[derive(Clone, Debug, Default, PartialEq)] +struct Transform { + summary: TransformSummary, + injection_id: Option, +} + +#[derive(Clone, Debug, Default, Eq, PartialEq)] +struct TransformSummary { + output: TextSummary, + input: TextSummary, +} + +#[derive(Copy, Clone)] +struct InjectionOffset(usize); + +impl sum_tree::Summary for InjectionId { + type Context = (); + + fn add_summary(&mut self, summary: &Self, cx: &Self::Context) { + *self = *summary + } +} + +impl InjectionMap { + pub fn read(&self, cx: &AppContext) -> (InjectionSnapshot, Vec>) { + let edits = self.sync(cx); + // self.check_invariants(cx); + let snapshot = InjectionSnapshot { + transforms: self.transforms.lock().clone(), + injection_contents: self.injection_contents.clone(), + buffer_snapshot: self.buffer.read(cx).snapshot(), + version: self.version.load(SeqCst), + }; + (snapshot, edits) + } + + pub fn write( + &mut self, + cx: &AppContext, + ) -> ( + InjectionMapWriter, + InjectionSnapshot, + Vec>, + ) { + let (snapshot, edits) = self.read(cx); + (InjectionMapWriter(self), snapshot, edits) + } + + fn sync(&self, cx: &AppContext) -> Vec> { + let buffer = self.buffer.read(cx); + let last_sync = mem::replace( + &mut *self.last_sync.lock(), + SyncState { + version: buffer.version(), + parse_count: buffer.parse_count(), + diagnostics_update_count: buffer.diagnostics_update_count(), + }, + ); + let edits = buffer + .edits_since(&last_sync.version) + .map(Into::into) + .collect::>(); + if edits.is_empty() { + if last_sync.parse_count != buffer.parse_count() + || last_sync.diagnostics_update_count != buffer.diagnostics_update_count() + { + self.version.fetch_add(1, SeqCst); + } + Vec::new() + } else { + self.apply_edits(edits, cx) + } + } + + fn apply_edits( + &self, + buffer_edits: Vec>, + cx: &AppContext, + ) -> Vec> { + let buffer = self.buffer.read(cx).snapshot(); + let mut buffer_edits_iter = buffer_edits.iter().cloned().peekable(); + + let mut new_transforms = SumTree::::new(); + let mut transforms = self.transforms.lock(); + let mut cursor = transforms.cursor::(); + + while let Some(mut edit) = buffer_edits_iter.next() { + new_transforms.push_tree(cursor.slice(&edit.old.start, Bias::Left, &()), &()); + edit.new.start -= edit.old.start - cursor.start(); + edit.old.start = *cursor.start(); + + cursor.seek(&edit.old.end, Bias::Right, &()); + cursor.next(&()); + + let mut delta = edit.new.len() as isize - edit.old.len() as isize; + loop { + edit.old.end = *cursor.start(); + + if let Some(next_edit) = buffer_edits_iter.peek() { + if next_edit.old.start > edit.old.end { + break; + } + + let next_edit = buffer_edits_iter.next().unwrap(); + delta += next_edit.new.len() as isize - next_edit.old.len() as isize; + + if next_edit.old.end >= edit.old.end { + edit.old.end = next_edit.old.end; + cursor.seek(&edit.old.end, Bias::Right, &()); + cursor.next(&()); + } + } else { + break; + } + } + + edit.new.end = ((edit.new.start + edit.old.len()) as isize + delta) as usize; + + let anchor = buffer.anchor_before(edit.new.start); + let mut injections_cursor = self.injections.cursor::(); + // folds_cursor.seek(&Fold(anchor..Anchor::max()), Bias::Left, &buffer); + } + + todo!() + } +} + +impl sum_tree::Item for Injection { + type Summary = InjectionSummary; + + fn summary(&self) -> Self::Summary { + InjectionSummary { + min_id: self.id, + max_id: self.id, + min_position: self.position.clone(), + max_position: self.position.clone(), + } + } +} + +impl sum_tree::Summary for InjectionSummary { + type Context = buffer::Snapshot; + + fn add_summary(&mut self, summary: &Self, _: &buffer::Snapshot) { + self.max_position = summary.max_position.clone(); + self.min_id = cmp::min(self.min_id, summary.min_id); + self.max_id = cmp::max(self.max_id, summary.max_id); + } +} + +impl Default for InjectionSummary { + fn default() -> Self { + Self { + min_id: InjectionId(0), + max_id: InjectionId(usize::MAX), + min_position: Anchor::max(), + max_position: Anchor::min(), + } + } +} + +impl sum_tree::Item for InjectionContent { + type Summary = InjectionId; + + fn summary(&self) -> Self::Summary { + self.injection_id + } +} + +impl sum_tree::Item for Transform { + type Summary = TransformSummary; + + fn summary(&self) -> Self::Summary { + self.summary.clone() + } +} + +impl sum_tree::Summary for TransformSummary { + type Context = (); + + fn add_summary(&mut self, other: &Self, _: &()) { + self.input += &other.input; + self.output += &other.output; + } +} + +impl<'a> sum_tree::Dimension<'a, TransformSummary> for usize { + fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) { + *self += summary.input.bytes + } +} From 0fff7d9166af911d498a283ffa687cf2befdadd0 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 10 Nov 2021 17:44:56 -0700 Subject: [PATCH 006/109] WIP: Probably the wrong direction --- crates/buffer/src/rope.rs | 23 +- .../editor/src/display_map/injection_map.rs | 219 +++++++++++++----- crates/sum_tree/src/lib.rs | 6 + 3 files changed, 183 insertions(+), 65 deletions(-) diff --git a/crates/buffer/src/rope.rs b/crates/buffer/src/rope.rs index 3cf43bd16025f408ad16dfc79181ad64dbc49a89..5498c403bf5986117ef89cdc0621a4d6c6809f2f 100644 --- a/crates/buffer/src/rope.rs +++ b/crates/buffer/src/rope.rs @@ -303,7 +303,7 @@ impl<'a> Cursor<'a> { if let Some(start_chunk) = self.chunks.item() { let start_ix = self.offset - self.chunks.start(); let end_ix = cmp::min(end_offset, self.chunks.end(&())) - self.chunks.start(); - summary.add_assign(&D::from_summary(&TextSummary::from( + summary.add_assign(&D::from_text_summary(&TextSummary::from( &start_chunk.0[start_ix..end_ix], ))); } @@ -313,7 +313,9 @@ impl<'a> Cursor<'a> { summary.add_assign(&self.chunks.summary(&end_offset, Bias::Right, &())); if let Some(end_chunk) = self.chunks.item() { let end_ix = end_offset - self.chunks.start(); - summary.add_assign(&D::from_summary(&TextSummary::from(&end_chunk.0[..end_ix]))); + summary.add_assign(&D::from_text_summary(&TextSummary::from( + &end_chunk.0[..end_ix], + ))); } } @@ -634,13 +636,16 @@ impl std::ops::AddAssign for TextSummary { } pub trait TextDimension<'a>: Dimension<'a, TextSummary> { - fn from_summary(summary: &TextSummary) -> Self; + fn from_text_summary(summary: &TextSummary) -> Self; fn add_assign(&mut self, other: &Self); } impl<'a, D1: TextDimension<'a>, D2: TextDimension<'a>> TextDimension<'a> for (D1, D2) { - fn from_summary(summary: &TextSummary) -> Self { - (D1::from_summary(summary), D2::from_summary(summary)) + fn from_text_summary(summary: &TextSummary) -> Self { + ( + D1::from_text_summary(summary), + D2::from_text_summary(summary), + ) } fn add_assign(&mut self, other: &Self) { @@ -650,7 +655,7 @@ impl<'a, D1: TextDimension<'a>, D2: TextDimension<'a>> TextDimension<'a> for (D1 } impl<'a> TextDimension<'a> for TextSummary { - fn from_summary(summary: &TextSummary) -> Self { + fn from_text_summary(summary: &TextSummary) -> Self { summary.clone() } @@ -666,7 +671,7 @@ impl<'a> sum_tree::Dimension<'a, TextSummary> for usize { } impl<'a> TextDimension<'a> for usize { - fn from_summary(summary: &TextSummary) -> Self { + fn from_text_summary(summary: &TextSummary) -> Self { summary.bytes } @@ -682,7 +687,7 @@ impl<'a> sum_tree::Dimension<'a, TextSummary> for Point { } impl<'a> TextDimension<'a> for Point { - fn from_summary(summary: &TextSummary) -> Self { + fn from_text_summary(summary: &TextSummary) -> Self { summary.lines } @@ -698,7 +703,7 @@ impl<'a> sum_tree::Dimension<'a, TextSummary> for PointUtf16 { } impl<'a> TextDimension<'a> for PointUtf16 { - fn from_summary(summary: &TextSummary) -> Self { + fn from_text_summary(summary: &TextSummary) -> Self { summary.lines_utf16 } diff --git a/crates/editor/src/display_map/injection_map.rs b/crates/editor/src/display_map/injection_map.rs index 3bd142fe78a17e18902a71342afb8bdedcb8e2c6..06a147c33d7f1972dbf124818d56d7414cdce85b 100644 --- a/crates/editor/src/display_map/injection_map.rs +++ b/crates/editor/src/display_map/injection_map.rs @@ -1,13 +1,15 @@ use std::{ - cmp, mem, + cmp::Ordering, + mem, sync::atomic::{AtomicUsize, Ordering::SeqCst}, }; -use buffer::{Anchor, Bias, Edit, Point, Rope, TextSummary}; +use buffer::{rope::TextDimension, Anchor, Bias, Edit, Rope, TextSummary, ToOffset}; use gpui::{fonts::HighlightStyle, AppContext, ModelHandle}; use language::Buffer; use parking_lot::Mutex; -use sum_tree::SumTree; +use sum_tree::{SeekTarget, SumTree}; +use util::post_inc; #[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Ord, PartialOrd)] pub struct InjectionId(usize); @@ -16,14 +18,14 @@ pub struct InjectionMap { buffer: ModelHandle, transforms: Mutex>, injections: SumTree, - injection_contents: SumTree, version: AtomicUsize, last_sync: Mutex, + next_injection_id: usize, } pub struct InjectionSnapshot { transforms: SumTree, - injection_contents: SumTree, + injections: SumTree, buffer_snapshot: language::Snapshot, pub version: usize, } @@ -37,13 +39,6 @@ struct SyncState { diagnostics_update_count: usize, } -#[derive(Clone, Debug)] -struct Injection { - id: InjectionId, - position: Anchor, - is_block: bool, -} - #[derive(Clone, Debug)] struct InjectionSummary { min_id: InjectionId, @@ -53,25 +48,28 @@ struct InjectionSummary { } #[derive(Clone, Debug)] -struct InjectionContent { - injection_id: InjectionId, - runs: Vec<(usize, HighlightStyle)>, +struct Injection { + id: InjectionId, text: Rope, + runs: Vec<(usize, HighlightStyle)>, } #[derive(Clone, Debug, Default, PartialEq)] struct Transform { - summary: TransformSummary, + input: TextSummary, + output: TextSummary, injection_id: Option, } #[derive(Clone, Debug, Default, Eq, PartialEq)] struct TransformSummary { - output: TextSummary, input: TextSummary, + output: TextSummary, + min_injection_id: InjectionId, + max_injection_id: InjectionId, } -#[derive(Copy, Clone)] +#[derive(Copy, Clone, Debug, Default)] struct InjectionOffset(usize); impl sum_tree::Summary for InjectionId { @@ -88,7 +86,7 @@ impl InjectionMap { // self.check_invariants(cx); let snapshot = InjectionSnapshot { transforms: self.transforms.lock().clone(), - injection_contents: self.injection_contents.clone(), + injections: self.injections.clone(), buffer_snapshot: self.buffer.read(cx).snapshot(), version: self.version.load(SeqCst), }; @@ -146,11 +144,11 @@ impl InjectionMap { let mut cursor = transforms.cursor::(); while let Some(mut edit) = buffer_edits_iter.next() { - new_transforms.push_tree(cursor.slice(&edit.old.start, Bias::Left, &()), &()); + new_transforms.push_tree(cursor.slice(&edit.old.start, Bias::Right, &()), &()); edit.new.start -= edit.old.start - cursor.start(); edit.old.start = *cursor.start(); - cursor.seek(&edit.old.end, Bias::Right, &()); + cursor.seek(&edit.old.end, Bias::Left, &()); cursor.next(&()); let mut delta = edit.new.len() as isize - edit.old.len() as isize; @@ -158,7 +156,7 @@ impl InjectionMap { edit.old.end = *cursor.start(); if let Some(next_edit) = buffer_edits_iter.peek() { - if next_edit.old.start > edit.old.end { + if next_edit.old.start >= edit.old.end { break; } @@ -167,7 +165,7 @@ impl InjectionMap { if next_edit.old.end >= edit.old.end { edit.old.end = next_edit.old.end; - cursor.seek(&edit.old.end, Bias::Right, &()); + cursor.seek(&edit.old.end, Bias::Left, &()); cursor.next(&()); } } else { @@ -177,62 +175,165 @@ impl InjectionMap { edit.new.end = ((edit.new.start + edit.old.len()) as isize + delta) as usize; - let anchor = buffer.anchor_before(edit.new.start); - let mut injections_cursor = self.injections.cursor::(); - // folds_cursor.seek(&Fold(anchor..Anchor::max()), Bias::Left, &buffer); + if !edit.new.is_empty() { + let text_summary = buffer.text_summary_for_range(edit.new.start..edit.new.end); + new_transforms.push( + Transform { + input: text_summary.clone(), + output: text_summary, + injection_id: None, + }, + &(), + ); + } } + new_transforms.push_tree(cursor.suffix(&()), &()); + drop(cursor); + + let injection_edits = { + let mut old_transforms = transforms.cursor::<(usize, InjectionOffset)>(); + let mut new_transforms = new_transforms.cursor::<(usize, InjectionOffset)>(); + + buffer_edits + .into_iter() + .map(|edit| { + old_transforms.seek(&edit.old.start, Bias::Right, &()); + let old_start = + old_transforms.start().1 .0 + (edit.old.start - old_transforms.start().0); + + old_transforms.seek_forward(&edit.old.end, Bias::Left, &()); + let old_end = + old_transforms.start().1 .0 + (edit.old.end - old_transforms.start().0); + + new_transforms.seek(&edit.new.start, Bias::Right, &()); + let new_start = + new_transforms.start().1 .0 + (edit.new.start - new_transforms.start().0); + + new_transforms.seek_forward(&edit.new.end, Bias::Left, &()); + let new_end = + new_transforms.start().1 .0 + (edit.new.end - new_transforms.start().0); + + Edit { + old: InjectionOffset(old_start)..InjectionOffset(old_end), + new: InjectionOffset(new_start)..InjectionOffset(new_end), + } + }) + .collect() + }; - todo!() + *transforms = new_transforms; + injection_edits } } -impl sum_tree::Item for Injection { - type Summary = InjectionSummary; +impl<'a> InjectionMapWriter<'a> { + pub fn insert<'b, T, U>( + &mut self, + injections: T, + cx: &AppContext, + ) -> ( + Vec, + InjectionSnapshot, + Vec>, + ) + where + T: IntoIterator)>, + U: ToOffset, + { + let buffer = self.0.buffer.read(cx); + let mut injections = injections + .into_iter() + .map(|(position, text, runs)| (position.to_offset(buffer), text, runs)) + .peekable(); + let mut edits = Vec::new(); + let mut injection_ids = Vec::new(); + let mut new_transforms = SumTree::new(); + let mut transforms = self.0.transforms.lock(); + let mut cursor = transforms.cursor::(); - fn summary(&self) -> Self::Summary { - InjectionSummary { - min_id: self.id, - max_id: self.id, - min_position: self.position.clone(), - max_position: self.position.clone(), + while let Some((injection_offset, text, runs)) = injections.next() { + new_transforms.push_tree(cursor.slice(&injection_offset, Bias::Right, &()), &()); + let new_transforms_end = new_transforms.summary().input.bytes; + if injection_offset > new_transforms_end { + new_transforms.push( + Transform::isomorphic( + buffer.text_summary_for_range(new_transforms_end..injection_offset), + ), + &(), + ); + } + + let injection = Injection { + id: InjectionId(post_inc(&mut self.0.next_injection_id)), + runs, + text: text.into(), + }; + new_transforms.push( + Transform { + input: Default::default(), + output: injection.text.summary(), + injection_id: Some(injection.id), + }, + &(), + ); + self.0.injections.push(injection, &()); + + if let Some((next_injection_offset, _, _)) = injections.peek() { + let old_transform_end = cursor.end(&()); + if *next_injection_offset > old_transform_end { + new_transforms.push( + Transform::isomorphic( + buffer.text_summary_for_range(new_transforms_end..old_transform_end), + ), + &(), + ); + cursor.next(&()); + } + } } + + (injection_ids, todo!(), edits) } } -impl sum_tree::Summary for InjectionSummary { - type Context = buffer::Snapshot; +impl sum_tree::Item for Injection { + type Summary = InjectionId; - fn add_summary(&mut self, summary: &Self, _: &buffer::Snapshot) { - self.max_position = summary.max_position.clone(); - self.min_id = cmp::min(self.min_id, summary.min_id); - self.max_id = cmp::max(self.max_id, summary.max_id); + fn summary(&self) -> Self::Summary { + self.id } } -impl Default for InjectionSummary { - fn default() -> Self { +impl Transform { + fn isomorphic(text_summary: TextSummary) -> Self { Self { - min_id: InjectionId(0), - max_id: InjectionId(usize::MAX), - min_position: Anchor::max(), - max_position: Anchor::min(), + input: text_summary.clone(), + output: text_summary, + injection_id: None, } } } -impl sum_tree::Item for InjectionContent { - type Summary = InjectionId; - - fn summary(&self) -> Self::Summary { - self.injection_id - } -} - impl sum_tree::Item for Transform { type Summary = TransformSummary; fn summary(&self) -> Self::Summary { - self.summary.clone() + let min_injection_id; + let max_injection_id; + if let Some(id) = self.injection_id { + min_injection_id = id; + max_injection_id = id; + } else { + min_injection_id = InjectionId(usize::MAX); + max_injection_id = InjectionId(0); + } + + TransformSummary { + input: self.input.clone(), + output: self.output.clone(), + min_injection_id, + max_injection_id, + } } } @@ -250,3 +351,9 @@ impl<'a> sum_tree::Dimension<'a, TransformSummary> for usize { *self += summary.input.bytes } } + +impl<'a> sum_tree::Dimension<'a, TransformSummary> for InjectionOffset { + fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) { + self.0 += summary.output.bytes + } +} diff --git a/crates/sum_tree/src/lib.rs b/crates/sum_tree/src/lib.rs index eeef9563249b0af2dbb67c6da699927004d577bc..8b4a45519f007fb70154f1576d34c1774bbe1af0 100644 --- a/crates/sum_tree/src/lib.rs +++ b/crates/sum_tree/src/lib.rs @@ -31,6 +31,12 @@ pub trait Summary: Default + Clone + fmt::Debug { pub trait Dimension<'a, S: Summary>: Clone + fmt::Debug + Default { fn add_summary(&mut self, _summary: &'a S, _: &S::Context); + + fn from_summary(summary: &'a S, cx: &S::Context) -> Self { + let mut dimension = Self::default(); + dimension.add_summary(summary, cx); + dimension + } } impl<'a, T: Summary> Dimension<'a, T> for T { From 118f137f183049ccf2df6eac27572d265bf8e351 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 10 Nov 2021 20:49:06 -0700 Subject: [PATCH 007/109] WIP: Rework injection map to be focused solely on block injections --- .../editor/src/display_map/injection_map.rs | 357 ++++++++++++------ 1 file changed, 239 insertions(+), 118 deletions(-) diff --git a/crates/editor/src/display_map/injection_map.rs b/crates/editor/src/display_map/injection_map.rs index 06a147c33d7f1972dbf124818d56d7414cdce85b..ea3d1b9da2ad6b6482680f746bf37dede4c1a117 100644 --- a/crates/editor/src/display_map/injection_map.rs +++ b/crates/editor/src/display_map/injection_map.rs @@ -1,14 +1,15 @@ use std::{ - cmp::Ordering, + cmp::{self, Ordering}, + collections::BTreeMap, mem, sync::atomic::{AtomicUsize, Ordering::SeqCst}, }; -use buffer::{rope::TextDimension, Anchor, Bias, Edit, Rope, TextSummary, ToOffset}; +use buffer::{Anchor, Bias, Edit, Point, Rope, TextSummary, ToOffset, ToPoint}; use gpui::{fonts::HighlightStyle, AppContext, ModelHandle}; use language::Buffer; use parking_lot::Mutex; -use sum_tree::{SeekTarget, SumTree}; +use sum_tree::SumTree; use util::post_inc; #[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Ord, PartialOrd)] @@ -18,6 +19,7 @@ pub struct InjectionMap { buffer: ModelHandle, transforms: Mutex>, injections: SumTree, + injection_sites: SumTree, version: AtomicUsize, last_sync: Mutex, next_injection_id: usize, @@ -54,6 +56,37 @@ struct Injection { runs: Vec<(usize, HighlightStyle)>, } +#[derive(Clone, Debug)] +pub struct InjectionProps { + text: Rope, + runs: Vec<(usize, HighlightStyle)>, + disposition: Disposition, +} + +#[derive(Clone, Debug)] +pub enum Disposition { + BeforeLine, + AfterLine, +} + +#[derive(Clone, Debug)] +struct InjectionSite { + injection_id: InjectionId, + position: Anchor, + disposition: Disposition, +} + +#[derive(Clone, Debug)] +struct InjectionSitePosition(Anchor); + +#[derive(Clone, Debug, Eq, PartialEq)] +struct InjectionSiteSummary { + min_injection_id: InjectionId, + max_injection_id: InjectionId, + min_position: Anchor, + max_position: Anchor, +} + #[derive(Clone, Debug, Default, PartialEq)] struct Transform { input: TextSummary, @@ -133,96 +166,111 @@ impl InjectionMap { fn apply_edits( &self, - buffer_edits: Vec>, + buffer_edits: Vec>, cx: &AppContext, ) -> Vec> { - let buffer = self.buffer.read(cx).snapshot(); + let buffer = self.buffer.read(cx); let mut buffer_edits_iter = buffer_edits.iter().cloned().peekable(); let mut new_transforms = SumTree::::new(); let mut transforms = self.transforms.lock(); - let mut cursor = transforms.cursor::(); + let mut cursor = transforms.cursor::(); + let mut injection_sites = self.injection_sites.cursor::(); + let mut pending_after_injections: Vec = Vec::new(); while let Some(mut edit) = buffer_edits_iter.next() { - new_transforms.push_tree(cursor.slice(&edit.old.start, Bias::Right, &()), &()); - edit.new.start -= edit.old.start - cursor.start(); - edit.old.start = *cursor.start(); + // Expand this edit to line boundaries + edit.old.start.column = 0; + edit.old.end += Point::new(1, 0); + edit.new.start.column = 0; + edit.new.end += Point::new(1, 0); + + // Merge with subsequent edits that intersect the same lines + while let Some(next_edit) = buffer_edits_iter.peek() { + if next_edit.old.start.row > edit.old.end.row { + break; + } + + let next_edit = buffer_edits_iter.next().unwrap(); + edit.old.end.row = next_edit.old.end.row + 1; + let row_delta = next_edit.new.end.row as i32 - next_edit.old.end.row as i32; + edit.new.end.row = (edit.new.end.row as i32 + row_delta) as u32; + } - cursor.seek(&edit.old.end, Bias::Left, &()); - cursor.next(&()); + // Push any transforms preceding the edit + new_transforms.push_tree(cursor.slice(&edit.old.start, Bias::Left, &()), &()); - let mut delta = edit.new.len() as isize - edit.old.len() as isize; - loop { - edit.old.end = *cursor.start(); + // Find and insert all injections on the lines spanned by the edit, interleaved with isomorphic regions + injection_sites.seek( + &InjectionSitePosition(buffer.anchor_before(edit.new.start)), + Bias::Right, + buffer, + ); + let mut last_injection_row: Option = None; + while let Some(site) = injection_sites.item() { + let injection_row = site.position.to_point(buffer).row; - if let Some(next_edit) = buffer_edits_iter.peek() { - if next_edit.old.start >= edit.old.end { - break; - } + if injection_row > edit.new.end.row { + break; + } - let next_edit = buffer_edits_iter.next().unwrap(); - delta += next_edit.new.len() as isize - next_edit.old.len() as isize; + // If we've moved on to a new injection row, ensure that any pending injections with an after + // disposition are inserted after their target row + if let Some(last_injection_row) = last_injection_row { + if injection_row != last_injection_row { + let injection_point = Point::new(last_injection_row + 1, 0); + if injection_point > new_transforms.summary().input.lines { + let injection_offset = injection_point.to_offset(buffer); + new_transforms.push( + Transform::isomorphic(buffer.text_summary_for_range( + new_transforms.summary().input.bytes..injection_offset, + )), + &(), + ); + } + for injection_id in pending_after_injections.drain(..) { + new_transforms.push( + Transform::for_injection( + self.injections.get(&injection_id, &()).unwrap(), + ), + &(), + ) + } + } + } - if next_edit.old.end >= edit.old.end { - edit.old.end = next_edit.old.end; - cursor.seek(&edit.old.end, Bias::Left, &()); - cursor.next(&()); + match site.disposition { + Disposition::AfterLine => pending_after_injections.push(site.injection_id), + Disposition::BeforeLine => { + let injection_point = Point::new(injection_row, 0); + if injection_point > new_transforms.summary().input.lines { + let injection_offset = injection_point.to_offset(buffer); + new_transforms.push( + Transform::isomorphic(buffer.text_summary_for_range( + new_transforms.summary().input.bytes..injection_offset, + )), + &(), + ); + } + new_transforms.push( + Transform::for_injection( + self.injections.get(&site.injection_id, &()).unwrap(), + ), + &(), + ); } - } else { - break; } - } - edit.new.end = ((edit.new.start + edit.old.len()) as isize + delta) as usize; - - if !edit.new.is_empty() { - let text_summary = buffer.text_summary_for_range(edit.new.start..edit.new.end); - new_transforms.push( - Transform { - input: text_summary.clone(), - output: text_summary, - injection_id: None, - }, - &(), - ); + last_injection_row = Some(injection_row); } + + if let Some(last_injection_row) = injection_row {} } new_transforms.push_tree(cursor.suffix(&()), &()); drop(cursor); - let injection_edits = { - let mut old_transforms = transforms.cursor::<(usize, InjectionOffset)>(); - let mut new_transforms = new_transforms.cursor::<(usize, InjectionOffset)>(); - - buffer_edits - .into_iter() - .map(|edit| { - old_transforms.seek(&edit.old.start, Bias::Right, &()); - let old_start = - old_transforms.start().1 .0 + (edit.old.start - old_transforms.start().0); - - old_transforms.seek_forward(&edit.old.end, Bias::Left, &()); - let old_end = - old_transforms.start().1 .0 + (edit.old.end - old_transforms.start().0); - - new_transforms.seek(&edit.new.start, Bias::Right, &()); - let new_start = - new_transforms.start().1 .0 + (edit.new.start - new_transforms.start().0); - - new_transforms.seek_forward(&edit.new.end, Bias::Left, &()); - let new_end = - new_transforms.start().1 .0 + (edit.new.end - new_transforms.start().0); - - Edit { - old: InjectionOffset(old_start)..InjectionOffset(old_end), - new: InjectionOffset(new_start)..InjectionOffset(new_end), - } - }) - .collect() - }; - *transforms = new_transforms; - injection_edits + todo!() } } @@ -237,62 +285,61 @@ impl<'a> InjectionMapWriter<'a> { Vec>, ) where - T: IntoIterator)>, - U: ToOffset, + T: IntoIterator, { let buffer = self.0.buffer.read(cx); - let mut injections = injections - .into_iter() - .map(|(position, text, runs)| (position.to_offset(buffer), text, runs)) - .peekable(); - let mut edits = Vec::new(); + let mut cursor = self.0.injection_sites.cursor::(); + let mut new_sites = SumTree::new(); let mut injection_ids = Vec::new(); - let mut new_transforms = SumTree::new(); - let mut transforms = self.0.transforms.lock(); - let mut cursor = transforms.cursor::(); - - while let Some((injection_offset, text, runs)) = injections.next() { - new_transforms.push_tree(cursor.slice(&injection_offset, Bias::Right, &()), &()); - let new_transforms_end = new_transforms.summary().input.bytes; - if injection_offset > new_transforms_end { - new_transforms.push( - Transform::isomorphic( - buffer.text_summary_for_range(new_transforms_end..injection_offset), - ), - &(), - ); - } + let mut edits = Vec::new(); - let injection = Injection { - id: InjectionId(post_inc(&mut self.0.next_injection_id)), - runs, - text: text.into(), - }; - new_transforms.push( - Transform { - input: Default::default(), - output: injection.text.summary(), - injection_id: Some(injection.id), + for (position, props) in injections { + let point = position.to_point(buffer); + edits.push(Edit { + old: point..point, + new: point..point, + }); + + let id = InjectionId(post_inc(&mut self.0.next_injection_id)); + injection_ids.push(id); + new_sites.push_tree( + cursor.slice( + &InjectionSitePosition(position.clone()), + Bias::Right, + buffer, + ), + buffer, + ); + new_sites.push( + InjectionSite { + injection_id: id, + position, + }, + buffer, + ); + self.0.injections.push( + Injection { + id, + text: props.text, + runs: props.runs, }, &(), ); - self.0.injections.push(injection, &()); - - if let Some((next_injection_offset, _, _)) = injections.peek() { - let old_transform_end = cursor.end(&()); - if *next_injection_offset > old_transform_end { - new_transforms.push( - Transform::isomorphic( - buffer.text_summary_for_range(new_transforms_end..old_transform_end), - ), - &(), - ); - cursor.next(&()); - } - } } + new_sites.push_tree(cursor.suffix(buffer), buffer); + + drop(cursor); + self.0.injection_sites = new_sites; + + let edits = self.0.apply_edits(edits, cx); + let snapshot = InjectionSnapshot { + transforms: self.0.transforms.lock().clone(), + injections: self.0.injections.clone(), + buffer_snapshot: buffer.snapshot(), + version: self.0.version.load(SeqCst), + }; - (injection_ids, todo!(), edits) + (injection_ids, snapshot, edits) } } @@ -304,6 +351,66 @@ impl sum_tree::Item for Injection { } } +impl sum_tree::KeyedItem for Injection { + type Key = InjectionId; + + fn key(&self) -> Self::Key { + self.id + } +} + +impl sum_tree::Item for InjectionSite { + type Summary = InjectionSiteSummary; + + fn summary(&self) -> Self::Summary { + InjectionSiteSummary { + min_injection_id: self.injection_id, + max_injection_id: self.injection_id, + min_position: self.position.clone(), + max_position: self.position.clone(), + } + } +} + +impl Default for InjectionSitePosition { + fn default() -> Self { + Self(Anchor::min()) + } +} + +impl sum_tree::Summary for InjectionSiteSummary { + type Context = buffer::Buffer; + + fn add_summary(&mut self, summary: &Self, _: &Self::Context) { + self.min_injection_id = cmp::min(self.min_injection_id, summary.min_injection_id); + self.max_injection_id = cmp::max(self.max_injection_id, summary.max_injection_id); + self.max_position = summary.max_position; + } +} + +impl<'a> sum_tree::Dimension<'a, InjectionSiteSummary> for InjectionSitePosition { + fn add_summary(&mut self, summary: &'a InjectionSiteSummary, _: &buffer::Buffer) { + self.0 = summary.max_position; + } +} + +impl<'a> sum_tree::SeekTarget<'a, InjectionSiteSummary, Self> for InjectionSitePosition { + fn cmp(&self, cursor_location: &Self, snapshot: &buffer::Buffer) -> Ordering { + self.0.cmp(&cursor_location.0, snapshot).unwrap() + } +} + +impl Default for InjectionSiteSummary { + fn default() -> Self { + Self { + min_injection_id: InjectionId(usize::MAX), + max_injection_id: InjectionId(0), + min_position: Anchor::max(), + max_position: Anchor::min(), + } + } +} + impl Transform { fn isomorphic(text_summary: TextSummary) -> Self { Self { @@ -312,6 +419,14 @@ impl Transform { injection_id: None, } } + + fn for_injection(injection: &Injection) -> Self { + Self { + input: Default::default(), + output: injection.text.summary(), + injection_id: Some(injection.id), + } + } } impl sum_tree::Item for Transform { @@ -352,6 +467,12 @@ impl<'a> sum_tree::Dimension<'a, TransformSummary> for usize { } } +impl<'a> sum_tree::Dimension<'a, TransformSummary> for Point { + fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) { + *self += summary.input.lines + } +} + impl<'a> sum_tree::Dimension<'a, TransformSummary> for InjectionOffset { fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) { self.0 += summary.output.bytes From 7dcf30c9542f5b23e01207dfa1fbd41e7648e599 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 11 Nov 2021 15:04:31 +0100 Subject: [PATCH 008/109] WIP --- .../editor/src/display_map/injection_map.rs | 166 ++++++++++++++---- 1 file changed, 136 insertions(+), 30 deletions(-) diff --git a/crates/editor/src/display_map/injection_map.rs b/crates/editor/src/display_map/injection_map.rs index ea3d1b9da2ad6b6482680f746bf37dede4c1a117..995190775cb099098e3e65e32120b2624a0fb342 100644 --- a/crates/editor/src/display_map/injection_map.rs +++ b/crates/editor/src/display_map/injection_map.rs @@ -25,7 +25,7 @@ pub struct InjectionMap { next_injection_id: usize, } -pub struct InjectionSnapshot { +pub struct Snapshot { transforms: SumTree, injections: SumTree, buffer_snapshot: language::Snapshot, @@ -103,7 +103,7 @@ struct TransformSummary { } #[derive(Copy, Clone, Debug, Default)] -struct InjectionOffset(usize); +pub struct InjectionOffset(usize); impl sum_tree::Summary for InjectionId { type Context = (); @@ -114,10 +114,32 @@ impl sum_tree::Summary for InjectionId { } impl InjectionMap { - pub fn read(&self, cx: &AppContext) -> (InjectionSnapshot, Vec>) { + pub fn new(buffer_handle: ModelHandle, cx: &AppContext) -> (Self, Snapshot) { + let buffer = buffer_handle.read(cx); + let this = Self { + buffer: buffer_handle, + injections: Default::default(), + injection_sites: Default::default(), + transforms: Mutex::new(SumTree::from_item( + Transform::isomorphic(buffer.text_summary()), + &(), + )), + last_sync: Mutex::new(SyncState { + version: buffer.version(), + parse_count: buffer.parse_count(), + diagnostics_update_count: buffer.diagnostics_update_count(), + }), + version: AtomicUsize::new(0), + next_injection_id: 0, + }; + let (snapshot, _) = this.read(cx); + (this, snapshot) + } + + pub fn read(&self, cx: &AppContext) -> (Snapshot, Vec>) { let edits = self.sync(cx); // self.check_invariants(cx); - let snapshot = InjectionSnapshot { + let snapshot = Snapshot { transforms: self.transforms.lock().clone(), injections: self.injections.clone(), buffer_snapshot: self.buffer.read(cx).snapshot(), @@ -129,11 +151,7 @@ impl InjectionMap { pub fn write( &mut self, cx: &AppContext, - ) -> ( - InjectionMapWriter, - InjectionSnapshot, - Vec>, - ) { + ) -> (InjectionMapWriter, Snapshot, Vec>) { let (snapshot, edits) = self.read(cx); (InjectionMapWriter(self), snapshot, edits) } @@ -174,31 +192,47 @@ impl InjectionMap { let mut new_transforms = SumTree::::new(); let mut transforms = self.transforms.lock(); + let old_max_point = transforms.summary().input.lines; + let new_max_point = buffer.max_point(); let mut cursor = transforms.cursor::(); let mut injection_sites = self.injection_sites.cursor::(); let mut pending_after_injections: Vec = Vec::new(); while let Some(mut edit) = buffer_edits_iter.next() { - // Expand this edit to line boundaries + dbg!(&edit); + // Expand this edit to line boundaries. edit.old.start.column = 0; edit.old.end += Point::new(1, 0); edit.new.start.column = 0; edit.new.end += Point::new(1, 0); - // Merge with subsequent edits that intersect the same lines - while let Some(next_edit) = buffer_edits_iter.peek() { - if next_edit.old.start.row > edit.old.end.row { - break; + // Push any transforms preceding the edit. + new_transforms.push_tree(cursor.slice(&edit.old.start, Bias::Left, &()), &()); + + // Snap edits to row boundaries of intersecting transforms. + loop { + if cmp::min(edit.old.end, old_max_point) <= cursor.end(&()) { + cursor.seek(&edit.old.end, Bias::Left, &()); + cursor.next(&()); + let new_old_end = *cursor.start() + Point::new(1, 0); + edit.new.end += new_old_end - edit.old.end; + edit.old.end = new_old_end; } - let next_edit = buffer_edits_iter.next().unwrap(); - edit.old.end.row = next_edit.old.end.row + 1; - let row_delta = next_edit.new.end.row as i32 - next_edit.old.end.row as i32; - edit.new.end.row = (edit.new.end.row as i32 + row_delta) as u32; + if buffer_edits_iter.peek().map_or(false, |next_edit| { + edit.old.end.row >= next_edit.old.start.row + }) { + let next_edit = buffer_edits_iter.next().unwrap(); + edit.old.end = cmp::max(edit.old.end, next_edit.old.end + Point::new(1, 0)); + let row_delta = (next_edit.new.end.row as i32 - next_edit.new.start.row as i32) + - (next_edit.old.end.row as i32 - next_edit.old.start.row as i32); + edit.new.end.row = (edit.new.end.row as i32 + row_delta) as u32; + } else { + break; + } } - // Push any transforms preceding the edit - new_transforms.push_tree(cursor.slice(&edit.old.start, Bias::Left, &()), &()); + dbg!(&edit); // Find and insert all injections on the lines spanned by the edit, interleaved with isomorphic regions injection_sites.seek( @@ -264,13 +298,38 @@ impl InjectionMap { last_injection_row = Some(injection_row); } - if let Some(last_injection_row) = injection_row {} + if let Some(last_injection_row) = last_injection_row { + let injection_point = Point::new(last_injection_row + 1, 0); + if injection_point > new_transforms.summary().input.lines { + let injection_offset = injection_point.to_offset(buffer); + new_transforms.push( + Transform::isomorphic(buffer.text_summary_for_range( + new_transforms.summary().input.bytes..injection_offset, + )), + &(), + ); + } + for injection_id in pending_after_injections.drain(..) { + new_transforms.push( + Transform::for_injection(self.injections.get(&injection_id, &()).unwrap()), + &(), + ) + } + } + + let sum = new_transforms.summary(); + let new_end = cmp::min(edit.new.end, new_max_point); + if sum.input.lines < new_end { + let text_summary = + buffer.text_summary_for_range(sum.input.bytes..new_end.to_offset(buffer)); + new_transforms.push(Transform::isomorphic(text_summary), &()); + } } new_transforms.push_tree(cursor.suffix(&()), &()); drop(cursor); *transforms = new_transforms; - todo!() + Vec::new() } } @@ -279,11 +338,7 @@ impl<'a> InjectionMapWriter<'a> { &mut self, injections: T, cx: &AppContext, - ) -> ( - Vec, - InjectionSnapshot, - Vec>, - ) + ) -> (Vec, Snapshot, Vec>) where T: IntoIterator, { @@ -314,6 +369,7 @@ impl<'a> InjectionMapWriter<'a> { InjectionSite { injection_id: id, position, + disposition: props.disposition, }, buffer, ); @@ -332,7 +388,7 @@ impl<'a> InjectionMapWriter<'a> { self.0.injection_sites = new_sites; let edits = self.0.apply_edits(edits, cx); - let snapshot = InjectionSnapshot { + let snapshot = Snapshot { transforms: self.0.transforms.lock().clone(), injections: self.0.injections.clone(), buffer_snapshot: buffer.snapshot(), @@ -384,13 +440,13 @@ impl sum_tree::Summary for InjectionSiteSummary { fn add_summary(&mut self, summary: &Self, _: &Self::Context) { self.min_injection_id = cmp::min(self.min_injection_id, summary.min_injection_id); self.max_injection_id = cmp::max(self.max_injection_id, summary.max_injection_id); - self.max_position = summary.max_position; + self.max_position = summary.max_position.clone(); } } impl<'a> sum_tree::Dimension<'a, InjectionSiteSummary> for InjectionSitePosition { fn add_summary(&mut self, summary: &'a InjectionSiteSummary, _: &buffer::Buffer) { - self.0 = summary.max_position; + self.0 = summary.max_position.clone(); } } @@ -478,3 +534,53 @@ impl<'a> sum_tree::Dimension<'a, TransformSummary> for InjectionOffset { self.0 += summary.output.bytes } } + +#[cfg(test)] +mod tests { + use std::env; + + use super::*; + use buffer::RandomCharIter; + use rand::prelude::*; + + #[gpui::test(iterations = 1000)] + fn test_random(cx: &mut gpui::MutableAppContext, mut rng: StdRng) { + let operations = env::var("OPERATIONS") + .map(|i| i.parse().expect("invalid `OPERATIONS` variable")) + .unwrap_or(1); + + let buffer = cx.add_model(|cx| { + let len = rng.gen_range(0..10); + let text = RandomCharIter::new(&mut rng).take(len).collect::(); + Buffer::new(0, text, cx) + }); + let (map, initial_snapshot) = InjectionMap::new(buffer.clone(), cx.as_ref()); + assert_eq!( + initial_snapshot.transforms.summary().input, + buffer.read(cx).text_summary() + ); + + for _ in 0..operations { + log::info!("text: {:?}", buffer.read(cx).text()); + match rng.gen_range(0..=100) { + _ => { + let edits = buffer.update(cx, |buffer, _| { + let start_version = buffer.version.clone(); + let edit_count = rng.gen_range(1..=5); + buffer.randomly_edit(&mut rng, edit_count); + buffer + .edits_since::(&start_version) + .collect::>() + }); + log::info!("editing {:?}", edits); + } + } + + let (snapshot, edits) = map.read(cx.as_ref()); + assert_eq!( + snapshot.transforms.summary().input, + buffer.read(cx).text_summary() + ); + } + } +} From b2caf9e905b64333a8995a435adc6a9fbc0959cc Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 11 Nov 2021 15:04:39 +0100 Subject: [PATCH 009/109] WIP: Start on BlockMap Co-Authored-By: Nathan Sobo --- crates/editor/src/display_map.rs | 1 + crates/editor/src/display_map/block_map.rs | 142 +++++++++++++++++++++ crates/editor/src/display_map/wrap_map.rs | 2 + 3 files changed, 145 insertions(+) create mode 100644 crates/editor/src/display_map/block_map.rs diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 537c4f8ee8973820971f359810d323c9c4f79215..7497e477b64236313ab62ad290c23b26efd47f8a 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -1,3 +1,4 @@ +mod block_map; mod fold_map; mod injection_map; mod tab_map; diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs new file mode 100644 index 0000000000000000000000000000000000000000..44355adc6927684c5e73c96e6838cb31880eb69d --- /dev/null +++ b/crates/editor/src/display_map/block_map.rs @@ -0,0 +1,142 @@ +use std::cmp; + +use super::wrap_map::{Edit as WrapEdit, Snapshot as WrapSnapshot}; +use buffer::Bias; +use parking_lot::Mutex; +use sum_tree::SumTree; + +struct BlockMap { + transforms: Mutex>, +} + +struct BlockMapWriter<'a>(&'a mut BlockMap); + +struct BlockSnapshot {} + +#[derive(Clone)] +struct Transform { + summary: TransformSummary, +} + +#[derive(Copy, Clone, Debug, Default)] +struct TransformSummary { + input_rows: u32, + output_rows: u32, +} + +#[derive(Copy, Clone, Default, Debug, PartialEq, Eq, PartialOrd, Ord)] +struct InputRow(u32); + +#[derive(Copy, Clone, Default, Debug, PartialEq, Eq, PartialOrd, Ord)] +struct OutputRow(u32); + +impl BlockMap { + fn new(wrap_snapshot: WrapSnapshot) -> Self { + Self { + transforms: Mutex::new(SumTree::from_item( + Transform::isomorphic(wrap_snapshot.max_point().row() + 1), + &(), + )), + } + } + + fn read(&self, wrap_snapshot: WrapSnapshot, edits: Vec) -> BlockSnapshot { + self.sync(wrap_snapshot, edits); + BlockSnapshot {} + } + + fn write(&mut self, wrap_snapshot: WrapSnapshot, edits: Vec) -> BlockMapWriter { + self.sync(wrap_snapshot, edits); + BlockMapWriter(self) + } + + fn sync(&self, wrap_snapshot: WrapSnapshot, edits: Vec) { + let transforms = self.transforms.lock(); + let mut new_transforms = SumTree::new(); + let mut cursor = transforms.cursor::(); + let mut edits = edits.into_iter().peekable(); + while let Some(mut edit) = edits.next() { + new_transforms.push_tree( + cursor.slice(&InputRow(edit.old.start), Bias::Left, &()), + &(), + ); + + let transform_start = cursor.start().0; + edit.new.start -= edit.old.start - transform_start; + edit.old.start = transform_start; + + loop { + if edit.old.end > cursor.start().0 { + cursor.seek(&InputRow(edit.old.end), Bias::Left, &()); + cursor.next(&()); + let transform_end = cursor.start().0; + edit.new.end += transform_end - edit.old.end; + edit.old.end = transform_end; + } + + if let Some(next_edit) = edits.peek() { + if edit.old.end >= next_edit.old.start { + edit.old.end = cmp::max(next_edit.old.end, edit.old.end); + edit.new.end += (edit.new.len() as i32 - edit.old.len() as i32) as u32; + edits.next(); + } else { + break; + } + } else { + break; + } + } + + // TODO: process injections + + let new_transforms_end = new_transforms.summary().input_rows; + if new_transforms_end < edit.new.end { + new_transforms.push( + Transform::isomorphic(edit.new.end - new_transforms_end), + &(), + ); + } + } + new_transforms.push_tree(cursor.suffix(&()), &()); + } +} + +impl Transform { + fn isomorphic(rows: u32) -> Self { + Self { + summary: TransformSummary { + input_rows: rows, + output_rows: rows, + }, + } + } +} + +impl sum_tree::Item for Transform { + type Summary = TransformSummary; + + fn summary(&self) -> Self::Summary { + self.summary + } +} + +impl sum_tree::Summary for TransformSummary { + type Context = (); + + fn add_summary(&mut self, summary: &Self, _: &()) { + self.input_rows += summary.input_rows; + self.output_rows += summary.output_rows; + } +} + +impl<'a> sum_tree::Dimension<'a, TransformSummary> for InputRow { + fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) { + self.0 += summary.input_rows; + } +} + +impl<'a> sum_tree::Dimension<'a, TransformSummary> for OutputRow { + fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) { + self.0 += summary.output_rows; + } +} diff --git a/crates/editor/src/display_map/wrap_map.rs b/crates/editor/src/display_map/wrap_map.rs index a62c67dbce5b4d3654015f7d060645462b383b47..5991d060a0b48727d9b0a3a97751426a5d961b62 100644 --- a/crates/editor/src/display_map/wrap_map.rs +++ b/crates/editor/src/display_map/wrap_map.rs @@ -9,6 +9,8 @@ use smol::future::yield_now; use std::{collections::VecDeque, ops::Range, time::Duration}; use sum_tree::{Bias, Cursor, SumTree}; +pub type Edit = buffer::Edit; + pub struct WrapMap { snapshot: Snapshot, pending_edits: VecDeque<(TabSnapshot, Vec)>, From 59bbe43a46fd9b31392f8463a80b2a6fcb40fd9f Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 11 Nov 2021 16:00:52 +0100 Subject: [PATCH 010/109] WIP Co-Authored-By: Nathan Sobo --- crates/editor/src/display_map.rs | 3 +- .../editor/src/display_map/injection_map.rs | 586 ------------------ crates/editor/src/display_map/wrap_map.rs | 104 +++- 3 files changed, 91 insertions(+), 602 deletions(-) delete mode 100644 crates/editor/src/display_map/injection_map.rs diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 7497e477b64236313ab62ad290c23b26efd47f8a..9e9fa4a664dd510253b1009eb774f34aff6a6cb1 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -1,6 +1,5 @@ mod block_map; mod fold_map; -mod injection_map; mod tab_map; mod wrap_map; @@ -53,7 +52,7 @@ impl DisplayMap { pub fn snapshot(&self, cx: &mut ModelContext) -> DisplayMapSnapshot { let (folds_snapshot, edits) = self.fold_map.read(cx); let (tabs_snapshot, edits) = self.tab_map.sync(folds_snapshot.clone(), edits); - let wraps_snapshot = self + let (wraps_snapshot, _) = self .wrap_map .update(cx, |map, cx| map.sync(tabs_snapshot.clone(), edits, cx)); DisplayMapSnapshot { diff --git a/crates/editor/src/display_map/injection_map.rs b/crates/editor/src/display_map/injection_map.rs deleted file mode 100644 index 995190775cb099098e3e65e32120b2624a0fb342..0000000000000000000000000000000000000000 --- a/crates/editor/src/display_map/injection_map.rs +++ /dev/null @@ -1,586 +0,0 @@ -use std::{ - cmp::{self, Ordering}, - collections::BTreeMap, - mem, - sync::atomic::{AtomicUsize, Ordering::SeqCst}, -}; - -use buffer::{Anchor, Bias, Edit, Point, Rope, TextSummary, ToOffset, ToPoint}; -use gpui::{fonts::HighlightStyle, AppContext, ModelHandle}; -use language::Buffer; -use parking_lot::Mutex; -use sum_tree::SumTree; -use util::post_inc; - -#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Ord, PartialOrd)] -pub struct InjectionId(usize); - -pub struct InjectionMap { - buffer: ModelHandle, - transforms: Mutex>, - injections: SumTree, - injection_sites: SumTree, - version: AtomicUsize, - last_sync: Mutex, - next_injection_id: usize, -} - -pub struct Snapshot { - transforms: SumTree, - injections: SumTree, - buffer_snapshot: language::Snapshot, - pub version: usize, -} - -pub struct InjectionMapWriter<'a>(&'a mut InjectionMap); - -#[derive(Clone)] -struct SyncState { - version: clock::Global, - parse_count: usize, - diagnostics_update_count: usize, -} - -#[derive(Clone, Debug)] -struct InjectionSummary { - min_id: InjectionId, - max_id: InjectionId, - min_position: Anchor, - max_position: Anchor, -} - -#[derive(Clone, Debug)] -struct Injection { - id: InjectionId, - text: Rope, - runs: Vec<(usize, HighlightStyle)>, -} - -#[derive(Clone, Debug)] -pub struct InjectionProps { - text: Rope, - runs: Vec<(usize, HighlightStyle)>, - disposition: Disposition, -} - -#[derive(Clone, Debug)] -pub enum Disposition { - BeforeLine, - AfterLine, -} - -#[derive(Clone, Debug)] -struct InjectionSite { - injection_id: InjectionId, - position: Anchor, - disposition: Disposition, -} - -#[derive(Clone, Debug)] -struct InjectionSitePosition(Anchor); - -#[derive(Clone, Debug, Eq, PartialEq)] -struct InjectionSiteSummary { - min_injection_id: InjectionId, - max_injection_id: InjectionId, - min_position: Anchor, - max_position: Anchor, -} - -#[derive(Clone, Debug, Default, PartialEq)] -struct Transform { - input: TextSummary, - output: TextSummary, - injection_id: Option, -} - -#[derive(Clone, Debug, Default, Eq, PartialEq)] -struct TransformSummary { - input: TextSummary, - output: TextSummary, - min_injection_id: InjectionId, - max_injection_id: InjectionId, -} - -#[derive(Copy, Clone, Debug, Default)] -pub struct InjectionOffset(usize); - -impl sum_tree::Summary for InjectionId { - type Context = (); - - fn add_summary(&mut self, summary: &Self, cx: &Self::Context) { - *self = *summary - } -} - -impl InjectionMap { - pub fn new(buffer_handle: ModelHandle, cx: &AppContext) -> (Self, Snapshot) { - let buffer = buffer_handle.read(cx); - let this = Self { - buffer: buffer_handle, - injections: Default::default(), - injection_sites: Default::default(), - transforms: Mutex::new(SumTree::from_item( - Transform::isomorphic(buffer.text_summary()), - &(), - )), - last_sync: Mutex::new(SyncState { - version: buffer.version(), - parse_count: buffer.parse_count(), - diagnostics_update_count: buffer.diagnostics_update_count(), - }), - version: AtomicUsize::new(0), - next_injection_id: 0, - }; - let (snapshot, _) = this.read(cx); - (this, snapshot) - } - - pub fn read(&self, cx: &AppContext) -> (Snapshot, Vec>) { - let edits = self.sync(cx); - // self.check_invariants(cx); - let snapshot = Snapshot { - transforms: self.transforms.lock().clone(), - injections: self.injections.clone(), - buffer_snapshot: self.buffer.read(cx).snapshot(), - version: self.version.load(SeqCst), - }; - (snapshot, edits) - } - - pub fn write( - &mut self, - cx: &AppContext, - ) -> (InjectionMapWriter, Snapshot, Vec>) { - let (snapshot, edits) = self.read(cx); - (InjectionMapWriter(self), snapshot, edits) - } - - fn sync(&self, cx: &AppContext) -> Vec> { - let buffer = self.buffer.read(cx); - let last_sync = mem::replace( - &mut *self.last_sync.lock(), - SyncState { - version: buffer.version(), - parse_count: buffer.parse_count(), - diagnostics_update_count: buffer.diagnostics_update_count(), - }, - ); - let edits = buffer - .edits_since(&last_sync.version) - .map(Into::into) - .collect::>(); - if edits.is_empty() { - if last_sync.parse_count != buffer.parse_count() - || last_sync.diagnostics_update_count != buffer.diagnostics_update_count() - { - self.version.fetch_add(1, SeqCst); - } - Vec::new() - } else { - self.apply_edits(edits, cx) - } - } - - fn apply_edits( - &self, - buffer_edits: Vec>, - cx: &AppContext, - ) -> Vec> { - let buffer = self.buffer.read(cx); - let mut buffer_edits_iter = buffer_edits.iter().cloned().peekable(); - - let mut new_transforms = SumTree::::new(); - let mut transforms = self.transforms.lock(); - let old_max_point = transforms.summary().input.lines; - let new_max_point = buffer.max_point(); - let mut cursor = transforms.cursor::(); - let mut injection_sites = self.injection_sites.cursor::(); - let mut pending_after_injections: Vec = Vec::new(); - - while let Some(mut edit) = buffer_edits_iter.next() { - dbg!(&edit); - // Expand this edit to line boundaries. - edit.old.start.column = 0; - edit.old.end += Point::new(1, 0); - edit.new.start.column = 0; - edit.new.end += Point::new(1, 0); - - // Push any transforms preceding the edit. - new_transforms.push_tree(cursor.slice(&edit.old.start, Bias::Left, &()), &()); - - // Snap edits to row boundaries of intersecting transforms. - loop { - if cmp::min(edit.old.end, old_max_point) <= cursor.end(&()) { - cursor.seek(&edit.old.end, Bias::Left, &()); - cursor.next(&()); - let new_old_end = *cursor.start() + Point::new(1, 0); - edit.new.end += new_old_end - edit.old.end; - edit.old.end = new_old_end; - } - - if buffer_edits_iter.peek().map_or(false, |next_edit| { - edit.old.end.row >= next_edit.old.start.row - }) { - let next_edit = buffer_edits_iter.next().unwrap(); - edit.old.end = cmp::max(edit.old.end, next_edit.old.end + Point::new(1, 0)); - let row_delta = (next_edit.new.end.row as i32 - next_edit.new.start.row as i32) - - (next_edit.old.end.row as i32 - next_edit.old.start.row as i32); - edit.new.end.row = (edit.new.end.row as i32 + row_delta) as u32; - } else { - break; - } - } - - dbg!(&edit); - - // Find and insert all injections on the lines spanned by the edit, interleaved with isomorphic regions - injection_sites.seek( - &InjectionSitePosition(buffer.anchor_before(edit.new.start)), - Bias::Right, - buffer, - ); - let mut last_injection_row: Option = None; - while let Some(site) = injection_sites.item() { - let injection_row = site.position.to_point(buffer).row; - - if injection_row > edit.new.end.row { - break; - } - - // If we've moved on to a new injection row, ensure that any pending injections with an after - // disposition are inserted after their target row - if let Some(last_injection_row) = last_injection_row { - if injection_row != last_injection_row { - let injection_point = Point::new(last_injection_row + 1, 0); - if injection_point > new_transforms.summary().input.lines { - let injection_offset = injection_point.to_offset(buffer); - new_transforms.push( - Transform::isomorphic(buffer.text_summary_for_range( - new_transforms.summary().input.bytes..injection_offset, - )), - &(), - ); - } - for injection_id in pending_after_injections.drain(..) { - new_transforms.push( - Transform::for_injection( - self.injections.get(&injection_id, &()).unwrap(), - ), - &(), - ) - } - } - } - - match site.disposition { - Disposition::AfterLine => pending_after_injections.push(site.injection_id), - Disposition::BeforeLine => { - let injection_point = Point::new(injection_row, 0); - if injection_point > new_transforms.summary().input.lines { - let injection_offset = injection_point.to_offset(buffer); - new_transforms.push( - Transform::isomorphic(buffer.text_summary_for_range( - new_transforms.summary().input.bytes..injection_offset, - )), - &(), - ); - } - new_transforms.push( - Transform::for_injection( - self.injections.get(&site.injection_id, &()).unwrap(), - ), - &(), - ); - } - } - - last_injection_row = Some(injection_row); - } - - if let Some(last_injection_row) = last_injection_row { - let injection_point = Point::new(last_injection_row + 1, 0); - if injection_point > new_transforms.summary().input.lines { - let injection_offset = injection_point.to_offset(buffer); - new_transforms.push( - Transform::isomorphic(buffer.text_summary_for_range( - new_transforms.summary().input.bytes..injection_offset, - )), - &(), - ); - } - for injection_id in pending_after_injections.drain(..) { - new_transforms.push( - Transform::for_injection(self.injections.get(&injection_id, &()).unwrap()), - &(), - ) - } - } - - let sum = new_transforms.summary(); - let new_end = cmp::min(edit.new.end, new_max_point); - if sum.input.lines < new_end { - let text_summary = - buffer.text_summary_for_range(sum.input.bytes..new_end.to_offset(buffer)); - new_transforms.push(Transform::isomorphic(text_summary), &()); - } - } - new_transforms.push_tree(cursor.suffix(&()), &()); - drop(cursor); - - *transforms = new_transforms; - Vec::new() - } -} - -impl<'a> InjectionMapWriter<'a> { - pub fn insert<'b, T, U>( - &mut self, - injections: T, - cx: &AppContext, - ) -> (Vec, Snapshot, Vec>) - where - T: IntoIterator, - { - let buffer = self.0.buffer.read(cx); - let mut cursor = self.0.injection_sites.cursor::(); - let mut new_sites = SumTree::new(); - let mut injection_ids = Vec::new(); - let mut edits = Vec::new(); - - for (position, props) in injections { - let point = position.to_point(buffer); - edits.push(Edit { - old: point..point, - new: point..point, - }); - - let id = InjectionId(post_inc(&mut self.0.next_injection_id)); - injection_ids.push(id); - new_sites.push_tree( - cursor.slice( - &InjectionSitePosition(position.clone()), - Bias::Right, - buffer, - ), - buffer, - ); - new_sites.push( - InjectionSite { - injection_id: id, - position, - disposition: props.disposition, - }, - buffer, - ); - self.0.injections.push( - Injection { - id, - text: props.text, - runs: props.runs, - }, - &(), - ); - } - new_sites.push_tree(cursor.suffix(buffer), buffer); - - drop(cursor); - self.0.injection_sites = new_sites; - - let edits = self.0.apply_edits(edits, cx); - let snapshot = Snapshot { - transforms: self.0.transforms.lock().clone(), - injections: self.0.injections.clone(), - buffer_snapshot: buffer.snapshot(), - version: self.0.version.load(SeqCst), - }; - - (injection_ids, snapshot, edits) - } -} - -impl sum_tree::Item for Injection { - type Summary = InjectionId; - - fn summary(&self) -> Self::Summary { - self.id - } -} - -impl sum_tree::KeyedItem for Injection { - type Key = InjectionId; - - fn key(&self) -> Self::Key { - self.id - } -} - -impl sum_tree::Item for InjectionSite { - type Summary = InjectionSiteSummary; - - fn summary(&self) -> Self::Summary { - InjectionSiteSummary { - min_injection_id: self.injection_id, - max_injection_id: self.injection_id, - min_position: self.position.clone(), - max_position: self.position.clone(), - } - } -} - -impl Default for InjectionSitePosition { - fn default() -> Self { - Self(Anchor::min()) - } -} - -impl sum_tree::Summary for InjectionSiteSummary { - type Context = buffer::Buffer; - - fn add_summary(&mut self, summary: &Self, _: &Self::Context) { - self.min_injection_id = cmp::min(self.min_injection_id, summary.min_injection_id); - self.max_injection_id = cmp::max(self.max_injection_id, summary.max_injection_id); - self.max_position = summary.max_position.clone(); - } -} - -impl<'a> sum_tree::Dimension<'a, InjectionSiteSummary> for InjectionSitePosition { - fn add_summary(&mut self, summary: &'a InjectionSiteSummary, _: &buffer::Buffer) { - self.0 = summary.max_position.clone(); - } -} - -impl<'a> sum_tree::SeekTarget<'a, InjectionSiteSummary, Self> for InjectionSitePosition { - fn cmp(&self, cursor_location: &Self, snapshot: &buffer::Buffer) -> Ordering { - self.0.cmp(&cursor_location.0, snapshot).unwrap() - } -} - -impl Default for InjectionSiteSummary { - fn default() -> Self { - Self { - min_injection_id: InjectionId(usize::MAX), - max_injection_id: InjectionId(0), - min_position: Anchor::max(), - max_position: Anchor::min(), - } - } -} - -impl Transform { - fn isomorphic(text_summary: TextSummary) -> Self { - Self { - input: text_summary.clone(), - output: text_summary, - injection_id: None, - } - } - - fn for_injection(injection: &Injection) -> Self { - Self { - input: Default::default(), - output: injection.text.summary(), - injection_id: Some(injection.id), - } - } -} - -impl sum_tree::Item for Transform { - type Summary = TransformSummary; - - fn summary(&self) -> Self::Summary { - let min_injection_id; - let max_injection_id; - if let Some(id) = self.injection_id { - min_injection_id = id; - max_injection_id = id; - } else { - min_injection_id = InjectionId(usize::MAX); - max_injection_id = InjectionId(0); - } - - TransformSummary { - input: self.input.clone(), - output: self.output.clone(), - min_injection_id, - max_injection_id, - } - } -} - -impl sum_tree::Summary for TransformSummary { - type Context = (); - - fn add_summary(&mut self, other: &Self, _: &()) { - self.input += &other.input; - self.output += &other.output; - } -} - -impl<'a> sum_tree::Dimension<'a, TransformSummary> for usize { - fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) { - *self += summary.input.bytes - } -} - -impl<'a> sum_tree::Dimension<'a, TransformSummary> for Point { - fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) { - *self += summary.input.lines - } -} - -impl<'a> sum_tree::Dimension<'a, TransformSummary> for InjectionOffset { - fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) { - self.0 += summary.output.bytes - } -} - -#[cfg(test)] -mod tests { - use std::env; - - use super::*; - use buffer::RandomCharIter; - use rand::prelude::*; - - #[gpui::test(iterations = 1000)] - fn test_random(cx: &mut gpui::MutableAppContext, mut rng: StdRng) { - let operations = env::var("OPERATIONS") - .map(|i| i.parse().expect("invalid `OPERATIONS` variable")) - .unwrap_or(1); - - let buffer = cx.add_model(|cx| { - let len = rng.gen_range(0..10); - let text = RandomCharIter::new(&mut rng).take(len).collect::(); - Buffer::new(0, text, cx) - }); - let (map, initial_snapshot) = InjectionMap::new(buffer.clone(), cx.as_ref()); - assert_eq!( - initial_snapshot.transforms.summary().input, - buffer.read(cx).text_summary() - ); - - for _ in 0..operations { - log::info!("text: {:?}", buffer.read(cx).text()); - match rng.gen_range(0..=100) { - _ => { - let edits = buffer.update(cx, |buffer, _| { - let start_version = buffer.version.clone(); - let edit_count = rng.gen_range(1..=5); - buffer.randomly_edit(&mut rng, edit_count); - buffer - .edits_since::(&start_version) - .collect::>() - }); - log::info!("editing {:?}", edits); - } - } - - let (snapshot, edits) = map.read(cx.as_ref()); - assert_eq!( - snapshot.transforms.summary().input, - buffer.read(cx).text_summary() - ); - } - } -} diff --git a/crates/editor/src/display_map/wrap_map.rs b/crates/editor/src/display_map/wrap_map.rs index 5991d060a0b48727d9b0a3a97751426a5d961b62..9649d7bf9a9489bd37e18198459ead4e005b7ec8 100644 --- a/crates/editor/src/display_map/wrap_map.rs +++ b/crates/editor/src/display_map/wrap_map.rs @@ -6,7 +6,7 @@ use gpui::{fonts::FontId, text_layout::LineWrapper, Entity, ModelContext, Task}; use language::{HighlightedChunk, Point}; use lazy_static::lazy_static; use smol::future::yield_now; -use std::{collections::VecDeque, ops::Range, time::Duration}; +use std::{collections::VecDeque, mem, ops::Range, time::Duration}; use sum_tree::{Bias, Cursor, SumTree}; pub type Edit = buffer::Edit; @@ -14,11 +14,16 @@ pub type Edit = buffer::Edit; pub struct WrapMap { snapshot: Snapshot, pending_edits: VecDeque<(TabSnapshot, Vec)>, + interpolated_edits: Patch, + edits_since_sync: Patch, wrap_width: Option, background_task: Option>, font: (FontId, f32), } +#[derive(Default)] +struct Patch(Vec); + impl Entity for WrapMap { type Event = (); } @@ -81,6 +86,8 @@ impl WrapMap { font: (font_id, font_size), wrap_width: None, pending_edits: Default::default(), + interpolated_edits: Default::default(), + edits_since_sync: Default::default(), snapshot: Snapshot::new(tab_snapshot), background_task: None, }; @@ -99,10 +106,13 @@ impl WrapMap { tab_snapshot: TabSnapshot, edits: Vec, cx: &mut ModelContext, - ) -> Snapshot { + ) -> (Snapshot, Vec) { self.pending_edits.push_back((tab_snapshot, edits)); self.flush_edits(cx); - self.snapshot.clone() + ( + self.snapshot.clone(), + mem::take(&mut self.edits_since_sync).0, + ) } pub fn set_font(&mut self, font_id: FontId, font_size: f32, cx: &mut ModelContext) { @@ -204,26 +214,32 @@ impl WrapMap { let update_task = cx.background().spawn(async move { let mut line_wrapper = font_cache.line_wrapper(font_id, font_size); + let mut output_edits = Patch::default(); for (tab_snapshot, edits) in pending_edits { - snapshot + let wrap_edits = snapshot .update(tab_snapshot, &edits, wrap_width, &mut line_wrapper) .await; + output_edits.compose(&wrap_edits); } - snapshot + (snapshot, output_edits) }); match cx .background() .block_with_timeout(Duration::from_millis(1), update_task) { - Ok(snapshot) => { + Ok((snapshot, output_edits)) => { self.snapshot = snapshot; + self.edits_since_sync.compose(&output_edits); } Err(update_task) => { self.background_task = Some(cx.spawn(|this, mut cx| async move { - let snapshot = update_task.await; + let (snapshot, output_edits) = update_task.await; this.update(&mut cx, |this, cx| { this.snapshot = snapshot; + this.edits_since_sync + .compose(mem::take(&mut this.interpolated_edits).invert()) + .compose(&output_edits); this.background_task = None; this.flush_edits(cx); cx.notify(); @@ -240,7 +256,9 @@ impl WrapMap { if tab_snapshot.version() <= self.snapshot.tab_snapshot.version() { to_remove_len += 1; } else { - self.snapshot.interpolate(tab_snapshot.clone(), &edits); + let interpolated_edits = self.snapshot.interpolate(tab_snapshot.clone(), &edits); + self.edits_since_sync.compose(&interpolated_edits); + self.interpolated_edits.compose(&interpolated_edits); } } @@ -250,6 +268,59 @@ impl WrapMap { } } +impl Patch { + fn compose(&mut self, other: &Self) -> &mut Self { + + // let mut other_ranges = edit.ranges.iter().peekable(); + // let mut new_ranges = Vec::new(); + // let insertion_len = edit.new_text.as_ref().map_or(0, |t| t.len()); + // let mut delta = 0; + + // for mut self_range in self.ranges.iter().cloned() { + // self_range.start += delta; + // self_range.end += delta; + + // while let Some(other_range) = other_ranges.peek() { + // let mut other_range = (*other_range).clone(); + // other_range.start += delta; + // other_range.end += delta; + + // if other_range.start <= self_range.end { + // other_ranges.next().unwrap(); + // delta += insertion_len; + + // if other_range.end < self_range.start { + // new_ranges.push(other_range.start..other_range.end + insertion_len); + // self_range.start += insertion_len; + // self_range.end += insertion_len; + // } else { + // self_range.start = cmp::min(self_range.start, other_range.start); + // self_range.end = cmp::max(self_range.end, other_range.end) + insertion_len; + // } + // } else { + // break; + // } + // } + + // new_ranges.push(self_range); + // } + + // for other_range in other_ranges { + // new_ranges.push(other_range.start + delta..other_range.end + delta + insertion_len); + // delta += insertion_len; + // } + + // self.ranges = new_ranges; + } + + fn invert(&mut self) -> &mut Self { + for edit in &mut self.0 { + mem::swap(&mut edit.old, &mut edit.new); + } + self + } +} + impl Snapshot { fn new(tab_snapshot: TabSnapshot) -> Self { let mut transforms = SumTree::new(); @@ -264,7 +335,7 @@ impl Snapshot { } } - fn interpolate(&mut self, new_tab_snapshot: TabSnapshot, edits: &[TabEdit]) { + fn interpolate(&mut self, new_tab_snapshot: TabSnapshot, edits: &[TabEdit]) -> Patch { let mut new_transforms; if edits.is_empty() { new_transforms = self.transforms.clone(); @@ -320,6 +391,7 @@ impl Snapshot { self.tab_snapshot = new_tab_snapshot; self.interpolated = true; self.check_invariants(); + todo!() } async fn update( @@ -328,7 +400,7 @@ impl Snapshot { edits: &[TabEdit], wrap_width: f32, line_wrapper: &mut LineWrapper, - ) { + ) -> Patch { #[derive(Debug)] struct RowEdit { old_rows: Range, @@ -458,6 +530,7 @@ impl Snapshot { self.tab_snapshot = new_tab_snapshot; self.interpolated = false; self.check_invariants(); + todo!() } pub fn chunks_at(&self, wrap_row: u32) -> Chunks { @@ -896,6 +969,8 @@ impl<'a> sum_tree::Dimension<'a, TransformSummary> for WrapPoint { } } +fn compose(prev: &mut Vec, next: &[Edit]) {} + #[cfg(test)] mod tests { use super::*; @@ -962,7 +1037,8 @@ mod tests { notifications.recv().await.unwrap(); } - let snapshot = wrap_map.update(&mut cx, |map, cx| map.sync(tabs_snapshot, Vec::new(), cx)); + let (snapshot, _) = + wrap_map.update(&mut cx, |map, cx| map.sync(tabs_snapshot, Vec::new(), cx)); let actual_text = snapshot.text(); assert_eq!( actual_text, expected_text, @@ -987,7 +1063,7 @@ mod tests { cx.read(|cx| fold_map.randomly_mutate(&mut rng, cx)) { let (tabs_snapshot, edits) = tab_map.sync(folds_snapshot, edits); - let mut snapshot = + let (mut snapshot, _) = wrap_map.update(&mut cx, |map, cx| map.sync(tabs_snapshot, edits, cx)); snapshot.check_invariants(); snapshot.verify_chunks(&mut rng); @@ -1012,7 +1088,7 @@ mod tests { let unwrapped_text = tabs_snapshot.text(); let expected_text = wrap_text(&unwrapped_text, wrap_width, &mut line_wrapper); - let mut snapshot = wrap_map.update(&mut cx, |map, cx| { + let (mut snapshot, _) = wrap_map.update(&mut cx, |map, cx| { map.sync(tabs_snapshot.clone(), edits, cx) }); snapshot.check_invariants(); @@ -1026,7 +1102,7 @@ mod tests { } if !wrap_map.read_with(&cx, |map, _| map.is_rewrapping()) { - let mut wrapped_snapshot = + let (mut wrapped_snapshot, _) = wrap_map.update(&mut cx, |map, cx| map.sync(tabs_snapshot, Vec::new(), cx)); let actual_text = wrapped_snapshot.text(); log::info!("Wrapping finished: {:?}", actual_text); From a939535d9524143106b1a2f9707d4a93212931a4 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 11 Nov 2021 16:52:14 +0100 Subject: [PATCH 011/109] WIP Co-Authored-By: Nathan Sobo --- crates/editor/src/display_map/wrap_map.rs | 50 ++++++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/crates/editor/src/display_map/wrap_map.rs b/crates/editor/src/display_map/wrap_map.rs index 9649d7bf9a9489bd37e18198459ead4e005b7ec8..88b629b10c4ebd501443643035d8ca304f8d04da 100644 --- a/crates/editor/src/display_map/wrap_map.rs +++ b/crates/editor/src/display_map/wrap_map.rs @@ -269,8 +269,56 @@ impl WrapMap { } impl Patch { - fn compose(&mut self, other: &Self) -> &mut Self { + fn compose(&mut self, new: &Self) -> &mut Self { + let mut new_edits = new.0.iter().peekable(); + let mut old_delta = 0; + let mut new_delta = 0; + let mut ix = 0; + 'outer: while ix < self.0.len() { + let old_edit = &mut self.0[ix]; + old_edit.new.start = (old_edit.new.start as i32 + new_delta) as u32; + old_edit.new.end = (old_edit.new.end as i32 + new_delta) as u32; + + while let Some(new_edit) = new_edits.peek() { + let new_edit_delta = new_edit.new.len() as i32 - new_edit.old.len() as i32; + if new_edit.old.end < self.0[ix].new.start { + let new_edit = new_edits.next().unwrap().clone(); + self.0.insert(ix, new_edit); + ix += 1; + + let old_edit = &mut self.0[ix]; + old_edit.new.start = (old_edit.new.start as i32 + new_edit_delta) as u32; + old_edit.new.end = (old_edit.new.end as i32 + new_edit_delta) as u32; + new_delta += new_edit_delta; + } else if new_edit.old.start <= self.0[ix].new.end { + let new_edit = new_edits.next().unwrap().clone(); + let old_edit = &mut self.0[ix]; + if new_edit.old.start < old_edit.new.start { + old_edit.old.start -= old_edit.new.start - new_edit.old.start; + old_edit.new.start = new_edit.new.start; + } + if new_edit.old.end > old_edit.new.end { + old_edit.old.end += new_edit.old.end - old_edit.new.end; + old_edit.new.end = new_edit.old.end; + } + + old_edit.new.end = (old_edit.new.end as i32 + new_edit_delta) as u32; + new_delta += new_edit_delta; + if old_edit.old.len() == 0 && old_edit.new.len() == 0 { + self.0.remove(ix); + continue 'outer; + } + } else { + break; + } + } + + ix += 1; + } + + self.0.extend(new_edits.cloned()); + self // let mut other_ranges = edit.ranges.iter().peekable(); // let mut new_ranges = Vec::new(); // let insertion_len = edit.new_text.as_ref().map_or(0, |t| t.len()); From e0897cd01938e8fa6abab646af3e4833f45f042c Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 11 Nov 2021 18:13:35 +0100 Subject: [PATCH 012/109] WIP --- crates/editor/src/display_map.rs | 1 + crates/editor/src/display_map/patch.rs | 138 ++++++++++++++++++++++ crates/editor/src/display_map/wrap_map.rs | 49 ++++++++ 3 files changed, 188 insertions(+) create mode 100644 crates/editor/src/display_map/patch.rs diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 9e9fa4a664dd510253b1009eb774f34aff6a6cb1..d7f49f68f79e675dbcd2654b8b0a61bbca3e95b3 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -1,5 +1,6 @@ mod block_map; mod fold_map; +mod patch; mod tab_map; mod wrap_map; diff --git a/crates/editor/src/display_map/patch.rs b/crates/editor/src/display_map/patch.rs new file mode 100644 index 0000000000000000000000000000000000000000..4fe81bc4186a0ba6e002839d07e5a8662d697e1e --- /dev/null +++ b/crates/editor/src/display_map/patch.rs @@ -0,0 +1,138 @@ +use std::mem; + +type Edit = buffer::Edit; + +#[derive(Default, Debug)] +struct Patch(Vec); + +impl Patch { + fn compose(&self, new: &Self) -> Patch { + let mut composed = Vec::new(); + let mut new_edits = new.0.iter().cloned().peekable(); + let mut old_delta = 0; + let mut new_delta = 0; + for mut old_edit in self.0.iter().cloned() { + let mut next_new_delta = new_delta; + while let Some(mut new_edit) = new_edits.peek().cloned() { + let new_edit_delta = new_edit.new.len() as i32 - new_edit.old.len() as i32; + if new_edit.old.end < old_edit.new.start { + new_edit.old.start = (new_edit.old.start as i32 - old_delta) as u32; + new_edit.old.end = (new_edit.old.end as i32 - old_delta) as u32; + new_edits.next(); + new_delta += new_edit_delta; + next_new_delta += new_edit_delta; + composed.push(new_edit); + } else if new_edit.old.start <= old_edit.new.end { + if new_edit.old.start < old_edit.new.start { + old_edit.old.start -= old_edit.new.start - new_edit.old.start; + old_edit.new.start = new_edit.new.start; + } + if new_edit.old.end > old_edit.new.end { + old_edit.old.end += new_edit.old.end - old_edit.new.end; + old_edit.new.end = new_edit.old.end; + } + + old_edit.new.end = (old_edit.new.end as i32 + new_edit_delta) as u32; + new_edits.next(); + next_new_delta += new_edit_delta; + } else { + break; + } + } + + old_edit.new.start = (old_edit.new.start as i32 + new_delta) as u32; + old_edit.new.end = (old_edit.new.end as i32 + new_delta) as u32; + old_delta += old_edit.new.len() as i32 - old_edit.old.len() as i32; + new_delta = next_new_delta; + composed.push(old_edit); + } + composed.extend(new_edits.map(|mut new_edit| { + new_edit.old.start = (new_edit.old.start as i32 - old_delta) as u32; + new_edit.old.end = (new_edit.old.end as i32 - old_delta) as u32; + new_edit + })); + + Patch(composed) + } + + fn invert(&mut self) -> &mut Self { + for edit in &mut self.0 { + mem::swap(&mut edit.old, &mut edit.new); + } + self + } +} + +#[cfg(test)] +mod tests { + use super::*; + use rand::prelude::*; + use std::env; + + #[gpui::test(iterations = 1000, seed = 28)] + fn test_random(mut rng: StdRng) { + let operations = env::var("OPERATIONS") + .map(|i| i.parse().expect("invalid `OPERATIONS` variable")) + .unwrap_or(3); + + let initial_chars = (0..rng.gen_range(0..=5)) + .map(|_| rng.gen_range(b'a'..=b'z') as char) + .collect::>(); + let mut final_chars = initial_chars.clone(); + let mut patches = Vec::new(); + + println!("initial chars: {:?}", initial_chars); + for _ in 0..operations { + let end = rng.gen_range(0..=final_chars.len()); + let start = rng.gen_range(0..=end); + let mut len = rng.gen_range(0..=3); + if start == end && len == 0 { + len += 1; + } + let new_chars = (0..len) + .map(|_| rng.gen_range(b'a'..=b'z') as char) + .collect::>(); + println!( + "editing {:?}: {:?}", + start..end, + new_chars.iter().collect::() + ); + + let patch = Patch(vec![Edit { + old: start as u32..end as u32, + new: start as u32..start as u32 + new_chars.len() as u32, + }]); + if patches.is_empty() || rng.gen() { + println!("pushing singleton patch: {:?}", patch.0); + patches.push(patch); + } else { + let patch = patches.pop().unwrap().compose(&patch); + println!("composed patches: {:?}", patch.0); + patches.push(patch); + } + final_chars.splice(start..end, new_chars); + } + + println!("final chars: {:?}", final_chars); + println!("final patches: {:?}", patches); + + let mut composed = Patch::default(); + for patch in patches { + println!("composing patches {:?} and {:?}", composed, patch); + composed = composed.compose(&patch); + println!("composed {:?}", composed); + } + println!("composed edits: {:?}", composed); + let mut chars = initial_chars.clone(); + for edit in composed.0 { + chars.splice( + edit.new.start as usize..edit.new.start as usize + edit.old.len(), + final_chars[edit.new.start as usize..edit.new.end as usize] + .iter() + .copied(), + ); + } + + assert_eq!(chars, final_chars); + } +} diff --git a/crates/editor/src/display_map/wrap_map.rs b/crates/editor/src/display_map/wrap_map.rs index 88b629b10c4ebd501443643035d8ca304f8d04da..14e746c2620357e9865b1260e0fd2afa21f7ecb4 100644 --- a/crates/editor/src/display_map/wrap_map.rs +++ b/crates/editor/src/display_map/wrap_map.rs @@ -361,6 +361,55 @@ impl Patch { // self.ranges = new_ranges; } + fn compose2(&self, new: &Self) -> Patch { + let mut composed = Vec::new(); + let mut new_edits = new.0.iter().cloned().peekable(); + let mut old_delta = 0; + let mut new_delta = 0; + for mut old_edit in self.0.iter().cloned() { + let mut next_new_delta = new_delta; + while let Some(mut new_edit) = new_edits.peek().cloned() { + let new_edit_delta = new_edit.new.len() as i32 - new_edit.old.len() as i32; + if new_edit.old.end < old_edit.new.start { + new_edit.old.start = (new_edit.old.start as i32 - old_delta) as u32; + new_edit.old.end = (new_edit.old.end as i32 - old_delta) as u32; + new_edits.next(); + new_delta += new_edit_delta; + next_new_delta += new_edit_delta; + composed.push(new_edit); + } else if new_edit.old.start <= old_edit.new.end { + if new_edit.old.start < old_edit.new.start { + old_edit.old.start -= old_edit.new.start - new_edit.old.start; + old_edit.new.start = new_edit.new.start; + } + if new_edit.old.end > old_edit.new.end { + old_edit.old.end += new_edit.old.end - old_edit.new.end; + old_edit.new.end = new_edit.old.end; + } + + old_edit.new.end = (old_edit.new.end as i32 + new_edit_delta) as u32; + new_edits.next(); + next_new_delta += new_edit_delta; + } else { + break; + } + } + + old_edit.new.start = (old_edit.new.start as i32 + new_delta) as u32; + old_edit.new.end = (old_edit.new.end as i32 + new_delta) as u32; + old_delta += old_edit.new.len() as i32 - old_edit.old.len() as i32; + new_delta = next_new_delta; + composed.push(old_edit); + } + composed.extend(new_edits.map(|mut new_edit| { + new_edit.old.start = (new_edit.old.start as i32 - old_delta) as u32; + new_edit.old.end = (new_edit.old.end as i32 - old_delta) as u32; + new_edit + })); + + Patch(composed) + } + fn invert(&mut self) -> &mut Self { for edit in &mut self.0 { mem::swap(&mut edit.old, &mut edit.new); From 4fecab6d4ba024ac6f3f9e169afcb33b3ca5320b Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 11 Nov 2021 19:42:55 +0100 Subject: [PATCH 013/109] WIP --- crates/editor/src/display_map/patch.rs | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/crates/editor/src/display_map/patch.rs b/crates/editor/src/display_map/patch.rs index 4fe81bc4186a0ba6e002839d07e5a8662d697e1e..208f893ffb2c08458aec78afc654ac28a4bd0acc 100644 --- a/crates/editor/src/display_map/patch.rs +++ b/crates/editor/src/display_map/patch.rs @@ -11,25 +11,28 @@ impl Patch { let mut new_edits = new.0.iter().cloned().peekable(); let mut old_delta = 0; let mut new_delta = 0; + for mut old_edit in self.0.iter().cloned() { + let old_edit_new_start = old_edit.new.start; + let old_edit_new_end = old_edit.new.end; let mut next_new_delta = new_delta; while let Some(mut new_edit) = new_edits.peek().cloned() { let new_edit_delta = new_edit.new.len() as i32 - new_edit.old.len() as i32; - if new_edit.old.end < old_edit.new.start { + if new_edit.old.end < old_edit_new_start { new_edit.old.start = (new_edit.old.start as i32 - old_delta) as u32; new_edit.old.end = (new_edit.old.end as i32 - old_delta) as u32; new_edits.next(); new_delta += new_edit_delta; next_new_delta += new_edit_delta; composed.push(new_edit); - } else if new_edit.old.start <= old_edit.new.end { - if new_edit.old.start < old_edit.new.start { - old_edit.old.start -= old_edit.new.start - new_edit.old.start; - old_edit.new.start = new_edit.new.start; + } else if new_edit.old.start <= old_edit_new_end { + if new_edit.old.start < old_edit_new_start { + old_edit.old.start -= old_edit_new_start - new_edit.old.start; + old_edit.new.start -= old_edit_new_start - new_edit.old.start; } - if new_edit.old.end > old_edit.new.end { - old_edit.old.end += new_edit.old.end - old_edit.new.end; - old_edit.new.end = new_edit.old.end; + if new_edit.old.end > old_edit_new_end { + old_edit.old.end += new_edit.old.end - old_edit_new_end; + old_edit.new.end += new_edit.old.end - old_edit_new_end; } old_edit.new.end = (old_edit.new.end as i32 + new_edit_delta) as u32; @@ -69,7 +72,7 @@ mod tests { use rand::prelude::*; use std::env; - #[gpui::test(iterations = 1000, seed = 28)] + #[gpui::test(iterations = 1000, seed = 131)] fn test_random(mut rng: StdRng) { let operations = env::var("OPERATIONS") .map(|i| i.parse().expect("invalid `OPERATIONS` variable")) From d75f415b25fb00f6e06ac5b71af678e51384a8db Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 11 Nov 2021 19:49:01 +0100 Subject: [PATCH 014/109] WIP --- crates/editor/src/display_map/patch.rs | 97 ++++++++++++++++---------- 1 file changed, 59 insertions(+), 38 deletions(-) diff --git a/crates/editor/src/display_map/patch.rs b/crates/editor/src/display_map/patch.rs index 208f893ffb2c08458aec78afc654ac28a4bd0acc..8efdb90e001fd0d4da977df6343b3e6b537a58f1 100644 --- a/crates/editor/src/display_map/patch.rs +++ b/crates/editor/src/display_map/patch.rs @@ -8,52 +8,73 @@ struct Patch(Vec); impl Patch { fn compose(&self, new: &Self) -> Patch { let mut composed = Vec::new(); - let mut new_edits = new.0.iter().cloned().peekable(); + let mut old_edits = self.0.iter().cloned().peekable(); let mut old_delta = 0; let mut new_delta = 0; - for mut old_edit in self.0.iter().cloned() { - let old_edit_new_start = old_edit.new.start; - let old_edit_new_end = old_edit.new.end; - let mut next_new_delta = new_delta; - while let Some(mut new_edit) = new_edits.peek().cloned() { - let new_edit_delta = new_edit.new.len() as i32 - new_edit.old.len() as i32; - if new_edit.old.end < old_edit_new_start { - new_edit.old.start = (new_edit.old.start as i32 - old_delta) as u32; - new_edit.old.end = (new_edit.old.end as i32 - old_delta) as u32; - new_edits.next(); - new_delta += new_edit_delta; - next_new_delta += new_edit_delta; - composed.push(new_edit); - } else if new_edit.old.start <= old_edit_new_end { - if new_edit.old.start < old_edit_new_start { - old_edit.old.start -= old_edit_new_start - new_edit.old.start; - old_edit.new.start -= old_edit_new_start - new_edit.old.start; + for mut new_edit in new.0.iter().cloned() { + while let Some(mut old_edit) = old_edits.peek().cloned() { + if old_edit.new.end < new_edit.old.start { + old_edit.new.start = (old_edit.new.start as i32 + new_delta) as u32; + old_edit.new.end = (old_edit.new.end as i32 + new_delta) as u32; + old_edits.next(); + composed.push(old_edit); + } else if old_edit.new.start < new_edit.old.end { + if old_edit.new.start < new_edit.old.start { + new_edit.old.start -= new_edit.old.start - old_edit.new.start; + new_edit.new.start -= new_edit.old.start - old_edit.new.start; } - if new_edit.old.end > old_edit_new_end { - old_edit.old.end += new_edit.old.end - old_edit_new_end; - old_edit.new.end += new_edit.old.end - old_edit_new_end; + if old_edit.new.end > new_edit.old.end { + new_edit.old.end += old_edit.new.end - new_edit.old.end; + new_edit.new.end += old_edit.new.end - new_edit.old.end; } - - old_edit.new.end = (old_edit.new.end as i32 + new_edit_delta) as u32; - new_edits.next(); - next_new_delta += new_edit_delta; - } else { - break; } } - - old_edit.new.start = (old_edit.new.start as i32 + new_delta) as u32; - old_edit.new.end = (old_edit.new.end as i32 + new_delta) as u32; - old_delta += old_edit.new.len() as i32 - old_edit.old.len() as i32; - new_delta = next_new_delta; - composed.push(old_edit); } - composed.extend(new_edits.map(|mut new_edit| { - new_edit.old.start = (new_edit.old.start as i32 - old_delta) as u32; - new_edit.old.end = (new_edit.old.end as i32 - old_delta) as u32; - new_edit - })); + + todo!(); + // for mut old_edit in self.0.iter().cloned() { + // let old_edit_new_start = old_edit.new.start; + // let old_edit_new_end = old_edit.new.end; + // let mut next_new_delta = new_delta; + // while let Some(mut new_edit) = new_edits.peek().cloned() { + // let new_edit_delta = new_edit.new.len() as i32 - new_edit.old.len() as i32; + // if new_edit.old.end < old_edit_new_start { + // new_edit.old.start = (new_edit.old.start as i32 - old_delta) as u32; + // new_edit.old.end = (new_edit.old.end as i32 - old_delta) as u32; + // new_edits.next(); + // new_delta += new_edit_delta; + // next_new_delta += new_edit_delta; + // composed.push(new_edit); + // } else if new_edit.old.start <= old_edit_new_end { + // if new_edit.old.start < old_edit_new_start { + // old_edit.old.start -= old_edit_new_start - new_edit.old.start; + // old_edit.new.start -= old_edit_new_start - new_edit.old.start; + // } + // if new_edit.old.end > old_edit_new_end { + // old_edit.old.end += new_edit.old.end - old_edit_new_end; + // old_edit.new.end += new_edit.old.end - old_edit_new_end; + // } + + // old_edit.new.end = (old_edit.new.end as i32 + new_edit_delta) as u32; + // new_edits.next(); + // next_new_delta += new_edit_delta; + // } else { + // break; + // } + // } + + // old_edit.new.start = (old_edit.new.start as i32 + new_delta) as u32; + // old_edit.new.end = (old_edit.new.end as i32 + new_delta) as u32; + // old_delta += old_edit.new.len() as i32 - old_edit.old.len() as i32; + // new_delta = next_new_delta; + // composed.push(old_edit); + // } + // composed.extend(new_edits.map(|mut new_edit| { + // new_edit.old.start = (new_edit.old.start as i32 - old_delta) as u32; + // new_edit.old.end = (new_edit.old.end as i32 - old_delta) as u32; + // new_edit + // })); Patch(composed) } From 1f2eb9ddbc233b5da454f77a4adae30befe37801 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 11 Nov 2021 11:26:50 -0800 Subject: [PATCH 015/109] Add patch unit tests, get composition working for ops <= 3 Co-Authored-By: Nathan Sobo --- crates/editor/src/display_map/patch.rs | 303 ++++++++++++++++++++----- 1 file changed, 248 insertions(+), 55 deletions(-) diff --git a/crates/editor/src/display_map/patch.rs b/crates/editor/src/display_map/patch.rs index 8efdb90e001fd0d4da977df6343b3e6b537a58f1..8da560ea396c07ac5f7110dda18ff049ef990d46 100644 --- a/crates/editor/src/display_map/patch.rs +++ b/crates/editor/src/display_map/patch.rs @@ -2,79 +2,76 @@ use std::mem; type Edit = buffer::Edit; -#[derive(Default, Debug)] +#[derive(Default, Debug, PartialEq, Eq)] struct Patch(Vec); impl Patch { fn compose(&self, new: &Self) -> Patch { - let mut composed = Vec::new(); + let mut composed = Vec::::new(); let mut old_edits = self.0.iter().cloned().peekable(); let mut old_delta = 0; let mut new_delta = 0; + let mut intermediate_start; + let mut intermediate_end = 0; for mut new_edit in new.0.iter().cloned() { - while let Some(mut old_edit) = old_edits.peek().cloned() { - if old_edit.new.end < new_edit.old.start { + let new_edit_delta = new_edit.new.len() as i32 - new_edit.old.len() as i32; + + if let Some(last_edit) = composed.last_mut() { + if intermediate_end >= new_edit.old.start { + if new_edit.old.end > intermediate_end { + last_edit.old.end += new_edit.old.end - intermediate_end; + last_edit.new.end += new_edit.old.end - intermediate_end; + intermediate_end = new_edit.old.end; + } + last_edit.new.end = (last_edit.new.end as i32 + new_edit_delta) as u32; + continue; + } + } + + intermediate_start = new_edit.old.start; + intermediate_end = new_edit.old.end; + new_edit.old.start = (new_edit.old.start as i32 - old_delta) as u32; + new_edit.old.end = (new_edit.old.end as i32 - old_delta) as u32; + + while let Some(old_edit) = old_edits.peek() { + let old_edit_delta = old_edit.new.len() as i32 - old_edit.old.len() as i32; + + if old_edit.new.end < intermediate_start { + let mut old_edit = old_edit.clone(); old_edit.new.start = (old_edit.new.start as i32 + new_delta) as u32; old_edit.new.end = (old_edit.new.end as i32 + new_delta) as u32; - old_edits.next(); + new_edit.old.start = (new_edit.old.start as i32 - old_edit_delta) as u32; + new_edit.old.end = (new_edit.old.end as i32 - old_edit_delta) as u32; composed.push(old_edit); - } else if old_edit.new.start < new_edit.old.end { - if old_edit.new.start < new_edit.old.start { - new_edit.old.start -= new_edit.old.start - old_edit.new.start; - new_edit.new.start -= new_edit.old.start - old_edit.new.start; + } else if old_edit.new.start <= intermediate_end { + if old_edit.new.start < intermediate_start { + new_edit.new.start -= intermediate_start - old_edit.new.start; + new_edit.old.start -= intermediate_start - old_edit.new.start; } - if old_edit.new.end > new_edit.old.end { - new_edit.old.end += old_edit.new.end - new_edit.old.end; - new_edit.new.end += old_edit.new.end - new_edit.old.end; + if old_edit.new.end > intermediate_end { + new_edit.new.end += old_edit.new.end - intermediate_end; + new_edit.old.end += old_edit.new.end - intermediate_end; + intermediate_end = old_edit.new.end; } + new_edit.old.end = (new_edit.old.end as i32 - old_edit_delta) as u32; + } else { + break; } + + old_delta += old_edit_delta; + old_edits.next(); } + + new_delta += new_edit_delta; + composed.push(new_edit); } - todo!(); - // for mut old_edit in self.0.iter().cloned() { - // let old_edit_new_start = old_edit.new.start; - // let old_edit_new_end = old_edit.new.end; - // let mut next_new_delta = new_delta; - // while let Some(mut new_edit) = new_edits.peek().cloned() { - // let new_edit_delta = new_edit.new.len() as i32 - new_edit.old.len() as i32; - // if new_edit.old.end < old_edit_new_start { - // new_edit.old.start = (new_edit.old.start as i32 - old_delta) as u32; - // new_edit.old.end = (new_edit.old.end as i32 - old_delta) as u32; - // new_edits.next(); - // new_delta += new_edit_delta; - // next_new_delta += new_edit_delta; - // composed.push(new_edit); - // } else if new_edit.old.start <= old_edit_new_end { - // if new_edit.old.start < old_edit_new_start { - // old_edit.old.start -= old_edit_new_start - new_edit.old.start; - // old_edit.new.start -= old_edit_new_start - new_edit.old.start; - // } - // if new_edit.old.end > old_edit_new_end { - // old_edit.old.end += new_edit.old.end - old_edit_new_end; - // old_edit.new.end += new_edit.old.end - old_edit_new_end; - // } - - // old_edit.new.end = (old_edit.new.end as i32 + new_edit_delta) as u32; - // new_edits.next(); - // next_new_delta += new_edit_delta; - // } else { - // break; - // } - // } - - // old_edit.new.start = (old_edit.new.start as i32 + new_delta) as u32; - // old_edit.new.end = (old_edit.new.end as i32 + new_delta) as u32; - // old_delta += old_edit.new.len() as i32 - old_edit.old.len() as i32; - // new_delta = next_new_delta; - // composed.push(old_edit); - // } - // composed.extend(new_edits.map(|mut new_edit| { - // new_edit.old.start = (new_edit.old.start as i32 - old_delta) as u32; - // new_edit.old.end = (new_edit.old.end as i32 - old_delta) as u32; - // new_edit - // })); + while let Some(mut old_edit) = old_edits.next() { + old_edit.new.start = (old_edit.new.start as i32 + new_delta) as u32; + old_edit.new.end = (old_edit.new.end as i32 + new_delta) as u32; + composed.push(old_edit); + } Patch(composed) } @@ -93,6 +90,171 @@ mod tests { use rand::prelude::*; use std::env; + #[gpui::test] + fn test_one_disjoint_edit() { + assert_patch_composition( + Patch(vec![Edit { + old: 1..3, + new: 1..4, + }]), + Patch(vec![Edit { + old: 0..0, + new: 0..4, + }]), + Patch(vec![ + Edit { + old: 0..0, + new: 0..4, + }, + Edit { + old: 1..3, + new: 5..8, + }, + ]), + ); + + assert_patch_composition( + Patch(vec![Edit { + old: 1..3, + new: 1..4, + }]), + Patch(vec![Edit { + old: 5..9, + new: 5..7, + }]), + Patch(vec![ + Edit { + old: 1..3, + new: 1..4, + }, + Edit { + old: 4..8, + new: 5..7, + }, + ]), + ); + } + + #[gpui::test] + fn test_one_overlapping_edit() { + assert_patch_composition( + Patch(vec![Edit { + old: 1..3, + new: 1..4, + }]), + Patch(vec![Edit { + old: 3..5, + new: 3..6, + }]), + Patch(vec![Edit { + old: 1..4, + new: 1..6, + }]), + ); + } + + #[gpui::test] + fn test_two_disjoint_and_overlapping() { + assert_patch_composition( + Patch(vec![ + Edit { + old: 1..3, + new: 1..4, + }, + Edit { + old: 8..12, + new: 9..11, + }, + ]), + Patch(vec![ + Edit { + old: 0..0, + new: 0..4, + }, + Edit { + old: 3..10, + new: 7..9, + }, + ]), + Patch(vec![ + Edit { + old: 0..0, + new: 0..4, + }, + Edit { + old: 1..12, + new: 5..10, + }, + ]), + ); + } + + #[gpui::test] + fn test_two_new_edits_overlapping_one_old_edit() { + assert_patch_composition( + Patch(vec![Edit { + old: 0..0, + new: 0..3, + }]), + Patch(vec![ + Edit { + old: 0..0, + new: 0..1, + }, + Edit { + old: 1..2, + new: 2..2, + }, + ]), + Patch(vec![Edit { + old: 0..0, + new: 0..3, + }]), + ); + + assert_patch_composition( + Patch(vec![Edit { + old: 2..3, + new: 2..4, + }]), + Patch(vec![ + Edit { + old: 0..2, + new: 0..1, + }, + Edit { + old: 3..3, + new: 2..5, + }, + ]), + Patch(vec![Edit { + old: 0..3, + new: 0..6, + }]), + ); + + assert_patch_composition( + Patch(vec![Edit { + old: 0..0, + new: 0..2, + }]), + Patch(vec![ + Edit { + old: 0..0, + new: 0..2, + }, + Edit { + old: 2..5, + new: 4..4, + }, + ]), + Patch(vec![Edit { + old: 0..3, + new: 0..4, + }]), + ); + } + #[gpui::test(iterations = 1000, seed = 131)] fn test_random(mut rng: StdRng) { let operations = env::var("OPERATIONS") @@ -159,4 +321,35 @@ mod tests { assert_eq!(chars, final_chars); } + + #[track_caller] + fn assert_patch_composition(old: Patch, new: Patch, composed: Patch) { + let original = ('a'..'z').collect::>(); + let inserted = ('A'..'Z').collect::>(); + + let mut expected = original.clone(); + apply_patch(&mut expected, &old, &inserted); + apply_patch(&mut expected, &new, &inserted); + + let mut actual = original.clone(); + apply_patch(&mut actual, &composed, &expected); + assert_eq!( + actual.into_iter().collect::(), + expected.into_iter().collect::(), + "expected patch is incorrect" + ); + + assert_eq!(old.compose(&new), composed); + } + + fn apply_patch(text: &mut Vec, patch: &Patch, new_text: &[char]) { + for edit in patch.0.iter().rev() { + text.splice( + edit.old.start as usize..edit.old.end as usize, + new_text[edit.new.start as usize..edit.new.end as usize] + .iter() + .copied(), + ); + } + } } From 0159019850f15fa58a1cccad19d4ec0ec1ed9ddb Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 11 Nov 2021 18:28:07 -0800 Subject: [PATCH 016/109] Simplify assertions in randomized patch test, fix some patch bugs --- crates/editor/src/display_map/patch.rs | 160 ++++++++++++++++++------- 1 file changed, 115 insertions(+), 45 deletions(-) diff --git a/crates/editor/src/display_map/patch.rs b/crates/editor/src/display_map/patch.rs index 8da560ea396c07ac5f7110dda18ff049ef990d46..97af438d45314ba63833387ba520d073e1a5d296 100644 --- a/crates/editor/src/display_map/patch.rs +++ b/crates/editor/src/display_map/patch.rs @@ -15,6 +15,8 @@ impl Patch { let mut intermediate_end = 0; for mut new_edit in new.0.iter().cloned() { + eprintln!("edit {:?}", new_edit); + let new_edit_delta = new_edit.new.len() as i32 - new_edit.old.len() as i32; if let Some(last_edit) = composed.last_mut() { @@ -25,6 +27,8 @@ impl Patch { intermediate_end = new_edit.old.end; } last_edit.new.end = (last_edit.new.end as i32 + new_edit_delta) as u32; + new_delta += new_edit_delta; + eprintln!(" merged {:?}", &composed); continue; } } @@ -44,6 +48,7 @@ impl Patch { new_edit.old.start = (new_edit.old.start as i32 - old_edit_delta) as u32; new_edit.old.end = (new_edit.old.end as i32 - old_edit_delta) as u32; composed.push(old_edit); + eprintln!(" pushed preceding {:?}", &composed); } else if old_edit.new.start <= intermediate_end { if old_edit.new.start < intermediate_start { new_edit.new.start -= intermediate_start - old_edit.new.start; @@ -54,6 +59,7 @@ impl Patch { new_edit.old.end += old_edit.new.end - intermediate_end; intermediate_end = old_edit.new.end; } + eprintln!(" expanded w/ intersecting {:?} - {:?}", old_edit, new_edit); new_edit.old.end = (new_edit.old.end as i32 - old_edit_delta) as u32; } else { break; @@ -65,9 +71,25 @@ impl Patch { new_delta += new_edit_delta; composed.push(new_edit); + eprintln!(" pushing {:?}", &composed); } while let Some(mut old_edit) = old_edits.next() { + let old_edit_delta = old_edit.new.len() as i32 - old_edit.old.len() as i32; + + if let Some(last_edit) = composed.last_mut() { + if intermediate_end >= old_edit.new.start { + if old_edit.new.end > intermediate_end { + last_edit.old.end += old_edit.new.end - intermediate_end; + last_edit.new.end += old_edit.new.end - intermediate_end; + intermediate_end = old_edit.new.end; + } + last_edit.old.end = (last_edit.old.end as i32 - old_edit_delta) as u32; + eprintln!(" merged {:?}", &composed); + continue; + } + } + old_edit.new.start = (old_edit.new.start as i32 + new_delta) as u32; old_edit.new.end = (old_edit.new.end as i32 + new_delta) as u32; composed.push(old_edit); @@ -255,71 +277,119 @@ mod tests { ); } - #[gpui::test(iterations = 1000, seed = 131)] + #[gpui::test] + fn test_two_new_edits_touching_one_old_edit() { + assert_patch_composition( + Patch(vec![ + Edit { + old: 2..3, + new: 2..4, + }, + Edit { + old: 7..7, + new: 8..11, + }, + ]), + Patch(vec![ + Edit { + old: 2..3, + new: 2..2, + }, + Edit { + old: 4..4, + new: 3..4, + }, + ]), + Patch(vec![ + Edit { + old: 2..3, + new: 2..4, + }, + Edit { + old: 7..7, + new: 8..11, + }, + ]), + ); + } + + #[gpui::test(iterations = 1000)] fn test_random(mut rng: StdRng) { let operations = env::var("OPERATIONS") .map(|i| i.parse().expect("invalid `OPERATIONS` variable")) - .unwrap_or(3); + .unwrap_or(2); - let initial_chars = (0..rng.gen_range(0..=5)) + let initial_chars = (0..rng.gen_range(0..=10)) .map(|_| rng.gen_range(b'a'..=b'z') as char) .collect::>(); - let mut final_chars = initial_chars.clone(); + println!("initial chars: {:?}", initial_chars); + + // Generate two sequential patches let mut patches = Vec::new(); + let mut expected_chars = initial_chars.clone(); + for i in 0..2 { + println!("patch {}:", i); + + let mut delta = 0i32; + let mut last_edit_end = 0; + let mut edits = Vec::new(); + for _ in 0..operations { + if last_edit_end >= expected_chars.len() { + break; + } - println!("initial chars: {:?}", initial_chars); - for _ in 0..operations { - let end = rng.gen_range(0..=final_chars.len()); - let start = rng.gen_range(0..=end); - let mut len = rng.gen_range(0..=3); - if start == end && len == 0 { - len += 1; - } - let new_chars = (0..len) - .map(|_| rng.gen_range(b'a'..=b'z') as char) - .collect::>(); - println!( - "editing {:?}: {:?}", - start..end, - new_chars.iter().collect::() - ); + let end = rng.gen_range(last_edit_end..=expected_chars.len()); + let start = rng.gen_range(last_edit_end..=end); + let old_len = end - start; - let patch = Patch(vec![Edit { - old: start as u32..end as u32, - new: start as u32..start as u32 + new_chars.len() as u32, - }]); - if patches.is_empty() || rng.gen() { - println!("pushing singleton patch: {:?}", patch.0); - patches.push(patch); - } else { - let patch = patches.pop().unwrap().compose(&patch); - println!("composed patches: {:?}", patch.0); - patches.push(patch); + let mut new_len = rng.gen_range(0..=3); + if start == end && new_len == 0 { + new_len += 1; + } + + last_edit_end = start + new_len + 1; + + let new_chars = (0..new_len) + .map(|_| rng.gen_range(b'A'..=b'Z') as char) + .collect::>(); + println!( + " editing {:?}: {:?}", + start..end, + new_chars.iter().collect::() + ); + edits.push(Edit { + old: (start as i32 - delta) as u32..(end as i32 - delta) as u32, + new: start as u32..(start + new_len) as u32, + }); + expected_chars.splice(start..end, new_chars); + + delta += new_len as i32 - old_len as i32; } - final_chars.splice(start..end, new_chars); + + patches.push(Patch(edits)); } - println!("final chars: {:?}", final_chars); - println!("final patches: {:?}", patches); + println!("old patch: {:?}", &patches[0]); + println!("new patch: {:?}", &patches[1]); + println!("initial chars: {:?}", initial_chars); + println!("final chars: {:?}", expected_chars); - let mut composed = Patch::default(); - for patch in patches { - println!("composing patches {:?} and {:?}", composed, patch); - composed = composed.compose(&patch); - println!("composed {:?}", composed); - } - println!("composed edits: {:?}", composed); - let mut chars = initial_chars.clone(); + // Compose the patches, and verify that it has the same effect as applying the + // two patches separately. + let composed = patches[0].compose(&patches[1]); + println!("composed patch: {:?}", &composed); + + let mut actual_chars = initial_chars.clone(); for edit in composed.0 { - chars.splice( + actual_chars.splice( edit.new.start as usize..edit.new.start as usize + edit.old.len(), - final_chars[edit.new.start as usize..edit.new.end as usize] + expected_chars[edit.new.start as usize..edit.new.end as usize] .iter() .copied(), ); } - assert_eq!(chars, final_chars); + assert_eq!(actual_chars, expected_chars); } #[track_caller] From 84d789b8acbeeb0624f49b9936cbdb22f7b5f572 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Thu, 11 Nov 2021 23:28:45 -0700 Subject: [PATCH 017/109] WIP --- crates/editor/src/display_map/patch.rs | 172 ++++++++++++++++++++++++- 1 file changed, 170 insertions(+), 2 deletions(-) diff --git a/crates/editor/src/display_map/patch.rs b/crates/editor/src/display_map/patch.rs index 97af438d45314ba63833387ba520d073e1a5d296..5167c537c655c5dc1965a390092bc1ea21b49db2 100644 --- a/crates/editor/src/display_map/patch.rs +++ b/crates/editor/src/display_map/patch.rs @@ -1,12 +1,151 @@ -use std::mem; +use std::{iter::Peekable, mem, ops::Range}; type Edit = buffer::Edit; #[derive(Default, Debug, PartialEq, Eq)] struct Patch(Vec); +fn compose_edits(old_edit: &Edit, new_edit: &Edit) -> Edit { + let mut composed = old_edit.clone(); + if old_edit.new.start > new_edit.old.start { + composed.old.start -= old_edit.new.start - new_edit.old.start; + composed.new.start = new_edit.old.start; + } + if new_edit.old.end > old_edit.new.end { + composed.old.end += new_edit.old.end - old_edit.new.end; + composed.new.end = new_edit.old.end; + } + let new_edit_delta = new_edit.new.len() as i32 - new_edit.old.len() as i32; + composed.new.end = (composed.new.end as i32 + new_edit_delta) as u32; + composed +} + impl Patch { fn compose(&self, new: &Self) -> Patch { + enum EditSource { + Old, + New, + } + + let mut composed_edits = Vec::new(); + let mut old_delta = 0; + let mut new_delta = 0; + let mut old_edits = self.0.iter().cloned().peekable(); + let mut new_edits = new.0.iter().cloned().peekable(); + + fn peek_next( + old_edits: &mut Peekable>, + new_edits: &mut Peekable>, + ) -> Option { + match (old_edits.peek(), new_edits.peek()) { + (Some(old_edit), Some(new_edit)) => { + if old_edit.new.start <= new_edit.old.start { + Some(EditSource::Old) + } else { + Some(EditSource::New) + } + } + (Some(_), None) => Some(EditSource::Old), + (None, Some(_)) => Some(EditSource::New), + (None, None) => None, + } + } + + while let Some(source) = peek_next(&mut old_edits, &mut new_edits) { + let mut intermediate_end; + let mut composed_edit; + match source { + EditSource::Old => { + let edit = old_edits.next().unwrap(); + println!("pulling an old edit {:?}", edit); + old_delta += edit_delta(&edit); + intermediate_end = edit.new.end; + composed_edit = edit.clone(); + apply_delta(&mut composed_edit.new, new_delta); + println!(" composed edit: {:?}", composed_edit); + } + EditSource::New => { + let edit = new_edits.next().unwrap(); + println!("pulling a new edit {:?}", edit); + new_delta += edit_delta(&edit); + intermediate_end = edit.old.end; + composed_edit = edit.clone(); + apply_delta(&mut composed_edit.old, -old_delta); + println!(" composed edit: {:?}", composed_edit); + } + } + + while let Some(source) = peek_next(&mut old_edits, &mut new_edits) { + match source { + EditSource::Old => { + if let Some(old_edit) = old_edits.peek() { + if old_edit.new.start <= intermediate_end { + let old_edit = old_edits.next().unwrap(); + println!(" merging with an old edit {:?}", old_edit); + + if old_edit.old.start < composed_edit.old.start { + composed_edit.new.start = + composed_edit.old.start - old_edit.old.start; + composed_edit.old.start = old_edit.old.start; + } + + if old_edit.old.end > composed_edit.old.end { + println!( + " old edit end exceeds composed edit by {:?}", + old_edit.old.end - composed_edit.old.end + ); + composed_edit.new.end += + old_edit.old.end - composed_edit.old.end; + composed_edit.old.end = old_edit.old.end; + intermediate_end = old_edit.new.end; + + println!( + " composed edit after expansion, before delta {:?}", + composed_edit, + ); + } + let edit_delta = edit_delta(&old_edit); + println!(" edit delta is {}", edit_delta); + composed_edit.old.end = + (composed_edit.old.end as i32 - edit_delta) as u32; + println!(" composed edit is now {:?}", composed_edit); + old_delta += edit_delta; + continue; + } + } + break; + } + EditSource::New => { + if let Some(new_edit) = new_edits.peek() { + if new_edit.old.start <= intermediate_end { + let new_edit = new_edits.next().unwrap(); + println!(" merging with a new edit {:?}", new_edit); + if new_edit.old.end > intermediate_end { + let expansion = new_edit.old.end - intermediate_end; + composed_edit.old.end += expansion; + composed_edit.new.end += expansion; + intermediate_end = new_edit.old.end; + } + let edit_delta = edit_delta(&new_edit); + composed_edit.new.end = + (composed_edit.new.end as i32 + edit_delta) as u32; + println!(" composed edit is now {:?}", composed_edit); + new_delta += edit_delta; + continue; + } + } + break; + } + } + } + + composed_edits.push(composed_edit); + } + + Patch(composed_edits) + } + + fn compose1(&self, new: &Self) -> Patch { let mut composed = Vec::::new(); let mut old_edits = self.0.iter().cloned().peekable(); let mut old_delta = 0; @@ -106,6 +245,15 @@ impl Patch { } } +fn edit_delta(edit: &Edit) -> i32 { + edit.new.len() as i32 - edit.old.len() as i32 +} + +fn apply_delta(range: &mut Range, delta: i32) { + range.start = (range.start as i32 + delta) as u32; + range.end = (range.end as i32 + delta) as u32; +} + #[cfg(test)] mod tests { use super::*; @@ -277,6 +425,26 @@ mod tests { ); } + #[test] + fn test_compose_edits() { + assert_eq!( + compose_edits( + &Edit { + old: 3..3, + new: 3..6, + }, + &Edit { + old: 2..7, + new: 2..4, + }, + ), + Edit { + old: 2..4, + new: 2..4 + } + ); + } + #[gpui::test] fn test_two_new_edits_touching_one_old_edit() { assert_patch_composition( @@ -314,7 +482,7 @@ mod tests { } #[gpui::test(iterations = 1000)] - fn test_random(mut rng: StdRng) { + fn test_random_patch_compositions(mut rng: StdRng) { let operations = env::var("OPERATIONS") .map(|i| i.parse().expect("invalid `OPERATIONS` variable")) .unwrap_or(2); From 47ad9baebc5a95ea0bd085e2d71d10dde3706bd9 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 12 Nov 2021 00:03:47 -0700 Subject: [PATCH 018/109] wip --- crates/editor/src/display_map/patch.rs | 157 ++++++++----------------- 1 file changed, 48 insertions(+), 109 deletions(-) diff --git a/crates/editor/src/display_map/patch.rs b/crates/editor/src/display_map/patch.rs index 5167c537c655c5dc1965a390092bc1ea21b49db2..2c0d001f766efab367deef196686885aba750755 100644 --- a/crates/editor/src/display_map/patch.rs +++ b/crates/editor/src/display_map/patch.rs @@ -20,129 +20,68 @@ fn compose_edits(old_edit: &Edit, new_edit: &Edit) -> Edit { composed } +fn merge_edits(left_edit: &mut Option, right_edit: Edit) { + if let Some(left_edit) = left_edit.as_mut() { + left_edit.old.end = right_edit.old.end; + left_edit.new.end = right_edit.new.end; + } else { + *left_edit = Some(right_edit); + } +} + impl Patch { fn compose(&self, new: &Self) -> Patch { - enum EditSource { - Old, - New, - } - - let mut composed_edits = Vec::new(); - let mut old_delta = 0; - let mut new_delta = 0; + let mut composed = Vec::new(); let mut old_edits = self.0.iter().cloned().peekable(); let mut new_edits = new.0.iter().cloned().peekable(); - - fn peek_next( - old_edits: &mut Peekable>, - new_edits: &mut Peekable>, - ) -> Option { - match (old_edits.peek(), new_edits.peek()) { - (Some(old_edit), Some(new_edit)) => { - if old_edit.new.start <= new_edit.old.start { - Some(EditSource::Old) - } else { - Some(EditSource::New) + let old_delta = 0; + + 'outer: loop { + // Find the next edit in the intermediate coordinate space + // Then merge together all old and new edits that intersect this edit in the intermediate coordinate space. + let mut pending_old_edit = None; + let mut pending_new_edit = None; + let mut intermediate_end = u32::MAX; + loop { + match (old_edits.peek(), new_edits.peek()) { + (None, None) => break, + (Some(edit), None) => { + if edit.new.start <= intermediate_end { + intermediate_end = edit.new.end; + merge_edits(&mut pending_old_edit, old_edits.next().unwrap()) + } else { + break; + } } - } - (Some(_), None) => Some(EditSource::Old), - (None, Some(_)) => Some(EditSource::New), - (None, None) => None, - } - } - - while let Some(source) = peek_next(&mut old_edits, &mut new_edits) { - let mut intermediate_end; - let mut composed_edit; - match source { - EditSource::Old => { - let edit = old_edits.next().unwrap(); - println!("pulling an old edit {:?}", edit); - old_delta += edit_delta(&edit); - intermediate_end = edit.new.end; - composed_edit = edit.clone(); - apply_delta(&mut composed_edit.new, new_delta); - println!(" composed edit: {:?}", composed_edit); - } - EditSource::New => { - let edit = new_edits.next().unwrap(); - println!("pulling a new edit {:?}", edit); - new_delta += edit_delta(&edit); - intermediate_end = edit.old.end; - composed_edit = edit.clone(); - apply_delta(&mut composed_edit.old, -old_delta); - println!(" composed edit: {:?}", composed_edit); - } - } - - while let Some(source) = peek_next(&mut old_edits, &mut new_edits) { - match source { - EditSource::Old => { - if let Some(old_edit) = old_edits.peek() { - if old_edit.new.start <= intermediate_end { - let old_edit = old_edits.next().unwrap(); - println!(" merging with an old edit {:?}", old_edit); - - if old_edit.old.start < composed_edit.old.start { - composed_edit.new.start = - composed_edit.old.start - old_edit.old.start; - composed_edit.old.start = old_edit.old.start; - } - - if old_edit.old.end > composed_edit.old.end { - println!( - " old edit end exceeds composed edit by {:?}", - old_edit.old.end - composed_edit.old.end - ); - composed_edit.new.end += - old_edit.old.end - composed_edit.old.end; - composed_edit.old.end = old_edit.old.end; - intermediate_end = old_edit.new.end; - - println!( - " composed edit after expansion, before delta {:?}", - composed_edit, - ); - } - let edit_delta = edit_delta(&old_edit); - println!(" edit delta is {}", edit_delta); - composed_edit.old.end = - (composed_edit.old.end as i32 - edit_delta) as u32; - println!(" composed edit is now {:?}", composed_edit); - old_delta += edit_delta; - continue; - } + (None, Some(edit)) => { + if edit.old.start <= intermediate_end { + intermediate_end = edit.old.end; + merge_edits(&mut pending_new_edit, new_edits.next().unwrap()); } - break; } - EditSource::New => { - if let Some(new_edit) = new_edits.peek() { - if new_edit.old.start <= intermediate_end { - let new_edit = new_edits.next().unwrap(); - println!(" merging with a new edit {:?}", new_edit); - if new_edit.old.end > intermediate_end { - let expansion = new_edit.old.end - intermediate_end; - composed_edit.old.end += expansion; - composed_edit.new.end += expansion; - intermediate_end = new_edit.old.end; - } - let edit_delta = edit_delta(&new_edit); - composed_edit.new.end = - (composed_edit.new.end as i32 + edit_delta) as u32; - println!(" composed edit is now {:?}", composed_edit); - new_delta += edit_delta; - continue; - } + (Some(old_edit), Some(new_edit)) => { + if old_edit.new.start <= new_edit.old.start { + intermediate_end = old_edit.new.end; + merge_edits(&mut pending_old_edit, old_edits.next().unwrap()) + } else { + intermediate_end = new_edit.old.end; + merge_edits(&mut pending_new_edit, new_edits.next().unwrap()); } - break; } } } - composed_edits.push(composed_edit); + match (pending_old_edit, pending_new_edit) { + (None, None) => break, + (None, Some(new_edit)) => todo!(), + (Some(old_edit), None) => todo!(), + (Some(old_edit), Some(new_edit)) => { + let composed = compose_edits(&old_edit, &new_edit); + } + } } - Patch(composed_edits) + Patch(composed) } fn compose1(&self, new: &Self) -> Patch { From 068aa1adb3c12c88314e9044a08532d5b198e850 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 12 Nov 2021 00:20:39 -0700 Subject: [PATCH 019/109] WIP --- crates/editor/src/display_map/patch.rs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/crates/editor/src/display_map/patch.rs b/crates/editor/src/display_map/patch.rs index 2c0d001f766efab367deef196686885aba750755..b059e65da797f5404977913c70df307f4d13787c 100644 --- a/crates/editor/src/display_map/patch.rs +++ b/crates/editor/src/display_map/patch.rs @@ -61,11 +61,19 @@ impl Patch { } (Some(old_edit), Some(new_edit)) => { if old_edit.new.start <= new_edit.old.start { - intermediate_end = old_edit.new.end; - merge_edits(&mut pending_old_edit, old_edits.next().unwrap()) + if old_edit.new.start <= intermediate_end { + intermediate_end = old_edit.new.end; + merge_edits(&mut pending_old_edit, old_edits.next().unwrap()) + } else { + break; + } } else { - intermediate_end = new_edit.old.end; - merge_edits(&mut pending_new_edit, new_edits.next().unwrap()); + if new_edit.old.start <= intermediate_end { + intermediate_end = new_edit.old.end; + merge_edits(&mut pending_new_edit, new_edits.next().unwrap()); + } else { + break; + } } } } From 6e882bcd0289cf8ea0534fed679878bdfc13351f Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 12 Nov 2021 08:55:17 +0100 Subject: [PATCH 020/109] Avoid composing edits together for now --- crates/editor/src/display_map/wrap_map.rs | 215 ++++------------------ 1 file changed, 40 insertions(+), 175 deletions(-) diff --git a/crates/editor/src/display_map/wrap_map.rs b/crates/editor/src/display_map/wrap_map.rs index 14e746c2620357e9865b1260e0fd2afa21f7ecb4..f507e501d397f4e496681fe4b69a6fcfed41d444 100644 --- a/crates/editor/src/display_map/wrap_map.rs +++ b/crates/editor/src/display_map/wrap_map.rs @@ -14,16 +14,13 @@ pub type Edit = buffer::Edit; pub struct WrapMap { snapshot: Snapshot, pending_edits: VecDeque<(TabSnapshot, Vec)>, - interpolated_edits: Patch, - edits_since_sync: Patch, + interpolated_edits: Vec>, + edits_since_sync: Vec>, wrap_width: Option, background_task: Option>, font: (FontId, f32), } -#[derive(Default)] -struct Patch(Vec); - impl Entity for WrapMap { type Event = (); } @@ -106,13 +103,10 @@ impl WrapMap { tab_snapshot: TabSnapshot, edits: Vec, cx: &mut ModelContext, - ) -> (Snapshot, Vec) { + ) -> (Snapshot, Vec>) { self.pending_edits.push_back((tab_snapshot, edits)); self.flush_edits(cx); - ( - self.snapshot.clone(), - mem::take(&mut self.edits_since_sync).0, - ) + (self.snapshot.clone(), mem::take(&mut self.edits_since_sync)) } pub fn set_font(&mut self, font_id: FontId, font_size: f32, cx: &mut ModelContext) { @@ -143,7 +137,7 @@ impl WrapMap { let mut line_wrapper = font_cache.line_wrapper(font_id, font_size); let tab_snapshot = new_snapshot.tab_snapshot.clone(); let range = TabPoint::zero()..tab_snapshot.max_point(); - new_snapshot + let edits = new_snapshot .update( tab_snapshot, &[TabEdit { @@ -154,22 +148,28 @@ impl WrapMap { &mut line_wrapper, ) .await; - new_snapshot + (new_snapshot, edits) }); match cx .background() .block_with_timeout(Duration::from_millis(5), task) { - Ok(snapshot) => { + Ok((snapshot, edits)) => { self.snapshot = snapshot; + self.edits_since_sync.push(edits); cx.notify(); } Err(wrap_task) => { self.background_task = Some(cx.spawn(|this, mut cx| async move { - let snapshot = wrap_task.await; + let (snapshot, edits) = wrap_task.await; this.update(&mut cx, |this, cx| { this.snapshot = snapshot; + for mut edits in this.interpolated_edits.drain(..) { + invert(&mut edits); + this.edits_since_sync.push(edits); + } + this.edits_since_sync.push(edits); this.background_task = None; this.flush_edits(cx); cx.notify(); @@ -178,6 +178,7 @@ impl WrapMap { } } } else { + let old_rows = self.snapshot.transforms.summary().output.lines.row + 1; self.snapshot.transforms = SumTree::new(); let summary = self.snapshot.tab_snapshot.text_summary(); if !summary.lines.is_zero() { @@ -185,6 +186,14 @@ impl WrapMap { .transforms .push(Transform::isomorphic(summary), &()); } + let new_rows = self.snapshot.transforms.summary().output.lines.row + 1; + self.snapshot.interpolated = false; + self.pending_edits.clear(); + self.interpolated_edits.clear(); + self.edits_since_sync.push(vec![Edit { + old: 0..old_rows, + new: 0..new_rows, + }]); } } @@ -214,12 +223,12 @@ impl WrapMap { let update_task = cx.background().spawn(async move { let mut line_wrapper = font_cache.line_wrapper(font_id, font_size); - let mut output_edits = Patch::default(); + let mut output_edits = Vec::new(); for (tab_snapshot, edits) in pending_edits { let wrap_edits = snapshot .update(tab_snapshot, &edits, wrap_width, &mut line_wrapper) .await; - output_edits.compose(&wrap_edits); + output_edits.push(wrap_edits); } (snapshot, output_edits) }); @@ -230,16 +239,18 @@ impl WrapMap { { Ok((snapshot, output_edits)) => { self.snapshot = snapshot; - self.edits_since_sync.compose(&output_edits); + self.edits_since_sync.extend(output_edits); } Err(update_task) => { self.background_task = Some(cx.spawn(|this, mut cx| async move { let (snapshot, output_edits) = update_task.await; this.update(&mut cx, |this, cx| { this.snapshot = snapshot; - this.edits_since_sync - .compose(mem::take(&mut this.interpolated_edits).invert()) - .compose(&output_edits); + for mut edits in this.interpolated_edits.drain(..) { + invert(&mut edits); + this.edits_since_sync.push(edits); + } + this.edits_since_sync.extend(output_edits); this.background_task = None; this.flush_edits(cx); cx.notify(); @@ -257,8 +268,8 @@ impl WrapMap { to_remove_len += 1; } else { let interpolated_edits = self.snapshot.interpolate(tab_snapshot.clone(), &edits); - self.edits_since_sync.compose(&interpolated_edits); - self.interpolated_edits.compose(&interpolated_edits); + self.edits_since_sync.push(interpolated_edits.clone()); + self.interpolated_edits.push(interpolated_edits); } } @@ -268,156 +279,6 @@ impl WrapMap { } } -impl Patch { - fn compose(&mut self, new: &Self) -> &mut Self { - let mut new_edits = new.0.iter().peekable(); - let mut old_delta = 0; - let mut new_delta = 0; - let mut ix = 0; - 'outer: while ix < self.0.len() { - let old_edit = &mut self.0[ix]; - old_edit.new.start = (old_edit.new.start as i32 + new_delta) as u32; - old_edit.new.end = (old_edit.new.end as i32 + new_delta) as u32; - - while let Some(new_edit) = new_edits.peek() { - let new_edit_delta = new_edit.new.len() as i32 - new_edit.old.len() as i32; - if new_edit.old.end < self.0[ix].new.start { - let new_edit = new_edits.next().unwrap().clone(); - self.0.insert(ix, new_edit); - ix += 1; - - let old_edit = &mut self.0[ix]; - old_edit.new.start = (old_edit.new.start as i32 + new_edit_delta) as u32; - old_edit.new.end = (old_edit.new.end as i32 + new_edit_delta) as u32; - new_delta += new_edit_delta; - } else if new_edit.old.start <= self.0[ix].new.end { - let new_edit = new_edits.next().unwrap().clone(); - let old_edit = &mut self.0[ix]; - if new_edit.old.start < old_edit.new.start { - old_edit.old.start -= old_edit.new.start - new_edit.old.start; - old_edit.new.start = new_edit.new.start; - } - if new_edit.old.end > old_edit.new.end { - old_edit.old.end += new_edit.old.end - old_edit.new.end; - old_edit.new.end = new_edit.old.end; - } - - old_edit.new.end = (old_edit.new.end as i32 + new_edit_delta) as u32; - new_delta += new_edit_delta; - if old_edit.old.len() == 0 && old_edit.new.len() == 0 { - self.0.remove(ix); - continue 'outer; - } - } else { - break; - } - } - - ix += 1; - } - - self.0.extend(new_edits.cloned()); - - self - // let mut other_ranges = edit.ranges.iter().peekable(); - // let mut new_ranges = Vec::new(); - // let insertion_len = edit.new_text.as_ref().map_or(0, |t| t.len()); - // let mut delta = 0; - - // for mut self_range in self.ranges.iter().cloned() { - // self_range.start += delta; - // self_range.end += delta; - - // while let Some(other_range) = other_ranges.peek() { - // let mut other_range = (*other_range).clone(); - // other_range.start += delta; - // other_range.end += delta; - - // if other_range.start <= self_range.end { - // other_ranges.next().unwrap(); - // delta += insertion_len; - - // if other_range.end < self_range.start { - // new_ranges.push(other_range.start..other_range.end + insertion_len); - // self_range.start += insertion_len; - // self_range.end += insertion_len; - // } else { - // self_range.start = cmp::min(self_range.start, other_range.start); - // self_range.end = cmp::max(self_range.end, other_range.end) + insertion_len; - // } - // } else { - // break; - // } - // } - - // new_ranges.push(self_range); - // } - - // for other_range in other_ranges { - // new_ranges.push(other_range.start + delta..other_range.end + delta + insertion_len); - // delta += insertion_len; - // } - - // self.ranges = new_ranges; - } - - fn compose2(&self, new: &Self) -> Patch { - let mut composed = Vec::new(); - let mut new_edits = new.0.iter().cloned().peekable(); - let mut old_delta = 0; - let mut new_delta = 0; - for mut old_edit in self.0.iter().cloned() { - let mut next_new_delta = new_delta; - while let Some(mut new_edit) = new_edits.peek().cloned() { - let new_edit_delta = new_edit.new.len() as i32 - new_edit.old.len() as i32; - if new_edit.old.end < old_edit.new.start { - new_edit.old.start = (new_edit.old.start as i32 - old_delta) as u32; - new_edit.old.end = (new_edit.old.end as i32 - old_delta) as u32; - new_edits.next(); - new_delta += new_edit_delta; - next_new_delta += new_edit_delta; - composed.push(new_edit); - } else if new_edit.old.start <= old_edit.new.end { - if new_edit.old.start < old_edit.new.start { - old_edit.old.start -= old_edit.new.start - new_edit.old.start; - old_edit.new.start = new_edit.new.start; - } - if new_edit.old.end > old_edit.new.end { - old_edit.old.end += new_edit.old.end - old_edit.new.end; - old_edit.new.end = new_edit.old.end; - } - - old_edit.new.end = (old_edit.new.end as i32 + new_edit_delta) as u32; - new_edits.next(); - next_new_delta += new_edit_delta; - } else { - break; - } - } - - old_edit.new.start = (old_edit.new.start as i32 + new_delta) as u32; - old_edit.new.end = (old_edit.new.end as i32 + new_delta) as u32; - old_delta += old_edit.new.len() as i32 - old_edit.old.len() as i32; - new_delta = next_new_delta; - composed.push(old_edit); - } - composed.extend(new_edits.map(|mut new_edit| { - new_edit.old.start = (new_edit.old.start as i32 - old_delta) as u32; - new_edit.old.end = (new_edit.old.end as i32 - old_delta) as u32; - new_edit - })); - - Patch(composed) - } - - fn invert(&mut self) -> &mut Self { - for edit in &mut self.0 { - mem::swap(&mut edit.old, &mut edit.new); - } - self - } -} - impl Snapshot { fn new(tab_snapshot: TabSnapshot) -> Self { let mut transforms = SumTree::new(); @@ -432,7 +293,7 @@ impl Snapshot { } } - fn interpolate(&mut self, new_tab_snapshot: TabSnapshot, edits: &[TabEdit]) -> Patch { + fn interpolate(&mut self, new_tab_snapshot: TabSnapshot, edits: &[TabEdit]) -> Vec { let mut new_transforms; if edits.is_empty() { new_transforms = self.transforms.clone(); @@ -497,7 +358,7 @@ impl Snapshot { edits: &[TabEdit], wrap_width: f32, line_wrapper: &mut LineWrapper, - ) -> Patch { + ) -> Vec { #[derive(Debug)] struct RowEdit { old_rows: Range, @@ -1066,7 +927,11 @@ impl<'a> sum_tree::Dimension<'a, TransformSummary> for WrapPoint { } } -fn compose(prev: &mut Vec, next: &[Edit]) {} +fn invert(edits: &mut Vec) { + for edit in edits { + mem::swap(&mut edit.old, &mut edit.new); + } +} #[cfg(test)] mod tests { From f49c9db423a4b941171989125a59163a5a3c351a Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 12 Nov 2021 15:07:28 +0100 Subject: [PATCH 021/109] Make `Patch::compose` work --- crates/editor/src/display_map/patch.rs | 370 ++++++++++++------------- 1 file changed, 171 insertions(+), 199 deletions(-) diff --git a/crates/editor/src/display_map/patch.rs b/crates/editor/src/display_map/patch.rs index b059e65da797f5404977913c70df307f4d13787c..6d67be629c36d4641741eb03245597121cffcae1 100644 --- a/crates/editor/src/display_map/patch.rs +++ b/crates/editor/src/display_map/patch.rs @@ -1,206 +1,177 @@ -use std::{iter::Peekable, mem, ops::Range}; +use std::{cmp, iter::Peekable, mem, ops::Range}; type Edit = buffer::Edit; #[derive(Default, Debug, PartialEq, Eq)] struct Patch(Vec); -fn compose_edits(old_edit: &Edit, new_edit: &Edit) -> Edit { - let mut composed = old_edit.clone(); - if old_edit.new.start > new_edit.old.start { - composed.old.start -= old_edit.new.start - new_edit.old.start; - composed.new.start = new_edit.old.start; - } - if new_edit.old.end > old_edit.new.end { - composed.old.end += new_edit.old.end - old_edit.new.end; - composed.new.end = new_edit.old.end; - } - let new_edit_delta = new_edit.new.len() as i32 - new_edit.old.len() as i32; - composed.new.end = (composed.new.end as i32 + new_edit_delta) as u32; - composed -} - -fn merge_edits(left_edit: &mut Option, right_edit: Edit) { - if let Some(left_edit) = left_edit.as_mut() { - left_edit.old.end = right_edit.old.end; - left_edit.new.end = right_edit.new.end; - } else { - *left_edit = Some(right_edit); - } -} - impl Patch { - fn compose(&self, new: &Self) -> Patch { - let mut composed = Vec::new(); - let mut old_edits = self.0.iter().cloned().peekable(); - let mut new_edits = new.0.iter().cloned().peekable(); - let old_delta = 0; - - 'outer: loop { - // Find the next edit in the intermediate coordinate space - // Then merge together all old and new edits that intersect this edit in the intermediate coordinate space. - let mut pending_old_edit = None; - let mut pending_new_edit = None; - let mut intermediate_end = u32::MAX; - loop { - match (old_edits.peek(), new_edits.peek()) { - (None, None) => break, - (Some(edit), None) => { - if edit.new.start <= intermediate_end { - intermediate_end = edit.new.end; - merge_edits(&mut pending_old_edit, old_edits.next().unwrap()) + fn compose(&self, other: &Self) -> Self { + let mut old_edits_iter = self.0.iter().cloned().peekable(); + let mut new_edits_iter = other.0.iter().cloned().peekable(); + let mut composed = Patch(Vec::new()); + + let mut old_start = 0; + let mut new_start = 0; + loop { + match (old_edits_iter.peek_mut(), new_edits_iter.peek_mut()) { + (None, None) => break, + (Some(old_edit), None) => { + let catchup = old_edit.old.start - old_start; + old_start += catchup; + new_start += catchup; + + let old_end = old_start + old_edit.old.len() as u32; + let new_end = new_start + old_edit.new.len() as u32; + composed.push(Edit { + old: old_start..old_end, + new: new_start..new_end, + }); + old_start = old_end; + new_start = new_end; + old_edits_iter.next(); + } + (None, Some(new_edit)) => { + let catchup = new_edit.new.start - new_start; + old_start += catchup; + new_start += catchup; + + let old_end = old_start + new_edit.old.len() as u32; + let new_end = new_start + new_edit.new.len() as u32; + composed.push(Edit { + old: old_start..old_end, + new: new_start..new_end, + }); + old_start = old_end; + new_start = new_end; + new_edits_iter.next(); + } + (Some(old_edit), Some(new_edit)) => { + if old_edit.new.end < new_edit.old.start { + let catchup = old_edit.old.start - old_start; + old_start += catchup; + new_start += catchup; + + let old_end = old_start + old_edit.old.len() as u32; + let new_end = new_start + old_edit.new.len() as u32; + composed.push(Edit { + old: old_start..old_end, + new: new_start..new_end, + }); + old_start = old_end; + new_start = new_end; + old_edits_iter.next(); + continue; + } else if new_edit.old.end < old_edit.new.start { + let catchup = new_edit.new.start - new_start; + old_start += catchup; + new_start += catchup; + + let old_end = old_start + new_edit.old.len() as u32; + let new_end = new_start + new_edit.new.len() as u32; + composed.push(Edit { + old: old_start..old_end, + new: new_start..new_end, + }); + old_start = old_end; + new_start = new_end; + new_edits_iter.next(); + } else { + if old_edit.new.start < new_edit.old.start { + let catchup = old_edit.old.start - old_start; + old_start += catchup; + new_start += catchup; + + let overshoot = new_edit.old.start - old_edit.new.start; + let old_end = cmp::min(old_start + overshoot, old_edit.old.end); + let new_end = new_start + overshoot; + composed.push(Edit { + old: old_start..old_end, + new: new_start..new_end, + }); + + old_edit.old.start += overshoot; + old_edit.new.start += overshoot; + old_start = old_end; + new_start = new_end; } else { - break; - } - } - (None, Some(edit)) => { - if edit.old.start <= intermediate_end { - intermediate_end = edit.old.end; - merge_edits(&mut pending_new_edit, new_edits.next().unwrap()); + let catchup = new_edit.new.start - new_start; + old_start += catchup; + new_start += catchup; + + let overshoot = old_edit.new.start - new_edit.old.start; + let old_end = old_start + overshoot; + let new_end = cmp::min(new_start + overshoot, new_edit.new.end); + composed.push(Edit { + old: old_start..old_end, + new: new_start..new_end, + }); + + new_edit.old.start += overshoot; + new_edit.new.start += overshoot; + old_start = old_end; + new_start = new_end; } - } - (Some(old_edit), Some(new_edit)) => { - if old_edit.new.start <= new_edit.old.start { - if old_edit.new.start <= intermediate_end { - intermediate_end = old_edit.new.end; - merge_edits(&mut pending_old_edit, old_edits.next().unwrap()) - } else { - break; - } + + if old_edit.new.end > new_edit.old.end { + let old_len = + cmp::min(old_edit.old.len() as u32, new_edit.old.len() as u32); + + let old_end = old_start + old_len; + let new_end = new_start + new_edit.new.len() as u32; + composed.push(Edit { + old: old_start..old_end, + new: new_start..new_end, + }); + + old_edit.old.start = old_end; + old_edit.new.start = new_edit.old.end; + old_start = old_end; + new_start = new_end; + new_edits_iter.next(); } else { - if new_edit.old.start <= intermediate_end { - intermediate_end = new_edit.old.end; - merge_edits(&mut pending_new_edit, new_edits.next().unwrap()); - } else { - break; - } + let new_len = + cmp::min(old_edit.new.len() as u32, new_edit.new.len() as u32); + + let old_end = old_start + old_edit.old.len() as u32; + let new_end = new_start + new_len; + composed.push(Edit { + old: old_start..old_end, + new: new_start..new_end, + }); + + new_edit.old.start = old_edit.new.end; + new_edit.new.start = new_end; + old_start = old_end; + new_start = new_end; + old_edits_iter.next(); } } } } - - match (pending_old_edit, pending_new_edit) { - (None, None) => break, - (None, Some(new_edit)) => todo!(), - (Some(old_edit), None) => todo!(), - (Some(old_edit), Some(new_edit)) => { - let composed = compose_edits(&old_edit, &new_edit); - } - } } - Patch(composed) + composed } - fn compose1(&self, new: &Self) -> Patch { - let mut composed = Vec::::new(); - let mut old_edits = self.0.iter().cloned().peekable(); - let mut old_delta = 0; - let mut new_delta = 0; - let mut intermediate_start; - let mut intermediate_end = 0; - - for mut new_edit in new.0.iter().cloned() { - eprintln!("edit {:?}", new_edit); - - let new_edit_delta = new_edit.new.len() as i32 - new_edit.old.len() as i32; - - if let Some(last_edit) = composed.last_mut() { - if intermediate_end >= new_edit.old.start { - if new_edit.old.end > intermediate_end { - last_edit.old.end += new_edit.old.end - intermediate_end; - last_edit.new.end += new_edit.old.end - intermediate_end; - intermediate_end = new_edit.old.end; - } - last_edit.new.end = (last_edit.new.end as i32 + new_edit_delta) as u32; - new_delta += new_edit_delta; - eprintln!(" merged {:?}", &composed); - continue; - } - } - - intermediate_start = new_edit.old.start; - intermediate_end = new_edit.old.end; - new_edit.old.start = (new_edit.old.start as i32 - old_delta) as u32; - new_edit.old.end = (new_edit.old.end as i32 - old_delta) as u32; - - while let Some(old_edit) = old_edits.peek() { - let old_edit_delta = old_edit.new.len() as i32 - old_edit.old.len() as i32; - - if old_edit.new.end < intermediate_start { - let mut old_edit = old_edit.clone(); - old_edit.new.start = (old_edit.new.start as i32 + new_delta) as u32; - old_edit.new.end = (old_edit.new.end as i32 + new_delta) as u32; - new_edit.old.start = (new_edit.old.start as i32 - old_edit_delta) as u32; - new_edit.old.end = (new_edit.old.end as i32 - old_edit_delta) as u32; - composed.push(old_edit); - eprintln!(" pushed preceding {:?}", &composed); - } else if old_edit.new.start <= intermediate_end { - if old_edit.new.start < intermediate_start { - new_edit.new.start -= intermediate_start - old_edit.new.start; - new_edit.old.start -= intermediate_start - old_edit.new.start; - } - if old_edit.new.end > intermediate_end { - new_edit.new.end += old_edit.new.end - intermediate_end; - new_edit.old.end += old_edit.new.end - intermediate_end; - intermediate_end = old_edit.new.end; - } - eprintln!(" expanded w/ intersecting {:?} - {:?}", old_edit, new_edit); - new_edit.old.end = (new_edit.old.end as i32 - old_edit_delta) as u32; - } else { - break; - } - - old_delta += old_edit_delta; - old_edits.next(); - } - - new_delta += new_edit_delta; - composed.push(new_edit); - eprintln!(" pushing {:?}", &composed); + fn push(&mut self, edit: Edit) { + if edit.old.len() == 0 && edit.new.len() == 0 { + return; } - while let Some(mut old_edit) = old_edits.next() { - let old_edit_delta = old_edit.new.len() as i32 - old_edit.old.len() as i32; - - if let Some(last_edit) = composed.last_mut() { - if intermediate_end >= old_edit.new.start { - if old_edit.new.end > intermediate_end { - last_edit.old.end += old_edit.new.end - intermediate_end; - last_edit.new.end += old_edit.new.end - intermediate_end; - intermediate_end = old_edit.new.end; - } - last_edit.old.end = (last_edit.old.end as i32 - old_edit_delta) as u32; - eprintln!(" merged {:?}", &composed); - continue; - } + if let Some(last) = self.0.last_mut() { + if last.old.end >= edit.old.start { + last.old.end = edit.old.end; + last.new.end = edit.new.end; + } else { + self.0.push(edit); } - - old_edit.new.start = (old_edit.new.start as i32 + new_delta) as u32; - old_edit.new.end = (old_edit.new.end as i32 + new_delta) as u32; - composed.push(old_edit); - } - - Patch(composed) - } - - fn invert(&mut self) -> &mut Self { - for edit in &mut self.0 { - mem::swap(&mut edit.old, &mut edit.new); + } else { + self.0.push(edit); } - self } } -fn edit_delta(edit: &Edit) -> i32 { - edit.new.len() as i32 - edit.old.len() as i32 -} - -fn apply_delta(range: &mut Range, delta: i32) { - range.start = (range.start as i32 + delta) as u32; - range.end = (range.end as i32 + delta) as u32; -} - #[cfg(test)] mod tests { use super::*; @@ -372,25 +343,25 @@ mod tests { ); } - #[test] - fn test_compose_edits() { - assert_eq!( - compose_edits( - &Edit { - old: 3..3, - new: 3..6, - }, - &Edit { - old: 2..7, - new: 2..4, - }, - ), - Edit { - old: 2..4, - new: 2..4 - } - ); - } + // #[test] + // fn test_compose_edits() { + // assert_eq!( + // compose_edits( + // &Edit { + // old: 3..3, + // new: 3..6, + // }, + // &Edit { + // old: 2..7, + // new: 2..4, + // }, + // ), + // Edit { + // old: 2..4, + // new: 2..4 + // } + // ); + // } #[gpui::test] fn test_two_new_edits_touching_one_old_edit() { @@ -428,11 +399,11 @@ mod tests { ); } - #[gpui::test(iterations = 1000)] + #[gpui::test(iterations = 100, seed = 1)] fn test_random_patch_compositions(mut rng: StdRng) { let operations = env::var("OPERATIONS") .map(|i| i.parse().expect("invalid `OPERATIONS` variable")) - .unwrap_or(2); + .unwrap_or(5); let initial_chars = (0..rng.gen_range(0..=10)) .map(|_| rng.gen_range(b'a'..=b'z') as char) @@ -448,6 +419,7 @@ mod tests { let mut delta = 0i32; let mut last_edit_end = 0; let mut edits = Vec::new(); + for _ in 0..operations { if last_edit_end >= expected_chars.len() { break; From b2aab0c773edf2dff1a59f5b2c48dabc0c88cf86 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 12 Nov 2021 15:18:55 +0100 Subject: [PATCH 022/109] :art: --- crates/editor/src/display_map/patch.rs | 49 ++++++++++++++++++-------- 1 file changed, 34 insertions(+), 15 deletions(-) diff --git a/crates/editor/src/display_map/patch.rs b/crates/editor/src/display_map/patch.rs index 6d67be629c36d4641741eb03245597121cffcae1..835c9f4c5c847e7b798248519b5af891acf9fa72 100644 --- a/crates/editor/src/display_map/patch.rs +++ b/crates/editor/src/display_map/patch.rs @@ -1,12 +1,16 @@ -use std::{cmp, iter::Peekable, mem, ops::Range}; +use std::{cmp, mem, slice}; type Edit = buffer::Edit; #[derive(Default, Debug, PartialEq, Eq)] -struct Patch(Vec); +pub struct Patch(Vec); impl Patch { - fn compose(&self, other: &Self) -> Self { + pub unsafe fn new_unchecked(edits: Vec) -> Self { + Self(edits) + } + + pub fn compose(&self, other: &Self) -> Self { let mut old_edits_iter = self.0.iter().cloned().peekable(); let mut new_edits_iter = other.0.iter().cloned().peekable(); let mut composed = Patch(Vec::new()); @@ -61,7 +65,6 @@ impl Patch { old_start = old_end; new_start = new_end; old_edits_iter.next(); - continue; } else if new_edit.old.end < old_edit.new.start { let catchup = new_edit.new.start - new_start; old_start += catchup; @@ -114,10 +117,8 @@ impl Patch { } if old_edit.new.end > new_edit.old.end { - let old_len = - cmp::min(old_edit.old.len() as u32, new_edit.old.len() as u32); - - let old_end = old_start + old_len; + let old_end = old_start + + cmp::min(old_edit.old.len() as u32, new_edit.old.len() as u32); let new_end = new_start + new_edit.new.len() as u32; composed.push(Edit { old: old_start..old_end, @@ -130,11 +131,9 @@ impl Patch { new_start = new_end; new_edits_iter.next(); } else { - let new_len = - cmp::min(old_edit.new.len() as u32, new_edit.new.len() as u32); - let old_end = old_start + old_edit.old.len() as u32; - let new_end = new_start + new_len; + let new_end = new_start + + cmp::min(old_edit.new.len() as u32, new_edit.new.len() as u32); composed.push(Edit { old: old_start..old_end, new: new_start..new_end, @@ -154,6 +153,17 @@ impl Patch { composed } + pub fn invert(&mut self) -> &mut Self { + for edit in &mut self.0 { + mem::swap(&mut edit.old, &mut edit.new); + } + self + } + + pub fn clear(&mut self) { + self.0.clear(); + } + fn push(&mut self, edit: Edit) { if edit.old.len() == 0 && edit.new.len() == 0 { return; @@ -172,6 +182,15 @@ impl Patch { } } +impl<'a> IntoIterator for &'a Patch { + type Item = &'a Edit; + type IntoIter = slice::Iter<'a, Edit>; + + fn into_iter(self) -> Self::IntoIter { + self.0.iter() + } +} + #[cfg(test)] mod tests { use super::*; @@ -399,13 +418,13 @@ mod tests { ); } - #[gpui::test(iterations = 100, seed = 1)] + #[gpui::test(iterations = 100)] fn test_random_patch_compositions(mut rng: StdRng) { let operations = env::var("OPERATIONS") .map(|i| i.parse().expect("invalid `OPERATIONS` variable")) - .unwrap_or(5); + .unwrap_or(20); - let initial_chars = (0..rng.gen_range(0..=10)) + let initial_chars = (0..rng.gen_range(0..=100)) .map(|_| rng.gen_range(b'a'..=b'z') as char) .collect::>(); println!("initial chars: {:?}", initial_chars); From b9c459e800ca1862cc62fa26af8686625408a5a9 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 12 Nov 2021 17:00:44 +0100 Subject: [PATCH 023/109] Use `log::info` instead of `println` in patch randomized tests --- crates/editor/src/display_map/patch.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/crates/editor/src/display_map/patch.rs b/crates/editor/src/display_map/patch.rs index 835c9f4c5c847e7b798248519b5af891acf9fa72..bbcb2fc05e5575bb1cf41c1514a39cff56d5150c 100644 --- a/crates/editor/src/display_map/patch.rs +++ b/crates/editor/src/display_map/patch.rs @@ -427,13 +427,13 @@ mod tests { let initial_chars = (0..rng.gen_range(0..=100)) .map(|_| rng.gen_range(b'a'..=b'z') as char) .collect::>(); - println!("initial chars: {:?}", initial_chars); + log::info!("initial chars: {:?}", initial_chars); // Generate two sequential patches let mut patches = Vec::new(); let mut expected_chars = initial_chars.clone(); for i in 0..2 { - println!("patch {}:", i); + log::info!("patch {}:", i); let mut delta = 0i32; let mut last_edit_end = 0; @@ -458,7 +458,7 @@ mod tests { let new_chars = (0..new_len) .map(|_| rng.gen_range(b'A'..=b'Z') as char) .collect::>(); - println!( + log::info!( " editing {:?}: {:?}", start..end, new_chars.iter().collect::() @@ -475,15 +475,15 @@ mod tests { patches.push(Patch(edits)); } - println!("old patch: {:?}", &patches[0]); - println!("new patch: {:?}", &patches[1]); - println!("initial chars: {:?}", initial_chars); - println!("final chars: {:?}", expected_chars); + log::info!("old patch: {:?}", &patches[0]); + log::info!("new patch: {:?}", &patches[1]); + log::info!("initial chars: {:?}", initial_chars); + log::info!("final chars: {:?}", expected_chars); // Compose the patches, and verify that it has the same effect as applying the // two patches separately. let composed = patches[0].compose(&patches[1]); - println!("composed patch: {:?}", &composed); + log::info!("composed patch: {:?}", &composed); let mut actual_chars = initial_chars.clone(); for edit in composed.0 { From fe786f33666ef8a63d8ab3e9c30e328c31a79c91 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 12 Nov 2021 17:01:57 +0100 Subject: [PATCH 024/109] Init `env_logger` in the `editor` crate for tests only --- Cargo.lock | 2 ++ crates/editor/Cargo.toml | 2 ++ crates/editor/src/test.rs | 6 ++++++ 3 files changed, 10 insertions(+) diff --git a/Cargo.lock b/Cargo.lock index 3d70d53b05eddc194203581b3bbbcb28d5ab4531..64f13c6540885c6b027658d121a65202a9ba6437 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1622,6 +1622,8 @@ dependencies = [ "anyhow", "buffer", "clock", + "ctor", + "env_logger", "gpui", "language", "lazy_static", diff --git a/crates/editor/Cargo.toml b/crates/editor/Cargo.toml index 59ed90d460558efcfdb9c478342144ca123e66f8..7f5ffec97ee79f7bb5761feb521ef80fefc7e26c 100644 --- a/crates/editor/Cargo.toml +++ b/crates/editor/Cargo.toml @@ -31,6 +31,8 @@ smol = "1.2" buffer = { path = "../buffer", features = ["test-support"] } language = { path = "../language", features = ["test-support"] } gpui = { path = "../gpui", features = ["test-support"] } +ctor = "0.1" +env_logger = "0.8" rand = "0.8" unindent = "0.1.7" tree-sitter = "0.19" diff --git a/crates/editor/src/test.rs b/crates/editor/src/test.rs index 97e7f5a08c23d193afa88c17dd8bd7019f0c30bc..33fafc3f54c4ecb2b7011f4ebf2783f97698c999 100644 --- a/crates/editor/src/test.rs +++ b/crates/editor/src/test.rs @@ -2,6 +2,12 @@ use gpui::{Entity, ModelHandle}; use smol::channel; use std::marker::PhantomData; +#[cfg(test)] +#[ctor::ctor] +fn init_logger() { + env_logger::init(); +} + pub fn sample_text(rows: usize, cols: usize) -> String { let mut text = String::new(); for row in 0..rows { From 4e32fabfdce75c634eab03158fc3d969b7a61f05 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 12 Nov 2021 17:02:51 +0100 Subject: [PATCH 025/109] Add text manipulation facilities to `Rope` for test purposes --- crates/buffer/src/rope.rs | 21 +++++++++++++++++---- crates/editor/src/display_map/fold_map.rs | 2 +- crates/language/src/lib.rs | 2 +- 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/crates/buffer/src/rope.rs b/crates/buffer/src/rope.rs index 5498c403bf5986117ef89cdc0621a4d6c6809f2f..d5bba2ef2e172a7c057c26ea4dc7c4d9ebc877bd 100644 --- a/crates/buffer/src/rope.rs +++ b/crates/buffer/src/rope.rs @@ -3,7 +3,7 @@ use crate::PointUtf16; use super::Point; use arrayvec::ArrayString; use smallvec::SmallVec; -use std::{cmp, ops::Range, str}; +use std::{cmp, fmt, ops::Range, str}; use sum_tree::{Bias, Dimension, SumTree}; #[cfg(test)] @@ -38,6 +38,16 @@ impl Rope { self.check_invariants(); } + pub fn replace(&mut self, range: Range, text: &str) { + let mut new_rope = Rope::new(); + let mut cursor = self.cursor(0); + new_rope.append(cursor.slice(range.start)); + cursor.seek_forward(range.end); + new_rope.push(text); + new_rope.append(cursor.suffix()); + *self = new_rope; + } + pub fn push(&mut self, text: &str) { let mut new_chunks = SmallVec::<[_; 16]>::new(); let mut new_chunk = ArrayString::new(); @@ -236,9 +246,12 @@ impl<'a> From<&'a str> for Rope { } } -impl Into for Rope { - fn into(self) -> String { - self.chunks().collect() +impl fmt::Display for Rope { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + for chunk in self.chunks() { + write!(f, "{}", chunk)?; + } + Ok(()) } } diff --git a/crates/editor/src/display_map/fold_map.rs b/crates/editor/src/display_map/fold_map.rs index 8a5b4c55846e638f8326269d17f291723f5bb162..a76fd2e5085e85576fb0eed2c708ccb6d4b12f22 100644 --- a/crates/editor/src/display_map/fold_map.rs +++ b/crates/editor/src/display_map/fold_map.rs @@ -1352,7 +1352,7 @@ mod tests { } let buffer = map.buffer.read(cx).snapshot(); - let mut expected_text: String = buffer.text().into(); + let mut expected_text: String = buffer.text().to_string(); let mut expected_buffer_rows = Vec::new(); let mut next_row = buffer.max_point().row; for fold_range in map.merged_fold_ranges(cx.as_ref()).into_iter().rev() { diff --git a/crates/language/src/lib.rs b/crates/language/src/lib.rs index f7e5016fc9b23bcc1cb97e057349887097d4bd05..00c63e1df69db8a558c52560dbf4c746a4110b40 100644 --- a/crates/language/src/lib.rs +++ b/crates/language/src/lib.rs @@ -439,7 +439,7 @@ impl Buffer { uri, Default::default(), snapshot.version as i32, - snapshot.buffer_snapshot.text().into(), + snapshot.buffer_snapshot.text().to_string(), ), }, ) From 3f11b8af56d15b41b62dac8fcd935ec9d0b4704d Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 12 Nov 2021 17:05:49 +0100 Subject: [PATCH 026/109] Maintain row edits since last sync in `WrapMap` --- crates/editor/src/display_map/wrap_map.rs | 225 ++++++++++++++++------ 1 file changed, 162 insertions(+), 63 deletions(-) diff --git a/crates/editor/src/display_map/wrap_map.rs b/crates/editor/src/display_map/wrap_map.rs index f507e501d397f4e496681fe4b69a6fcfed41d444..b7d1063980e4b50a9745d7ac173cfdf09ed2b80d 100644 --- a/crates/editor/src/display_map/wrap_map.rs +++ b/crates/editor/src/display_map/wrap_map.rs @@ -1,5 +1,6 @@ use super::{ fold_map, + patch::Patch, tab_map::{self, Edit as TabEdit, Snapshot as TabSnapshot, TabPoint, TextSummary}, }; use gpui::{fonts::FontId, text_layout::LineWrapper, Entity, ModelContext, Task}; @@ -14,8 +15,8 @@ pub type Edit = buffer::Edit; pub struct WrapMap { snapshot: Snapshot, pending_edits: VecDeque<(TabSnapshot, Vec)>, - interpolated_edits: Vec>, - edits_since_sync: Vec>, + interpolated_edits: Patch, + edits_since_sync: Patch, wrap_width: Option, background_task: Option>, font: (FontId, f32), @@ -89,7 +90,7 @@ impl WrapMap { background_task: None, }; this.set_wrap_width(wrap_width, cx); - + mem::take(&mut this.edits_since_sync); this } @@ -103,7 +104,7 @@ impl WrapMap { tab_snapshot: TabSnapshot, edits: Vec, cx: &mut ModelContext, - ) -> (Snapshot, Vec>) { + ) -> (Snapshot, Patch) { self.pending_edits.push_back((tab_snapshot, edits)); self.flush_edits(cx); (self.snapshot.clone(), mem::take(&mut self.edits_since_sync)) @@ -128,6 +129,8 @@ impl WrapMap { fn rewrap(&mut self, cx: &mut ModelContext) { self.background_task.take(); + self.interpolated_edits.clear(); + self.pending_edits.clear(); if let Some(wrap_width) = self.wrap_width { let mut new_snapshot = self.snapshot.clone(); @@ -157,7 +160,7 @@ impl WrapMap { { Ok((snapshot, edits)) => { self.snapshot = snapshot; - self.edits_since_sync.push(edits); + self.edits_since_sync = self.edits_since_sync.compose(&edits); cx.notify(); } Err(wrap_task) => { @@ -165,11 +168,10 @@ impl WrapMap { let (snapshot, edits) = wrap_task.await; this.update(&mut cx, |this, cx| { this.snapshot = snapshot; - for mut edits in this.interpolated_edits.drain(..) { - invert(&mut edits); - this.edits_since_sync.push(edits); - } - this.edits_since_sync.push(edits); + this.edits_since_sync = this + .edits_since_sync + .compose(mem::take(&mut this.interpolated_edits).invert()) + .compose(&edits); this.background_task = None; this.flush_edits(cx); cx.notify(); @@ -188,12 +190,12 @@ impl WrapMap { } let new_rows = self.snapshot.transforms.summary().output.lines.row + 1; self.snapshot.interpolated = false; - self.pending_edits.clear(); - self.interpolated_edits.clear(); - self.edits_since_sync.push(vec![Edit { - old: 0..old_rows, - new: 0..new_rows, - }]); + self.edits_since_sync = self.edits_since_sync.compose(&unsafe { + Patch::new_unchecked(vec![Edit { + old: 0..old_rows, + new: 0..new_rows, + }]) + }); } } @@ -223,14 +225,14 @@ impl WrapMap { let update_task = cx.background().spawn(async move { let mut line_wrapper = font_cache.line_wrapper(font_id, font_size); - let mut output_edits = Vec::new(); - for (tab_snapshot, edits) in pending_edits { + let mut edits = Patch::default(); + for (tab_snapshot, tab_edits) in pending_edits { let wrap_edits = snapshot - .update(tab_snapshot, &edits, wrap_width, &mut line_wrapper) + .update(tab_snapshot, &tab_edits, wrap_width, &mut line_wrapper) .await; - output_edits.push(wrap_edits); + edits = edits.compose(&wrap_edits); } - (snapshot, output_edits) + (snapshot, edits) }); match cx @@ -239,18 +241,17 @@ impl WrapMap { { Ok((snapshot, output_edits)) => { self.snapshot = snapshot; - self.edits_since_sync.extend(output_edits); + self.edits_since_sync = self.edits_since_sync.compose(&output_edits); } Err(update_task) => { self.background_task = Some(cx.spawn(|this, mut cx| async move { - let (snapshot, output_edits) = update_task.await; + let (snapshot, edits) = update_task.await; this.update(&mut cx, |this, cx| { this.snapshot = snapshot; - for mut edits in this.interpolated_edits.drain(..) { - invert(&mut edits); - this.edits_since_sync.push(edits); - } - this.edits_since_sync.extend(output_edits); + this.edits_since_sync = this + .edits_since_sync + .compose(mem::take(&mut this.interpolated_edits).invert()) + .compose(&edits); this.background_task = None; this.flush_edits(cx); cx.notify(); @@ -268,8 +269,8 @@ impl WrapMap { to_remove_len += 1; } else { let interpolated_edits = self.snapshot.interpolate(tab_snapshot.clone(), &edits); - self.edits_since_sync.push(interpolated_edits.clone()); - self.interpolated_edits.push(interpolated_edits); + self.edits_since_sync = self.edits_since_sync.compose(&interpolated_edits); + self.interpolated_edits = self.interpolated_edits.compose(&interpolated_edits); } } @@ -293,17 +294,20 @@ impl Snapshot { } } - fn interpolate(&mut self, new_tab_snapshot: TabSnapshot, edits: &[TabEdit]) -> Vec { + fn interpolate(&mut self, new_tab_snapshot: TabSnapshot, tab_edits: &[TabEdit]) -> Patch { let mut new_transforms; - if edits.is_empty() { + if tab_edits.is_empty() { new_transforms = self.transforms.clone(); } else { let mut old_cursor = self.transforms.cursor::(); - let mut edits = edits.into_iter().peekable(); - new_transforms = - old_cursor.slice(&edits.peek().unwrap().old_lines.start, Bias::Right, &()); + let mut tab_edits_iter = tab_edits.iter().peekable(); + new_transforms = old_cursor.slice( + &tab_edits_iter.peek().unwrap().old_lines.start, + Bias::Right, + &(), + ); - while let Some(edit) = edits.next() { + while let Some(edit) = tab_edits_iter.next() { if edit.new_lines.start > TabPoint::from(new_transforms.summary().input.lines) { let summary = new_tab_snapshot.text_summary_for_range( TabPoint::from(new_transforms.summary().input.lines)..edit.new_lines.start, @@ -318,7 +322,7 @@ impl Snapshot { } old_cursor.seek_forward(&edit.old_lines.end, Bias::Right, &()); - if let Some(next_edit) = edits.peek() { + if let Some(next_edit) = tab_edits_iter.peek() { if next_edit.old_lines.start > old_cursor.end(&()) { if old_cursor.end(&()) > edit.old_lines.end { let summary = self @@ -345,39 +349,44 @@ impl Snapshot { } } - self.transforms = new_transforms; - self.tab_snapshot = new_tab_snapshot; - self.interpolated = true; + let old_snapshot = mem::replace( + self, + Snapshot { + tab_snapshot: new_tab_snapshot, + transforms: new_transforms, + interpolated: true, + }, + ); self.check_invariants(); - todo!() + old_snapshot.compute_edits(tab_edits, self) } async fn update( &mut self, new_tab_snapshot: TabSnapshot, - edits: &[TabEdit], + tab_edits: &[TabEdit], wrap_width: f32, line_wrapper: &mut LineWrapper, - ) -> Vec { + ) -> Patch { #[derive(Debug)] struct RowEdit { old_rows: Range, new_rows: Range, } - let mut edits = edits.into_iter().peekable(); + let mut tab_edits_iter = tab_edits.into_iter().peekable(); let mut row_edits = Vec::new(); - while let Some(edit) = edits.next() { + while let Some(edit) = tab_edits_iter.next() { let mut row_edit = RowEdit { old_rows: edit.old_lines.start.row()..edit.old_lines.end.row() + 1, new_rows: edit.new_lines.start.row()..edit.new_lines.end.row() + 1, }; - while let Some(next_edit) = edits.peek() { + while let Some(next_edit) = tab_edits_iter.peek() { if next_edit.old_lines.start.row() <= row_edit.old_rows.end { row_edit.old_rows.end = next_edit.old_lines.end.row() + 1; row_edit.new_rows.end = next_edit.new_lines.end.row() + 1; - edits.next(); + tab_edits_iter.next(); } else { break; } @@ -484,11 +493,52 @@ impl Snapshot { } } - self.transforms = new_transforms; - self.tab_snapshot = new_tab_snapshot; - self.interpolated = false; + let old_snapshot = mem::replace( + self, + Snapshot { + tab_snapshot: new_tab_snapshot, + transforms: new_transforms, + interpolated: false, + }, + ); self.check_invariants(); - todo!() + old_snapshot.compute_edits(tab_edits, self) + } + + fn compute_edits(&self, tab_edits: &[TabEdit], new_snapshot: &Snapshot) -> Patch { + let mut wrap_edits = Vec::new(); + let mut old_cursor = self.transforms.cursor::(); + let mut new_cursor = new_snapshot.transforms.cursor::(); + for mut tab_edit in tab_edits.iter().cloned() { + tab_edit.old_lines.start.0.column = 0; + tab_edit.old_lines.end.0 += Point::new(1, 0); + tab_edit.new_lines.start.0.column = 0; + tab_edit.new_lines.end.0 += Point::new(1, 0); + + old_cursor.seek(&tab_edit.old_lines.start, Bias::Right, &()); + let mut old_start = old_cursor.start().output.lines; + old_start += tab_edit.old_lines.start.0 - old_cursor.start().input.lines; + + old_cursor.seek(&tab_edit.old_lines.end, Bias::Right, &()); + let mut old_end = old_cursor.start().output.lines; + old_end += tab_edit.old_lines.end.0 - old_cursor.start().input.lines; + + new_cursor.seek(&tab_edit.new_lines.start, Bias::Right, &()); + let mut new_start = new_cursor.start().output.lines; + new_start += tab_edit.new_lines.start.0 - new_cursor.start().input.lines; + + new_cursor.seek(&tab_edit.new_lines.end, Bias::Right, &()); + let mut new_end = new_cursor.start().output.lines; + new_end += tab_edit.new_lines.end.0 - new_cursor.start().input.lines; + + wrap_edits.push(Edit { + old: old_start.row..old_end.row, + new: new_start.row..new_end.row, + }); + } + + consolidate_wrap_edits(&mut wrap_edits); + unsafe { Patch::new_unchecked(wrap_edits) } } pub fn chunks_at(&self, wrap_row: u32) -> Chunks { @@ -921,6 +971,12 @@ impl<'a> sum_tree::Dimension<'a, TransformSummary> for TabPoint { } } +impl<'a> sum_tree::SeekTarget<'a, TransformSummary, TransformSummary> for TabPoint { + fn cmp(&self, cursor_location: &TransformSummary, _: &()) -> std::cmp::Ordering { + Ord::cmp(&self.0, &cursor_location.input.lines) + } +} + impl<'a> sum_tree::Dimension<'a, TransformSummary> for WrapPoint { fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) { self.0 += summary.output.lines; @@ -933,6 +989,21 @@ fn invert(edits: &mut Vec) { } } +fn consolidate_wrap_edits(edits: &mut Vec) { + let mut i = 1; + while i < edits.len() { + let edit = edits[i].clone(); + let prev_edit = &mut edits[i - 1]; + if prev_edit.old.end >= edit.old.start { + prev_edit.old.end = edit.old.end; + prev_edit.new.end = edit.new.end; + edits.remove(i); + continue; + } + i += 1; + } +} + #[cfg(test)] mod tests { use super::*; @@ -940,9 +1011,10 @@ mod tests { display_map::{fold_map::FoldMap, tab_map::TabMap}, test::Observer, }; + use buffer::Rope; use language::{Buffer, RandomCharIter}; use rand::prelude::*; - use std::env; + use std::{cmp, env}; #[gpui::test(iterations = 100)] async fn test_random_wraps(mut cx: gpui::TestAppContext, mut rng: StdRng) { @@ -999,9 +1071,9 @@ mod tests { notifications.recv().await.unwrap(); } - let (snapshot, _) = + let (initial_snapshot, _) = wrap_map.update(&mut cx, |map, cx| map.sync(tabs_snapshot, Vec::new(), cx)); - let actual_text = snapshot.text(); + let actual_text = initial_snapshot.text(); assert_eq!( actual_text, expected_text, "unwrapped text is: {:?}", @@ -1009,6 +1081,7 @@ mod tests { ); log::info!("Wrapped text: {:?}", actual_text); + let mut edits = Vec::new(); for _i in 0..operations { match rng.gen_range(0..=100) { 0..=19 => { @@ -1021,14 +1094,15 @@ mod tests { wrap_map.update(&mut cx, |map, cx| map.set_wrap_width(wrap_width, cx)); } 20..=39 => { - for (folds_snapshot, edits) in + for (folds_snapshot, fold_edits) in cx.read(|cx| fold_map.randomly_mutate(&mut rng, cx)) { - let (tabs_snapshot, edits) = tab_map.sync(folds_snapshot, edits); - let (mut snapshot, _) = - wrap_map.update(&mut cx, |map, cx| map.sync(tabs_snapshot, edits, cx)); + let (tabs_snapshot, tab_edits) = tab_map.sync(folds_snapshot, fold_edits); + let (mut snapshot, wrap_edits) = wrap_map + .update(&mut cx, |map, cx| map.sync(tabs_snapshot, tab_edits, cx)); snapshot.check_invariants(); snapshot.verify_chunks(&mut rng); + edits.push((snapshot, wrap_edits)); } } _ => { @@ -1040,21 +1114,22 @@ mod tests { "Unwrapped text (no folds): {:?}", buffer.read_with(&cx, |buf, _| buf.text()) ); - let (folds_snapshot, edits) = cx.read(|cx| fold_map.read(cx)); + let (folds_snapshot, fold_edits) = cx.read(|cx| fold_map.read(cx)); log::info!( "Unwrapped text (unexpanded tabs): {:?}", folds_snapshot.text() ); - let (tabs_snapshot, edits) = tab_map.sync(folds_snapshot, edits); + let (tabs_snapshot, tab_edits) = tab_map.sync(folds_snapshot, fold_edits); log::info!("Unwrapped text (expanded tabs): {:?}", tabs_snapshot.text()); let unwrapped_text = tabs_snapshot.text(); let expected_text = wrap_text(&unwrapped_text, wrap_width, &mut line_wrapper); - let (mut snapshot, _) = wrap_map.update(&mut cx, |map, cx| { - map.sync(tabs_snapshot.clone(), edits, cx) + let (mut snapshot, wrap_edits) = wrap_map.update(&mut cx, |map, cx| { + map.sync(tabs_snapshot.clone(), tab_edits, cx) }); snapshot.check_invariants(); snapshot.verify_chunks(&mut rng); + edits.push((snapshot, wrap_edits)); if wrap_map.read_with(&cx, |map, _| map.is_rewrapping()) && rng.gen_bool(0.4) { log::info!("Waiting for wrapping to finish"); @@ -1064,12 +1139,13 @@ mod tests { } if !wrap_map.read_with(&cx, |map, _| map.is_rewrapping()) { - let (mut wrapped_snapshot, _) = + let (mut wrapped_snapshot, wrap_edits) = wrap_map.update(&mut cx, |map, cx| map.sync(tabs_snapshot, Vec::new(), cx)); let actual_text = wrapped_snapshot.text(); log::info!("Wrapping finished: {:?}", actual_text); wrapped_snapshot.check_invariants(); wrapped_snapshot.verify_chunks(&mut rng); + edits.push((wrapped_snapshot, wrap_edits)); assert_eq!( actual_text, expected_text, "unwrapped text is: {:?}", @@ -1077,6 +1153,29 @@ mod tests { ); } } + + let mut initial_text = Rope::from(initial_snapshot.text().as_str()); + for (snapshot, edits) in edits { + let snapshot_text = Rope::from(snapshot.text().as_str()); + for edit in &edits { + let old_start = initial_text.point_to_offset(Point::new(edit.new.start, 0)); + let old_end = initial_text.point_to_offset(cmp::min( + Point::new(edit.new.start + edit.old.len() as u32, 0), + initial_text.max_point(), + )); + let new_start = snapshot_text.point_to_offset(Point::new(edit.new.start, 0)); + let new_end = snapshot_text.point_to_offset(cmp::min( + Point::new(edit.new.end, 0), + snapshot_text.max_point(), + )); + let new_text = snapshot_text + .chunks_in_range(new_start..new_end) + .collect::(); + + initial_text.replace(old_start..old_end, &new_text); + } + assert_eq!(initial_text.to_string(), snapshot_text.to_string()); + } } fn wrap_text( From d721c2ba4b7da9e33dedffc69a37c0ef05df787c Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 12 Nov 2021 17:24:25 +0100 Subject: [PATCH 027/109] Remove redundant code path in `Patch::compose` --- crates/editor/src/display_map/patch.rs | 185 ++++++++++++------------- 1 file changed, 85 insertions(+), 100 deletions(-) diff --git a/crates/editor/src/display_map/patch.rs b/crates/editor/src/display_map/patch.rs index bbcb2fc05e5575bb1cf41c1514a39cff56d5150c..da78a7e2dfa184c4fa51d65fb67b0131052ed706 100644 --- a/crates/editor/src/display_map/patch.rs +++ b/crates/editor/src/display_map/patch.rs @@ -18,9 +18,13 @@ impl Patch { let mut old_start = 0; let mut new_start = 0; loop { - match (old_edits_iter.peek_mut(), new_edits_iter.peek_mut()) { - (None, None) => break, - (Some(old_edit), None) => { + let old_edit = old_edits_iter.peek_mut(); + let new_edit = new_edits_iter.peek_mut(); + + // Push the old edit if its new end is before the new edit's old start. + if let Some(old_edit) = old_edit.as_ref() { + let new_edit = new_edit.as_ref(); + if new_edit.map_or(true, |new_edit| old_edit.new.end < new_edit.old.start) { let catchup = old_edit.old.start - old_start; old_start += catchup; new_start += catchup; @@ -34,8 +38,14 @@ impl Patch { old_start = old_end; new_start = new_end; old_edits_iter.next(); + continue; } - (None, Some(new_edit)) => { + } + + // Push the new edit if its old end is before the old edit's new start. + if let Some(new_edit) = new_edit.as_ref() { + let old_edit = old_edit.as_ref(); + if old_edit.map_or(true, |old_edit| new_edit.old.end < old_edit.new.start) { let catchup = new_edit.new.start - new_start; old_start += catchup; new_start += catchup; @@ -49,104 +59,79 @@ impl Patch { old_start = old_end; new_start = new_end; new_edits_iter.next(); + continue; + } + } + + // If we still have edits by this point then they must intersect, so we compose them. + if let Some((old_edit, new_edit)) = old_edit.zip(new_edit) { + if old_edit.new.start < new_edit.old.start { + let catchup = old_edit.old.start - old_start; + old_start += catchup; + new_start += catchup; + + let overshoot = new_edit.old.start - old_edit.new.start; + let old_end = cmp::min(old_start + overshoot, old_edit.old.end); + let new_end = new_start + overshoot; + composed.push(Edit { + old: old_start..old_end, + new: new_start..new_end, + }); + + old_edit.old.start += overshoot; + old_edit.new.start += overshoot; + old_start = old_end; + new_start = new_end; + } else { + let catchup = new_edit.new.start - new_start; + old_start += catchup; + new_start += catchup; + + let overshoot = old_edit.new.start - new_edit.old.start; + let old_end = old_start + overshoot; + let new_end = cmp::min(new_start + overshoot, new_edit.new.end); + composed.push(Edit { + old: old_start..old_end, + new: new_start..new_end, + }); + + new_edit.old.start += overshoot; + new_edit.new.start += overshoot; + old_start = old_end; + new_start = new_end; } - (Some(old_edit), Some(new_edit)) => { - if old_edit.new.end < new_edit.old.start { - let catchup = old_edit.old.start - old_start; - old_start += catchup; - new_start += catchup; - - let old_end = old_start + old_edit.old.len() as u32; - let new_end = new_start + old_edit.new.len() as u32; - composed.push(Edit { - old: old_start..old_end, - new: new_start..new_end, - }); - old_start = old_end; - new_start = new_end; - old_edits_iter.next(); - } else if new_edit.old.end < old_edit.new.start { - let catchup = new_edit.new.start - new_start; - old_start += catchup; - new_start += catchup; - - let old_end = old_start + new_edit.old.len() as u32; - let new_end = new_start + new_edit.new.len() as u32; - composed.push(Edit { - old: old_start..old_end, - new: new_start..new_end, - }); - old_start = old_end; - new_start = new_end; - new_edits_iter.next(); - } else { - if old_edit.new.start < new_edit.old.start { - let catchup = old_edit.old.start - old_start; - old_start += catchup; - new_start += catchup; - - let overshoot = new_edit.old.start - old_edit.new.start; - let old_end = cmp::min(old_start + overshoot, old_edit.old.end); - let new_end = new_start + overshoot; - composed.push(Edit { - old: old_start..old_end, - new: new_start..new_end, - }); - - old_edit.old.start += overshoot; - old_edit.new.start += overshoot; - old_start = old_end; - new_start = new_end; - } else { - let catchup = new_edit.new.start - new_start; - old_start += catchup; - new_start += catchup; - - let overshoot = old_edit.new.start - new_edit.old.start; - let old_end = old_start + overshoot; - let new_end = cmp::min(new_start + overshoot, new_edit.new.end); - composed.push(Edit { - old: old_start..old_end, - new: new_start..new_end, - }); - - new_edit.old.start += overshoot; - new_edit.new.start += overshoot; - old_start = old_end; - new_start = new_end; - } - - if old_edit.new.end > new_edit.old.end { - let old_end = old_start - + cmp::min(old_edit.old.len() as u32, new_edit.old.len() as u32); - let new_end = new_start + new_edit.new.len() as u32; - composed.push(Edit { - old: old_start..old_end, - new: new_start..new_end, - }); - - old_edit.old.start = old_end; - old_edit.new.start = new_edit.old.end; - old_start = old_end; - new_start = new_end; - new_edits_iter.next(); - } else { - let old_end = old_start + old_edit.old.len() as u32; - let new_end = new_start - + cmp::min(old_edit.new.len() as u32, new_edit.new.len() as u32); - composed.push(Edit { - old: old_start..old_end, - new: new_start..new_end, - }); - - new_edit.old.start = old_edit.new.end; - new_edit.new.start = new_end; - old_start = old_end; - new_start = new_end; - old_edits_iter.next(); - } - } + + if old_edit.new.end > new_edit.old.end { + let old_end = + old_start + cmp::min(old_edit.old.len() as u32, new_edit.old.len() as u32); + let new_end = new_start + new_edit.new.len() as u32; + composed.push(Edit { + old: old_start..old_end, + new: new_start..new_end, + }); + + old_edit.old.start = old_end; + old_edit.new.start = new_edit.old.end; + old_start = old_end; + new_start = new_end; + new_edits_iter.next(); + } else { + let old_end = old_start + old_edit.old.len() as u32; + let new_end = + new_start + cmp::min(old_edit.new.len() as u32, new_edit.new.len() as u32); + composed.push(Edit { + old: old_start..old_end, + new: new_start..new_end, + }); + + new_edit.old.start = old_edit.new.end; + new_edit.new.start = new_end; + old_start = old_end; + new_start = new_end; + old_edits_iter.next(); } + } else { + break; } } From c8e47a8c63e2ab121490c7ba6783d75543c18ece Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 12 Nov 2021 19:01:39 +0100 Subject: [PATCH 028/109] Start on a randomized test for `BlockMap` This is currently passing and ensures we maintain the input coordinate space correctly. Co-Authored-By: Max Brunsfeld --- crates/editor/src/display_map.rs | 3 +- crates/editor/src/display_map/block_map.rs | 83 ++++++++++++++++++++-- crates/editor/src/display_map/patch.rs | 15 ++-- crates/editor/src/display_map/wrap_map.rs | 63 ++++++++-------- 4 files changed, 117 insertions(+), 47 deletions(-) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index d7f49f68f79e675dbcd2654b8b0a61bbca3e95b3..984531af98dd9e0b74c632c6d52b39def923e7bc 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -39,8 +39,7 @@ impl DisplayMap { ) -> Self { let (fold_map, snapshot) = FoldMap::new(buffer.clone(), cx); let (tab_map, snapshot) = TabMap::new(snapshot, tab_size); - let wrap_map = - cx.add_model(|cx| WrapMap::new(snapshot, font_id, font_size, wrap_width, cx)); + let (wrap_map, _) = WrapMap::new(snapshot, font_id, font_size, wrap_width, cx); cx.observe(&wrap_map, |_, _, cx| cx.notify()).detach(); DisplayMap { buffer, diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index 44355adc6927684c5e73c96e6838cb31880eb69d..95d3339c3ec58f83788b5c1a1b58b3e513cb4622 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -11,7 +11,9 @@ struct BlockMap { struct BlockMapWriter<'a>(&'a mut BlockMap); -struct BlockSnapshot {} +struct BlockSnapshot { + transforms: SumTree, +} #[derive(Clone)] struct Transform { @@ -42,7 +44,9 @@ impl BlockMap { fn read(&self, wrap_snapshot: WrapSnapshot, edits: Vec) -> BlockSnapshot { self.sync(wrap_snapshot, edits); - BlockSnapshot {} + BlockSnapshot { + transforms: self.transforms.lock().clone(), + } } fn write(&mut self, wrap_snapshot: WrapSnapshot, edits: Vec) -> BlockMapWriter { @@ -51,7 +55,7 @@ impl BlockMap { } fn sync(&self, wrap_snapshot: WrapSnapshot, edits: Vec) { - let transforms = self.transforms.lock(); + let mut transforms = self.transforms.lock(); let mut new_transforms = SumTree::new(); let mut cursor = transforms.cursor::(); let mut edits = edits.into_iter().peekable(); @@ -76,8 +80,9 @@ impl BlockMap { if let Some(next_edit) = edits.peek() { if edit.old.end >= next_edit.old.start { + let delta = next_edit.new.len() as i32 - next_edit.old.len() as i32; edit.old.end = cmp::max(next_edit.old.end, edit.old.end); - edit.new.end += (edit.new.len() as i32 - edit.old.len() as i32) as u32; + edit.new.end = (edit.new.end as i32 + delta) as u32; edits.next(); } else { break; @@ -98,6 +103,8 @@ impl BlockMap { } } new_transforms.push_tree(cursor.suffix(&()), &()); + drop(cursor); + *transforms = new_transforms; } } @@ -140,3 +147,71 @@ impl<'a> sum_tree::Dimension<'a, TransformSummary> for OutputRow { self.0 += summary.output_rows; } } + +#[cfg(test)] +mod tests { + use super::BlockMap; + use crate::display_map::{fold_map::FoldMap, tab_map::TabMap, wrap_map::WrapMap}; + use buffer::RandomCharIter; + use language::Buffer; + use rand::prelude::*; + use std::env; + + #[gpui::test(iterations = 100)] + fn test_random(cx: &mut gpui::MutableAppContext, mut rng: StdRng) { + let operations = env::var("OPERATIONS") + .map(|i| i.parse().expect("invalid `OPERATIONS` variable")) + .unwrap_or(10); + + let wrap_width = Some(rng.gen_range(0.0..=1000.0)); + let tab_size = 1; + let family_id = cx.font_cache().load_family(&["Helvetica"]).unwrap(); + let font_id = cx + .font_cache() + .select_font(family_id, &Default::default()) + .unwrap(); + let font_size = 14.0; + + log::info!("Tab size: {}", tab_size); + log::info!("Wrap width: {:?}", wrap_width); + + let buffer = cx.add_model(|cx| { + let len = rng.gen_range(0..10); + let text = RandomCharIter::new(&mut rng).take(len).collect::(); + Buffer::new(0, text, cx) + }); + let (fold_map, folds_snapshot) = FoldMap::new(buffer.clone(), cx); + let (tab_map, tabs_snapshot) = TabMap::new(folds_snapshot.clone(), tab_size); + let (wrap_map, wraps_snapshot) = + WrapMap::new(tabs_snapshot, font_id, font_size, wrap_width, cx); + let block_map = BlockMap::new(wraps_snapshot); + + for _ in 0..operations { + match rng.gen_range(0..=100) { + 0..=19 => { + let wrap_width = if rng.gen_bool(0.2) { + None + } else { + Some(rng.gen_range(0.0..=1000.0)) + }; + log::info!("Setting wrap width to {:?}", wrap_width); + wrap_map.update(cx, |map, cx| map.set_wrap_width(wrap_width, cx)); + } + _ => { + buffer.update(cx, |buffer, _| buffer.randomly_edit(&mut rng, 5)); + } + } + + let (folds_snapshot, fold_edits) = fold_map.read(cx); + let (tabs_snapshot, tab_edits) = tab_map.sync(folds_snapshot, fold_edits); + let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| { + wrap_map.sync(tabs_snapshot, tab_edits, cx) + }); + let blocks_snapshot = block_map.read(wraps_snapshot.clone(), wrap_edits); + assert_eq!( + blocks_snapshot.transforms.summary().input_rows, + wraps_snapshot.max_point().row() + 1 + ); + } + } +} diff --git a/crates/editor/src/display_map/patch.rs b/crates/editor/src/display_map/patch.rs index da78a7e2dfa184c4fa51d65fb67b0131052ed706..1fa940d91c88e3da13a172b9ed856ac5ffd84d25 100644 --- a/crates/editor/src/display_map/patch.rs +++ b/crates/editor/src/display_map/patch.rs @@ -1,4 +1,4 @@ -use std::{cmp, mem, slice}; +use std::{cmp, mem}; type Edit = buffer::Edit; @@ -10,6 +10,10 @@ impl Patch { Self(edits) } + pub fn into_inner(self) -> Vec { + self.0 + } + pub fn compose(&self, other: &Self) -> Self { let mut old_edits_iter = self.0.iter().cloned().peekable(); let mut new_edits_iter = other.0.iter().cloned().peekable(); @@ -167,15 +171,6 @@ impl Patch { } } -impl<'a> IntoIterator for &'a Patch { - type Item = &'a Edit; - type IntoIter = slice::Iter<'a, Edit>; - - fn into_iter(self) -> Self::IntoIter { - self.0.iter() - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/crates/editor/src/display_map/wrap_map.rs b/crates/editor/src/display_map/wrap_map.rs index b7d1063980e4b50a9745d7ac173cfdf09ed2b80d..6faadc3d257202d9c3ccd6dc241152e8bc2624b2 100644 --- a/crates/editor/src/display_map/wrap_map.rs +++ b/crates/editor/src/display_map/wrap_map.rs @@ -3,7 +3,10 @@ use super::{ patch::Patch, tab_map::{self, Edit as TabEdit, Snapshot as TabSnapshot, TabPoint, TextSummary}, }; -use gpui::{fonts::FontId, text_layout::LineWrapper, Entity, ModelContext, Task}; +use gpui::{ + fonts::FontId, text_layout::LineWrapper, Entity, ModelContext, ModelHandle, MutableAppContext, + Task, +}; use language::{HighlightedChunk, Point}; use lazy_static::lazy_static; use smol::future::yield_now; @@ -78,20 +81,24 @@ impl WrapMap { font_id: FontId, font_size: f32, wrap_width: Option, - cx: &mut ModelContext, - ) -> Self { - let mut this = Self { - font: (font_id, font_size), - wrap_width: None, - pending_edits: Default::default(), - interpolated_edits: Default::default(), - edits_since_sync: Default::default(), - snapshot: Snapshot::new(tab_snapshot), - background_task: None, - }; - this.set_wrap_width(wrap_width, cx); - mem::take(&mut this.edits_since_sync); - this + cx: &mut MutableAppContext, + ) -> (ModelHandle, Snapshot) { + let handle = cx.add_model(|cx| { + let mut this = Self { + font: (font_id, font_size), + wrap_width: None, + pending_edits: Default::default(), + interpolated_edits: Default::default(), + edits_since_sync: Default::default(), + snapshot: Snapshot::new(tab_snapshot), + background_task: None, + }; + this.set_wrap_width(wrap_width, cx); + mem::take(&mut this.edits_since_sync); + this + }); + let snapshot = handle.read(cx).snapshot.clone(); + (handle, snapshot) } #[cfg(test)] @@ -104,10 +111,13 @@ impl WrapMap { tab_snapshot: TabSnapshot, edits: Vec, cx: &mut ModelContext, - ) -> (Snapshot, Patch) { + ) -> (Snapshot, Vec) { self.pending_edits.push_back((tab_snapshot, edits)); self.flush_edits(cx); - (self.snapshot.clone(), mem::take(&mut self.edits_since_sync)) + ( + self.snapshot.clone(), + mem::take(&mut self.edits_since_sync).into_inner(), + ) } pub fn set_font(&mut self, font_id: FontId, font_size: f32, cx: &mut ModelContext) { @@ -983,12 +993,6 @@ impl<'a> sum_tree::Dimension<'a, TransformSummary> for WrapPoint { } } -fn invert(edits: &mut Vec) { - for edit in edits { - mem::swap(&mut edit.old, &mut edit.new); - } -} - fn consolidate_wrap_edits(edits: &mut Vec) { let mut i = 1; while i < edits.len() { @@ -1062,17 +1066,14 @@ mod tests { let unwrapped_text = tabs_snapshot.text(); let expected_text = wrap_text(&unwrapped_text, wrap_width, &mut line_wrapper); - let wrap_map = cx.add_model(|cx| { - WrapMap::new(tabs_snapshot.clone(), font_id, font_size, wrap_width, cx) - }); + let (wrap_map, initial_snapshot) = + cx.update(|cx| WrapMap::new(tabs_snapshot.clone(), font_id, font_size, wrap_width, cx)); let (_observer, notifications) = Observer::new(&wrap_map, &mut cx); if wrap_map.read_with(&cx, |map, _| map.is_rewrapping()) { notifications.recv().await.unwrap(); } - let (initial_snapshot, _) = - wrap_map.update(&mut cx, |map, cx| map.sync(tabs_snapshot, Vec::new(), cx)); let actual_text = initial_snapshot.text(); assert_eq!( actual_text, expected_text, @@ -1155,9 +1156,9 @@ mod tests { } let mut initial_text = Rope::from(initial_snapshot.text().as_str()); - for (snapshot, edits) in edits { + for (snapshot, patch) in edits { let snapshot_text = Rope::from(snapshot.text().as_str()); - for edit in &edits { + for edit in &patch { let old_start = initial_text.point_to_offset(Point::new(edit.new.start, 0)); let old_end = initial_text.point_to_offset(cmp::min( Point::new(edit.new.start + edit.old.len() as u32, 0), @@ -1206,7 +1207,7 @@ mod tests { } impl Snapshot { - fn text(&self) -> String { + pub fn text(&self) -> String { self.chunks_at(0).collect() } From 227c612dacf1587fc15ef85114ae3d987a454514 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 12 Nov 2021 11:49:48 -0800 Subject: [PATCH 029/109] BlockMap WIP --- crates/editor/src/display_map/block_map.rs | 334 ++++++++++++++++++++- 1 file changed, 323 insertions(+), 11 deletions(-) diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index 95d3339c3ec58f83788b5c1a1b58b3e513cb4622..61f4a0c50076620a97391707e1c76608653dfe32 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -1,23 +1,59 @@ -use std::cmp; - -use super::wrap_map::{Edit as WrapEdit, Snapshot as WrapSnapshot}; -use buffer::Bias; +use super::wrap_map::{self, Edit as WrapEdit, Snapshot as WrapSnapshot, WrapPoint}; +use buffer::{rope, Anchor, Bias, Point, Rope, ToOffset}; +use gpui::fonts::HighlightStyle; +use language::HighlightedChunk; use parking_lot::Mutex; +use std::{cmp, collections::HashSet, iter, ops::Range, slice, sync::Arc}; use sum_tree::SumTree; struct BlockMap { + blocks: Vec<(BlockId, Arc)>, transforms: Mutex>, } struct BlockMapWriter<'a>(&'a mut BlockMap); struct BlockSnapshot { + wrap_snapshot: WrapSnapshot, transforms: SumTree, } +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)] +struct BlockId(usize); + +#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)] +pub struct BlockPoint(super::Point); + +struct Block { + id: BlockId, + position: Anchor, + text: Rope, + runs: Vec<(usize, HighlightStyle)>, + disposition: BlockDisposition, +} + +#[derive(Clone)] +struct BlockProperties +where + P: Clone, + T: Clone, +{ + position: P, + text: T, + runs: Vec<(usize, HighlightStyle)>, + disposition: BlockDisposition, +} + +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +enum BlockDisposition { + Above, + Below, +} + #[derive(Clone)] struct Transform { summary: TransformSummary, + block: Option>, } #[derive(Copy, Clone, Debug, Default)] @@ -26,6 +62,23 @@ struct TransformSummary { output_rows: u32, } +struct HighlightedChunks<'a> { + transforms: sum_tree::Cursor<'a, Transform, (OutputRow, InputRow)>, + input_chunks: wrap_map::HighlightedChunks<'a>, + input_chunk: Option>, + block_chunks: Option>, + output_position: BlockPoint, + max_output_row: u32, +} + +struct BlockChunks<'a> { + chunks: rope::Chunks<'a>, + runs: iter::Peekable>, + chunk: Option<&'a str>, + run_start: usize, + offset: usize, +} + #[derive(Copy, Clone, Default, Debug, PartialEq, Eq, PartialOrd, Ord)] struct InputRow(u32); @@ -35,6 +88,7 @@ struct OutputRow(u32); impl BlockMap { fn new(wrap_snapshot: WrapSnapshot) -> Self { Self { + blocks: Vec::new(), transforms: Mutex::new(SumTree::from_item( Transform::isomorphic(wrap_snapshot.max_point().row() + 1), &(), @@ -43,18 +97,19 @@ impl BlockMap { } fn read(&self, wrap_snapshot: WrapSnapshot, edits: Vec) -> BlockSnapshot { - self.sync(wrap_snapshot, edits); + self.sync(&wrap_snapshot, edits); BlockSnapshot { + wrap_snapshot, transforms: self.transforms.lock().clone(), } } fn write(&mut self, wrap_snapshot: WrapSnapshot, edits: Vec) -> BlockMapWriter { - self.sync(wrap_snapshot, edits); + self.sync(&wrap_snapshot, edits); BlockMapWriter(self) } - fn sync(&self, wrap_snapshot: WrapSnapshot, edits: Vec) { + fn sync(&self, wrap_snapshot: &WrapSnapshot, edits: Vec) { let mut transforms = self.transforms.lock(); let mut new_transforms = SumTree::new(); let mut cursor = transforms.cursor::(); @@ -108,6 +163,81 @@ impl BlockMap { } } +impl<'a> BlockMapWriter<'a> { + pub fn insert( + &self, + blocks: impl IntoIterator>, + ) -> Vec + where + P: ToOffset + Clone, + T: Into + Clone, + { + vec![] + } + + pub fn remove(&self, ids: HashSet) { + // + } +} + +impl BlockSnapshot { + #[cfg(test)] + fn text(&mut self) -> String { + self.highlighted_chunks_for_rows(0..(self.max_point().0.row + 1)) + .map(|chunk| chunk.text) + .collect() + } + + pub fn highlighted_chunks_for_rows(&mut self, rows: Range) -> HighlightedChunks { + let mut cursor = self.transforms.cursor::<(OutputRow, InputRow)>(); + cursor.seek(&OutputRow(rows.start), Bias::Right, &()); + let (input_start, output_start) = cursor.start(); + let row_overshoot = rows.start - output_start.0; + let input_row = input_start.0 + row_overshoot; + let input_end = self.to_wrap_point(BlockPoint(Point::new(rows.end, 0))); + let input_chunks = self + .wrap_snapshot + .highlighted_chunks_for_rows(input_row..input_end.row()); + HighlightedChunks { + input_chunks, + input_chunk: None, + block_chunks: None, + transforms: cursor, + output_position: BlockPoint(Point::new(rows.start, 0)), + max_output_row: rows.end, + } + } + + pub fn max_point(&self) -> BlockPoint { + self.to_block_point(self.wrap_snapshot.max_point()) + } + + pub fn to_block_point(&self, wrap_point: WrapPoint) -> BlockPoint { + let mut cursor = self.transforms.cursor::<(InputRow, OutputRow)>(); + cursor.seek(&InputRow(wrap_point.row()), Bias::Left, &()); + while let Some(item) = cursor.item() { + if item.is_isomorphic() { + break; + } + cursor.next(&()); + } + let (input_start, output_start) = cursor.start(); + let row_overshoot = wrap_point.row() - input_start.0; + BlockPoint(Point::new( + output_start.0 + row_overshoot, + wrap_point.column(), + )) + } + + pub fn to_wrap_point(&self, block_point: BlockPoint) -> WrapPoint { + let mut cursor = self.transforms.cursor::<(OutputRow, InputRow)>(); + cursor.seek(&OutputRow(block_point.0.row), Bias::Right, &()); + let (output_start, input_start) = cursor.start(); + let row_overshoot = block_point.0.row - output_start.0; + WrapPoint::new(input_start.0 + row_overshoot, block_point.0.column) + } +} + impl Transform { fn isomorphic(rows: u32) -> Self { Self { @@ -115,7 +245,81 @@ impl Transform { input_rows: rows, output_rows: rows, }, + block: None, + } + } + + fn is_isomorphic(&self) -> bool { + self.block.is_none() + } +} + +impl<'a> Iterator for HighlightedChunks<'a> { + type Item = HighlightedChunk<'a>; + + fn next(&mut self) -> Option { + // + } +} + +impl<'a> BlockChunks<'a> { + fn new(block: &'a Block, range: Range) -> Self { + let mut runs = block.runs.iter().peekable(); + let mut run_start = 0; + while let Some((run_len, _)) = runs.peek() { + let run_end = run_start + run_len; + if run_end <= range.start { + run_start = run_end; + runs.next(); + } else { + break; + } + } + + Self { + chunk: None, + run_start, + chunks: block.text.chunks_in_range(range.clone()), + runs, + offset: range.start, + } + } +} + +impl<'a> Iterator for BlockChunks<'a> { + type Item = HighlightedChunk<'a>; + + fn next(&mut self) -> Option { + if self.chunk.is_none() { + self.chunk = self.chunks.next(); + } + + let chunk = self.chunk?; + let mut chunk_len = chunk.len(); + let mut highlight_style = None; + if let Some((run_len, style)) = self.runs.peek() { + highlight_style = Some(style.clone()); + let run_end_in_chunk = self.run_start + run_len - self.offset; + if run_end_in_chunk <= chunk_len { + chunk_len = run_end_in_chunk; + self.run_start += run_len; + self.runs.next(); + } } + + self.offset += chunk_len; + let (chunk, suffix) = chunk.split_at(chunk_len); + self.chunk = if suffix.is_empty() { + None + } else { + Some(suffix) + }; + + Some(HighlightedChunk { + text: chunk, + highlight_id: Default::default(), + diagnostic: None, + }) } } @@ -150,9 +354,9 @@ impl<'a> sum_tree::Dimension<'a, TransformSummary> for OutputRow { #[cfg(test)] mod tests { - use super::BlockMap; + use super::*; use crate::display_map::{fold_map::FoldMap, tab_map::TabMap, wrap_map::WrapMap}; - use buffer::RandomCharIter; + use buffer::{RandomCharIter, ToPoint as _}; use language::Buffer; use rand::prelude::*; use std::env; @@ -184,7 +388,8 @@ mod tests { let (tab_map, tabs_snapshot) = TabMap::new(folds_snapshot.clone(), tab_size); let (wrap_map, wraps_snapshot) = WrapMap::new(tabs_snapshot, font_id, font_size, wrap_width, cx); - let block_map = BlockMap::new(wraps_snapshot); + let mut block_map = BlockMap::new(wraps_snapshot); + let mut expected_blocks = Vec::new(); for _ in 0..operations { match rng.gen_range(0..=100) { @@ -197,6 +402,66 @@ mod tests { log::info!("Setting wrap width to {:?}", wrap_width); wrap_map.update(cx, |map, cx| map.set_wrap_width(wrap_width, cx)); } + 20..=39 => { + let block_count = rng.gen_range(1..=4); + let block_properties = (0..block_count) + .map(|_| { + let buffer = buffer.read(cx); + let position = buffer.anchor_before(rng.gen_range(0..=buffer.len())); + + let len = rng.gen_range(0..10); + let text = Rope::from( + RandomCharIter::new(&mut rng) + .take(len) + .collect::() + .as_str(), + ); + BlockProperties { + position, + text, + runs: Vec::<(usize, HighlightStyle)>::new(), + disposition: if rng.gen() { + BlockDisposition::Above + } else { + BlockDisposition::Below + }, + } + }) + .collect::>(); + + let (folds_snapshot, fold_edits) = fold_map.read(cx); + let (tabs_snapshot, tab_edits) = tab_map.sync(folds_snapshot, fold_edits); + let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| { + wrap_map.sync(tabs_snapshot, tab_edits, cx) + }); + let block_map = block_map.write(wraps_snapshot, wrap_edits); + + expected_blocks.extend( + block_map + .insert(block_properties.clone()) + .into_iter() + .zip(block_properties), + ); + } + 40..=59 => { + let block_count = rng.gen_range(1..=4.min(expected_blocks.len())); + let block_ids_to_remove = (0..block_count) + .map(|_| { + expected_blocks + .remove(rng.gen_range(0..expected_blocks.len())) + .0 + }) + .collect(); + + let (folds_snapshot, fold_edits) = fold_map.read(cx); + let (tabs_snapshot, tab_edits) = tab_map.sync(folds_snapshot, fold_edits); + let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| { + wrap_map.sync(tabs_snapshot, tab_edits, cx) + }); + let block_map = block_map.write(wraps_snapshot, wrap_edits); + + block_map.remove(block_ids_to_remove); + } _ => { buffer.update(cx, |buffer, _| buffer.randomly_edit(&mut rng, 5)); } @@ -207,11 +472,58 @@ mod tests { let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| { wrap_map.sync(tabs_snapshot, tab_edits, cx) }); - let blocks_snapshot = block_map.read(wraps_snapshot.clone(), wrap_edits); + let mut blocks_snapshot = block_map.read(wraps_snapshot.clone(), wrap_edits); assert_eq!( blocks_snapshot.transforms.summary().input_rows, wraps_snapshot.max_point().row() + 1 ); + + let buffer = buffer.read(cx); + let mut sorted_blocks = expected_blocks + .iter() + .cloned() + .map(|(_, block)| BlockProperties { + position: block.position.to_point(buffer), + text: block.text, + runs: block.runs, + disposition: block.disposition, + }) + .collect::>(); + sorted_blocks.sort_unstable_by_key(|block| (block.position.row, block.disposition)); + let mut sorted_blocks = sorted_blocks.into_iter().peekable(); + + let mut expected_text = String::new(); + let input_text = wraps_snapshot.text(); + for (row, input_line) in input_text.split('\n').enumerate() { + let row = row as u32; + if row > 0 { + expected_text.push('\n'); + } + + while let Some(block) = sorted_blocks.peek() { + if block.position.row == row && block.disposition == BlockDisposition::Above { + expected_text.extend(block.text.chunks()); + expected_text.push('\n'); + sorted_blocks.next(); + } else { + break; + } + } + + expected_text.push_str(input_line); + + while let Some(block) = sorted_blocks.peek() { + if block.position.row == row && block.disposition == BlockDisposition::Below { + expected_text.push('\n'); + expected_text.extend(block.text.chunks()); + sorted_blocks.next(); + } else { + break; + } + } + } + + assert_eq!(blocks_snapshot.text(), expected_text); } } } From 6f97a9be3bdfd0ed71d52815b2c3cf37a07230a1 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 12 Nov 2021 12:09:35 -0800 Subject: [PATCH 030/109] wip --- crates/editor/src/display_map/block_map.rs | 26 ++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index 61f4a0c50076620a97391707e1c76608653dfe32..b07bd2258a0cd640cdc024c4362174db1f318c5c 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -3,7 +3,7 @@ use buffer::{rope, Anchor, Bias, Point, Rope, ToOffset}; use gpui::fonts::HighlightStyle; use language::HighlightedChunk; use parking_lot::Mutex; -use std::{cmp, collections::HashSet, iter, ops::Range, slice, sync::Arc}; +use std::{borrow::Borrow, cmp, collections::HashSet, iter, ops::Range, slice, sync::Arc}; use sum_tree::SumTree; struct BlockMap { @@ -63,7 +63,7 @@ struct TransformSummary { } struct HighlightedChunks<'a> { - transforms: sum_tree::Cursor<'a, Transform, (OutputRow, InputRow)>, + transform_cursor: sum_tree::Cursor<'a, Transform, (OutputRow, InputRow)>, input_chunks: wrap_map::HighlightedChunks<'a>, input_chunk: Option>, block_chunks: Option>, @@ -202,7 +202,7 @@ impl BlockSnapshot { input_chunks, input_chunk: None, block_chunks: None, - transforms: cursor, + transform_cursor: cursor, output_position: BlockPoint(Point::new(rows.start, 0)), max_output_row: rows.end, } @@ -258,7 +258,25 @@ impl<'a> Iterator for HighlightedChunks<'a> { type Item = HighlightedChunk<'a>; fn next(&mut self) -> Option { - // + if let Some(current_block) = self.block_chunks.as_mut() { + if let Some(chunk) = current_block.next() { + return Some(chunk); + } else { + self.block_chunks.take(); + } + } + + let transform = if let Some(item) = self.transform_cursor.item() { + item + } else { + return None; + }; + + if let Some(block) = &transform.block { + let of + } + + None } } From e605a5ead298e2f186d07ecce864c6ed9d82f3cf Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 12 Nov 2021 14:51:25 -0700 Subject: [PATCH 031/109] Sketch an initial implementation for block_map::HighlightedChunks Co-Authored-By: Max Brunsfeld --- crates/buffer/src/rope.rs | 16 +++- crates/editor/src/display_map/block_map.rs | 104 +++++++++++++++++---- 2 files changed, 97 insertions(+), 23 deletions(-) diff --git a/crates/buffer/src/rope.rs b/crates/buffer/src/rope.rs index d5bba2ef2e172a7c057c26ea4dc7c4d9ebc877bd..24186c8056bba714e477f8935b92db6ba0636fa5 100644 --- a/crates/buffer/src/rope.rs +++ b/crates/buffer/src/rope.rs @@ -149,7 +149,9 @@ impl Rope { } pub fn offset_to_point(&self, offset: usize) -> Point { - assert!(offset <= self.summary().bytes); + if offset >= self.summary().bytes { + return self.summary().lines; + } let mut cursor = self.chunks.cursor::<(usize, Point)>(); cursor.seek(&offset, Bias::Left, &()); let overshoot = offset - cursor.start().0; @@ -160,7 +162,9 @@ impl Rope { } pub fn offset_to_point_utf16(&self, offset: usize) -> PointUtf16 { - assert!(offset <= self.summary().bytes); + if offset >= self.summary().bytes { + return self.summary().lines_utf16; + } let mut cursor = self.chunks.cursor::<(usize, PointUtf16)>(); cursor.seek(&offset, Bias::Left, &()); let overshoot = offset - cursor.start().0; @@ -171,7 +175,9 @@ impl Rope { } pub fn point_to_offset(&self, point: Point) -> usize { - assert!(point <= self.summary().lines); + if point >= self.summary().lines { + return self.summary().bytes; + } let mut cursor = self.chunks.cursor::<(Point, usize)>(); cursor.seek(&point, Bias::Left, &()); let overshoot = point - cursor.start().0; @@ -182,7 +188,9 @@ impl Rope { } pub fn point_utf16_to_offset(&self, point: PointUtf16) -> usize { - assert!(point <= self.summary().lines_utf16); + if point >= self.summary().lines_utf16 { + return self.summary().bytes; + } let mut cursor = self.chunks.cursor::<(PointUtf16, usize)>(); cursor.seek(&point, Bias::Left, &()); let overshoot = point - cursor.start().0; diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index b07bd2258a0cd640cdc024c4362174db1f318c5c..4496b1ad22535babc1704f14d30d2a026109de1c 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -3,6 +3,7 @@ use buffer::{rope, Anchor, Bias, Point, Rope, ToOffset}; use gpui::fonts::HighlightStyle; use language::HighlightedChunk; use parking_lot::Mutex; +use smol::io::AsyncBufReadExt; use std::{borrow::Borrow, cmp, collections::HashSet, iter, ops::Range, slice, sync::Arc}; use sum_tree::SumTree; @@ -63,11 +64,11 @@ struct TransformSummary { } struct HighlightedChunks<'a> { - transform_cursor: sum_tree::Cursor<'a, Transform, (OutputRow, InputRow)>, + transforms: sum_tree::Cursor<'a, Transform, (OutputRow, InputRow)>, input_chunks: wrap_map::HighlightedChunks<'a>, - input_chunk: Option>, + input_chunk: HighlightedChunk<'a>, block_chunks: Option>, - output_position: BlockPoint, + output_row: u32, max_output_row: u32, } @@ -163,6 +164,24 @@ impl BlockMap { } } +impl BlockPoint { + fn row(&self) -> u32 { + self.0.row + } + + fn row_mut(&self) -> &mut u32 { + &mut self.0.row + } + + fn column(&self) -> u32 { + self.0.column + } + + fn column_mut(&self) -> &mut u32 { + &mut self.0.column + } +} + impl<'a> BlockMapWriter<'a> { pub fn insert( &self, @@ -202,7 +221,7 @@ impl BlockSnapshot { input_chunks, input_chunk: None, block_chunks: None, - transform_cursor: cursor, + transforms: cursor, output_position: BlockPoint(Point::new(rows.start, 0)), max_output_row: rows.end, } @@ -258,35 +277,62 @@ impl<'a> Iterator for HighlightedChunks<'a> { type Item = HighlightedChunk<'a>; fn next(&mut self) -> Option { - if let Some(current_block) = self.block_chunks.as_mut() { - if let Some(chunk) = current_block.next() { - return Some(chunk); + if self.output_row >= self.max_output_row { + return None; + } + + if let Some(block_chunks) = self.block_chunks.as_mut() { + if let Some(mut block_chunk) = block_chunks.next() { + self.output_row += block_chunk.text.matches('\n').count() as u32; + return Some(block_chunk); } else { self.block_chunks.take(); } } - let transform = if let Some(item) = self.transform_cursor.item() { - item - } else { - return None; - }; + let transform = self.transforms.item()?; + if let Some(block) = transform.block.as_ref() { + let block_start = self.transforms.start().0 .0; + let block_end = self.transforms.end(&()).0 .0; + let start_row_in_block = self.output_row - block_start; + let end_row_in_block = cmp::min(self.max_output_row, block_end) - block_start; + self.transforms.next(&()); + let mut block_chunks = BlockChunks::new(block, start_row_in_block..end_row_in_block); + if let Some(block_chunk) = block_chunks.next() { + self.output_row += block_chunk.text.matches('\n').count() as u32; + return Some(block_chunk); + } + } - if let Some(block) = &transform.block { - let of + if self.input_chunk.text.is_empty() { + self.input_chunk = self.input_chunks.next().unwrap(); } - None + let transform_end = self.transforms.end(&()).0 .0; + let (prefix_rows, prefix_bytes) = + offset_for_row(self.input_chunk.text, transform_end - self.output_row); + self.output_row += prefix_rows; + let (prefix, suffix) = self.input_chunk.text.split_at(prefix_bytes); + + self.input_chunk.text = suffix; + Some(HighlightedChunk { + text: prefix, + ..self.input_chunk + }) } } impl<'a> BlockChunks<'a> { - fn new(block: &'a Block, range: Range) -> Self { + fn new(block: &'a Block, row_range: Range) -> Self { + let point_range = Point::new(row_range.start, 0)..Point::new(row_range.end, 0); + let offset_range = block.text.point_to_offset(point_range.start) + ..block.text.point_to_offset(point_range.end); + let mut runs = block.runs.iter().peekable(); let mut run_start = 0; while let Some((run_len, _)) = runs.peek() { let run_end = run_start + run_len; - if run_end <= range.start { + if run_end <= offset_range.start { run_start = run_end; runs.next(); } else { @@ -297,9 +343,9 @@ impl<'a> BlockChunks<'a> { Self { chunk: None, run_start, - chunks: block.text.chunks_in_range(range.clone()), + chunks: block.text.chunks_in_range(offset_range.clone()), runs, - offset: range.start, + offset: offset_range.start, } } } @@ -370,6 +416,26 @@ impl<'a> sum_tree::Dimension<'a, TransformSummary> for OutputRow { } } +// Count the number of bytes prior to a target row. +// If the string doesn't contain the target row, return the total number of rows it does contain. +// Otherwise return the target row itself. +fn offset_for_row(s: &str, target_row: u32) -> (u32, usize) { + assert!(target_row > 0); + let mut row = 0; + let mut offset = 0; + for (ix, line) in s.split('\n').enumerate() { + if ix > 0 { + row += 1; + offset += 1; + if row as u32 >= target_row { + break; + } + } + offset += line.len(); + } + (row, offset) +} + #[cfg(test)] mod tests { use super::*; From 2e61a586b6938f3c3972b69c4283d637dcd15c98 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 12 Nov 2021 13:55:47 -0800 Subject: [PATCH 032/109] Fix compile errors --- crates/editor/src/display_map/block_map.rs | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index 4496b1ad22535babc1704f14d30d2a026109de1c..4e74dee9ea5a4f6ffe7e597e6bde948d01d22c00 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -3,8 +3,7 @@ use buffer::{rope, Anchor, Bias, Point, Rope, ToOffset}; use gpui::fonts::HighlightStyle; use language::HighlightedChunk; use parking_lot::Mutex; -use smol::io::AsyncBufReadExt; -use std::{borrow::Borrow, cmp, collections::HashSet, iter, ops::Range, slice, sync::Arc}; +use std::{cmp, collections::HashSet, iter, ops::Range, slice, sync::Arc}; use sum_tree::SumTree; struct BlockMap { @@ -169,7 +168,7 @@ impl BlockPoint { self.0.row } - fn row_mut(&self) -> &mut u32 { + fn row_mut(&mut self) -> &mut u32 { &mut self.0.row } @@ -177,7 +176,7 @@ impl BlockPoint { self.0.column } - fn column_mut(&self) -> &mut u32 { + fn column_mut(&mut self) -> &mut u32 { &mut self.0.column } } @@ -212,17 +211,19 @@ impl BlockSnapshot { cursor.seek(&OutputRow(rows.start), Bias::Right, &()); let (input_start, output_start) = cursor.start(); let row_overshoot = rows.start - output_start.0; - let input_row = input_start.0 + row_overshoot; - let input_end = self.to_wrap_point(BlockPoint(Point::new(rows.end, 0))); + let input_start_row = input_start.0 + row_overshoot; + let input_end_row = self + .to_wrap_point(BlockPoint(Point::new(rows.end, 0))) + .row(); let input_chunks = self .wrap_snapshot - .highlighted_chunks_for_rows(input_row..input_end.row()); + .highlighted_chunks_for_rows(input_start_row..input_end_row); HighlightedChunks { input_chunks, - input_chunk: None, + input_chunk: Default::default(), block_chunks: None, transforms: cursor, - output_position: BlockPoint(Point::new(rows.start, 0)), + output_row: rows.start, max_output_row: rows.end, } } From c278503166291b4f4add36e65a0d1524a0ea70c0 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 12 Nov 2021 15:02:08 -0800 Subject: [PATCH 033/109] Make block insertion work in simple cases Co-Authored-By: Nathan Sobo --- crates/editor/src/display_map.rs | 1 + crates/editor/src/display_map/block_map.rs | 259 ++++++++++++++++++--- 2 files changed, 229 insertions(+), 31 deletions(-) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 984531af98dd9e0b74c632c6d52b39def923e7bc..5ec8d90121187f79740d338eba943002bdd2dcbb 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -55,6 +55,7 @@ impl DisplayMap { let (wraps_snapshot, _) = self .wrap_map .update(cx, |map, cx| map.sync(tabs_snapshot.clone(), edits, cx)); + DisplayMapSnapshot { buffer_snapshot: self.buffer.read(cx).snapshot(), folds_snapshot, diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index 4e74dee9ea5a4f6ffe7e597e6bde948d01d22c00..21b41e48d160bdd93448caebd28fd03868f1c3b2 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -1,13 +1,26 @@ use super::wrap_map::{self, Edit as WrapEdit, Snapshot as WrapSnapshot, WrapPoint}; -use buffer::{rope, Anchor, Bias, Point, Rope, ToOffset}; -use gpui::fonts::HighlightStyle; -use language::HighlightedChunk; +use buffer::{rope, Anchor, Bias, Edit, Point, Rope, ToOffset, ToPoint as _}; +use gpui::{fonts::HighlightStyle, AppContext, ModelHandle}; +use language::{Buffer, HighlightedChunk}; use parking_lot::Mutex; -use std::{cmp, collections::HashSet, iter, ops::Range, slice, sync::Arc}; +use std::{ + cmp, + collections::HashSet, + iter, + ops::Range, + slice, + sync::{ + atomic::{AtomicUsize, Ordering::SeqCst}, + Arc, + }, +}; use sum_tree::SumTree; struct BlockMap { - blocks: Vec<(BlockId, Arc)>, + buffer: ModelHandle, + next_block_id: AtomicUsize, + wrap_snapshot: Mutex, + blocks: Vec>, transforms: Mutex>, } @@ -24,6 +37,7 @@ struct BlockId(usize); #[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)] pub struct BlockPoint(super::Point); +#[derive(Debug)] struct Block { id: BlockId, position: Anchor, @@ -44,13 +58,13 @@ where disposition: BlockDisposition, } -#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] enum BlockDisposition { Above, Below, } -#[derive(Clone)] +#[derive(Clone, Debug)] struct Transform { summary: TransformSummary, block: Option>, @@ -86,34 +100,53 @@ struct InputRow(u32); struct OutputRow(u32); impl BlockMap { - fn new(wrap_snapshot: WrapSnapshot) -> Self { + fn new(buffer: ModelHandle, wrap_snapshot: WrapSnapshot) -> Self { Self { + buffer, + next_block_id: AtomicUsize::new(0), blocks: Vec::new(), transforms: Mutex::new(SumTree::from_item( Transform::isomorphic(wrap_snapshot.max_point().row() + 1), &(), )), + wrap_snapshot: Mutex::new(wrap_snapshot), } } - fn read(&self, wrap_snapshot: WrapSnapshot, edits: Vec) -> BlockSnapshot { - self.sync(&wrap_snapshot, edits); + fn read( + &self, + wrap_snapshot: WrapSnapshot, + edits: Vec, + cx: &AppContext, + ) -> BlockSnapshot { + self.apply_edits(&wrap_snapshot, edits, cx); + *self.wrap_snapshot.lock() = wrap_snapshot.clone(); BlockSnapshot { wrap_snapshot, transforms: self.transforms.lock().clone(), } } - fn write(&mut self, wrap_snapshot: WrapSnapshot, edits: Vec) -> BlockMapWriter { - self.sync(&wrap_snapshot, edits); + fn write( + &mut self, + wrap_snapshot: WrapSnapshot, + edits: Vec, + cx: &AppContext, + ) -> BlockMapWriter { + self.apply_edits(&wrap_snapshot, edits, cx); + *self.wrap_snapshot.lock() = wrap_snapshot; BlockMapWriter(self) } - fn sync(&self, wrap_snapshot: &WrapSnapshot, edits: Vec) { + fn apply_edits(&self, wrap_snapshot: &WrapSnapshot, edits: Vec, cx: &AppContext) { + let buffer = self.buffer.read(cx); let mut transforms = self.transforms.lock(); let mut new_transforms = SumTree::new(); let mut cursor = transforms.cursor::(); let mut edits = edits.into_iter().peekable(); + let mut last_block_ix = 0; + let mut blocks_in_edit = Vec::new(); + while let Some(mut edit) = edits.next() { new_transforms.push_tree( cursor.slice(&InputRow(edit.old.start), Bias::Left, &()), @@ -147,7 +180,45 @@ impl BlockMap { } } - // TODO: process injections + let start_anchor = buffer.anchor_before(Point::new(edit.new.start, 0)); + let end_anchor = buffer.anchor_after(Point::new(edit.new.end, 0)); + let start_block_ix = match self.blocks[last_block_ix..] + .binary_search_by(|probe| probe.position.cmp(&start_anchor, buffer).unwrap()) + { + Ok(ix) | Err(ix) => last_block_ix + ix, + }; + let end_block_ix = match self.blocks[start_block_ix..] + .binary_search_by(|probe| probe.position.cmp(&end_anchor, buffer).unwrap()) + { + Ok(ix) | Err(ix) => start_block_ix + ix, + }; + last_block_ix = end_block_ix; + + blocks_in_edit.clear(); + blocks_in_edit.extend( + self.blocks[start_block_ix..end_block_ix] + .iter() + .map(|block| (block.position.to_point(buffer).row, block)), + ); + blocks_in_edit.sort_unstable_by_key(|(row, block)| (*row, block.disposition)); + + for (row, block) in blocks_in_edit.iter().copied() { + let insertion_row = if block.disposition.is_above() { + row + } else { + row + 1 + }; + + let new_transforms_end = new_transforms.summary().input_rows; + if new_transforms_end < insertion_row { + new_transforms.push( + Transform::isomorphic(insertion_row - new_transforms_end), + &(), + ); + } + + new_transforms.push(Transform::block(block.clone()), &()); + } let new_transforms_end = new_transforms.summary().input_rows; if new_transforms_end < edit.new.end { @@ -183,14 +254,58 @@ impl BlockPoint { impl<'a> BlockMapWriter<'a> { pub fn insert( - &self, + &mut self, blocks: impl IntoIterator>, + cx: &AppContext, ) -> Vec where P: ToOffset + Clone, T: Into + Clone, { - vec![] + let buffer = self.0.buffer.read(cx); + let mut ids = Vec::new(); + let mut edits = Vec::>::new(); + + for block in blocks { + let id = BlockId(self.0.next_block_id.fetch_add(1, SeqCst)); + ids.push(id); + + let position = buffer.anchor_before(block.position); + let row = position.to_point(buffer).row; + + let block_ix = match self + .0 + .blocks + .binary_search_by(|probe| probe.position.cmp(&position, buffer).unwrap()) + { + Ok(ix) | Err(ix) => ix, + }; + let mut text = block.text.into(); + text.push("\n"); + self.0.blocks.insert( + block_ix, + Arc::new(Block { + id, + position, + text, + runs: block.runs, + disposition: block.disposition, + }), + ); + + if let Err(edit_ix) = edits.binary_search_by_key(&row, |edit| edit.old.start) { + edits.insert( + edit_ix, + Edit { + old: row..(row + 1), + new: row..(row + 1), + }, + ); + } + } + + self.0.apply_edits(&*self.0.wrap_snapshot.lock(), edits, cx); + ids } pub fn remove(&self, ids: HashSet) { @@ -269,6 +384,16 @@ impl Transform { } } + fn block(block: Arc) -> Self { + Self { + summary: TransformSummary { + input_rows: 0, + output_rows: block.text.summary().lines.row, + }, + block: Some(block), + } + } + fn is_isomorphic(&self) -> bool { self.block.is_none() } @@ -283,7 +408,7 @@ impl<'a> Iterator for HighlightedChunks<'a> { } if let Some(block_chunks) = self.block_chunks.as_mut() { - if let Some(mut block_chunk) = block_chunks.next() { + if let Some(block_chunk) = block_chunks.next() { self.output_row += block_chunk.text.matches('\n').count() as u32; return Some(block_chunk); } else { @@ -306,7 +431,7 @@ impl<'a> Iterator for HighlightedChunks<'a> { } if self.input_chunk.text.is_empty() { - self.input_chunk = self.input_chunks.next().unwrap(); + self.input_chunk = self.input_chunks.next()?; } let transform_end = self.transforms.end(&()).0 .0; @@ -314,8 +439,11 @@ impl<'a> Iterator for HighlightedChunks<'a> { offset_for_row(self.input_chunk.text, transform_end - self.output_row); self.output_row += prefix_rows; let (prefix, suffix) = self.input_chunk.text.split_at(prefix_bytes); - self.input_chunk.text = suffix; + if self.output_row == transform_end { + self.transforms.next(&()); + } + Some(HighlightedChunk { text: prefix, ..self.input_chunk @@ -417,20 +545,25 @@ impl<'a> sum_tree::Dimension<'a, TransformSummary> for OutputRow { } } +impl BlockDisposition { + fn is_above(&self) -> bool { + matches!(self, BlockDisposition::Above) + } +} + // Count the number of bytes prior to a target row. // If the string doesn't contain the target row, return the total number of rows it does contain. // Otherwise return the target row itself. fn offset_for_row(s: &str, target_row: u32) -> (u32, usize) { - assert!(target_row > 0); let mut row = 0; let mut offset = 0; for (ix, line) in s.split('\n').enumerate() { if ix > 0 { row += 1; offset += 1; - if row as u32 >= target_row { - break; - } + } + if row as u32 >= target_row { + break; } offset += line.len(); } @@ -441,13 +574,78 @@ fn offset_for_row(s: &str, target_row: u32) -> (u32, usize) { mod tests { use super::*; use crate::display_map::{fold_map::FoldMap, tab_map::TabMap, wrap_map::WrapMap}; - use buffer::{RandomCharIter, ToPoint as _}; + use buffer::RandomCharIter; use language::Buffer; use rand::prelude::*; use std::env; + #[gpui::test] + fn test_basic_blocks(cx: &mut gpui::MutableAppContext) { + let family_id = cx.font_cache().load_family(&["Helvetica"]).unwrap(); + let font_id = cx + .font_cache() + .select_font(family_id, &Default::default()) + .unwrap(); + + let text = "aaa\nbbb\nccc\nddd\n"; + + let buffer = cx.add_model(|cx| Buffer::new(0, text, cx)); + let (fold_map, folds_snapshot) = FoldMap::new(buffer.clone(), cx); + let (tab_map, tabs_snapshot) = TabMap::new(folds_snapshot.clone(), 1); + let (wrap_map, wraps_snapshot) = WrapMap::new(tabs_snapshot, font_id, 14.0, None, cx); + let mut block_map = BlockMap::new(buffer.clone(), wraps_snapshot.clone()); + + let mut writer = block_map.write(wraps_snapshot.clone(), vec![], cx); + writer.insert( + vec![ + BlockProperties { + position: Point::new(1, 0), + text: "BLOCK 1", + disposition: BlockDisposition::Above, + runs: vec![], + }, + BlockProperties { + position: Point::new(1, 2), + text: "BLOCK 2", + disposition: BlockDisposition::Above, + runs: vec![], + }, + BlockProperties { + position: Point::new(3, 2), + text: "BLOCK 3", + disposition: BlockDisposition::Below, + runs: vec![], + }, + ], + cx, + ); + + let mut snapshot = block_map.read(wraps_snapshot, vec![], cx); + assert_eq!( + snapshot.text(), + "aaa\nBLOCK 1\nBLOCK 2\nbbb\nccc\nddd\nBLOCK 3\n" + ); + + // Insert a line break, separating two block decorations into separate + // lines. + buffer.update(cx, |buffer, cx| { + buffer.edit([Point::new(1, 1)..Point::new(1, 1)], "!!!\n", cx) + }); + + let (folds_snapshot, fold_edits) = fold_map.read(cx); + let (tabs_snapshot, tab_edits) = tab_map.sync(folds_snapshot, fold_edits); + let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| { + wrap_map.sync(tabs_snapshot, tab_edits, cx) + }); + let mut snapshot = block_map.read(wraps_snapshot, wrap_edits, cx); + assert_eq!( + snapshot.text(), + "aaa\nBLOCK 1\nb!!!\nBLOCK 2\nbb\nccc\nddd\nBLOCK 3\n" + ); + } + #[gpui::test(iterations = 100)] - fn test_random(cx: &mut gpui::MutableAppContext, mut rng: StdRng) { + fn test_random_blocks(cx: &mut gpui::MutableAppContext, mut rng: StdRng) { let operations = env::var("OPERATIONS") .map(|i| i.parse().expect("invalid `OPERATIONS` variable")) .unwrap_or(10); @@ -461,7 +659,6 @@ mod tests { .unwrap(); let font_size = 14.0; - log::info!("Tab size: {}", tab_size); log::info!("Wrap width: {:?}", wrap_width); let buffer = cx.add_model(|cx| { @@ -473,7 +670,7 @@ mod tests { let (tab_map, tabs_snapshot) = TabMap::new(folds_snapshot.clone(), tab_size); let (wrap_map, wraps_snapshot) = WrapMap::new(tabs_snapshot, font_id, font_size, wrap_width, cx); - let mut block_map = BlockMap::new(wraps_snapshot); + let mut block_map = BlockMap::new(buffer.clone(), wraps_snapshot); let mut expected_blocks = Vec::new(); for _ in 0..operations { @@ -519,11 +716,11 @@ mod tests { let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| { wrap_map.sync(tabs_snapshot, tab_edits, cx) }); - let block_map = block_map.write(wraps_snapshot, wrap_edits); + let mut block_map = block_map.write(wraps_snapshot, wrap_edits, cx); expected_blocks.extend( block_map - .insert(block_properties.clone()) + .insert(block_properties.clone(), cx) .into_iter() .zip(block_properties), ); @@ -543,7 +740,7 @@ mod tests { let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| { wrap_map.sync(tabs_snapshot, tab_edits, cx) }); - let block_map = block_map.write(wraps_snapshot, wrap_edits); + let block_map = block_map.write(wraps_snapshot, wrap_edits, cx); block_map.remove(block_ids_to_remove); } @@ -557,7 +754,7 @@ mod tests { let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| { wrap_map.sync(tabs_snapshot, tab_edits, cx) }); - let mut blocks_snapshot = block_map.read(wraps_snapshot.clone(), wrap_edits); + let mut blocks_snapshot = block_map.read(wraps_snapshot.clone(), wrap_edits, cx); assert_eq!( blocks_snapshot.transforms.summary().input_rows, wraps_snapshot.max_point().row() + 1 From 364fab7b5fdfe7217c10383b6b2325579ff13bc8 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Fri, 12 Nov 2021 17:29:09 -0800 Subject: [PATCH 034/109] wip - wiring up blockmap into displaymap --- crates/editor/src/display_map.rs | 82 ++++++++++++++++------ crates/editor/src/display_map/block_map.rs | 68 +++++++++--------- 2 files changed, 95 insertions(+), 55 deletions(-) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 5ec8d90121187f79740d338eba943002bdd2dcbb..67dbb34d5d376269185bfab0370e95b5f2e0f278 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -4,14 +4,18 @@ mod patch; mod tab_map; mod wrap_map; +use block_map::{BlockId, BlockMap, BlockPoint, BlockProperties}; +use buffer::Rope; use fold_map::{FoldMap, ToFoldPoint as _}; use gpui::{fonts::FontId, Entity, ModelContext, ModelHandle}; use language::{Anchor, Buffer, Point, ToOffset, ToPoint}; -use std::ops::Range; +use std::{collections::HashSet, ops::Range}; use sum_tree::Bias; use tab_map::TabMap; use wrap_map::WrapMap; -pub use wrap_map::{BufferRows, HighlightedChunks}; + +pub use block_map::HighlightedChunks; +pub use wrap_map::BufferRows; pub trait ToDisplayPoint { fn to_display_point(&self, map: &DisplayMapSnapshot, bias: Bias) -> DisplayPoint; @@ -22,6 +26,7 @@ pub struct DisplayMap { fold_map: FoldMap, tab_map: TabMap, wrap_map: ModelHandle, + block_map: BlockMap, } impl Entity for DisplayMap { @@ -39,28 +44,32 @@ impl DisplayMap { ) -> Self { let (fold_map, snapshot) = FoldMap::new(buffer.clone(), cx); let (tab_map, snapshot) = TabMap::new(snapshot, tab_size); - let (wrap_map, _) = WrapMap::new(snapshot, font_id, font_size, wrap_width, cx); + let (wrap_map, snapshot) = WrapMap::new(snapshot, font_id, font_size, wrap_width, cx); + let block_map = BlockMap::new(buffer.clone(), snapshot); cx.observe(&wrap_map, |_, _, cx| cx.notify()).detach(); DisplayMap { buffer, fold_map, tab_map, wrap_map, + block_map, } } pub fn snapshot(&self, cx: &mut ModelContext) -> DisplayMapSnapshot { let (folds_snapshot, edits) = self.fold_map.read(cx); let (tabs_snapshot, edits) = self.tab_map.sync(folds_snapshot.clone(), edits); - let (wraps_snapshot, _) = self + let (wraps_snapshot, edits) = self .wrap_map .update(cx, |map, cx| map.sync(tabs_snapshot.clone(), edits, cx)); + let blocks_snapshot = self.block_map.read(wraps_snapshot.clone(), edits, cx); DisplayMapSnapshot { buffer_snapshot: self.buffer.read(cx).snapshot(), folds_snapshot, tabs_snapshot, wraps_snapshot, + blocks_snapshot, } } @@ -94,6 +103,34 @@ impl DisplayMap { .update(cx, |map, cx| map.sync(snapshot, edits, cx)); } + pub fn insert_blocks( + &mut self, + blocks: impl IntoIterator>, + cx: &mut ModelContext, + ) -> Vec + where + P: ToOffset + Clone, + T: Into + Clone, + { + let (snapshot, edits) = self.fold_map.read(cx); + let (snapshot, edits) = self.tab_map.sync(snapshot, edits); + let (snapshot, edits) = self + .wrap_map + .update(cx, |map, cx| map.sync(snapshot, edits, cx)); + let mut block_map = self.block_map.write(snapshot, edits, cx); + block_map.insert(blocks, cx) + } + + pub fn remove_blocks(&mut self, ids: HashSet, cx: &mut ModelContext) { + let (snapshot, edits) = self.fold_map.read(cx); + let (snapshot, edits) = self.tab_map.sync(snapshot, edits); + let (snapshot, edits) = self + .wrap_map + .update(cx, |map, cx| map.sync(snapshot, edits, cx)); + let mut block_map = self.block_map.write(snapshot, edits, cx); + block_map.remove(ids, cx); + } + pub fn set_font(&self, font_id: FontId, font_size: f32, cx: &mut ModelContext) { self.wrap_map .update(cx, |map, cx| map.set_font(font_id, font_size, cx)); @@ -115,6 +152,7 @@ pub struct DisplayMapSnapshot { folds_snapshot: fold_map::Snapshot, tabs_snapshot: tab_map::Snapshot, wraps_snapshot: wrap_map::Snapshot, + blocks_snapshot: block_map::BlockSnapshot, } impl DisplayMapSnapshot { @@ -162,7 +200,7 @@ impl DisplayMapSnapshot { } pub fn max_point(&self) -> DisplayPoint { - DisplayPoint(self.wraps_snapshot.max_point()) + DisplayPoint(self.blocks_snapshot.max_point()) } pub fn chunks_at(&self, display_row: u32) -> wrap_map::Chunks { @@ -172,8 +210,8 @@ impl DisplayMapSnapshot { pub fn highlighted_chunks_for_rows( &mut self, display_rows: Range, - ) -> wrap_map::HighlightedChunks { - self.wraps_snapshot + ) -> block_map::HighlightedChunks { + self.blocks_snapshot .highlighted_chunks_for_rows(display_rows) } @@ -217,7 +255,7 @@ impl DisplayMapSnapshot { } pub fn clip_point(&self, point: DisplayPoint, bias: Bias) -> DisplayPoint { - DisplayPoint(self.wraps_snapshot.clip_point(point.0, bias)) + DisplayPoint(self.blocks_snapshot.clip_point(point.0, bias)) } pub fn folds_in_range<'a, T>( @@ -235,9 +273,10 @@ impl DisplayMapSnapshot { } pub fn is_line_folded(&self, display_row: u32) -> bool { - let wrap_point = DisplayPoint::new(display_row, 0).0; - let row = self.wraps_snapshot.to_tab_point(wrap_point).row(); - self.folds_snapshot.is_line_folded(row) + let block_point = BlockPoint(Point::new(display_row, 0)); + let wrap_point = self.blocks_snapshot.to_wrap_point(block_point); + let tab_point = self.wraps_snapshot.to_tab_point(wrap_point); + self.folds_snapshot.is_line_folded(tab_point.row()) } pub fn soft_wrap_indent(&self, display_row: u32) -> Option { @@ -295,11 +334,11 @@ impl DisplayMapSnapshot { } #[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)] -pub struct DisplayPoint(wrap_map::WrapPoint); +pub struct DisplayPoint(BlockPoint); impl DisplayPoint { pub fn new(row: u32, column: u32) -> Self { - Self(wrap_map::WrapPoint::new(row, column)) + Self(BlockPoint(Point::new(row, column))) } pub fn zero() -> Self { @@ -312,29 +351,31 @@ impl DisplayPoint { } pub fn row(self) -> u32 { - self.0.row() + self.0.row } pub fn column(self) -> u32 { - self.0.column() + self.0.column } pub fn row_mut(&mut self) -> &mut u32 { - self.0.row_mut() + &mut self.0.row } pub fn column_mut(&mut self) -> &mut u32 { - self.0.column_mut() + &mut self.0.column } pub fn to_buffer_point(self, map: &DisplayMapSnapshot, bias: Bias) -> Point { - let unwrapped_point = map.wraps_snapshot.to_tab_point(self.0); + let unblocked_point = map.blocks_snapshot.to_wrap_point(self.0); + let unwrapped_point = map.wraps_snapshot.to_tab_point(unblocked_point); let unexpanded_point = map.tabs_snapshot.to_fold_point(unwrapped_point, bias).0; unexpanded_point.to_buffer_point(&map.folds_snapshot) } pub fn to_buffer_offset(self, map: &DisplayMapSnapshot, bias: Bias) -> usize { - let unwrapped_point = map.wraps_snapshot.to_tab_point(self.0); + let unblocked_point = map.blocks_snapshot.to_wrap_point(self.0); + let unwrapped_point = map.wraps_snapshot.to_tab_point(unblocked_point); let unexpanded_point = map.tabs_snapshot.to_fold_point(unwrapped_point, bias).0; unexpanded_point.to_buffer_offset(&map.folds_snapshot) } @@ -345,7 +386,8 @@ impl ToDisplayPoint for Point { let fold_point = self.to_fold_point(&map.folds_snapshot, bias); let tab_point = map.tabs_snapshot.to_tab_point(fold_point); let wrap_point = map.wraps_snapshot.to_wrap_point(tab_point); - DisplayPoint(wrap_point) + let block_point = map.blocks_snapshot.to_block_point(wrap_point); + DisplayPoint(block_point) } } diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index 21b41e48d160bdd93448caebd28fd03868f1c3b2..91193c942b655243104028fe9e73053b36d18e05 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -16,7 +16,7 @@ use std::{ }; use sum_tree::SumTree; -struct BlockMap { +pub struct BlockMap { buffer: ModelHandle, next_block_id: AtomicUsize, wrap_snapshot: Mutex, @@ -24,18 +24,18 @@ struct BlockMap { transforms: Mutex>, } -struct BlockMapWriter<'a>(&'a mut BlockMap); +pub struct BlockMapWriter<'a>(&'a mut BlockMap); -struct BlockSnapshot { +pub struct BlockSnapshot { wrap_snapshot: WrapSnapshot, transforms: SumTree, } #[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)] -struct BlockId(usize); +pub struct BlockId(usize); #[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)] -pub struct BlockPoint(super::Point); +pub struct BlockPoint(pub super::Point); #[derive(Debug)] struct Block { @@ -47,7 +47,7 @@ struct Block { } #[derive(Clone)] -struct BlockProperties +pub struct BlockProperties where P: Clone, T: Clone, @@ -76,7 +76,7 @@ struct TransformSummary { output_rows: u32, } -struct HighlightedChunks<'a> { +pub struct HighlightedChunks<'a> { transforms: sum_tree::Cursor<'a, Transform, (OutputRow, InputRow)>, input_chunks: wrap_map::HighlightedChunks<'a>, input_chunk: HighlightedChunk<'a>, @@ -100,7 +100,7 @@ struct InputRow(u32); struct OutputRow(u32); impl BlockMap { - fn new(buffer: ModelHandle, wrap_snapshot: WrapSnapshot) -> Self { + pub fn new(buffer: ModelHandle, wrap_snapshot: WrapSnapshot) -> Self { Self { buffer, next_block_id: AtomicUsize::new(0), @@ -113,7 +113,7 @@ impl BlockMap { } } - fn read( + pub fn read( &self, wrap_snapshot: WrapSnapshot, edits: Vec, @@ -127,7 +127,7 @@ impl BlockMap { } } - fn write( + pub fn write( &mut self, wrap_snapshot: WrapSnapshot, edits: Vec, @@ -234,21 +234,17 @@ impl BlockMap { } } -impl BlockPoint { - fn row(&self) -> u32 { - self.0.row - } - - fn row_mut(&mut self) -> &mut u32 { - &mut self.0.row - } +impl std::ops::Deref for BlockPoint { + type Target = Point; - fn column(&self) -> u32 { - self.0.column + fn deref(&self) -> &Self::Target { + &self.0 } +} - fn column_mut(&mut self) -> &mut u32 { - &mut self.0.column +impl std::ops::DerefMut for BlockPoint { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.0 } } @@ -308,8 +304,8 @@ impl<'a> BlockMapWriter<'a> { ids } - pub fn remove(&self, ids: HashSet) { - // + pub fn remove(&mut self, _: HashSet, _: &AppContext) { + todo!() } } @@ -347,9 +343,13 @@ impl BlockSnapshot { self.to_block_point(self.wrap_snapshot.max_point()) } + pub fn clip_point(&self, point: BlockPoint, bias: Bias) -> BlockPoint { + todo!() + } + pub fn to_block_point(&self, wrap_point: WrapPoint) -> BlockPoint { let mut cursor = self.transforms.cursor::<(InputRow, OutputRow)>(); - cursor.seek(&InputRow(wrap_point.row()), Bias::Left, &()); + cursor.seek(&InputRow(wrap_point.row()), Bias::Right, &()); while let Some(item) = cursor.item() { if item.is_isomorphic() { break; @@ -625,6 +625,10 @@ mod tests { snapshot.text(), "aaa\nBLOCK 1\nBLOCK 2\nbbb\nccc\nddd\nBLOCK 3\n" ); + assert_eq!( + snapshot.to_block_point(WrapPoint::new(1, 0)), + BlockPoint(Point::new(3, 0)) + ); // Insert a line break, separating two block decorations into separate // lines. @@ -717,13 +721,8 @@ mod tests { wrap_map.sync(tabs_snapshot, tab_edits, cx) }); let mut block_map = block_map.write(wraps_snapshot, wrap_edits, cx); - - expected_blocks.extend( - block_map - .insert(block_properties.clone(), cx) - .into_iter() - .zip(block_properties), - ); + let block_ids = block_map.insert(block_properties.clone(), cx); + expected_blocks.extend(block_ids.into_iter().zip(block_properties)); } 40..=59 => { let block_count = rng.gen_range(1..=4.min(expected_blocks.len())); @@ -740,9 +739,8 @@ mod tests { let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| { wrap_map.sync(tabs_snapshot, tab_edits, cx) }); - let block_map = block_map.write(wraps_snapshot, wrap_edits, cx); - - block_map.remove(block_ids_to_remove); + let mut block_map = block_map.write(wraps_snapshot, wrap_edits, cx); + block_map.remove(block_ids_to_remove, cx); } _ => { buffer.update(cx, |buffer, _| buffer.randomly_edit(&mut rng, 5)); From c9cbeafc05087edccfa8a9eed69034e0332c3016 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Sat, 13 Nov 2021 17:44:09 -0700 Subject: [PATCH 035/109] Start on BlockSnapshot::clip_point Not sure it works yet. Ran into another failure in the unit tests. --- crates/editor/src/display_map/block_map.rs | 54 ++++++++++++++++++---- 1 file changed, 45 insertions(+), 9 deletions(-) diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index 91193c942b655243104028fe9e73053b36d18e05..0862e06ce4432e5544183a941b9128508f54c89b 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -234,6 +234,12 @@ impl BlockMap { } } +impl BlockPoint { + fn new(row: u32, column: u32) -> Self { + Self(Point::new(row, column)) + } +} + impl std::ops::Deref for BlockPoint { type Target = Point; @@ -323,9 +329,7 @@ impl BlockSnapshot { let (input_start, output_start) = cursor.start(); let row_overshoot = rows.start - output_start.0; let input_start_row = input_start.0 + row_overshoot; - let input_end_row = self - .to_wrap_point(BlockPoint(Point::new(rows.end, 0))) - .row(); + let input_end_row = self.to_wrap_point(BlockPoint::new(rows.end, 0)).row(); let input_chunks = self .wrap_snapshot .highlighted_chunks_for_rows(input_start_row..input_end_row); @@ -344,7 +348,42 @@ impl BlockSnapshot { } pub fn clip_point(&self, point: BlockPoint, bias: Bias) -> BlockPoint { - todo!() + let mut cursor = self.transforms.cursor::<(OutputRow, InputRow)>(); + cursor.seek(&OutputRow(point.row), Bias::Right, &()); + if let Some(transform) = cursor.item() { + if transform.is_isomorphic() { + let (output_start_row, input_start_row) = cursor.start(); + let output_overshoot = point.row - output_start_row.0; + let input_point = self.wrap_snapshot.clip_point( + WrapPoint::new(input_start_row.0 + output_overshoot, point.column), + bias, + ); + let input_overshoot = input_point.row() - input_start_row.0; + BlockPoint::new(output_start_row.0 + input_overshoot, input_point.column()) + } else { + if bias == Bias::Left && cursor.start().1 .0 > 0 + || cursor.end(&()).1 .0 == self.wrap_snapshot.max_point().row() + { + loop { + cursor.prev(&()); + let transform = cursor.item().unwrap(); + if transform.is_isomorphic() { + return BlockPoint::new(cursor.end(&()).0 .0 - 1, 0); + } + } + } else { + loop { + cursor.next(&()); + let transform = cursor.item().unwrap(); + if transform.is_isomorphic() { + return BlockPoint::new(cursor.start().0 .0, 0); + } + } + } + } + } else { + self.max_point() + } } pub fn to_block_point(&self, wrap_point: WrapPoint) -> BlockPoint { @@ -358,10 +397,7 @@ impl BlockSnapshot { } let (input_start, output_start) = cursor.start(); let row_overshoot = wrap_point.row() - input_start.0; - BlockPoint(Point::new( - output_start.0 + row_overshoot, - wrap_point.column(), - )) + BlockPoint::new(output_start.0 + row_overshoot, wrap_point.column()) } pub fn to_wrap_point(&self, block_point: BlockPoint) -> WrapPoint { @@ -627,7 +663,7 @@ mod tests { ); assert_eq!( snapshot.to_block_point(WrapPoint::new(1, 0)), - BlockPoint(Point::new(3, 0)) + BlockPoint::new(3, 0) ); // Insert a line break, separating two block decorations into separate From d6bc05cad05c9104ff56d498e25f112d00e7fc71 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Sat, 13 Nov 2021 18:19:21 -0700 Subject: [PATCH 036/109] Fix BlockMap unit test by skipping below blocks when advancing transforms --- crates/editor/src/display_map/block_map.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index 0862e06ce4432e5544183a941b9128508f54c89b..e84f3fe41db62e9aad80d096dcb747da3c2f2b16 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -161,6 +161,13 @@ impl BlockMap { if edit.old.end > cursor.start().0 { cursor.seek(&InputRow(edit.old.end), Bias::Left, &()); cursor.next(&()); + while let Some(item) = cursor.item() { + if item.is_isomorphic() { + break; + } else { + cursor.next(&()); + } + } let transform_end = cursor.start().0; edit.new.end += transform_end - edit.old.end; edit.old.end = transform_end; From b6e6dafca7da592551615fb573f5f6539164b09b Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Sat, 13 Nov 2021 19:50:42 -0700 Subject: [PATCH 037/109] Account for trailing below blocks in BlockSnapshot::max_point --- crates/editor/src/display_map/block_map.rs | 9 ++++++++- crates/editor/src/display_map/wrap_map.rs | 5 ----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index e84f3fe41db62e9aad80d096dcb747da3c2f2b16..733c3e45c01cb26c242679be8a54d88c0a630209 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -351,7 +351,14 @@ impl BlockSnapshot { } pub fn max_point(&self) -> BlockPoint { - self.to_block_point(self.wrap_snapshot.max_point()) + let last_transform = self.transforms.last().unwrap(); + if let Some(block) = &last_transform.block { + let row = self.transforms.summary().output_rows - 1; + let column = block.text.summary().lines.column; + BlockPoint::new(row, column) + } else { + self.to_block_point(self.wrap_snapshot.max_point()) + } } pub fn clip_point(&self, point: BlockPoint, bias: Bias) -> BlockPoint { diff --git a/crates/editor/src/display_map/wrap_map.rs b/crates/editor/src/display_map/wrap_map.rs index 6faadc3d257202d9c3ccd6dc241152e8bc2624b2..c40088cb007295efc95b5fd6c3e2e4963c8550f5 100644 --- a/crates/editor/src/display_map/wrap_map.rs +++ b/crates/editor/src/display_map/wrap_map.rs @@ -944,11 +944,6 @@ impl WrapPoint { Self(super::Point::new(row, column)) } - #[cfg(test)] - pub fn is_zero(&self) -> bool { - self.0.is_zero() - } - pub fn row(self) -> u32 { self.0.row } From 58321537123dded796e51e684f90dd0a4bf5d41f Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Sat, 13 Nov 2021 19:52:04 -0700 Subject: [PATCH 038/109] Hack: Synthesize a newline before blocks below the last line of the buffer --- crates/editor/src/display_map/block_map.rs | 39 +++++++++++++++++----- 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index 733c3e45c01cb26c242679be8a54d88c0a630209..838bcd93b52bf34fa9d0634ff6419d20032db976 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -138,7 +138,7 @@ impl BlockMap { BlockMapWriter(self) } - fn apply_edits(&self, wrap_snapshot: &WrapSnapshot, edits: Vec, cx: &AppContext) { + fn apply_edits(&self, _: &WrapSnapshot, edits: Vec, cx: &AppContext) { let buffer = self.buffer.read(cx); let mut transforms = self.transforms.lock(); let mut new_transforms = SumTree::new(); @@ -325,7 +325,7 @@ impl<'a> BlockMapWriter<'a> { impl BlockSnapshot { #[cfg(test)] fn text(&mut self) -> String { - self.highlighted_chunks_for_rows(0..(self.max_point().0.row + 1)) + self.highlighted_chunks_for_rows(0..self.max_point().0.row + 1) .map(|chunk| chunk.text) .collect() } @@ -481,7 +481,19 @@ impl<'a> Iterator for HighlightedChunks<'a> { } if self.input_chunk.text.is_empty() { - self.input_chunk = self.input_chunks.next()?; + if let Some(input_chunk) = self.input_chunks.next() { + self.input_chunk = input_chunk; + } else { + self.output_row += 1; + self.transforms.next(&()); + if self.output_row < self.max_output_row { + let mut chunk = self.input_chunk.clone(); + chunk.text = "\n"; + return Some(chunk); + } else { + return None; + } + } } let transform_end = self.transforms.end(&()).0 .0; @@ -539,9 +551,9 @@ impl<'a> Iterator for BlockChunks<'a> { let chunk = self.chunk?; let mut chunk_len = chunk.len(); - let mut highlight_style = None; - if let Some((run_len, style)) = self.runs.peek() { - highlight_style = Some(style.clone()); + // let mut highlight_style = None; + if let Some((run_len, _)) = self.runs.peek() { + // highlight_style = Some(style.clone()); let run_end_in_chunk = self.run_start + run_len - self.offset; if run_end_in_chunk <= chunk_len { chunk_len = run_end_in_chunk; @@ -772,11 +784,20 @@ mod tests { }); let mut block_map = block_map.write(wraps_snapshot, wrap_edits, cx); let block_ids = block_map.insert(block_properties.clone(), cx); - expected_blocks.extend(block_ids.into_iter().zip(block_properties)); + for (block_id, props) in block_ids.into_iter().zip(block_properties) { + log::info!( + "inserted block {:?} {:?} {:?} with text {:?}", + block_id.0, + props.disposition, + props.position.to_point(buffer.read(cx)), + props.text.to_string() + ); + expected_blocks.push((block_id, props)); + } } - 40..=59 => { + 40..=59 if !expected_blocks.is_empty() => { let block_count = rng.gen_range(1..=4.min(expected_blocks.len())); - let block_ids_to_remove = (0..block_count) + let block_ids_to_remove = (0..=block_count) .map(|_| { expected_blocks .remove(rng.gen_range(0..expected_blocks.len())) From e644c0876eaf05797385511f4a04e8d186cef9c3 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Sun, 14 Nov 2021 08:29:41 -0700 Subject: [PATCH 039/109] WIP: Start moving BlockMap to a 2d indexing scheme --- crates/editor/src/display_map/block_map.rs | 116 ++++++++++----------- crates/editor/src/display_map/wrap_map.rs | 11 +- 2 files changed, 64 insertions(+), 63 deletions(-) diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index 838bcd93b52bf34fa9d0634ff6419d20032db976..fd8f056868d716daca303bca9db46f49c4cc5bd3 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -1,4 +1,4 @@ -use super::wrap_map::{self, Edit as WrapEdit, Snapshot as WrapSnapshot, WrapPoint}; +use super::wrap_map::{self, Edit as WrapEdit, Snapshot as WrapSnapshot, TextSummary, WrapPoint}; use buffer::{rope, Anchor, Bias, Edit, Point, Rope, ToOffset, ToPoint as _}; use gpui::{fonts::HighlightStyle, AppContext, ModelHandle}; use language::{Buffer, HighlightedChunk}; @@ -70,14 +70,14 @@ struct Transform { block: Option>, } -#[derive(Copy, Clone, Debug, Default)] +#[derive(Clone, Debug, Default)] struct TransformSummary { - input_rows: u32, - output_rows: u32, + input: Point, + output: Point, } pub struct HighlightedChunks<'a> { - transforms: sum_tree::Cursor<'a, Transform, (OutputRow, InputRow)>, + transforms: sum_tree::Cursor<'a, Transform, (BlockPoint, WrapPoint)>, input_chunks: wrap_map::HighlightedChunks<'a>, input_chunk: HighlightedChunk<'a>, block_chunks: Option>, @@ -93,12 +93,6 @@ struct BlockChunks<'a> { offset: usize, } -#[derive(Copy, Clone, Default, Debug, PartialEq, Eq, PartialOrd, Ord)] -struct InputRow(u32); - -#[derive(Copy, Clone, Default, Debug, PartialEq, Eq, PartialOrd, Ord)] -struct OutputRow(u32); - impl BlockMap { pub fn new(buffer: ModelHandle, wrap_snapshot: WrapSnapshot) -> Self { Self { @@ -106,7 +100,7 @@ impl BlockMap { next_block_id: AtomicUsize::new(0), blocks: Vec::new(), transforms: Mutex::new(SumTree::from_item( - Transform::isomorphic(wrap_snapshot.max_point().row() + 1), + Transform::isomorphic(wrap_snapshot.text_summary()), &(), )), wrap_snapshot: Mutex::new(wrap_snapshot), @@ -138,28 +132,28 @@ impl BlockMap { BlockMapWriter(self) } - fn apply_edits(&self, _: &WrapSnapshot, edits: Vec, cx: &AppContext) { + fn apply_edits(&self, wrap_snapshot: &WrapSnapshot, edits: Vec, cx: &AppContext) { let buffer = self.buffer.read(cx); let mut transforms = self.transforms.lock(); let mut new_transforms = SumTree::new(); - let mut cursor = transforms.cursor::(); + let mut cursor = transforms.cursor::(); let mut edits = edits.into_iter().peekable(); let mut last_block_ix = 0; let mut blocks_in_edit = Vec::new(); while let Some(mut edit) = edits.next() { new_transforms.push_tree( - cursor.slice(&InputRow(edit.old.start), Bias::Left, &()), + cursor.slice(&WrapPoint::new(edit.old.start, 0), Bias::Left, &()), &(), ); let transform_start = cursor.start().0; - edit.new.start -= edit.old.start - transform_start; - edit.old.start = transform_start; + edit.new.start -= edit.old.start - transform_start.row; + edit.old.start = transform_start.row; loop { - if edit.old.end > cursor.start().0 { - cursor.seek(&InputRow(edit.old.end), Bias::Left, &()); + if edit.old.end > cursor.start().0.row { + cursor.seek(&WrapPoint::new(edit.old.end, 0), Bias::Left, &()); cursor.next(&()); while let Some(item) = cursor.item() { if item.is_isomorphic() { @@ -169,8 +163,8 @@ impl BlockMap { } } let transform_end = cursor.start().0; - edit.new.end += transform_end - edit.old.end; - edit.old.end = transform_end; + edit.new.end += transform_end.row - edit.old.end; + edit.old.end = transform_end.row; } if let Some(next_edit) = edits.peek() { @@ -188,7 +182,7 @@ impl BlockMap { } let start_anchor = buffer.anchor_before(Point::new(edit.new.start, 0)); - let end_anchor = buffer.anchor_after(Point::new(edit.new.end, 0)); + let end_anchor = buffer.anchor_before(Point::new(edit.new.end, 0)); let start_block_ix = match self.blocks[last_block_ix..] .binary_search_by(|probe| probe.position.cmp(&start_anchor, buffer).unwrap()) { @@ -209,28 +203,37 @@ impl BlockMap { ); blocks_in_edit.sort_unstable_by_key(|(row, block)| (*row, block.disposition)); - for (row, block) in blocks_in_edit.iter().copied() { - let insertion_row = if block.disposition.is_above() { - row + for (block_row, block) in blocks_in_edit.iter().copied() { + let new_transforms_end = new_transforms.summary().input; + if block.disposition.is_above() { + if block_row > new_transforms_end.row { + new_transforms.push( + Transform::isomorphic(Point::new(block_row, 0) - new_transforms_end), + &(), + ); + } } else { - row + 1 - }; - - let new_transforms_end = new_transforms.summary().input_rows; - if new_transforms_end < insertion_row { - new_transforms.push( - Transform::isomorphic(insertion_row - new_transforms_end), - &(), - ); + if block_row >= new_transforms_end.row { + new_transforms.push( + Transform::isomorphic( + Point::new(block_row, wrap_snapshot.line_len(block_row)) + - new_transforms_end, + ), + &(), + ); + } } new_transforms.push(Transform::block(block.clone()), &()); } - let new_transforms_end = new_transforms.summary().input_rows; - if new_transforms_end < edit.new.end { + let new_transforms_end = new_transforms.summary().input; + if new_transforms_end.row < edit.new.end { + // TODO: Should we just report the wrap edits in 2d so we can skip this max point check? + let edit_new_end_point = + cmp::min(Point::new(edit.new.end, 0), wrap_snapshot.max_point().0); new_transforms.push( - Transform::isomorphic(edit.new.end - new_transforms_end), + Transform::isomorphic(edit_new_end_point - new_transforms_end), &(), ); } @@ -331,8 +334,8 @@ impl BlockSnapshot { } pub fn highlighted_chunks_for_rows(&mut self, rows: Range) -> HighlightedChunks { - let mut cursor = self.transforms.cursor::<(OutputRow, InputRow)>(); - cursor.seek(&OutputRow(rows.start), Bias::Right, &()); + let mut cursor = self.transforms.cursor::<(BlockPoint, WrapPoint)>(); + cursor.seek(&BlockPoint::new(rows.start, 0), Bias::Right, &()); let (input_start, output_start) = cursor.start(); let row_overshoot = rows.start - output_start.0; let input_start_row = input_start.0 + row_overshoot; @@ -351,14 +354,7 @@ impl BlockSnapshot { } pub fn max_point(&self) -> BlockPoint { - let last_transform = self.transforms.last().unwrap(); - if let Some(block) = &last_transform.block { - let row = self.transforms.summary().output_rows - 1; - let column = block.text.summary().lines.column; - BlockPoint::new(row, column) - } else { - self.to_block_point(self.wrap_snapshot.max_point()) - } + BlockPoint(self.transforms.summary().output) } pub fn clip_point(&self, point: BlockPoint, bias: Bias) -> BlockPoint { @@ -424,11 +420,11 @@ impl BlockSnapshot { } impl Transform { - fn isomorphic(rows: u32) -> Self { + fn isomorphic(lines: Point) -> Self { Self { summary: TransformSummary { - input_rows: rows, - output_rows: rows, + input: lines, + output: lines, }, block: None, } @@ -437,8 +433,8 @@ impl Transform { fn block(block: Arc) -> Self { Self { summary: TransformSummary { - input_rows: 0, - output_rows: block.text.summary().lines.row, + input: Default::default(), + output: block.text.summary().lines, }, block: Some(block), } @@ -590,20 +586,20 @@ impl sum_tree::Summary for TransformSummary { type Context = (); fn add_summary(&mut self, summary: &Self, _: &()) { - self.input_rows += summary.input_rows; - self.output_rows += summary.output_rows; + self.input += summary.input; + self.output += summary.output; } } -impl<'a> sum_tree::Dimension<'a, TransformSummary> for InputRow { +impl<'a> sum_tree::Dimension<'a, TransformSummary> for WrapPoint { fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) { - self.0 += summary.input_rows; + self.0 += summary.input.lines; } } -impl<'a> sum_tree::Dimension<'a, TransformSummary> for OutputRow { +impl<'a> sum_tree::Dimension<'a, TransformSummary> for BlockPoint { fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) { - self.0 += summary.output_rows; + self.0 += summary.output.lines; } } @@ -825,8 +821,8 @@ mod tests { }); let mut blocks_snapshot = block_map.read(wraps_snapshot.clone(), wrap_edits, cx); assert_eq!( - blocks_snapshot.transforms.summary().input_rows, - wraps_snapshot.max_point().row() + 1 + blocks_snapshot.transforms.summary().input, + wraps_snapshot.max_point().0 ); let buffer = buffer.read(cx); diff --git a/crates/editor/src/display_map/wrap_map.rs b/crates/editor/src/display_map/wrap_map.rs index c40088cb007295efc95b5fd6c3e2e4963c8550f5..ffc27815205869fdd61b55659f9ecce603236ba2 100644 --- a/crates/editor/src/display_map/wrap_map.rs +++ b/crates/editor/src/display_map/wrap_map.rs @@ -1,7 +1,7 @@ use super::{ fold_map, patch::Patch, - tab_map::{self, Edit as TabEdit, Snapshot as TabSnapshot, TabPoint, TextSummary}, + tab_map::{self, Edit as TabEdit, Snapshot as TabSnapshot, TabPoint}, }; use gpui::{ fonts::FontId, text_layout::LineWrapper, Entity, ModelContext, ModelHandle, MutableAppContext, @@ -13,6 +13,7 @@ use smol::future::yield_now; use std::{collections::VecDeque, mem, ops::Range, time::Duration}; use sum_tree::{Bias, Cursor, SumTree}; +pub use super::tab_map::TextSummary; pub type Edit = buffer::Edit; pub struct WrapMap { @@ -49,7 +50,7 @@ struct TransformSummary { } #[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)] -pub struct WrapPoint(super::Point); +pub struct WrapPoint(pub super::Point); pub struct Chunks<'a> { input_chunks: tab_map::Chunks<'a>, @@ -589,8 +590,12 @@ impl Snapshot { } } + pub fn text_summary(&self) -> TextSummary { + self.transforms.summary().output + } + pub fn max_point(&self) -> WrapPoint { - self.to_wrap_point(self.tab_snapshot.max_point()) + WrapPoint(self.transforms.summary().output.lines) } pub fn line_len(&self, row: u32) -> u32 { From 3154ccbafec54258462d68f8c656768ce32f32ef Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Sun, 14 Nov 2021 16:24:40 -0700 Subject: [PATCH 040/109] WIP --- crates/editor/src/display_map/block_map.rs | 28 ++++++++++------------ 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index fd8f056868d716daca303bca9db46f49c4cc5bd3..b22d4149351d40a14d8958e7587c2c64c0c3e41d 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -81,7 +81,7 @@ pub struct HighlightedChunks<'a> { input_chunks: wrap_map::HighlightedChunks<'a>, input_chunk: HighlightedChunk<'a>, block_chunks: Option>, - output_row: u32, + output_position: Point, max_output_row: u32, } @@ -100,7 +100,7 @@ impl BlockMap { next_block_id: AtomicUsize::new(0), blocks: Vec::new(), transforms: Mutex::new(SumTree::from_item( - Transform::isomorphic(wrap_snapshot.text_summary()), + Transform::isomorphic(wrap_snapshot.text_summary().lines), &(), )), wrap_snapshot: Mutex::new(wrap_snapshot), @@ -292,14 +292,12 @@ impl<'a> BlockMapWriter<'a> { { Ok(ix) | Err(ix) => ix, }; - let mut text = block.text.into(); - text.push("\n"); self.0.blocks.insert( block_ix, Arc::new(Block { id, position, - text, + text: block.text.into(), runs: block.runs, disposition: block.disposition, }), @@ -348,7 +346,7 @@ impl BlockSnapshot { input_chunk: Default::default(), block_chunks: None, transforms: cursor, - output_row: rows.start, + output_position: rows.start, max_output_row: rows.end, } } @@ -449,13 +447,13 @@ impl<'a> Iterator for HighlightedChunks<'a> { type Item = HighlightedChunk<'a>; fn next(&mut self) -> Option { - if self.output_row >= self.max_output_row { + if self.output_position >= self.max_output_row { return None; } if let Some(block_chunks) = self.block_chunks.as_mut() { if let Some(block_chunk) = block_chunks.next() { - self.output_row += block_chunk.text.matches('\n').count() as u32; + self.output_position += block_chunk.text.matches('\n').count() as u32; return Some(block_chunk); } else { self.block_chunks.take(); @@ -466,12 +464,12 @@ impl<'a> Iterator for HighlightedChunks<'a> { if let Some(block) = transform.block.as_ref() { let block_start = self.transforms.start().0 .0; let block_end = self.transforms.end(&()).0 .0; - let start_row_in_block = self.output_row - block_start; + let start_row_in_block = self.output_position - block_start; let end_row_in_block = cmp::min(self.max_output_row, block_end) - block_start; self.transforms.next(&()); let mut block_chunks = BlockChunks::new(block, start_row_in_block..end_row_in_block); if let Some(block_chunk) = block_chunks.next() { - self.output_row += block_chunk.text.matches('\n').count() as u32; + self.output_position += block_chunk.text.matches('\n').count() as u32; return Some(block_chunk); } } @@ -480,9 +478,9 @@ impl<'a> Iterator for HighlightedChunks<'a> { if let Some(input_chunk) = self.input_chunks.next() { self.input_chunk = input_chunk; } else { - self.output_row += 1; + self.output_position += 1; self.transforms.next(&()); - if self.output_row < self.max_output_row { + if self.output_position < self.max_output_row { let mut chunk = self.input_chunk.clone(); chunk.text = "\n"; return Some(chunk); @@ -494,11 +492,11 @@ impl<'a> Iterator for HighlightedChunks<'a> { let transform_end = self.transforms.end(&()).0 .0; let (prefix_rows, prefix_bytes) = - offset_for_row(self.input_chunk.text, transform_end - self.output_row); - self.output_row += prefix_rows; + offset_for_row(self.input_chunk.text, transform_end - self.output_position); + self.output_position += prefix_rows; let (prefix, suffix) = self.input_chunk.text.split_at(prefix_bytes); self.input_chunk.text = suffix; - if self.output_row == transform_end { + if self.output_position == transform_end { self.transforms.next(&()); } From 296944e34d2b902754b1abb10f7f890446dc03bf Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 15 Nov 2021 16:30:58 +0100 Subject: [PATCH 041/109] Make `BlockMap` unit test pass with 2d coordinates Co-Authored-By: Nathan Sobo --- crates/buffer/src/point.rs | 9 + crates/buffer/src/rope.rs | 17 +- crates/editor/src/display_map/block_map.rs | 183 ++++++++++++--------- 3 files changed, 127 insertions(+), 82 deletions(-) diff --git a/crates/buffer/src/point.rs b/crates/buffer/src/point.rs index 5e62176956cfb378089b465e6778425cc40ec183..93cc2c60076dc949245e62697b92367c9f764cf0 100644 --- a/crates/buffer/src/point.rs +++ b/crates/buffer/src/point.rs @@ -23,6 +23,15 @@ impl Point { Point::new(0, 0) } + pub fn from_str(s: &str) -> Self { + let mut point = Self::zero(); + for (row, line) in s.split('\n').enumerate() { + point.row = row as u32; + point.column = line.len() as u32; + } + point + } + pub fn is_zero(&self) -> bool { self.row == 0 && self.column == 0 } diff --git a/crates/buffer/src/rope.rs b/crates/buffer/src/rope.rs index 24186c8056bba714e477f8935b92db6ba0636fa5..1ceada807238d67472f035e5fc12aaedf309f04d 100644 --- a/crates/buffer/src/rope.rs +++ b/crates/buffer/src/rope.rs @@ -3,7 +3,7 @@ use crate::PointUtf16; use super::Point; use arrayvec::ArrayString; use smallvec::SmallVec; -use std::{cmp, fmt, ops::Range, str}; +use std::{cmp, fmt, mem, ops::Range, str}; use sum_tree::{Bias, Dimension, SumTree}; #[cfg(test)] @@ -89,6 +89,21 @@ impl Rope { self.check_invariants(); } + pub fn push_front(&mut self, text: &str) { + let suffix = mem::replace(self, Rope::from(text)); + self.append(suffix); + } + + pub fn starts_with(&self, text: &str) -> bool { + self.chunks().flat_map(|c| c.bytes()).eq(text.bytes()) + } + + pub fn ends_with(&self, text: &str) -> bool { + self.reversed_chunks_in_range(0..self.len()) + .flat_map(|c| c.bytes().rev()) + .eq(text.bytes().rev()) + } + fn check_invariants(&self) { #[cfg(test)] { diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index b22d4149351d40a14d8958e7587c2c64c0c3e41d..14e7be214722f62e273ae4ab778fa5bb5bb0d9a6 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -4,7 +4,7 @@ use gpui::{fonts::HighlightStyle, AppContext, ModelHandle}; use language::{Buffer, HighlightedChunk}; use parking_lot::Mutex; use std::{ - cmp, + cmp::{self, Ordering}, collections::HashSet, iter, ops::Range, @@ -81,7 +81,7 @@ pub struct HighlightedChunks<'a> { input_chunks: wrap_map::HighlightedChunks<'a>, input_chunk: HighlightedChunk<'a>, block_chunks: Option>, - output_position: Point, + output_position: BlockPoint, max_output_row: u32, } @@ -147,6 +147,7 @@ impl BlockMap { &(), ); + dbg!("collapsed edit", &edit); let transform_start = cursor.start().0; edit.new.start -= edit.old.start - transform_start.row; edit.old.start = transform_start.row; @@ -154,17 +155,22 @@ impl BlockMap { loop { if edit.old.end > cursor.start().0.row { cursor.seek(&WrapPoint::new(edit.old.end, 0), Bias::Left, &()); - cursor.next(&()); - while let Some(item) = cursor.item() { - if item.is_isomorphic() { - break; + if cursor.item().map_or(false, |t| t.is_isomorphic()) { + if let Some(prev_transform) = cursor.prev_item() { + if prev_transform.block_disposition() != Some(BlockDisposition::Below) + || edit.old.end > cursor.start().row() + 1 + { + cursor.next(&()); + } } else { cursor.next(&()); } } - let transform_end = cursor.start().0; - edit.new.end += transform_end.row - edit.old.end; - edit.old.end = transform_end.row; + + let transform_end_row = cursor.start().0.row + 1; + cursor.seek(&WrapPoint::new(transform_end_row, 0), Bias::Left, &()); + edit.new.end += transform_end_row - edit.old.end; + edit.old.end = transform_end_row; } if let Some(next_edit) = edits.peek() { @@ -181,6 +187,7 @@ impl BlockMap { } } + dbg!("expanded edit", &edit); let start_anchor = buffer.anchor_before(Point::new(edit.new.start, 0)); let end_anchor = buffer.anchor_before(Point::new(edit.new.end, 0)); let start_block_ix = match self.blocks[last_block_ix..] @@ -188,9 +195,13 @@ impl BlockMap { { Ok(ix) | Err(ix) => last_block_ix + ix, }; - let end_block_ix = match self.blocks[start_block_ix..] - .binary_search_by(|probe| probe.position.cmp(&end_anchor, buffer).unwrap()) - { + let end_block_ix = match self.blocks[start_block_ix..].binary_search_by(|probe| { + probe + .position + .cmp(&end_anchor, buffer) + .unwrap() + .then(Ordering::Greater) + }) { Ok(ix) | Err(ix) => start_block_ix + ix, }; last_block_ix = end_block_ix; @@ -202,6 +213,7 @@ impl BlockMap { .map(|block| (block.position.to_point(buffer).row, block)), ); blocks_in_edit.sort_unstable_by_key(|(row, block)| (*row, block.disposition)); + dbg!(&blocks_in_edit); for (block_row, block) in blocks_in_edit.iter().copied() { let new_transforms_end = new_transforms.summary().input; @@ -228,7 +240,7 @@ impl BlockMap { } let new_transforms_end = new_transforms.summary().input; - if new_transforms_end.row < edit.new.end { + if new_transforms_end.row < edit.new.end - 1 { // TODO: Should we just report the wrap edits in 2d so we can skip this max point check? let edit_new_end_point = cmp::min(Point::new(edit.new.end, 0), wrap_snapshot.max_point().0); @@ -292,12 +304,23 @@ impl<'a> BlockMapWriter<'a> { { Ok(ix) | Err(ix) => ix, }; + let mut text = block.text.into(); + if block.disposition.is_above() { + if !text.ends_with("\n") { + text.push("\n"); + } + } else { + if !text.starts_with("\n") { + text.push_front("\n"); + } + } + self.0.blocks.insert( block_ix, Arc::new(Block { id, position, - text: block.text.into(), + text, runs: block.runs, disposition: block.disposition, }), @@ -333,10 +356,11 @@ impl BlockSnapshot { pub fn highlighted_chunks_for_rows(&mut self, rows: Range) -> HighlightedChunks { let mut cursor = self.transforms.cursor::<(BlockPoint, WrapPoint)>(); - cursor.seek(&BlockPoint::new(rows.start, 0), Bias::Right, &()); + let output_position = BlockPoint::new(rows.start, 0); + cursor.seek(&output_position, Bias::Right, &()); let (input_start, output_start) = cursor.start(); - let row_overshoot = rows.start - output_start.0; - let input_start_row = input_start.0 + row_overshoot; + let row_overshoot = rows.start - output_start.0.row; + let input_start_row = input_start.0.row + row_overshoot; let input_end_row = self.to_wrap_point(BlockPoint::new(rows.end, 0)).row(); let input_chunks = self .wrap_snapshot @@ -346,7 +370,7 @@ impl BlockSnapshot { input_chunk: Default::default(), block_chunks: None, transforms: cursor, - output_position: rows.start, + output_position, max_output_row: rows.end, } } @@ -356,27 +380,26 @@ impl BlockSnapshot { } pub fn clip_point(&self, point: BlockPoint, bias: Bias) -> BlockPoint { - let mut cursor = self.transforms.cursor::<(OutputRow, InputRow)>(); - cursor.seek(&OutputRow(point.row), Bias::Right, &()); + let mut cursor = self.transforms.cursor::<(BlockPoint, WrapPoint)>(); + cursor.seek(&point, Bias::Right, &()); if let Some(transform) = cursor.item() { if transform.is_isomorphic() { - let (output_start_row, input_start_row) = cursor.start(); - let output_overshoot = point.row - output_start_row.0; - let input_point = self.wrap_snapshot.clip_point( - WrapPoint::new(input_start_row.0 + output_overshoot, point.column), - bias, - ); - let input_overshoot = input_point.row() - input_start_row.0; - BlockPoint::new(output_start_row.0 + input_overshoot, input_point.column()) + let (output_start, input_start) = cursor.start(); + let output_overshoot = point.0 - output_start.0; + let input_point = self + .wrap_snapshot + .clip_point(WrapPoint(input_start.0 + output_overshoot), bias); + let input_overshoot = input_point.0 - input_start.0; + BlockPoint(output_start.0 + input_overshoot) } else { - if bias == Bias::Left && cursor.start().1 .0 > 0 - || cursor.end(&()).1 .0 == self.wrap_snapshot.max_point().row() + if bias == Bias::Left && cursor.start().1 .0 > Point::zero() + || cursor.end(&()).1 == self.wrap_snapshot.max_point() { loop { cursor.prev(&()); let transform = cursor.item().unwrap(); if transform.is_isomorphic() { - return BlockPoint::new(cursor.end(&()).0 .0 - 1, 0); + return BlockPoint(cursor.end(&()).0 .0); } } } else { @@ -384,7 +407,7 @@ impl BlockSnapshot { cursor.next(&()); let transform = cursor.item().unwrap(); if transform.is_isomorphic() { - return BlockPoint::new(cursor.start().0 .0, 0); + return BlockPoint(cursor.start().0 .0); } } } @@ -395,8 +418,8 @@ impl BlockSnapshot { } pub fn to_block_point(&self, wrap_point: WrapPoint) -> BlockPoint { - let mut cursor = self.transforms.cursor::<(InputRow, OutputRow)>(); - cursor.seek(&InputRow(wrap_point.row()), Bias::Right, &()); + let mut cursor = self.transforms.cursor::<(WrapPoint, BlockPoint)>(); + cursor.seek(&wrap_point, Bias::Right, &()); while let Some(item) = cursor.item() { if item.is_isomorphic() { break; @@ -404,16 +427,16 @@ impl BlockSnapshot { cursor.next(&()); } let (input_start, output_start) = cursor.start(); - let row_overshoot = wrap_point.row() - input_start.0; - BlockPoint::new(output_start.0 + row_overshoot, wrap_point.column()) + let input_overshoot = wrap_point.0 - input_start.0; + BlockPoint(output_start.0 + input_overshoot) } pub fn to_wrap_point(&self, block_point: BlockPoint) -> WrapPoint { - let mut cursor = self.transforms.cursor::<(OutputRow, InputRow)>(); - cursor.seek(&OutputRow(block_point.0.row), Bias::Right, &()); + let mut cursor = self.transforms.cursor::<(BlockPoint, WrapPoint)>(); + cursor.seek(&block_point, Bias::Right, &()); let (output_start, input_start) = cursor.start(); - let row_overshoot = block_point.0.row - output_start.0; - WrapPoint::new(input_start.0 + row_overshoot, block_point.0.column) + let output_overshoot = block_point.0 - output_start.0; + WrapPoint(input_start.0 + output_overshoot) } } @@ -441,19 +464,23 @@ impl Transform { fn is_isomorphic(&self) -> bool { self.block.is_none() } + + fn block_disposition(&self) -> Option { + self.block.as_ref().map(|b| b.disposition) + } } impl<'a> Iterator for HighlightedChunks<'a> { type Item = HighlightedChunk<'a>; fn next(&mut self) -> Option { - if self.output_position >= self.max_output_row { + if self.output_position.row >= self.max_output_row { return None; } if let Some(block_chunks) = self.block_chunks.as_mut() { if let Some(block_chunk) = block_chunks.next() { - self.output_position += block_chunk.text.matches('\n').count() as u32; + self.output_position.0 += Point::from_str(block_chunk.text); return Some(block_chunk); } else { self.block_chunks.take(); @@ -464,12 +491,13 @@ impl<'a> Iterator for HighlightedChunks<'a> { if let Some(block) = transform.block.as_ref() { let block_start = self.transforms.start().0 .0; let block_end = self.transforms.end(&()).0 .0; - let start_row_in_block = self.output_position - block_start; - let end_row_in_block = cmp::min(self.max_output_row, block_end) - block_start; + let start_in_block = self.output_position.0 - block_start; + let end_in_block = + cmp::min(Point::new(self.max_output_row, 0), block_end) - block_start; self.transforms.next(&()); - let mut block_chunks = BlockChunks::new(block, start_row_in_block..end_row_in_block); + let mut block_chunks = BlockChunks::new(block, start_in_block..end_in_block); if let Some(block_chunk) = block_chunks.next() { - self.output_position += block_chunk.text.matches('\n').count() as u32; + self.output_position.0 += Point::from_str(block_chunk.text); return Some(block_chunk); } } @@ -477,26 +505,18 @@ impl<'a> Iterator for HighlightedChunks<'a> { if self.input_chunk.text.is_empty() { if let Some(input_chunk) = self.input_chunks.next() { self.input_chunk = input_chunk; - } else { - self.output_position += 1; - self.transforms.next(&()); - if self.output_position < self.max_output_row { - let mut chunk = self.input_chunk.clone(); - chunk.text = "\n"; - return Some(chunk); - } else { - return None; - } } } let transform_end = self.transforms.end(&()).0 .0; - let (prefix_rows, prefix_bytes) = - offset_for_row(self.input_chunk.text, transform_end - self.output_position); - self.output_position += prefix_rows; + let (prefix_lines, prefix_bytes) = offset_for_point( + self.input_chunk.text, + transform_end - self.output_position.0, + ); + self.output_position.0 += prefix_lines; let (prefix, suffix) = self.input_chunk.text.split_at(prefix_bytes); self.input_chunk.text = suffix; - if self.output_position == transform_end { + if self.output_position.0 == transform_end { self.transforms.next(&()); } @@ -508,8 +528,7 @@ impl<'a> Iterator for HighlightedChunks<'a> { } impl<'a> BlockChunks<'a> { - fn new(block: &'a Block, row_range: Range) -> Self { - let point_range = Point::new(row_range.start, 0)..Point::new(row_range.end, 0); + fn new(block: &'a Block, point_range: Range) -> Self { let offset_range = block.text.point_to_offset(point_range.start) ..block.text.point_to_offset(point_range.end); @@ -576,7 +595,7 @@ impl sum_tree::Item for Transform { type Summary = TransformSummary; fn summary(&self) -> Self::Summary { - self.summary + self.summary.clone() } } @@ -591,13 +610,13 @@ impl sum_tree::Summary for TransformSummary { impl<'a> sum_tree::Dimension<'a, TransformSummary> for WrapPoint { fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) { - self.0 += summary.input.lines; + self.0 += summary.input; } } impl<'a> sum_tree::Dimension<'a, TransformSummary> for BlockPoint { fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) { - self.0 += summary.output.lines; + self.0 += summary.output; } } @@ -607,23 +626,25 @@ impl BlockDisposition { } } -// Count the number of bytes prior to a target row. -// If the string doesn't contain the target row, return the total number of rows it does contain. -// Otherwise return the target row itself. -fn offset_for_row(s: &str, target_row: u32) -> (u32, usize) { - let mut row = 0; +// Count the number of bytes prior to a target point. If the string doesn't contain the target +// point, return its total extent. Otherwise return the target point itself. +fn offset_for_point(s: &str, target: Point) -> (Point, usize) { + let mut point = Point::zero(); let mut offset = 0; - for (ix, line) in s.split('\n').enumerate() { - if ix > 0 { - row += 1; + for (row, line) in s.split('\n').enumerate().take(target.row as usize + 1) { + let row = row as u32; + if row > 0 { offset += 1; } - if row as u32 >= target_row { - break; - } - offset += line.len(); + point.row = row; + point.column = if row == target.row { + cmp::min(line.len() as u32, target.column) + } else { + line.len() as u32 + }; + offset += point.column as usize; } - (row, offset) + (point, offset) } #[cfg(test)] @@ -643,7 +664,7 @@ mod tests { .select_font(family_id, &Default::default()) .unwrap(); - let text = "aaa\nbbb\nccc\nddd\n"; + let text = "aaa\nbbb\nccc\nddd"; let buffer = cx.add_model(|cx| Buffer::new(0, text, cx)); let (fold_map, folds_snapshot) = FoldMap::new(buffer.clone(), cx); @@ -679,7 +700,7 @@ mod tests { let mut snapshot = block_map.read(wraps_snapshot, vec![], cx); assert_eq!( snapshot.text(), - "aaa\nBLOCK 1\nBLOCK 2\nbbb\nccc\nddd\nBLOCK 3\n" + "aaa\nBLOCK 1\nBLOCK 2\nbbb\nccc\nddd\nBLOCK 3" ); assert_eq!( snapshot.to_block_point(WrapPoint::new(1, 0)), @@ -700,7 +721,7 @@ mod tests { let mut snapshot = block_map.read(wraps_snapshot, wrap_edits, cx); assert_eq!( snapshot.text(), - "aaa\nBLOCK 1\nb!!!\nBLOCK 2\nbb\nccc\nddd\nBLOCK 3\n" + "aaa\nBLOCK 1\nb!!!\nBLOCK 2\nbb\nccc\nddd\nBLOCK 3" ); } From cebab56c940ab228b7154b7b42aa981f85e92b97 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 15 Nov 2021 16:47:30 +0100 Subject: [PATCH 042/109] Make `BlockMap` randomized test pass in low-complexity cases Co-Authored-By: Nathan Sobo --- crates/buffer/src/rope.rs | 10 ------ crates/editor/src/display_map/block_map.rs | 38 +++++++++++----------- 2 files changed, 19 insertions(+), 29 deletions(-) diff --git a/crates/buffer/src/rope.rs b/crates/buffer/src/rope.rs index 1ceada807238d67472f035e5fc12aaedf309f04d..d5ac56be6921fcdd4f9394a45c4bf28808edbdad 100644 --- a/crates/buffer/src/rope.rs +++ b/crates/buffer/src/rope.rs @@ -94,16 +94,6 @@ impl Rope { self.append(suffix); } - pub fn starts_with(&self, text: &str) -> bool { - self.chunks().flat_map(|c| c.bytes()).eq(text.bytes()) - } - - pub fn ends_with(&self, text: &str) -> bool { - self.reversed_chunks_in_range(0..self.len()) - .flat_map(|c| c.bytes().rev()) - .eq(text.bytes().rev()) - } - fn check_invariants(&self) { #[cfg(test)] { diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index 14e7be214722f62e273ae4ab778fa5bb5bb0d9a6..e4329e2708db68261144dee326390d709540217d 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -189,20 +189,24 @@ impl BlockMap { dbg!("expanded edit", &edit); let start_anchor = buffer.anchor_before(Point::new(edit.new.start, 0)); - let end_anchor = buffer.anchor_before(Point::new(edit.new.end, 0)); let start_block_ix = match self.blocks[last_block_ix..] .binary_search_by(|probe| probe.position.cmp(&start_anchor, buffer).unwrap()) { Ok(ix) | Err(ix) => last_block_ix + ix, }; - let end_block_ix = match self.blocks[start_block_ix..].binary_search_by(|probe| { - probe - .position - .cmp(&end_anchor, buffer) - .unwrap() - .then(Ordering::Greater) - }) { - Ok(ix) | Err(ix) => start_block_ix + ix, + let end_block_ix = if edit.new.end > wrap_snapshot.max_point().row() { + self.blocks.len() + } else { + let end_anchor = buffer.anchor_before(Point::new(edit.new.end, 0)); + match self.blocks[start_block_ix..].binary_search_by(|probe| { + probe + .position + .cmp(&end_anchor, buffer) + .unwrap() + .then(Ordering::Greater) + }) { + Ok(ix) | Err(ix) => start_block_ix + ix, + } }; last_block_ix = end_block_ix; @@ -240,10 +244,9 @@ impl BlockMap { } let new_transforms_end = new_transforms.summary().input; - if new_transforms_end.row < edit.new.end - 1 { - // TODO: Should we just report the wrap edits in 2d so we can skip this max point check? - let edit_new_end_point = - cmp::min(Point::new(edit.new.end, 0), wrap_snapshot.max_point().0); + let edit_new_end_point = + cmp::min(Point::new(edit.new.end, 0), wrap_snapshot.max_point().0); + if new_transforms_end < edit_new_end_point { new_transforms.push( Transform::isomorphic(edit_new_end_point - new_transforms_end), &(), @@ -306,13 +309,9 @@ impl<'a> BlockMapWriter<'a> { }; let mut text = block.text.into(); if block.disposition.is_above() { - if !text.ends_with("\n") { - text.push("\n"); - } + text.push("\n"); } else { - if !text.starts_with("\n") { - text.push_front("\n"); - } + text.push_front("\n"); } self.0.blocks.insert( @@ -745,6 +744,7 @@ mod tests { let buffer = cx.add_model(|cx| { let len = rng.gen_range(0..10); let text = RandomCharIter::new(&mut rng).take(len).collect::(); + log::info!("initial buffer text: {:?}", text); Buffer::new(0, text, cx) }); let (fold_map, folds_snapshot) = FoldMap::new(buffer.clone(), cx); From 34f85b569042be19ac47d11584016be1cf41f0ef Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 15 Nov 2021 17:15:30 +0100 Subject: [PATCH 043/109] WIP --- crates/editor/src/display_map/block_map.rs | 89 +++++++++++++--------- 1 file changed, 55 insertions(+), 34 deletions(-) diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index e4329e2708db68261144dee326390d709540217d..0f2fbcf973b2722f17bf9d628bcd9b0d39b2cc0e 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -31,7 +31,7 @@ pub struct BlockSnapshot { transforms: SumTree, } -#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)] +#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct BlockId(usize); #[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)] @@ -147,10 +147,15 @@ impl BlockMap { &(), ); + let mut transform_start_row = cursor.start().row(); + if cursor.prev_item().map_or(false, |t| { + t.block_disposition() == Some(BlockDisposition::Below) + }) { + transform_start_row += 1; + } dbg!("collapsed edit", &edit); - let transform_start = cursor.start().0; - edit.new.start -= edit.old.start - transform_start.row; - edit.old.start = transform_start.row; + edit.new.start -= edit.old.start - transform_start_row; + edit.old.start = transform_start_row; loop { if edit.old.end > cursor.start().0.row { @@ -167,7 +172,7 @@ impl BlockMap { } } - let transform_end_row = cursor.start().0.row + 1; + let transform_end_row = cursor.start().row() + 1; cursor.seek(&WrapPoint::new(transform_end_row, 0), Bias::Left, &()); edit.new.end += transform_end_row - edit.old.end; edit.old.end = transform_end_row; @@ -189,9 +194,13 @@ impl BlockMap { dbg!("expanded edit", &edit); let start_anchor = buffer.anchor_before(Point::new(edit.new.start, 0)); - let start_block_ix = match self.blocks[last_block_ix..] - .binary_search_by(|probe| probe.position.cmp(&start_anchor, buffer).unwrap()) - { + let start_block_ix = match self.blocks[last_block_ix..].binary_search_by(|probe| { + probe + .position + .cmp(&start_anchor, buffer) + .unwrap() + .then(Ordering::Greater) + }) { Ok(ix) | Err(ix) => last_block_ix + ix, }; let end_block_ix = if edit.new.end > wrap_snapshot.max_point().row() { @@ -300,11 +309,13 @@ impl<'a> BlockMapWriter<'a> { let position = buffer.anchor_before(block.position); let row = position.to_point(buffer).row; - let block_ix = match self - .0 - .blocks - .binary_search_by(|probe| probe.position.cmp(&position, buffer).unwrap()) - { + let block_ix = match self.0.blocks.binary_search_by(|probe| { + probe + .position + .cmp(&position, buffer) + .unwrap() + .then_with(|| probe.id.cmp(&id)) + }) { Ok(ix) | Err(ix) => ix, }; let mut text = block.text.into(); @@ -779,15 +790,22 @@ mod tests { .collect::() .as_str(), ); + let disposition = if rng.gen() { + BlockDisposition::Above + } else { + BlockDisposition::Below + }; + log::info!( + "inserting block {:?} {:?} with text {:?}", + disposition, + position.to_point(buffer), + text.to_string() + ); BlockProperties { position, text, runs: Vec::<(usize, HighlightStyle)>::new(), - disposition: if rng.gen() { - BlockDisposition::Above - } else { - BlockDisposition::Below - }, + disposition, } }) .collect::>(); @@ -800,13 +818,6 @@ mod tests { let mut block_map = block_map.write(wraps_snapshot, wrap_edits, cx); let block_ids = block_map.insert(block_properties.clone(), cx); for (block_id, props) in block_ids.into_iter().zip(block_properties) { - log::info!( - "inserted block {:?} {:?} {:?} with text {:?}", - block_id.0, - props.disposition, - props.position.to_point(buffer.read(cx)), - props.text.to_string() - ); expected_blocks.push((block_id, props)); } } @@ -829,7 +840,10 @@ mod tests { block_map.remove(block_ids_to_remove, cx); } _ => { - buffer.update(cx, |buffer, _| buffer.randomly_edit(&mut rng, 5)); + buffer.update(cx, |buffer, _| { + buffer.randomly_edit(&mut rng, 1); + log::info!("buffer text: {:?}", buffer.text()); + }); } } @@ -839,6 +853,7 @@ mod tests { wrap_map.sync(tabs_snapshot, tab_edits, cx) }); let mut blocks_snapshot = block_map.read(wraps_snapshot.clone(), wrap_edits, cx); + log::info!("blocks text: {:?}", blocks_snapshot.text()); assert_eq!( blocks_snapshot.transforms.summary().input, wraps_snapshot.max_point().0 @@ -848,14 +863,20 @@ mod tests { let mut sorted_blocks = expected_blocks .iter() .cloned() - .map(|(_, block)| BlockProperties { - position: block.position.to_point(buffer), - text: block.text, - runs: block.runs, - disposition: block.disposition, + .map(|(id, block)| { + ( + id, + BlockProperties { + position: block.position.to_point(buffer), + text: block.text, + runs: block.runs, + disposition: block.disposition, + }, + ) }) .collect::>(); - sorted_blocks.sort_unstable_by_key(|block| (block.position.row, block.disposition)); + sorted_blocks + .sort_unstable_by_key(|(id, block)| (block.position.row, block.disposition, *id)); let mut sorted_blocks = sorted_blocks.into_iter().peekable(); let mut expected_text = String::new(); @@ -866,7 +887,7 @@ mod tests { expected_text.push('\n'); } - while let Some(block) = sorted_blocks.peek() { + while let Some((_, block)) = sorted_blocks.peek() { if block.position.row == row && block.disposition == BlockDisposition::Above { expected_text.extend(block.text.chunks()); expected_text.push('\n'); @@ -878,7 +899,7 @@ mod tests { expected_text.push_str(input_line); - while let Some(block) = sorted_blocks.peek() { + while let Some((_, block)) = sorted_blocks.peek() { if block.position.row == row && block.disposition == BlockDisposition::Below { expected_text.push('\n'); expected_text.extend(block.text.chunks()); From 131979dff0f2f1eed72a093be2575f8de0db28f5 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 15 Nov 2021 17:54:28 +0100 Subject: [PATCH 044/109] WIP --- crates/buffer/src/random_char_iter.rs | 16 ++-- crates/editor/src/display_map/block_map.rs | 104 +++++++++++---------- 2 files changed, 63 insertions(+), 57 deletions(-) diff --git a/crates/buffer/src/random_char_iter.rs b/crates/buffer/src/random_char_iter.rs index 244665688d6008caa1bbb0c8208aef0df863b8e9..535ee03eef860e420c31e3a460a2522bf0f78359 100644 --- a/crates/buffer/src/random_char_iter.rs +++ b/crates/buffer/src/random_char_iter.rs @@ -14,14 +14,14 @@ impl Iterator for RandomCharIter { fn next(&mut self) -> Option { match self.0.gen_range(0..100) { // whitespace - 0..=19 => [' ', '\n', '\t'].choose(&mut self.0).copied(), - // two-byte greek letters - 20..=32 => char::from_u32(self.0.gen_range(('α' as u32)..('ω' as u32 + 1))), - // three-byte characters - 33..=45 => ['✋', '✅', '❌', '❎', '⭐'].choose(&mut self.0).copied(), - // four-byte characters - 46..=58 => ['🍐', '🏀', '🍗', '🎉'].choose(&mut self.0).copied(), - // ascii letters + 0..=4 => ['\n'].choose(&mut self.0).copied(), + // // two-byte greek letters + // 20..=32 => char::from_u32(self.0.gen_range(('α' as u32)..('ω' as u32 + 1))), + // // three-byte characters + // 33..=45 => ['✋', '✅', '❌', '❎', '⭐'].choose(&mut self.0).copied(), + // // four-byte characters + // 46..=58 => ['🍐', '🏀', '🍗', '🎉'].choose(&mut self.0).copied(), + // // ascii letters _ => Some(self.0.gen_range(b'a'..b'z' + 1).into()), } } diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index 0f2fbcf973b2722f17bf9d628bcd9b0d39b2cc0e..34c5672c5e675ab62210ce7d734522375015ce90 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -156,18 +156,24 @@ impl BlockMap { dbg!("collapsed edit", &edit); edit.new.start -= edit.old.start - transform_start_row; edit.old.start = transform_start_row; + let mut edit_follows_below_block = false; loop { if edit.old.end > cursor.start().0.row { cursor.seek(&WrapPoint::new(edit.old.end, 0), Bias::Left, &()); + // dbg!(cursor.start(), cursor.item()); if cursor.item().map_or(false, |t| t.is_isomorphic()) { if let Some(prev_transform) = cursor.prev_item() { - if prev_transform.block_disposition() != Some(BlockDisposition::Below) - || edit.old.end > cursor.start().row() + 1 + if prev_transform.block_disposition() == Some(BlockDisposition::Below) + && edit.old.end == cursor.start().row() + 1 { + edit_follows_below_block = true; + } else { + edit_follows_below_block = false; cursor.next(&()); } } else { + edit_follows_below_block = false; cursor.next(&()); } } @@ -225,8 +231,8 @@ impl BlockMap { .iter() .map(|block| (block.position.to_point(buffer).row, block)), ); - blocks_in_edit.sort_unstable_by_key(|(row, block)| (*row, block.disposition)); - dbg!(&blocks_in_edit); + blocks_in_edit.sort_unstable_by_key(|(row, block)| (*row, block.disposition, block.id)); + // dbg!(&blocks_in_edit); for (block_row, block) in blocks_in_edit.iter().copied() { let new_transforms_end = new_transforms.summary().input; @@ -252,14 +258,16 @@ impl BlockMap { new_transforms.push(Transform::block(block.clone()), &()); } - let new_transforms_end = new_transforms.summary().input; - let edit_new_end_point = - cmp::min(Point::new(edit.new.end, 0), wrap_snapshot.max_point().0); - if new_transforms_end < edit_new_end_point { - new_transforms.push( - Transform::isomorphic(edit_new_end_point - new_transforms_end), - &(), - ); + if !edit_follows_below_block { + let new_transforms_end = new_transforms.summary().input; + let edit_new_end_point = + cmp::min(Point::new(edit.new.end, 0), wrap_snapshot.max_point().0); + if new_transforms_end < edit_new_end_point { + new_transforms.push( + Transform::isomorphic(edit_new_end_point - new_transforms_end), + &(), + ); + } } } new_transforms.push_tree(cursor.suffix(&()), &()); @@ -309,13 +317,11 @@ impl<'a> BlockMapWriter<'a> { let position = buffer.anchor_before(block.position); let row = position.to_point(buffer).row; - let block_ix = match self.0.blocks.binary_search_by(|probe| { - probe - .position - .cmp(&position, buffer) - .unwrap() - .then_with(|| probe.id.cmp(&id)) - }) { + let block_ix = match self + .0 + .blocks + .binary_search_by(|probe| probe.position.cmp(&position, buffer).unwrap()) + { Ok(ix) | Err(ix) => ix, }; let mut text = block.text.into(); @@ -741,7 +747,7 @@ mod tests { .map(|i| i.parse().expect("invalid `OPERATIONS` variable")) .unwrap_or(10); - let wrap_width = Some(rng.gen_range(0.0..=1000.0)); + let wrap_width = None; let tab_size = 1; let family_id = cx.font_cache().load_family(&["Helvetica"]).unwrap(); let font_id = cx @@ -767,17 +773,17 @@ mod tests { for _ in 0..operations { match rng.gen_range(0..=100) { - 0..=19 => { - let wrap_width = if rng.gen_bool(0.2) { - None - } else { - Some(rng.gen_range(0.0..=1000.0)) - }; - log::info!("Setting wrap width to {:?}", wrap_width); - wrap_map.update(cx, |map, cx| map.set_wrap_width(wrap_width, cx)); - } + // 0..=19 => { + // let wrap_width = if rng.gen_bool(0.2) { + // None + // } else { + // Some(rng.gen_range(0.0..=1000.0)) + // }; + // log::info!("Setting wrap width to {:?}", wrap_width); + // wrap_map.update(cx, |map, cx| map.set_wrap_width(wrap_width, cx)); + // } 20..=39 => { - let block_count = rng.gen_range(1..=4); + let block_count = rng.gen_range(1..=1); let block_properties = (0..block_count) .map(|_| { let buffer = buffer.read(cx); @@ -821,24 +827,24 @@ mod tests { expected_blocks.push((block_id, props)); } } - 40..=59 if !expected_blocks.is_empty() => { - let block_count = rng.gen_range(1..=4.min(expected_blocks.len())); - let block_ids_to_remove = (0..=block_count) - .map(|_| { - expected_blocks - .remove(rng.gen_range(0..expected_blocks.len())) - .0 - }) - .collect(); - - let (folds_snapshot, fold_edits) = fold_map.read(cx); - let (tabs_snapshot, tab_edits) = tab_map.sync(folds_snapshot, fold_edits); - let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| { - wrap_map.sync(tabs_snapshot, tab_edits, cx) - }); - let mut block_map = block_map.write(wraps_snapshot, wrap_edits, cx); - block_map.remove(block_ids_to_remove, cx); - } + // 40..=59 if !expected_blocks.is_empty() => { + // let block_count = rng.gen_range(1..=4.min(expected_blocks.len())); + // let block_ids_to_remove = (0..block_count) + // .map(|_| { + // expected_blocks + // .remove(rng.gen_range(0..expected_blocks.len())) + // .0 + // }) + // .collect(); + + // let (folds_snapshot, fold_edits) = fold_map.read(cx); + // let (tabs_snapshot, tab_edits) = tab_map.sync(folds_snapshot, fold_edits); + // let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| { + // wrap_map.sync(tabs_snapshot, tab_edits, cx) + // }); + // let mut block_map = block_map.write(wraps_snapshot, wrap_edits, cx); + // block_map.remove(block_ids_to_remove, cx); + // } _ => { buffer.update(cx, |buffer, _| { buffer.randomly_edit(&mut rng, 1); @@ -853,11 +859,11 @@ mod tests { wrap_map.sync(tabs_snapshot, tab_edits, cx) }); let mut blocks_snapshot = block_map.read(wraps_snapshot.clone(), wrap_edits, cx); - log::info!("blocks text: {:?}", blocks_snapshot.text()); assert_eq!( blocks_snapshot.transforms.summary().input, wraps_snapshot.max_point().0 ); + log::info!("blocks text: {:?}", blocks_snapshot.text()); let buffer = buffer.read(cx); let mut sorted_blocks = expected_blocks From 314c97715d4e61c1e7a59ec430f29abf52a9d1de Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 15 Nov 2021 18:01:30 +0100 Subject: [PATCH 045/109] WIP --- crates/editor/src/display_map/block_map.rs | 264 +++++++++++---------- 1 file changed, 138 insertions(+), 126 deletions(-) diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index 34c5672c5e675ab62210ce7d734522375015ce90..4eee7afa874eda8456870f001e9c7deddab94bb6 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -137,139 +137,151 @@ impl BlockMap { let mut transforms = self.transforms.lock(); let mut new_transforms = SumTree::new(); let mut cursor = transforms.cursor::(); - let mut edits = edits.into_iter().peekable(); let mut last_block_ix = 0; let mut blocks_in_edit = Vec::new(); - while let Some(mut edit) = edits.next() { - new_transforms.push_tree( - cursor.slice(&WrapPoint::new(edit.old.start, 0), Bias::Left, &()), - &(), - ); - - let mut transform_start_row = cursor.start().row(); - if cursor.prev_item().map_or(false, |t| { - t.block_disposition() == Some(BlockDisposition::Below) - }) { - transform_start_row += 1; - } - dbg!("collapsed edit", &edit); - edit.new.start -= edit.old.start - transform_start_row; - edit.old.start = transform_start_row; - let mut edit_follows_below_block = false; - - loop { - if edit.old.end > cursor.start().0.row { - cursor.seek(&WrapPoint::new(edit.old.end, 0), Bias::Left, &()); - // dbg!(cursor.start(), cursor.item()); - if cursor.item().map_or(false, |t| t.is_isomorphic()) { - if let Some(prev_transform) = cursor.prev_item() { - if prev_transform.block_disposition() == Some(BlockDisposition::Below) - && edit.old.end == cursor.start().row() + 1 - { - edit_follows_below_block = true; - } else { - edit_follows_below_block = false; - cursor.next(&()); - } - } else { - edit_follows_below_block = false; - cursor.next(&()); - } - } + for edit in edits { + let old_start = WrapPoint::new(edit.old.start, 0); + new_transforms.push_tree(cursor.slice(&old_start, Bias::Left, &()), &()); - let transform_end_row = cursor.start().row() + 1; - cursor.seek(&WrapPoint::new(transform_end_row, 0), Bias::Left, &()); - edit.new.end += transform_end_row - edit.old.end; - edit.old.end = transform_end_row; - } - - if let Some(next_edit) = edits.peek() { - if edit.old.end >= next_edit.old.start { - let delta = next_edit.new.len() as i32 - next_edit.old.len() as i32; - edit.old.end = cmp::max(next_edit.old.end, edit.old.end); - edit.new.end = (edit.new.end as i32 + delta) as u32; - edits.next(); - } else { - break; - } - } else { - break; - } + let overshoot = old_start.0 - cursor.start().0; + if !overshoot.is_zero() { + new_transforms.push(Transform::isomorphic(overshoot), &()); } - dbg!("expanded edit", &edit); - let start_anchor = buffer.anchor_before(Point::new(edit.new.start, 0)); - let start_block_ix = match self.blocks[last_block_ix..].binary_search_by(|probe| { - probe - .position - .cmp(&start_anchor, buffer) - .unwrap() - .then(Ordering::Greater) - }) { - Ok(ix) | Err(ix) => last_block_ix + ix, - }; - let end_block_ix = if edit.new.end > wrap_snapshot.max_point().row() { - self.blocks.len() - } else { - let end_anchor = buffer.anchor_before(Point::new(edit.new.end, 0)); - match self.blocks[start_block_ix..].binary_search_by(|probe| { - probe - .position - .cmp(&end_anchor, buffer) - .unwrap() - .then(Ordering::Greater) - }) { - Ok(ix) | Err(ix) => start_block_ix + ix, - } - }; - last_block_ix = end_block_ix; - - blocks_in_edit.clear(); - blocks_in_edit.extend( - self.blocks[start_block_ix..end_block_ix] - .iter() - .map(|block| (block.position.to_point(buffer).row, block)), - ); - blocks_in_edit.sort_unstable_by_key(|(row, block)| (*row, block.disposition, block.id)); - // dbg!(&blocks_in_edit); - - for (block_row, block) in blocks_in_edit.iter().copied() { - let new_transforms_end = new_transforms.summary().input; - if block.disposition.is_above() { - if block_row > new_transforms_end.row { - new_transforms.push( - Transform::isomorphic(Point::new(block_row, 0) - new_transforms_end), - &(), - ); - } - } else { - if block_row >= new_transforms_end.row { - new_transforms.push( - Transform::isomorphic( - Point::new(block_row, wrap_snapshot.line_len(block_row)) - - new_transforms_end, - ), - &(), - ); - } - } - - new_transforms.push(Transform::block(block.clone()), &()); - } - - if !edit_follows_below_block { - let new_transforms_end = new_transforms.summary().input; - let edit_new_end_point = - cmp::min(Point::new(edit.new.end, 0), wrap_snapshot.max_point().0); - if new_transforms_end < edit_new_end_point { - new_transforms.push( - Transform::isomorphic(edit_new_end_point - new_transforms_end), - &(), - ); - } - } + let old_end = WrapPoint::new(edit.old.end, 0); + cursor.seek(&old_end, Bias::Left, &()); } + + // while let Some(mut edit) = edits.next() { + // new_transforms.push_tree( + // cursor.slice(&WrapPoint::new(edit.old.start, 0), Bias::Left, &()), + // &(), + // ); + + // let mut transform_start_row = cursor.start().row(); + // if cursor.prev_item().map_or(false, |t| { + // t.block_disposition() == Some(BlockDisposition::Below) + // }) { + // transform_start_row += 1; + // } + // dbg!("collapsed edit", &edit); + // edit.new.start -= edit.old.start - transform_start_row; + // edit.old.start = transform_start_row; + // let mut edit_follows_below_block = false; + + // loop { + // if edit.old.end > cursor.start().0.row { + // cursor.seek(&WrapPoint::new(edit.old.end, 0), Bias::Left, &()); + // // dbg!(cursor.start(), cursor.item()); + // if cursor.item().map_or(false, |t| t.is_isomorphic()) { + // if let Some(prev_transform) = cursor.prev_item() { + // if prev_transform.block_disposition() == Some(BlockDisposition::Below) + // && edit.old.end == cursor.start().row() + 1 + // { + // edit_follows_below_block = true; + // } else { + // edit_follows_below_block = false; + // cursor.next(&()); + // } + // } else { + // edit_follows_below_block = false; + // cursor.next(&()); + // } + // } + + // let transform_end_row = cursor.start().row() + 1; + // cursor.seek(&WrapPoint::new(transform_end_row, 0), Bias::Left, &()); + // edit.new.end += transform_end_row - edit.old.end; + // edit.old.end = transform_end_row; + // } + + // if let Some(next_edit) = edits.peek() { + // if edit.old.end >= next_edit.old.start { + // let delta = next_edit.new.len() as i32 - next_edit.old.len() as i32; + // edit.old.end = cmp::max(next_edit.old.end, edit.old.end); + // edit.new.end = (edit.new.end as i32 + delta) as u32; + // edits.next(); + // } else { + // break; + // } + // } else { + // break; + // } + // } + + // dbg!("expanded edit", &edit); + // let start_anchor = buffer.anchor_before(Point::new(edit.new.start, 0)); + // let start_block_ix = match self.blocks[last_block_ix..].binary_search_by(|probe| { + // probe + // .position + // .cmp(&start_anchor, buffer) + // .unwrap() + // .then(Ordering::Greater) + // }) { + // Ok(ix) | Err(ix) => last_block_ix + ix, + // }; + // let end_block_ix = if edit.new.end > wrap_snapshot.max_point().row() { + // self.blocks.len() + // } else { + // let end_anchor = buffer.anchor_before(Point::new(edit.new.end, 0)); + // match self.blocks[start_block_ix..].binary_search_by(|probe| { + // probe + // .position + // .cmp(&end_anchor, buffer) + // .unwrap() + // .then(Ordering::Greater) + // }) { + // Ok(ix) | Err(ix) => start_block_ix + ix, + // } + // }; + // last_block_ix = end_block_ix; + + // blocks_in_edit.clear(); + // blocks_in_edit.extend( + // self.blocks[start_block_ix..end_block_ix] + // .iter() + // .map(|block| (block.position.to_point(buffer).row, block)), + // ); + // blocks_in_edit.sort_unstable_by_key(|(row, block)| (*row, block.disposition, block.id)); + // // dbg!(&blocks_in_edit); + + // for (block_row, block) in blocks_in_edit.iter().copied() { + // let new_transforms_end = new_transforms.summary().input; + // if block.disposition.is_above() { + // if block_row > new_transforms_end.row { + // new_transforms.push( + // Transform::isomorphic(Point::new(block_row, 0) - new_transforms_end), + // &(), + // ); + // } + // } else { + // if block_row >= new_transforms_end.row { + // new_transforms.push( + // Transform::isomorphic( + // Point::new(block_row, wrap_snapshot.line_len(block_row)) + // - new_transforms_end, + // ), + // &(), + // ); + // } + // } + + // new_transforms.push(Transform::block(block.clone()), &()); + // } + + // if !edit_follows_below_block { + // let new_transforms_end = new_transforms.summary().input; + // let edit_new_end_point = + // cmp::min(Point::new(edit.new.end, 0), wrap_snapshot.max_point().0); + // if new_transforms_end < edit_new_end_point { + // new_transforms.push( + // Transform::isomorphic(edit_new_end_point - new_transforms_end), + // &(), + // ); + // } + // } + // } new_transforms.push_tree(cursor.suffix(&()), &()); drop(cursor); *transforms = new_transforms; From 53872a6024f7b3afd50e8cab2820ffa5d2f11285 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 15 Nov 2021 19:38:06 +0100 Subject: [PATCH 046/109] WIP --- crates/editor/src/display_map/block_map.rs | 193 +++++++-------------- 1 file changed, 63 insertions(+), 130 deletions(-) diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index 4eee7afa874eda8456870f001e9c7deddab94bb6..12bf8cfb348e1a2a27c78dd9c812e39c858cbe17 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -142,7 +142,9 @@ impl BlockMap { for edit in edits { let old_start = WrapPoint::new(edit.old.start, 0); - new_transforms.push_tree(cursor.slice(&old_start, Bias::Left, &()), &()); + if old_start > *cursor.start() { + new_transforms.push_tree(cursor.slice(&old_start, Bias::Left, &()), &()); + } let overshoot = old_start.0 - cursor.start().0; if !overshoot.is_zero() { @@ -151,138 +153,69 @@ impl BlockMap { let old_end = WrapPoint::new(edit.old.end, 0); cursor.seek(&old_end, Bias::Left, &()); + cursor.next(&()); + + let start_anchor = buffer.anchor_before(Point::new(edit.new.start, 0)); + let start_block_ix = match self.blocks[last_block_ix..].binary_search_by(|probe| { + probe + .position + .cmp(&start_anchor, buffer) + .unwrap() + .then(Ordering::Greater) + }) { + Ok(ix) | Err(ix) => last_block_ix + ix, + }; + let end_block_ix = if edit.new.end > wrap_snapshot.max_point().row() { + self.blocks.len() + } else { + let end_anchor = buffer.anchor_before(Point::new(edit.new.end, 0)); + match self.blocks[start_block_ix..].binary_search_by(|probe| { + probe + .position + .cmp(&end_anchor, buffer) + .unwrap() + .then(Ordering::Greater) + }) { + Ok(ix) | Err(ix) => start_block_ix + ix, + } + }; + last_block_ix = end_block_ix; + + blocks_in_edit.clear(); + blocks_in_edit.extend( + self.blocks[start_block_ix..end_block_ix] + .iter() + .map(|block| (block.position.to_point(buffer).row, block)), + ); + blocks_in_edit.sort_unstable_by_key(|(row, block)| (*row, block.disposition, block.id)); + + for (block_row, block) in blocks_in_edit.iter().copied() { + let new_transforms_end = new_transforms.summary().input; + if block.disposition.is_above() { + if block_row > new_transforms_end.row { + new_transforms.push( + Transform::isomorphic(Point::new(block_row, 0) - new_transforms_end), + &(), + ); + } + } else { + if block_row >= new_transforms_end.row { + new_transforms.push( + Transform::isomorphic( + Point::new(block_row, wrap_snapshot.line_len(block_row)) + - new_transforms_end, + ), + &(), + ); + } + } + + new_transforms.push(Transform::block(block.clone()), &()); + } } - // while let Some(mut edit) = edits.next() { - // new_transforms.push_tree( - // cursor.slice(&WrapPoint::new(edit.old.start, 0), Bias::Left, &()), - // &(), - // ); - - // let mut transform_start_row = cursor.start().row(); - // if cursor.prev_item().map_or(false, |t| { - // t.block_disposition() == Some(BlockDisposition::Below) - // }) { - // transform_start_row += 1; - // } - // dbg!("collapsed edit", &edit); - // edit.new.start -= edit.old.start - transform_start_row; - // edit.old.start = transform_start_row; - // let mut edit_follows_below_block = false; - - // loop { - // if edit.old.end > cursor.start().0.row { - // cursor.seek(&WrapPoint::new(edit.old.end, 0), Bias::Left, &()); - // // dbg!(cursor.start(), cursor.item()); - // if cursor.item().map_or(false, |t| t.is_isomorphic()) { - // if let Some(prev_transform) = cursor.prev_item() { - // if prev_transform.block_disposition() == Some(BlockDisposition::Below) - // && edit.old.end == cursor.start().row() + 1 - // { - // edit_follows_below_block = true; - // } else { - // edit_follows_below_block = false; - // cursor.next(&()); - // } - // } else { - // edit_follows_below_block = false; - // cursor.next(&()); - // } - // } - - // let transform_end_row = cursor.start().row() + 1; - // cursor.seek(&WrapPoint::new(transform_end_row, 0), Bias::Left, &()); - // edit.new.end += transform_end_row - edit.old.end; - // edit.old.end = transform_end_row; - // } - - // if let Some(next_edit) = edits.peek() { - // if edit.old.end >= next_edit.old.start { - // let delta = next_edit.new.len() as i32 - next_edit.old.len() as i32; - // edit.old.end = cmp::max(next_edit.old.end, edit.old.end); - // edit.new.end = (edit.new.end as i32 + delta) as u32; - // edits.next(); - // } else { - // break; - // } - // } else { - // break; - // } - // } - - // dbg!("expanded edit", &edit); - // let start_anchor = buffer.anchor_before(Point::new(edit.new.start, 0)); - // let start_block_ix = match self.blocks[last_block_ix..].binary_search_by(|probe| { - // probe - // .position - // .cmp(&start_anchor, buffer) - // .unwrap() - // .then(Ordering::Greater) - // }) { - // Ok(ix) | Err(ix) => last_block_ix + ix, - // }; - // let end_block_ix = if edit.new.end > wrap_snapshot.max_point().row() { - // self.blocks.len() - // } else { - // let end_anchor = buffer.anchor_before(Point::new(edit.new.end, 0)); - // match self.blocks[start_block_ix..].binary_search_by(|probe| { - // probe - // .position - // .cmp(&end_anchor, buffer) - // .unwrap() - // .then(Ordering::Greater) - // }) { - // Ok(ix) | Err(ix) => start_block_ix + ix, - // } - // }; - // last_block_ix = end_block_ix; - - // blocks_in_edit.clear(); - // blocks_in_edit.extend( - // self.blocks[start_block_ix..end_block_ix] - // .iter() - // .map(|block| (block.position.to_point(buffer).row, block)), - // ); - // blocks_in_edit.sort_unstable_by_key(|(row, block)| (*row, block.disposition, block.id)); - // // dbg!(&blocks_in_edit); - - // for (block_row, block) in blocks_in_edit.iter().copied() { - // let new_transforms_end = new_transforms.summary().input; - // if block.disposition.is_above() { - // if block_row > new_transforms_end.row { - // new_transforms.push( - // Transform::isomorphic(Point::new(block_row, 0) - new_transforms_end), - // &(), - // ); - // } - // } else { - // if block_row >= new_transforms_end.row { - // new_transforms.push( - // Transform::isomorphic( - // Point::new(block_row, wrap_snapshot.line_len(block_row)) - // - new_transforms_end, - // ), - // &(), - // ); - // } - // } - - // new_transforms.push(Transform::block(block.clone()), &()); - // } - - // if !edit_follows_below_block { - // let new_transforms_end = new_transforms.summary().input; - // let edit_new_end_point = - // cmp::min(Point::new(edit.new.end, 0), wrap_snapshot.max_point().0); - // if new_transforms_end < edit_new_end_point { - // new_transforms.push( - // Transform::isomorphic(edit_new_end_point - new_transforms_end), - // &(), - // ); - // } - // } - // } new_transforms.push_tree(cursor.suffix(&()), &()); + drop(cursor); *transforms = new_transforms; } From 763ab4d5f135287a953165793035fd5cc884b938 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Mon, 15 Nov 2021 19:52:48 +0100 Subject: [PATCH 047/109] WIP --- crates/editor/src/display_map/block_map.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index 12bf8cfb348e1a2a27c78dd9c812e39c858cbe17..8cc7f85967660df494ba6adbaab0fea564eea3e0 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -146,14 +146,16 @@ impl BlockMap { new_transforms.push_tree(cursor.slice(&old_start, Bias::Left, &()), &()); } - let overshoot = old_start.0 - cursor.start().0; + let overshoot = Point::new(edit.new.start, 0) - new_transforms.summary().input; if !overshoot.is_zero() { new_transforms.push(Transform::isomorphic(overshoot), &()); } let old_end = WrapPoint::new(edit.old.end, 0); - cursor.seek(&old_end, Bias::Left, &()); - cursor.next(&()); + if old_end > *cursor.start() { + cursor.seek(&old_end, Bias::Left, &()); + cursor.next(&()); + } let start_anchor = buffer.anchor_before(Point::new(edit.new.start, 0)); let start_block_ix = match self.blocks[last_block_ix..].binary_search_by(|probe| { @@ -214,6 +216,9 @@ impl BlockMap { } } + if let Some(last_old_end) = last_old_end { + new_transforms.push(Transform::isomorphic(cursor.start() - last_old_end), &()); + } new_transforms.push_tree(cursor.suffix(&()), &()); drop(cursor); From 3498e92d1c9a7f2cb6f426ffdaf022e43341e067 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 15 Nov 2021 12:37:20 -0800 Subject: [PATCH 048/109] Get BlockMap unit test passing --- crates/editor/src/display_map/block_map.rs | 106 +++++++++++++++------ 1 file changed, 77 insertions(+), 29 deletions(-) diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index 8cc7f85967660df494ba6adbaab0fea564eea3e0..615cf0d94bb1eadf073dd90919902a6e3441dc1a 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -1,4 +1,4 @@ -use super::wrap_map::{self, Edit as WrapEdit, Snapshot as WrapSnapshot, TextSummary, WrapPoint}; +use super::wrap_map::{self, Edit as WrapEdit, Snapshot as WrapSnapshot, WrapPoint}; use buffer::{rope, Anchor, Bias, Edit, Point, Rope, ToOffset, ToPoint as _}; use gpui::{fonts::HighlightStyle, AppContext, ModelHandle}; use language::{Buffer, HighlightedChunk}; @@ -136,28 +136,50 @@ impl BlockMap { let buffer = self.buffer.read(cx); let mut transforms = self.transforms.lock(); let mut new_transforms = SumTree::new(); + let old_max_point = WrapPoint(transforms.summary().input); + let new_max_point = wrap_snapshot.max_point(); let mut cursor = transforms.cursor::(); let mut last_block_ix = 0; let mut blocks_in_edit = Vec::new(); + let mut edits = edits.into_iter().peekable(); - for edit in edits { + while let Some(edit) = edits.next() { + // Preserve any old transforms that precede this edit. let old_start = WrapPoint::new(edit.old.start, 0); - if old_start > *cursor.start() { - new_transforms.push_tree(cursor.slice(&old_start, Bias::Left, &()), &()); - } + let new_start = WrapPoint::new(edit.new.start, 0); + new_transforms.push_tree(cursor.slice(&old_start, Bias::Left, &()), &()); - let overshoot = Point::new(edit.new.start, 0) - new_transforms.summary().input; - if !overshoot.is_zero() { - new_transforms.push(Transform::isomorphic(overshoot), &()); + // Preserve any portion of an old transform that precedes this edit. + let extent_before_edit = old_start.0 - cursor.start().0; + if !extent_before_edit.is_zero() { + push_isomorphic(&mut new_transforms, extent_before_edit); } - let old_end = WrapPoint::new(edit.old.end, 0); - if old_end > *cursor.start() { - cursor.seek(&old_end, Bias::Left, &()); - cursor.next(&()); + // Skip over any old transforms that intersect this edit. + let mut old_end = WrapPoint::new(edit.old.end, 0); + let mut new_end = WrapPoint::new(edit.new.end, 0); + cursor.seek(&old_end, Bias::Left, &()); + cursor.next(&()); + + // Combine this edit with any subsequent edits that intersect the same transform. + while let Some(next_edit) = edits.peek() { + if next_edit.old.start <= cursor.start().row() { + old_end = WrapPoint::new(next_edit.old.end, 0); + new_end = WrapPoint::new(next_edit.new.end, 0); + cursor.seek(&old_end, Bias::Left, &()); + cursor.next(&()); + edits.next(); + } else { + break; + } } + old_end = old_end.min(old_max_point); + new_end = new_end.min(new_max_point); - let start_anchor = buffer.anchor_before(Point::new(edit.new.start, 0)); + // Find the blocks within this edited region. + // + // TODO - convert these wrap map edits into buffer positions. + let start_anchor = buffer.anchor_before(Point::new(new_start.row(), 0)); let start_block_ix = match self.blocks[last_block_ix..].binary_search_by(|probe| { probe .position @@ -167,10 +189,10 @@ impl BlockMap { }) { Ok(ix) | Err(ix) => last_block_ix + ix, }; - let end_block_ix = if edit.new.end > wrap_snapshot.max_point().row() { + let end_block_ix = if new_end.row() > wrap_snapshot.max_point().row() { self.blocks.len() } else { - let end_anchor = buffer.anchor_before(Point::new(edit.new.end, 0)); + let end_anchor = buffer.anchor_before(Point::new(new_end.row() + 1, 0)); match self.blocks[start_block_ix..].binary_search_by(|probe| { probe .position @@ -182,7 +204,6 @@ impl BlockMap { } }; last_block_ix = end_block_ix; - blocks_in_edit.clear(); blocks_in_edit.extend( self.blocks[start_block_ix..end_block_ix] @@ -191,41 +212,68 @@ impl BlockMap { ); blocks_in_edit.sort_unstable_by_key(|(row, block)| (*row, block.disposition, block.id)); + // For each of these blocks, insert a new isomorphic transform preceding the block, + // and then insert the block itself. for (block_row, block) in blocks_in_edit.iter().copied() { let new_transforms_end = new_transforms.summary().input; if block.disposition.is_above() { if block_row > new_transforms_end.row { - new_transforms.push( - Transform::isomorphic(Point::new(block_row, 0) - new_transforms_end), - &(), - ); + push_isomorphic( + &mut new_transforms, + Point::new(block_row, 0) - new_transforms_end, + ) } } else { if block_row >= new_transforms_end.row { - new_transforms.push( - Transform::isomorphic( - Point::new(block_row, wrap_snapshot.line_len(block_row)) - - new_transforms_end, - ), - &(), + push_isomorphic( + &mut new_transforms, + Point::new(block_row, wrap_snapshot.line_len(block_row)) + - new_transforms_end, ); } } new_transforms.push(Transform::block(block.clone()), &()); } - } - if let Some(last_old_end) = last_old_end { - new_transforms.push(Transform::isomorphic(cursor.start() - last_old_end), &()); + // Insert an isomorphic transform after the final block. + let extent_after_last_block = new_end.0 - new_transforms.summary().input; + if !extent_after_last_block.is_zero() { + push_isomorphic(&mut new_transforms, extent_after_last_block); + } + + // Preserve any portion of the old transform after this edit. + let extent_after_edit = cursor.start().0 - old_end.0; + if !extent_after_edit.is_zero() { + push_isomorphic(&mut new_transforms, extent_after_edit); + } } + new_transforms.push_tree(cursor.suffix(&()), &()); + debug_assert_eq!(new_transforms.summary().input, wrap_snapshot.max_point().0); drop(cursor); *transforms = new_transforms; } } +fn push_isomorphic(tree: &mut SumTree, extent: Point) { + let mut extent = Some(extent); + tree.update_last( + |last_transform| { + if last_transform.is_isomorphic() { + let extent = extent.take().unwrap(); + last_transform.summary.input += &extent; + last_transform.summary.output += &extent; + } + }, + &(), + ); + if let Some(extent) = extent { + tree.push(Transform::isomorphic(extent), &()); + } +} + impl BlockPoint { fn new(row: u32, column: u32) -> Self { Self(Point::new(row, column)) From d2f4d37af8bb59690eaaec9221dd9e28ac45a767 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 15 Nov 2021 13:36:40 -0800 Subject: [PATCH 049/109] Get BlockMap randomized test passing w/o soft wraps Co-Authored-By: Nathan Sobo --- crates/editor/src/display_map/block_map.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index 615cf0d94bb1eadf073dd90919902a6e3441dc1a..68f0117192354a5fdf2e7cb4542e84bd84d528c7 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -133,6 +133,10 @@ impl BlockMap { } fn apply_edits(&self, wrap_snapshot: &WrapSnapshot, edits: Vec, cx: &AppContext) { + if edits.is_empty() { + return; + } + let buffer = self.buffer.read(cx); let mut transforms = self.transforms.lock(); let mut new_transforms = SumTree::new(); @@ -173,8 +177,6 @@ impl BlockMap { break; } } - old_end = old_end.min(old_max_point); - new_end = new_end.min(new_max_point); // Find the blocks within this edited region. // @@ -192,7 +194,7 @@ impl BlockMap { let end_block_ix = if new_end.row() > wrap_snapshot.max_point().row() { self.blocks.len() } else { - let end_anchor = buffer.anchor_before(Point::new(new_end.row() + 1, 0)); + let end_anchor = buffer.anchor_before(Point::new(new_end.row(), 0)); match self.blocks[start_block_ix..].binary_search_by(|probe| { probe .position @@ -236,6 +238,9 @@ impl BlockMap { new_transforms.push(Transform::block(block.clone()), &()); } + old_end = old_end.min(old_max_point); + new_end = new_end.min(new_max_point); + // Insert an isomorphic transform after the final block. let extent_after_last_block = new_end.0 - new_transforms.summary().input; if !extent_after_last_block.is_zero() { From b4bc7906d251254aa18384b0ed5854e0854b588d Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 15 Nov 2021 14:17:48 -0800 Subject: [PATCH 050/109] Propagate wrap edits to block map when folding / unfolding Co-Authored-By: Nathan Sobo --- crates/editor/src/display_map.rs | 16 ++++++++++++---- crates/editor/src/display_map/block_map.rs | 8 ++++---- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 67dbb34d5d376269185bfab0370e95b5f2e0f278..64ee9b9cc1bf7bf81ffa069892db267048aaa38d 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -80,12 +80,16 @@ impl DisplayMap { ) { let (mut fold_map, snapshot, edits) = self.fold_map.write(cx); let (snapshot, edits) = self.tab_map.sync(snapshot, edits); - self.wrap_map + let (snapshot, edits) = self + .wrap_map .update(cx, |map, cx| map.sync(snapshot, edits, cx)); + self.block_map.sync(&snapshot, edits, cx); let (snapshot, edits) = fold_map.fold(ranges, cx); let (snapshot, edits) = self.tab_map.sync(snapshot, edits); - self.wrap_map + let (snapshot, edits) = self + .wrap_map .update(cx, |map, cx| map.sync(snapshot, edits, cx)); + self.block_map.sync(&snapshot, edits, cx); } pub fn unfold( @@ -95,12 +99,16 @@ impl DisplayMap { ) { let (mut fold_map, snapshot, edits) = self.fold_map.write(cx); let (snapshot, edits) = self.tab_map.sync(snapshot, edits); - self.wrap_map + let (snapshot, edits) = self + .wrap_map .update(cx, |map, cx| map.sync(snapshot, edits, cx)); + self.block_map.sync(&snapshot, edits, cx); let (snapshot, edits) = fold_map.unfold(ranges, cx); let (snapshot, edits) = self.tab_map.sync(snapshot, edits); - self.wrap_map + let (snapshot, edits) = self + .wrap_map .update(cx, |map, cx| map.sync(snapshot, edits, cx)); + self.block_map.sync(&snapshot, edits, cx); } pub fn insert_blocks( diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index 68f0117192354a5fdf2e7cb4542e84bd84d528c7..331762552537798ef8539c5605b20c5684cbd5a2 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -113,7 +113,7 @@ impl BlockMap { edits: Vec, cx: &AppContext, ) -> BlockSnapshot { - self.apply_edits(&wrap_snapshot, edits, cx); + self.sync(&wrap_snapshot, edits, cx); *self.wrap_snapshot.lock() = wrap_snapshot.clone(); BlockSnapshot { wrap_snapshot, @@ -127,12 +127,12 @@ impl BlockMap { edits: Vec, cx: &AppContext, ) -> BlockMapWriter { - self.apply_edits(&wrap_snapshot, edits, cx); + self.sync(&wrap_snapshot, edits, cx); *self.wrap_snapshot.lock() = wrap_snapshot; BlockMapWriter(self) } - fn apply_edits(&self, wrap_snapshot: &WrapSnapshot, edits: Vec, cx: &AppContext) { + pub fn sync(&self, wrap_snapshot: &WrapSnapshot, edits: Vec, cx: &AppContext) { if edits.is_empty() { return; } @@ -356,7 +356,7 @@ impl<'a> BlockMapWriter<'a> { } } - self.0.apply_edits(&*self.0.wrap_snapshot.lock(), edits, cx); + self.0.sync(&*self.0.wrap_snapshot.lock(), edits, cx); ids } From 0a704b8d671ff7b556cbefa32998f2749f18a0a9 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 15 Nov 2021 14:18:45 -0800 Subject: [PATCH 051/109] Fix infinite loop in BlockMap::highlighted_chunks Co-Authored-By: Nathan Sobo --- crates/editor/src/display_map/block_map.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index 331762552537798ef8539c5605b20c5684cbd5a2..7be0507e7264fe4a8771371c555e3355833a383d 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -82,7 +82,7 @@ pub struct HighlightedChunks<'a> { input_chunk: HighlightedChunk<'a>, block_chunks: Option>, output_position: BlockPoint, - max_output_row: u32, + max_output_position: BlockPoint, } struct BlockChunks<'a> { @@ -374,6 +374,7 @@ impl BlockSnapshot { } pub fn highlighted_chunks_for_rows(&mut self, rows: Range) -> HighlightedChunks { + let max_output_position = self.max_point().min(BlockPoint::new(rows.end, 0)); let mut cursor = self.transforms.cursor::<(BlockPoint, WrapPoint)>(); let output_position = BlockPoint::new(rows.start, 0); cursor.seek(&output_position, Bias::Right, &()); @@ -390,7 +391,7 @@ impl BlockSnapshot { block_chunks: None, transforms: cursor, output_position, - max_output_row: rows.end, + max_output_position, } } @@ -493,7 +494,7 @@ impl<'a> Iterator for HighlightedChunks<'a> { type Item = HighlightedChunk<'a>; fn next(&mut self) -> Option { - if self.output_position.row >= self.max_output_row { + if self.output_position >= self.max_output_position { return None; } @@ -511,8 +512,7 @@ impl<'a> Iterator for HighlightedChunks<'a> { let block_start = self.transforms.start().0 .0; let block_end = self.transforms.end(&()).0 .0; let start_in_block = self.output_position.0 - block_start; - let end_in_block = - cmp::min(Point::new(self.max_output_row, 0), block_end) - block_start; + let end_in_block = cmp::min(self.max_output_position.0, block_end) - block_start; self.transforms.next(&()); let mut block_chunks = BlockChunks::new(block, start_in_block..end_in_block); if let Some(block_chunk) = block_chunks.next() { From 8b1b35913a38e2b5079a0b73c2731c8ff8a59cbd Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 15 Nov 2021 14:18:58 -0800 Subject: [PATCH 052/109] Fix group_ids assertions in diagnostics test Co-Authored-By: Nathan Sobo --- crates/language/src/tests.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/language/src/tests.rs b/crates/language/src/tests.rs index 4044c81d730239194e2b9bfcae3d0833b7b3ecce..28256fe066dbd9c6bea8bf46ca4cf8bbac05e982 100644 --- a/crates/language/src/tests.rs +++ b/crates/language/src/tests.rs @@ -483,7 +483,7 @@ async fn test_diagnostics(mut cx: gpui::TestAppContext) { &Diagnostic { severity: DiagnosticSeverity::ERROR, message: "undefined variable 'BB'".to_string(), - group_id: 0, + group_id: 1, }, ), ( @@ -491,7 +491,7 @@ async fn test_diagnostics(mut cx: gpui::TestAppContext) { &Diagnostic { severity: DiagnosticSeverity::ERROR, message: "undefined variable 'CCC'".to_string(), - group_id: 0, + group_id: 2, } ) ] @@ -548,7 +548,7 @@ async fn test_diagnostics(mut cx: gpui::TestAppContext) { &Diagnostic { severity: DiagnosticSeverity::WARNING, message: "unreachable statement".to_string(), - group_id: 0, + group_id: 1, } ), ( @@ -633,7 +633,7 @@ async fn test_diagnostics(mut cx: gpui::TestAppContext) { &Diagnostic { severity: DiagnosticSeverity::ERROR, message: "undefined variable 'BB'".to_string(), - group_id: 0, + group_id: 1, }, ) ] From 213b94afd4a8bfaeac4e5ed338868fa795b137a1 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 15 Nov 2021 14:21:55 -0800 Subject: [PATCH 053/109] Remove bias parameter from to_display_point Co-Authored-By: Nathan Sobo --- crates/editor/src/display_map.rs | 32 +++++++------ crates/editor/src/lib.rs | 79 +++++++++++++++----------------- 2 files changed, 56 insertions(+), 55 deletions(-) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 64ee9b9cc1bf7bf81ffa069892db267048aaa38d..75b336c707cdb225e875b7366367174d954cb7bf 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -18,7 +18,7 @@ pub use block_map::HighlightedChunks; pub use wrap_map::BufferRows; pub trait ToDisplayPoint { - fn to_display_point(&self, map: &DisplayMapSnapshot, bias: Bias) -> DisplayPoint; + fn to_display_point(&self, map: &DisplayMapSnapshot) -> DisplayPoint; } pub struct DisplayMap { @@ -186,7 +186,7 @@ impl DisplayMapSnapshot { *display_point.column_mut() = 0; let mut point = display_point.to_buffer_point(self, Bias::Left); point.column = 0; - let next_display_point = point.to_display_point(self, Bias::Left); + let next_display_point = point.to_display_point(self); if next_display_point == display_point { return (display_point, point); } @@ -199,7 +199,14 @@ impl DisplayMapSnapshot { *display_point.column_mut() = self.line_len(display_point.row()); let mut point = display_point.to_buffer_point(self, Bias::Right); point.column = self.buffer_snapshot.line_len(point.row); - let next_display_point = point.to_display_point(self, Bias::Right); + let next_display_point = DisplayPoint( + self.blocks_snapshot.to_block_point( + self.wraps_snapshot.to_wrap_point( + self.tabs_snapshot + .to_tab_point(point.to_fold_point(&self.folds_snapshot, Bias::Right)), + ), + ), + ); if next_display_point == display_point { return (display_point, point); } @@ -390,8 +397,8 @@ impl DisplayPoint { } impl ToDisplayPoint for Point { - fn to_display_point(&self, map: &DisplayMapSnapshot, bias: Bias) -> DisplayPoint { - let fold_point = self.to_fold_point(&map.folds_snapshot, bias); + fn to_display_point(&self, map: &DisplayMapSnapshot) -> DisplayPoint { + let fold_point = self.to_fold_point(&map.folds_snapshot, Bias::Left); let tab_point = map.tabs_snapshot.to_tab_point(fold_point); let wrap_point = map.wraps_snapshot.to_wrap_point(tab_point); let block_point = map.blocks_snapshot.to_block_point(wrap_point); @@ -400,9 +407,8 @@ impl ToDisplayPoint for Point { } impl ToDisplayPoint for Anchor { - fn to_display_point(&self, map: &DisplayMapSnapshot, bias: Bias) -> DisplayPoint { - self.to_point(&map.buffer_snapshot) - .to_display_point(map, bias) + fn to_display_point(&self, map: &DisplayMapSnapshot) -> DisplayPoint { + self.to_point(&map.buffer_snapshot).to_display_point(map) } } @@ -524,14 +530,14 @@ mod tests { assert_eq!( prev_display_bound, - prev_buffer_bound.to_display_point(&snapshot, Left), + prev_buffer_bound.to_display_point(&snapshot), "row boundary before {:?}. reported buffer row boundary: {:?}", point, prev_buffer_bound ); assert_eq!( next_display_bound, - next_buffer_bound.to_display_point(&snapshot, Right), + next_buffer_bound.to_display_point(&snapshot), "display row boundary after {:?}. reported buffer row boundary: {:?}", point, next_buffer_bound @@ -955,17 +961,17 @@ mod tests { let point = Point::new(0, "✅\t\t".len() as u32); let display_point = DisplayPoint::new(0, "✅ ".len() as u32); - assert_eq!(point.to_display_point(&map, Left), display_point); + assert_eq!(point.to_display_point(&map), display_point); assert_eq!(display_point.to_buffer_point(&map, Left), point,); let point = Point::new(1, "β\t".len() as u32); let display_point = DisplayPoint::new(1, "β ".len() as u32); - assert_eq!(point.to_display_point(&map, Left), display_point); + assert_eq!(point.to_display_point(&map), display_point); assert_eq!(display_point.to_buffer_point(&map, Left), point,); let point = Point::new(2, "🏀β\t\t".len() as u32); let display_point = DisplayPoint::new(2, "🏀β ".len() as u32); - assert_eq!(point.to_display_point(&map, Left), display_point); + assert_eq!(point.to_display_point(&map), display_point); assert_eq!(display_point.to_buffer_point(&map, Left), point,); // Display points inside of expanded tabs diff --git a/crates/editor/src/lib.rs b/crates/editor/src/lib.rs index 4a1f6392e4eb8469c9f500c352a98c5b39bcb458..8e495e5e9b08f287719586ab745928af16a785f6 100644 --- a/crates/editor/src/lib.rs +++ b/crates/editor/src/lib.rs @@ -519,13 +519,13 @@ impl Editor { .peek() .unwrap() .head() - .to_display_point(&display_map, Bias::Left) + .to_display_point(&display_map) .row() as f32; let last_cursor_bottom = selections .last() .unwrap() .head() - .to_display_point(&display_map, Bias::Right) + .to_display_point(&display_map) .row() as f32 + 1.0; @@ -570,7 +570,7 @@ impl Editor { let mut target_left = std::f32::INFINITY; let mut target_right = 0.0_f32; for selection in selections { - let head = selection.head().to_display_point(&display_map, Bias::Left); + let head = selection.head().to_display_point(&display_map); let start_column = head.column().saturating_sub(3); let end_column = cmp::min(display_map.line_len(head.row()), head.column() + 3); target_left = target_left @@ -1052,7 +1052,7 @@ impl Editor { let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); for selection in &mut selections { if selection.is_empty() { - let head = selection.head().to_display_point(&display_map, Bias::Left); + let head = selection.head().to_display_point(&display_map); let cursor = movement::left(&display_map, head) .unwrap() .to_buffer_point(&display_map, Bias::Left); @@ -1071,7 +1071,7 @@ impl Editor { let mut selections = self.selections::(cx).collect::>(); for selection in &mut selections { if selection.is_empty() { - let head = selection.head().to_display_point(&display_map, Bias::Left); + let head = selection.head().to_display_point(&display_map); let cursor = movement::right(&display_map, head) .unwrap() .to_buffer_point(&display_map, Bias::Right); @@ -1138,10 +1138,7 @@ impl Editor { let mut selections = selections.iter().peekable(); while let Some(selection) = selections.next() { let mut rows = selection.spanned_rows(false, &display_map).buffer_rows; - let goal_display_column = selection - .head() - .to_display_point(&display_map, Bias::Left) - .column(); + let goal_display_column = selection.head().to_display_point(&display_map).column(); // Accumulate contiguous regions of rows that we want to delete. while let Some(next_selection) = selections.peek() { @@ -1170,8 +1167,8 @@ impl Editor { cursor_buffer_row = rows.start.saturating_sub(1); } - let mut cursor = Point::new(cursor_buffer_row - row_delta, 0) - .to_display_point(&display_map, Bias::Left); + let mut cursor = + Point::new(cursor_buffer_row - row_delta, 0).to_display_point(&display_map); *cursor.column_mut() = cmp::min(goal_display_column, display_map.line_len(cursor.row())); row_delta += rows.len() as u32; @@ -1566,8 +1563,8 @@ impl Editor { let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); let mut selections = self.selections::(cx).collect::>(); for selection in &mut selections { - let start = selection.start.to_display_point(&display_map, Bias::Left); - let end = selection.end.to_display_point(&display_map, Bias::Left); + let start = selection.start.to_display_point(&display_map); + let end = selection.end.to_display_point(&display_map); if start != end { selection.end = selection.start.clone(); @@ -1588,7 +1585,7 @@ impl Editor { let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); let mut selections = self.selections::(cx).collect::>(); for selection in &mut selections { - let head = selection.head().to_display_point(&display_map, Bias::Left); + let head = selection.head().to_display_point(&display_map); let cursor = movement::left(&display_map, head) .unwrap() .to_buffer_point(&display_map, Bias::Left); @@ -1602,8 +1599,8 @@ impl Editor { let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); let mut selections = self.selections::(cx).collect::>(); for selection in &mut selections { - let start = selection.start.to_display_point(&display_map, Bias::Left); - let end = selection.end.to_display_point(&display_map, Bias::Left); + let start = selection.start.to_display_point(&display_map); + let end = selection.end.to_display_point(&display_map); if start != end { selection.start = selection.end.clone(); @@ -1624,7 +1621,7 @@ impl Editor { let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); let mut selections = self.selections::(cx).collect::>(); for selection in &mut selections { - let head = selection.head().to_display_point(&display_map, Bias::Left); + let head = selection.head().to_display_point(&display_map); let cursor = movement::right(&display_map, head) .unwrap() .to_buffer_point(&display_map, Bias::Right); @@ -1643,8 +1640,8 @@ impl Editor { let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); let mut selections = self.selections::(cx).collect::>(); for selection in &mut selections { - let start = selection.start.to_display_point(&display_map, Bias::Left); - let end = selection.end.to_display_point(&display_map, Bias::Left); + let start = selection.start.to_display_point(&display_map); + let end = selection.end.to_display_point(&display_map); if start != end { selection.goal = SelectionGoal::None; } @@ -1663,7 +1660,7 @@ impl Editor { let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); let mut selections = self.selections::(cx).collect::>(); for selection in &mut selections { - let head = selection.head().to_display_point(&display_map, Bias::Left); + let head = selection.head().to_display_point(&display_map); let (head, goal) = movement::up(&display_map, head, selection.goal).unwrap(); let cursor = head.to_buffer_point(&display_map, Bias::Left); selection.set_head(cursor); @@ -1681,8 +1678,8 @@ impl Editor { let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); let mut selections = self.selections::(cx).collect::>(); for selection in &mut selections { - let start = selection.start.to_display_point(&display_map, Bias::Left); - let end = selection.end.to_display_point(&display_map, Bias::Left); + let start = selection.start.to_display_point(&display_map); + let end = selection.end.to_display_point(&display_map); if start != end { selection.goal = SelectionGoal::None; } @@ -1701,7 +1698,7 @@ impl Editor { let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); let mut selections = self.selections::(cx).collect::>(); for selection in &mut selections { - let head = selection.head().to_display_point(&display_map, Bias::Left); + let head = selection.head().to_display_point(&display_map); let (head, goal) = movement::down(&display_map, head, selection.goal).unwrap(); let cursor = head.to_buffer_point(&display_map, Bias::Right); selection.set_head(cursor); @@ -1718,7 +1715,7 @@ impl Editor { let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); let mut selections = self.selections::(cx).collect::>(); for selection in &mut selections { - let head = selection.head().to_display_point(&display_map, Bias::Left); + let head = selection.head().to_display_point(&display_map); let new_head = movement::prev_word_boundary(&display_map, head).unwrap(); let cursor = new_head.to_buffer_point(&display_map, Bias::Left); selection.start = cursor.clone(); @@ -1737,7 +1734,7 @@ impl Editor { let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); let mut selections = self.selections::(cx).collect::>(); for selection in &mut selections { - let head = selection.head().to_display_point(&display_map, Bias::Left); + let head = selection.head().to_display_point(&display_map); let new_head = movement::prev_word_boundary(&display_map, head).unwrap(); let cursor = new_head.to_buffer_point(&display_map, Bias::Left); selection.set_head(cursor); @@ -1756,7 +1753,7 @@ impl Editor { let mut selections = self.selections::(cx).collect::>(); for selection in &mut selections { if selection.is_empty() { - let head = selection.head().to_display_point(&display_map, Bias::Left); + let head = selection.head().to_display_point(&display_map); let new_head = movement::prev_word_boundary(&display_map, head).unwrap(); let cursor = new_head.to_buffer_point(&display_map, Bias::Right); selection.set_head(cursor); @@ -1776,7 +1773,7 @@ impl Editor { let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); let mut selections = self.selections::(cx).collect::>(); for selection in &mut selections { - let head = selection.head().to_display_point(&display_map, Bias::Left); + let head = selection.head().to_display_point(&display_map); let new_head = movement::next_word_boundary(&display_map, head).unwrap(); let cursor = new_head.to_buffer_point(&display_map, Bias::Left); selection.start = cursor; @@ -1795,7 +1792,7 @@ impl Editor { let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); let mut selections = self.selections::(cx).collect::>(); for selection in &mut selections { - let head = selection.head().to_display_point(&display_map, Bias::Left); + let head = selection.head().to_display_point(&display_map); let new_head = movement::next_word_boundary(&display_map, head).unwrap(); let cursor = new_head.to_buffer_point(&display_map, Bias::Left); selection.set_head(cursor); @@ -1814,7 +1811,7 @@ impl Editor { let mut selections = self.selections::(cx).collect::>(); for selection in &mut selections { if selection.is_empty() { - let head = selection.head().to_display_point(&display_map, Bias::Left); + let head = selection.head().to_display_point(&display_map); let new_head = movement::next_word_boundary(&display_map, head).unwrap(); let cursor = new_head.to_buffer_point(&display_map, Bias::Right); selection.set_head(cursor); @@ -1834,7 +1831,7 @@ impl Editor { let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); let mut selections = self.selections::(cx).collect::>(); for selection in &mut selections { - let head = selection.head().to_display_point(&display_map, Bias::Left); + let head = selection.head().to_display_point(&display_map); let new_head = movement::line_beginning(&display_map, head, true).unwrap(); let cursor = new_head.to_buffer_point(&display_map, Bias::Left); selection.start = cursor; @@ -1853,7 +1850,7 @@ impl Editor { let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); let mut selections = self.selections::(cx).collect::>(); for selection in &mut selections { - let head = selection.head().to_display_point(&display_map, Bias::Left); + let head = selection.head().to_display_point(&display_map); let new_head = movement::line_beginning(&display_map, head, *toggle_indent).unwrap(); selection.set_head(new_head.to_buffer_point(&display_map, Bias::Left)); selection.goal = SelectionGoal::None; @@ -1877,7 +1874,7 @@ impl Editor { let mut selections = self.selections::(cx).collect::>(); { for selection in &mut selections { - let head = selection.head().to_display_point(&display_map, Bias::Left); + let head = selection.head().to_display_point(&display_map); let new_head = movement::line_end(&display_map, head).unwrap(); let anchor = new_head.to_buffer_point(&display_map, Bias::Left); selection.start = anchor.clone(); @@ -1893,7 +1890,7 @@ impl Editor { let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); let mut selections = self.selections::(cx).collect::>(); for selection in &mut selections { - let head = selection.head().to_display_point(&display_map, Bias::Left); + let head = selection.head().to_display_point(&display_map); let new_head = movement::line_end(&display_map, head).unwrap(); selection.set_head(new_head.to_buffer_point(&display_map, Bias::Left)); selection.goal = SelectionGoal::None; @@ -2263,8 +2260,8 @@ impl Editor { let start_index = self.selection_insertion_index(&selections, start); let pending_selection = if set_id.replica_id == self.buffer.read(cx).replica_id() { self.pending_selection.as_ref().and_then(|pending| { - let mut selection_start = pending.start.to_display_point(&display_map, Bias::Left); - let mut selection_end = pending.end.to_display_point(&display_map, Bias::Left); + let mut selection_start = pending.start.to_display_point(&display_map); + let mut selection_end = pending.end.to_display_point(&display_map); if pending.reversed { mem::swap(&mut selection_start, &mut selection_end); } @@ -2758,9 +2755,7 @@ fn compute_scroll_position( mut scroll_position: Vector2F, scroll_top_anchor: &Anchor, ) -> Vector2F { - let scroll_top = scroll_top_anchor - .to_display_point(snapshot, Bias::Left) - .row() as f32; + let scroll_top = scroll_top_anchor.to_display_point(snapshot).row() as f32; scroll_position.set_y(scroll_top + scroll_position.y()); scroll_position } @@ -2838,8 +2833,8 @@ impl View for Editor { impl SelectionExt for Selection { fn display_range(&self, map: &DisplayMapSnapshot) -> Range { - let start = self.start.to_display_point(map, Bias::Left); - let end = self.end.to_display_point(map, Bias::Left); + let start = self.start.to_display_point(map); + let end = self.end.to_display_point(map); if self.reversed { end..start } else { @@ -2852,8 +2847,8 @@ impl SelectionExt for Selection { include_end_if_at_line_start: bool, map: &DisplayMapSnapshot, ) -> SpannedRows { - let display_start = self.start.to_display_point(map, Bias::Left); - let mut display_end = self.end.to_display_point(map, Bias::Right); + let display_start = self.start.to_display_point(map); + let mut display_end = self.end.to_display_point(map); if !include_end_if_at_line_start && display_end.row() != map.max_point().row() && display_start.row() != display_end.row() From 3bd4542bcea6a9f4d48507bb6c890a57c9235927 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 15 Nov 2021 14:35:52 -0800 Subject: [PATCH 054/109] Remove bias parameter when converting display points to buffer points Co-Authored-By: Nathan Sobo --- crates/editor/src/display_map.rs | 80 +++++++++++++------------------- crates/editor/src/lib.rs | 70 +++++++++++++--------------- 2 files changed, 66 insertions(+), 84 deletions(-) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 75b336c707cdb225e875b7366367174d954cb7bf..d2e50f982007dcb8761aae59f2ae718851861706 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -184,9 +184,9 @@ impl DisplayMapSnapshot { pub fn prev_row_boundary(&self, mut display_point: DisplayPoint) -> (DisplayPoint, Point) { loop { *display_point.column_mut() = 0; - let mut point = display_point.to_buffer_point(self, Bias::Left); + let mut point = display_point.to_point(self); point.column = 0; - let next_display_point = point.to_display_point(self); + let next_display_point = self.point_to_display_point(point, Bias::Left); if next_display_point == display_point { return (display_point, point); } @@ -197,16 +197,9 @@ impl DisplayMapSnapshot { pub fn next_row_boundary(&self, mut display_point: DisplayPoint) -> (DisplayPoint, Point) { loop { *display_point.column_mut() = self.line_len(display_point.row()); - let mut point = display_point.to_buffer_point(self, Bias::Right); + let mut point = display_point.to_point(self); point.column = self.buffer_snapshot.line_len(point.row); - let next_display_point = DisplayPoint( - self.blocks_snapshot.to_block_point( - self.wraps_snapshot.to_wrap_point( - self.tabs_snapshot - .to_tab_point(point.to_fold_point(&self.folds_snapshot, Bias::Right)), - ), - ), - ); + let next_display_point = self.point_to_display_point(point, Bias::Right); if next_display_point == display_point { return (display_point, point); } @@ -214,6 +207,24 @@ impl DisplayMapSnapshot { } } + fn point_to_display_point(&self, point: Point, bias: Bias) -> DisplayPoint { + DisplayPoint( + self.blocks_snapshot.to_block_point( + self.wraps_snapshot.to_wrap_point( + self.tabs_snapshot + .to_tab_point(point.to_fold_point(&self.folds_snapshot, bias)), + ), + ), + ) + } + + fn display_point_to_point(&self, point: DisplayPoint, bias: Bias) -> Point { + let unblocked_point = self.blocks_snapshot.to_wrap_point(point.0); + let unwrapped_point = self.wraps_snapshot.to_tab_point(unblocked_point); + let unexpanded_point = self.tabs_snapshot.to_fold_point(unwrapped_point, bias).0; + unexpanded_point.to_buffer_point(&self.folds_snapshot) + } + pub fn max_point(&self) -> DisplayPoint { DisplayPoint(self.blocks_snapshot.max_point()) } @@ -336,16 +347,6 @@ impl DisplayMapSnapshot { pub fn longest_row(&self) -> u32 { self.wraps_snapshot.longest_row() } - - pub fn anchor_before(&self, point: DisplayPoint, bias: Bias) -> Anchor { - self.buffer_snapshot - .anchor_before(point.to_buffer_point(self, bias)) - } - - pub fn anchor_after(&self, point: DisplayPoint, bias: Bias) -> Anchor { - self.buffer_snapshot - .anchor_after(point.to_buffer_point(self, bias)) - } } #[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)] @@ -381,14 +382,11 @@ impl DisplayPoint { &mut self.0.column } - pub fn to_buffer_point(self, map: &DisplayMapSnapshot, bias: Bias) -> Point { - let unblocked_point = map.blocks_snapshot.to_wrap_point(self.0); - let unwrapped_point = map.wraps_snapshot.to_tab_point(unblocked_point); - let unexpanded_point = map.tabs_snapshot.to_fold_point(unwrapped_point, bias).0; - unexpanded_point.to_buffer_point(&map.folds_snapshot) + pub fn to_point(self, map: &DisplayMapSnapshot) -> Point { + map.display_point_to_point(self, Bias::Left) } - pub fn to_buffer_offset(self, map: &DisplayMapSnapshot, bias: Bias) -> usize { + pub fn to_offset(self, map: &DisplayMapSnapshot, bias: Bias) -> usize { let unblocked_point = map.blocks_snapshot.to_wrap_point(self.0); let unwrapped_point = map.wraps_snapshot.to_tab_point(unblocked_point); let unexpanded_point = map.tabs_snapshot.to_fold_point(unwrapped_point, bias).0; @@ -398,11 +396,7 @@ impl DisplayPoint { impl ToDisplayPoint for Point { fn to_display_point(&self, map: &DisplayMapSnapshot) -> DisplayPoint { - let fold_point = self.to_fold_point(&map.folds_snapshot, Bias::Left); - let tab_point = map.tabs_snapshot.to_tab_point(fold_point); - let wrap_point = map.wraps_snapshot.to_wrap_point(tab_point); - let block_point = map.blocks_snapshot.to_block_point(wrap_point); - DisplayPoint(block_point) + map.point_to_display_point(*self, Bias::Left) } } @@ -544,14 +538,14 @@ mod tests { ); assert_eq!( prev_buffer_bound, - prev_display_bound.to_buffer_point(&snapshot, Left), + prev_display_bound.to_point(&snapshot), "row boundary before {:?}. reported display row boundary: {:?}", point, prev_display_bound ); assert_eq!( next_buffer_bound, - next_display_bound.to_buffer_point(&snapshot, Right), + next_display_bound.to_point(&snapshot), "row boundary after {:?}. reported display row boundary: {:?}", point, next_display_bound @@ -962,33 +956,25 @@ mod tests { let point = Point::new(0, "✅\t\t".len() as u32); let display_point = DisplayPoint::new(0, "✅ ".len() as u32); assert_eq!(point.to_display_point(&map), display_point); - assert_eq!(display_point.to_buffer_point(&map, Left), point,); + assert_eq!(display_point.to_point(&map), point); let point = Point::new(1, "β\t".len() as u32); let display_point = DisplayPoint::new(1, "β ".len() as u32); assert_eq!(point.to_display_point(&map), display_point); - assert_eq!(display_point.to_buffer_point(&map, Left), point,); + assert_eq!(display_point.to_point(&map), point,); let point = Point::new(2, "🏀β\t\t".len() as u32); let display_point = DisplayPoint::new(2, "🏀β ".len() as u32); assert_eq!(point.to_display_point(&map), display_point); - assert_eq!(display_point.to_buffer_point(&map, Left), point,); + assert_eq!(display_point.to_point(&map), point,); // Display points inside of expanded tabs assert_eq!( - DisplayPoint::new(0, "✅ ".len() as u32).to_buffer_point(&map, Right), - Point::new(0, "✅\t\t".len() as u32), - ); - assert_eq!( - DisplayPoint::new(0, "✅ ".len() as u32).to_buffer_point(&map, Left), - Point::new(0, "✅\t".len() as u32), - ); - assert_eq!( - DisplayPoint::new(0, "✅ ".len() as u32).to_buffer_point(&map, Right), + DisplayPoint::new(0, "✅ ".len() as u32).to_point(&map), Point::new(0, "✅\t".len() as u32), ); assert_eq!( - DisplayPoint::new(0, "✅ ".len() as u32).to_buffer_point(&map, Left), + DisplayPoint::new(0, "✅ ".len() as u32).to_point(&map), Point::new(0, "✅".len() as u32), ); diff --git a/crates/editor/src/lib.rs b/crates/editor/src/lib.rs index 8e495e5e9b08f287719586ab745928af16a785f6..e6de6e351bc2eab42bf9004959c843ff2413ebe8 100644 --- a/crates/editor/src/lib.rs +++ b/crates/editor/src/lib.rs @@ -469,7 +469,7 @@ impl Editor { fn set_scroll_position(&mut self, mut scroll_position: Vector2F, cx: &mut ViewContext) { let map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); let scroll_top_buffer_offset = - DisplayPoint::new(scroll_position.y() as u32, 0).to_buffer_offset(&map, Bias::Right); + DisplayPoint::new(scroll_position.y() as u32, 0).to_offset(&map, Bias::Right); self.scroll_top_anchor = self .buffer .read(cx) @@ -620,7 +620,7 @@ impl Editor { let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); let buffer = self.buffer.read(cx); - let cursor = buffer.anchor_before(position.to_buffer_point(&display_map, Bias::Left)); + let cursor = buffer.anchor_before(position.to_point(&display_map)); let selection = Selection { id: post_inc(&mut self.next_selection_id), start: cursor.clone(), @@ -646,7 +646,7 @@ impl Editor { let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); if let Some(pending_selection) = self.pending_selection.as_mut() { let buffer = self.buffer.read(cx); - let cursor = buffer.anchor_before(position.to_buffer_point(&display_map, Bias::Left)); + let cursor = buffer.anchor_before(position.to_point(&display_map)); if cursor.cmp(&pending_selection.tail(), buffer).unwrap() < Ordering::Equal { if !pending_selection.reversed { pending_selection.end = pending_selection.start.clone(); @@ -763,8 +763,8 @@ impl Editor { }; Selection { id: post_inc(&mut self.next_selection_id), - start: start.to_buffer_point(&display_map, Bias::Left), - end: end.to_buffer_point(&display_map, Bias::Left), + start: start.to_point(&display_map), + end: end.to_point(&display_map), reversed, goal: SelectionGoal::None, } @@ -1055,7 +1055,7 @@ impl Editor { let head = selection.head().to_display_point(&display_map); let cursor = movement::left(&display_map, head) .unwrap() - .to_buffer_point(&display_map, Bias::Left); + .to_point(&display_map); selection.set_head(cursor); selection.goal = SelectionGoal::None; } @@ -1074,7 +1074,7 @@ impl Editor { let head = selection.head().to_display_point(&display_map); let cursor = movement::right(&display_map, head) .unwrap() - .to_buffer_point(&display_map, Bias::Right); + .to_point(&display_map); selection.set_head(cursor); selection.goal = SelectionGoal::None; } @@ -1173,10 +1173,7 @@ impl Editor { cmp::min(goal_display_column, display_map.line_len(cursor.row())); row_delta += rows.len() as u32; - new_cursors.push(( - selection.id, - cursor.to_buffer_point(&display_map, Bias::Left), - )); + new_cursors.push((selection.id, cursor.to_point(&display_map))); edit_ranges.push(edit_start..edit_end); } @@ -1571,7 +1568,7 @@ impl Editor { } else { let cursor = movement::left(&display_map, start) .unwrap() - .to_buffer_point(&display_map, Bias::Left); + .to_point(&display_map); selection.start = cursor.clone(); selection.end = cursor; } @@ -1588,7 +1585,7 @@ impl Editor { let head = selection.head().to_display_point(&display_map); let cursor = movement::left(&display_map, head) .unwrap() - .to_buffer_point(&display_map, Bias::Left); + .to_point(&display_map); selection.set_head(cursor); selection.goal = SelectionGoal::None; } @@ -1607,7 +1604,7 @@ impl Editor { } else { let cursor = movement::right(&display_map, end) .unwrap() - .to_buffer_point(&display_map, Bias::Right); + .to_point(&display_map); selection.start = cursor; selection.end = cursor; } @@ -1624,7 +1621,7 @@ impl Editor { let head = selection.head().to_display_point(&display_map); let cursor = movement::right(&display_map, head) .unwrap() - .to_buffer_point(&display_map, Bias::Right); + .to_point(&display_map); selection.set_head(cursor); selection.goal = SelectionGoal::None; } @@ -1647,7 +1644,7 @@ impl Editor { } let (start, goal) = movement::up(&display_map, start, selection.goal).unwrap(); - let cursor = start.to_buffer_point(&display_map, Bias::Left); + let cursor = start.to_point(&display_map); selection.start = cursor; selection.end = cursor; selection.goal = goal; @@ -1662,7 +1659,7 @@ impl Editor { for selection in &mut selections { let head = selection.head().to_display_point(&display_map); let (head, goal) = movement::up(&display_map, head, selection.goal).unwrap(); - let cursor = head.to_buffer_point(&display_map, Bias::Left); + let cursor = head.to_point(&display_map); selection.set_head(cursor); selection.goal = goal; } @@ -1685,7 +1682,7 @@ impl Editor { } let (start, goal) = movement::down(&display_map, end, selection.goal).unwrap(); - let cursor = start.to_buffer_point(&display_map, Bias::Right); + let cursor = start.to_point(&display_map); selection.start = cursor; selection.end = cursor; selection.goal = goal; @@ -1700,7 +1697,7 @@ impl Editor { for selection in &mut selections { let head = selection.head().to_display_point(&display_map); let (head, goal) = movement::down(&display_map, head, selection.goal).unwrap(); - let cursor = head.to_buffer_point(&display_map, Bias::Right); + let cursor = head.to_point(&display_map); selection.set_head(cursor); selection.goal = goal; } @@ -1717,7 +1714,7 @@ impl Editor { for selection in &mut selections { let head = selection.head().to_display_point(&display_map); let new_head = movement::prev_word_boundary(&display_map, head).unwrap(); - let cursor = new_head.to_buffer_point(&display_map, Bias::Left); + let cursor = new_head.to_point(&display_map); selection.start = cursor.clone(); selection.end = cursor; selection.reversed = false; @@ -1736,7 +1733,7 @@ impl Editor { for selection in &mut selections { let head = selection.head().to_display_point(&display_map); let new_head = movement::prev_word_boundary(&display_map, head).unwrap(); - let cursor = new_head.to_buffer_point(&display_map, Bias::Left); + let cursor = new_head.to_point(&display_map); selection.set_head(cursor); selection.goal = SelectionGoal::None; } @@ -1755,7 +1752,7 @@ impl Editor { if selection.is_empty() { let head = selection.head().to_display_point(&display_map); let new_head = movement::prev_word_boundary(&display_map, head).unwrap(); - let cursor = new_head.to_buffer_point(&display_map, Bias::Right); + let cursor = new_head.to_point(&display_map); selection.set_head(cursor); selection.goal = SelectionGoal::None; } @@ -1775,7 +1772,7 @@ impl Editor { for selection in &mut selections { let head = selection.head().to_display_point(&display_map); let new_head = movement::next_word_boundary(&display_map, head).unwrap(); - let cursor = new_head.to_buffer_point(&display_map, Bias::Left); + let cursor = new_head.to_point(&display_map); selection.start = cursor; selection.end = cursor; selection.reversed = false; @@ -1794,7 +1791,7 @@ impl Editor { for selection in &mut selections { let head = selection.head().to_display_point(&display_map); let new_head = movement::next_word_boundary(&display_map, head).unwrap(); - let cursor = new_head.to_buffer_point(&display_map, Bias::Left); + let cursor = new_head.to_point(&display_map); selection.set_head(cursor); selection.goal = SelectionGoal::None; } @@ -1813,7 +1810,7 @@ impl Editor { if selection.is_empty() { let head = selection.head().to_display_point(&display_map); let new_head = movement::next_word_boundary(&display_map, head).unwrap(); - let cursor = new_head.to_buffer_point(&display_map, Bias::Right); + let cursor = new_head.to_point(&display_map); selection.set_head(cursor); selection.goal = SelectionGoal::None; } @@ -1833,7 +1830,7 @@ impl Editor { for selection in &mut selections { let head = selection.head().to_display_point(&display_map); let new_head = movement::line_beginning(&display_map, head, true).unwrap(); - let cursor = new_head.to_buffer_point(&display_map, Bias::Left); + let cursor = new_head.to_point(&display_map); selection.start = cursor; selection.end = cursor; selection.reversed = false; @@ -1852,7 +1849,7 @@ impl Editor { for selection in &mut selections { let head = selection.head().to_display_point(&display_map); let new_head = movement::line_beginning(&display_map, head, *toggle_indent).unwrap(); - selection.set_head(new_head.to_buffer_point(&display_map, Bias::Left)); + selection.set_head(new_head.to_point(&display_map)); selection.goal = SelectionGoal::None; } self.update_selections(selections, true, cx); @@ -1876,7 +1873,7 @@ impl Editor { for selection in &mut selections { let head = selection.head().to_display_point(&display_map); let new_head = movement::line_end(&display_map, head).unwrap(); - let anchor = new_head.to_buffer_point(&display_map, Bias::Left); + let anchor = new_head.to_point(&display_map); selection.start = anchor.clone(); selection.end = anchor; selection.reversed = false; @@ -1892,7 +1889,7 @@ impl Editor { for selection in &mut selections { let head = selection.head().to_display_point(&display_map); let new_head = movement::line_end(&display_map, head).unwrap(); - selection.set_head(new_head.to_buffer_point(&display_map, Bias::Left)); + selection.set_head(new_head.to_point(&display_map)); selection.goal = SelectionGoal::None; } self.update_selections(selections, true, cx); @@ -2216,8 +2213,8 @@ impl Editor { let end = DisplayPoint::new(row, cmp::min(columns.end, line_len)); Some(Selection { id: post_inc(&mut self.next_selection_id), - start: start.to_buffer_point(display_map, Bias::Left), - end: end.to_buffer_point(display_map, Bias::Left), + start: start.to_point(display_map), + end: end.to_point(display_map), reversed, goal: SelectionGoal::ColumnRange { start: columns.start, @@ -2256,7 +2253,7 @@ impl Editor { .unwrap() .selections::(buffer) .collect::>(); - let start = range.start.to_buffer_point(&display_map, Bias::Left); + let start = range.start.to_point(&display_map); let start_index = self.selection_insertion_index(&selections, start); let pending_selection = if set_id.replica_id == self.buffer.read(cx).replica_id() { self.pending_selection.as_ref().and_then(|pending| { @@ -2435,7 +2432,7 @@ impl Editor { let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); for selection in selections { let range = selection.display_range(&display_map).sorted(); - let buffer_start_row = range.start.to_buffer_point(&display_map, Bias::Left).row; + let buffer_start_row = range.start.to_point(&display_map).row; for row in (0..=range.end.row()).rev() { if self.is_line_foldable(&display_map, row) && !display_map.is_line_folded(row) { @@ -2461,8 +2458,8 @@ impl Editor { .iter() .map(|s| { let range = s.display_range(&display_map).sorted(); - let mut start = range.start.to_buffer_point(&display_map, Bias::Left); - let mut end = range.end.to_buffer_point(&display_map, Bias::Left); + let mut start = range.start.to_point(&display_map); + let mut end = range.end.to_point(&display_map); start.column = 0; end.column = buffer.line_len(end.row); start..end @@ -2510,8 +2507,7 @@ impl Editor { } let end = end.unwrap_or(max_point); - return start.to_buffer_point(display_map, Bias::Left) - ..end.to_buffer_point(display_map, Bias::Left); + return start.to_point(display_map)..end.to_point(display_map); } pub fn fold_selected_ranges(&mut self, _: &FoldSelectedRanges, cx: &mut ViewContext) { From 2cb8b0fcd30282b75444ea8cba43f9711b8c2ac3 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 15 Nov 2021 15:04:53 -0800 Subject: [PATCH 055/109] wip --- crates/editor/src/display_map/block_map.rs | 44 ++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index 7be0507e7264fe4a8771371c555e3355833a383d..ec407ce5219af0b52a1df5f9d90038c7ff67ba3e 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -744,6 +744,50 @@ mod tests { ); } + #[gpui::test] + fn test_blocks_on_wrapped_lines(cx: &mut gpui::MutableAppContext) { + let family_id = cx.font_cache().load_family(&["Helvetica"]).unwrap(); + let font_id = cx + .font_cache() + .select_font(family_id, &Default::default()) + .unwrap(); + + let text = "\none two three\nfour five six\nseven eight"; + + let buffer = cx.add_model(|cx| Buffer::new(0, text, cx)); + let (fold_map, folds_snapshot) = FoldMap::new(buffer.clone(), cx); + let (tab_map, tabs_snapshot) = TabMap::new(folds_snapshot.clone(), 1); + let (wrap_map, wraps_snapshot) = WrapMap::new(tabs_snapshot, font_id, 14.0, Some(60.), cx); + let mut block_map = BlockMap::new(buffer.clone(), wraps_snapshot.clone()); + + let mut writer = block_map.write(wraps_snapshot.clone(), vec![], cx); + writer.insert( + vec![ + BlockProperties { + position: Point::new(1, 8), + text: "BLOCK 1", + disposition: BlockDisposition::Above, + runs: vec![], + }, + BlockProperties { + position: Point::new(2, 0), + text: "BLOCK 2", + disposition: BlockDisposition::Below, + runs: vec![], + }, + ], + cx, + ); + + // Blocks with an 'above' disposition go above their corresponding buffer line. + // Blocks with a 'below' disposition go below their corresponding buffer line. + let mut snapshot = block_map.read(wraps_snapshot, vec![], cx); + assert_eq!( + snapshot.text(), + "\nBLOCK 1\none two \nthree\nfour five \nsix\nBLOCK 2\nseven \neight" + ); + } + #[gpui::test(iterations = 100)] fn test_random_blocks(cx: &mut gpui::MutableAppContext, mut rng: StdRng) { let operations = env::var("OPERATIONS") From 862b988d563350822424ed57946cd6ba670310d1 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Mon, 15 Nov 2021 16:45:51 -0700 Subject: [PATCH 056/109] Position blocks above/below buffer lines, even when the anchored line is soft-wrapped Co-Authored-By: Max Brunsfeld --- crates/editor/src/display_map.rs | 2 +- crates/editor/src/display_map/block_map.rs | 49 +++++++++++++++------- crates/editor/src/display_map/tab_map.rs | 13 +++++- crates/editor/src/display_map/wrap_map.rs | 12 +++++- 4 files changed, 58 insertions(+), 18 deletions(-) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index d2e50f982007dcb8761aae59f2ae718851861706..5d9eaed693a18716b9196a7e1793581bfd8bc710 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -210,7 +210,7 @@ impl DisplayMapSnapshot { fn point_to_display_point(&self, point: Point, bias: Bias) -> DisplayPoint { DisplayPoint( self.blocks_snapshot.to_block_point( - self.wraps_snapshot.to_wrap_point( + self.wraps_snapshot.from_tab_point( self.tabs_snapshot .to_tab_point(point.to_fold_point(&self.folds_snapshot, bias)), ), diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index ec407ce5219af0b52a1df5f9d90038c7ff67ba3e..9ab0e9dc0a64d79ae70628beddd2be73a710169c 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -179,9 +179,8 @@ impl BlockMap { } // Find the blocks within this edited region. - // - // TODO - convert these wrap map edits into buffer positions. - let start_anchor = buffer.anchor_before(Point::new(new_start.row(), 0)); + let new_start = wrap_snapshot.to_point(new_start, Bias::Left); + let start_anchor = buffer.anchor_before(new_start); let start_block_ix = match self.blocks[last_block_ix..].binary_search_by(|probe| { probe .position @@ -194,7 +193,8 @@ impl BlockMap { let end_block_ix = if new_end.row() > wrap_snapshot.max_point().row() { self.blocks.len() } else { - let end_anchor = buffer.anchor_before(Point::new(new_end.row(), 0)); + let new_end = wrap_snapshot.to_point(new_end, Bias::Left); + let end_anchor = buffer.anchor_before(new_end); match self.blocks[start_block_ix..].binary_search_by(|probe| { probe .position @@ -210,7 +210,17 @@ impl BlockMap { blocks_in_edit.extend( self.blocks[start_block_ix..end_block_ix] .iter() - .map(|block| (block.position.to_point(buffer).row, block)), + .map(|block| { + let mut position = block.position.to_point(buffer); + match block.disposition { + BlockDisposition::Above => position.column = 0, + BlockDisposition::Below => { + position.column = buffer.line_len(position.row) + } + } + let position = wrap_snapshot.from_point(position, Bias::Left); + (position.row(), block) + }), ); blocks_in_edit.sort_unstable_by_key(|(row, block)| (*row, block.disposition, block.id)); @@ -312,13 +322,24 @@ impl<'a> BlockMapWriter<'a> { let buffer = self.0.buffer.read(cx); let mut ids = Vec::new(); let mut edits = Vec::>::new(); + let wrap_snapshot = &*self.0.wrap_snapshot.lock(); for block in blocks { let id = BlockId(self.0.next_block_id.fetch_add(1, SeqCst)); ids.push(id); let position = buffer.anchor_before(block.position); - let row = position.to_point(buffer).row; + let point = position.to_point(buffer); + let start_row = wrap_snapshot + .from_point(Point::new(point.row, 0), Bias::Left) + .row(); + let end_row = if point.row == buffer.max_point().row { + wrap_snapshot.max_point().row() + 1 + } else { + wrap_snapshot + .from_point(Point::new(point.row + 1, 0), Bias::Left) + .row() + }; let block_ix = match self .0 @@ -345,18 +366,18 @@ impl<'a> BlockMapWriter<'a> { }), ); - if let Err(edit_ix) = edits.binary_search_by_key(&row, |edit| edit.old.start) { + if let Err(edit_ix) = edits.binary_search_by_key(&start_row, |edit| edit.old.start) { edits.insert( edit_ix, Edit { - old: row..(row + 1), - new: row..(row + 1), + old: start_row..end_row, + new: start_row..end_row, }, ); } } - self.0.sync(&*self.0.wrap_snapshot.lock(), edits, cx); + self.0.sync(wrap_snapshot, edits, cx); ids } @@ -752,7 +773,7 @@ mod tests { .select_font(family_id, &Default::default()) .unwrap(); - let text = "\none two three\nfour five six\nseven eight"; + let text = "one two three\nfour five six\nseven eight"; let buffer = cx.add_model(|cx| Buffer::new(0, text, cx)); let (fold_map, folds_snapshot) = FoldMap::new(buffer.clone(), cx); @@ -764,13 +785,13 @@ mod tests { writer.insert( vec![ BlockProperties { - position: Point::new(1, 8), + position: Point::new(1, 12), text: "BLOCK 1", disposition: BlockDisposition::Above, runs: vec![], }, BlockProperties { - position: Point::new(2, 0), + position: Point::new(1, 1), text: "BLOCK 2", disposition: BlockDisposition::Below, runs: vec![], @@ -784,7 +805,7 @@ mod tests { let mut snapshot = block_map.read(wraps_snapshot, vec![], cx); assert_eq!( snapshot.text(), - "\nBLOCK 1\none two \nthree\nfour five \nsix\nBLOCK 2\nseven \neight" + "one two \nthree\nBLOCK 1\nfour five \nsix\nBLOCK 2\nseven \neight" ); } diff --git a/crates/editor/src/display_map/tab_map.rs b/crates/editor/src/display_map/tab_map.rs index 93fae6d6b2c5ea443574afd5f82dd0c636a131d8..4d6ab6ac71a22a94ca0e78f3ffa4113ecb9fb144 100644 --- a/crates/editor/src/display_map/tab_map.rs +++ b/crates/editor/src/display_map/tab_map.rs @@ -1,4 +1,5 @@ -use super::fold_map::{self, FoldEdit, FoldPoint, Snapshot as FoldSnapshot}; +use super::fold_map::{self, FoldEdit, FoldPoint, Snapshot as FoldSnapshot, ToFoldPoint}; +use buffer::Point; use language::{rope, HighlightedChunk}; use parking_lot::Mutex; use std::{mem, ops::Range}; @@ -207,6 +208,10 @@ impl Snapshot { TabPoint::new(input.row(), expanded as u32) } + pub fn from_point(&self, point: Point, bias: Bias) -> TabPoint { + self.to_tab_point(point.to_fold_point(&self.fold_snapshot, bias)) + } + pub fn to_fold_point(&self, output: TabPoint, bias: Bias) -> (FoldPoint, usize, usize) { let chars = self.fold_snapshot.chars_at(FoldPoint::new(output.row(), 0)); let expanded = output.column() as usize; @@ -219,6 +224,12 @@ impl Snapshot { ) } + pub fn to_point(&self, point: TabPoint, bias: Bias) -> Point { + self.to_fold_point(point, bias) + .0 + .to_buffer_point(&self.fold_snapshot) + } + fn expand_tabs(chars: impl Iterator, column: usize, tab_size: usize) -> usize { let mut expanded_chars = 0; let mut expanded_bytes = 0; diff --git a/crates/editor/src/display_map/wrap_map.rs b/crates/editor/src/display_map/wrap_map.rs index ffc27815205869fdd61b55659f9ecce603236ba2..51908c087764e2361755e41c24ecb273e00b5d04 100644 --- a/crates/editor/src/display_map/wrap_map.rs +++ b/crates/editor/src/display_map/wrap_map.rs @@ -657,7 +657,15 @@ impl Snapshot { TabPoint(tab_point) } - pub fn to_wrap_point(&self, point: TabPoint) -> WrapPoint { + pub fn to_point(&self, point: WrapPoint, bias: Bias) -> Point { + self.tab_snapshot.to_point(self.to_tab_point(point), bias) + } + + pub fn from_point(&self, point: Point, bias: Bias) -> WrapPoint { + self.from_tab_point(self.tab_snapshot.from_point(point, bias)) + } + + pub fn from_tab_point(&self, point: TabPoint) -> WrapPoint { let mut cursor = self.transforms.cursor::<(TabPoint, WrapPoint)>(); cursor.seek(&point, Bias::Right, &()); WrapPoint(cursor.start().1 .0 + (point.0 - cursor.start().0 .0)) @@ -673,7 +681,7 @@ impl Snapshot { } } - self.to_wrap_point(self.tab_snapshot.clip_point(self.to_tab_point(point), bias)) + self.from_tab_point(self.tab_snapshot.clip_point(self.to_tab_point(point), bias)) } fn check_invariants(&self) { From 7b12c1c9e0c3e785fc1a1e69939e24d5e0ce1674 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Mon, 15 Nov 2021 16:59:02 -0700 Subject: [PATCH 057/109] Enable soft wrap in randomized test of BlockMap Co-Authored-By: Max Brunsfeld --- crates/editor/src/display_map/block_map.rs | 46 +++++++++++++--------- 1 file changed, 28 insertions(+), 18 deletions(-) diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index 9ab0e9dc0a64d79ae70628beddd2be73a710169c..878833562802d3d27ea5160c2542278da41fbf29 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -505,10 +505,6 @@ impl Transform { fn is_isomorphic(&self) -> bool { self.block.is_none() } - - fn block_disposition(&self) -> Option { - self.block.as_ref().map(|b| b.disposition) - } } impl<'a> Iterator for HighlightedChunks<'a> { @@ -815,7 +811,11 @@ mod tests { .map(|i| i.parse().expect("invalid `OPERATIONS` variable")) .unwrap_or(10); - let wrap_width = None; + let wrap_width = if rng.gen_bool(0.2) { + None + } else { + Some(rng.gen_range(0.0..=100.0)) + }; let tab_size = 1; let family_id = cx.font_cache().load_family(&["Helvetica"]).unwrap(); let font_id = cx @@ -841,15 +841,15 @@ mod tests { for _ in 0..operations { match rng.gen_range(0..=100) { - // 0..=19 => { - // let wrap_width = if rng.gen_bool(0.2) { - // None - // } else { - // Some(rng.gen_range(0.0..=1000.0)) - // }; - // log::info!("Setting wrap width to {:?}", wrap_width); - // wrap_map.update(cx, |map, cx| map.set_wrap_width(wrap_width, cx)); - // } + 0..=19 => { + let wrap_width = if rng.gen_bool(0.2) { + None + } else { + Some(rng.gen_range(0.0..=100.0)) + }; + log::info!("Setting wrap width to {:?}", wrap_width); + wrap_map.update(cx, |map, cx| map.set_wrap_width(wrap_width, cx)); + } 20..=39 => { let block_count = rng.gen_range(1..=1); let block_properties = (0..block_count) @@ -938,10 +938,20 @@ mod tests { .iter() .cloned() .map(|(id, block)| { + let mut position = block.position.to_point(buffer); + match block.disposition { + BlockDisposition::Above => { + position.column = 0; + } + BlockDisposition::Below => { + position.column = buffer.line_len(position.row); + } + }; + let row = wraps_snapshot.from_point(position, Bias::Left).row(); ( id, BlockProperties { - position: block.position.to_point(buffer), + position: row, text: block.text, runs: block.runs, disposition: block.disposition, @@ -950,7 +960,7 @@ mod tests { }) .collect::>(); sorted_blocks - .sort_unstable_by_key(|(id, block)| (block.position.row, block.disposition, *id)); + .sort_unstable_by_key(|(id, block)| (block.position, block.disposition, *id)); let mut sorted_blocks = sorted_blocks.into_iter().peekable(); let mut expected_text = String::new(); @@ -962,7 +972,7 @@ mod tests { } while let Some((_, block)) = sorted_blocks.peek() { - if block.position.row == row && block.disposition == BlockDisposition::Above { + if block.position == row && block.disposition == BlockDisposition::Above { expected_text.extend(block.text.chunks()); expected_text.push('\n'); sorted_blocks.next(); @@ -974,7 +984,7 @@ mod tests { expected_text.push_str(input_line); while let Some((_, block)) = sorted_blocks.peek() { - if block.position.row == row && block.disposition == BlockDisposition::Below { + if block.position == row && block.disposition == BlockDisposition::Below { expected_text.push('\n'); expected_text.extend(block.text.chunks()); sorted_blocks.next(); From 7d1ba6455b713721fc088a81dbe29f3b09092874 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Mon, 15 Nov 2021 17:09:26 -0700 Subject: [PATCH 058/109] Implement BlockMapWriter::remove Co-Authored-By: Max Brunsfeld --- crates/editor/src/display_map/block_map.rs | 69 +++++++++++++++------- 1 file changed, 49 insertions(+), 20 deletions(-) diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index 878833562802d3d27ea5160c2542278da41fbf29..beb53caeb047d43185df7646ac083ccb56921bf8 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -381,8 +381,37 @@ impl<'a> BlockMapWriter<'a> { ids } - pub fn remove(&mut self, _: HashSet, _: &AppContext) { - todo!() + pub fn remove(&mut self, block_ids: HashSet, cx: &AppContext) { + let buffer = self.0.buffer.read(cx); + let wrap_snapshot = &*self.0.wrap_snapshot.lock(); + let mut edits = Vec::new(); + let mut last_block_buffer_row = None; + self.0.blocks.retain(|block| { + if block_ids.contains(&block.id) { + let buffer_row = block.position.to_point(buffer).row; + if last_block_buffer_row != Some(buffer_row) { + last_block_buffer_row = Some(buffer_row); + let start_row = wrap_snapshot + .from_point(Point::new(buffer_row, 0), Bias::Left) + .row(); + let end_row = wrap_snapshot + .from_point( + Point::new(buffer_row, buffer.line_len(buffer_row)), + Bias::Left, + ) + .row() + + 1; + edits.push(Edit { + old: start_row..end_row, + new: start_row..end_row, + }) + } + false + } else { + true + } + }); + self.0.sync(wrap_snapshot, edits, cx); } } @@ -895,24 +924,24 @@ mod tests { expected_blocks.push((block_id, props)); } } - // 40..=59 if !expected_blocks.is_empty() => { - // let block_count = rng.gen_range(1..=4.min(expected_blocks.len())); - // let block_ids_to_remove = (0..block_count) - // .map(|_| { - // expected_blocks - // .remove(rng.gen_range(0..expected_blocks.len())) - // .0 - // }) - // .collect(); - - // let (folds_snapshot, fold_edits) = fold_map.read(cx); - // let (tabs_snapshot, tab_edits) = tab_map.sync(folds_snapshot, fold_edits); - // let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| { - // wrap_map.sync(tabs_snapshot, tab_edits, cx) - // }); - // let mut block_map = block_map.write(wraps_snapshot, wrap_edits, cx); - // block_map.remove(block_ids_to_remove, cx); - // } + 40..=59 if !expected_blocks.is_empty() => { + let block_count = rng.gen_range(1..=4.min(expected_blocks.len())); + let block_ids_to_remove = (0..block_count) + .map(|_| { + expected_blocks + .remove(rng.gen_range(0..expected_blocks.len())) + .0 + }) + .collect(); + + let (folds_snapshot, fold_edits) = fold_map.read(cx); + let (tabs_snapshot, tab_edits) = tab_map.sync(folds_snapshot, fold_edits); + let (wraps_snapshot, wrap_edits) = wrap_map.update(cx, |wrap_map, cx| { + wrap_map.sync(tabs_snapshot, tab_edits, cx) + }); + let mut block_map = block_map.write(wraps_snapshot, wrap_edits, cx); + block_map.remove(block_ids_to_remove, cx); + } _ => { buffer.update(cx, |buffer, _| { buffer.randomly_edit(&mut rng, 1); From 76ee44748e7cba198c2ee240980236a98580284d Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 15 Nov 2021 16:25:51 -0800 Subject: [PATCH 059/109] Fix minor bug in BlockMap::clip_point Co-Authored-By: Nathan Sobo --- crates/editor/src/display_map/block_map.rs | 45 ++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index beb53caeb047d43185df7646ac083ccb56921bf8..0d8ae9776f62fc32b5f049ae709e4b306c4c858e 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -452,6 +452,11 @@ impl BlockSnapshot { pub fn clip_point(&self, point: BlockPoint, bias: Bias) -> BlockPoint { let mut cursor = self.transforms.cursor::<(BlockPoint, WrapPoint)>(); cursor.seek(&point, Bias::Right, &()); + if let Some(transform) = cursor.prev_item() { + if transform.is_isomorphic() && point == cursor.start().0 { + return point; + } + } if let Some(transform) = cursor.item() { if transform.is_isomorphic() { let (output_start, input_start) = cursor.start(); @@ -771,6 +776,46 @@ mod tests { snapshot.to_block_point(WrapPoint::new(1, 0)), BlockPoint::new(3, 0) ); + assert_eq!( + snapshot.clip_point(BlockPoint::new(1, 0), Bias::Left), + BlockPoint::new(1, 0) + ); + assert_eq!( + snapshot.clip_point(BlockPoint::new(1, 0), Bias::Right), + BlockPoint::new(1, 0) + ); + assert_eq!( + snapshot.clip_point(BlockPoint::new(1, 1), Bias::Left), + BlockPoint::new(1, 0) + ); + assert_eq!( + snapshot.clip_point(BlockPoint::new(1, 1), Bias::Right), + BlockPoint::new(3, 0) + ); + assert_eq!( + snapshot.clip_point(BlockPoint::new(3, 0), Bias::Left), + BlockPoint::new(3, 0) + ); + assert_eq!( + snapshot.clip_point(BlockPoint::new(3, 0), Bias::Right), + BlockPoint::new(3, 0) + ); + assert_eq!( + snapshot.clip_point(BlockPoint::new(5, 3), Bias::Left), + BlockPoint::new(5, 3) + ); + assert_eq!( + snapshot.clip_point(BlockPoint::new(5, 3), Bias::Right), + BlockPoint::new(5, 3) + ); + assert_eq!( + snapshot.clip_point(BlockPoint::new(6, 0), Bias::Left), + BlockPoint::new(5, 3) + ); + assert_eq!( + snapshot.clip_point(BlockPoint::new(6, 0), Bias::Right), + BlockPoint::new(5, 3) + ); // Insert a line break, separating two block decorations into separate // lines. From 1cc7615d065d816916a78f17a5e97b6b41899768 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Mon, 15 Nov 2021 17:41:13 -0800 Subject: [PATCH 060/109] Implement basic version of BlockMap::buffer_rows Passed 1 simple test Co-Authored-By: Nathan Sobo --- crates/editor/src/display_map/block_map.rs | 66 ++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index 0d8ae9776f62fc32b5f049ae709e4b306c4c858e..22375433b7f63ecea9984ccc65cf28bf4817266b 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -93,6 +93,13 @@ struct BlockChunks<'a> { offset: usize, } +struct BufferRows<'a> { + transforms: sum_tree::Cursor<'a, Transform, (BlockPoint, WrapPoint)>, + input_buffer_rows: wrap_map::BufferRows<'a>, + input_buffer_row: (u32, bool), + output_row: u32, +} + impl BlockMap { pub fn new(buffer: ModelHandle, wrap_snapshot: WrapSnapshot) -> Self { Self { @@ -445,6 +452,25 @@ impl BlockSnapshot { } } + pub fn buffer_rows(&mut self, start_row: u32) -> BufferRows { + let mut transforms = self.transforms.cursor::<(BlockPoint, WrapPoint)>(); + transforms.seek(&BlockPoint::new(start_row, 0), Bias::Left, &()); + let mut input_row = transforms.start().1.row(); + if let Some(transform) = transforms.item() { + if transform.is_isomorphic() { + input_row += start_row - transforms.start().0.row; + } + } + let mut input_buffer_rows = self.wrap_snapshot.buffer_rows(input_row); + let input_buffer_row = input_buffer_rows.next().unwrap(); + BufferRows { + transforms, + input_buffer_row, + input_buffer_rows, + output_row: start_row, + } + } + pub fn max_point(&self) -> BlockPoint { BlockPoint(self.transforms.summary().output) } @@ -661,6 +687,33 @@ impl<'a> Iterator for BlockChunks<'a> { } } +impl<'a> Iterator for BufferRows<'a> { + type Item = (u32, bool); + + fn next(&mut self) -> Option { + let transform = self.transforms.item()?; + let block_disposition = transform.block.as_ref().map(|b| b.disposition); + let (buffer_row, is_wrapped) = self.input_buffer_row; + + self.output_row += 1; + if BlockPoint::new(self.output_row, 0) >= self.transforms.end(&()).0 { + self.transforms.next(&()); + if let Some(transform) = self.transforms.item() { + let next_block_disposition = transform.block.as_ref().map(|b| b.disposition); + if block_disposition != Some(BlockDisposition::Above) + && next_block_disposition != Some(BlockDisposition::Below) + { + self.input_buffer_row = self.input_buffer_rows.next().unwrap(); + } + } + } else if block_disposition.is_none() { + self.input_buffer_row = self.input_buffer_rows.next().unwrap(); + } + + Some((buffer_row, !is_wrapped && block_disposition.is_none())) + } +} + impl sum_tree::Item for Transform { type Summary = TransformSummary; @@ -817,6 +870,19 @@ mod tests { BlockPoint::new(5, 3) ); + assert_eq!( + snapshot.buffer_rows(0).collect::>(), + &[ + (0, true), + (1, false), + (1, false), + (1, true), + (2, true), + (3, true), + (3, false), + ] + ); + // Insert a line break, separating two block decorations into separate // lines. buffer.update(cx, |buffer, cx| { From 712616d16788b492d74ed1327046055ca49c1e7b Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 16 Nov 2021 15:35:35 +0100 Subject: [PATCH 061/109] Start on a randomized test for `BlockMap::buffer_rows` --- crates/editor/src/display_map.rs | 5 ++-- crates/editor/src/display_map/block_map.rs | 34 +++++++++++++++++----- 2 files changed, 29 insertions(+), 10 deletions(-) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 5d9eaed693a18716b9196a7e1793581bfd8bc710..da9756956fd77ad6b12612b8009985d63a94dd12 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -14,8 +14,7 @@ use sum_tree::Bias; use tab_map::TabMap; use wrap_map::WrapMap; -pub use block_map::HighlightedChunks; -pub use wrap_map::BufferRows; +pub use block_map::{BufferRows, HighlightedChunks}; pub trait ToDisplayPoint { fn to_display_point(&self, map: &DisplayMapSnapshot) -> DisplayPoint; @@ -174,7 +173,7 @@ impl DisplayMapSnapshot { } pub fn buffer_rows(&self, start_row: u32) -> BufferRows { - self.wraps_snapshot.buffer_rows(start_row) + self.blocks_snapshot.buffer_rows(start_row) } pub fn buffer_row_count(&self) -> u32 { diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index 22375433b7f63ecea9984ccc65cf28bf4817266b..0a8f681ee81426bc0be41d8dd85203f5f504c9bd 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -93,7 +93,7 @@ struct BlockChunks<'a> { offset: usize, } -struct BufferRows<'a> { +pub struct BufferRows<'a> { transforms: sum_tree::Cursor<'a, Transform, (BlockPoint, WrapPoint)>, input_buffer_rows: wrap_map::BufferRows<'a>, input_buffer_row: (u32, bool), @@ -452,7 +452,7 @@ impl BlockSnapshot { } } - pub fn buffer_rows(&mut self, start_row: u32) -> BufferRows { + pub fn buffer_rows(&self, start_row: u32) -> BufferRows { let mut transforms = self.transforms.cursor::<(BlockPoint, WrapPoint)>(); transforms.seek(&BlockPoint::new(start_row, 0), Bias::Left, &()); let mut input_row = transforms.start().1.row(); @@ -912,9 +912,9 @@ mod tests { let text = "one two three\nfour five six\nseven eight"; let buffer = cx.add_model(|cx| Buffer::new(0, text, cx)); - let (fold_map, folds_snapshot) = FoldMap::new(buffer.clone(), cx); - let (tab_map, tabs_snapshot) = TabMap::new(folds_snapshot.clone(), 1); - let (wrap_map, wraps_snapshot) = WrapMap::new(tabs_snapshot, font_id, 14.0, Some(60.), cx); + let (_, folds_snapshot) = FoldMap::new(buffer.clone(), cx); + let (_, tabs_snapshot) = TabMap::new(folds_snapshot.clone(), 1); + let (_, wraps_snapshot) = WrapMap::new(tabs_snapshot, font_id, 14.0, Some(60.), cx); let mut block_map = BlockMap::new(buffer.clone(), wraps_snapshot.clone()); let mut writer = block_map.write(wraps_snapshot.clone(), vec![], cx); @@ -1103,6 +1103,7 @@ mod tests { .sort_unstable_by_key(|(id, block)| (block.position, block.disposition, *id)); let mut sorted_blocks = sorted_blocks.into_iter().peekable(); + let mut expected_buffer_rows = Vec::new(); let mut expected_text = String::new(); let input_text = wraps_snapshot.text(); for (row, input_line) in input_text.split('\n').enumerate() { @@ -1111,22 +1112,37 @@ mod tests { expected_text.push('\n'); } + let buffer_row = wraps_snapshot + .to_point(WrapPoint::new(row, 0), Bias::Left) + .row; + while let Some((_, block)) = sorted_blocks.peek() { if block.position == row && block.disposition == BlockDisposition::Above { - expected_text.extend(block.text.chunks()); + let text = block.text.to_string(); + expected_text.push_str(&text); expected_text.push('\n'); + for _ in text.split('\n') { + expected_buffer_rows.push((buffer_row, false)); + } sorted_blocks.next(); } else { break; } } + let soft_wrapped = + wraps_snapshot.to_tab_point(WrapPoint::new(row, 0)).column() != 0; + expected_buffer_rows.push((buffer_row, !soft_wrapped)); expected_text.push_str(input_line); while let Some((_, block)) = sorted_blocks.peek() { if block.position == row && block.disposition == BlockDisposition::Below { + let text = block.text.to_string(); expected_text.push('\n'); - expected_text.extend(block.text.chunks()); + expected_text.push_str(&text); + for _ in text.split('\n') { + expected_buffer_rows.push((buffer_row, false)); + } sorted_blocks.next(); } else { break; @@ -1135,6 +1151,10 @@ mod tests { } assert_eq!(blocks_snapshot.text(), expected_text); + assert_eq!( + blocks_snapshot.buffer_rows(0).collect::>(), + expected_buffer_rows + ); } } } From d25ec39a23f7971e3791cd08997623bf8d32d982 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Tue, 16 Nov 2021 18:18:47 +0100 Subject: [PATCH 062/109] Rework `BufferRows` iterator to pass the randomized tests ...without booleans. Co-Authored-By: Nathan Sobo Co-Authored-By: Max Brunsfeld --- crates/editor/src/display_map/block_map.rs | 123 +++++++++++++-------- 1 file changed, 75 insertions(+), 48 deletions(-) diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index 0a8f681ee81426bc0be41d8dd85203f5f504c9bd..9da39f0a61d5164d0f21bd9d456d1b4adda3b236 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -96,20 +96,24 @@ struct BlockChunks<'a> { pub struct BufferRows<'a> { transforms: sum_tree::Cursor<'a, Transform, (BlockPoint, WrapPoint)>, input_buffer_rows: wrap_map::BufferRows<'a>, - input_buffer_row: (u32, bool), + input_buffer_row: Option<(u32, bool)>, + input_row: u32, output_row: u32, + max_output_row: u32, } impl BlockMap { pub fn new(buffer: ModelHandle, wrap_snapshot: WrapSnapshot) -> Self { + let mut transforms = SumTree::new(); + let lines = wrap_snapshot.text_summary().lines; + if !lines.is_zero() { + transforms.push(Transform::isomorphic(lines), &()); + } Self { buffer, next_block_id: AtomicUsize::new(0), blocks: Vec::new(), - transforms: Mutex::new(SumTree::from_item( - Transform::isomorphic(wrap_snapshot.text_summary().lines), - &(), - )), + transforms: Mutex::new(transforms), wrap_snapshot: Mutex::new(wrap_snapshot), } } @@ -162,9 +166,7 @@ impl BlockMap { // Preserve any portion of an old transform that precedes this edit. let extent_before_edit = old_start.0 - cursor.start().0; - if !extent_before_edit.is_zero() { - push_isomorphic(&mut new_transforms, extent_before_edit); - } + push_isomorphic(&mut new_transforms, extent_before_edit); // Skip over any old transforms that intersect this edit. let mut old_end = WrapPoint::new(edit.old.end, 0); @@ -234,24 +236,14 @@ impl BlockMap { // For each of these blocks, insert a new isomorphic transform preceding the block, // and then insert the block itself. for (block_row, block) in blocks_in_edit.iter().copied() { - let new_transforms_end = new_transforms.summary().input; - if block.disposition.is_above() { - if block_row > new_transforms_end.row { - push_isomorphic( - &mut new_transforms, - Point::new(block_row, 0) - new_transforms_end, - ) + let block_insertion_point = match block.disposition { + BlockDisposition::Above => Point::new(block_row, 0), + BlockDisposition::Below => { + Point::new(block_row, wrap_snapshot.line_len(block_row)) } - } else { - if block_row >= new_transforms_end.row { - push_isomorphic( - &mut new_transforms, - Point::new(block_row, wrap_snapshot.line_len(block_row)) - - new_transforms_end, - ); - } - } - + }; + let extent_before_block = block_insertion_point - new_transforms.summary().input; + push_isomorphic(&mut new_transforms, extent_before_block); new_transforms.push(Transform::block(block.clone()), &()); } @@ -260,15 +252,11 @@ impl BlockMap { // Insert an isomorphic transform after the final block. let extent_after_last_block = new_end.0 - new_transforms.summary().input; - if !extent_after_last_block.is_zero() { - push_isomorphic(&mut new_transforms, extent_after_last_block); - } + push_isomorphic(&mut new_transforms, extent_after_last_block); // Preserve any portion of the old transform after this edit. let extent_after_edit = cursor.start().0 - old_end.0; - if !extent_after_edit.is_zero() { - push_isomorphic(&mut new_transforms, extent_after_edit); - } + push_isomorphic(&mut new_transforms, extent_after_edit); } new_transforms.push_tree(cursor.suffix(&()), &()); @@ -280,6 +268,10 @@ impl BlockMap { } fn push_isomorphic(tree: &mut SumTree, extent: Point) { + if extent.is_zero() { + return; + } + let mut extent = Some(extent); tree.update_last( |last_transform| { @@ -465,9 +457,11 @@ impl BlockSnapshot { let input_buffer_row = input_buffer_rows.next().unwrap(); BufferRows { transforms, - input_buffer_row, + input_buffer_row: Some(input_buffer_row), input_buffer_rows, + input_row, output_row: start_row, + max_output_row: self.max_point().row, } } @@ -691,26 +685,56 @@ impl<'a> Iterator for BufferRows<'a> { type Item = (u32, bool); fn next(&mut self) -> Option { - let transform = self.transforms.item()?; - let block_disposition = transform.block.as_ref().map(|b| b.disposition); - let (buffer_row, is_wrapped) = self.input_buffer_row; + if self.output_row > self.max_output_row { + return None; + } + + let (buffer_row, _) = self.input_buffer_row.unwrap(); + log::info!( + "Called next. Output row: {}, Input row: {}, Buffer row: {}", + self.output_row, + self.input_row, + buffer_row + ); self.output_row += 1; if BlockPoint::new(self.output_row, 0) >= self.transforms.end(&()).0 { - self.transforms.next(&()); - if let Some(transform) = self.transforms.item() { - let next_block_disposition = transform.block.as_ref().map(|b| b.disposition); - if block_disposition != Some(BlockDisposition::Above) - && next_block_disposition != Some(BlockDisposition::Below) - { - self.input_buffer_row = self.input_buffer_rows.next().unwrap(); - } + self.transforms + .seek_forward(&BlockPoint::new(self.output_row, 0), Bias::Right, &()); + log::info!( + " Advancing to the next transform (block text: {:?}). Output row: {}, Transform starts at: {:?}", + self.transforms.item().and_then(|t| t.block.as_ref()).map(|b| b.text.to_string()), + self.output_row, + self.transforms.start().1 + ); + + let mut new_input_position = self.transforms.start().1 .0; + if self.transforms.item().map_or(false, |t| t.is_isomorphic()) { + new_input_position += Point::new(self.output_row, 0) - self.transforms.start().0 .0; + new_input_position = cmp::min(new_input_position, self.transforms.end(&()).1 .0); + } + + if new_input_position.row > self.input_row { + self.input_row = new_input_position.row; + self.input_buffer_row = self.input_buffer_rows.next(); + log::info!( + " Advancing the input buffer row. Input row: {}, Input buffer row {:?}", + self.input_row, + self.input_buffer_row + ) } - } else if block_disposition.is_none() { - self.input_buffer_row = self.input_buffer_rows.next().unwrap(); + } else if self.transforms.item().map_or(true, |t| t.is_isomorphic()) { + self.input_row += 1; + self.input_buffer_row = self.input_buffer_rows.next(); + log::info!( + " Advancing in isomorphic transform (off the end: {}). Input row: {}, Input buffer row {:?}", + self.transforms.item().is_none(), + self.input_row, + self.input_buffer_row + ) } - Some((buffer_row, !is_wrapped && block_disposition.is_none())) + Some((buffer_row, false)) } } @@ -998,15 +1022,18 @@ mod tests { let position = buffer.anchor_before(rng.gen_range(0..=buffer.len())); let len = rng.gen_range(0..10); - let text = Rope::from( + let mut text = Rope::from( RandomCharIter::new(&mut rng) .take(len) .collect::() + .to_uppercase() .as_str(), ); let disposition = if rng.gen() { + text.push_front("<"); BlockDisposition::Above } else { + text.push_front(">"); BlockDisposition::Below }; log::info!( @@ -1132,7 +1159,7 @@ mod tests { let soft_wrapped = wraps_snapshot.to_tab_point(WrapPoint::new(row, 0)).column() != 0; - expected_buffer_rows.push((buffer_row, !soft_wrapped)); + expected_buffer_rows.push((buffer_row, false)); expected_text.push_str(input_line); while let Some((_, block)) = sorted_blocks.peek() { From 880b3f087f1a9fb149c1ab6c9794da154a7721c1 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 16 Nov 2021 12:14:00 -0700 Subject: [PATCH 063/109] Insert empty isomorphic transforms on empty lines Co-Authored-By: Max Brunsfeld Co-Authored-By: Antonio Scandurra --- crates/editor/src/display_map/block_map.rs | 41 +++++++++++++++++----- 1 file changed, 33 insertions(+), 8 deletions(-) diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index 9da39f0a61d5164d0f21bd9d456d1b4adda3b236..247e0222797a5b273ccbb975cf0a47d31198214e 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -104,16 +104,14 @@ pub struct BufferRows<'a> { impl BlockMap { pub fn new(buffer: ModelHandle, wrap_snapshot: WrapSnapshot) -> Self { - let mut transforms = SumTree::new(); - let lines = wrap_snapshot.text_summary().lines; - if !lines.is_zero() { - transforms.push(Transform::isomorphic(lines), &()); - } Self { buffer, next_block_id: AtomicUsize::new(0), blocks: Vec::new(), - transforms: Mutex::new(transforms), + transforms: Mutex::new(SumTree::from_item( + Transform::isomorphic(wrap_snapshot.text_summary().lines), + &(), + )), wrap_snapshot: Mutex::new(wrap_snapshot), } } @@ -242,8 +240,13 @@ impl BlockMap { Point::new(block_row, wrap_snapshot.line_len(block_row)) } }; + let extent_before_block = block_insertion_point - new_transforms.summary().input; push_isomorphic(&mut new_transforms, extent_before_block); + if block.disposition == BlockDisposition::Below { + ensure_last_is_isomorphic_or_below_block(&mut new_transforms); + } + new_transforms.push(Transform::block(block.clone()), &()); } @@ -260,6 +263,7 @@ impl BlockMap { } new_transforms.push_tree(cursor.suffix(&()), &()); + ensure_last_is_isomorphic_or_below_block(&mut new_transforms); debug_assert_eq!(new_transforms.summary().input, wrap_snapshot.max_point().0); drop(cursor); @@ -267,6 +271,17 @@ impl BlockMap { } } +fn ensure_last_is_isomorphic_or_below_block(tree: &mut SumTree) { + if tree.last().map_or(true, |transform| { + transform + .block + .as_ref() + .map_or(false, |block| block.disposition.is_above()) + }) { + tree.push(Transform::isomorphic(Point::zero()), &()) + } +} + fn push_isomorphic(tree: &mut SumTree, extent: Point) { if extent.is_zero() { return; @@ -690,6 +705,7 @@ impl<'a> Iterator for BufferRows<'a> { } let (buffer_row, _) = self.input_buffer_row.unwrap(); + log::info!( "Called next. Output row: {}, Input row: {}, Buffer row: {}", self.output_row, @@ -771,6 +787,10 @@ impl BlockDisposition { fn is_above(&self) -> bool { matches!(self, BlockDisposition::Above) } + + fn is_below(&self) -> bool { + matches!(self, BlockDisposition::Below) + } } // Count the number of bytes prior to a target point. If the string doesn't contain the target @@ -1157,8 +1177,7 @@ mod tests { } } - let soft_wrapped = - wraps_snapshot.to_tab_point(WrapPoint::new(row, 0)).column() != 0; + let soft_wrapped = wraps_snapshot.to_tab_point(WrapPoint::new(row, 0)).column() > 0; expected_buffer_rows.push((buffer_row, false)); expected_text.push_str(input_line); @@ -1178,6 +1197,12 @@ mod tests { } assert_eq!(blocks_snapshot.text(), expected_text); + for row in 0..=blocks_snapshot.wrap_snapshot.max_point().row() { + let wrap_point = WrapPoint::new(row, 0); + let block_point = blocks_snapshot.to_block_point(wrap_point); + assert_eq!(blocks_snapshot.to_wrap_point(block_point), wrap_point); + } + assert_eq!( blocks_snapshot.buffer_rows(0).collect::>(), expected_buffer_rows From 092689ed5611e2c79f86465c69bfe0e58621e0b5 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 16 Nov 2021 13:10:06 -0700 Subject: [PATCH 064/109] WIP Co-Authored-By: Max Brunsfeld --- crates/editor/src/display_map/block_map.rs | 45 +++++++++++++++++----- 1 file changed, 35 insertions(+), 10 deletions(-) diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index 247e0222797a5b273ccbb975cf0a47d31198214e..202b8deec4d1d1695feefd7aeff7ec49bbafcd8c 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -704,21 +704,46 @@ impl<'a> Iterator for BufferRows<'a> { return None; } - let (buffer_row, _) = self.input_buffer_row.unwrap(); + let (buffer_row, is_wrapped) = self.input_buffer_row.unwrap(); + + let in_block = if let Some(transform) = self.transforms.item() { + if let Some(block) = transform.block.as_ref() { + if block.disposition.is_below() + && self.transforms.start().0 == BlockPoint::new(self.output_row, 0) + { + !self.transforms.prev_item().unwrap().is_isomorphic() + } else { + true + } + } else { + false + } + } else { + self.transforms.prev_item().unwrap().block.is_some() + }; log::info!( - "Called next. Output row: {}, Input row: {}, Buffer row: {}", + "============== Iterator next. Output row: {}, Input row: {}, Buffer row: {}, In block {} ===============", self.output_row, self.input_row, - buffer_row + buffer_row, + in_block ); self.output_row += 1; - if BlockPoint::new(self.output_row, 0) >= self.transforms.end(&()).0 { - self.transforms - .seek_forward(&BlockPoint::new(self.output_row, 0), Bias::Right, &()); + let output_point = BlockPoint::new(self.output_row, 0); + let transform_end = self.transforms.end(&()).0; + // if output_point > transform_end || output_point == transform_end && in_block { + if output_point >= transform_end { + log::info!(" Calling next once"); + self.transforms.next(&()); + if self.transforms.end(&()).0 < output_point { + log::info!(" Calling next twice"); + self.transforms.next(&()); + } + log::info!( - " Advancing to the next transform (block text: {:?}). Output row: {}, Transform starts at: {:?}", + " Advanced to the next transform (block text: {:?}). Output row: {}, Transform starts at: {:?}", self.transforms.item().and_then(|t| t.block.as_ref()).map(|b| b.text.to_string()), self.output_row, self.transforms.start().1 @@ -734,7 +759,7 @@ impl<'a> Iterator for BufferRows<'a> { self.input_row = new_input_position.row; self.input_buffer_row = self.input_buffer_rows.next(); log::info!( - " Advancing the input buffer row. Input row: {}, Input buffer row {:?}", + " Advanced the input buffer row. Input row: {}, Input buffer row {:?}", self.input_row, self.input_buffer_row ) @@ -750,7 +775,7 @@ impl<'a> Iterator for BufferRows<'a> { ) } - Some((buffer_row, false)) + Some((buffer_row, !is_wrapped && !in_block)) } } @@ -1178,7 +1203,7 @@ mod tests { } let soft_wrapped = wraps_snapshot.to_tab_point(WrapPoint::new(row, 0)).column() > 0; - expected_buffer_rows.push((buffer_row, false)); + expected_buffer_rows.push((buffer_row, !soft_wrapped)); expected_text.push_str(input_line); while let Some((_, block)) = sorted_blocks.peek() { From 7dd9b9539eefddda759284ffaf15d7264e014a7f Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 16 Nov 2021 13:19:04 -0700 Subject: [PATCH 065/109] WIP --- crates/editor/src/display_map/block_map.rs | 31 ++++++++-------------- 1 file changed, 11 insertions(+), 20 deletions(-) diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index 202b8deec4d1d1695feefd7aeff7ec49bbafcd8c..1af18fe2a03356692fd25c94c7c6893692a72990 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -100,6 +100,7 @@ pub struct BufferRows<'a> { input_row: u32, output_row: u32, max_output_row: u32, + in_block: bool, } impl BlockMap { @@ -463,10 +464,13 @@ impl BlockSnapshot { let mut transforms = self.transforms.cursor::<(BlockPoint, WrapPoint)>(); transforms.seek(&BlockPoint::new(start_row, 0), Bias::Left, &()); let mut input_row = transforms.start().1.row(); - if let Some(transform) = transforms.item() { - if transform.is_isomorphic() { - input_row += start_row - transforms.start().0.row; - } + let transform = transforms.item().unwrap(); + let in_block; + if transform.is_isomorphic() { + input_row += start_row - transforms.start().0.row; + in_block = false; + } else { + in_block = true; } let mut input_buffer_rows = self.wrap_snapshot.buffer_rows(input_row); let input_buffer_row = input_buffer_rows.next().unwrap(); @@ -477,6 +481,7 @@ impl BlockSnapshot { input_row, output_row: start_row, max_output_row: self.max_point().row, + in_block, } } @@ -705,22 +710,7 @@ impl<'a> Iterator for BufferRows<'a> { } let (buffer_row, is_wrapped) = self.input_buffer_row.unwrap(); - - let in_block = if let Some(transform) = self.transforms.item() { - if let Some(block) = transform.block.as_ref() { - if block.disposition.is_below() - && self.transforms.start().0 == BlockPoint::new(self.output_row, 0) - { - !self.transforms.prev_item().unwrap().is_isomorphic() - } else { - true - } - } else { - false - } - } else { - self.transforms.prev_item().unwrap().block.is_some() - }; + let in_block = self.in_block; log::info!( "============== Iterator next. Output row: {}, Input row: {}, Buffer row: {}, In block {} ===============", @@ -741,6 +731,7 @@ impl<'a> Iterator for BufferRows<'a> { log::info!(" Calling next twice"); self.transforms.next(&()); } + self.in_block = self.transforms.item().map_or(false, |t| !t.is_isomorphic()); log::info!( " Advanced to the next transform (block text: {:?}). Output row: {}, Transform starts at: {:?}", From 52a4c15c1413a1bd9c815ca24a7e6466b3dca233 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 16 Nov 2021 16:14:17 -0700 Subject: [PATCH 066/109] Eliminate non-highlighted chunks APIs Now we only have a single code path for chunks across all layers, but highlighting is optional and controlled by a flag. Co-Authored-By: Max Brunsfeld --- crates/editor/src/display_map.rs | 63 ++++++------ crates/editor/src/display_map/block_map.rs | 105 ++++++++++---------- crates/editor/src/display_map/fold_map.rs | 105 ++++---------------- crates/editor/src/display_map/tab_map.rs | 106 +++++---------------- crates/editor/src/display_map/wrap_map.rs | 103 +++++--------------- crates/editor/src/element.rs | 6 +- crates/editor/src/lib.rs | 8 +- crates/language/src/lib.rs | 85 +++++++++-------- crates/language/src/tests.rs | 4 +- 9 files changed, 206 insertions(+), 379 deletions(-) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index da9756956fd77ad6b12612b8009985d63a94dd12..c487151a36c93ad248040fe9a4bbe0488fd9419b 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -4,7 +4,7 @@ mod patch; mod tab_map; mod wrap_map; -use block_map::{BlockId, BlockMap, BlockPoint, BlockProperties}; +use block_map::{BlockId, BlockMap, BlockPoint}; use buffer::Rope; use fold_map::{FoldMap, ToFoldPoint as _}; use gpui::{fonts::FontId, Entity, ModelContext, ModelHandle}; @@ -14,7 +14,7 @@ use sum_tree::Bias; use tab_map::TabMap; use wrap_map::WrapMap; -pub use block_map::{BufferRows, HighlightedChunks}; +pub use block_map::{BlockDisposition, BlockProperties, BufferRows, Chunks}; pub trait ToDisplayPoint { fn to_display_point(&self, map: &DisplayMapSnapshot) -> DisplayPoint; @@ -213,6 +213,7 @@ impl DisplayMapSnapshot { self.tabs_snapshot .to_tab_point(point.to_fold_point(&self.folds_snapshot, bias)), ), + bias, ), ) } @@ -228,21 +229,19 @@ impl DisplayMapSnapshot { DisplayPoint(self.blocks_snapshot.max_point()) } - pub fn chunks_at(&self, display_row: u32) -> wrap_map::Chunks { - self.wraps_snapshot.chunks_at(display_row) + pub fn text_chunks(&self, display_row: u32) -> impl Iterator { + self.blocks_snapshot + .chunks(display_row..self.max_point().row() + 1, false) + .map(|h| h.text) } - pub fn highlighted_chunks_for_rows( - &mut self, - display_rows: Range, - ) -> block_map::HighlightedChunks { - self.blocks_snapshot - .highlighted_chunks_for_rows(display_rows) + pub fn chunks(&mut self, display_rows: Range) -> block_map::Chunks { + self.blocks_snapshot.chunks(display_rows, true) } pub fn chars_at<'a>(&'a self, point: DisplayPoint) -> impl Iterator + 'a { let mut column = 0; - let mut chars = self.chunks_at(point.row()).flat_map(str::chars); + let mut chars = self.text_chunks(point.row()).flat_map(str::chars); while column < point.column() { if let Some(c) = chars.next() { column += c.len_utf8() as u32; @@ -309,12 +308,12 @@ impl DisplayMapSnapshot { } pub fn text(&self) -> String { - self.chunks_at(0).collect() + self.text_chunks(0).collect() } pub fn line(&self, display_row: u32) -> String { let mut result = String::new(); - for chunk in self.chunks_at(display_row) { + for chunk in self.text_chunks(display_row) { if let Some(ix) = chunk.find('\n') { result.push_str(&chunk[0..ix]); break; @@ -610,7 +609,7 @@ mod tests { let snapshot = map.update(cx, |map, cx| map.snapshot(cx)); assert_eq!( - snapshot.chunks_at(0).collect::(), + snapshot.text_chunks(0).collect::(), "one two \nthree four \nfive\nsix seven \neight" ); assert_eq!( @@ -659,7 +658,7 @@ mod tests { let snapshot = map.update(cx, |map, cx| map.snapshot(cx)); assert_eq!( - snapshot.chunks_at(1).collect::(), + snapshot.text_chunks(1).collect::(), "three four \nfive\nsix and \nseven eight" ); @@ -668,13 +667,13 @@ mod tests { let snapshot = map.update(cx, |map, cx| map.snapshot(cx)); assert_eq!( - snapshot.chunks_at(1).collect::(), + snapshot.text_chunks(1).collect::(), "three \nfour five\nsix and \nseven \neight" ) } #[gpui::test] - fn test_chunks_at(cx: &mut gpui::MutableAppContext) { + fn test_text_chunks(cx: &mut gpui::MutableAppContext) { let text = sample_text(6, 6); let buffer = cx.add_model(|cx| Buffer::new(0, text, cx)); let tab_size = 4; @@ -701,7 +700,7 @@ mod tests { assert_eq!( map.update(cx, |map, cx| map.snapshot(cx)) - .chunks_at(1) + .text_chunks(1) .collect::() .lines() .next(), @@ -709,7 +708,7 @@ mod tests { ); assert_eq!( map.update(cx, |map, cx| map.snapshot(cx)) - .chunks_at(2) + .text_chunks(2) .collect::() .lines() .next(), @@ -718,7 +717,7 @@ mod tests { } #[gpui::test] - async fn test_highlighted_chunks_at(mut cx: gpui::TestAppContext) { + async fn test_chunks(mut cx: gpui::TestAppContext) { use unindent::Unindent as _; let text = r#" @@ -767,7 +766,7 @@ mod tests { let map = cx.add_model(|cx| DisplayMap::new(buffer, tab_size, font_id, font_size, None, cx)); assert_eq!( - cx.update(|cx| highlighted_chunks(0..5, &map, &theme, cx)), + cx.update(|cx| chunks(0..5, &map, &theme, cx)), vec![ ("fn ".to_string(), None), ("outer".to_string(), Some("fn.name")), @@ -778,7 +777,7 @@ mod tests { ] ); assert_eq!( - cx.update(|cx| highlighted_chunks(3..5, &map, &theme, cx)), + cx.update(|cx| chunks(3..5, &map, &theme, cx)), vec![ (" fn ".to_string(), Some("mod.body")), ("inner".to_string(), Some("fn.name")), @@ -790,7 +789,7 @@ mod tests { map.fold(vec![Point::new(0, 6)..Point::new(3, 2)], cx) }); assert_eq!( - cx.update(|cx| highlighted_chunks(0..2, &map, &theme, cx)), + cx.update(|cx| chunks(0..2, &map, &theme, cx)), vec![ ("fn ".to_string(), None), ("out".to_string(), Some("fn.name")), @@ -803,7 +802,7 @@ mod tests { } #[gpui::test] - async fn test_highlighted_chunks_with_soft_wrapping(mut cx: gpui::TestAppContext) { + async fn test_chunks_with_soft_wrapping(mut cx: gpui::TestAppContext) { use unindent::Unindent as _; cx.foreground().set_block_on_ticks(usize::MAX..=usize::MAX); @@ -855,7 +854,7 @@ mod tests { let map = cx .add_model(|cx| DisplayMap::new(buffer, tab_size, font_id, font_size, Some(40.0), cx)); assert_eq!( - cx.update(|cx| highlighted_chunks(0..5, &map, &theme, cx)), + cx.update(|cx| chunks(0..5, &map, &theme, cx)), [ ("fn \n".to_string(), None), ("oute\nr".to_string(), Some("fn.name")), @@ -863,7 +862,7 @@ mod tests { ] ); assert_eq!( - cx.update(|cx| highlighted_chunks(3..5, &map, &theme, cx)), + cx.update(|cx| chunks(3..5, &map, &theme, cx)), [("{}\n\n".to_string(), None)] ); @@ -871,7 +870,7 @@ mod tests { map.fold(vec![Point::new(0, 6)..Point::new(3, 2)], cx) }); assert_eq!( - cx.update(|cx| highlighted_chunks(1..4, &map, &theme, cx)), + cx.update(|cx| chunks(1..4, &map, &theme, cx)), [ ("out".to_string(), Some("fn.name")), ("…\n".to_string(), None), @@ -946,11 +945,11 @@ mod tests { let map = map.update(cx, |map, cx| map.snapshot(cx)); assert_eq!(map.text(), "✅ α\nβ \n🏀β γ"); assert_eq!( - map.chunks_at(0).collect::(), + map.text_chunks(0).collect::(), "✅ α\nβ \n🏀β γ" ); - assert_eq!(map.chunks_at(1).collect::(), "β \n🏀β γ"); - assert_eq!(map.chunks_at(2).collect::(), "🏀β γ"); + assert_eq!(map.text_chunks(1).collect::(), "β \n🏀β γ"); + assert_eq!(map.text_chunks(2).collect::(), "🏀β γ"); let point = Point::new(0, "✅\t\t".len() as u32); let display_point = DisplayPoint::new(0, "✅ ".len() as u32); @@ -1007,7 +1006,7 @@ mod tests { ) } - fn highlighted_chunks<'a>( + fn chunks<'a>( rows: Range, map: &ModelHandle, theme: &'a SyntaxTheme, @@ -1015,7 +1014,7 @@ mod tests { ) -> Vec<(String, Option<&'a str>)> { let mut snapshot = map.update(cx, |map, cx| map.snapshot(cx)); let mut chunks: Vec<(String, Option<&str>)> = Vec::new(); - for chunk in snapshot.highlighted_chunks_for_rows(rows) { + for chunk in snapshot.chunks(rows) { let style_name = chunk.highlight_id.name(theme); if let Some((last_chunk, last_style_name)) = chunks.last_mut() { if style_name == *last_style_name { diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index 1af18fe2a03356692fd25c94c7c6893692a72990..6cd6fbe6d4cf7a06b22e4d2def5b2a95ba43c260 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -1,7 +1,7 @@ use super::wrap_map::{self, Edit as WrapEdit, Snapshot as WrapSnapshot, WrapPoint}; use buffer::{rope, Anchor, Bias, Edit, Point, Rope, ToOffset, ToPoint as _}; use gpui::{fonts::HighlightStyle, AppContext, ModelHandle}; -use language::{Buffer, HighlightedChunk}; +use language::{Buffer, Chunk}; use parking_lot::Mutex; use std::{ cmp::{self, Ordering}, @@ -52,14 +52,14 @@ where P: Clone, T: Clone, { - position: P, - text: T, - runs: Vec<(usize, HighlightStyle)>, - disposition: BlockDisposition, + pub position: P, + pub text: T, + pub runs: Vec<(usize, HighlightStyle)>, + pub disposition: BlockDisposition, } #[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)] -enum BlockDisposition { +pub enum BlockDisposition { Above, Below, } @@ -76,10 +76,10 @@ struct TransformSummary { output: Point, } -pub struct HighlightedChunks<'a> { +pub struct Chunks<'a> { transforms: sum_tree::Cursor<'a, Transform, (BlockPoint, WrapPoint)>, - input_chunks: wrap_map::HighlightedChunks<'a>, - input_chunk: HighlightedChunk<'a>, + input_chunks: wrap_map::Chunks<'a>, + input_chunk: Chunk<'a>, block_chunks: Option>, output_position: BlockPoint, max_output_position: BlockPoint, @@ -433,12 +433,12 @@ impl<'a> BlockMapWriter<'a> { impl BlockSnapshot { #[cfg(test)] fn text(&mut self) -> String { - self.highlighted_chunks_for_rows(0..self.max_point().0.row + 1) + self.chunks(0..self.max_point().0.row + 1, false) .map(|chunk| chunk.text) .collect() } - pub fn highlighted_chunks_for_rows(&mut self, rows: Range) -> HighlightedChunks { + pub fn chunks(&self, rows: Range, highlights: bool) -> Chunks { let max_output_position = self.max_point().min(BlockPoint::new(rows.end, 0)); let mut cursor = self.transforms.cursor::<(BlockPoint, WrapPoint)>(); let output_position = BlockPoint::new(rows.start, 0); @@ -449,8 +449,8 @@ impl BlockSnapshot { let input_end_row = self.to_wrap_point(BlockPoint::new(rows.end, 0)).row(); let input_chunks = self .wrap_snapshot - .highlighted_chunks_for_rows(input_start_row..input_end_row); - HighlightedChunks { + .chunks(input_start_row..input_end_row, highlights); + Chunks { input_chunks, input_chunk: Default::default(), block_chunks: None, @@ -532,9 +532,9 @@ impl BlockSnapshot { } } - pub fn to_block_point(&self, wrap_point: WrapPoint) -> BlockPoint { + pub fn to_block_point(&self, wrap_point: WrapPoint, bias: Bias) -> BlockPoint { let mut cursor = self.transforms.cursor::<(WrapPoint, BlockPoint)>(); - cursor.seek(&wrap_point, Bias::Right, &()); + cursor.seek(&wrap_point, bias, &()); while let Some(item) = cursor.item() { if item.is_isomorphic() { break; @@ -581,8 +581,8 @@ impl Transform { } } -impl<'a> Iterator for HighlightedChunks<'a> { - type Item = HighlightedChunk<'a>; +impl<'a> Iterator for Chunks<'a> { + type Item = Chunk<'a>; fn next(&mut self) -> Option { if self.output_position >= self.max_output_position { @@ -630,7 +630,7 @@ impl<'a> Iterator for HighlightedChunks<'a> { self.transforms.next(&()); } - Some(HighlightedChunk { + Some(Chunk { text: prefix, ..self.input_chunk }) @@ -665,7 +665,7 @@ impl<'a> BlockChunks<'a> { } impl<'a> Iterator for BlockChunks<'a> { - type Item = HighlightedChunk<'a>; + type Item = Chunk<'a>; fn next(&mut self) -> Option { if self.chunk.is_none() { @@ -693,7 +693,7 @@ impl<'a> Iterator for BlockChunks<'a> { Some(suffix) }; - Some(HighlightedChunk { + Some(Chunk { text: chunk, highlight_id: Default::default(), diagnostic: None, @@ -712,33 +712,36 @@ impl<'a> Iterator for BufferRows<'a> { let (buffer_row, is_wrapped) = self.input_buffer_row.unwrap(); let in_block = self.in_block; - log::info!( - "============== Iterator next. Output row: {}, Input row: {}, Buffer row: {}, In block {} ===============", - self.output_row, - self.input_row, - buffer_row, - in_block - ); + // log::info!( + // "============== next - (output_row: {}, input_row: {}, buffer_row: {}, in_block: {}) ===============", + // self.output_row, + // self.input_row, + // buffer_row, + // in_block + // ); self.output_row += 1; let output_point = BlockPoint::new(self.output_row, 0); let transform_end = self.transforms.end(&()).0; // if output_point > transform_end || output_point == transform_end && in_block { if output_point >= transform_end { - log::info!(" Calling next once"); + // log::info!(" Calling next once"); self.transforms.next(&()); if self.transforms.end(&()).0 < output_point { - log::info!(" Calling next twice"); + // log::info!(" Calling next twice"); self.transforms.next(&()); } - self.in_block = self.transforms.item().map_or(false, |t| !t.is_isomorphic()); - log::info!( - " Advanced to the next transform (block text: {:?}). Output row: {}, Transform starts at: {:?}", - self.transforms.item().and_then(|t| t.block.as_ref()).map(|b| b.text.to_string()), - self.output_row, - self.transforms.start().1 - ); + if let Some(transform) = self.transforms.item() { + self.in_block = !transform.is_isomorphic(); + } + + // log::info!( + // " Advanced to the next transform (block text: {:?}). Output row: {}, Transform starts at: {:?}", + // self.transforms.item().and_then(|t| t.block.as_ref()).map(|b| b.text.to_string()), + // self.output_row, + // self.transforms.start().1 + // ); let mut new_input_position = self.transforms.start().1 .0; if self.transforms.item().map_or(false, |t| t.is_isomorphic()) { @@ -749,24 +752,24 @@ impl<'a> Iterator for BufferRows<'a> { if new_input_position.row > self.input_row { self.input_row = new_input_position.row; self.input_buffer_row = self.input_buffer_rows.next(); - log::info!( - " Advanced the input buffer row. Input row: {}, Input buffer row {:?}", - self.input_row, - self.input_buffer_row - ) + // log::info!( + // " Advanced the input buffer row. Input row: {}, Input buffer row {:?}", + // self.input_row, + // self.input_buffer_row + // ) } } else if self.transforms.item().map_or(true, |t| t.is_isomorphic()) { self.input_row += 1; self.input_buffer_row = self.input_buffer_rows.next(); - log::info!( - " Advancing in isomorphic transform (off the end: {}). Input row: {}, Input buffer row {:?}", - self.transforms.item().is_none(), - self.input_row, - self.input_buffer_row - ) + // log::info!( + // " Advancing in isomorphic transform (off the end: {}). Input row: {}, Input buffer row {:?}", + // self.transforms.item().is_none(), + // self.input_row, + // self.input_buffer_row + // ) } - Some((buffer_row, !is_wrapped && !in_block)) + Some((buffer_row, false)) } } @@ -886,7 +889,7 @@ mod tests { "aaa\nBLOCK 1\nBLOCK 2\nbbb\nccc\nddd\nBLOCK 3" ); assert_eq!( - snapshot.to_block_point(WrapPoint::new(1, 0)), + snapshot.to_block_point(WrapPoint::new(1, 0), Bias::Right), BlockPoint::new(3, 0) ); assert_eq!( @@ -1194,7 +1197,7 @@ mod tests { } let soft_wrapped = wraps_snapshot.to_tab_point(WrapPoint::new(row, 0)).column() > 0; - expected_buffer_rows.push((buffer_row, !soft_wrapped)); + expected_buffer_rows.push((buffer_row, false)); expected_text.push_str(input_line); while let Some((_, block)) = sorted_blocks.peek() { @@ -1215,7 +1218,7 @@ mod tests { assert_eq!(blocks_snapshot.text(), expected_text); for row in 0..=blocks_snapshot.wrap_snapshot.max_point().row() { let wrap_point = WrapPoint::new(row, 0); - let block_point = blocks_snapshot.to_block_point(wrap_point); + let block_point = blocks_snapshot.to_block_point(wrap_point, Bias::Right); assert_eq!(blocks_snapshot.to_wrap_point(block_point), wrap_point); } diff --git a/crates/editor/src/display_map/fold_map.rs b/crates/editor/src/display_map/fold_map.rs index a76fd2e5085e85576fb0eed2c708ccb6d4b12f22..3384928a8f324eea994f2019275a8cd4d217edcc 100644 --- a/crates/editor/src/display_map/fold_map.rs +++ b/crates/editor/src/display_map/fold_map.rs @@ -1,7 +1,6 @@ use gpui::{AppContext, ModelHandle}; use language::{ - Anchor, AnchorRangeExt, Buffer, HighlightId, HighlightedChunk, Point, PointUtf16, TextSummary, - ToOffset, + Anchor, AnchorRangeExt, Buffer, Chunk, HighlightId, Point, PointUtf16, TextSummary, ToOffset, }; use parking_lot::Mutex; use std::{ @@ -499,7 +498,9 @@ pub struct Snapshot { impl Snapshot { #[cfg(test)] pub fn text(&self) -> String { - self.chunks_at(FoldOffset(0)).collect() + self.chunks(FoldOffset(0)..self.len(), false) + .map(|c| c.text) + .collect() } #[cfg(test)] @@ -551,7 +552,6 @@ impl Snapshot { summary } - #[cfg(test)] pub fn len(&self) -> FoldOffset { FoldOffset(self.transforms.summary().output.bytes) } @@ -628,21 +628,13 @@ impl Snapshot { false } - pub fn chunks_at(&self, offset: FoldOffset) -> Chunks { - let mut transform_cursor = self.transforms.cursor::<(FoldOffset, usize)>(); - transform_cursor.seek(&offset, Bias::Right, &()); - let overshoot = offset.0 - transform_cursor.start().0 .0; - let buffer_offset = transform_cursor.start().1 + overshoot; - Chunks { - transform_cursor, - buffer_offset, - buffer_chunks: self - .buffer_snapshot - .text_for_range(buffer_offset..self.buffer_snapshot.len()), - } + pub fn chars_at(&self, start: FoldPoint) -> impl '_ + Iterator { + let start = start.to_offset(self); + self.chunks(start..self.len(), false) + .flat_map(|chunk| chunk.text.chars()) } - pub fn highlighted_chunks(&mut self, range: Range) -> HighlightedChunks { + pub fn chunks(&self, range: Range, enable_highlights: bool) -> Chunks { let mut transform_cursor = self.transforms.cursor::<(FoldOffset, usize)>(); transform_cursor.seek(&range.end, Bias::Right, &()); @@ -653,21 +645,16 @@ impl Snapshot { let overshoot = range.start.0 - transform_cursor.start().0 .0; let buffer_start = transform_cursor.start().1 + overshoot; - HighlightedChunks { + Chunks { transform_cursor, buffer_offset: buffer_start, buffer_chunks: self .buffer_snapshot - .highlighted_text_for_range(buffer_start..buffer_end), + .chunks(buffer_start..buffer_end, enable_highlights), buffer_chunk: None, } } - pub fn chars_at<'a>(&'a self, point: FoldPoint) -> impl Iterator + 'a { - let offset = point.to_offset(self); - self.chunks_at(offset).flat_map(str::chars) - } - #[cfg(test)] pub fn clip_offset(&self, offset: FoldOffset, bias: Bias) -> FoldOffset { let mut cursor = self.transforms.cursor::<(FoldOffset, usize)>(); @@ -948,66 +935,13 @@ impl<'a> Iterator for BufferRows<'a> { pub struct Chunks<'a> { transform_cursor: Cursor<'a, Transform, (FoldOffset, usize)>, - buffer_chunks: buffer::Chunks<'a>, + buffer_chunks: language::Chunks<'a>, + buffer_chunk: Option<(usize, Chunk<'a>)>, buffer_offset: usize, } impl<'a> Iterator for Chunks<'a> { - type Item = &'a str; - - fn next(&mut self) -> Option { - let transform = if let Some(item) = self.transform_cursor.item() { - item - } else { - return None; - }; - - // If we're in a fold, then return the fold's display text and - // advance the transform and buffer cursors to the end of the fold. - if let Some(output_text) = transform.output_text { - self.buffer_offset += transform.summary.input.bytes; - self.buffer_chunks.seek(self.buffer_offset); - - while self.buffer_offset >= self.transform_cursor.end(&()).1 - && self.transform_cursor.item().is_some() - { - self.transform_cursor.next(&()); - } - - return Some(output_text); - } - - // Otherwise, take a chunk from the buffer's text. - if let Some(mut chunk) = self.buffer_chunks.peek() { - let offset_in_chunk = self.buffer_offset - self.buffer_chunks.offset(); - chunk = &chunk[offset_in_chunk..]; - - // Truncate the chunk so that it ends at the next fold. - let region_end = self.transform_cursor.end(&()).1 - self.buffer_offset; - if chunk.len() >= region_end { - chunk = &chunk[0..region_end]; - self.transform_cursor.next(&()); - } else { - self.buffer_chunks.next(); - } - - self.buffer_offset += chunk.len(); - return Some(chunk); - } - - None - } -} - -pub struct HighlightedChunks<'a> { - transform_cursor: Cursor<'a, Transform, (FoldOffset, usize)>, - buffer_chunks: language::HighlightedChunks<'a>, - buffer_chunk: Option<(usize, HighlightedChunk<'a>)>, - buffer_offset: usize, -} - -impl<'a> Iterator for HighlightedChunks<'a> { - type Item = HighlightedChunk<'a>; + type Item = Chunk<'a>; fn next(&mut self) -> Option { let transform = if let Some(item) = self.transform_cursor.item() { @@ -1029,7 +963,7 @@ impl<'a> Iterator for HighlightedChunks<'a> { self.transform_cursor.next(&()); } - return Some(HighlightedChunk { + return Some(Chunk { text: output_text, highlight_id: HighlightId::default(), diagnostic: None, @@ -1428,11 +1362,14 @@ mod tests { } for _ in 0..5 { - let offset = snapshot + let start = snapshot .clip_offset(FoldOffset(rng.gen_range(0..=snapshot.len().0)), Bias::Right); assert_eq!( - snapshot.chunks_at(offset).collect::(), - &expected_text[offset.0..], + snapshot + .chunks(start..snapshot.len(), false) + .map(|c| c.text) + .collect::(), + &expected_text[start.0..], ); } diff --git a/crates/editor/src/display_map/tab_map.rs b/crates/editor/src/display_map/tab_map.rs index 4d6ab6ac71a22a94ca0e78f3ffa4113ecb9fb144..e0a7f2f846476d95c77bee0f4dc9b62d1dfc67d6 100644 --- a/crates/editor/src/display_map/tab_map.rs +++ b/crates/editor/src/display_map/tab_map.rs @@ -1,6 +1,6 @@ use super::fold_map::{self, FoldEdit, FoldPoint, Snapshot as FoldSnapshot, ToFoldPoint}; use buffer::Point; -use language::{rope, HighlightedChunk}; +use language::{rope, Chunk}; use parking_lot::Mutex; use std::{mem, ops::Range}; use sum_tree::Bias; @@ -22,6 +22,7 @@ impl TabMap { mut fold_edits: Vec, ) -> (Snapshot, Vec) { let mut old_snapshot = self.0.lock(); + let max_offset = old_snapshot.fold_snapshot.len(); let new_snapshot = Snapshot { fold_snapshot, tab_size: old_snapshot.tab_size, @@ -32,11 +33,11 @@ impl TabMap { let mut delta = 0; for chunk in old_snapshot .fold_snapshot - .chunks_at(fold_edit.old_bytes.end) + .chunks(fold_edit.old_bytes.end..max_offset, false) { let patterns: &[_] = &['\t', '\n']; - if let Some(ix) = chunk.find(patterns) { - if &chunk[ix..ix + 1] == "\t" { + if let Some(ix) = chunk.text.find(patterns) { + if &chunk.text[ix..ix + 1] == "\t" { fold_edit.old_bytes.end.0 += delta + ix + 1; fold_edit.new_bytes.end.0 += delta + ix + 1; } @@ -44,7 +45,7 @@ impl TabMap { break; } - delta += chunk.len(); + delta += chunk.text.len(); } } @@ -110,7 +111,10 @@ impl Snapshot { let mut first_line_chars = 0; let mut first_line_bytes = 0; - for c in self.chunks_at(range.start).flat_map(|chunk| chunk.chars()) { + for c in self + .chunks(range.start..self.max_point(), false) + .flat_map(|chunk| chunk.text.chars()) + { if c == '\n' || (range.start.row() == range.end.row() && first_line_bytes == range.end.column()) { @@ -123,8 +127,11 @@ impl Snapshot { let mut last_line_chars = 0; let mut last_line_bytes = 0; for c in self - .chunks_at(TabPoint::new(range.end.row(), 0).max(range.start)) - .flat_map(|chunk| chunk.chars()) + .chunks( + TabPoint::new(range.end.row(), 0).max(range.start)..self.max_point(), + false, + ) + .flat_map(|chunk| chunk.text.chars()) { if last_line_bytes == range.end.column() { break; @@ -146,21 +153,7 @@ impl Snapshot { self.fold_snapshot.version } - pub fn chunks_at(&self, point: TabPoint) -> Chunks { - let (point, expanded_char_column, to_next_stop) = self.to_fold_point(point, Bias::Left); - let fold_chunks = self - .fold_snapshot - .chunks_at(point.to_offset(&self.fold_snapshot)); - Chunks { - fold_chunks, - column: expanded_char_column, - tab_size: self.tab_size, - chunk: &SPACES[0..to_next_stop], - skip_leading_tab: to_next_stop > 0, - } - } - - pub fn highlighted_chunks(&mut self, range: Range) -> HighlightedChunks { + pub fn chunks(&self, range: Range, highlights: bool) -> Chunks { let (input_start, expanded_char_column, to_next_stop) = self.to_fold_point(range.start, Bias::Left); let input_start = input_start.to_offset(&self.fold_snapshot); @@ -168,13 +161,13 @@ impl Snapshot { .to_fold_point(range.end, Bias::Right) .0 .to_offset(&self.fold_snapshot); - HighlightedChunks { + Chunks { fold_chunks: self .fold_snapshot - .highlighted_chunks(input_start..input_end), + .chunks(input_start..input_end, highlights), column: expanded_char_column, tab_size: self.tab_size, - chunk: HighlightedChunk { + chunk: Chunk { text: &SPACES[0..to_next_stop], ..Default::default() }, @@ -188,7 +181,9 @@ impl Snapshot { #[cfg(test)] pub fn text(&self) -> String { - self.chunks_at(Default::default()).collect() + self.chunks(TabPoint::zero()..self.max_point(), false) + .map(|chunk| chunk.text) + .collect() } pub fn max_point(&self) -> TabPoint { @@ -379,63 +374,14 @@ const SPACES: &'static str = " "; pub struct Chunks<'a> { fold_chunks: fold_map::Chunks<'a>, - chunk: &'a str, + chunk: Chunk<'a>, column: usize, tab_size: usize, skip_leading_tab: bool, } impl<'a> Iterator for Chunks<'a> { - type Item = &'a str; - - fn next(&mut self) -> Option { - if self.chunk.is_empty() { - if let Some(chunk) = self.fold_chunks.next() { - self.chunk = chunk; - if self.skip_leading_tab { - self.chunk = &self.chunk[1..]; - self.skip_leading_tab = false; - } - } else { - return None; - } - } - - for (ix, c) in self.chunk.char_indices() { - match c { - '\t' => { - if ix > 0 { - let (prefix, suffix) = self.chunk.split_at(ix); - self.chunk = suffix; - return Some(prefix); - } else { - self.chunk = &self.chunk[1..]; - let len = self.tab_size - self.column % self.tab_size; - self.column += len; - return Some(&SPACES[0..len]); - } - } - '\n' => self.column = 0, - _ => self.column += 1, - } - } - - let result = Some(self.chunk); - self.chunk = ""; - result - } -} - -pub struct HighlightedChunks<'a> { - fold_chunks: fold_map::HighlightedChunks<'a>, - chunk: HighlightedChunk<'a>, - column: usize, - tab_size: usize, - skip_leading_tab: bool, -} - -impl<'a> Iterator for HighlightedChunks<'a> { - type Item = HighlightedChunk<'a>; + type Item = Chunk<'a>; fn next(&mut self) -> Option { if self.chunk.text.is_empty() { @@ -456,7 +402,7 @@ impl<'a> Iterator for HighlightedChunks<'a> { if ix > 0 { let (prefix, suffix) = self.chunk.text.split_at(ix); self.chunk.text = suffix; - return Some(HighlightedChunk { + return Some(Chunk { text: prefix, ..self.chunk }); @@ -464,7 +410,7 @@ impl<'a> Iterator for HighlightedChunks<'a> { self.chunk.text = &self.chunk.text[1..]; let len = self.tab_size - self.column % self.tab_size; self.column += len; - return Some(HighlightedChunk { + return Some(Chunk { text: &SPACES[0..len], ..self.chunk }); diff --git a/crates/editor/src/display_map/wrap_map.rs b/crates/editor/src/display_map/wrap_map.rs index 51908c087764e2361755e41c24ecb273e00b5d04..6076bd69380c2b6f0429244c1fc255f0216029f3 100644 --- a/crates/editor/src/display_map/wrap_map.rs +++ b/crates/editor/src/display_map/wrap_map.rs @@ -7,7 +7,7 @@ use gpui::{ fonts::FontId, text_layout::LineWrapper, Entity, ModelContext, ModelHandle, MutableAppContext, Task, }; -use language::{HighlightedChunk, Point}; +use language::{Chunk, Point}; use lazy_static::lazy_static; use smol::future::yield_now; use std::{collections::VecDeque, mem, ops::Range, time::Duration}; @@ -54,14 +54,7 @@ pub struct WrapPoint(pub super::Point); pub struct Chunks<'a> { input_chunks: tab_map::Chunks<'a>, - input_chunk: &'a str, - output_position: WrapPoint, - transforms: Cursor<'a, Transform, (WrapPoint, TabPoint)>, -} - -pub struct HighlightedChunks<'a> { - input_chunks: tab_map::HighlightedChunks<'a>, - input_chunk: HighlightedChunk<'a>, + input_chunk: Chunk<'a>, output_position: WrapPoint, max_output_row: u32, transforms: Cursor<'a, Transform, (WrapPoint, TabPoint)>, @@ -430,10 +423,15 @@ impl Snapshot { let mut line = String::new(); let mut remaining = None; - let mut chunks = new_tab_snapshot.chunks_at(TabPoint::new(edit.new_rows.start, 0)); + let mut chunks = new_tab_snapshot.chunks( + TabPoint::new(edit.new_rows.start, 0)..new_tab_snapshot.max_point(), + false, + ); let mut edit_transforms = Vec::::new(); for _ in edit.new_rows.start..edit.new_rows.end { - while let Some(chunk) = remaining.take().or_else(|| chunks.next()) { + while let Some(chunk) = + remaining.take().or_else(|| chunks.next().map(|c| c.text)) + { if let Some(ix) = chunk.find('\n') { line.push_str(&chunk[..ix + 1]); remaining = Some(&chunk[ix + 1..]); @@ -552,24 +550,12 @@ impl Snapshot { unsafe { Patch::new_unchecked(wrap_edits) } } - pub fn chunks_at(&self, wrap_row: u32) -> Chunks { - let point = WrapPoint::new(wrap_row, 0); - let mut transforms = self.transforms.cursor::<(WrapPoint, TabPoint)>(); - transforms.seek(&point, Bias::Right, &()); - let mut input_position = TabPoint(transforms.start().1 .0); - if transforms.item().map_or(false, |t| t.is_isomorphic()) { - input_position.0 += point.0 - transforms.start().0 .0; - } - let input_chunks = self.tab_snapshot.chunks_at(input_position); - Chunks { - input_chunks, - transforms, - output_position: point, - input_chunk: "", - } + pub fn text_chunks(&self, wrap_row: u32) -> impl Iterator { + self.chunks(wrap_row..self.max_point().row() + 1, false) + .map(|h| h.text) } - pub fn highlighted_chunks_for_rows(&mut self, rows: Range) -> HighlightedChunks { + pub fn chunks(&self, rows: Range, highlights: bool) -> Chunks { let output_start = WrapPoint::new(rows.start, 0); let output_end = WrapPoint::new(rows.end, 0); let mut transforms = self.transforms.cursor::<(WrapPoint, TabPoint)>(); @@ -581,8 +567,8 @@ impl Snapshot { let input_end = self .to_tab_point(output_end) .min(self.tab_snapshot.max_point()); - HighlightedChunks { - input_chunks: self.tab_snapshot.highlighted_chunks(input_start..input_end), + Chunks { + input_chunks: self.tab_snapshot.chunks(input_start..input_end, highlights), input_chunk: Default::default(), output_position: output_start, max_output_row: rows.end, @@ -600,7 +586,7 @@ impl Snapshot { pub fn line_len(&self, row: u32) -> u32 { let mut len = 0; - for chunk in self.chunks_at(row) { + for chunk in self.text_chunks(row) { if let Some(newline_ix) = chunk.find('\n') { len += newline_ix; break; @@ -733,52 +719,7 @@ impl Snapshot { } impl<'a> Iterator for Chunks<'a> { - type Item = &'a str; - - fn next(&mut self) -> Option { - let transform = self.transforms.item()?; - if let Some(display_text) = transform.display_text { - if self.output_position > self.transforms.start().0 { - self.output_position.0.column += transform.summary.output.lines.column; - self.transforms.next(&()); - return Some(&display_text[1..]); - } else { - self.output_position.0 += transform.summary.output.lines; - self.transforms.next(&()); - return Some(display_text); - } - } - - if self.input_chunk.is_empty() { - self.input_chunk = self.input_chunks.next().unwrap(); - } - - let mut input_len = 0; - let transform_end = self.transforms.end(&()).0; - for c in self.input_chunk.chars() { - let char_len = c.len_utf8(); - input_len += char_len; - if c == '\n' { - *self.output_position.row_mut() += 1; - *self.output_position.column_mut() = 0; - } else { - *self.output_position.column_mut() += char_len as u32; - } - - if self.output_position >= transform_end { - self.transforms.next(&()); - break; - } - } - - let (prefix, suffix) = self.input_chunk.split_at(input_len); - self.input_chunk = suffix; - Some(prefix) - } -} - -impl<'a> Iterator for HighlightedChunks<'a> { - type Item = HighlightedChunk<'a>; + type Item = Chunk<'a>; fn next(&mut self) -> Option { if self.output_position.row() >= self.max_output_row { @@ -803,7 +744,7 @@ impl<'a> Iterator for HighlightedChunks<'a> { self.output_position.0 += summary; self.transforms.next(&()); - return Some(HighlightedChunk { + return Some(Chunk { text: &display_text[start_ix..end_ix], ..self.input_chunk }); @@ -833,7 +774,7 @@ impl<'a> Iterator for HighlightedChunks<'a> { let (prefix, suffix) = self.input_chunk.text.split_at(input_len); self.input_chunk.text = suffix; - Some(HighlightedChunk { + Some(Chunk { text: prefix, ..self.input_chunk }) @@ -1216,7 +1157,7 @@ mod tests { impl Snapshot { pub fn text(&self) -> String { - self.chunks_at(0).collect() + self.text_chunks(0).collect() } fn verify_chunks(&mut self, rng: &mut impl Rng) { @@ -1225,7 +1166,7 @@ mod tests { let start_row = rng.gen_range(0..=end_row); end_row += 1; - let mut expected_text = self.chunks_at(start_row).collect::(); + let mut expected_text = self.text_chunks(start_row).collect::(); if expected_text.ends_with("\n") { expected_text.push('\n'); } @@ -1239,7 +1180,7 @@ mod tests { } let actual_text = self - .highlighted_chunks_for_rows(start_row..end_row) + .chunks(start_row..end_row, false) .map(|c| c.text) .collect::(); assert_eq!( diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index f538f3f4cb4c477e239899b89408a49e0a5ef450..1e0e512ffdf135773c3b4c9f7558e674ed43b06b 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -17,7 +17,7 @@ use gpui::{ MutableAppContext, PaintContext, Quad, Scene, SizeConstraint, ViewContext, WeakViewHandle, }; use json::json; -use language::{DiagnosticSeverity, HighlightedChunk}; +use language::{Chunk, DiagnosticSeverity}; use smallvec::SmallVec; use std::{ cmp::{self, Ordering}, @@ -493,9 +493,9 @@ impl EditorElement { let mut styles = Vec::new(); let mut row = rows.start; let mut line_exceeded_max_len = false; - let chunks = snapshot.highlighted_chunks_for_rows(rows.clone()); + let chunks = snapshot.chunks(rows.clone()); - let newline_chunk = HighlightedChunk { + let newline_chunk = Chunk { text: "\n", ..Default::default() }; diff --git a/crates/editor/src/lib.rs b/crates/editor/src/lib.rs index e6de6e351bc2eab42bf9004959c843ff2413ebe8..418db1e206ba006933550a63a6213e0995526afa 100644 --- a/crates/editor/src/lib.rs +++ b/crates/editor/src/lib.rs @@ -2663,12 +2663,8 @@ impl Snapshot { self.display_snapshot.buffer_rows(start_row) } - pub fn highlighted_chunks_for_rows( - &mut self, - display_rows: Range, - ) -> display_map::HighlightedChunks { - self.display_snapshot - .highlighted_chunks_for_rows(display_rows) + pub fn chunks(&mut self, display_rows: Range) -> display_map::Chunks { + self.display_snapshot.chunks(display_rows) } pub fn scroll_position(&self) -> Vector2F { diff --git a/crates/language/src/lib.rs b/crates/language/src/lib.rs index 00c63e1df69db8a558c52560dbf4c746a4110b40..85b720d7097a46c959a9440e671c46fd1ca1663b 100644 --- a/crates/language/src/lib.rs +++ b/crates/language/src/lib.rs @@ -78,7 +78,6 @@ pub struct Snapshot { diagnostics: AnchorRangeMultimap, is_parsing: bool, language: Option>, - query_cursor: QueryCursorHandle, } #[derive(Clone, Debug, PartialEq, Eq)] @@ -191,11 +190,12 @@ struct Highlights<'a> { next_capture: Option<(tree_sitter::QueryMatch<'a, 'a>, usize)>, stack: Vec<(usize, HighlightId)>, highlight_map: HighlightMap, + _query_cursor: QueryCursorHandle, } -pub struct HighlightedChunks<'a> { +pub struct Chunks<'a> { range: Range, - chunks: Chunks<'a>, + chunks: rope::Chunks<'a>, diagnostic_endpoints: Peekable>, error_depth: usize, warning_depth: usize, @@ -205,7 +205,7 @@ pub struct HighlightedChunks<'a> { } #[derive(Clone, Copy, Debug, Default)] -pub struct HighlightedChunk<'a> { +pub struct Chunk<'a> { pub text: &'a str, pub highlight_id: HighlightId, pub diagnostic: Option, @@ -342,7 +342,6 @@ impl Buffer { diagnostics: self.diagnostics.clone(), is_parsing: self.parsing_in_background, language: self.language.clone(), - query_cursor: QueryCursorHandle::new(), } } @@ -1635,51 +1634,56 @@ impl Snapshot { .all(|chunk| chunk.matches(|c: char| !c.is_whitespace()).next().is_none()) } - pub fn highlighted_text_for_range( - &mut self, - range: Range, - ) -> HighlightedChunks { + pub fn chunks(&self, range: Range, highlight: bool) -> Chunks { let range = range.start.to_offset(&*self)..range.end.to_offset(&*self); + let mut highlights = None; let mut diagnostic_endpoints = Vec::::new(); - for (_, range, diagnostic) in - self.diagnostics - .intersecting_ranges(range.clone(), self.content(), true) - { - diagnostic_endpoints.push(DiagnosticEndpoint { - offset: range.start, - is_start: true, - severity: diagnostic.severity, - }); - diagnostic_endpoints.push(DiagnosticEndpoint { - offset: range.end, - is_start: false, - severity: diagnostic.severity, - }); - } - diagnostic_endpoints.sort_unstable_by_key(|endpoint| (endpoint.offset, !endpoint.is_start)); - let diagnostic_endpoints = diagnostic_endpoints.into_iter().peekable(); + if highlight { + for (_, range, diagnostic) in + self.diagnostics + .intersecting_ranges(range.clone(), self.content(), true) + { + diagnostic_endpoints.push(DiagnosticEndpoint { + offset: range.start, + is_start: true, + severity: diagnostic.severity, + }); + diagnostic_endpoints.push(DiagnosticEndpoint { + offset: range.end, + is_start: false, + severity: diagnostic.severity, + }); + } + diagnostic_endpoints + .sort_unstable_by_key(|endpoint| (endpoint.offset, !endpoint.is_start)); - let chunks = self.text.as_rope().chunks_in_range(range.clone()); - let highlights = if let Some((language, tree)) = self.language.as_ref().zip(self.tree.as_ref()) { - let captures = self.query_cursor.set_byte_range(range.clone()).captures( + let mut query_cursor = QueryCursorHandle::new(); + + // TODO - add a Tree-sitter API to remove the need for this. + let cursor = unsafe { + std::mem::transmute::<_, &'static mut QueryCursor>(query_cursor.deref_mut()) + }; + let captures = cursor.set_byte_range(range.clone()).captures( &language.highlights_query, tree.root_node(), TextProvider(self.text.as_rope()), ); - - Some(Highlights { + highlights = Some(Highlights { captures, next_capture: None, stack: Default::default(), highlight_map: language.highlight_map(), + _query_cursor: query_cursor, }) - } else { - None - }; + } + } - HighlightedChunks { + let diagnostic_endpoints = diagnostic_endpoints.into_iter().peekable(); + let chunks = self.text.as_rope().chunks_in_range(range.clone()); + + Chunks { range, chunks, diagnostic_endpoints, @@ -1700,7 +1704,6 @@ impl Clone for Snapshot { diagnostics: self.diagnostics.clone(), is_parsing: self.is_parsing, language: self.language.clone(), - query_cursor: QueryCursorHandle::new(), } } } @@ -1731,7 +1734,9 @@ impl<'a> Iterator for ByteChunks<'a> { } } -impl<'a> HighlightedChunks<'a> { +unsafe impl<'a> Send for Chunks<'a> {} + +impl<'a> Chunks<'a> { pub fn seek(&mut self, offset: usize) { self.range.start = offset; self.chunks.seek(self.range.start); @@ -1790,8 +1795,8 @@ impl<'a> HighlightedChunks<'a> { } } -impl<'a> Iterator for HighlightedChunks<'a> { - type Item = HighlightedChunk<'a>; +impl<'a> Iterator for Chunks<'a> { + type Item = Chunk<'a>; fn next(&mut self) -> Option { let mut next_capture_start = usize::MAX; @@ -1855,7 +1860,7 @@ impl<'a> Iterator for HighlightedChunks<'a> { self.chunks.next().unwrap(); } - Some(HighlightedChunk { + Some(Chunk { text: slice, highlight_id, diagnostic: self.current_diagnostic_severity(), diff --git a/crates/language/src/tests.rs b/crates/language/src/tests.rs index 28256fe066dbd9c6bea8bf46ca4cf8bbac05e982..23bb2b8589bacfca1fb22fca9dc0c29169a994d3 100644 --- a/crates/language/src/tests.rs +++ b/crates/language/src/tests.rs @@ -901,12 +901,12 @@ async fn test_grouped_diagnostics(mut cx: gpui::TestAppContext) { }); } -fn chunks_with_diagnostics( +fn chunks_with_diagnostics( buffer: &Buffer, range: Range, ) -> Vec<(String, Option)> { let mut chunks: Vec<(String, Option)> = Vec::new(); - for chunk in buffer.snapshot().highlighted_text_for_range(range) { + for chunk in buffer.snapshot().chunks(range, true) { if chunks .last() .map_or(false, |prev_chunk| prev_chunk.1 == chunk.diagnostic) From 18354c5e048e52fa4bb834625f3ca482b01f823f Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 16 Nov 2021 16:14:46 -0700 Subject: [PATCH 067/109] Hack in show next diagnostic command Co-Authored-By: Max Brunsfeld --- crates/editor/src/lib.rs | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/crates/editor/src/lib.rs b/crates/editor/src/lib.rs index 418db1e206ba006933550a63a6213e0995526afa..bb710b4353e9c59887fd0a028661a7cc6d0b9c50 100644 --- a/crates/editor/src/lib.rs +++ b/crates/editor/src/lib.rs @@ -83,6 +83,7 @@ action!(AddSelectionBelow); action!(SelectLargerSyntaxNode); action!(SelectSmallerSyntaxNode); action!(MoveToEnclosingBracket); +action!(ShowNextDiagnostic); action!(PageUp); action!(PageDown); action!(Fold); @@ -184,6 +185,7 @@ pub fn init(cx: &mut MutableAppContext) { Binding::new("ctrl-w", SelectLargerSyntaxNode, Some("Editor")), Binding::new("alt-down", SelectSmallerSyntaxNode, Some("Editor")), Binding::new("ctrl-shift-W", SelectSmallerSyntaxNode, Some("Editor")), + Binding::new("ctrl-.", ShowNextDiagnostic, Some("Editor")), Binding::new("ctrl-m", MoveToEnclosingBracket, Some("Editor")), Binding::new("pageup", PageUp, Some("Editor")), Binding::new("pagedown", PageDown, Some("Editor")), @@ -242,6 +244,7 @@ pub fn init(cx: &mut MutableAppContext) { cx.add_action(Editor::select_larger_syntax_node); cx.add_action(Editor::select_smaller_syntax_node); cx.add_action(Editor::move_to_enclosing_bracket); + cx.add_action(Editor::show_next_diagnostic); cx.add_action(Editor::page_up); cx.add_action(Editor::page_down); cx.add_action(Editor::fold); @@ -2199,6 +2202,39 @@ impl Editor { self.update_selections(selections, true, cx); } + pub fn show_next_diagnostic(&mut self, _: &ShowNextDiagnostic, cx: &mut ViewContext) { + let selection = self.selections::(cx).last().unwrap(); + let buffer = self.buffer.read(cx.as_ref()); + let diagnostic_group_id = dbg!(buffer + .diagnostics_in_range::<_, usize>(selection.head()..buffer.len()) + .next()) + .map(|(_, diagnostic)| diagnostic.group_id); + + if let Some(group_id) = diagnostic_group_id { + self.display_map.update(cx, |display_map, cx| { + let buffer = self.buffer.read(cx); + let diagnostic_group = buffer + .diagnostic_group::(group_id) + .map(|(range, diagnostic)| (range, format!("{}\n", diagnostic.message))) + .collect::>(); + + dbg!(group_id, &diagnostic_group); + + display_map.insert_blocks( + diagnostic_group + .iter() + .map(|(range, message)| BlockProperties { + position: range.start, + text: message.as_str(), + runs: vec![], + disposition: BlockDisposition::Above, + }), + cx, + ); + }); + } + } + fn build_columnar_selection( &mut self, display_map: &DisplayMapSnapshot, From d9283efbe627cf9134f4f816b1696bbbde91a26e Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 17 Nov 2021 19:26:57 +0100 Subject: [PATCH 068/109] Make `BlockMap` 1d Co-Authored-By: Nathan Sobo Co-Authored-By: Max Brunsfeld --- crates/editor/src/display_map.rs | 5 +- crates/editor/src/display_map/block_map.rs | 688 ++++++++++----------- 2 files changed, 327 insertions(+), 366 deletions(-) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index c487151a36c93ad248040fe9a4bbe0488fd9419b..ce3962b4a7d9c56502209838acb8ffb6e8cba2a9 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -14,7 +14,8 @@ use sum_tree::Bias; use tab_map::TabMap; use wrap_map::WrapMap; -pub use block_map::{BlockDisposition, BlockProperties, BufferRows, Chunks}; +pub use block_map::{BlockDisposition, BlockProperties, Chunks}; +pub use wrap_map::BufferRows; pub trait ToDisplayPoint { fn to_display_point(&self, map: &DisplayMapSnapshot) -> DisplayPoint; @@ -173,7 +174,7 @@ impl DisplayMapSnapshot { } pub fn buffer_rows(&self, start_row: u32) -> BufferRows { - self.blocks_snapshot.buffer_rows(start_row) + self.wraps_snapshot.buffer_rows(start_row) } pub fn buffer_row_count(&self) -> u32 { diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index 6cd6fbe6d4cf7a06b22e4d2def5b2a95ba43c260..157a7f1dcf781cca5c0f9ef2e0cb2305b5c8496f 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -37,6 +37,12 @@ pub struct BlockId(usize); #[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)] pub struct BlockPoint(pub super::Point); +#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)] +struct BlockRow(u32); + +#[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)] +struct WrapRow(u32); + #[derive(Debug)] struct Block { id: BlockId, @@ -72,17 +78,18 @@ struct Transform { #[derive(Clone, Debug, Default)] struct TransformSummary { - input: Point, - output: Point, + input_rows: u32, + output_rows: u32, } pub struct Chunks<'a> { - transforms: sum_tree::Cursor<'a, Transform, (BlockPoint, WrapPoint)>, + transforms: sum_tree::Cursor<'a, Transform, (BlockRow, WrapRow)>, input_chunks: wrap_map::Chunks<'a>, input_chunk: Chunk<'a>, block_chunks: Option>, - output_position: BlockPoint, - max_output_position: BlockPoint, + output_row: u32, + max_output_row: u32, + max_input_row: u32, } struct BlockChunks<'a> { @@ -93,16 +100,6 @@ struct BlockChunks<'a> { offset: usize, } -pub struct BufferRows<'a> { - transforms: sum_tree::Cursor<'a, Transform, (BlockPoint, WrapPoint)>, - input_buffer_rows: wrap_map::BufferRows<'a>, - input_buffer_row: Option<(u32, bool)>, - input_row: u32, - output_row: u32, - max_output_row: u32, - in_block: bool, -} - impl BlockMap { pub fn new(buffer: ModelHandle, wrap_snapshot: WrapSnapshot) -> Self { Self { @@ -110,7 +107,7 @@ impl BlockMap { next_block_id: AtomicUsize::new(0), blocks: Vec::new(), transforms: Mutex::new(SumTree::from_item( - Transform::isomorphic(wrap_snapshot.text_summary().lines), + Transform::isomorphic(wrap_snapshot.text_summary().lines.row + 1), &(), )), wrap_snapshot: Mutex::new(wrap_snapshot), @@ -150,36 +147,80 @@ impl BlockMap { let buffer = self.buffer.read(cx); let mut transforms = self.transforms.lock(); let mut new_transforms = SumTree::new(); - let old_max_point = WrapPoint(transforms.summary().input); - let new_max_point = wrap_snapshot.max_point(); - let mut cursor = transforms.cursor::(); + let old_row_count = transforms.summary().input_rows; + let new_row_count = wrap_snapshot.max_point().row() + 1; + let mut cursor = transforms.cursor::(); let mut last_block_ix = 0; let mut blocks_in_edit = Vec::new(); let mut edits = edits.into_iter().peekable(); while let Some(edit) = edits.next() { // Preserve any old transforms that precede this edit. - let old_start = WrapPoint::new(edit.old.start, 0); - let new_start = WrapPoint::new(edit.new.start, 0); + let old_start = WrapRow(edit.old.start); + let new_start = WrapRow(edit.new.start); new_transforms.push_tree(cursor.slice(&old_start, Bias::Left, &()), &()); + if let Some(transform) = cursor.item() { + if transform.is_isomorphic() && old_start == cursor.end(&()) { + new_transforms.push(transform.clone(), &()); + cursor.next(&()); + while let Some(transform) = cursor.item() { + if transform + .block + .as_ref() + .map_or(false, |b| b.disposition.is_below()) + { + new_transforms.push(transform.clone(), &()); + cursor.next(&()); + } else { + break; + } + } + } + } // Preserve any portion of an old transform that precedes this edit. let extent_before_edit = old_start.0 - cursor.start().0; push_isomorphic(&mut new_transforms, extent_before_edit); // Skip over any old transforms that intersect this edit. - let mut old_end = WrapPoint::new(edit.old.end, 0); - let mut new_end = WrapPoint::new(edit.new.end, 0); + let mut old_end = WrapRow(edit.old.end); + let mut new_end = WrapRow(edit.new.end); cursor.seek(&old_end, Bias::Left, &()); cursor.next(&()); + if old_end == *cursor.start() { + while let Some(transform) = cursor.item() { + if transform + .block + .as_ref() + .map_or(false, |b| b.disposition.is_below()) + { + cursor.next(&()); + } else { + break; + } + } + } // Combine this edit with any subsequent edits that intersect the same transform. while let Some(next_edit) = edits.peek() { - if next_edit.old.start <= cursor.start().row() { - old_end = WrapPoint::new(next_edit.old.end, 0); - new_end = WrapPoint::new(next_edit.new.end, 0); + if next_edit.old.start <= cursor.start().0 { + old_end = WrapRow(next_edit.old.end); + new_end = WrapRow(next_edit.new.end); cursor.seek(&old_end, Bias::Left, &()); cursor.next(&()); + if old_end == *cursor.start() { + while let Some(transform) = cursor.item() { + if transform + .block + .as_ref() + .map_or(false, |b| b.disposition.is_below()) + { + cursor.next(&()); + } else { + break; + } + } + } edits.next(); } else { break; @@ -187,7 +228,7 @@ impl BlockMap { } // Find the blocks within this edited region. - let new_start = wrap_snapshot.to_point(new_start, Bias::Left); + let new_start = wrap_snapshot.to_point(WrapPoint::new(new_start.0, 0), Bias::Left); let start_anchor = buffer.anchor_before(new_start); let start_block_ix = match self.blocks[last_block_ix..].binary_search_by(|probe| { probe @@ -198,10 +239,10 @@ impl BlockMap { }) { Ok(ix) | Err(ix) => last_block_ix + ix, }; - let end_block_ix = if new_end.row() > wrap_snapshot.max_point().row() { + let end_block_ix = if new_end.0 > wrap_snapshot.max_point().row() { self.blocks.len() } else { - let new_end = wrap_snapshot.to_point(new_end, Bias::Left); + let new_end = wrap_snapshot.to_point(WrapPoint::new(new_end.0, 0), Bias::Left); let end_anchor = buffer.anchor_before(new_end); match self.blocks[start_block_ix..].binary_search_by(|probe| { probe @@ -235,27 +276,20 @@ impl BlockMap { // For each of these blocks, insert a new isomorphic transform preceding the block, // and then insert the block itself. for (block_row, block) in blocks_in_edit.iter().copied() { - let block_insertion_point = match block.disposition { - BlockDisposition::Above => Point::new(block_row, 0), - BlockDisposition::Below => { - Point::new(block_row, wrap_snapshot.line_len(block_row)) - } + let insertion_row = match block.disposition { + BlockDisposition::Above => block_row, + BlockDisposition::Below => block_row + 1, }; - - let extent_before_block = block_insertion_point - new_transforms.summary().input; + let extent_before_block = insertion_row - new_transforms.summary().input_rows; push_isomorphic(&mut new_transforms, extent_before_block); - if block.disposition == BlockDisposition::Below { - ensure_last_is_isomorphic_or_below_block(&mut new_transforms); - } - new_transforms.push(Transform::block(block.clone()), &()); } - old_end = old_end.min(old_max_point); - new_end = new_end.min(new_max_point); + old_end = WrapRow(old_end.0.min(old_row_count)); + new_end = WrapRow(new_end.0.min(new_row_count)); // Insert an isomorphic transform after the final block. - let extent_after_last_block = new_end.0 - new_transforms.summary().input; + let extent_after_last_block = new_end.0 - new_transforms.summary().input_rows; push_isomorphic(&mut new_transforms, extent_after_last_block); // Preserve any portion of the old transform after this edit. @@ -264,37 +298,28 @@ impl BlockMap { } new_transforms.push_tree(cursor.suffix(&()), &()); - ensure_last_is_isomorphic_or_below_block(&mut new_transforms); - debug_assert_eq!(new_transforms.summary().input, wrap_snapshot.max_point().0); + debug_assert_eq!( + new_transforms.summary().input_rows, + wrap_snapshot.max_point().row() + 1 + ); drop(cursor); *transforms = new_transforms; } } -fn ensure_last_is_isomorphic_or_below_block(tree: &mut SumTree) { - if tree.last().map_or(true, |transform| { - transform - .block - .as_ref() - .map_or(false, |block| block.disposition.is_above()) - }) { - tree.push(Transform::isomorphic(Point::zero()), &()) - } -} - -fn push_isomorphic(tree: &mut SumTree, extent: Point) { - if extent.is_zero() { +fn push_isomorphic(tree: &mut SumTree, rows: u32) { + if rows == 0 { return; } - let mut extent = Some(extent); + let mut extent = Some(rows); tree.update_last( |last_transform| { if last_transform.is_isomorphic() { let extent = extent.take().unwrap(); - last_transform.summary.input += &extent; - last_transform.summary.output += &extent; + last_transform.summary.input_rows += extent; + last_transform.summary.output_rows += extent; } }, &(), @@ -363,19 +388,12 @@ impl<'a> BlockMapWriter<'a> { { Ok(ix) | Err(ix) => ix, }; - let mut text = block.text.into(); - if block.disposition.is_above() { - text.push("\n"); - } else { - text.push_front("\n"); - } - self.0.blocks.insert( block_ix, Arc::new(Block { id, position, - text, + text: block.text.into(), runs: block.runs, disposition: block.disposition, }), @@ -433,20 +451,28 @@ impl<'a> BlockMapWriter<'a> { impl BlockSnapshot { #[cfg(test)] fn text(&mut self) -> String { - self.chunks(0..self.max_point().0.row + 1, false) + self.chunks(0..self.transforms.summary().output_rows, false) .map(|chunk| chunk.text) .collect() } pub fn chunks(&self, rows: Range, highlights: bool) -> Chunks { - let max_output_position = self.max_point().min(BlockPoint::new(rows.end, 0)); - let mut cursor = self.transforms.cursor::<(BlockPoint, WrapPoint)>(); - let output_position = BlockPoint::new(rows.start, 0); - cursor.seek(&output_position, Bias::Right, &()); + let max_input_row = self.transforms.summary().input_rows; + let max_output_row = cmp::min(rows.end, self.transforms.summary().output_rows); + let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>(); + let output_row = rows.start; + cursor.seek(&BlockRow(output_row), Bias::Right, &()); let (input_start, output_start) = cursor.start(); - let row_overshoot = rows.start - output_start.0.row; - let input_start_row = input_start.0.row + row_overshoot; - let input_end_row = self.to_wrap_point(BlockPoint::new(rows.end, 0)).row(); + let overshoot = rows.start - output_start.0; + let output_end_row = max_output_row.saturating_sub(1); + let input_start_row = input_start.0 + overshoot; + let input_end_row = self + .to_wrap_point(BlockPoint::new( + output_end_row, + self.wrap_snapshot.line_len(output_end_row), + )) + .row() + + 1; let input_chunks = self .wrap_snapshot .chunks(input_start_row..input_end_row, highlights); @@ -455,112 +481,103 @@ impl BlockSnapshot { input_chunk: Default::default(), block_chunks: None, transforms: cursor, - output_position, - max_output_position, - } - } - - pub fn buffer_rows(&self, start_row: u32) -> BufferRows { - let mut transforms = self.transforms.cursor::<(BlockPoint, WrapPoint)>(); - transforms.seek(&BlockPoint::new(start_row, 0), Bias::Left, &()); - let mut input_row = transforms.start().1.row(); - let transform = transforms.item().unwrap(); - let in_block; - if transform.is_isomorphic() { - input_row += start_row - transforms.start().0.row; - in_block = false; - } else { - in_block = true; - } - let mut input_buffer_rows = self.wrap_snapshot.buffer_rows(input_row); - let input_buffer_row = input_buffer_rows.next().unwrap(); - BufferRows { - transforms, - input_buffer_row: Some(input_buffer_row), - input_buffer_rows, - input_row, - output_row: start_row, - max_output_row: self.max_point().row, - in_block, + output_row, + max_output_row, + max_input_row, } } pub fn max_point(&self) -> BlockPoint { - BlockPoint(self.transforms.summary().output) + todo!() } pub fn clip_point(&self, point: BlockPoint, bias: Bias) -> BlockPoint { - let mut cursor = self.transforms.cursor::<(BlockPoint, WrapPoint)>(); - cursor.seek(&point, Bias::Right, &()); - if let Some(transform) = cursor.prev_item() { - if transform.is_isomorphic() && point == cursor.start().0 { - return point; - } - } - if let Some(transform) = cursor.item() { - if transform.is_isomorphic() { - let (output_start, input_start) = cursor.start(); - let output_overshoot = point.0 - output_start.0; - let input_point = self - .wrap_snapshot - .clip_point(WrapPoint(input_start.0 + output_overshoot), bias); - let input_overshoot = input_point.0 - input_start.0; - BlockPoint(output_start.0 + input_overshoot) - } else { - if bias == Bias::Left && cursor.start().1 .0 > Point::zero() - || cursor.end(&()).1 == self.wrap_snapshot.max_point() - { - loop { - cursor.prev(&()); - let transform = cursor.item().unwrap(); - if transform.is_isomorphic() { - return BlockPoint(cursor.end(&()).0 .0); - } - } - } else { - loop { - cursor.next(&()); - let transform = cursor.item().unwrap(); - if transform.is_isomorphic() { - return BlockPoint(cursor.start().0 .0); - } - } - } - } - } else { - self.max_point() - } + todo!() + // let mut cursor = self.transforms.cursor::<(BlockPoint, WrapPoint)>(); + // cursor.seek(&point, Bias::Right, &()); + // if let Some(transform) = cursor.prev_item() { + // if transform.is_isomorphic() && point == cursor.start().0 { + // return point; + // } + // } + // if let Some(transform) = cursor.item() { + // if transform.is_isomorphic() { + // let (output_start, input_start) = cursor.start(); + // let output_overshoot = point.0 - output_start.0; + // let input_point = self + // .wrap_snapshot + // .clip_point(WrapPoint(input_start.0 + output_overshoot), bias); + // let input_overshoot = input_point.0 - input_start.0; + // BlockPoint(output_start.0 + input_overshoot) + // } else { + // if bias == Bias::Left && cursor.start().1 .0 > Point::zero() + // || cursor.end(&()).1 == self.wrap_snapshot.max_point() + // { + // loop { + // cursor.prev(&()); + // let transform = cursor.item().unwrap(); + // if transform.is_isomorphic() { + // return BlockPoint(cursor.end(&()).0 .0); + // } + // } + // } else { + // loop { + // cursor.next(&()); + // let transform = cursor.item().unwrap(); + // if transform.is_isomorphic() { + // return BlockPoint(cursor.start().0 .0); + // } + // } + // } + // } + // } else { + // self.max_point() + // } } pub fn to_block_point(&self, wrap_point: WrapPoint, bias: Bias) -> BlockPoint { - let mut cursor = self.transforms.cursor::<(WrapPoint, BlockPoint)>(); - cursor.seek(&wrap_point, bias, &()); - while let Some(item) = cursor.item() { - if item.is_isomorphic() { - break; - } - cursor.next(&()); - } - let (input_start, output_start) = cursor.start(); - let input_overshoot = wrap_point.0 - input_start.0; - BlockPoint(output_start.0 + input_overshoot) + todo!() + // let mut cursor = self.transforms.cursor::<(WrapPoint, BlockPoint)>(); + // cursor.seek(&wrap_point, bias, &()); + // while let Some(item) = cursor.item() { + // if item.is_isomorphic() { + // break; + // } + // cursor.next(&()); + // } + // let (input_start, output_start) = cursor.start(); + // let input_overshoot = wrap_point.0 - input_start.0; + // BlockPoint(output_start.0 + input_overshoot) } pub fn to_wrap_point(&self, block_point: BlockPoint) -> WrapPoint { - let mut cursor = self.transforms.cursor::<(BlockPoint, WrapPoint)>(); - cursor.seek(&block_point, Bias::Right, &()); - let (output_start, input_start) = cursor.start(); - let output_overshoot = block_point.0 - output_start.0; - WrapPoint(input_start.0 + output_overshoot) + let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>(); + cursor.seek(&BlockRow(block_point.row), Bias::Right, &()); + if let Some(transform) = cursor.item() { + match transform.block.as_ref().map(|b| b.disposition) { + Some(BlockDisposition::Above) => WrapPoint::new(cursor.start().1 .0, 0), + Some(BlockDisposition::Below) => { + let wrap_row = cursor.start().1 .0 - 1; + WrapPoint::new(wrap_row, self.wrap_snapshot.line_len(wrap_row)) + } + None => { + let overshoot = block_point.row - cursor.start().0 .0; + let wrap_row = cursor.start().1 .0 + overshoot; + WrapPoint::new(wrap_row, block_point.column) + } + } + } else { + self.wrap_snapshot.max_point() + } } } impl Transform { - fn isomorphic(lines: Point) -> Self { + fn isomorphic(rows: u32) -> Self { Self { summary: TransformSummary { - input: lines, - output: lines, + input_rows: rows, + output_rows: rows, }, block: None, } @@ -569,8 +586,8 @@ impl Transform { fn block(block: Arc) -> Self { Self { summary: TransformSummary { - input: Default::default(), - output: block.text.summary().lines, + input_rows: 0, + output_rows: block.text.summary().lines.row + 1, }, block: Some(block), } @@ -585,16 +602,25 @@ impl<'a> Iterator for Chunks<'a> { type Item = Chunk<'a>; fn next(&mut self) -> Option { - if self.output_position >= self.max_output_position { + if self.output_row >= self.max_output_row { return None; } if let Some(block_chunks) = self.block_chunks.as_mut() { if let Some(block_chunk) = block_chunks.next() { - self.output_position.0 += Point::from_str(block_chunk.text); + self.output_row += block_chunk.text.matches('\n').count() as u32; return Some(block_chunk); } else { self.block_chunks.take(); + self.output_row += 1; + if self.output_row < self.max_output_row { + return Some(Chunk { + text: "\n", + ..Default::default() + }); + } else { + return None; + } } } @@ -602,31 +628,37 @@ impl<'a> Iterator for Chunks<'a> { if let Some(block) = transform.block.as_ref() { let block_start = self.transforms.start().0 .0; let block_end = self.transforms.end(&()).0 .0; - let start_in_block = self.output_position.0 - block_start; - let end_in_block = cmp::min(self.max_output_position.0, block_end) - block_start; + let start_in_block = self.output_row - block_start; + let end_in_block = cmp::min(self.max_output_row, block_end) - block_start; self.transforms.next(&()); - let mut block_chunks = BlockChunks::new(block, start_in_block..end_in_block); - if let Some(block_chunk) = block_chunks.next() { - self.output_position.0 += Point::from_str(block_chunk.text); - return Some(block_chunk); - } + self.block_chunks = Some(BlockChunks::new(block, start_in_block..end_in_block)); + return self.next(); } if self.input_chunk.text.is_empty() { if let Some(input_chunk) = self.input_chunks.next() { self.input_chunk = input_chunk; + } else { + self.output_row += 1; + if self.output_row < self.max_output_row { + self.transforms.next(&()); + return Some(Chunk { + text: "\n", + ..Default::default() + }); + } else { + return None; + } } } let transform_end = self.transforms.end(&()).0 .0; - let (prefix_lines, prefix_bytes) = offset_for_point( - self.input_chunk.text, - transform_end - self.output_position.0, - ); - self.output_position.0 += prefix_lines; + let (prefix_rows, prefix_bytes) = + offset_for_row(self.input_chunk.text, transform_end - self.output_row); + self.output_row += prefix_rows; let (prefix, suffix) = self.input_chunk.text.split_at(prefix_bytes); self.input_chunk.text = suffix; - if self.output_position.0 == transform_end { + if self.output_row == transform_end { self.transforms.next(&()); } @@ -638,9 +670,9 @@ impl<'a> Iterator for Chunks<'a> { } impl<'a> BlockChunks<'a> { - fn new(block: &'a Block, point_range: Range) -> Self { - let offset_range = block.text.point_to_offset(point_range.start) - ..block.text.point_to_offset(point_range.end); + fn new(block: &'a Block, rows: Range) -> Self { + let offset_range = block.text.point_to_offset(Point::new(rows.start, 0)) + ..block.text.point_to_offset(Point::new(rows.end, 0)); let mut runs = block.runs.iter().peekable(); let mut run_start = 0; @@ -701,78 +733,6 @@ impl<'a> Iterator for BlockChunks<'a> { } } -impl<'a> Iterator for BufferRows<'a> { - type Item = (u32, bool); - - fn next(&mut self) -> Option { - if self.output_row > self.max_output_row { - return None; - } - - let (buffer_row, is_wrapped) = self.input_buffer_row.unwrap(); - let in_block = self.in_block; - - // log::info!( - // "============== next - (output_row: {}, input_row: {}, buffer_row: {}, in_block: {}) ===============", - // self.output_row, - // self.input_row, - // buffer_row, - // in_block - // ); - - self.output_row += 1; - let output_point = BlockPoint::new(self.output_row, 0); - let transform_end = self.transforms.end(&()).0; - // if output_point > transform_end || output_point == transform_end && in_block { - if output_point >= transform_end { - // log::info!(" Calling next once"); - self.transforms.next(&()); - if self.transforms.end(&()).0 < output_point { - // log::info!(" Calling next twice"); - self.transforms.next(&()); - } - - if let Some(transform) = self.transforms.item() { - self.in_block = !transform.is_isomorphic(); - } - - // log::info!( - // " Advanced to the next transform (block text: {:?}). Output row: {}, Transform starts at: {:?}", - // self.transforms.item().and_then(|t| t.block.as_ref()).map(|b| b.text.to_string()), - // self.output_row, - // self.transforms.start().1 - // ); - - let mut new_input_position = self.transforms.start().1 .0; - if self.transforms.item().map_or(false, |t| t.is_isomorphic()) { - new_input_position += Point::new(self.output_row, 0) - self.transforms.start().0 .0; - new_input_position = cmp::min(new_input_position, self.transforms.end(&()).1 .0); - } - - if new_input_position.row > self.input_row { - self.input_row = new_input_position.row; - self.input_buffer_row = self.input_buffer_rows.next(); - // log::info!( - // " Advanced the input buffer row. Input row: {}, Input buffer row {:?}", - // self.input_row, - // self.input_buffer_row - // ) - } - } else if self.transforms.item().map_or(true, |t| t.is_isomorphic()) { - self.input_row += 1; - self.input_buffer_row = self.input_buffer_rows.next(); - // log::info!( - // " Advancing in isomorphic transform (off the end: {}). Input row: {}, Input buffer row {:?}", - // self.transforms.item().is_none(), - // self.input_row, - // self.input_buffer_row - // ) - } - - Some((buffer_row, false)) - } -} - impl sum_tree::Item for Transform { type Summary = TransformSummary; @@ -785,20 +745,20 @@ impl sum_tree::Summary for TransformSummary { type Context = (); fn add_summary(&mut self, summary: &Self, _: &()) { - self.input += summary.input; - self.output += summary.output; + self.input_rows += summary.input_rows; + self.output_rows += summary.output_rows; } } -impl<'a> sum_tree::Dimension<'a, TransformSummary> for WrapPoint { +impl<'a> sum_tree::Dimension<'a, TransformSummary> for WrapRow { fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) { - self.0 += summary.input; + self.0 += summary.input_rows; } } -impl<'a> sum_tree::Dimension<'a, TransformSummary> for BlockPoint { +impl<'a> sum_tree::Dimension<'a, TransformSummary> for BlockRow { fn add_summary(&mut self, summary: &'a TransformSummary, _: &()) { - self.0 += summary.output; + self.0 += summary.output_rows; } } @@ -814,23 +774,20 @@ impl BlockDisposition { // Count the number of bytes prior to a target point. If the string doesn't contain the target // point, return its total extent. Otherwise return the target point itself. -fn offset_for_point(s: &str, target: Point) -> (Point, usize) { - let mut point = Point::zero(); +fn offset_for_row(s: &str, target: u32) -> (u32, usize) { + let mut row = 0; let mut offset = 0; - for (row, line) in s.split('\n').enumerate().take(target.row as usize + 1) { - let row = row as u32; - if row > 0 { + for (ix, line) in s.split('\n').enumerate() { + if ix > 0 { + row += 1; offset += 1; } - point.row = row; - point.column = if row == target.row { - cmp::min(line.len() as u32, target.column) - } else { - line.len() as u32 - }; - offset += point.column as usize; + if row >= target { + break; + } + offset += line.len() as usize; } - (point, offset) + (row, offset) } #[cfg(test)] @@ -842,6 +799,20 @@ mod tests { use rand::prelude::*; use std::env; + #[gpui::test] + fn test_offset_for_row() { + assert_eq!(offset_for_row("", 0), (0, 0)); + assert_eq!(offset_for_row("", 1), (0, 0)); + assert_eq!(offset_for_row("abcd", 0), (0, 0)); + assert_eq!(offset_for_row("abcd", 1), (0, 4)); + assert_eq!(offset_for_row("\n", 0), (0, 0)); + assert_eq!(offset_for_row("\n", 1), (1, 1)); + assert_eq!(offset_for_row("abc\ndef\nghi", 0), (0, 0)); + assert_eq!(offset_for_row("abc\ndef\nghi", 1), (1, 4)); + assert_eq!(offset_for_row("abc\ndef\nghi", 2), (2, 8)); + assert_eq!(offset_for_row("abc\ndef\nghi", 3), (2, 11)); + } + #[gpui::test] fn test_basic_blocks(cx: &mut gpui::MutableAppContext) { let family_id = cx.font_cache().load_family(&["Helvetica"]).unwrap(); @@ -888,63 +859,55 @@ mod tests { snapshot.text(), "aaa\nBLOCK 1\nBLOCK 2\nbbb\nccc\nddd\nBLOCK 3" ); - assert_eq!( - snapshot.to_block_point(WrapPoint::new(1, 0), Bias::Right), - BlockPoint::new(3, 0) - ); - assert_eq!( - snapshot.clip_point(BlockPoint::new(1, 0), Bias::Left), - BlockPoint::new(1, 0) - ); - assert_eq!( - snapshot.clip_point(BlockPoint::new(1, 0), Bias::Right), - BlockPoint::new(1, 0) - ); - assert_eq!( - snapshot.clip_point(BlockPoint::new(1, 1), Bias::Left), - BlockPoint::new(1, 0) - ); - assert_eq!( - snapshot.clip_point(BlockPoint::new(1, 1), Bias::Right), - BlockPoint::new(3, 0) - ); - assert_eq!( - snapshot.clip_point(BlockPoint::new(3, 0), Bias::Left), - BlockPoint::new(3, 0) - ); - assert_eq!( - snapshot.clip_point(BlockPoint::new(3, 0), Bias::Right), - BlockPoint::new(3, 0) - ); - assert_eq!( - snapshot.clip_point(BlockPoint::new(5, 3), Bias::Left), - BlockPoint::new(5, 3) - ); - assert_eq!( - snapshot.clip_point(BlockPoint::new(5, 3), Bias::Right), - BlockPoint::new(5, 3) - ); - assert_eq!( - snapshot.clip_point(BlockPoint::new(6, 0), Bias::Left), - BlockPoint::new(5, 3) - ); - assert_eq!( - snapshot.clip_point(BlockPoint::new(6, 0), Bias::Right), - BlockPoint::new(5, 3) - ); + // assert_eq!( + // snapshot.to_block_point(WrapPoint::new(1, 0), Bias::Right), + // BlockPoint::new(3, 0) + // ); + // assert_eq!( + // snapshot.clip_point(BlockPoint::new(1, 0), Bias::Left), + // BlockPoint::new(1, 0) + // ); + // assert_eq!( + // snapshot.clip_point(BlockPoint::new(1, 0), Bias::Right), + // BlockPoint::new(1, 0) + // ); + // assert_eq!( + // snapshot.clip_point(BlockPoint::new(1, 1), Bias::Left), + // BlockPoint::new(1, 0) + // ); + // assert_eq!( + // snapshot.clip_point(BlockPoint::new(1, 1), Bias::Right), + // BlockPoint::new(3, 0) + // ); + // assert_eq!( + // snapshot.clip_point(BlockPoint::new(3, 0), Bias::Left), + // BlockPoint::new(3, 0) + // ); + // assert_eq!( + // snapshot.clip_point(BlockPoint::new(3, 0), Bias::Right), + // BlockPoint::new(3, 0) + // ); + // assert_eq!( + // snapshot.clip_point(BlockPoint::new(5, 3), Bias::Left), + // BlockPoint::new(5, 3) + // ); + // assert_eq!( + // snapshot.clip_point(BlockPoint::new(5, 3), Bias::Right), + // BlockPoint::new(5, 3) + // ); + // assert_eq!( + // snapshot.clip_point(BlockPoint::new(6, 0), Bias::Left), + // BlockPoint::new(5, 3) + // ); + // assert_eq!( + // snapshot.clip_point(BlockPoint::new(6, 0), Bias::Right), + // BlockPoint::new(5, 3) + // ); - assert_eq!( - snapshot.buffer_rows(0).collect::>(), - &[ - (0, true), - (1, false), - (1, false), - (1, true), - (2, true), - (3, true), - (3, false), - ] - ); + // assert_eq!( + // buffer_rows_from_chunks(snapshot.chunks(0..snapshot.max_point().row + 1, false)), + // &[Some(0), None, None, Some(1), Some(2), Some(3), None] + // ); // Insert a line break, separating two block decorations into separate // lines. @@ -985,13 +948,13 @@ mod tests { vec![ BlockProperties { position: Point::new(1, 12), - text: "BLOCK 1", + text: "BLOCK 2", disposition: BlockDisposition::Below, runs: vec![], }, @@ -1004,7 +967,7 @@ mod tests { let mut snapshot = block_map.read(wraps_snapshot, vec![], cx); assert_eq!( snapshot.text(), - "one two \nthree\nBLOCK 1\nfour five \nsix\nBLOCK 2\nseven \neight" + "one two \nthree\nBLOCK 2\nseven \neight" ); } @@ -1012,13 +975,9 @@ mod tests { fn test_random_blocks(cx: &mut gpui::MutableAppContext, mut rng: StdRng) { let operations = env::var("OPERATIONS") .map(|i| i.parse().expect("invalid `OPERATIONS` variable")) - .unwrap_or(10); + .unwrap_or(1); - let wrap_width = if rng.gen_bool(0.2) { - None - } else { - Some(rng.gen_range(0.0..=100.0)) - }; + let wrap_width = None; let tab_size = 1; let family_id = cx.font_cache().load_family(&["Helvetica"]).unwrap(); let font_id = cx @@ -1044,15 +1003,15 @@ mod tests { for _ in 0..operations { match rng.gen_range(0..=100) { - 0..=19 => { - let wrap_width = if rng.gen_bool(0.2) { - None - } else { - Some(rng.gen_range(0.0..=100.0)) - }; - log::info!("Setting wrap width to {:?}", wrap_width); - wrap_map.update(cx, |map, cx| map.set_wrap_width(wrap_width, cx)); - } + // 0..=19 => { + // let wrap_width = if rng.gen_bool(0.2) { + // None + // } else { + // Some(rng.gen_range(0.0..=100.0)) + // }; + // log::info!("Setting wrap width to {:?}", wrap_width); + // wrap_map.update(cx, |map, cx| map.set_wrap_width(wrap_width, cx)); + // } 20..=39 => { let block_count = rng.gen_range(1..=1); let block_properties = (0..block_count) @@ -1134,8 +1093,8 @@ mod tests { }); let mut blocks_snapshot = block_map.read(wraps_snapshot.clone(), wrap_edits, cx); assert_eq!( - blocks_snapshot.transforms.summary().input, - wraps_snapshot.max_point().0 + blocks_snapshot.transforms.summary().input_rows, + wraps_snapshot.max_point().row() + 1 ); log::info!("blocks text: {:?}", blocks_snapshot.text()); @@ -1188,7 +1147,7 @@ mod tests { expected_text.push_str(&text); expected_text.push('\n'); for _ in text.split('\n') { - expected_buffer_rows.push((buffer_row, false)); + expected_buffer_rows.push(None); } sorted_blocks.next(); } else { @@ -1197,7 +1156,7 @@ mod tests { } let soft_wrapped = wraps_snapshot.to_tab_point(WrapPoint::new(row, 0)).column() > 0; - expected_buffer_rows.push((buffer_row, false)); + expected_buffer_rows.push(if soft_wrapped { None } else { Some(buffer_row) }); expected_text.push_str(input_line); while let Some((_, block)) = sorted_blocks.peek() { @@ -1206,7 +1165,7 @@ mod tests { expected_text.push('\n'); expected_text.push_str(&text); for _ in text.split('\n') { - expected_buffer_rows.push((buffer_row, false)); + expected_buffer_rows.push(None); } sorted_blocks.next(); } else { @@ -1216,16 +1175,17 @@ mod tests { } assert_eq!(blocks_snapshot.text(), expected_text); - for row in 0..=blocks_snapshot.wrap_snapshot.max_point().row() { - let wrap_point = WrapPoint::new(row, 0); - let block_point = blocks_snapshot.to_block_point(wrap_point, Bias::Right); - assert_eq!(blocks_snapshot.to_wrap_point(block_point), wrap_point); - } - - assert_eq!( - blocks_snapshot.buffer_rows(0).collect::>(), - expected_buffer_rows - ); + // for row in 0..=blocks_snapshot.wrap_snapshot.max_point().row() { + // let wrap_point = WrapPoint::new(row, 0); + // let block_point = blocks_snapshot.to_block_point(wrap_point, Bias::Right); + // assert_eq!(blocks_snapshot.to_wrap_point(block_point), wrap_point); + // } + // assert_eq!( + // buffer_rows_from_chunks( + // blocks_snapshot.chunks(0..blocks_snapshot.max_point().row + 1, false) + // ), + // expected_buffer_rows + // ); } } } From 198f6694b78ff53b6d424dc14b433290fc8cdff5 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 17 Nov 2021 19:30:40 +0100 Subject: [PATCH 069/109] Use options to represent soft-wrapped buffer rows Co-Authored-By: Nathan Sobo Co-Authored-By: Max Brunsfeld --- crates/editor/src/display_map/wrap_map.rs | 6 +++--- crates/editor/src/element.rs | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/crates/editor/src/display_map/wrap_map.rs b/crates/editor/src/display_map/wrap_map.rs index 6076bd69380c2b6f0429244c1fc255f0216029f3..e337dc7bbf69f6622dbc4ebb482c79212e0b394b 100644 --- a/crates/editor/src/display_map/wrap_map.rs +++ b/crates/editor/src/display_map/wrap_map.rs @@ -702,7 +702,7 @@ impl Snapshot { prev_tab_row = tab_point.row(); soft_wrapped = false; } - expected_buffer_rows.push((buffer_row, soft_wrapped)); + expected_buffer_rows.push(if soft_wrapped { None } else { Some(buffer_row) }); } for start_display_row in 0..expected_buffer_rows.len() { @@ -782,7 +782,7 @@ impl<'a> Iterator for Chunks<'a> { } impl<'a> Iterator for BufferRows<'a> { - type Item = (u32, bool); + type Item = Option; fn next(&mut self) -> Option { if self.output_row > self.max_output_row { @@ -802,7 +802,7 @@ impl<'a> Iterator for BufferRows<'a> { self.soft_wrapped = true; } - Some((buffer_row, soft_wrapped)) + Some(if soft_wrapped { None } else { Some(buffer_row) }) } } diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 1e0e512ffdf135773c3b4c9f7558e674ed43b06b..966396840875ed362556c12443747c44f02e4e24 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -411,7 +411,7 @@ impl EditorElement { let style = &self.settings.style; let mut layouts = Vec::with_capacity(rows.len()); let mut line_number = String::new(); - for (ix, (buffer_row, soft_wrapped)) in snapshot + for (ix, buffer_row) in snapshot .buffer_rows(rows.start) .take((rows.end - rows.start) as usize) .enumerate() @@ -422,9 +422,7 @@ impl EditorElement { } else { style.line_number }; - if soft_wrapped { - layouts.push(None); - } else { + if let Some(buffer_row) = buffer_row { line_number.clear(); write!(&mut line_number, "{}", buffer_row + 1).unwrap(); layouts.push(Some(cx.text_layout_cache.layout_str( @@ -439,6 +437,8 @@ impl EditorElement { }, )], ))); + } else { + layouts.push(None); } } From 88d0c04444e0767c0df93fa341546d5efd67feff Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 17 Nov 2021 19:45:06 +0100 Subject: [PATCH 070/109] Implement `BlockSnapshot::buffer_rows` Co-Authored-By: Nathan Sobo Co-Authored-By: Max Brunsfeld --- crates/editor/src/display_map/block_map.rs | 66 ++++++++++++++++++---- 1 file changed, 56 insertions(+), 10 deletions(-) diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index 157a7f1dcf781cca5c0f9ef2e0cb2305b5c8496f..d4489ee2199e2468f1d052201f56d32653194f57 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -100,6 +100,13 @@ struct BlockChunks<'a> { offset: usize, } +pub struct BufferRows<'a> { + transforms: sum_tree::Cursor<'a, Transform, (BlockRow, WrapRow)>, + input_buffer_rows: wrap_map::BufferRows<'a>, + output_row: u32, + started: bool, +} + impl BlockMap { pub fn new(buffer: ModelHandle, wrap_snapshot: WrapSnapshot) -> Self { Self { @@ -487,6 +494,24 @@ impl BlockSnapshot { } } + pub fn buffer_rows(&self, start_row: u32) -> BufferRows { + let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>(); + cursor.seek(&BlockRow(start_row), Bias::Right, &()); + let (input_start, output_start) = cursor.start(); + let overshoot = if cursor.item().map_or(false, |t| t.is_isomorphic()) { + start_row - output_start.0 + } else { + 0 + }; + let input_start_row = input_start.0 + overshoot; + BufferRows { + transforms: cursor, + input_buffer_rows: self.wrap_snapshot.buffer_rows(input_start_row), + output_row: start_row, + started: false, + } + } + pub fn max_point(&self) -> BlockPoint { todo!() } @@ -733,6 +758,29 @@ impl<'a> Iterator for BlockChunks<'a> { } } +impl<'a> Iterator for BufferRows<'a> { + type Item = Option; + + fn next(&mut self) -> Option { + if self.started { + self.output_row += 1; + } else { + self.started = true; + } + + if self.output_row >= self.transforms.end(&()).0 .0 { + self.transforms.next(&()); + } + + let transform = self.transforms.item()?; + if transform.is_isomorphic() { + Some(self.input_buffer_rows.next().unwrap()) + } else { + Some(None) + } + } +} + impl sum_tree::Item for Transform { type Summary = TransformSummary; @@ -904,10 +952,10 @@ mod tests { // BlockPoint::new(5, 3) // ); - // assert_eq!( - // buffer_rows_from_chunks(snapshot.chunks(0..snapshot.max_point().row + 1, false)), - // &[Some(0), None, None, Some(1), Some(2), Some(3), None] - // ); + assert_eq!( + snapshot.buffer_rows(0).collect::>(), + &[Some(0), None, None, Some(1), Some(2), Some(3), None] + ); // Insert a line break, separating two block decorations into separate // lines. @@ -1180,12 +1228,10 @@ mod tests { // let block_point = blocks_snapshot.to_block_point(wrap_point, Bias::Right); // assert_eq!(blocks_snapshot.to_wrap_point(block_point), wrap_point); // } - // assert_eq!( - // buffer_rows_from_chunks( - // blocks_snapshot.chunks(0..blocks_snapshot.max_point().row + 1, false) - // ), - // expected_buffer_rows - // ); + assert_eq!( + blocks_snapshot.buffer_rows(0).collect::>(), + expected_buffer_rows + ); } } } From e60500dd7c3327fc536d4e8d8896cb3e4ca2bc8a Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Wed, 17 Nov 2021 19:46:43 +0100 Subject: [PATCH 071/109] Re-enable soft-wrapping in randomized tests Co-Authored-By: Nathan Sobo Co-Authored-By: Max Brunsfeld --- crates/editor/src/display_map/block_map.rs | 26 +++++++++++++--------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index d4489ee2199e2468f1d052201f56d32653194f57..a92996445eaeb782ff8e5091187d59a95ead7737 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -1023,9 +1023,13 @@ mod tests { fn test_random_blocks(cx: &mut gpui::MutableAppContext, mut rng: StdRng) { let operations = env::var("OPERATIONS") .map(|i| i.parse().expect("invalid `OPERATIONS` variable")) - .unwrap_or(1); + .unwrap_or(10); - let wrap_width = None; + let wrap_width = if rng.gen_bool(0.2) { + None + } else { + Some(rng.gen_range(0.0..=100.0)) + }; let tab_size = 1; let family_id = cx.font_cache().load_family(&["Helvetica"]).unwrap(); let font_id = cx @@ -1051,15 +1055,15 @@ mod tests { for _ in 0..operations { match rng.gen_range(0..=100) { - // 0..=19 => { - // let wrap_width = if rng.gen_bool(0.2) { - // None - // } else { - // Some(rng.gen_range(0.0..=100.0)) - // }; - // log::info!("Setting wrap width to {:?}", wrap_width); - // wrap_map.update(cx, |map, cx| map.set_wrap_width(wrap_width, cx)); - // } + 0..=19 => { + let wrap_width = if rng.gen_bool(0.2) { + None + } else { + Some(rng.gen_range(0.0..=100.0)) + }; + log::info!("Setting wrap width to {:?}", wrap_width); + wrap_map.update(cx, |map, cx| map.set_wrap_width(wrap_width, cx)); + } 20..=39 => { let block_count = rng.gen_range(1..=1); let block_properties = (0..block_count) From 1c3bf90a8a629ee03341df4deb57d91bbd68b866 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 17 Nov 2021 12:54:14 -0800 Subject: [PATCH 072/109] Reimplement BlockSnapshot::{clip_point,to_block_point,max_point} Co-Authored-By: Nathan Sobo --- crates/editor/src/display_map.rs | 1 - crates/editor/src/display_map/block_map.rs | 256 ++++++++++++--------- 2 files changed, 152 insertions(+), 105 deletions(-) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index ce3962b4a7d9c56502209838acb8ffb6e8cba2a9..f17c79ab316b026644226dcf182e60a14a325b20 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -214,7 +214,6 @@ impl DisplayMapSnapshot { self.tabs_snapshot .to_tab_point(point.to_fold_point(&self.folds_snapshot, bias)), ), - bias, ), ) } diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index a92996445eaeb782ff8e5091187d59a95ead7737..6dae5d8d9c2f7dbac1d83674f9f502d872fe9366 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -513,66 +513,67 @@ impl BlockSnapshot { } pub fn max_point(&self) -> BlockPoint { - todo!() + self.to_block_point(self.wrap_snapshot.max_point()) } pub fn clip_point(&self, point: BlockPoint, bias: Bias) -> BlockPoint { - todo!() - // let mut cursor = self.transforms.cursor::<(BlockPoint, WrapPoint)>(); - // cursor.seek(&point, Bias::Right, &()); - // if let Some(transform) = cursor.prev_item() { - // if transform.is_isomorphic() && point == cursor.start().0 { - // return point; - // } - // } - // if let Some(transform) = cursor.item() { - // if transform.is_isomorphic() { - // let (output_start, input_start) = cursor.start(); - // let output_overshoot = point.0 - output_start.0; - // let input_point = self - // .wrap_snapshot - // .clip_point(WrapPoint(input_start.0 + output_overshoot), bias); - // let input_overshoot = input_point.0 - input_start.0; - // BlockPoint(output_start.0 + input_overshoot) - // } else { - // if bias == Bias::Left && cursor.start().1 .0 > Point::zero() - // || cursor.end(&()).1 == self.wrap_snapshot.max_point() - // { - // loop { - // cursor.prev(&()); - // let transform = cursor.item().unwrap(); - // if transform.is_isomorphic() { - // return BlockPoint(cursor.end(&()).0 .0); - // } - // } - // } else { - // loop { - // cursor.next(&()); - // let transform = cursor.item().unwrap(); - // if transform.is_isomorphic() { - // return BlockPoint(cursor.start().0 .0); - // } - // } - // } - // } - // } else { - // self.max_point() - // } + let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>(); + cursor.seek(&BlockRow(point.row), Bias::Right, &()); + + let max_input_row = WrapRow(self.transforms.summary().input_rows); + let search_left = + (bias == Bias::Left && cursor.start().1 .0 > 0) || cursor.end(&()).1 == max_input_row; + + loop { + if let Some(transform) = cursor.item() { + if transform.is_isomorphic() { + let (output_start_row, input_start_row) = cursor.start(); + let (output_end_row, input_end_row) = cursor.end(&()); + + if point.row >= output_end_row.0 { + return BlockPoint::new( + output_end_row.0 - 1, + self.wrap_snapshot.line_len(input_end_row.0 - 1), + ); + } + + let output_start = Point::new(output_start_row.0, 0); + if point.0 > output_start { + let output_overshoot = point.0 - output_start; + let input_start = Point::new(input_start_row.0, 0); + let input_point = self + .wrap_snapshot + .clip_point(WrapPoint(input_start + output_overshoot), bias); + let input_overshoot = input_point.0 - input_start; + return BlockPoint(output_start + input_overshoot); + } else { + return BlockPoint(output_start); + } + } else if search_left { + cursor.prev(&()); + } else { + cursor.next(&()); + } + } else { + return self.max_point(); + } + } } - pub fn to_block_point(&self, wrap_point: WrapPoint, bias: Bias) -> BlockPoint { - todo!() - // let mut cursor = self.transforms.cursor::<(WrapPoint, BlockPoint)>(); - // cursor.seek(&wrap_point, bias, &()); - // while let Some(item) = cursor.item() { - // if item.is_isomorphic() { - // break; - // } - // cursor.next(&()); - // } - // let (input_start, output_start) = cursor.start(); - // let input_overshoot = wrap_point.0 - input_start.0; - // BlockPoint(output_start.0 + input_overshoot) + pub fn to_block_point(&self, wrap_point: WrapPoint) -> BlockPoint { + let mut cursor = self.transforms.cursor::<(WrapRow, BlockRow)>(); + cursor.seek(&WrapRow(wrap_point.row()), Bias::Right, &()); + if let Some(transform) = cursor.item() { + debug_assert!(transform.is_isomorphic()); + } else { + return self.max_point(); + } + + let (input_start_row, output_start_row) = cursor.start(); + let input_start = Point::new(input_start_row.0, 0); + let output_start = Point::new(output_start_row.0, 0); + let input_overshoot = wrap_point.0 - input_start; + BlockPoint(output_start + input_overshoot) } pub fn to_wrap_point(&self, block_point: BlockPoint) -> WrapPoint { @@ -907,50 +908,76 @@ mod tests { snapshot.text(), "aaa\nBLOCK 1\nBLOCK 2\nbbb\nccc\nddd\nBLOCK 3" ); - // assert_eq!( - // snapshot.to_block_point(WrapPoint::new(1, 0), Bias::Right), - // BlockPoint::new(3, 0) - // ); - // assert_eq!( - // snapshot.clip_point(BlockPoint::new(1, 0), Bias::Left), - // BlockPoint::new(1, 0) - // ); - // assert_eq!( - // snapshot.clip_point(BlockPoint::new(1, 0), Bias::Right), - // BlockPoint::new(1, 0) - // ); - // assert_eq!( - // snapshot.clip_point(BlockPoint::new(1, 1), Bias::Left), - // BlockPoint::new(1, 0) - // ); - // assert_eq!( - // snapshot.clip_point(BlockPoint::new(1, 1), Bias::Right), - // BlockPoint::new(3, 0) - // ); - // assert_eq!( - // snapshot.clip_point(BlockPoint::new(3, 0), Bias::Left), - // BlockPoint::new(3, 0) - // ); - // assert_eq!( - // snapshot.clip_point(BlockPoint::new(3, 0), Bias::Right), - // BlockPoint::new(3, 0) - // ); - // assert_eq!( - // snapshot.clip_point(BlockPoint::new(5, 3), Bias::Left), - // BlockPoint::new(5, 3) - // ); - // assert_eq!( - // snapshot.clip_point(BlockPoint::new(5, 3), Bias::Right), - // BlockPoint::new(5, 3) - // ); - // assert_eq!( - // snapshot.clip_point(BlockPoint::new(6, 0), Bias::Left), - // BlockPoint::new(5, 3) - // ); - // assert_eq!( - // snapshot.clip_point(BlockPoint::new(6, 0), Bias::Right), - // BlockPoint::new(5, 3) - // ); + assert_eq!( + snapshot.to_block_point(WrapPoint::new(0, 3)), + BlockPoint::new(0, 3) + ); + assert_eq!( + snapshot.to_block_point(WrapPoint::new(1, 0)), + BlockPoint::new(3, 0) + ); + assert_eq!( + snapshot.to_block_point(WrapPoint::new(3, 3)), + BlockPoint::new(5, 3) + ); + + assert_eq!( + snapshot.to_wrap_point(BlockPoint::new(0, 3)), + WrapPoint::new(0, 3) + ); + assert_eq!( + snapshot.to_wrap_point(BlockPoint::new(1, 0)), + WrapPoint::new(1, 0) + ); + assert_eq!( + snapshot.to_wrap_point(BlockPoint::new(3, 0)), + WrapPoint::new(1, 0) + ); + assert_eq!( + snapshot.to_wrap_point(BlockPoint::new(6, 0)), + WrapPoint::new(3, 3) + ); + + assert_eq!( + snapshot.clip_point(BlockPoint::new(1, 0), Bias::Left), + BlockPoint::new(0, 3) + ); + assert_eq!( + snapshot.clip_point(BlockPoint::new(1, 0), Bias::Right), + BlockPoint::new(3, 0) + ); + assert_eq!( + snapshot.clip_point(BlockPoint::new(1, 1), Bias::Left), + BlockPoint::new(0, 3) + ); + assert_eq!( + snapshot.clip_point(BlockPoint::new(1, 1), Bias::Right), + BlockPoint::new(3, 0) + ); + assert_eq!( + snapshot.clip_point(BlockPoint::new(3, 0), Bias::Left), + BlockPoint::new(3, 0) + ); + assert_eq!( + snapshot.clip_point(BlockPoint::new(3, 0), Bias::Right), + BlockPoint::new(3, 0) + ); + assert_eq!( + snapshot.clip_point(BlockPoint::new(5, 3), Bias::Left), + BlockPoint::new(5, 3) + ); + assert_eq!( + snapshot.clip_point(BlockPoint::new(5, 3), Bias::Right), + BlockPoint::new(5, 3) + ); + assert_eq!( + snapshot.clip_point(BlockPoint::new(6, 0), Bias::Left), + BlockPoint::new(5, 3) + ); + assert_eq!( + snapshot.clip_point(BlockPoint::new(6, 0), Bias::Right), + BlockPoint::new(5, 3) + ); assert_eq!( snapshot.buffer_rows(0).collect::>(), @@ -1227,15 +1254,36 @@ mod tests { } assert_eq!(blocks_snapshot.text(), expected_text); - // for row in 0..=blocks_snapshot.wrap_snapshot.max_point().row() { - // let wrap_point = WrapPoint::new(row, 0); - // let block_point = blocks_snapshot.to_block_point(wrap_point, Bias::Right); - // assert_eq!(blocks_snapshot.to_wrap_point(block_point), wrap_point); - // } + for row in 0..=blocks_snapshot.wrap_snapshot.max_point().row() { + let wrap_point = WrapPoint::new(row, 0); + let block_point = blocks_snapshot.to_block_point(wrap_point); + assert_eq!(blocks_snapshot.to_wrap_point(block_point), wrap_point); + } assert_eq!( blocks_snapshot.buffer_rows(0).collect::>(), expected_buffer_rows ); + + let mut block_point = BlockPoint::new(0, 0); + for c in expected_text.chars() { + let left_point = blocks_snapshot.clip_point(block_point, Bias::Left); + let right_point = blocks_snapshot.clip_point(block_point, Bias::Right); + + assert_eq!( + blocks_snapshot.to_block_point(blocks_snapshot.to_wrap_point(left_point)), + left_point + ); + assert_eq!( + blocks_snapshot.to_block_point(blocks_snapshot.to_wrap_point(right_point)), + right_point + ); + + if c == '\n' { + block_point.0 += Point::new(1, 0); + } else { + block_point.column += c.len_utf8() as u32; + } + } } } } From 00b5cc472effd6751d6d882306584e294785e6e1 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 17 Nov 2021 14:54:47 -0800 Subject: [PATCH 073/109] Fix BlockSnapshot::chunks when starting in a block Co-Authored-By: Nathan Sobo --- crates/editor/src/display_map/block_map.rs | 65 ++++++++++++++-------- 1 file changed, 43 insertions(+), 22 deletions(-) diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index 6dae5d8d9c2f7dbac1d83674f9f502d872fe9366..29be4784066bef85da920a66e87a0c2b89890b14 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -89,7 +89,6 @@ pub struct Chunks<'a> { block_chunks: Option>, output_row: u32, max_output_row: u32, - max_input_row: u32, } struct BlockChunks<'a> { @@ -464,33 +463,41 @@ impl BlockSnapshot { } pub fn chunks(&self, rows: Range, highlights: bool) -> Chunks { - let max_input_row = self.transforms.summary().input_rows; let max_output_row = cmp::min(rows.end, self.transforms.summary().output_rows); let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>(); - let output_row = rows.start; - cursor.seek(&BlockRow(output_row), Bias::Right, &()); - let (input_start, output_start) = cursor.start(); - let overshoot = rows.start - output_start.0; - let output_end_row = max_output_row.saturating_sub(1); - let input_start_row = input_start.0 + overshoot; - let input_end_row = self - .to_wrap_point(BlockPoint::new( - output_end_row, - self.wrap_snapshot.line_len(output_end_row), - )) - .row() - + 1; - let input_chunks = self - .wrap_snapshot - .chunks(input_start_row..input_end_row, highlights); + let input_end = { + cursor.seek(&BlockRow(rows.end), Bias::Right, &()); + let overshoot = if cursor + .item() + .map_or(false, |transform| transform.is_isomorphic()) + { + rows.end - cursor.start().0 .0 + } else { + 0 + }; + cursor.start().1 .0 + overshoot + }; + let input_start = { + cursor.seek(&BlockRow(rows.start), Bias::Right, &()); + let overshoot = if cursor + .item() + .map_or(false, |transform| transform.is_isomorphic()) + { + rows.start - cursor.start().0 .0 + } else { + 0 + }; + cursor.start().1 .0 + overshoot + }; Chunks { - input_chunks, + input_chunks: self + .wrap_snapshot + .chunks(input_start..input_end, highlights), input_chunk: Default::default(), block_chunks: None, transforms: cursor, - output_row, + output_row: rows.start, max_output_row, - max_input_row, } } @@ -1253,7 +1260,21 @@ mod tests { } } - assert_eq!(blocks_snapshot.text(), expected_text); + let expected_lines = expected_text.split('\n').collect::>(); + let expected_row_count = expected_lines.len(); + for start_row in 0..expected_row_count { + let expected_text = expected_lines[start_row..].join("\n"); + let actual_text = blocks_snapshot + .chunks(start_row as u32..expected_row_count as u32, false) + .map(|chunk| chunk.text) + .collect::(); + assert_eq!( + actual_text, expected_text, + "incorrect text starting from row {}", + start_row + ); + } + for row in 0..=blocks_snapshot.wrap_snapshot.max_point().row() { let wrap_point = WrapPoint::new(row, 0); let block_point = blocks_snapshot.to_block_point(wrap_point); From 707ffe8ff35b3a98c0de816ccf46e1d8a62103b6 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 17 Nov 2021 14:56:05 -0800 Subject: [PATCH 074/109] Implement BlockSnapshot::line_len, use it in DisplayMap Co-Authored-By: Nathan Sobo --- crates/buffer/src/rope.rs | 5 ++++ crates/editor/src/display_map.rs | 13 +++++---- crates/editor/src/display_map/block_map.rs | 34 ++++++++++++++++++---- crates/editor/src/display_map/wrap_map.rs | 4 --- 4 files changed, 41 insertions(+), 15 deletions(-) diff --git a/crates/buffer/src/rope.rs b/crates/buffer/src/rope.rs index d5ac56be6921fcdd4f9394a45c4bf28808edbdad..bb61b29b74bbbe115a22d870bbc62c9e0d5e55a6 100644 --- a/crates/buffer/src/rope.rs +++ b/crates/buffer/src/rope.rs @@ -249,6 +249,11 @@ impl Rope { self.summary().lines_utf16 } } + + pub fn line_len(&self, row: u32) -> u32 { + self.clip_point(Point::new(row, u32::MAX), Bias::Left) + .column + } } impl<'a> From<&'a str> for Rope { diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index f17c79ab316b026644226dcf182e60a14a325b20..f5cdb4b157f202dca75e2c223849cb89dffe6151 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -14,8 +14,7 @@ use sum_tree::Bias; use tab_map::TabMap; use wrap_map::WrapMap; -pub use block_map::{BlockDisposition, BlockProperties, Chunks}; -pub use wrap_map::BufferRows; +pub use block_map::{BlockDisposition, BlockProperties, BufferRows, Chunks}; pub trait ToDisplayPoint { fn to_display_point(&self, map: &DisplayMapSnapshot) -> DisplayPoint; @@ -174,7 +173,7 @@ impl DisplayMapSnapshot { } pub fn buffer_rows(&self, start_row: u32) -> BufferRows { - self.wraps_snapshot.buffer_rows(start_row) + self.blocks_snapshot.buffer_rows(start_row) } pub fn buffer_row_count(&self) -> u32 { @@ -304,7 +303,11 @@ impl DisplayMapSnapshot { } pub fn soft_wrap_indent(&self, display_row: u32) -> Option { - self.wraps_snapshot.soft_wrap_indent(display_row) + let wrap_row = self + .blocks_snapshot + .to_wrap_point(BlockPoint::new(display_row, 0)) + .row(); + self.wraps_snapshot.soft_wrap_indent(wrap_row) } pub fn text(&self) -> String { @@ -339,7 +342,7 @@ impl DisplayMapSnapshot { } pub fn line_len(&self, row: u32) -> u32 { - self.wraps_snapshot.line_len(row) + self.blocks_snapshot.line_len(row) } pub fn longest_row(&self) -> u32 { diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index 29be4784066bef85da920a66e87a0c2b89890b14..13212290adb3b42ef53bb07ceffcafe5b8d093ed 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -336,7 +336,7 @@ fn push_isomorphic(tree: &mut SumTree, rows: u32) { } impl BlockPoint { - fn new(row: u32, column: u32) -> Self { + pub fn new(row: u32, column: u32) -> Self { Self(Point::new(row, column)) } } @@ -520,7 +520,24 @@ impl BlockSnapshot { } pub fn max_point(&self) -> BlockPoint { - self.to_block_point(self.wrap_snapshot.max_point()) + let row = self.transforms.summary().output_rows - 1; + BlockPoint::new(row, self.line_len(row)) + } + + pub fn line_len(&self, row: u32) -> u32 { + let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>(); + cursor.seek(&BlockRow(row), Bias::Right, &()); + if let Some(transform) = cursor.item() { + let (output_start, input_start) = cursor.start(); + let overshoot = row - output_start.0; + if let Some(block) = &transform.block { + block.text.line_len(overshoot) + } else { + self.wrap_snapshot.line_len(input_start.0 + overshoot) + } + } else { + panic!("row out of range"); + } } pub fn clip_point(&self, point: BlockPoint, bias: Bias) -> BlockPoint { @@ -819,10 +836,6 @@ impl<'a> sum_tree::Dimension<'a, TransformSummary> for BlockRow { } impl BlockDisposition { - fn is_above(&self) -> bool { - matches!(self, BlockDisposition::Above) - } - fn is_below(&self) -> bool { matches!(self, BlockDisposition::Below) } @@ -1275,6 +1288,15 @@ mod tests { ); } + for (row, line) in expected_lines.iter().enumerate() { + assert_eq!( + blocks_snapshot.line_len(row as u32), + line.len() as u32, + "invalid line len for row {}", + row + ); + } + for row in 0..=blocks_snapshot.wrap_snapshot.max_point().row() { let wrap_point = WrapPoint::new(row, 0); let block_point = blocks_snapshot.to_block_point(wrap_point); diff --git a/crates/editor/src/display_map/wrap_map.rs b/crates/editor/src/display_map/wrap_map.rs index e337dc7bbf69f6622dbc4ebb482c79212e0b394b..c0764190b17be15e17a1cb86b2f30f83776708c4 100644 --- a/crates/editor/src/display_map/wrap_map.rs +++ b/crates/editor/src/display_map/wrap_map.rs @@ -902,10 +902,6 @@ impl WrapPoint { self.0.row } - pub fn column(self) -> u32 { - self.0.column - } - pub fn row_mut(&mut self) -> &mut u32 { &mut self.0.row } From cb181314327f228db8bc2f6383aaddbcbeb06dda Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 17 Nov 2021 14:56:55 -0800 Subject: [PATCH 075/109] Represent scroll position correctly when scrolled mid-block Co-Authored-By: Nathan Sobo --- crates/editor/src/lib.rs | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/crates/editor/src/lib.rs b/crates/editor/src/lib.rs index bb710b4353e9c59887fd0a028661a7cc6d0b9c50..4d3ed3d22c0419174eeef8c427056b3e80adfbd3 100644 --- a/crates/editor/src/lib.rs +++ b/crates/editor/src/lib.rs @@ -11,9 +11,11 @@ pub use display_map::DisplayPoint; use display_map::*; pub use element::*; use gpui::{ - action, geometry::vector::Vector2F, keymap::Binding, text_layout, AppContext, ClipboardItem, - Element, ElementBox, Entity, ModelHandle, MutableAppContext, RenderContext, View, ViewContext, - WeakViewHandle, + action, + geometry::vector::{vec2f, Vector2F}, + keymap::Binding, + text_layout, AppContext, ClipboardItem, Element, ElementBox, Entity, ModelHandle, + MutableAppContext, RenderContext, View, ViewContext, WeakViewHandle, }; use language::*; use serde::{Deserialize, Serialize}; @@ -469,7 +471,7 @@ impl Editor { cx.notify(); } - fn set_scroll_position(&mut self, mut scroll_position: Vector2F, cx: &mut ViewContext) { + fn set_scroll_position(&mut self, scroll_position: Vector2F, cx: &mut ViewContext) { let map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); let scroll_top_buffer_offset = DisplayPoint::new(scroll_position.y() as u32, 0).to_offset(&map, Bias::Right); @@ -477,8 +479,16 @@ impl Editor { .buffer .read(cx) .anchor_at(scroll_top_buffer_offset, Bias::Right); - scroll_position.set_y(scroll_position.y().fract()); - self.scroll_position = scroll_position; + self.scroll_position = vec2f( + scroll_position.x(), + scroll_position.y() - self.scroll_top_anchor.to_display_point(&map).row() as f32, + ); + + debug_assert_eq!( + compute_scroll_position(&map, self.scroll_position, &self.scroll_top_anchor), + scroll_position + ); + cx.notify(); } From 8230dd9a3b318d6670d65c897d3de0166bce4dac Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 17 Nov 2021 15:33:02 -0800 Subject: [PATCH 076/109] WIP - BlockSnapshot::longest_row --- crates/editor/src/display_map/block_map.rs | 56 +++++++++++++++++++++- crates/editor/src/lib.rs | 2 +- 2 files changed, 55 insertions(+), 3 deletions(-) diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index 13212290adb3b42ef53bb07ceffcafe5b8d093ed..2d02bc40f0467de45a058c8fec6c011a170360a6 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -80,6 +80,8 @@ struct Transform { struct TransformSummary { input_rows: u32, output_rows: u32, + longest_row_in_block: u32, + longest_row_in_block_chars: u32, } pub struct Chunks<'a> { @@ -524,6 +526,24 @@ impl BlockSnapshot { BlockPoint::new(row, self.line_len(row)) } + pub fn longest_row(&self) -> u32 { + let input_row = self.wrap_snapshot.longest_row(); + let input_row_len = self.wrap_snapshot.line_len(input_row); + let TransformSummary { + longest_row_in_block: block_row, + longest_row_in_block_chars: block_row_len, + .. + } = &self.transforms.summary(); + + dbg!(block_row, block_row_len, input_row, input_row_len); + + if *block_row_len > input_row_len { + *block_row + } else { + self.to_block_point(WrapPoint::new(input_row, 0)).row + } + } + pub fn line_len(&self, row: u32) -> u32 { let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>(); cursor.seek(&BlockRow(row), Bias::Right, &()); @@ -628,16 +648,21 @@ impl Transform { summary: TransformSummary { input_rows: rows, output_rows: rows, + longest_row_in_block: 0, + longest_row_in_block_chars: 0, }, block: None, } } fn block(block: Arc) -> Self { + let text_summary = block.text.summary(); Self { summary: TransformSummary { input_rows: 0, - output_rows: block.text.summary().lines.row + 1, + output_rows: text_summary.lines.row + 1, + longest_row_in_block: text_summary.longest_row, + longest_row_in_block_chars: text_summary.longest_row_chars, }, block: Some(block), } @@ -818,6 +843,11 @@ impl sum_tree::Summary for TransformSummary { type Context = (); fn add_summary(&mut self, summary: &Self, _: &()) { + if summary.longest_row_in_block_chars > self.longest_row_in_block_chars { + self.longest_row_in_block_chars = summary.longest_row_in_block_chars; + self.longest_row_in_block = self.output_rows + summary.longest_row_in_block; + } + self.input_rows += summary.input_rows; self.output_rows += summary.output_rows; } @@ -1288,15 +1318,37 @@ mod tests { ); } + let mut expected_longest_rows = Vec::new(); + let mut longest_line_len = -1_isize; for (row, line) in expected_lines.iter().enumerate() { + let row = row as u32; + assert_eq!( - blocks_snapshot.line_len(row as u32), + blocks_snapshot.line_len(row), line.len() as u32, "invalid line len for row {}", row ); + + match (line.len() as isize).cmp(&longest_line_len) { + Ordering::Less => {} + Ordering::Equal => expected_longest_rows.push(row), + Ordering::Greater => { + longest_line_len = line.len() as isize; + expected_longest_rows.clear(); + expected_longest_rows.push(row); + } + } } + let longest_row = blocks_snapshot.longest_row(); + assert!( + expected_longest_rows.contains(&longest_row), + "incorrect longest row {}. expected {:?}", + longest_row, + expected_longest_rows, + ); + for row in 0..=blocks_snapshot.wrap_snapshot.max_point().row() { let wrap_point = WrapPoint::new(row, 0); let block_point = blocks_snapshot.to_block_point(wrap_point); diff --git a/crates/editor/src/lib.rs b/crates/editor/src/lib.rs index 4d3ed3d22c0419174eeef8c427056b3e80adfbd3..ffd9e961bc1cd612a123fdd78a4885663956dace 100644 --- a/crates/editor/src/lib.rs +++ b/crates/editor/src/lib.rs @@ -2225,7 +2225,7 @@ impl Editor { let buffer = self.buffer.read(cx); let diagnostic_group = buffer .diagnostic_group::(group_id) - .map(|(range, diagnostic)| (range, format!("{}\n", diagnostic.message))) + .map(|(range, diagnostic)| (range, diagnostic.message.clone())) .collect::>(); dbg!(group_id, &diagnostic_group); From c5956a03635734a5d32aff9663a8139200951705 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 17 Nov 2021 19:44:17 -0700 Subject: [PATCH 077/109] Start at the end of the last transform when catching up to edits during wrapping Co-Authored-By: Max Brunsfeld --- crates/editor/src/display_map/wrap_map.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/editor/src/display_map/wrap_map.rs b/crates/editor/src/display_map/wrap_map.rs index c0764190b17be15e17a1cb86b2f30f83776708c4..25623b47c9f7103f196eda66fe93e85763a82f26 100644 --- a/crates/editor/src/display_map/wrap_map.rs +++ b/crates/editor/src/display_map/wrap_map.rs @@ -415,7 +415,7 @@ impl Snapshot { while let Some(edit) = row_edits.next() { if edit.new_rows.start > new_transforms.summary().input.lines.row { let summary = new_tab_snapshot.text_summary_for_range( - TabPoint::new(new_transforms.summary().input.lines.row, 0) + TabPoint(new_transforms.summary().input.lines) ..TabPoint::new(edit.new_rows.start, 0), ); new_transforms.push_or_extend(Transform::isomorphic(summary)); From 213aa36e1c03e50ad083a1f58605b544d9d90782 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Wed, 17 Nov 2021 19:45:56 -0700 Subject: [PATCH 078/109] WIP: Track down bugs with longest_row on wrap map Co-Authored-By: Max Brunsfeld --- crates/buffer/src/rope.rs | 36 +++++- crates/editor/src/display_map/block_map.rs | 9 +- crates/editor/src/display_map/tab_map.rs | 54 +++++--- crates/editor/src/display_map/wrap_map.rs | 141 ++++++++++++++++++++- crates/editor/src/test.rs | 1 + 5 files changed, 216 insertions(+), 25 deletions(-) diff --git a/crates/buffer/src/rope.rs b/crates/buffer/src/rope.rs index bb61b29b74bbbe115a22d870bbc62c9e0d5e55a6..71a906fe5dcee851c0781299a32b5593fce1839e 100644 --- a/crates/buffer/src/rope.rs +++ b/crates/buffer/src/rope.rs @@ -767,7 +767,7 @@ mod tests { use super::*; use crate::random_char_iter::RandomCharIter; use rand::prelude::*; - use std::env; + use std::{cmp::Ordering, env}; use Bias::{Left, Right}; #[test] @@ -814,7 +814,7 @@ mod tests { } #[gpui::test(iterations = 100)] - fn test_random(mut rng: StdRng) { + fn test_random_rope(mut rng: StdRng) { let operations = env::var("OPERATIONS") .map(|i| i.parse().expect("invalid `OPERATIONS` variable")) .unwrap_or(10); @@ -898,6 +898,38 @@ mod tests { TextSummary::from(&expected[start_ix..end_ix]) ); } + + let mut expected_longest_rows = Vec::new(); + let mut longest_line_len = -1_isize; + for (row, line) in expected.split('\n').enumerate() { + let row = row as u32; + assert_eq!( + actual.line_len(row), + line.len() as u32, + "invalid line len for row {}", + row + ); + + let line_char_count = line.chars().count() as isize; + match line_char_count.cmp(&longest_line_len) { + Ordering::Less => {} + Ordering::Equal => expected_longest_rows.push(row), + Ordering::Greater => { + longest_line_len = line_char_count; + expected_longest_rows.clear(); + expected_longest_rows.push(row); + } + } + } + + let longest_row = actual.summary().longest_row; + assert!( + expected_longest_rows.contains(&longest_row), + "incorrect longest row {}. expected {:?} with length {}", + longest_row, + expected_longest_rows, + longest_line_len, + ); } } diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index 2d02bc40f0467de45a058c8fec6c011a170360a6..ef7e262136b8ddac8df277664e0125175649feda 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -1330,23 +1330,26 @@ mod tests { row ); - match (line.len() as isize).cmp(&longest_line_len) { + let line_char_count = line.chars().count() as isize; + match line_char_count.cmp(&longest_line_len) { Ordering::Less => {} Ordering::Equal => expected_longest_rows.push(row), Ordering::Greater => { - longest_line_len = line.len() as isize; + longest_line_len = line_char_count; expected_longest_rows.clear(); expected_longest_rows.push(row); } } } + log::info!("getting longest row >>>>>>>>>>>>>>>>>>>>>>>>"); let longest_row = blocks_snapshot.longest_row(); assert!( expected_longest_rows.contains(&longest_row), - "incorrect longest row {}. expected {:?}", + "incorrect longest row {}. expected {:?} with length {}", longest_row, expected_longest_rows, + longest_line_len, ); for row in 0..=blocks_snapshot.wrap_snapshot.max_point().row() { diff --git a/crates/editor/src/display_map/tab_map.rs b/crates/editor/src/display_map/tab_map.rs index e0a7f2f846476d95c77bee0f4dc9b62d1dfc67d6..a5b614e50656512bc51777ba582cec91d5992b1c 100644 --- a/crates/editor/src/display_map/tab_map.rs +++ b/crates/editor/src/display_map/tab_map.rs @@ -110,34 +110,31 @@ impl Snapshot { .text_summary_for_range(input_start..input_end); let mut first_line_chars = 0; - let mut first_line_bytes = 0; + let line_end = if range.start.row() == range.end.row() { + range.end + } else { + self.max_point() + }; for c in self - .chunks(range.start..self.max_point(), false) + .chunks(range.start..line_end, false) .flat_map(|chunk| chunk.text.chars()) { - if c == '\n' - || (range.start.row() == range.end.row() && first_line_bytes == range.end.column()) - { + if c == '\n' { break; } first_line_chars += 1; - first_line_bytes += c.len_utf8() as u32; } let mut last_line_chars = 0; - let mut last_line_bytes = 0; - for c in self - .chunks( - TabPoint::new(range.end.row(), 0).max(range.start)..self.max_point(), - false, - ) - .flat_map(|chunk| chunk.text.chars()) - { - if last_line_bytes == range.end.column() { - break; + if range.start.row() == range.end.row() { + last_line_chars = first_line_chars; + } else { + for _ in self + .chunks(TabPoint::new(range.end.row(), 0)..self.max_point(), false) + .flat_map(|chunk| chunk.text.chars()) + { + last_line_chars += 1; } - last_line_chars += 1; - last_line_bytes += c.len_utf8() as u32; } TextSummary { @@ -427,6 +424,12 @@ impl<'a> Iterator for Chunks<'a> { #[cfg(test)] mod tests { + use buffer::RandomCharIter; + use language::Buffer; + use rand::{prelude::StdRng, Rng}; + + use crate::display_map::fold_map::FoldMap; + use super::*; #[test] @@ -435,4 +438,19 @@ mod tests { assert_eq!(Snapshot::expand_tabs("\t".chars(), 1, 4), 4); assert_eq!(Snapshot::expand_tabs("\ta".chars(), 2, 4), 5); } + + #[gpui::test(iterations = 100)] + fn test_text_summary_for_range(cx: &mut gpui::MutableAppContext, mut rng: StdRng) { + let tab_size = rng.gen_range(1..=4); + let buffer = cx.add_model(|cx| { + let len = rng.gen_range(0..30); + let text = RandomCharIter::new(&mut rng).take(len).collect::(); + Buffer::new(0, text, cx) + }); + let (_, folds_snapshot) = FoldMap::new(buffer.clone(), cx); + let (_, tabs_snapshot) = TabMap::new(folds_snapshot.clone(), tab_size); + + println!("{:?}", tabs_snapshot.text()); + // TODO: Test text_summary_for_range with random ranges + } } diff --git a/crates/editor/src/display_map/wrap_map.rs b/crates/editor/src/display_map/wrap_map.rs index 25623b47c9f7103f196eda66fe93e85763a82f26..b2ad95788238e07ba6b968fe911c088e62e27d8a 100644 --- a/crates/editor/src/display_map/wrap_map.rs +++ b/crates/editor/src/display_map/wrap_map.rs @@ -299,11 +299,20 @@ impl Snapshot { } fn interpolate(&mut self, new_tab_snapshot: TabSnapshot, tab_edits: &[TabEdit]) -> Patch { + log::info!("INTERPOLATING"); + + log::info!("updating transforms... old transforms are:"); + for transform in self.transforms.items(&()) { + log::info!(" - i {:?}", transform.summary.input); + log::info!(" o {:?}", transform.summary.output); + } + let mut new_transforms; if tab_edits.is_empty() { new_transforms = self.transforms.clone(); } else { let mut old_cursor = self.transforms.cursor::(); + let mut tab_edits_iter = tab_edits.iter().peekable(); new_transforms = old_cursor.slice( &tab_edits_iter.peek().unwrap().old_lines.start, @@ -311,12 +320,26 @@ impl Snapshot { &(), ); + log::info!("sliced, new_transforms are:"); + for transform in new_transforms.items(&()) { + log::info!(" - i {:?}", transform.summary.input); + log::info!(" o {:?}", transform.summary.output); + } + while let Some(edit) = tab_edits_iter.next() { + log::info!("processing edit {:?}", edit); + if edit.new_lines.start > TabPoint::from(new_transforms.summary().input.lines) { let summary = new_tab_snapshot.text_summary_for_range( TabPoint::from(new_transforms.summary().input.lines)..edit.new_lines.start, ); + log::info!("pushing prefix before edit: {:?}", summary); new_transforms.push_or_extend(Transform::isomorphic(summary)); + log::info!("new transforms are now:"); + for transform in new_transforms.items(&()) { + log::info!(" - i {:?}", transform.summary.input); + log::info!(" o {:?}", transform.summary.output); + } } if !edit.new_lines.is_empty() { @@ -325,6 +348,12 @@ impl Snapshot { )); } + log::info!("pushed summary within edit new range; new transforms now:"); + for transform in new_transforms.items(&()) { + log::info!(" - i {:?}", transform.summary.input); + log::info!(" o {:?}", transform.summary.output); + } + old_cursor.seek_forward(&edit.old_lines.end, Bias::Right, &()); if let Some(next_edit) = tab_edits_iter.peek() { if next_edit.old_lines.start > old_cursor.end(&()) { @@ -334,21 +363,46 @@ impl Snapshot { .text_summary_for_range(edit.old_lines.end..old_cursor.end(&())); new_transforms.push_or_extend(Transform::isomorphic(summary)); } + + log::info!("pushed transform suffix after edit; new transforms now:"); + for transform in new_transforms.items(&()) { + log::info!(" - i {:?}", transform.summary.input); + log::info!(" o {:?}", transform.summary.output); + } + old_cursor.next(&()); new_transforms.push_tree( old_cursor.slice(&next_edit.old_lines.start, Bias::Right, &()), &(), ); + + log::info!("pushed tree suffix after edit; new transforms now:"); + for transform in new_transforms.items(&()) { + log::info!(" - i {:?}", transform.summary.input); + log::info!(" o {:?}", transform.summary.output); + } } } else { + log::info!("no more edits"); if old_cursor.end(&()) > edit.old_lines.end { let summary = self .tab_snapshot .text_summary_for_range(edit.old_lines.end..old_cursor.end(&())); new_transforms.push_or_extend(Transform::isomorphic(summary)); + + log::info!("pushed transform suffix after edit; new transforms now:"); + for transform in new_transforms.items(&()) { + log::info!(" - i {:?}", transform.summary.input); + log::info!(" o {:?}", transform.summary.output); + } } old_cursor.next(&()); new_transforms.push_tree(old_cursor.suffix(&()), &()); + log::info!("pushed suffix:"); + for transform in new_transforms.items(&()) { + log::info!(" - i {:?}", transform.summary.input); + log::info!(" o {:?}", transform.summary.output); + } } } } @@ -378,6 +432,12 @@ impl Snapshot { new_rows: Range, } + log::info!("updating transforms... old transforms are:"); + for transform in self.transforms.items(&()) { + log::info!(" - i {:?}", transform.summary.input); + log::info!(" o {:?}", transform.summary.output); + } + let mut tab_edits_iter = tab_edits.into_iter().peekable(); let mut row_edits = Vec::new(); while let Some(edit) = tab_edits_iter.next() { @@ -399,6 +459,11 @@ impl Snapshot { row_edits.push(row_edit); } + log::info!("row edits are:"); + for edit in &row_edits { + log::info!(" {:?}", edit); + } + let mut new_transforms; if row_edits.is_empty() { new_transforms = self.transforms.clone(); @@ -412,6 +477,12 @@ impl Snapshot { &(), ); + log::info!("sliced a prefix:"); + for transform in new_transforms.items(&()) { + log::info!(" - i {:?}", transform.summary.input); + log::info!(" o {:?}", transform.summary.output); + } + while let Some(edit) = row_edits.next() { if edit.new_rows.start > new_transforms.summary().input.lines.row { let summary = new_tab_snapshot.text_summary_for_range( @@ -465,9 +536,15 @@ impl Snapshot { } let mut edit_transforms = edit_transforms.into_iter(); + log::info!("extending tree with edit transforms"); if let Some(transform) = edit_transforms.next() { + log::info!( + "push or extend with first transform: {:?}", + transform.summary.output + ); new_transforms.push_or_extend(transform); } + log::info!("extending with remaining transforms"); new_transforms.extend(edit_transforms, &()); old_cursor.seek_forward(&TabPoint::new(edit.old_rows.end, 0), Bias::Right, &()); @@ -859,10 +936,17 @@ impl sum_tree::Item for Transform { } fn push_isomorphic(transforms: &mut Vec, summary: TextSummary) { + log::info!("push_isomorphic: {:?}", summary); if let Some(last_transform) = transforms.last_mut() { if last_transform.is_isomorphic() { last_transform.summary.input += &summary; last_transform.summary.output += &summary; + + log::info!( + " extended previous isomorphic: {:?}", + last_transform.summary.output + ); + return; } } @@ -879,15 +963,28 @@ impl SumTreeExt for SumTree { self.update_last( |last_transform| { if last_transform.is_isomorphic() && transform.as_ref().unwrap().is_isomorphic() { + // log::info!("extending last transform in tree"); + // log::info!( + // " extending with: {:?}", + // transform.as_ref().map(|t| t.summary.output.clone()), + // ); + // log::info!(" last transform was: {:?}", last_transform.summary.output); + let transform = transform.take().unwrap(); last_transform.summary.input += &transform.summary.input; last_transform.summary.output += &transform.summary.output; + + // log::info!( + // " last transform is now {:?}", + // last_transform.summary.output, + // ) } }, &(), ); if let Some(transform) = transform { + log::info!("!!!!!!!!!!! push transform: {:?}", transform.summary.output,); self.push(transform, &()); } } @@ -1011,7 +1108,7 @@ mod tests { let unwrapped_text = tabs_snapshot.text(); let expected_text = wrap_text(&unwrapped_text, wrap_width, &mut line_wrapper); - let (wrap_map, initial_snapshot) = + let (wrap_map, _) = cx.update(|cx| WrapMap::new(tabs_snapshot.clone(), font_id, font_size, wrap_width, cx)); let (_observer, notifications) = Observer::new(&wrap_map, &mut cx); @@ -1019,6 +1116,11 @@ mod tests { notifications.recv().await.unwrap(); } + let (initial_snapshot, _) = wrap_map.update(&mut cx, |map, cx| { + assert!(!map.is_rewrapping()); + map.sync(tabs_snapshot.clone(), Vec::new(), cx) + }); + let actual_text = initial_snapshot.text(); assert_eq!( actual_text, expected_text, @@ -1029,6 +1131,8 @@ mod tests { let mut edits = Vec::new(); for _i in 0..operations { + log::info!("{} ==============================================", _i); + match rng.gen_range(0..=100) { 0..=19 => { wrap_width = if rng.gen_bool(0.2) { @@ -1088,15 +1192,48 @@ mod tests { let (mut wrapped_snapshot, wrap_edits) = wrap_map.update(&mut cx, |map, cx| map.sync(tabs_snapshot, Vec::new(), cx)); let actual_text = wrapped_snapshot.text(); + let actual_longest_row = wrapped_snapshot.longest_row(); log::info!("Wrapping finished: {:?}", actual_text); wrapped_snapshot.check_invariants(); wrapped_snapshot.verify_chunks(&mut rng); - edits.push((wrapped_snapshot, wrap_edits)); + edits.push((wrapped_snapshot.clone(), wrap_edits)); assert_eq!( actual_text, expected_text, "unwrapped text is: {:?}", unwrapped_text ); + + let mut summary = TextSummary::default(); + for (ix, item) in wrapped_snapshot + .transforms + .items(&()) + .into_iter() + .enumerate() + { + summary += &item.summary.output; + log::info!("{} summary: {:?}", ix, item.summary.output,); + } + + let mut expected_longest_rows = Vec::new(); + let mut longest_line_len = -1; + for (row, line) in expected_text.split('\n').enumerate() { + let line_char_count = line.chars().count() as isize; + if line_char_count > longest_line_len { + expected_longest_rows.clear(); + longest_line_len = line_char_count; + } + if line_char_count >= longest_line_len { + expected_longest_rows.push(row as u32); + } + } + + assert!( + expected_longest_rows.contains(&actual_longest_row), + "incorrect longest row {}. expected {:?} with length {}", + actual_longest_row, + expected_longest_rows, + longest_line_len, + ) } } diff --git a/crates/editor/src/test.rs b/crates/editor/src/test.rs index 33fafc3f54c4ecb2b7011f4ebf2783f97698c999..26f29364fd8a0240e9fae12b3f4de3fada4d665e 100644 --- a/crates/editor/src/test.rs +++ b/crates/editor/src/test.rs @@ -5,6 +5,7 @@ use std::marker::PhantomData; #[cfg(test)] #[ctor::ctor] fn init_logger() { + // std::env::set_var("RUST_LOG", "info"); env_logger::init(); } From b10c82c01540273a33137cec59571ff14f8f0586 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 18 Nov 2021 09:52:05 +0100 Subject: [PATCH 079/109] Stop at `range.end` when computing text summary for range in TabMap --- crates/editor/src/display_map/tab_map.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/editor/src/display_map/tab_map.rs b/crates/editor/src/display_map/tab_map.rs index a5b614e50656512bc51777ba582cec91d5992b1c..b864019033e54afbeff1965580e60ffd539df477 100644 --- a/crates/editor/src/display_map/tab_map.rs +++ b/crates/editor/src/display_map/tab_map.rs @@ -130,7 +130,7 @@ impl Snapshot { last_line_chars = first_line_chars; } else { for _ in self - .chunks(TabPoint::new(range.end.row(), 0)..self.max_point(), false) + .chunks(TabPoint::new(range.end.row(), 0)..range.end, false) .flat_map(|chunk| chunk.text.chars()) { last_line_chars += 1; From 4967a8d5ef96f5f231553b91e072ca5351c6c416 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 18 Nov 2021 09:52:42 +0100 Subject: [PATCH 080/109] Trim expanded tabs if they overshoot the provided range --- crates/editor/src/display_map/tab_map.rs | 79 ++++++++++++++++++++---- 1 file changed, 67 insertions(+), 12 deletions(-) diff --git a/crates/editor/src/display_map/tab_map.rs b/crates/editor/src/display_map/tab_map.rs index b864019033e54afbeff1965580e60ffd539df477..c0a0fc64b0ebc12332d14e6fe0f432108ae84158 100644 --- a/crates/editor/src/display_map/tab_map.rs +++ b/crates/editor/src/display_map/tab_map.rs @@ -2,7 +2,7 @@ use super::fold_map::{self, FoldEdit, FoldPoint, Snapshot as FoldSnapshot, ToFol use buffer::Point; use language::{rope, Chunk}; use parking_lot::Mutex; -use std::{mem, ops::Range}; +use std::{cmp, mem, ops::Range}; use sum_tree::Bias; pub struct TabMap(Mutex); @@ -158,11 +158,19 @@ impl Snapshot { .to_fold_point(range.end, Bias::Right) .0 .to_offset(&self.fold_snapshot); + let to_next_stop = if range.start.0 + Point::new(0, to_next_stop as u32) > range.end.0 { + (range.end.column() - range.start.column()) as usize + } else { + to_next_stop + }; + Chunks { fold_chunks: self .fold_snapshot .chunks(input_start..input_end, highlights), column: expanded_char_column, + output_position: range.start.0, + max_output_position: range.end.0, tab_size: self.tab_size, chunk: Chunk { text: &SPACES[0..to_next_stop], @@ -373,6 +381,8 @@ pub struct Chunks<'a> { fold_chunks: fold_map::Chunks<'a>, chunk: Chunk<'a>, column: usize, + output_position: Point, + max_output_position: Point, tab_size: usize, skip_leading_tab: bool, } @@ -405,16 +415,28 @@ impl<'a> Iterator for Chunks<'a> { }); } else { self.chunk.text = &self.chunk.text[1..]; - let len = self.tab_size - self.column % self.tab_size; + let mut len = self.tab_size - self.column % self.tab_size; + let next_output_position = cmp::min( + self.output_position + Point::new(0, len as u32), + self.max_output_position, + ); + len = (next_output_position.column - self.output_position.column) as usize; self.column += len; + self.output_position = next_output_position; return Some(Chunk { text: &SPACES[0..len], ..self.chunk }); } } - '\n' => self.column = 0, - _ => self.column += 1, + '\n' => { + self.column = 0; + self.output_position += Point::new(1, 0); + } + _ => { + self.column += 1; + self.output_position.column += c.len_utf8() as u32; + } } } @@ -424,14 +446,12 @@ impl<'a> Iterator for Chunks<'a> { #[cfg(test)] mod tests { - use buffer::RandomCharIter; + use super::*; + use crate::display_map::fold_map::FoldMap; + use buffer::{RandomCharIter, Rope}; use language::Buffer; use rand::{prelude::StdRng, Rng}; - use crate::display_map::fold_map::FoldMap; - - use super::*; - #[test] fn test_expand_tabs() { assert_eq!(Snapshot::expand_tabs("\t".chars(), 0, 4), 0); @@ -440,7 +460,7 @@ mod tests { } #[gpui::test(iterations = 100)] - fn test_text_summary_for_range(cx: &mut gpui::MutableAppContext, mut rng: StdRng) { + fn test_random(cx: &mut gpui::MutableAppContext, mut rng: StdRng) { let tab_size = rng.gen_range(1..=4); let buffer = cx.add_model(|cx| { let len = rng.gen_range(0..30); @@ -449,8 +469,43 @@ mod tests { }); let (_, folds_snapshot) = FoldMap::new(buffer.clone(), cx); let (_, tabs_snapshot) = TabMap::new(folds_snapshot.clone(), tab_size); + let text = Rope::from(tabs_snapshot.text().as_str()); + log::info!("Tab size: {}", tab_size); + log::info!("Buffer text: {:?}", buffer.read(cx).text()); + log::info!("FoldMap text: {:?}", folds_snapshot.text()); + log::info!("TabMap text: {:?}", tabs_snapshot.text()); + for _ in 0..1 { + let end_row = rng.gen_range(0..=text.max_point().row); + let end_column = rng.gen_range(0..=text.line_len(end_row)); + let mut end = TabPoint(text.clip_point(Point::new(end_row, end_column), Bias::Right)); + let start_row = rng.gen_range(0..=text.max_point().row); + let start_column = rng.gen_range(0..=text.line_len(start_row)); + let mut start = + TabPoint(text.clip_point(Point::new(start_row, start_column), Bias::Left)); + if start > end { + mem::swap(&mut start, &mut end); + } - println!("{:?}", tabs_snapshot.text()); - // TODO: Test text_summary_for_range with random ranges + let expected_text = text + .chunks_in_range(text.point_to_offset(start.0)..text.point_to_offset(end.0)) + .collect::(); + let expected_summary = TextSummary::from(expected_text.as_str()); + log::info!("Slicing {:?}..{:?} (text: {:?})", start, end, text); + assert_eq!( + expected_text, + tabs_snapshot + .chunks(start..end, false) + .map(|c| c.text) + .collect::() + ); + assert_eq!( + TextSummary { + longest_row: expected_summary.longest_row, + longest_row_chars: expected_summary.longest_row_chars, + ..tabs_snapshot.text_summary_for_range(start..end) + }, + expected_summary, + ); + } } } From 84d257470a3f748e5b799759b08776cd3af6313b Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 18 Nov 2021 10:33:31 +0100 Subject: [PATCH 081/109] Fix empty range edge case in `FoldMap` --- crates/editor/src/display_map/fold_map.rs | 26 +++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/crates/editor/src/display_map/fold_map.rs b/crates/editor/src/display_map/fold_map.rs index 3384928a8f324eea994f2019275a8cd4d217edcc..2cb0f215bf003b7ded38a803b23a8f2cd19a2b0e 100644 --- a/crates/editor/src/display_map/fold_map.rs +++ b/crates/editor/src/display_map/fold_map.rs @@ -647,11 +647,13 @@ impl Snapshot { Chunks { transform_cursor, - buffer_offset: buffer_start, buffer_chunks: self .buffer_snapshot .chunks(buffer_start..buffer_end, enable_highlights), buffer_chunk: None, + buffer_offset: buffer_start, + output_offset: range.start.0, + max_output_offset: range.end.0, } } @@ -938,12 +940,18 @@ pub struct Chunks<'a> { buffer_chunks: language::Chunks<'a>, buffer_chunk: Option<(usize, Chunk<'a>)>, buffer_offset: usize, + output_offset: usize, + max_output_offset: usize, } impl<'a> Iterator for Chunks<'a> { type Item = Chunk<'a>; fn next(&mut self) -> Option { + if self.output_offset >= self.max_output_offset { + return None; + } + let transform = if let Some(item) = self.transform_cursor.item() { item } else { @@ -963,6 +971,7 @@ impl<'a> Iterator for Chunks<'a> { self.transform_cursor.next(&()); } + self.output_offset += output_text.len(); return Some(Chunk { text: output_text, highlight_id: HighlightId::default(), @@ -991,6 +1000,7 @@ impl<'a> Iterator for Chunks<'a> { } self.buffer_offset += chunk.text.len(); + self.output_offset += chunk.text.len(); return Some(chunk); } @@ -1362,14 +1372,22 @@ mod tests { } for _ in 0..5 { - let start = snapshot + let mut start = snapshot + .clip_offset(FoldOffset(rng.gen_range(0..=snapshot.len().0)), Bias::Left); + let mut end = snapshot .clip_offset(FoldOffset(rng.gen_range(0..=snapshot.len().0)), Bias::Right); + if start > end { + mem::swap(&mut start, &mut end); + } + + let text = &expected_text[start.0..end.0]; + log::info!("slicing {:?}..{:?} (text: {:?})", start, end, text); assert_eq!( snapshot - .chunks(start..snapshot.len(), false) + .chunks(start..end, false) .map(|c| c.text) .collect::(), - &expected_text[start.0..], + text, ); } From 9ba24794c701ed2c6680e967dfd44eec91214922 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 18 Nov 2021 10:35:31 +0100 Subject: [PATCH 082/109] Re-enable tabs, spaces and multi-byte characters in randomized tests --- crates/buffer/src/random_char_iter.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/crates/buffer/src/random_char_iter.rs b/crates/buffer/src/random_char_iter.rs index 535ee03eef860e420c31e3a460a2522bf0f78359..244665688d6008caa1bbb0c8208aef0df863b8e9 100644 --- a/crates/buffer/src/random_char_iter.rs +++ b/crates/buffer/src/random_char_iter.rs @@ -14,14 +14,14 @@ impl Iterator for RandomCharIter { fn next(&mut self) -> Option { match self.0.gen_range(0..100) { // whitespace - 0..=4 => ['\n'].choose(&mut self.0).copied(), - // // two-byte greek letters - // 20..=32 => char::from_u32(self.0.gen_range(('α' as u32)..('ω' as u32 + 1))), - // // three-byte characters - // 33..=45 => ['✋', '✅', '❌', '❎', '⭐'].choose(&mut self.0).copied(), - // // four-byte characters - // 46..=58 => ['🍐', '🏀', '🍗', '🎉'].choose(&mut self.0).copied(), - // // ascii letters + 0..=19 => [' ', '\n', '\t'].choose(&mut self.0).copied(), + // two-byte greek letters + 20..=32 => char::from_u32(self.0.gen_range(('α' as u32)..('ω' as u32 + 1))), + // three-byte characters + 33..=45 => ['✋', '✅', '❌', '❎', '⭐'].choose(&mut self.0).copied(), + // four-byte characters + 46..=58 => ['🍐', '🏀', '🍗', '🎉'].choose(&mut self.0).copied(), + // ascii letters _ => Some(self.0.gen_range(b'a'..b'z' + 1).into()), } } From 5a9dea52993325765a2d5df53cbef8f2bc75c97e Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 18 Nov 2021 10:36:07 +0100 Subject: [PATCH 083/109] Ensure `TabMap` works correctly when there are folds --- crates/editor/src/display_map/tab_map.rs | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/crates/editor/src/display_map/tab_map.rs b/crates/editor/src/display_map/tab_map.rs index c0a0fc64b0ebc12332d14e6fe0f432108ae84158..2ceba96a8266a59998e17b0c27c1f70dd65bf490 100644 --- a/crates/editor/src/display_map/tab_map.rs +++ b/crates/editor/src/display_map/tab_map.rs @@ -467,14 +467,22 @@ mod tests { let text = RandomCharIter::new(&mut rng).take(len).collect::(); Buffer::new(0, text, cx) }); - let (_, folds_snapshot) = FoldMap::new(buffer.clone(), cx); - let (_, tabs_snapshot) = TabMap::new(folds_snapshot.clone(), tab_size); - let text = Rope::from(tabs_snapshot.text().as_str()); - log::info!("Tab size: {}", tab_size); log::info!("Buffer text: {:?}", buffer.read(cx).text()); + + let (mut fold_map, _) = FoldMap::new(buffer.clone(), cx); + fold_map.randomly_mutate(&mut rng, cx); + let (folds_snapshot, _) = fold_map.read(cx); log::info!("FoldMap text: {:?}", folds_snapshot.text()); - log::info!("TabMap text: {:?}", tabs_snapshot.text()); - for _ in 0..1 { + + let (_, tabs_snapshot) = TabMap::new(folds_snapshot.clone(), tab_size); + let text = Rope::from(tabs_snapshot.text().as_str()); + log::info!( + "TabMap text (tab size: {}): {:?}", + tab_size, + tabs_snapshot.text(), + ); + + for _ in 0..5 { let end_row = rng.gen_range(0..=text.max_point().row); let end_column = rng.gen_range(0..=text.line_len(end_row)); let mut end = TabPoint(text.clip_point(Point::new(end_row, end_column), Bias::Right)); @@ -490,7 +498,7 @@ mod tests { .chunks_in_range(text.point_to_offset(start.0)..text.point_to_offset(end.0)) .collect::(); let expected_summary = TextSummary::from(expected_text.as_str()); - log::info!("Slicing {:?}..{:?} (text: {:?})", start, end, text); + log::info!("slicing {:?}..{:?} (text: {:?})", start, end, text); assert_eq!( expected_text, tabs_snapshot From 572e571927f779161e496c077e6e49f581045a4c Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 18 Nov 2021 10:41:47 +0100 Subject: [PATCH 084/109] Test longest row only when tabs are not present or the tab size is 1 This is because the longest row calculation is best-effort at the moment, since this information is not indexed in the `TabMap`. --- crates/editor/src/display_map/tab_map.rs | 16 ++++----- crates/editor/src/display_map/wrap_map.rs | 44 +++++++++++++---------- 2 files changed, 34 insertions(+), 26 deletions(-) diff --git a/crates/editor/src/display_map/tab_map.rs b/crates/editor/src/display_map/tab_map.rs index 2ceba96a8266a59998e17b0c27c1f70dd65bf490..33f8d692a2959e5f3e973c55f7a2c55a1a51fc28 100644 --- a/crates/editor/src/display_map/tab_map.rs +++ b/crates/editor/src/display_map/tab_map.rs @@ -506,14 +506,14 @@ mod tests { .map(|c| c.text) .collect::() ); - assert_eq!( - TextSummary { - longest_row: expected_summary.longest_row, - longest_row_chars: expected_summary.longest_row_chars, - ..tabs_snapshot.text_summary_for_range(start..end) - }, - expected_summary, - ); + + let mut actual_summary = tabs_snapshot.text_summary_for_range(start..end); + if tab_size > 1 && folds_snapshot.text().contains('\t') { + actual_summary.longest_row = expected_summary.longest_row; + actual_summary.longest_row_chars = expected_summary.longest_row_chars; + } + + assert_eq!(actual_summary, expected_summary,); } } } diff --git a/crates/editor/src/display_map/wrap_map.rs b/crates/editor/src/display_map/wrap_map.rs index b2ad95788238e07ba6b968fe911c088e62e27d8a..6e14df4dbb3e8d224facd217e913b3be208fcc13 100644 --- a/crates/editor/src/display_map/wrap_map.rs +++ b/crates/editor/src/display_map/wrap_map.rs @@ -1214,26 +1214,34 @@ mod tests { log::info!("{} summary: {:?}", ix, item.summary.output,); } - let mut expected_longest_rows = Vec::new(); - let mut longest_line_len = -1; - for (row, line) in expected_text.split('\n').enumerate() { - let line_char_count = line.chars().count() as isize; - if line_char_count > longest_line_len { - expected_longest_rows.clear(); - longest_line_len = line_char_count; - } - if line_char_count >= longest_line_len { - expected_longest_rows.push(row as u32); + if tab_size == 1 + || !wrapped_snapshot + .tab_snapshot + .fold_snapshot + .text() + .contains('\t') + { + let mut expected_longest_rows = Vec::new(); + let mut longest_line_len = -1; + for (row, line) in expected_text.split('\n').enumerate() { + let line_char_count = line.chars().count() as isize; + if line_char_count > longest_line_len { + expected_longest_rows.clear(); + longest_line_len = line_char_count; + } + if line_char_count >= longest_line_len { + expected_longest_rows.push(row as u32); + } } - } - assert!( - expected_longest_rows.contains(&actual_longest_row), - "incorrect longest row {}. expected {:?} with length {}", - actual_longest_row, - expected_longest_rows, - longest_line_len, - ) + assert!( + expected_longest_rows.contains(&actual_longest_row), + "incorrect longest row {}. expected {:?} with length {}", + actual_longest_row, + expected_longest_rows, + longest_line_len, + ) + } } } From b80887dabe64df5f2d2f71eb74a4b502802679e5 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 18 Nov 2021 10:49:08 +0100 Subject: [PATCH 085/109] Don't insert blocks within multi-byte characters in randomized test --- crates/editor/src/display_map/block_map.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index ef7e262136b8ddac8df277664e0125175649feda..26aa2e193b3097631a1c1ef6834d9e3766b78950 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -1146,7 +1146,9 @@ mod tests { let block_properties = (0..block_count) .map(|_| { let buffer = buffer.read(cx); - let position = buffer.anchor_before(rng.gen_range(0..=buffer.len())); + let position = buffer.anchor_before( + buffer.clip_offset(rng.gen_range(0..=buffer.len()), Bias::Left), + ); let len = rng.gen_range(0..10); let mut text = Rope::from( From 08e0444ee4b15d8c24c9f44a8a70b6e436992616 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 18 Nov 2021 11:01:12 +0100 Subject: [PATCH 086/109] Use char count instead of byte count to determine longest row --- crates/editor/src/display_map/block_map.rs | 8 +++----- crates/editor/src/display_map/wrap_map.rs | 7 +++++++ 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index 26aa2e193b3097631a1c1ef6834d9e3766b78950..e41afffe3c3c6d33ce320543db70a243d433201f 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -528,16 +528,14 @@ impl BlockSnapshot { pub fn longest_row(&self) -> u32 { let input_row = self.wrap_snapshot.longest_row(); - let input_row_len = self.wrap_snapshot.line_len(input_row); + let input_row_chars = self.wrap_snapshot.line_char_count(input_row); let TransformSummary { longest_row_in_block: block_row, - longest_row_in_block_chars: block_row_len, + longest_row_in_block_chars: block_row_chars, .. } = &self.transforms.summary(); - dbg!(block_row, block_row_len, input_row, input_row_len); - - if *block_row_len > input_row_len { + if *block_row_chars > input_row_chars { *block_row } else { self.to_block_point(WrapPoint::new(input_row, 0)).row diff --git a/crates/editor/src/display_map/wrap_map.rs b/crates/editor/src/display_map/wrap_map.rs index 6e14df4dbb3e8d224facd217e913b3be208fcc13..a30101a0038215e5db1925f01efb895c4f78853a 100644 --- a/crates/editor/src/display_map/wrap_map.rs +++ b/crates/editor/src/display_map/wrap_map.rs @@ -674,6 +674,13 @@ impl Snapshot { len as u32 } + pub fn line_char_count(&self, row: u32) -> u32 { + self.text_chunks(row) + .flat_map(|c| c.chars()) + .take_while(|c| *c != '\n') + .count() as u32 + } + pub fn soft_wrap_indent(&self, row: u32) -> Option { let mut cursor = self.transforms.cursor::(); cursor.seek(&WrapPoint::new(row + 1, 0), Bias::Right, &()); From 856768a43c541039b7057864c879824380140e8b Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 18 Nov 2021 11:13:40 +0100 Subject: [PATCH 087/109] Remove aggressive logging from `WrapMap` update code paths --- crates/editor/src/display_map.rs | 2 +- crates/editor/src/display_map/wrap_map.rs | 95 ----------------------- 2 files changed, 1 insertion(+), 96 deletions(-) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index f5cdb4b157f202dca75e2c223849cb89dffe6151..33f166aee6080e4efd1afb0fe37a1254bccb9bbf 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -346,7 +346,7 @@ impl DisplayMapSnapshot { } pub fn longest_row(&self) -> u32 { - self.wraps_snapshot.longest_row() + self.blocks_snapshot.longest_row() } } diff --git a/crates/editor/src/display_map/wrap_map.rs b/crates/editor/src/display_map/wrap_map.rs index a30101a0038215e5db1925f01efb895c4f78853a..4efe1ce47b8cf18bb04a63c7f48d6b40e957c950 100644 --- a/crates/editor/src/display_map/wrap_map.rs +++ b/crates/editor/src/display_map/wrap_map.rs @@ -299,14 +299,6 @@ impl Snapshot { } fn interpolate(&mut self, new_tab_snapshot: TabSnapshot, tab_edits: &[TabEdit]) -> Patch { - log::info!("INTERPOLATING"); - - log::info!("updating transforms... old transforms are:"); - for transform in self.transforms.items(&()) { - log::info!(" - i {:?}", transform.summary.input); - log::info!(" o {:?}", transform.summary.output); - } - let mut new_transforms; if tab_edits.is_empty() { new_transforms = self.transforms.clone(); @@ -320,26 +312,12 @@ impl Snapshot { &(), ); - log::info!("sliced, new_transforms are:"); - for transform in new_transforms.items(&()) { - log::info!(" - i {:?}", transform.summary.input); - log::info!(" o {:?}", transform.summary.output); - } - while let Some(edit) = tab_edits_iter.next() { - log::info!("processing edit {:?}", edit); - if edit.new_lines.start > TabPoint::from(new_transforms.summary().input.lines) { let summary = new_tab_snapshot.text_summary_for_range( TabPoint::from(new_transforms.summary().input.lines)..edit.new_lines.start, ); - log::info!("pushing prefix before edit: {:?}", summary); new_transforms.push_or_extend(Transform::isomorphic(summary)); - log::info!("new transforms are now:"); - for transform in new_transforms.items(&()) { - log::info!(" - i {:?}", transform.summary.input); - log::info!(" o {:?}", transform.summary.output); - } } if !edit.new_lines.is_empty() { @@ -348,12 +326,6 @@ impl Snapshot { )); } - log::info!("pushed summary within edit new range; new transforms now:"); - for transform in new_transforms.items(&()) { - log::info!(" - i {:?}", transform.summary.input); - log::info!(" o {:?}", transform.summary.output); - } - old_cursor.seek_forward(&edit.old_lines.end, Bias::Right, &()); if let Some(next_edit) = tab_edits_iter.peek() { if next_edit.old_lines.start > old_cursor.end(&()) { @@ -364,45 +336,21 @@ impl Snapshot { new_transforms.push_or_extend(Transform::isomorphic(summary)); } - log::info!("pushed transform suffix after edit; new transforms now:"); - for transform in new_transforms.items(&()) { - log::info!(" - i {:?}", transform.summary.input); - log::info!(" o {:?}", transform.summary.output); - } - old_cursor.next(&()); new_transforms.push_tree( old_cursor.slice(&next_edit.old_lines.start, Bias::Right, &()), &(), ); - - log::info!("pushed tree suffix after edit; new transforms now:"); - for transform in new_transforms.items(&()) { - log::info!(" - i {:?}", transform.summary.input); - log::info!(" o {:?}", transform.summary.output); - } } } else { - log::info!("no more edits"); if old_cursor.end(&()) > edit.old_lines.end { let summary = self .tab_snapshot .text_summary_for_range(edit.old_lines.end..old_cursor.end(&())); new_transforms.push_or_extend(Transform::isomorphic(summary)); - - log::info!("pushed transform suffix after edit; new transforms now:"); - for transform in new_transforms.items(&()) { - log::info!(" - i {:?}", transform.summary.input); - log::info!(" o {:?}", transform.summary.output); - } } old_cursor.next(&()); new_transforms.push_tree(old_cursor.suffix(&()), &()); - log::info!("pushed suffix:"); - for transform in new_transforms.items(&()) { - log::info!(" - i {:?}", transform.summary.input); - log::info!(" o {:?}", transform.summary.output); - } } } } @@ -432,12 +380,6 @@ impl Snapshot { new_rows: Range, } - log::info!("updating transforms... old transforms are:"); - for transform in self.transforms.items(&()) { - log::info!(" - i {:?}", transform.summary.input); - log::info!(" o {:?}", transform.summary.output); - } - let mut tab_edits_iter = tab_edits.into_iter().peekable(); let mut row_edits = Vec::new(); while let Some(edit) = tab_edits_iter.next() { @@ -459,11 +401,6 @@ impl Snapshot { row_edits.push(row_edit); } - log::info!("row edits are:"); - for edit in &row_edits { - log::info!(" {:?}", edit); - } - let mut new_transforms; if row_edits.is_empty() { new_transforms = self.transforms.clone(); @@ -477,12 +414,6 @@ impl Snapshot { &(), ); - log::info!("sliced a prefix:"); - for transform in new_transforms.items(&()) { - log::info!(" - i {:?}", transform.summary.input); - log::info!(" o {:?}", transform.summary.output); - } - while let Some(edit) = row_edits.next() { if edit.new_rows.start > new_transforms.summary().input.lines.row { let summary = new_tab_snapshot.text_summary_for_range( @@ -536,15 +467,9 @@ impl Snapshot { } let mut edit_transforms = edit_transforms.into_iter(); - log::info!("extending tree with edit transforms"); if let Some(transform) = edit_transforms.next() { - log::info!( - "push or extend with first transform: {:?}", - transform.summary.output - ); new_transforms.push_or_extend(transform); } - log::info!("extending with remaining transforms"); new_transforms.extend(edit_transforms, &()); old_cursor.seek_forward(&TabPoint::new(edit.old_rows.end, 0), Bias::Right, &()); @@ -943,17 +868,10 @@ impl sum_tree::Item for Transform { } fn push_isomorphic(transforms: &mut Vec, summary: TextSummary) { - log::info!("push_isomorphic: {:?}", summary); if let Some(last_transform) = transforms.last_mut() { if last_transform.is_isomorphic() { last_transform.summary.input += &summary; last_transform.summary.output += &summary; - - log::info!( - " extended previous isomorphic: {:?}", - last_transform.summary.output - ); - return; } } @@ -970,28 +888,15 @@ impl SumTreeExt for SumTree { self.update_last( |last_transform| { if last_transform.is_isomorphic() && transform.as_ref().unwrap().is_isomorphic() { - // log::info!("extending last transform in tree"); - // log::info!( - // " extending with: {:?}", - // transform.as_ref().map(|t| t.summary.output.clone()), - // ); - // log::info!(" last transform was: {:?}", last_transform.summary.output); - let transform = transform.take().unwrap(); last_transform.summary.input += &transform.summary.input; last_transform.summary.output += &transform.summary.output; - - // log::info!( - // " last transform is now {:?}", - // last_transform.summary.output, - // ) } }, &(), ); if let Some(transform) = transform { - log::info!("!!!!!!!!!!! push transform: {:?}", transform.summary.output,); self.push(transform, &()); } } From 0f1eb3dd2e9dc5a673d575203cf88f28f4e39733 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 18 Nov 2021 13:45:06 +0100 Subject: [PATCH 088/109] Skip block lines when moving up and down Co-Authored-By: Nathan Sobo --- crates/editor/src/display_map.rs | 4 +++ crates/editor/src/display_map/block_map.rs | 6 ++++ crates/editor/src/movement.rs | 32 +++++++++++++++------- 3 files changed, 32 insertions(+), 10 deletions(-) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 33f166aee6080e4efd1afb0fe37a1254bccb9bbf..5d90c36d303d3358c5a11d70d8d6539f187817af 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -302,6 +302,10 @@ impl DisplayMapSnapshot { self.folds_snapshot.is_line_folded(tab_point.row()) } + pub fn is_block_line(&self, display_row: u32) -> bool { + self.blocks_snapshot.is_block_line(display_row) + } + pub fn soft_wrap_indent(&self, display_row: u32) -> Option { let wrap_row = self .blocks_snapshot diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index e41afffe3c3c6d33ce320543db70a243d433201f..f9731662eef9aa4c1f250667f68bc457df3e120f 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -558,6 +558,12 @@ impl BlockSnapshot { } } + pub fn is_block_line(&self, row: u32) -> bool { + let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>(); + cursor.seek(&BlockRow(row), Bias::Right, &()); + cursor.item().map_or(false, |t| t.block.is_some()) + } + pub fn clip_point(&self, point: BlockPoint, bias: Bias) -> BlockPoint { let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>(); cursor.seek(&BlockRow(point.row), Bias::Right, &()); diff --git a/crates/editor/src/movement.rs b/crates/editor/src/movement.rs index 7ad1374f8efec7bad889d5500a2318de91c3170c..c6190fec294e54c917ed202d11615618e1b07510 100644 --- a/crates/editor/src/movement.rs +++ b/crates/editor/src/movement.rs @@ -33,11 +33,17 @@ pub fn up( map.column_to_chars(point.row(), point.column()) }; - if point.row() > 0 { - *point.row_mut() -= 1; - *point.column_mut() = map.column_from_chars(point.row(), goal_column); - } else { - point = DisplayPoint::new(0, 0); + loop { + if point.row() > 0 { + *point.row_mut() -= 1; + *point.column_mut() = map.column_from_chars(point.row(), goal_column); + if !map.is_block_line(point.row()) { + break; + } + } else { + point = DisplayPoint::new(0, 0); + break; + } } let clip_bias = if point.column() == map.line_len(point.row()) { @@ -64,11 +70,17 @@ pub fn down( map.column_to_chars(point.row(), point.column()) }; - if point.row() < max_point.row() { - *point.row_mut() += 1; - *point.column_mut() = map.column_from_chars(point.row(), goal_column); - } else { - point = max_point; + loop { + if point.row() < max_point.row() { + *point.row_mut() += 1; + *point.column_mut() = map.column_from_chars(point.row(), goal_column); + if !map.is_block_line(point.row()) { + break; + } + } else { + point = max_point; + break; + } } let clip_bias = if point.column() == map.line_len(point.row()) { From 0a6293bcda5e6d4027699e90b5151efcdd16e0f8 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 18 Nov 2021 14:08:21 +0100 Subject: [PATCH 089/109] Support highlighting in blocks Co-Authored-By: Nathan Sobo --- crates/editor/src/display_map.rs | 67 ++++++++++++---------- crates/editor/src/display_map/block_map.rs | 19 +++--- crates/editor/src/display_map/fold_map.rs | 23 ++++---- crates/editor/src/display_map/tab_map.rs | 21 ++++--- crates/editor/src/display_map/wrap_map.rs | 11 ++-- crates/editor/src/element.rs | 8 +-- crates/editor/src/lib.rs | 10 +++- crates/gpui/src/fonts.rs | 2 +- crates/language/src/lib.rs | 29 ++++++---- crates/language/src/tests.rs | 2 +- 10 files changed, 105 insertions(+), 87 deletions(-) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 5d90c36d303d3358c5a11d70d8d6539f187817af..61fe1fa00a36069cf26f8d729441406597facdba 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -12,6 +12,7 @@ use language::{Anchor, Buffer, Point, ToOffset, ToPoint}; use std::{collections::HashSet, ops::Range}; use sum_tree::Bias; use tab_map::TabMap; +use theme::SyntaxTheme; use wrap_map::WrapMap; pub use block_map::{BlockDisposition, BlockProperties, BufferRows, Chunks}; @@ -230,12 +231,16 @@ impl DisplayMapSnapshot { pub fn text_chunks(&self, display_row: u32) -> impl Iterator { self.blocks_snapshot - .chunks(display_row..self.max_point().row() + 1, false) + .chunks(display_row..self.max_point().row() + 1, None) .map(|h| h.text) } - pub fn chunks(&mut self, display_rows: Range) -> block_map::Chunks { - self.blocks_snapshot.chunks(display_rows, true) + pub fn chunks<'a>( + &'a self, + display_rows: Range, + theme: Option<&'a SyntaxTheme>, + ) -> block_map::Chunks<'a> { + self.blocks_snapshot.chunks(display_rows, theme) } pub fn chars_at<'a>(&'a self, point: DisplayPoint) -> impl Iterator + 'a { @@ -736,8 +741,8 @@ mod tests { .unindent(); let theme = SyntaxTheme::new(vec![ - ("mod.body".to_string(), Color::from_u32(0xff0000ff).into()), - ("fn.name".to_string(), Color::from_u32(0x00ff00ff).into()), + ("mod.body".to_string(), Color::red().into()), + ("fn.name".to_string(), Color::blue().into()), ]); let lang = Arc::new( Language::new( @@ -776,19 +781,19 @@ mod tests { cx.update(|cx| chunks(0..5, &map, &theme, cx)), vec![ ("fn ".to_string(), None), - ("outer".to_string(), Some("fn.name")), + ("outer".to_string(), Some(Color::blue())), ("() {}\n\nmod module ".to_string(), None), - ("{\n fn ".to_string(), Some("mod.body")), - ("inner".to_string(), Some("fn.name")), - ("() {}\n}".to_string(), Some("mod.body")), + ("{\n fn ".to_string(), Some(Color::red())), + ("inner".to_string(), Some(Color::blue())), + ("() {}\n}".to_string(), Some(Color::red())), ] ); assert_eq!( cx.update(|cx| chunks(3..5, &map, &theme, cx)), vec![ - (" fn ".to_string(), Some("mod.body")), - ("inner".to_string(), Some("fn.name")), - ("() {}\n}".to_string(), Some("mod.body")), + (" fn ".to_string(), Some(Color::red())), + ("inner".to_string(), Some(Color::blue())), + ("() {}\n}".to_string(), Some(Color::red())), ] ); @@ -799,11 +804,11 @@ mod tests { cx.update(|cx| chunks(0..2, &map, &theme, cx)), vec![ ("fn ".to_string(), None), - ("out".to_string(), Some("fn.name")), + ("out".to_string(), Some(Color::blue())), ("…".to_string(), None), - (" fn ".to_string(), Some("mod.body")), - ("inner".to_string(), Some("fn.name")), - ("() {}\n}".to_string(), Some("mod.body")), + (" fn ".to_string(), Some(Color::red())), + ("inner".to_string(), Some(Color::blue())), + ("() {}\n}".to_string(), Some(Color::red())), ] ); } @@ -823,8 +828,8 @@ mod tests { .unindent(); let theme = SyntaxTheme::new(vec![ - ("mod.body".to_string(), Color::from_u32(0xff0000ff).into()), - ("fn.name".to_string(), Color::from_u32(0x00ff00ff).into()), + ("mod.body".to_string(), Color::red().into()), + ("fn.name".to_string(), Color::blue().into()), ]); let lang = Arc::new( Language::new( @@ -864,7 +869,7 @@ mod tests { cx.update(|cx| chunks(0..5, &map, &theme, cx)), [ ("fn \n".to_string(), None), - ("oute\nr".to_string(), Some("fn.name")), + ("oute\nr".to_string(), Some(Color::blue())), ("() \n{}\n\n".to_string(), None), ] ); @@ -879,10 +884,10 @@ mod tests { assert_eq!( cx.update(|cx| chunks(1..4, &map, &theme, cx)), [ - ("out".to_string(), Some("fn.name")), + ("out".to_string(), Some(Color::blue())), ("…\n".to_string(), None), - (" \nfn ".to_string(), Some("mod.body")), - ("i\n".to_string(), Some("fn.name")) + (" \nfn ".to_string(), Some(Color::red())), + ("i\n".to_string(), Some(Color::blue())) ] ); } @@ -1018,19 +1023,19 @@ mod tests { map: &ModelHandle, theme: &'a SyntaxTheme, cx: &mut MutableAppContext, - ) -> Vec<(String, Option<&'a str>)> { - let mut snapshot = map.update(cx, |map, cx| map.snapshot(cx)); - let mut chunks: Vec<(String, Option<&str>)> = Vec::new(); - for chunk in snapshot.chunks(rows) { - let style_name = chunk.highlight_id.name(theme); - if let Some((last_chunk, last_style_name)) = chunks.last_mut() { - if style_name == *last_style_name { + ) -> Vec<(String, Option)> { + let snapshot = map.update(cx, |map, cx| map.snapshot(cx)); + let mut chunks: Vec<(String, Option)> = Vec::new(); + for chunk in snapshot.chunks(rows, Some(theme)) { + let color = chunk.highlight_style.map(|s| s.color); + if let Some((last_chunk, last_color)) = chunks.last_mut() { + if color == *last_color { last_chunk.push_str(chunk.text); } else { - chunks.push((chunk.text.to_string(), style_name)); + chunks.push((chunk.text.to_string(), color)); } } else { - chunks.push((chunk.text.to_string(), style_name)); + chunks.push((chunk.text.to_string(), color)); } } chunks diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index f9731662eef9aa4c1f250667f68bc457df3e120f..c95520a41d7b872c5ea3ddbea1c05354adf73a23 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -15,6 +15,7 @@ use std::{ }, }; use sum_tree::SumTree; +use theme::SyntaxTheme; pub struct BlockMap { buffer: ModelHandle, @@ -459,12 +460,12 @@ impl<'a> BlockMapWriter<'a> { impl BlockSnapshot { #[cfg(test)] fn text(&mut self) -> String { - self.chunks(0..self.transforms.summary().output_rows, false) + self.chunks(0..self.transforms.summary().output_rows, None) .map(|chunk| chunk.text) .collect() } - pub fn chunks(&self, rows: Range, highlights: bool) -> Chunks { + pub fn chunks<'a>(&'a self, rows: Range, theme: Option<&'a SyntaxTheme>) -> Chunks<'a> { let max_output_row = cmp::min(rows.end, self.transforms.summary().output_rows); let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>(); let input_end = { @@ -492,9 +493,7 @@ impl BlockSnapshot { cursor.start().1 .0 + overshoot }; Chunks { - input_chunks: self - .wrap_snapshot - .chunks(input_start..input_end, highlights), + input_chunks: self.wrap_snapshot.chunks(input_start..input_end, theme), input_chunk: Default::default(), block_chunks: None, transforms: cursor, @@ -785,9 +784,9 @@ impl<'a> Iterator for BlockChunks<'a> { let chunk = self.chunk?; let mut chunk_len = chunk.len(); - // let mut highlight_style = None; - if let Some((run_len, _)) = self.runs.peek() { - // highlight_style = Some(style.clone()); + let mut highlight_style = None; + if let Some((run_len, style)) = self.runs.peek() { + highlight_style = Some(style.clone()); let run_end_in_chunk = self.run_start + run_len - self.offset; if run_end_in_chunk <= chunk_len { chunk_len = run_end_in_chunk; @@ -806,7 +805,7 @@ impl<'a> Iterator for BlockChunks<'a> { Some(Chunk { text: chunk, - highlight_id: Default::default(), + highlight_style, diagnostic: None, }) } @@ -1314,7 +1313,7 @@ mod tests { for start_row in 0..expected_row_count { let expected_text = expected_lines[start_row..].join("\n"); let actual_text = blocks_snapshot - .chunks(start_row as u32..expected_row_count as u32, false) + .chunks(start_row as u32..expected_row_count as u32, None) .map(|chunk| chunk.text) .collect::(); assert_eq!( diff --git a/crates/editor/src/display_map/fold_map.rs b/crates/editor/src/display_map/fold_map.rs index 2cb0f215bf003b7ded38a803b23a8f2cd19a2b0e..26d3ff3a7d559e7959de5cb105e47daeeb03059e 100644 --- a/crates/editor/src/display_map/fold_map.rs +++ b/crates/editor/src/display_map/fold_map.rs @@ -1,7 +1,5 @@ use gpui::{AppContext, ModelHandle}; -use language::{ - Anchor, AnchorRangeExt, Buffer, Chunk, HighlightId, Point, PointUtf16, TextSummary, ToOffset, -}; +use language::{Anchor, AnchorRangeExt, Buffer, Chunk, Point, PointUtf16, TextSummary, ToOffset}; use parking_lot::Mutex; use std::{ cmp::{self, Ordering}, @@ -10,6 +8,7 @@ use std::{ sync::atomic::{AtomicUsize, Ordering::SeqCst}, }; use sum_tree::{Bias, Cursor, FilterCursor, SumTree}; +use theme::SyntaxTheme; pub trait ToFoldPoint { fn to_fold_point(&self, snapshot: &Snapshot, bias: Bias) -> FoldPoint; @@ -498,7 +497,7 @@ pub struct Snapshot { impl Snapshot { #[cfg(test)] pub fn text(&self) -> String { - self.chunks(FoldOffset(0)..self.len(), false) + self.chunks(FoldOffset(0)..self.len(), None) .map(|c| c.text) .collect() } @@ -630,11 +629,15 @@ impl Snapshot { pub fn chars_at(&self, start: FoldPoint) -> impl '_ + Iterator { let start = start.to_offset(self); - self.chunks(start..self.len(), false) + self.chunks(start..self.len(), None) .flat_map(|chunk| chunk.text.chars()) } - pub fn chunks(&self, range: Range, enable_highlights: bool) -> Chunks { + pub fn chunks<'a>( + &'a self, + range: Range, + theme: Option<&'a SyntaxTheme>, + ) -> Chunks<'a> { let mut transform_cursor = self.transforms.cursor::<(FoldOffset, usize)>(); transform_cursor.seek(&range.end, Bias::Right, &()); @@ -647,9 +650,7 @@ impl Snapshot { Chunks { transform_cursor, - buffer_chunks: self - .buffer_snapshot - .chunks(buffer_start..buffer_end, enable_highlights), + buffer_chunks: self.buffer_snapshot.chunks(buffer_start..buffer_end, theme), buffer_chunk: None, buffer_offset: buffer_start, output_offset: range.start.0, @@ -974,7 +975,7 @@ impl<'a> Iterator for Chunks<'a> { self.output_offset += output_text.len(); return Some(Chunk { text: output_text, - highlight_id: HighlightId::default(), + highlight_style: None, diagnostic: None, }); } @@ -1384,7 +1385,7 @@ mod tests { log::info!("slicing {:?}..{:?} (text: {:?})", start, end, text); assert_eq!( snapshot - .chunks(start..end, false) + .chunks(start..end, None) .map(|c| c.text) .collect::(), text, diff --git a/crates/editor/src/display_map/tab_map.rs b/crates/editor/src/display_map/tab_map.rs index 33f8d692a2959e5f3e973c55f7a2c55a1a51fc28..675ce9132ebdf90e786a95bc1c7c97615cd78ff3 100644 --- a/crates/editor/src/display_map/tab_map.rs +++ b/crates/editor/src/display_map/tab_map.rs @@ -4,6 +4,7 @@ use language::{rope, Chunk}; use parking_lot::Mutex; use std::{cmp, mem, ops::Range}; use sum_tree::Bias; +use theme::SyntaxTheme; pub struct TabMap(Mutex); @@ -33,7 +34,7 @@ impl TabMap { let mut delta = 0; for chunk in old_snapshot .fold_snapshot - .chunks(fold_edit.old_bytes.end..max_offset, false) + .chunks(fold_edit.old_bytes.end..max_offset, None) { let patterns: &[_] = &['\t', '\n']; if let Some(ix) = chunk.text.find(patterns) { @@ -116,7 +117,7 @@ impl Snapshot { self.max_point() }; for c in self - .chunks(range.start..line_end, false) + .chunks(range.start..line_end, None) .flat_map(|chunk| chunk.text.chars()) { if c == '\n' { @@ -130,7 +131,7 @@ impl Snapshot { last_line_chars = first_line_chars; } else { for _ in self - .chunks(TabPoint::new(range.end.row(), 0)..range.end, false) + .chunks(TabPoint::new(range.end.row(), 0)..range.end, None) .flat_map(|chunk| chunk.text.chars()) { last_line_chars += 1; @@ -150,7 +151,11 @@ impl Snapshot { self.fold_snapshot.version } - pub fn chunks(&self, range: Range, highlights: bool) -> Chunks { + pub fn chunks<'a>( + &'a self, + range: Range, + theme: Option<&'a SyntaxTheme>, + ) -> Chunks<'a> { let (input_start, expanded_char_column, to_next_stop) = self.to_fold_point(range.start, Bias::Left); let input_start = input_start.to_offset(&self.fold_snapshot); @@ -165,9 +170,7 @@ impl Snapshot { }; Chunks { - fold_chunks: self - .fold_snapshot - .chunks(input_start..input_end, highlights), + fold_chunks: self.fold_snapshot.chunks(input_start..input_end, theme), column: expanded_char_column, output_position: range.start.0, max_output_position: range.end.0, @@ -186,7 +189,7 @@ impl Snapshot { #[cfg(test)] pub fn text(&self) -> String { - self.chunks(TabPoint::zero()..self.max_point(), false) + self.chunks(TabPoint::zero()..self.max_point(), None) .map(|chunk| chunk.text) .collect() } @@ -502,7 +505,7 @@ mod tests { assert_eq!( expected_text, tabs_snapshot - .chunks(start..end, false) + .chunks(start..end, None) .map(|c| c.text) .collect::() ); diff --git a/crates/editor/src/display_map/wrap_map.rs b/crates/editor/src/display_map/wrap_map.rs index 4efe1ce47b8cf18bb04a63c7f48d6b40e957c950..70afe9d892bca1d0de896d25553d5c60cae9f646 100644 --- a/crates/editor/src/display_map/wrap_map.rs +++ b/crates/editor/src/display_map/wrap_map.rs @@ -12,6 +12,7 @@ use lazy_static::lazy_static; use smol::future::yield_now; use std::{collections::VecDeque, mem, ops::Range, time::Duration}; use sum_tree::{Bias, Cursor, SumTree}; +use theme::SyntaxTheme; pub use super::tab_map::TextSummary; pub type Edit = buffer::Edit; @@ -427,7 +428,7 @@ impl Snapshot { let mut remaining = None; let mut chunks = new_tab_snapshot.chunks( TabPoint::new(edit.new_rows.start, 0)..new_tab_snapshot.max_point(), - false, + None, ); let mut edit_transforms = Vec::::new(); for _ in edit.new_rows.start..edit.new_rows.end { @@ -553,11 +554,11 @@ impl Snapshot { } pub fn text_chunks(&self, wrap_row: u32) -> impl Iterator { - self.chunks(wrap_row..self.max_point().row() + 1, false) + self.chunks(wrap_row..self.max_point().row() + 1, None) .map(|h| h.text) } - pub fn chunks(&self, rows: Range, highlights: bool) -> Chunks { + pub fn chunks<'a>(&'a self, rows: Range, theme: Option<&'a SyntaxTheme>) -> Chunks<'a> { let output_start = WrapPoint::new(rows.start, 0); let output_end = WrapPoint::new(rows.end, 0); let mut transforms = self.transforms.cursor::<(WrapPoint, TabPoint)>(); @@ -570,7 +571,7 @@ impl Snapshot { .to_tab_point(output_end) .min(self.tab_snapshot.max_point()); Chunks { - input_chunks: self.tab_snapshot.chunks(input_start..input_end, highlights), + input_chunks: self.tab_snapshot.chunks(input_start..input_end, theme), input_chunk: Default::default(), output_position: output_start, max_output_row: rows.end, @@ -1233,7 +1234,7 @@ mod tests { } let actual_text = self - .chunks(start_row..end_row, false) + .chunks(start_row..end_row, None) .map(|c| c.text) .collect::(); assert_eq!( diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 966396840875ed362556c12443747c44f02e4e24..6e186ef9af88b5f4ee5b8d2df53fe54592b10b5c 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -493,7 +493,7 @@ impl EditorElement { let mut styles = Vec::new(); let mut row = rows.start; let mut line_exceeded_max_len = false; - let chunks = snapshot.chunks(rows.clone()); + let chunks = snapshot.chunks(rows.clone(), Some(&style.syntax)); let newline_chunk = Chunk { text: "\n", @@ -517,10 +517,8 @@ impl EditorElement { } if !line_chunk.is_empty() && !line_exceeded_max_len { - let highlight_style = chunk - .highlight_id - .style(&style.syntax) - .unwrap_or(style.text.clone().into()); + let highlight_style = + chunk.highlight_style.unwrap_or(style.text.clone().into()); // Avoid a lookup if the font properties match the previous ones. let font_id = if highlight_style.font_properties == prev_font_properties { prev_font_id diff --git a/crates/editor/src/lib.rs b/crates/editor/src/lib.rs index ffd9e961bc1cd612a123fdd78a4885663956dace..7d4f05dba68a1cf3b2863174db9ae436568c08be 100644 --- a/crates/editor/src/lib.rs +++ b/crates/editor/src/lib.rs @@ -31,7 +31,7 @@ use std::{ time::Duration, }; use sum_tree::Bias; -use theme::EditorStyle; +use theme::{EditorStyle, SyntaxTheme}; use util::post_inc; const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500); @@ -2709,8 +2709,12 @@ impl Snapshot { self.display_snapshot.buffer_rows(start_row) } - pub fn chunks(&mut self, display_rows: Range) -> display_map::Chunks { - self.display_snapshot.chunks(display_rows) + pub fn chunks<'a>( + &'a self, + display_rows: Range, + theme: Option<&'a SyntaxTheme>, + ) -> display_map::Chunks<'a> { + self.display_snapshot.chunks(display_rows, theme) } pub fn scroll_position(&self) -> Vector2F { diff --git a/crates/gpui/src/fonts.rs b/crates/gpui/src/fonts.rs index b1aae4c9be323aa54bbd23b4ff4aa463e4a0882e..3dbfd660344faabcb8352b9f8fe22f6c36f779a3 100644 --- a/crates/gpui/src/fonts.rs +++ b/crates/gpui/src/fonts.rs @@ -30,7 +30,7 @@ pub struct TextStyle { pub underline: Option, } -#[derive(Clone, Debug, Default)] +#[derive(Copy, Clone, Debug, Default)] pub struct HighlightStyle { pub color: Color, pub font_properties: Properties, diff --git a/crates/language/src/lib.rs b/crates/language/src/lib.rs index 85b720d7097a46c959a9440e671c46fd1ca1663b..e2747504b93349638ee99f61d29cfa4c1bbdd616 100644 --- a/crates/language/src/lib.rs +++ b/crates/language/src/lib.rs @@ -12,7 +12,7 @@ use anyhow::{anyhow, Result}; pub use buffer::{Buffer as TextBuffer, Operation as _, *}; use clock::ReplicaId; use futures::FutureExt as _; -use gpui::{AppContext, Entity, ModelContext, MutableAppContext, Task}; +use gpui::{fonts::HighlightStyle, AppContext, Entity, ModelContext, MutableAppContext, Task}; use lazy_static::lazy_static; use lsp::LanguageServer; use parking_lot::Mutex; @@ -34,6 +34,7 @@ use std::{ time::{Duration, Instant, SystemTime, UNIX_EPOCH}, vec, }; +use theme::SyntaxTheme; use tree_sitter::{InputEdit, Parser, QueryCursor, Tree}; use util::{post_inc, TryFutureExt as _}; @@ -190,6 +191,7 @@ struct Highlights<'a> { next_capture: Option<(tree_sitter::QueryMatch<'a, 'a>, usize)>, stack: Vec<(usize, HighlightId)>, highlight_map: HighlightMap, + theme: &'a SyntaxTheme, _query_cursor: QueryCursorHandle, } @@ -207,7 +209,7 @@ pub struct Chunks<'a> { #[derive(Clone, Copy, Debug, Default)] pub struct Chunk<'a> { pub text: &'a str, - pub highlight_id: HighlightId, + pub highlight_style: Option, pub diagnostic: Option, } @@ -1634,12 +1636,16 @@ impl Snapshot { .all(|chunk| chunk.matches(|c: char| !c.is_whitespace()).next().is_none()) } - pub fn chunks(&self, range: Range, highlight: bool) -> Chunks { + pub fn chunks<'a, T: ToOffset>( + &'a self, + range: Range, + theme: Option<&'a SyntaxTheme>, + ) -> Chunks<'a> { let range = range.start.to_offset(&*self)..range.end.to_offset(&*self); let mut highlights = None; let mut diagnostic_endpoints = Vec::::new(); - if highlight { + if let Some(theme) = theme { for (_, range, diagnostic) in self.diagnostics .intersecting_ranges(range.clone(), self.content(), true) @@ -1676,6 +1682,7 @@ impl Snapshot { stack: Default::default(), highlight_map: language.highlight_map(), _query_cursor: query_cursor, + theme, }) } } @@ -1845,12 +1852,12 @@ impl<'a> Iterator for Chunks<'a> { let mut chunk_end = (self.chunks.offset() + chunk.len()) .min(next_capture_start) .min(next_diagnostic_endpoint); - let mut highlight_id = HighlightId::default(); - if let Some((parent_capture_end, parent_highlight_id)) = - self.highlights.as_ref().and_then(|h| h.stack.last()) - { - chunk_end = chunk_end.min(*parent_capture_end); - highlight_id = *parent_highlight_id; + let mut highlight_style = None; + if let Some(highlights) = self.highlights.as_ref() { + if let Some((parent_capture_end, parent_highlight_id)) = highlights.stack.last() { + chunk_end = chunk_end.min(*parent_capture_end); + highlight_style = parent_highlight_id.style(highlights.theme); + } } let slice = @@ -1862,7 +1869,7 @@ impl<'a> Iterator for Chunks<'a> { Some(Chunk { text: slice, - highlight_id, + highlight_style, diagnostic: self.current_diagnostic_severity(), }) } else { diff --git a/crates/language/src/tests.rs b/crates/language/src/tests.rs index 23bb2b8589bacfca1fb22fca9dc0c29169a994d3..c73b29224fa783671a9e4d63130e9e2ed804d515 100644 --- a/crates/language/src/tests.rs +++ b/crates/language/src/tests.rs @@ -906,7 +906,7 @@ fn chunks_with_diagnostics( range: Range, ) -> Vec<(String, Option)> { let mut chunks: Vec<(String, Option)> = Vec::new(); - for chunk in buffer.snapshot().chunks(range, true) { + for chunk in buffer.snapshot().chunks(range, Some(&Default::default())) { if chunks .last() .map_or(false, |prev_chunk| prev_chunk.1 == chunk.diagnostic) From 401b59be5c420249f66b21325f2e0fe9f19c0996 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 18 Nov 2021 15:04:12 +0100 Subject: [PATCH 090/109] Refactor retrieving oldest and newest selection Co-Authored-By: Nathan Sobo --- crates/buffer/src/anchor.rs | 60 +++++++++++++++++++++++++ crates/buffer/src/selection.rs | 32 ++++++++++++++ crates/editor/src/lib.rs | 81 +++++++++++++++++++++++----------- crates/workspace/src/items.rs | 6 +-- 4 files changed, 149 insertions(+), 30 deletions(-) diff --git a/crates/buffer/src/anchor.rs b/crates/buffer/src/anchor.rs index ceee746c6a9ce9d00c54a6965fe81d010ed68042..7b765a948b32392ebf5a489e6b7d0fbdddb5bff3 100644 --- a/crates/buffer/src/anchor.rs +++ b/crates/buffer/src/anchor.rs @@ -194,6 +194,66 @@ impl AnchorRangeMap { .iter() .map(|(range, value)| (range.start.0..range.end.0, value)) } + + pub fn min_by_key<'a, C, D, F, K>( + &self, + content: C, + mut extract_key: F, + ) -> Option<(Range, &T)> + where + C: Into>, + D: 'a + TextDimension<'a>, + F: FnMut(&T) -> K, + K: Ord, + { + let content = content.into(); + self.entries + .iter() + .min_by_key(|(_, value)| extract_key(value)) + .map(|(range, value)| (self.resolve_range(range, &content), value)) + } + + pub fn max_by_key<'a, C, D, F, K>( + &self, + content: C, + mut extract_key: F, + ) -> Option<(Range, &T)> + where + C: Into>, + D: 'a + TextDimension<'a>, + F: FnMut(&T) -> K, + K: Ord, + { + let content = content.into(); + self.entries + .iter() + .max_by_key(|(_, value)| extract_key(value)) + .map(|(range, value)| (self.resolve_range(range, &content), value)) + } + + fn resolve_range<'a, D>( + &self, + range: &Range<(FullOffset, Bias)>, + content: &Content<'a>, + ) -> Range + where + D: 'a + TextDimension<'a>, + { + let (start, start_bias) = range.start; + let mut anchor = Anchor { + full_offset: start, + bias: start_bias, + version: self.version.clone(), + }; + let start = content.summary_for_anchor(&anchor); + + let (end, end_bias) = range.end; + anchor.full_offset = end; + anchor.bias = end_bias; + let end = content.summary_for_anchor(&anchor); + + start..end + } } impl PartialEq for AnchorRangeMap { diff --git a/crates/buffer/src/selection.rs b/crates/buffer/src/selection.rs index dfd4522368067a9c095c9957014f3a0e0c90be84..b90a6fa10579a63aaace2877cf722d40de53e9eb 100644 --- a/crates/buffer/src/selection.rs +++ b/crates/buffer/src/selection.rs @@ -116,4 +116,36 @@ impl SelectionSet { goal: state.goal, }) } + + pub fn oldest_selection<'a, D, C>(&'a self, content: C) -> Option> + where + D: 'a + TextDimension<'a>, + C: 'a + Into>, + { + self.selections + .min_by_key(content, |selection| selection.id) + .map(|(range, state)| Selection { + id: state.id, + start: range.start, + end: range.end, + reversed: state.reversed, + goal: state.goal, + }) + } + + pub fn newest_selection<'a, D, C>(&'a self, content: C) -> Option> + where + D: 'a + TextDimension<'a>, + C: 'a + Into>, + { + self.selections + .max_by_key(content, |selection| selection.id) + .map(|(range, state)| Selection { + id: state.id, + start: range.start, + end: range.end, + reversed: state.reversed, + goal: state.goal, + }) + } } diff --git a/crates/editor/src/lib.rs b/crates/editor/src/lib.rs index 7d4f05dba68a1cf3b2863174db9ae436568c08be..ffaf5e6cf7d573140e5c4af1ecbade17690038d1 100644 --- a/crates/editor/src/lib.rs +++ b/crates/editor/src/lib.rs @@ -707,16 +707,8 @@ impl Editor { self.update_selections(vec![pending_selection], true, cx); } } else { - let selections = self.selections::(cx); - let mut selection_count = 0; - let mut oldest_selection = selections - .min_by_key(|s| { - selection_count += 1; - s.id - }) - .unwrap() - .clone(); - if selection_count == 1 { + let mut oldest_selection = self.oldest_selection::(cx); + if self.selection_count(cx) == 1 { oldest_selection.start = oldest_selection.head().clone(); oldest_selection.end = oldest_selection.head().clone(); } @@ -2294,9 +2286,8 @@ impl Editor { ) -> impl 'a + Iterator> { let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx)); let buffer = self.buffer.read(cx); - let selections = buffer - .selection_set(set_id) - .unwrap() + let selections = self + .selection_set(cx) .selections::(buffer) .collect::>(); let start = range.start.to_point(&display_map); @@ -2343,18 +2334,8 @@ impl Editor { D: 'a + TextDimension<'a> + Ord, { let buffer = self.buffer.read(cx); - let mut selections = buffer - .selection_set(self.selection_set_id) - .unwrap() - .selections::(buffer) - .peekable(); - let mut pending_selection = self.pending_selection.clone().map(|selection| Selection { - id: selection.id, - start: selection.start.summary::(buffer), - end: selection.end.summary::(buffer), - reversed: selection.reversed, - goal: selection.goal, - }); + let mut selections = self.selection_set(cx).selections::(buffer).peekable(); + let mut pending_selection = self.pending_selection(cx); iter::from_fn(move || { if let Some(pending) = pending_selection.as_mut() { while let Some(next_selection) = selections.peek() { @@ -2380,6 +2361,56 @@ impl Editor { }) } + fn pending_selection<'a, D>(&self, cx: &'a AppContext) -> Option> + where + D: 'a + TextDimension<'a>, + { + let buffer = self.buffer.read(cx); + self.pending_selection.as_ref().map(|selection| Selection { + id: selection.id, + start: selection.start.summary::(buffer), + end: selection.end.summary::(buffer), + reversed: selection.reversed, + goal: selection.goal, + }) + } + + fn selection_count<'a>(&self, cx: &'a AppContext) -> usize { + let mut selection_count = self.selection_set(cx).len(); + if self.pending_selection.is_some() { + selection_count += 1; + } + selection_count + } + + pub fn oldest_selection<'a, T>(&self, cx: &'a AppContext) -> Selection + where + T: 'a + TextDimension<'a>, + { + let buffer = self.buffer.read(cx); + self.selection_set(cx) + .oldest_selection(buffer) + .or_else(|| self.pending_selection(cx)) + .unwrap() + } + + pub fn newest_selection<'a, T>(&self, cx: &'a AppContext) -> Selection + where + T: 'a + TextDimension<'a>, + { + let buffer = self.buffer.read(cx); + self.pending_selection(cx) + .or_else(|| self.selection_set(cx).newest_selection(buffer)) + .unwrap() + } + + fn selection_set<'a>(&self, cx: &'a AppContext) -> &'a SelectionSet { + self.buffer + .read(cx) + .selection_set(self.selection_set_id) + .unwrap() + } + fn update_selections( &mut self, mut selections: Vec>, diff --git a/crates/workspace/src/items.rs b/crates/workspace/src/items.rs index 9a14379c7aeb4895d39c7b5ecdc2b7bd363e743b..60e2e9d767a07ce2a25e5f142b7f94fccf3be6ac 100644 --- a/crates/workspace/src/items.rs +++ b/crates/workspace/src/items.rs @@ -258,11 +258,7 @@ impl DiagnosticMessage { fn update(&mut self, editor: ViewHandle, cx: &mut ViewContext) { let editor = editor.read(cx); - let cursor_position = editor - .selections::(cx) - .max_by_key(|selection| selection.id) - .unwrap() - .head(); + let cursor_position = editor.newest_selection(cx).head(); let new_diagnostic = editor .buffer() .read(cx) From 0e513657706436cc8ebf1d04cb29fd9e9170f120 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 18 Nov 2021 15:53:00 +0100 Subject: [PATCH 091/109] In a diagnostic group, mark the highest-severity diagnostic as primary Co-Authored-By: Nathan Sobo --- crates/editor/src/lib.rs | 2 +- crates/language/src/lib.rs | 117 +++++++++++++++++++-------------- crates/language/src/proto.rs | 2 + crates/language/src/tests.rs | 36 +++++++--- crates/project/src/worktree.rs | 1 + crates/rpc/proto/zed.proto | 1 + crates/server/src/rpc.rs | 4 +- 7 files changed, 100 insertions(+), 63 deletions(-) diff --git a/crates/editor/src/lib.rs b/crates/editor/src/lib.rs index ffaf5e6cf7d573140e5c4af1ecbade17690038d1..f518253733ffa4bb2a7b095da8eff1248d8c39cf 100644 --- a/crates/editor/src/lib.rs +++ b/crates/editor/src/lib.rs @@ -2205,7 +2205,7 @@ impl Editor { } pub fn show_next_diagnostic(&mut self, _: &ShowNextDiagnostic, cx: &mut ViewContext) { - let selection = self.selections::(cx).last().unwrap(); + let selection = self.newest_selection(cx); let buffer = self.buffer.read(cx.as_ref()); let diagnostic_group_id = dbg!(buffer .diagnostics_in_range::<_, usize>(selection.head()..buffer.len()) diff --git a/crates/language/src/lib.rs b/crates/language/src/lib.rs index e2747504b93349638ee99f61d29cfa4c1bbdd616..f810c75cc4414e4b553ad5025ccb5164ffabd123 100644 --- a/crates/language/src/lib.rs +++ b/crates/language/src/lib.rs @@ -86,6 +86,7 @@ pub struct Diagnostic { pub severity: DiagnosticSeverity, pub message: String, pub group_id: usize, + pub is_primary: bool, } struct LanguageServerState { @@ -717,68 +718,82 @@ impl Buffer { .peekable(); let mut last_edit_old_end = PointUtf16::zero(); let mut last_edit_new_end = PointUtf16::zero(); - let mut groups = HashMap::new(); + let mut group_ids_by_diagnostic_range = HashMap::new(); + let mut diagnostics_by_group_id = HashMap::new(); let mut next_group_id = 0; - - content.anchor_range_multimap( - Bias::Left, - Bias::Right, - diagnostics.iter().filter_map(|diagnostic| { - let mut start = diagnostic.range.start.to_point_utf16(); - let mut end = diagnostic.range.end.to_point_utf16(); - let source = diagnostic.source.as_ref(); - let code = diagnostic.code.as_ref(); - let group_id = diagnostic_ranges(&diagnostic, abs_path.as_deref()) - .find_map(|range| groups.get(&(source, code, range))) - .copied() - .unwrap_or_else(|| { - let group_id = post_inc(&mut next_group_id); - for range in diagnostic_ranges(&diagnostic, abs_path.as_deref()) { - groups.insert((source, code, range), group_id); - } - group_id - }); - - if diagnostic - .source - .as_ref() - .map_or(false, |source| disk_based_sources.contains(source)) - { - while let Some(edit) = edits_since_save.peek() { - if edit.old.end <= start { - last_edit_old_end = edit.old.end; - last_edit_new_end = edit.new.end; - edits_since_save.next(); - } else if edit.old.start <= end && edit.old.end >= start { - return None; - } else { - break; - } + 'outer: for diagnostic in &diagnostics { + let mut start = diagnostic.range.start.to_point_utf16(); + let mut end = diagnostic.range.end.to_point_utf16(); + let source = diagnostic.source.as_ref(); + let code = diagnostic.code.as_ref(); + let group_id = diagnostic_ranges(&diagnostic, abs_path.as_deref()) + .find_map(|range| group_ids_by_diagnostic_range.get(&(source, code, range))) + .copied() + .unwrap_or_else(|| { + let group_id = post_inc(&mut next_group_id); + for range in diagnostic_ranges(&diagnostic, abs_path.as_deref()) { + group_ids_by_diagnostic_range.insert((source, code, range), group_id); + } + group_id + }); + + if diagnostic + .source + .as_ref() + .map_or(false, |source| disk_based_sources.contains(source)) + { + while let Some(edit) = edits_since_save.peek() { + if edit.old.end <= start { + last_edit_old_end = edit.old.end; + last_edit_new_end = edit.new.end; + edits_since_save.next(); + } else if edit.old.start <= end && edit.old.end >= start { + continue 'outer; + } else { + break; } - - start = last_edit_new_end + (start - last_edit_old_end); - end = last_edit_new_end + (end - last_edit_old_end); } - let mut range = content.clip_point_utf16(start, Bias::Left) - ..content.clip_point_utf16(end, Bias::Right); - if range.start == range.end { - range.end.column += 1; - range.end = content.clip_point_utf16(range.end, Bias::Right); - if range.start == range.end && range.end.column > 0 { - range.start.column -= 1; - range.start = content.clip_point_utf16(range.start, Bias::Left); - } + start = last_edit_new_end + (start - last_edit_old_end); + end = last_edit_new_end + (end - last_edit_old_end); + } + + let mut range = content.clip_point_utf16(start, Bias::Left) + ..content.clip_point_utf16(end, Bias::Right); + if range.start == range.end { + range.end.column += 1; + range.end = content.clip_point_utf16(range.end, Bias::Right); + if range.start == range.end && range.end.column > 0 { + range.start.column -= 1; + range.start = content.clip_point_utf16(range.start, Bias::Left); } - Some(( + } + + diagnostics_by_group_id + .entry(group_id) + .or_insert(Vec::new()) + .push(( range, Diagnostic { severity: diagnostic.severity.unwrap_or(DiagnosticSeverity::ERROR), message: diagnostic.message.clone(), group_id, + is_primary: false, }, - )) - }), + )); + } + + content.anchor_range_multimap( + Bias::Left, + Bias::Right, + diagnostics_by_group_id + .into_values() + .flat_map(|mut diagnostics| { + let primary_diagnostic = + diagnostics.iter_mut().min_by_key(|d| d.1.severity).unwrap(); + primary_diagnostic.1.is_primary = true; + diagnostics + }), ) }; diff --git a/crates/language/src/proto.rs b/crates/language/src/proto.rs index bccb965a55ac537536ede541caf19a9ab69e550c..6a259e072637d2d3a2511498fde7a45d343359ea 100644 --- a/crates/language/src/proto.rs +++ b/crates/language/src/proto.rs @@ -142,6 +142,7 @@ pub fn serialize_diagnostics(map: &AnchorRangeMultimap) -> proto::Di _ => proto::diagnostic::Severity::None, } as i32, group_id: diagnostic.group_id as u64, + is_primary: diagnostic.is_primary, }) .collect(), } @@ -310,6 +311,7 @@ pub fn deserialize_diagnostics(message: proto::DiagnosticSet) -> AnchorRangeMult }, message: diagnostic.message, group_id: diagnostic.group_id as usize, + is_primary: diagnostic.is_primary, }, )) }), diff --git a/crates/language/src/tests.rs b/crates/language/src/tests.rs index c73b29224fa783671a9e4d63130e9e2ed804d515..671755195d1b2a537a29234b9dd52d97921562a9 100644 --- a/crates/language/src/tests.rs +++ b/crates/language/src/tests.rs @@ -484,6 +484,7 @@ async fn test_diagnostics(mut cx: gpui::TestAppContext) { severity: DiagnosticSeverity::ERROR, message: "undefined variable 'BB'".to_string(), group_id: 1, + is_primary: true, }, ), ( @@ -492,6 +493,7 @@ async fn test_diagnostics(mut cx: gpui::TestAppContext) { severity: DiagnosticSeverity::ERROR, message: "undefined variable 'CCC'".to_string(), group_id: 2, + is_primary: true, } ) ] @@ -549,6 +551,7 @@ async fn test_diagnostics(mut cx: gpui::TestAppContext) { severity: DiagnosticSeverity::WARNING, message: "unreachable statement".to_string(), group_id: 1, + is_primary: true, } ), ( @@ -557,6 +560,7 @@ async fn test_diagnostics(mut cx: gpui::TestAppContext) { severity: DiagnosticSeverity::ERROR, message: "undefined variable 'A'".to_string(), group_id: 0, + is_primary: true, }, ) ] @@ -626,6 +630,7 @@ async fn test_diagnostics(mut cx: gpui::TestAppContext) { severity: DiagnosticSeverity::ERROR, message: "undefined variable 'A'".to_string(), group_id: 0, + is_primary: true, } ), ( @@ -634,6 +639,7 @@ async fn test_diagnostics(mut cx: gpui::TestAppContext) { severity: DiagnosticSeverity::ERROR, message: "undefined variable 'BB'".to_string(), group_id: 1, + is_primary: true, }, ) ] @@ -808,7 +814,8 @@ async fn test_grouped_diagnostics(mut cx: gpui::TestAppContext) { &Diagnostic { severity: DiagnosticSeverity::WARNING, message: "error 1".to_string(), - group_id: 0 + group_id: 0, + is_primary: true, } ), ( @@ -816,7 +823,8 @@ async fn test_grouped_diagnostics(mut cx: gpui::TestAppContext) { &Diagnostic { severity: DiagnosticSeverity::HINT, message: "error 1 hint 1".to_string(), - group_id: 0 + group_id: 0, + is_primary: false, } ), ( @@ -824,7 +832,8 @@ async fn test_grouped_diagnostics(mut cx: gpui::TestAppContext) { &Diagnostic { severity: DiagnosticSeverity::HINT, message: "error 2 hint 1".to_string(), - group_id: 1 + group_id: 1, + is_primary: false, } ), ( @@ -832,7 +841,8 @@ async fn test_grouped_diagnostics(mut cx: gpui::TestAppContext) { &Diagnostic { severity: DiagnosticSeverity::HINT, message: "error 2 hint 2".to_string(), - group_id: 1 + group_id: 1, + is_primary: false, } ), ( @@ -840,7 +850,8 @@ async fn test_grouped_diagnostics(mut cx: gpui::TestAppContext) { &Diagnostic { severity: DiagnosticSeverity::ERROR, message: "error 2".to_string(), - group_id: 1 + group_id: 1, + is_primary: true, } ) ] @@ -854,7 +865,8 @@ async fn test_grouped_diagnostics(mut cx: gpui::TestAppContext) { &Diagnostic { severity: DiagnosticSeverity::WARNING, message: "error 1".to_string(), - group_id: 0 + group_id: 0, + is_primary: true, } ), ( @@ -862,7 +874,8 @@ async fn test_grouped_diagnostics(mut cx: gpui::TestAppContext) { &Diagnostic { severity: DiagnosticSeverity::HINT, message: "error 1 hint 1".to_string(), - group_id: 0 + group_id: 0, + is_primary: false, } ), ] @@ -875,7 +888,8 @@ async fn test_grouped_diagnostics(mut cx: gpui::TestAppContext) { &Diagnostic { severity: DiagnosticSeverity::HINT, message: "error 2 hint 1".to_string(), - group_id: 1 + group_id: 1, + is_primary: false, } ), ( @@ -883,7 +897,8 @@ async fn test_grouped_diagnostics(mut cx: gpui::TestAppContext) { &Diagnostic { severity: DiagnosticSeverity::HINT, message: "error 2 hint 2".to_string(), - group_id: 1 + group_id: 1, + is_primary: false, } ), ( @@ -891,7 +906,8 @@ async fn test_grouped_diagnostics(mut cx: gpui::TestAppContext) { &Diagnostic { severity: DiagnosticSeverity::ERROR, message: "error 2".to_string(), - group_id: 1 + group_id: 1, + is_primary: true, } ) ] diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 5a9b7d205ed5acf12ba15c6c024ae33dc4917df3..e6b444b5480b21c0deadfef5234356f6b9dd03df 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -3635,6 +3635,7 @@ mod tests { severity: lsp::DiagnosticSeverity::ERROR, message: "undefined variable 'A'".to_string(), group_id: 0, + is_primary: true } )] ) diff --git a/crates/rpc/proto/zed.proto b/crates/rpc/proto/zed.proto index f826c4b03f55b4b46927496b23e0e4f535fbabc8..950178be340c9a0ff54e6722b9306e68f852a7fc 100644 --- a/crates/rpc/proto/zed.proto +++ b/crates/rpc/proto/zed.proto @@ -257,6 +257,7 @@ message Diagnostic { Severity severity = 3; string message = 4; uint64 group_id = 5; + bool is_primary = 6; enum Severity { None = 0; Error = 1; diff --git a/crates/server/src/rpc.rs b/crates/server/src/rpc.rs index d9d45207d054c63f52fe722e4f0e65957a49cf48..0c60ba3cbd1d51190a3fcdd3042e59c04998cf3a 100644 --- a/crates/server/src/rpc.rs +++ b/crates/server/src/rpc.rs @@ -1716,6 +1716,7 @@ mod tests { group_id: 0, message: "message 1".to_string(), severity: lsp::DiagnosticSeverity::ERROR, + is_primary: true } ), ( @@ -1723,7 +1724,8 @@ mod tests { &Diagnostic { group_id: 1, severity: lsp::DiagnosticSeverity::WARNING, - message: "message 2".to_string() + message: "message 2".to_string(), + is_primary: true } ) ] From 643545e91e7b523f076ed146cca032244c9dd1c2 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 18 Nov 2021 16:04:16 +0100 Subject: [PATCH 092/109] When showing the next diagnostic, advance to the next *primary* one Co-Authored-By: Nathan Sobo --- crates/editor/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/editor/src/lib.rs b/crates/editor/src/lib.rs index f518253733ffa4bb2a7b095da8eff1248d8c39cf..f97d6166e25d781b7be4624e00a09a068f843c2f 100644 --- a/crates/editor/src/lib.rs +++ b/crates/editor/src/lib.rs @@ -2209,6 +2209,7 @@ impl Editor { let buffer = self.buffer.read(cx.as_ref()); let diagnostic_group_id = dbg!(buffer .diagnostics_in_range::<_, usize>(selection.head()..buffer.len()) + .filter(|(_, diagnostic)| diagnostic.is_primary) .next()) .map(|(_, diagnostic)| diagnostic.group_id); From 5094380c83b73fadad8fb40359ad5b30bef2d442 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 18 Nov 2021 16:55:18 +0100 Subject: [PATCH 093/109] Enhance keyboard navigation when showing next diagnostic Co-Authored-By: Nathan Sobo --- crates/buffer/src/anchor.rs | 6 ++ crates/editor/src/display_map.rs | 5 +- crates/editor/src/lib.rs | 112 ++++++++++++++++++++++++------- crates/language/src/lib.rs | 4 +- 4 files changed, 99 insertions(+), 28 deletions(-) diff --git a/crates/buffer/src/anchor.rs b/crates/buffer/src/anchor.rs index 7b765a948b32392ebf5a489e6b7d0fbdddb5bff3..4fdd387be0ae841701e5d3311afbc836559c45ca 100644 --- a/crates/buffer/src/anchor.rs +++ b/crates/buffer/src/anchor.rs @@ -527,6 +527,7 @@ impl<'a> sum_tree::SeekTarget<'a, AnchorRangeMultimapSummary, FullOffsetRange> f pub trait AnchorRangeExt { fn cmp<'a>(&self, b: &Range, buffer: impl Into>) -> Result; + fn to_offset<'a>(&self, content: impl Into>) -> Range; } impl AnchorRangeExt for Range { @@ -537,4 +538,9 @@ impl AnchorRangeExt for Range { ord @ _ => ord, }) } + + fn to_offset<'a>(&self, content: impl Into>) -> Range { + let content = content.into(); + self.start.to_offset(&content)..self.end.to_offset(&content) + } } diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 61fe1fa00a36069cf26f8d729441406597facdba..3354d76e6158824fd8aee3a403d647f667081427 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -4,7 +4,8 @@ mod patch; mod tab_map; mod wrap_map; -use block_map::{BlockId, BlockMap, BlockPoint}; +pub use block_map::{BlockDisposition, BlockId, BlockProperties, BufferRows, Chunks}; +use block_map::{BlockMap, BlockPoint}; use buffer::Rope; use fold_map::{FoldMap, ToFoldPoint as _}; use gpui::{fonts::FontId, Entity, ModelContext, ModelHandle}; @@ -15,8 +16,6 @@ use tab_map::TabMap; use theme::SyntaxTheme; use wrap_map::WrapMap; -pub use block_map::{BlockDisposition, BlockProperties, BufferRows, Chunks}; - pub trait ToDisplayPoint { fn to_display_point(&self, map: &DisplayMapSnapshot) -> DisplayPoint; } diff --git a/crates/editor/src/lib.rs b/crates/editor/src/lib.rs index f97d6166e25d781b7be4624e00a09a068f843c2f..2afb18d1defd553720ab08b012a3d1734d35f8bf 100644 --- a/crates/editor/src/lib.rs +++ b/crates/editor/src/lib.rs @@ -24,6 +24,7 @@ use smol::Timer; use std::{ cell::RefCell, cmp::{self, Ordering}, + collections::HashSet, iter, mem, ops::{Range, RangeInclusive}, rc::Rc, @@ -304,6 +305,7 @@ pub struct Editor { add_selections_state: Option, autoclose_stack: Vec, select_larger_syntax_node_stack: Vec]>>, + active_diagnostics: Option, scroll_position: Vector2F, scroll_top_anchor: Anchor, autoscroll_requested: bool, @@ -336,6 +338,12 @@ struct BracketPairState { pair: BracketPair, } +#[derive(Debug)] +struct ActiveDiagnosticGroup { + primary_range: Range, + block_ids: HashSet, +} + #[derive(Serialize, Deserialize)] struct ClipboardSelection { len: usize, @@ -423,6 +431,7 @@ impl Editor { add_selections_state: None, autoclose_stack: Default::default(), select_larger_syntax_node_stack: Vec::new(), + active_diagnostics: None, build_settings, scroll_position: Vector2F::zero(), scroll_top_anchor: Anchor::min(), @@ -2205,36 +2214,93 @@ impl Editor { } pub fn show_next_diagnostic(&mut self, _: &ShowNextDiagnostic, cx: &mut ViewContext) { - let selection = self.newest_selection(cx); + let selection = self.newest_selection::(cx); let buffer = self.buffer.read(cx.as_ref()); - let diagnostic_group_id = dbg!(buffer - .diagnostics_in_range::<_, usize>(selection.head()..buffer.len()) - .filter(|(_, diagnostic)| diagnostic.is_primary) - .next()) - .map(|(_, diagnostic)| diagnostic.group_id); + let active_primary_range = self.active_diagnostics.as_ref().map(|active_diagnostics| { + active_diagnostics + .primary_range + .to_offset(buffer) + .to_inclusive() + }); + let mut search_start = if let Some(active_primary_range) = active_primary_range.as_ref() { + if active_primary_range.contains(&selection.head()) { + *active_primary_range.end() + } else { + selection.head() + } + } else { + selection.head() + }; - if let Some(group_id) = diagnostic_group_id { - self.display_map.update(cx, |display_map, cx| { - let buffer = self.buffer.read(cx); - let diagnostic_group = buffer - .diagnostic_group::(group_id) - .map(|(range, diagnostic)| (range, diagnostic.message.clone())) - .collect::>(); + loop { + let next_group = buffer + .diagnostics_in_range::<_, usize>(search_start..buffer.len()) + .filter(|(_, diagnostic)| diagnostic.is_primary) + .skip_while(|(range, _)| { + Some(range.end) == active_primary_range.as_ref().map(|r| *r.end()) + }) + .next() + .map(|(range, diagnostic)| (range, diagnostic.group_id)); + + if let Some((primary_range, group_id)) = next_group { + self.dismiss_diagnostics(cx); + self.active_diagnostics = self.display_map.update(cx, |display_map, cx| { + let buffer = self.buffer.read(cx); + let diagnostic_group = buffer + .diagnostic_group::(group_id) + .map(|(range, diagnostic)| (range, diagnostic.message.clone())) + .collect::>(); + let primary_range = buffer.anchor_after(primary_range.start) + ..buffer.anchor_before(primary_range.end); + + let block_ids = display_map + .insert_blocks( + diagnostic_group + .iter() + .map(|(range, message)| BlockProperties { + position: range.start, + text: message.as_str(), + runs: vec![], + disposition: BlockDisposition::Above, + }), + cx, + ) + .into_iter() + .collect(); - dbg!(group_id, &diagnostic_group); + Some(ActiveDiagnosticGroup { + primary_range, + block_ids, + }) + }); - display_map.insert_blocks( - diagnostic_group - .iter() - .map(|(range, message)| BlockProperties { - position: range.start, - text: message.as_str(), - runs: vec![], - disposition: BlockDisposition::Above, - }), + self.update_selections( + vec![Selection { + id: selection.id, + start: primary_range.start, + end: primary_range.start, + reversed: false, + goal: SelectionGoal::None, + }], + true, cx, ); + break; + } else if search_start == 0 { + break; + } else { + // Cycle around to the start of the buffer. + search_start = 0; + } + } + } + + fn dismiss_diagnostics(&mut self, cx: &mut ViewContext) { + if let Some(active_diagnostic_group) = self.active_diagnostics.take() { + self.display_map.update(cx, |display_map, cx| { + display_map.remove_blocks(active_diagnostic_group.block_ids, cx); }); + cx.notify(); } } diff --git a/crates/language/src/lib.rs b/crates/language/src/lib.rs index f810c75cc4414e4b553ad5025ccb5164ffabd123..0755ed95716a96a4adbad4304c1c9315d5d0c1cd 100644 --- a/crates/language/src/lib.rs +++ b/crates/language/src/lib.rs @@ -816,7 +816,7 @@ impl Buffer { pub fn diagnostics_in_range<'a, T, O>( &'a self, - range: Range, + search_range: Range, ) -> impl Iterator, &Diagnostic)> + 'a where T: 'a + ToOffset, @@ -824,7 +824,7 @@ impl Buffer { { let content = self.content(); self.diagnostics - .intersecting_ranges(range, content, true) + .intersecting_ranges(search_range, content, true) .map(move |(_, range, diagnostic)| (range, diagnostic)) } From f39942863b7ee5bb268dd9fda1ed650cad4edf37 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 18 Nov 2021 16:57:05 +0100 Subject: [PATCH 094/109] Dismiss active diagnostics when hitting escape Co-Authored-By: Nathan Sobo --- crates/editor/src/lib.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/crates/editor/src/lib.rs b/crates/editor/src/lib.rs index 2afb18d1defd553720ab08b012a3d1734d35f8bf..e5f8d101c51223794434cd29be7e8a05a8bd2c76 100644 --- a/crates/editor/src/lib.rs +++ b/crates/editor/src/lib.rs @@ -703,7 +703,9 @@ impl Editor { } pub fn cancel(&mut self, _: &Cancel, cx: &mut ViewContext) { - if let Some(pending_selection) = self.pending_selection.take() { + if self.active_diagnostics.is_some() { + self.dismiss_diagnostics(cx); + } else if let Some(pending_selection) = self.pending_selection.take() { let buffer = self.buffer.read(cx); let pending_selection = Selection { id: pending_selection.id, From 1a8b23e1183be9e5824c76204cf2f7c5fdef4acd Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 18 Nov 2021 17:47:10 +0100 Subject: [PATCH 095/109] Color diagnostic messages based on their severity Co-Authored-By: Nathan Sobo --- crates/editor/src/display_map.rs | 9 +-- crates/editor/src/display_map/block_map.rs | 67 ++++++++++++++++------ crates/editor/src/element.rs | 12 +--- crates/editor/src/lib.rs | 47 ++++++++++----- crates/theme/src/lib.rs | 16 +++--- crates/zed/assets/themes/_base.toml | 10 ++-- 6 files changed, 103 insertions(+), 58 deletions(-) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 3354d76e6158824fd8aee3a403d647f667081427..4fc3af636d7c50a144771c2ab31ce56898a5f8e3 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -8,7 +8,7 @@ pub use block_map::{BlockDisposition, BlockId, BlockProperties, BufferRows, Chun use block_map::{BlockMap, BlockPoint}; use buffer::Rope; use fold_map::{FoldMap, ToFoldPoint as _}; -use gpui::{fonts::FontId, Entity, ModelContext, ModelHandle}; +use gpui::{fonts::FontId, AppContext, Entity, ModelContext, ModelHandle}; use language::{Anchor, Buffer, Point, ToOffset, ToPoint}; use std::{collections::HashSet, ops::Range}; use sum_tree::Bias; @@ -230,7 +230,7 @@ impl DisplayMapSnapshot { pub fn text_chunks(&self, display_row: u32) -> impl Iterator { self.blocks_snapshot - .chunks(display_row..self.max_point().row() + 1, None) + .chunks(display_row..self.max_point().row() + 1, None, None) .map(|h| h.text) } @@ -238,8 +238,9 @@ impl DisplayMapSnapshot { &'a self, display_rows: Range, theme: Option<&'a SyntaxTheme>, + cx: &'a AppContext, ) -> block_map::Chunks<'a> { - self.blocks_snapshot.chunks(display_rows, theme) + self.blocks_snapshot.chunks(display_rows, theme, Some(cx)) } pub fn chars_at<'a>(&'a self, point: DisplayPoint) -> impl Iterator + 'a { @@ -1025,7 +1026,7 @@ mod tests { ) -> Vec<(String, Option)> { let snapshot = map.update(cx, |map, cx| map.snapshot(cx)); let mut chunks: Vec<(String, Option)> = Vec::new(); - for chunk in snapshot.chunks(rows, Some(theme)) { + for chunk in snapshot.chunks(rows, Some(theme), cx) { let color = chunk.highlight_style.map(|s| s.color); if let Some((last_chunk, last_color)) = chunks.last_mut() { if color == *last_color { diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index c95520a41d7b872c5ea3ddbea1c05354adf73a23..df45e2932ecdf52076708be893dc02d3120622b2 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -6,13 +6,14 @@ use parking_lot::Mutex; use std::{ cmp::{self, Ordering}, collections::HashSet, + fmt::Debug, iter, ops::Range, - slice, sync::{ atomic::{AtomicUsize, Ordering::SeqCst}, Arc, }, + vec, }; use sum_tree::SumTree; use theme::SyntaxTheme; @@ -44,12 +45,11 @@ struct BlockRow(u32); #[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)] struct WrapRow(u32); -#[derive(Debug)] struct Block { id: BlockId, position: Anchor, text: Rope, - runs: Vec<(usize, HighlightStyle)>, + build_runs: Option Vec<(usize, HighlightStyle)>>>, disposition: BlockDisposition, } @@ -61,7 +61,7 @@ where { pub position: P, pub text: T, - pub runs: Vec<(usize, HighlightStyle)>, + pub build_runs: Option Vec<(usize, HighlightStyle)>>>, pub disposition: BlockDisposition, } @@ -92,11 +92,12 @@ pub struct Chunks<'a> { block_chunks: Option>, output_row: u32, max_output_row: u32, + cx: Option<&'a AppContext>, } struct BlockChunks<'a> { chunks: rope::Chunks<'a>, - runs: iter::Peekable>, + runs: iter::Peekable>, chunk: Option<&'a str>, run_start: usize, offset: usize, @@ -403,7 +404,7 @@ impl<'a> BlockMapWriter<'a> { id, position, text: block.text.into(), - runs: block.runs, + build_runs: block.build_runs, disposition: block.disposition, }), ); @@ -460,12 +461,17 @@ impl<'a> BlockMapWriter<'a> { impl BlockSnapshot { #[cfg(test)] fn text(&mut self) -> String { - self.chunks(0..self.transforms.summary().output_rows, None) + self.chunks(0..self.transforms.summary().output_rows, None, None) .map(|chunk| chunk.text) .collect() } - pub fn chunks<'a>(&'a self, rows: Range, theme: Option<&'a SyntaxTheme>) -> Chunks<'a> { + pub fn chunks<'a>( + &'a self, + rows: Range, + theme: Option<&'a SyntaxTheme>, + cx: Option<&'a AppContext>, + ) -> Chunks<'a> { let max_output_row = cmp::min(rows.end, self.transforms.summary().output_rows); let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>(); let input_end = { @@ -499,6 +505,7 @@ impl BlockSnapshot { transforms: cursor, output_row: rows.start, max_output_row, + cx, } } @@ -709,7 +716,11 @@ impl<'a> Iterator for Chunks<'a> { let start_in_block = self.output_row - block_start; let end_in_block = cmp::min(self.max_output_row, block_end) - block_start; self.transforms.next(&()); - self.block_chunks = Some(BlockChunks::new(block, start_in_block..end_in_block)); + self.block_chunks = Some(BlockChunks::new( + block, + start_in_block..end_in_block, + self.cx, + )); return self.next(); } @@ -748,11 +759,18 @@ impl<'a> Iterator for Chunks<'a> { } impl<'a> BlockChunks<'a> { - fn new(block: &'a Block, rows: Range) -> Self { + fn new(block: &'a Block, rows: Range, cx: Option<&'a AppContext>) -> Self { let offset_range = block.text.point_to_offset(Point::new(rows.start, 0)) ..block.text.point_to_offset(Point::new(rows.end, 0)); - let mut runs = block.runs.iter().peekable(); + let mut runs = block + .build_runs + .as_ref() + .zip(cx) + .map(|(build_runs, cx)| build_runs(cx)) + .unwrap_or_default() + .into_iter() + .peekable(); let mut run_start = 0; while let Some((run_len, _)) = runs.peek() { let run_end = run_start + run_len; @@ -874,6 +892,17 @@ impl BlockDisposition { } } +impl Debug for Block { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("Block") + .field("id", &self.id) + .field("position", &self.position) + .field("text", &self.text) + .field("disposition", &self.disposition) + .finish() + } +} + // Count the number of bytes prior to a target point. If the string doesn't contain the target // point, return its total extent. Otherwise return the target point itself. fn offset_for_row(s: &str, target: u32) -> (u32, usize) { @@ -938,19 +967,19 @@ mod tests { position: Point::new(1, 0), text: "BLOCK 1", disposition: BlockDisposition::Above, - runs: vec![], + build_runs: None, }, BlockProperties { position: Point::new(1, 2), text: "BLOCK 2", disposition: BlockDisposition::Above, - runs: vec![], + build_runs: None, }, BlockProperties { position: Point::new(3, 2), text: "BLOCK 3", disposition: BlockDisposition::Below, - runs: vec![], + build_runs: None, }, ], cx, @@ -1078,13 +1107,13 @@ mod tests { position: Point::new(1, 12), text: "BLOCK 2", disposition: BlockDisposition::Below, - runs: vec![], + build_runs: None, }, ], cx, @@ -1177,7 +1206,7 @@ mod tests { BlockProperties { position, text, - runs: Vec::<(usize, HighlightStyle)>::new(), + build_runs: None, disposition, } }) @@ -1252,7 +1281,7 @@ mod tests { BlockProperties { position: row, text: block.text, - runs: block.runs, + build_runs: block.build_runs.clone(), disposition: block.disposition, }, ) @@ -1313,7 +1342,7 @@ mod tests { for start_row in 0..expected_row_count { let expected_text = expected_lines[start_row..].join("\n"); let actual_text = blocks_snapshot - .chunks(start_row as u32..expected_row_count as u32, None) + .chunks(start_row as u32..expected_row_count as u32, None, None) .map(|chunk| chunk.text) .collect::(); assert_eq!( diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 6e186ef9af88b5f4ee5b8d2df53fe54592b10b5c..15b89b3f58559816fa6dcc72d21e2d491f943ce4 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -17,7 +17,7 @@ use gpui::{ MutableAppContext, PaintContext, Quad, Scene, SizeConstraint, ViewContext, WeakViewHandle, }; use json::json; -use language::{Chunk, DiagnosticSeverity}; +use language::Chunk; use smallvec::SmallVec; use std::{ cmp::{self, Ordering}, @@ -493,7 +493,7 @@ impl EditorElement { let mut styles = Vec::new(); let mut row = rows.start; let mut line_exceeded_max_len = false; - let chunks = snapshot.chunks(rows.clone(), Some(&style.syntax)); + let chunks = snapshot.chunks(rows.clone(), Some(&style.syntax), cx); let newline_chunk = Chunk { text: "\n", @@ -541,13 +541,7 @@ impl EditorElement { } let underline = if let Some(severity) = chunk.diagnostic { - match severity { - DiagnosticSeverity::ERROR => Some(style.error_underline), - DiagnosticSeverity::WARNING => Some(style.warning_underline), - DiagnosticSeverity::INFORMATION => Some(style.information_underline), - DiagnosticSeverity::HINT => Some(style.hint_underline), - _ => highlight_style.underline, - } + Some(super::diagnostic_color(severity, style)) } else { highlight_style.underline }; diff --git a/crates/editor/src/lib.rs b/crates/editor/src/lib.rs index e5f8d101c51223794434cd29be7e8a05a8bd2c76..81a890d71fa08bb42f6d3a3e0bd459156019a6ac 100644 --- a/crates/editor/src/lib.rs +++ b/crates/editor/src/lib.rs @@ -12,6 +12,7 @@ use display_map::*; pub use element::*; use gpui::{ action, + color::Color, geometry::vector::{vec2f, Vector2F}, keymap::Binding, text_layout, AppContext, ClipboardItem, Element, ElementBox, Entity, ModelHandle, @@ -2250,21 +2251,30 @@ impl Editor { let buffer = self.buffer.read(cx); let diagnostic_group = buffer .diagnostic_group::(group_id) - .map(|(range, diagnostic)| (range, diagnostic.message.clone())) + .map(|(range, diagnostic)| (range, diagnostic.clone())) .collect::>(); let primary_range = buffer.anchor_after(primary_range.start) ..buffer.anchor_before(primary_range.end); let block_ids = display_map .insert_blocks( - diagnostic_group - .iter() - .map(|(range, message)| BlockProperties { + diagnostic_group.iter().map(|(range, diagnostic)| { + let build_settings = self.build_settings.clone(); + let message_len = diagnostic.message.len(); + let severity = diagnostic.severity; + BlockProperties { position: range.start, - text: message.as_str(), - runs: vec![], - disposition: BlockDisposition::Above, - }), + text: diagnostic.message.as_str(), + build_runs: Some(Arc::new(move |cx| { + let settings = build_settings.borrow()(cx); + vec![( + message_len, + diagnostic_color(severity, &settings.style).into(), + )] + })), + disposition: BlockDisposition::Below, + } + }), cx, ) .into_iter() @@ -2813,8 +2823,9 @@ impl Snapshot { &'a self, display_rows: Range, theme: Option<&'a SyntaxTheme>, + cx: &'a AppContext, ) -> display_map::Chunks<'a> { - self.display_snapshot.chunks(display_rows, theme) + self.display_snapshot.chunks(display_rows, theme, cx) } pub fn scroll_position(&self) -> Vector2F { @@ -2882,10 +2893,10 @@ impl EditorSettings { selection: Default::default(), guest_selections: Default::default(), syntax: Default::default(), - error_underline: Default::default(), - warning_underline: Default::default(), - information_underline: Default::default(), - hint_underline: Default::default(), + error_color: Default::default(), + warning_color: Default::default(), + information_color: Default::default(), + hint_color: Default::default(), } }, } @@ -3009,6 +3020,16 @@ impl SelectionExt for Selection { } } +pub fn diagnostic_color(severity: DiagnosticSeverity, style: &EditorStyle) -> Color { + match severity { + DiagnosticSeverity::ERROR => style.error_color, + DiagnosticSeverity::WARNING => style.warning_color, + DiagnosticSeverity::INFORMATION => style.information_color, + DiagnosticSeverity::HINT => style.hint_color, + _ => style.text.color, + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/theme/src/lib.rs b/crates/theme/src/lib.rs index c4fbcc3031a8c5d6c9f7265686172f63242a1e7b..ab451b014141f1bcb046919c5604f36ff32732fc 100644 --- a/crates/theme/src/lib.rs +++ b/crates/theme/src/lib.rs @@ -227,12 +227,12 @@ pub struct EditorStyle { pub line_number_active: Color, pub guest_selections: Vec, pub syntax: Arc, - pub error_underline: Color, - pub warning_underline: Color, + pub error_color: Color, + pub warning_color: Color, #[serde(default)] - pub information_underline: Color, + pub information_color: Color, #[serde(default)] - pub hint_underline: Color, + pub hint_color: Color, } #[derive(Clone, Copy, Default, Deserialize)] @@ -273,10 +273,10 @@ impl InputEditorStyle { line_number_active: Default::default(), guest_selections: Default::default(), syntax: Default::default(), - error_underline: Default::default(), - warning_underline: Default::default(), - information_underline: Default::default(), - hint_underline: Default::default(), + error_color: Default::default(), + warning_color: Default::default(), + information_color: Default::default(), + hint_color: Default::default(), } } } diff --git a/crates/zed/assets/themes/_base.toml b/crates/zed/assets/themes/_base.toml index 77167df8b0a9fa5971f45e83e190278efc1c374e..609228d86ded908c0c326e47ba51bf1b7b5d7ef7 100644 --- a/crates/zed/assets/themes/_base.toml +++ b/crates/zed/assets/themes/_base.toml @@ -173,7 +173,7 @@ corner_radius = 6 [project_panel] extends = "$panel" -padding.top = 6 # ($workspace.tab.height - $project_panel.entry.height) / 2 +padding.top = 6 # ($workspace.tab.height - $project_panel.entry.height) / 2 [project_panel.entry] text = "$text.1" @@ -235,7 +235,7 @@ line_number = "$text.2.color" line_number_active = "$text.0.color" selection = "$selection.host" guest_selections = "$selection.guests" -error_underline = "$status.bad" -warning_underline = "$status.warn" -info_underline = "$status.info" -hint_underline = "$status.info" +error_color = "$status.bad" +warning_color = "$status.warn" +info_color = "$status.info" +hint_color = "$status.info" From bef09696f6dd3d9f0f941487dba5db02273121e6 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 18 Nov 2021 12:17:22 -0800 Subject: [PATCH 096/109] Align block text with the anchor's column Co-Authored-By: Antonio Scandurra Co-Authored-By: Nathan Sobo --- crates/editor/src/display_map/block_map.rs | 180 ++++++++++++++++++--- crates/editor/src/display_map/wrap_map.rs | 4 + 2 files changed, 158 insertions(+), 26 deletions(-) diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index df45e2932ecdf52076708be893dc02d3120622b2..cbc207fc5b614ffc0a35623303559057cc481129 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -8,7 +8,7 @@ use std::{ collections::HashSet, fmt::Debug, iter, - ops::Range, + ops::{Deref, Range}, sync::{ atomic::{AtomicUsize, Ordering::SeqCst}, Arc, @@ -74,7 +74,13 @@ pub enum BlockDisposition { #[derive(Clone, Debug)] struct Transform { summary: TransformSummary, - block: Option>, + block: Option, +} + +#[derive(Clone, Debug)] +struct AlignedBlock { + block: Arc, + column: u32, } #[derive(Clone, Debug, Default)] @@ -99,6 +105,8 @@ struct BlockChunks<'a> { chunks: rope::Chunks<'a>, runs: iter::Peekable>, chunk: Option<&'a str>, + remaining_padding: u32, + padding_column: u32, run_start: usize, offset: usize, } @@ -271,6 +279,7 @@ impl BlockMap { .iter() .map(|block| { let mut position = block.position.to_point(buffer); + let column = wrap_snapshot.from_point(position, Bias::Left).column(); match block.disposition { BlockDisposition::Above => position.column = 0, BlockDisposition::Below => { @@ -278,21 +287,22 @@ impl BlockMap { } } let position = wrap_snapshot.from_point(position, Bias::Left); - (position.row(), block) + (position.row(), column, block) }), ); - blocks_in_edit.sort_unstable_by_key(|(row, block)| (*row, block.disposition, block.id)); + blocks_in_edit + .sort_unstable_by_key(|(row, _, block)| (*row, block.disposition, block.id)); // For each of these blocks, insert a new isomorphic transform preceding the block, // and then insert the block itself. - for (block_row, block) in blocks_in_edit.iter().copied() { + for (block_row, column, block) in blocks_in_edit.iter().copied() { let insertion_row = match block.disposition { BlockDisposition::Above => block_row, BlockDisposition::Below => block_row + 1, }; let extent_before_block = insertion_row - new_transforms.summary().input_rows; push_isomorphic(&mut new_transforms, extent_before_block); - new_transforms.push(Transform::block(block.clone()), &()); + new_transforms.push(Transform::block(block.clone(), column), &()); } old_end = WrapRow(old_end.0.min(old_row_count)); @@ -345,7 +355,7 @@ impl BlockPoint { } } -impl std::ops::Deref for BlockPoint { +impl Deref for BlockPoint { type Target = Point; fn deref(&self) -> &Self::Target { @@ -555,7 +565,11 @@ impl BlockSnapshot { let (output_start, input_start) = cursor.start(); let overshoot = row - output_start.0; if let Some(block) = &transform.block { - block.text.line_len(overshoot) + let mut len = block.text.line_len(overshoot); + if len > 0 { + len += block.column; + } + len } else { self.wrap_snapshot.line_len(input_start.0 + overshoot) } @@ -665,16 +679,16 @@ impl Transform { } } - fn block(block: Arc) -> Self { + fn block(block: Arc, column: u32) -> Self { let text_summary = block.text.summary(); Self { summary: TransformSummary { input_rows: 0, output_rows: text_summary.lines.row + 1, longest_row_in_block: text_summary.longest_row, - longest_row_in_block_chars: text_summary.longest_row_chars, + longest_row_in_block_chars: column + text_summary.longest_row_chars, }, - block: Some(block), + block: Some(AlignedBlock { block, column }), } } @@ -759,7 +773,7 @@ impl<'a> Iterator for Chunks<'a> { } impl<'a> BlockChunks<'a> { - fn new(block: &'a Block, rows: Range, cx: Option<&'a AppContext>) -> Self { + fn new(block: &'a AlignedBlock, rows: Range, cx: Option<&'a AppContext>) -> Self { let offset_range = block.text.point_to_offset(Point::new(rows.start, 0)) ..block.text.point_to_offset(Point::new(rows.end, 0)); @@ -785,6 +799,8 @@ impl<'a> BlockChunks<'a> { Self { chunk: None, run_start, + padding_column: block.column, + remaining_padding: block.column, chunks: block.text.chunks_in_range(offset_range.clone()), runs, offset: offset_range.start, @@ -801,7 +817,27 @@ impl<'a> Iterator for BlockChunks<'a> { } let chunk = self.chunk?; - let mut chunk_len = chunk.len(); + + if chunk.starts_with('\n') { + self.remaining_padding = 0; + } + + if self.remaining_padding > 0 { + const PADDING: &'static str = " "; + let padding_len = self.remaining_padding.min(PADDING.len() as u32); + self.remaining_padding -= padding_len; + return Some(Chunk { + text: &PADDING[..padding_len as usize], + ..Default::default() + }); + } + + let mut chunk_len = if let Some(ix) = chunk.find('\n') { + ix + 1 + } else { + chunk.len() + }; + let mut highlight_style = None; if let Some((run_len, style)) = self.runs.peek() { highlight_style = Some(style.clone()); @@ -815,6 +851,11 @@ impl<'a> Iterator for BlockChunks<'a> { self.offset += chunk_len; let (chunk, suffix) = chunk.split_at(chunk_len); + + if chunk.ends_with('\n') { + self.remaining_padding = self.padding_column; + } + self.chunk = if suffix.is_empty() { None } else { @@ -892,6 +933,14 @@ impl BlockDisposition { } } +impl Deref for AlignedBlock { + type Target = Block; + + fn deref(&self) -> &Self::Target { + self.block.as_ref() + } +} + impl Debug for Block { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("Block") @@ -926,6 +975,7 @@ mod tests { use super::*; use crate::display_map::{fold_map::FoldMap, tab_map::TabMap, wrap_map::WrapMap}; use buffer::RandomCharIter; + use gpui::color::Color; use language::Buffer; use rand::prelude::*; use std::env; @@ -944,6 +994,75 @@ mod tests { assert_eq!(offset_for_row("abc\ndef\nghi", 3), (2, 11)); } + #[gpui::test] + fn test_block_chunks(cx: &mut gpui::MutableAppContext) { + let red = Color::red(); + let blue = Color::blue(); + let clear = Color::default(); + + let block = AlignedBlock { + column: 5, + block: Arc::new(Block { + id: BlockId(0), + position: Anchor::min(), + text: "one!\ntwo three\nfour".into(), + build_runs: Some(Arc::new(move |_| { + vec![(3, red.into()), (6, Default::default()), (5, blue.into())] + })), + disposition: BlockDisposition::Above, + }), + }; + + assert_eq!( + colored_chunks(&block, 0..3, cx), + &[ + (" ", clear), + ("one", red), + ("!\n", clear), + (" ", clear), + ("two ", clear), + ("three", blue), + ("\n", clear), + (" ", clear), + ("four", clear) + ] + ); + assert_eq!( + colored_chunks(&block, 0..1, cx), + &[ + (" ", clear), // + ("one", red), + ("!\n", clear), + ] + ); + assert_eq!( + colored_chunks(&block, 1..3, cx), + &[ + (" ", clear), + ("two ", clear), + ("three", blue), + ("\n", clear), + (" ", clear), + ("four", clear) + ] + ); + + fn colored_chunks<'a>( + block: &'a AlignedBlock, + row_range: Range, + cx: &'a AppContext, + ) -> Vec<(&'a str, Color)> { + BlockChunks::new(block, row_range, Some(cx)) + .map(|c| { + ( + c.text, + c.highlight_style.map_or(Color::default(), |s| s.color), + ) + }) + .collect() + } + } + #[gpui::test] fn test_basic_blocks(cx: &mut gpui::MutableAppContext) { let family_id = cx.font_cache().load_family(&["Helvetica"]).unwrap(); @@ -988,7 +1107,7 @@ mod tests { let mut snapshot = block_map.read(wraps_snapshot, vec![], cx); assert_eq!( snapshot.text(), - "aaa\nBLOCK 1\nBLOCK 2\nbbb\nccc\nddd\nBLOCK 3" + "aaa\nBLOCK 1\n BLOCK 2\nbbb\nccc\nddd\n BLOCK 3" ); assert_eq!( snapshot.to_block_point(WrapPoint::new(0, 3)), @@ -1080,7 +1199,7 @@ mod tests { let mut snapshot = block_map.read(wraps_snapshot, wrap_edits, cx); assert_eq!( snapshot.text(), - "aaa\nBLOCK 1\nb!!!\nBLOCK 2\nbb\nccc\nddd\nBLOCK 3" + "aaa\nBLOCK 1\nb!!!\n BLOCK 2\nbb\nccc\nddd\n BLOCK 3" ); } @@ -1124,7 +1243,7 @@ mod tests { let mut snapshot = block_map.read(wraps_snapshot, vec![], cx); assert_eq!( snapshot.text(), - "one two \nthree\nBLOCK 2\nseven \neight" + "one two \nthree\n BLOCK 2\nseven \neight" ); } @@ -1267,6 +1386,7 @@ mod tests { .cloned() .map(|(id, block)| { let mut position = block.position.to_point(buffer); + let column = wraps_snapshot.from_point(position, Bias::Left).column(); match block.disposition { BlockDisposition::Above => { position.column = 0; @@ -1279,7 +1399,7 @@ mod tests { ( id, BlockProperties { - position: row, + position: BlockPoint::new(row, column), text: block.text, build_runs: block.build_runs.clone(), disposition: block.disposition, @@ -1288,7 +1408,7 @@ mod tests { }) .collect::>(); sorted_blocks - .sort_unstable_by_key(|(id, block)| (block.position, block.disposition, *id)); + .sort_unstable_by_key(|(id, block)| (block.position.row, block.disposition, *id)); let mut sorted_blocks = sorted_blocks.into_iter().peekable(); let mut expected_buffer_rows = Vec::new(); @@ -1305,11 +1425,15 @@ mod tests { .row; while let Some((_, block)) = sorted_blocks.peek() { - if block.position == row && block.disposition == BlockDisposition::Above { + if block.position.row == row && block.disposition == BlockDisposition::Above { let text = block.text.to_string(); - expected_text.push_str(&text); - expected_text.push('\n'); - for _ in text.split('\n') { + let padding = " ".repeat(block.position.column as usize); + for line in text.split('\n') { + if !line.is_empty() { + expected_text.push_str(&padding); + expected_text.push_str(line); + } + expected_text.push('\n'); expected_buffer_rows.push(None); } sorted_blocks.next(); @@ -1323,11 +1447,15 @@ mod tests { expected_text.push_str(input_line); while let Some((_, block)) = sorted_blocks.peek() { - if block.position == row && block.disposition == BlockDisposition::Below { + if block.position.row == row && block.disposition == BlockDisposition::Below { let text = block.text.to_string(); - expected_text.push('\n'); - expected_text.push_str(&text); - for _ in text.split('\n') { + let padding = " ".repeat(block.position.column as usize); + for line in text.split('\n') { + expected_text.push('\n'); + if !line.is_empty() { + expected_text.push_str(&padding); + expected_text.push_str(line); + } expected_buffer_rows.push(None); } sorted_blocks.next(); diff --git a/crates/editor/src/display_map/wrap_map.rs b/crates/editor/src/display_map/wrap_map.rs index 70afe9d892bca1d0de896d25553d5c60cae9f646..6a6327a0b3a7a0bedbf90e3574de6cec43e34ea3 100644 --- a/crates/editor/src/display_map/wrap_map.rs +++ b/crates/editor/src/display_map/wrap_map.rs @@ -916,6 +916,10 @@ impl WrapPoint { &mut self.0.row } + pub fn column(&self) -> u32 { + self.0.column + } + pub fn column_mut(&mut self) -> &mut u32 { &mut self.0.column } From 6aa346dec8671dc07477eb97a7e7605db31be34f Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 18 Nov 2021 12:41:58 -0800 Subject: [PATCH 097/109] Fix switched input + output rows in BlockSnapshot::buffer_rows --- crates/editor/src/display_map/block_map.rs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index cbc207fc5b614ffc0a35623303559057cc481129..cf1bec487bcf7c04d13338cc182ca52d78a4b214 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -522,7 +522,7 @@ impl BlockSnapshot { pub fn buffer_rows(&self, start_row: u32) -> BufferRows { let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>(); cursor.seek(&BlockRow(start_row), Bias::Right, &()); - let (input_start, output_start) = cursor.start(); + let (output_start, input_start) = cursor.start(); let overshoot = if cursor.item().map_or(false, |t| t.is_isomorphic()) { start_row - output_start.0 } else { @@ -1478,6 +1478,12 @@ mod tests { "incorrect text starting from row {}", start_row ); + assert_eq!( + blocks_snapshot + .buffer_rows(start_row as u32) + .collect::>(), + &expected_buffer_rows[start_row..] + ); } let mut expected_longest_rows = Vec::new(); @@ -1519,10 +1525,6 @@ mod tests { let block_point = blocks_snapshot.to_block_point(wrap_point); assert_eq!(blocks_snapshot.to_wrap_point(block_point), wrap_point); } - assert_eq!( - blocks_snapshot.buffer_rows(0).collect::>(), - expected_buffer_rows - ); let mut block_point = BlockPoint::new(0, 0); for c in expected_text.chars() { From 0b63d882ceeb47a395ecd3ff1c72a8e07dad5233 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 18 Nov 2021 14:23:36 -0800 Subject: [PATCH 098/109] Allow key bindings to F1 through F12 --- crates/gpui/src/platform/mac/event.rs | 46 ++++++++++++++++----------- 1 file changed, 28 insertions(+), 18 deletions(-) diff --git a/crates/gpui/src/platform/mac/event.rs b/crates/gpui/src/platform/mac/event.rs index 04712d599ffc85de2a98e9af761f6764d2a3b0c4..f205420ddedb02d3f2491c0fe6ae5bb6ecb55f57 100644 --- a/crates/gpui/src/platform/mac/event.rs +++ b/crates/gpui/src/platform/mac/event.rs @@ -1,10 +1,4 @@ use crate::{geometry::vector::vec2f, keymap::Keystroke, platform::Event}; -use cocoa::appkit::{ - NSDeleteFunctionKey as DELETE_KEY, NSDownArrowFunctionKey as ARROW_DOWN_KEY, - NSLeftArrowFunctionKey as ARROW_LEFT_KEY, NSPageDownFunctionKey as PAGE_DOWN_KEY, - NSPageUpFunctionKey as PAGE_UP_KEY, NSRightArrowFunctionKey as ARROW_RIGHT_KEY, - NSUpArrowFunctionKey as ARROW_UP_KEY, -}; use cocoa::{ appkit::{NSEvent, NSEventModifierFlags, NSEventType}, base::{id, nil, YES}, @@ -12,11 +6,6 @@ use cocoa::{ }; use std::{ffi::CStr, os::raw::c_char}; -const BACKSPACE_KEY: u16 = 0x7f; -const ENTER_KEY: u16 = 0x0d; -const ESCAPE_KEY: u16 = 0x1b; -const TAB_KEY: u16 = 0x09; - impl Event { pub unsafe fn from_native(native_event: id, window_height: Option) -> Option { let event_type = native_event.eventType(); @@ -39,18 +28,39 @@ impl Event { .unwrap(); let unmodified_chars = if let Some(first_char) = unmodified_chars.chars().next() { + use cocoa::appkit::*; + const BACKSPACE_KEY: u16 = 0x7f; + const ENTER_KEY: u16 = 0x0d; + const ESCAPE_KEY: u16 = 0x1b; + const TAB_KEY: u16 = 0x09; + + #[allow(non_upper_case_globals)] match first_char as u16 { - ARROW_UP_KEY => "up", - ARROW_DOWN_KEY => "down", - ARROW_LEFT_KEY => "left", - ARROW_RIGHT_KEY => "right", - PAGE_UP_KEY => "pageup", - PAGE_DOWN_KEY => "pagedown", BACKSPACE_KEY => "backspace", ENTER_KEY => "enter", - DELETE_KEY => "delete", ESCAPE_KEY => "escape", TAB_KEY => "tab", + + NSUpArrowFunctionKey => "up", + NSDownArrowFunctionKey => "down", + NSLeftArrowFunctionKey => "left", + NSRightArrowFunctionKey => "right", + NSPageUpFunctionKey => "pageup", + NSPageDownFunctionKey => "pagedown", + NSDeleteFunctionKey => "delete", + NSF1FunctionKey => "f1", + NSF2FunctionKey => "f2", + NSF3FunctionKey => "f3", + NSF4FunctionKey => "f4", + NSF5FunctionKey => "f5", + NSF6FunctionKey => "f6", + NSF7FunctionKey => "f7", + NSF8FunctionKey => "f8", + NSF9FunctionKey => "f9", + NSF10FunctionKey => "f10", + NSF11FunctionKey => "f11", + NSF12FunctionKey => "f12", + _ => unmodified_chars, } } else { From c04151f999db44711d173ed668fd79ea9e2d1f78 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 18 Nov 2021 14:23:53 -0800 Subject: [PATCH 099/109] Bind ShowNextDiagnostic to f8 --- crates/editor/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/editor/src/lib.rs b/crates/editor/src/lib.rs index 81a890d71fa08bb42f6d3a3e0bd459156019a6ac..52697427821ccd8afe07c418adf5290857cdf34f 100644 --- a/crates/editor/src/lib.rs +++ b/crates/editor/src/lib.rs @@ -189,7 +189,7 @@ pub fn init(cx: &mut MutableAppContext) { Binding::new("ctrl-w", SelectLargerSyntaxNode, Some("Editor")), Binding::new("alt-down", SelectSmallerSyntaxNode, Some("Editor")), Binding::new("ctrl-shift-W", SelectSmallerSyntaxNode, Some("Editor")), - Binding::new("ctrl-.", ShowNextDiagnostic, Some("Editor")), + Binding::new("f8", ShowNextDiagnostic, Some("Editor")), Binding::new("ctrl-m", MoveToEnclosingBracket, Some("Editor")), Binding::new("pageup", PageUp, Some("Editor")), Binding::new("pagedown", PageDown, Some("Editor")), From 8d1a4a6a24441b2b4b35217ac18b3d0a673303fe Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 18 Nov 2021 18:16:35 -0800 Subject: [PATCH 100/109] Start work on allowing blocks to be styled --- crates/editor/src/display_map.rs | 13 ++- crates/editor/src/display_map/block_map.rs | 65 +++++++++--- crates/editor/src/display_map/wrap_map.rs | 15 ++- crates/editor/src/element.rs | 115 +++++++++++++++------ crates/editor/src/lib.rs | 82 ++++++++++----- crates/theme/src/lib.rs | 29 ++++-- crates/zed/assets/themes/_base.toml | 9 +- 7 files changed, 232 insertions(+), 96 deletions(-) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 4fc3af636d7c50a144771c2ab31ce56898a5f8e3..5eb3d108df1b57804a3f11f48168bfcd35e549a0 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -13,7 +13,7 @@ use language::{Anchor, Buffer, Point, ToOffset, ToPoint}; use std::{collections::HashSet, ops::Range}; use sum_tree::Bias; use tab_map::TabMap; -use theme::SyntaxTheme; +use theme::{BlockStyle, SyntaxTheme}; use wrap_map::WrapMap; pub trait ToDisplayPoint { @@ -172,8 +172,8 @@ impl DisplayMapSnapshot { self.buffer_snapshot.len() == 0 } - pub fn buffer_rows(&self, start_row: u32) -> BufferRows { - self.blocks_snapshot.buffer_rows(start_row) + pub fn buffer_rows<'a>(&'a self, start_row: u32, cx: Option<&'a AppContext>) -> BufferRows<'a> { + self.blocks_snapshot.buffer_rows(start_row, cx) } pub fn buffer_row_count(&self) -> u32 { @@ -416,6 +416,13 @@ impl ToDisplayPoint for Anchor { } } +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum DisplayRow { + Buffer(u32), + Block(BlockId, Option), + Wrap, +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index cf1bec487bcf7c04d13338cc182ca52d78a4b214..6f5300d3914d94afbde20a4467f22a4ee57116e7 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -1,4 +1,7 @@ -use super::wrap_map::{self, Edit as WrapEdit, Snapshot as WrapSnapshot, WrapPoint}; +use super::{ + wrap_map::{self, Edit as WrapEdit, Snapshot as WrapSnapshot, WrapPoint}, + BlockStyle, DisplayRow, +}; use buffer::{rope, Anchor, Bias, Edit, Point, Rope, ToOffset, ToPoint as _}; use gpui::{fonts::HighlightStyle, AppContext, ModelHandle}; use language::{Buffer, Chunk}; @@ -45,11 +48,12 @@ struct BlockRow(u32); #[derive(Copy, Clone, Debug, Default, Eq, Ord, PartialOrd, PartialEq)] struct WrapRow(u32); -struct Block { +pub struct Block { id: BlockId, position: Anchor, text: Rope, build_runs: Option Vec<(usize, HighlightStyle)>>>, + build_style: Option BlockStyle>>, disposition: BlockDisposition, } @@ -62,6 +66,7 @@ where pub position: P, pub text: T, pub build_runs: Option Vec<(usize, HighlightStyle)>>>, + pub build_style: Option BlockStyle>>, pub disposition: BlockDisposition, } @@ -115,6 +120,7 @@ pub struct BufferRows<'a> { transforms: sum_tree::Cursor<'a, Transform, (BlockRow, WrapRow)>, input_buffer_rows: wrap_map::BufferRows<'a>, output_row: u32, + cx: Option<&'a AppContext>, started: bool, } @@ -415,6 +421,7 @@ impl<'a> BlockMapWriter<'a> { position, text: block.text.into(), build_runs: block.build_runs, + build_style: block.build_style, disposition: block.disposition, }), ); @@ -519,7 +526,7 @@ impl BlockSnapshot { } } - pub fn buffer_rows(&self, start_row: u32) -> BufferRows { + pub fn buffer_rows<'a>(&'a self, start_row: u32, cx: Option<&'a AppContext>) -> BufferRows<'a> { let mut cursor = self.transforms.cursor::<(BlockRow, WrapRow)>(); cursor.seek(&BlockRow(start_row), Bias::Right, &()); let (output_start, input_start) = cursor.start(); @@ -530,6 +537,7 @@ impl BlockSnapshot { }; let input_start_row = input_start.0 + overshoot; BufferRows { + cx, transforms: cursor, input_buffer_rows: self.wrap_snapshot.buffer_rows(input_start_row), output_row: start_row, @@ -871,7 +879,7 @@ impl<'a> Iterator for BlockChunks<'a> { } impl<'a> Iterator for BufferRows<'a> { - type Item = Option; + type Item = DisplayRow; fn next(&mut self) -> Option { if self.started { @@ -885,10 +893,13 @@ impl<'a> Iterator for BufferRows<'a> { } let transform = self.transforms.item()?; - if transform.is_isomorphic() { - Some(self.input_buffer_rows.next().unwrap()) + if let Some(block) = &transform.block { + let style = self + .cx + .and_then(|cx| block.build_style.as_ref().map(|f| f(cx))); + Some(DisplayRow::Block(block.id, style)) } else { - Some(None) + Some(self.input_buffer_rows.next().unwrap()) } } } @@ -1006,6 +1017,7 @@ mod tests { id: BlockId(0), position: Anchor::min(), text: "one!\ntwo three\nfour".into(), + build_style: None, build_runs: Some(Arc::new(move |_| { vec![(3, red.into()), (6, Default::default()), (5, blue.into())] })), @@ -1080,25 +1092,28 @@ mod tests { let mut block_map = BlockMap::new(buffer.clone(), wraps_snapshot.clone()); let mut writer = block_map.write(wraps_snapshot.clone(), vec![], cx); - writer.insert( + let block_ids = writer.insert( vec![ BlockProperties { position: Point::new(1, 0), text: "BLOCK 1", disposition: BlockDisposition::Above, build_runs: None, + build_style: None, }, BlockProperties { position: Point::new(1, 2), text: "BLOCK 2", disposition: BlockDisposition::Above, build_runs: None, + build_style: None, }, BlockProperties { position: Point::new(3, 2), text: "BLOCK 3", disposition: BlockDisposition::Below, build_runs: None, + build_style: None, }, ], cx, @@ -1181,8 +1196,16 @@ mod tests { ); assert_eq!( - snapshot.buffer_rows(0).collect::>(), - &[Some(0), None, None, Some(1), Some(2), Some(3), None] + snapshot.buffer_rows(0, None).collect::>(), + &[ + DisplayRow::Buffer(0), + DisplayRow::Block(block_ids[0], None), + DisplayRow::Block(block_ids[1], None), + DisplayRow::Buffer(1), + DisplayRow::Buffer(2), + DisplayRow::Buffer(3), + DisplayRow::Block(block_ids[2], None) + ] ); // Insert a line break, separating two block decorations into separate @@ -1227,12 +1250,14 @@ mod tests { text: "BLOCK 2", disposition: BlockDisposition::Below, build_runs: None, + build_style: None, }, ], cx, @@ -1325,8 +1350,9 @@ mod tests { BlockProperties { position, text, - build_runs: None, disposition, + build_runs: None, + build_style: None, } }) .collect::>(); @@ -1402,6 +1428,7 @@ mod tests { position: BlockPoint::new(row, column), text: block.text, build_runs: block.build_runs.clone(), + build_style: None, disposition: block.disposition, }, ) @@ -1424,7 +1451,7 @@ mod tests { .to_point(WrapPoint::new(row, 0), Bias::Left) .row; - while let Some((_, block)) = sorted_blocks.peek() { + while let Some((block_id, block)) = sorted_blocks.peek() { if block.position.row == row && block.disposition == BlockDisposition::Above { let text = block.text.to_string(); let padding = " ".repeat(block.position.column as usize); @@ -1434,7 +1461,7 @@ mod tests { expected_text.push_str(line); } expected_text.push('\n'); - expected_buffer_rows.push(None); + expected_buffer_rows.push(DisplayRow::Block(*block_id, None)); } sorted_blocks.next(); } else { @@ -1443,10 +1470,14 @@ mod tests { } let soft_wrapped = wraps_snapshot.to_tab_point(WrapPoint::new(row, 0)).column() > 0; - expected_buffer_rows.push(if soft_wrapped { None } else { Some(buffer_row) }); + expected_buffer_rows.push(if soft_wrapped { + DisplayRow::Wrap + } else { + DisplayRow::Buffer(buffer_row) + }); expected_text.push_str(input_line); - while let Some((_, block)) = sorted_blocks.peek() { + while let Some((block_id, block)) = sorted_blocks.peek() { if block.position.row == row && block.disposition == BlockDisposition::Below { let text = block.text.to_string(); let padding = " ".repeat(block.position.column as usize); @@ -1456,7 +1487,7 @@ mod tests { expected_text.push_str(&padding); expected_text.push_str(line); } - expected_buffer_rows.push(None); + expected_buffer_rows.push(DisplayRow::Block(*block_id, None)); } sorted_blocks.next(); } else { @@ -1480,7 +1511,7 @@ mod tests { ); assert_eq!( blocks_snapshot - .buffer_rows(start_row as u32) + .buffer_rows(start_row as u32, None) .collect::>(), &expected_buffer_rows[start_row..] ); diff --git a/crates/editor/src/display_map/wrap_map.rs b/crates/editor/src/display_map/wrap_map.rs index 6a6327a0b3a7a0bedbf90e3574de6cec43e34ea3..d84b25936a3987ca74da808664458efaae95fe07 100644 --- a/crates/editor/src/display_map/wrap_map.rs +++ b/crates/editor/src/display_map/wrap_map.rs @@ -2,6 +2,7 @@ use super::{ fold_map, patch::Patch, tab_map::{self, Edit as TabEdit, Snapshot as TabSnapshot, TabPoint}, + DisplayRow, }; use gpui::{ fonts::FontId, text_layout::LineWrapper, Entity, ModelContext, ModelHandle, MutableAppContext, @@ -712,7 +713,11 @@ impl Snapshot { prev_tab_row = tab_point.row(); soft_wrapped = false; } - expected_buffer_rows.push(if soft_wrapped { None } else { Some(buffer_row) }); + expected_buffer_rows.push(if soft_wrapped { + DisplayRow::Wrap + } else { + DisplayRow::Buffer(buffer_row) + }); } for start_display_row in 0..expected_buffer_rows.len() { @@ -792,7 +797,7 @@ impl<'a> Iterator for Chunks<'a> { } impl<'a> Iterator for BufferRows<'a> { - type Item = Option; + type Item = DisplayRow; fn next(&mut self) -> Option { if self.output_row > self.max_output_row { @@ -812,7 +817,11 @@ impl<'a> Iterator for BufferRows<'a> { self.soft_wrapped = true; } - Some(if soft_wrapped { None } else { Some(buffer_row) }) + Some(if soft_wrapped { + DisplayRow::Wrap + } else { + DisplayRow::Buffer(buffer_row) + }) } } diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 15b89b3f58559816fa6dcc72d21e2d491f943ce4..7b138853197a9f2189537e18a2b608772d25d05a 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -1,6 +1,6 @@ use super::{ - DisplayPoint, Editor, EditorMode, EditorSettings, EditorStyle, Input, Scroll, Select, - SelectPhase, Snapshot, MAX_LINE_LEN, + DisplayPoint, DisplayRow, Editor, EditorMode, EditorSettings, EditorStyle, Input, Scroll, + Select, SelectPhase, Snapshot, MAX_LINE_LEN, }; use clock::ReplicaId; use gpui::{ @@ -25,6 +25,7 @@ use std::{ fmt::Write, ops::Range, }; +use theme::BlockStyle; pub struct EditorElement { view: WeakViewHandle, @@ -359,6 +360,30 @@ impl EditorElement { } if let Some(visible_text_bounds) = bounds.intersection(visible_bounds) { + // Draw blocks + for (ixs, block_style) in &layout.block_layouts { + let row = start_row + ixs.start; + let origin = content_origin + + vec2f(-scroll_left, row as f32 * layout.line_height - scroll_top); + let height = ixs.len() as f32 * layout.line_height; + cx.scene.push_quad(Quad { + bounds: RectF::new(origin, vec2f(visible_text_bounds.width(), height)), + background: block_style.background, + border: block_style + .border + .map_or(Default::default(), |color| Border { + width: 1., + color, + overlay: true, + top: true, + right: false, + bottom: true, + left: false, + }), + corner_radius: 0., + }); + } + // Draw glyphs for (ix, line) in layout.line_layouts.iter().enumerate() { let row = start_row + ix as u32; @@ -401,18 +426,24 @@ impl EditorElement { .width() } - fn layout_line_numbers( + fn layout_rows( &self, rows: Range, active_rows: &BTreeMap, snapshot: &Snapshot, cx: &LayoutContext, - ) -> Vec> { + ) -> ( + Vec>, + Vec<(Range, BlockStyle)>, + ) { let style = &self.settings.style; - let mut layouts = Vec::with_capacity(rows.len()); + let include_line_numbers = snapshot.mode == EditorMode::Full; + let mut last_block_id = None; + let mut blocks = Vec::<(Range, BlockStyle)>::new(); + let mut line_number_layouts = Vec::with_capacity(rows.len()); let mut line_number = String::new(); - for (ix, buffer_row) in snapshot - .buffer_rows(rows.start) + for (ix, row) in snapshot + .buffer_rows(rows.start, cx) .take((rows.end - rows.start) as usize) .enumerate() { @@ -422,27 +453,46 @@ impl EditorElement { } else { style.line_number }; - if let Some(buffer_row) = buffer_row { - line_number.clear(); - write!(&mut line_number, "{}", buffer_row + 1).unwrap(); - layouts.push(Some(cx.text_layout_cache.layout_str( - &line_number, - style.text.font_size, - &[( - line_number.len(), - RunStyle { - font_id: style.text.font_id, - color, - underline: None, - }, - )], - ))); - } else { - layouts.push(None); + match row { + DisplayRow::Buffer(buffer_row) => { + if include_line_numbers { + line_number.clear(); + write!(&mut line_number, "{}", buffer_row + 1).unwrap(); + line_number_layouts.push(Some(cx.text_layout_cache.layout_str( + &line_number, + style.text.font_size, + &[( + line_number.len(), + RunStyle { + font_id: style.text.font_id, + color, + underline: None, + }, + )], + ))); + } + last_block_id = None; + } + DisplayRow::Block(block_id, style) => { + let ix = ix as u32; + if last_block_id == Some(block_id) { + if let Some((row_range, _)) = blocks.last_mut() { + row_range.end += 1; + } + } else if let Some(style) = style { + blocks.push((ix..ix + 1, style)); + } + line_number_layouts.push(None); + last_block_id = Some(block_id); + } + DisplayRow::Wrap => { + line_number_layouts.push(None); + last_block_id = None; + } } } - layouts + (line_number_layouts, blocks) } fn layout_lines( @@ -541,7 +591,7 @@ impl EditorElement { } let underline = if let Some(severity) = chunk.diagnostic { - Some(super::diagnostic_color(severity, style)) + Some(super::diagnostic_style(severity, style).text) } else { highlight_style.underline }; @@ -669,11 +719,8 @@ impl Element for EditorElement { } }); - let line_number_layouts = if snapshot.mode == EditorMode::Full { - self.layout_line_numbers(start_row..end_row, &active_rows, &snapshot, cx) - } else { - Vec::new() - }; + let (line_number_layouts, block_layouts) = + self.layout_rows(start_row..end_row, &active_rows, &snapshot, cx); let mut max_visible_line_width = 0.0; let line_layouts = self.layout_lines(start_row..end_row, &mut snapshot, cx); @@ -695,6 +742,7 @@ impl Element for EditorElement { active_rows, line_layouts, line_number_layouts, + block_layouts, line_height, em_width, selections, @@ -817,6 +865,7 @@ pub struct LayoutState { active_rows: BTreeMap, line_layouts: Vec, line_number_layouts: Vec>, + block_layouts: Vec<(Range, BlockStyle)>, line_height: f32, em_width: f32, selections: HashMap>>, @@ -1071,11 +1120,11 @@ mod tests { }); let element = EditorElement::new(editor.downgrade(), settings); - let layouts = editor.update(cx, |editor, cx| { + let (layouts, _) = editor.update(cx, |editor, cx| { let snapshot = editor.snapshot(cx); let mut presenter = cx.build_presenter(window_id, 30.); let mut layout_cx = presenter.build_layout_context(false, cx); - element.layout_line_numbers(0..6, &Default::default(), &snapshot, &mut layout_cx) + element.layout_rows(0..6, &Default::default(), &snapshot, &mut layout_cx) }); assert_eq!(layouts.len(), 6); } diff --git a/crates/editor/src/lib.rs b/crates/editor/src/lib.rs index 52697427821ccd8afe07c418adf5290857cdf34f..e4eb4ffb75510a3491eb4bb14a20e00bb2421712 100644 --- a/crates/editor/src/lib.rs +++ b/crates/editor/src/lib.rs @@ -7,12 +7,11 @@ mod test; use buffer::rope::TextDimension; use clock::ReplicaId; -pub use display_map::DisplayPoint; use display_map::*; +pub use display_map::{DisplayPoint, DisplayRow}; pub use element::*; use gpui::{ action, - color::Color, geometry::vector::{vec2f, Vector2F}, keymap::Binding, text_layout, AppContext, ClipboardItem, Element, ElementBox, Entity, ModelHandle, @@ -33,7 +32,7 @@ use std::{ time::Duration, }; use sum_tree::Bias; -use theme::{EditorStyle, SyntaxTheme}; +use theme::{DiagnosticStyle, EditorStyle, SyntaxTheme}; use util::post_inc; const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500); @@ -342,6 +341,7 @@ struct BracketPairState { #[derive(Debug)] struct ActiveDiagnosticGroup { primary_range: Range, + group_range: Range, block_ids: HashSet, } @@ -2238,21 +2238,34 @@ impl Editor { loop { let next_group = buffer .diagnostics_in_range::<_, usize>(search_start..buffer.len()) - .filter(|(_, diagnostic)| diagnostic.is_primary) - .skip_while(|(range, _)| { - Some(range.end) == active_primary_range.as_ref().map(|r| *r.end()) - }) - .next() - .map(|(range, diagnostic)| (range, diagnostic.group_id)); + .find_map(|(range, diagnostic)| { + if diagnostic.is_primary + && Some(range.end) != active_primary_range.as_ref().map(|r| *r.end()) + { + Some((range, diagnostic.group_id)) + } else { + None + } + }); if let Some((primary_range, group_id)) = next_group { self.dismiss_diagnostics(cx); self.active_diagnostics = self.display_map.update(cx, |display_map, cx| { let buffer = self.buffer.read(cx); + + let mut group_end = Point::zero(); let diagnostic_group = buffer .diagnostic_group::(group_id) - .map(|(range, diagnostic)| (range, diagnostic.clone())) + .map(|(range, diagnostic)| { + if range.end > group_end { + group_end = range.end; + } + (range, diagnostic.clone()) + }) .collect::>(); + + let group_range = buffer.anchor_after(diagnostic_group[0].0.start) + ..buffer.anchor_before(group_end); let primary_range = buffer.anchor_after(primary_range.start) ..buffer.anchor_before(primary_range.end); @@ -2265,12 +2278,24 @@ impl Editor { BlockProperties { position: range.start, text: diagnostic.message.as_str(), - build_runs: Some(Arc::new(move |cx| { - let settings = build_settings.borrow()(cx); - vec![( - message_len, - diagnostic_color(severity, &settings.style).into(), - )] + build_runs: Some(Arc::new({ + let build_settings = build_settings.clone(); + move |cx| { + let settings = build_settings.borrow()(cx); + vec![( + message_len, + diagnostic_style(severity, &settings.style) + .text + .into(), + )] + } + })), + build_style: Some(Arc::new({ + let build_settings = build_settings.clone(); + move |cx| { + let settings = build_settings.borrow()(cx); + diagnostic_style(severity, &settings.style).block + } })), disposition: BlockDisposition::Below, } @@ -2282,6 +2307,7 @@ impl Editor { Some(ActiveDiagnosticGroup { primary_range, + group_range, block_ids, }) }); @@ -2815,8 +2841,8 @@ impl Snapshot { self.display_snapshot.buffer_row_count() } - pub fn buffer_rows(&self, start_row: u32) -> BufferRows { - self.display_snapshot.buffer_rows(start_row) + pub fn buffer_rows<'a>(&'a self, start_row: u32, cx: &'a AppContext) -> BufferRows<'a> { + self.display_snapshot.buffer_rows(start_row, Some(cx)) } pub fn chunks<'a>( @@ -2893,10 +2919,10 @@ impl EditorSettings { selection: Default::default(), guest_selections: Default::default(), syntax: Default::default(), - error_color: Default::default(), - warning_color: Default::default(), - information_color: Default::default(), - hint_color: Default::default(), + diagnostic_error: Default::default(), + diagnostic_warning: Default::default(), + diagnostic_information: Default::default(), + diagnostic_hint: Default::default(), } }, } @@ -3020,13 +3046,13 @@ impl SelectionExt for Selection { } } -pub fn diagnostic_color(severity: DiagnosticSeverity, style: &EditorStyle) -> Color { +pub fn diagnostic_style(severity: DiagnosticSeverity, style: &EditorStyle) -> DiagnosticStyle { match severity { - DiagnosticSeverity::ERROR => style.error_color, - DiagnosticSeverity::WARNING => style.warning_color, - DiagnosticSeverity::INFORMATION => style.information_color, - DiagnosticSeverity::HINT => style.hint_color, - _ => style.text.color, + DiagnosticSeverity::ERROR => style.diagnostic_error, + DiagnosticSeverity::WARNING => style.diagnostic_warning, + DiagnosticSeverity::INFORMATION => style.diagnostic_information, + DiagnosticSeverity::HINT => style.diagnostic_hint, + _ => Default::default(), } } diff --git a/crates/theme/src/lib.rs b/crates/theme/src/lib.rs index ab451b014141f1bcb046919c5604f36ff32732fc..8f48ebbc13b8a11ee5a44878466ba440d9c72663 100644 --- a/crates/theme/src/lib.rs +++ b/crates/theme/src/lib.rs @@ -227,12 +227,19 @@ pub struct EditorStyle { pub line_number_active: Color, pub guest_selections: Vec, pub syntax: Arc, - pub error_color: Color, - pub warning_color: Color, + pub diagnostic_error: DiagnosticStyle, + pub diagnostic_warning: DiagnosticStyle, #[serde(default)] - pub information_color: Color, + pub diagnostic_information: DiagnosticStyle, #[serde(default)] - pub hint_color: Color, + pub diagnostic_hint: DiagnosticStyle, +} + +#[derive(Copy, Clone, Deserialize, Default)] +pub struct DiagnosticStyle { + pub text: Color, + #[serde(flatten)] + pub block: BlockStyle, } #[derive(Clone, Copy, Default, Deserialize)] @@ -251,6 +258,12 @@ pub struct InputEditorStyle { pub selection: SelectionStyle, } +#[derive(Clone, Copy, Debug, Default, Deserialize, PartialEq, Eq)] +pub struct BlockStyle { + pub background: Option, + pub border: Option, +} + impl EditorStyle { pub fn placeholder_text(&self) -> &TextStyle { self.placeholder_text.as_ref().unwrap_or(&self.text) @@ -273,10 +286,10 @@ impl InputEditorStyle { line_number_active: Default::default(), guest_selections: Default::default(), syntax: Default::default(), - error_color: Default::default(), - warning_color: Default::default(), - information_color: Default::default(), - hint_color: Default::default(), + diagnostic_error: Default::default(), + diagnostic_warning: Default::default(), + diagnostic_information: Default::default(), + diagnostic_hint: Default::default(), } } } diff --git a/crates/zed/assets/themes/_base.toml b/crates/zed/assets/themes/_base.toml index 609228d86ded908c0c326e47ba51bf1b7b5d7ef7..2cef216a5fc4767a9c1af29a967cbcc5f175bd83 100644 --- a/crates/zed/assets/themes/_base.toml +++ b/crates/zed/assets/themes/_base.toml @@ -173,7 +173,7 @@ corner_radius = 6 [project_panel] extends = "$panel" -padding.top = 6 # ($workspace.tab.height - $project_panel.entry.height) / 2 +padding.top = 6 # ($workspace.tab.height - $project_panel.entry.height) / 2 [project_panel.entry] text = "$text.1" @@ -236,6 +236,7 @@ line_number_active = "$text.0.color" selection = "$selection.host" guest_selections = "$selection.guests" error_color = "$status.bad" -warning_color = "$status.warn" -info_color = "$status.info" -hint_color = "$status.info" +diagnostic_error = { text = "$status.bad", border = "#ff0000", background = "#ffdddd" } +diagnostic_warning = { text = "$status.warn", border = "#ffff00", background = "#ffffdd" } +diagnostic_info = { text = "$status.info" } +diagnostic_hint = { text = "$status.info" } From 8d5e3fb159912b57f49f4781c03ff1312f9b6b61 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 19 Nov 2021 11:29:10 +0100 Subject: [PATCH 101/109] Allow styling of the gutter for block lines --- crates/editor/src/element.rs | 70 +++++++++++++++++++++++------------- crates/theme/src/lib.rs | 2 ++ 2 files changed, 48 insertions(+), 24 deletions(-) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 7b138853197a9f2189537e18a2b608772d25d05a..8af043293e55a54313174ad2bac91d7657e69864 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -196,6 +196,7 @@ impl EditorElement { ) { let bounds = gutter_bounds.union_rect(text_bounds); let scroll_top = layout.snapshot.scroll_position().y() * layout.line_height; + let start_row = layout.snapshot.scroll_position().y() as u32; let editor = self.view(cx.app); let style = &self.settings.style; cx.scene.push_quad(Quad { @@ -240,6 +241,51 @@ impl EditorElement { } } } + + // Draw block backgrounds + for (ixs, block_style) in &layout.block_layouts { + let row = start_row + ixs.start; + let offset = vec2f(0., row as f32 * layout.line_height - scroll_top); + let height = ixs.len() as f32 * layout.line_height; + cx.scene.push_quad(Quad { + bounds: RectF::new( + text_bounds.origin() + offset, + vec2f(text_bounds.width(), height), + ), + background: block_style.background, + border: block_style + .border + .map_or(Default::default(), |color| Border { + width: 1., + color, + overlay: true, + top: true, + right: false, + bottom: true, + left: false, + }), + corner_radius: 0., + }); + cx.scene.push_quad(Quad { + bounds: RectF::new( + gutter_bounds.origin() + offset, + vec2f(gutter_bounds.width(), height), + ), + background: block_style.gutter_background, + border: block_style + .gutter_border + .map_or(Default::default(), |color| Border { + width: 1., + color, + overlay: true, + top: true, + right: false, + bottom: true, + left: false, + }), + corner_radius: 0., + }); + } } fn paint_gutter( @@ -360,30 +406,6 @@ impl EditorElement { } if let Some(visible_text_bounds) = bounds.intersection(visible_bounds) { - // Draw blocks - for (ixs, block_style) in &layout.block_layouts { - let row = start_row + ixs.start; - let origin = content_origin - + vec2f(-scroll_left, row as f32 * layout.line_height - scroll_top); - let height = ixs.len() as f32 * layout.line_height; - cx.scene.push_quad(Quad { - bounds: RectF::new(origin, vec2f(visible_text_bounds.width(), height)), - background: block_style.background, - border: block_style - .border - .map_or(Default::default(), |color| Border { - width: 1., - color, - overlay: true, - top: true, - right: false, - bottom: true, - left: false, - }), - corner_radius: 0., - }); - } - // Draw glyphs for (ix, line) in layout.line_layouts.iter().enumerate() { let row = start_row + ix as u32; diff --git a/crates/theme/src/lib.rs b/crates/theme/src/lib.rs index 8f48ebbc13b8a11ee5a44878466ba440d9c72663..baeaba2e6956f9f22c9749c2fd2d5555761a4758 100644 --- a/crates/theme/src/lib.rs +++ b/crates/theme/src/lib.rs @@ -262,6 +262,8 @@ pub struct InputEditorStyle { pub struct BlockStyle { pub background: Option, pub border: Option, + pub gutter_background: Option, + pub gutter_border: Option, } impl EditorStyle { From 2664dad2bc2b73f2b06b7454a49d4c555d05ed85 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 19 Nov 2021 14:35:00 +0100 Subject: [PATCH 102/109] Allow styling of invalid diagnostics Co-Authored-By: Nathan Sobo --- crates/editor/src/display_map/block_map.rs | 2 +- crates/editor/src/element.rs | 2 +- crates/editor/src/lib.rs | 36 ++++++++++++++-------- crates/lsp/src/lib.rs | 1 + crates/theme/src/lib.rs | 26 ++++++++++------ crates/zed/assets/themes/_base.toml | 14 ++++++--- 6 files changed, 52 insertions(+), 29 deletions(-) diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index 6f5300d3914d94afbde20a4467f22a4ee57116e7..62efc61eec766dceb2a394bdee389c5eb2b889e0 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -394,7 +394,7 @@ impl<'a> BlockMapWriter<'a> { let id = BlockId(self.0.next_block_id.fetch_add(1, SeqCst)); ids.push(id); - let position = buffer.anchor_before(block.position); + let position = buffer.anchor_after(block.position); let point = position.to_point(buffer); let start_row = wrap_snapshot .from_point(Point::new(point.row, 0), Bias::Left) diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 8af043293e55a54313174ad2bac91d7657e69864..e12e7e6f2f0277eb48a9f5cae4917aa3289f3840 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -613,7 +613,7 @@ impl EditorElement { } let underline = if let Some(severity) = chunk.diagnostic { - Some(super::diagnostic_style(severity, style).text) + Some(super::diagnostic_style(severity, false, style).text) } else { highlight_style.underline }; diff --git a/crates/editor/src/lib.rs b/crates/editor/src/lib.rs index e4eb4ffb75510a3491eb4bb14a20e00bb2421712..72cc75fa3948b2ac4728f6aa91b001ca9dd6266e 100644 --- a/crates/editor/src/lib.rs +++ b/crates/editor/src/lib.rs @@ -2284,7 +2284,7 @@ impl Editor { let settings = build_settings.borrow()(cx); vec![( message_len, - diagnostic_style(severity, &settings.style) + diagnostic_style(severity, false, &settings.style) .text .into(), )] @@ -2294,7 +2294,7 @@ impl Editor { let build_settings = build_settings.clone(); move |cx| { let settings = build_settings.borrow()(cx); - diagnostic_style(severity, &settings.style).block + diagnostic_style(severity, false, &settings.style).block } })), disposition: BlockDisposition::Below, @@ -2919,10 +2919,14 @@ impl EditorSettings { selection: Default::default(), guest_selections: Default::default(), syntax: Default::default(), - diagnostic_error: Default::default(), - diagnostic_warning: Default::default(), - diagnostic_information: Default::default(), - diagnostic_hint: Default::default(), + error_diagnostic: Default::default(), + invalid_error_diagnostic: Default::default(), + warning_diagnostic: Default::default(), + invalid_warning_diagnostic: Default::default(), + information_diagnostic: Default::default(), + invalid_information_diagnostic: Default::default(), + hint_diagnostic: Default::default(), + invalid_hint_diagnostic: Default::default(), } }, } @@ -3046,12 +3050,20 @@ impl SelectionExt for Selection { } } -pub fn diagnostic_style(severity: DiagnosticSeverity, style: &EditorStyle) -> DiagnosticStyle { - match severity { - DiagnosticSeverity::ERROR => style.diagnostic_error, - DiagnosticSeverity::WARNING => style.diagnostic_warning, - DiagnosticSeverity::INFORMATION => style.diagnostic_information, - DiagnosticSeverity::HINT => style.diagnostic_hint, +pub fn diagnostic_style( + severity: DiagnosticSeverity, + valid: bool, + style: &EditorStyle, +) -> DiagnosticStyle { + match (severity, valid) { + (DiagnosticSeverity::ERROR, true) => style.error_diagnostic, + (DiagnosticSeverity::ERROR, false) => style.invalid_error_diagnostic, + (DiagnosticSeverity::WARNING, true) => style.warning_diagnostic, + (DiagnosticSeverity::WARNING, false) => style.invalid_warning_diagnostic, + (DiagnosticSeverity::INFORMATION, true) => style.information_diagnostic, + (DiagnosticSeverity::INFORMATION, false) => style.invalid_information_diagnostic, + (DiagnosticSeverity::HINT, true) => style.hint_diagnostic, + (DiagnosticSeverity::HINT, false) => style.invalid_hint_diagnostic, _ => Default::default(), } } diff --git a/crates/lsp/src/lib.rs b/crates/lsp/src/lib.rs index ef5435d80c59491f7c271311f6e8a3847a53bab6..49653c25443ce3b23a0eb765b12413aac3385717 100644 --- a/crates/lsp/src/lib.rs +++ b/crates/lsp/src/lib.rs @@ -136,6 +136,7 @@ impl LanguageServer { buffer.resize(message_len, 0); stdout.read_exact(&mut buffer).await?; + println!("{}", std::str::from_utf8(&buffer).unwrap()); if let Ok(AnyNotification { method, params }) = serde_json::from_slice(&buffer) { diff --git a/crates/theme/src/lib.rs b/crates/theme/src/lib.rs index baeaba2e6956f9f22c9749c2fd2d5555761a4758..133475426cc188fcb0d7ac8e252ed26abe2b1136 100644 --- a/crates/theme/src/lib.rs +++ b/crates/theme/src/lib.rs @@ -227,12 +227,14 @@ pub struct EditorStyle { pub line_number_active: Color, pub guest_selections: Vec, pub syntax: Arc, - pub diagnostic_error: DiagnosticStyle, - pub diagnostic_warning: DiagnosticStyle, - #[serde(default)] - pub diagnostic_information: DiagnosticStyle, - #[serde(default)] - pub diagnostic_hint: DiagnosticStyle, + pub error_diagnostic: DiagnosticStyle, + pub invalid_error_diagnostic: DiagnosticStyle, + pub warning_diagnostic: DiagnosticStyle, + pub invalid_warning_diagnostic: DiagnosticStyle, + pub information_diagnostic: DiagnosticStyle, + pub invalid_information_diagnostic: DiagnosticStyle, + pub hint_diagnostic: DiagnosticStyle, + pub invalid_hint_diagnostic: DiagnosticStyle, } #[derive(Copy, Clone, Deserialize, Default)] @@ -288,10 +290,14 @@ impl InputEditorStyle { line_number_active: Default::default(), guest_selections: Default::default(), syntax: Default::default(), - diagnostic_error: Default::default(), - diagnostic_warning: Default::default(), - diagnostic_information: Default::default(), - diagnostic_hint: Default::default(), + error_diagnostic: Default::default(), + invalid_error_diagnostic: Default::default(), + warning_diagnostic: Default::default(), + invalid_warning_diagnostic: Default::default(), + information_diagnostic: Default::default(), + invalid_information_diagnostic: Default::default(), + hint_diagnostic: Default::default(), + invalid_hint_diagnostic: Default::default(), } } } diff --git a/crates/zed/assets/themes/_base.toml b/crates/zed/assets/themes/_base.toml index 2cef216a5fc4767a9c1af29a967cbcc5f175bd83..16861b74b67946ce40bf093ef0ff0f91b50467b0 100644 --- a/crates/zed/assets/themes/_base.toml +++ b/crates/zed/assets/themes/_base.toml @@ -173,7 +173,7 @@ corner_radius = 6 [project_panel] extends = "$panel" -padding.top = 6 # ($workspace.tab.height - $project_panel.entry.height) / 2 +padding.top = 6 # ($workspace.tab.height - $project_panel.entry.height) / 2 [project_panel.entry] text = "$text.1" @@ -236,7 +236,11 @@ line_number_active = "$text.0.color" selection = "$selection.host" guest_selections = "$selection.guests" error_color = "$status.bad" -diagnostic_error = { text = "$status.bad", border = "#ff0000", background = "#ffdddd" } -diagnostic_warning = { text = "$status.warn", border = "#ffff00", background = "#ffffdd" } -diagnostic_info = { text = "$status.info" } -diagnostic_hint = { text = "$status.info" } +error_diagnostic = { text = "$status.bad" } +invalid_error_diagnostic = { text = "$text.3.color" } +warning_diagnostic = { text = "$status.warn" } +invalid_warning_diagnostic = { text = "$text.3.color" } +information_diagnostic = { text = "$status.info" } +invalid_information_diagnostic = { text = "$text.3.color" } +hint_diagnostic = { text = "$status.info" } +invalid_hint_diagnostic = { text = "$text.3.color" } From dfbfa86548bb5dbbddff590f7ebb07302ef045fc Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 19 Nov 2021 15:31:33 +0100 Subject: [PATCH 103/109] WIP --- crates/editor/src/display_map.rs | 12 ++++- crates/editor/src/display_map/block_map.rs | 23 +++++++-- crates/editor/src/lib.rs | 60 ++++++++++++++++++++-- 3 files changed, 84 insertions(+), 11 deletions(-) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index 5eb3d108df1b57804a3f11f48168bfcd35e549a0..d671773d259bae412601534c0452ad9ede9b0b6d 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -10,7 +10,10 @@ use buffer::Rope; use fold_map::{FoldMap, ToFoldPoint as _}; use gpui::{fonts::FontId, AppContext, Entity, ModelContext, ModelHandle}; use language::{Anchor, Buffer, Point, ToOffset, ToPoint}; -use std::{collections::HashSet, ops::Range}; +use std::{ + collections::{HashMap, HashSet}, + ops::Range, +}; use sum_tree::Bias; use tab_map::TabMap; use theme::{BlockStyle, SyntaxTheme}; @@ -128,6 +131,13 @@ impl DisplayMap { block_map.insert(blocks, cx) } + pub fn restyle_blocks(&mut self, styles: HashMap>) + where + F: 'static + Fn(&AppContext) -> BlockStyle, + { + self.block_map.restyle(styles); + } + pub fn remove_blocks(&mut self, ids: HashSet, cx: &mut ModelContext) { let (snapshot, edits) = self.fold_map.read(cx); let (snapshot, edits) = self.tab_map.sync(snapshot, edits); diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index 62efc61eec766dceb2a394bdee389c5eb2b889e0..7e7cdc328f8981be9d063d5408cd0b4e4d70aa45 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -8,7 +8,7 @@ use language::{Buffer, Chunk}; use parking_lot::Mutex; use std::{ cmp::{self, Ordering}, - collections::HashSet, + collections::{HashMap, HashSet}, fmt::Debug, iter, ops::{Deref, Range}, @@ -53,7 +53,7 @@ pub struct Block { position: Anchor, text: Rope, build_runs: Option Vec<(usize, HighlightStyle)>>>, - build_style: Option BlockStyle>>, + build_style: Mutex BlockStyle>>>, disposition: BlockDisposition, } @@ -332,6 +332,19 @@ impl BlockMap { drop(cursor); *transforms = new_transforms; } + + pub fn restyle(&mut self, mut styles: HashMap>) + where + F: 'static + Fn(&AppContext) -> BlockStyle, + { + for block in &self.blocks { + if let Some(build_style) = styles.remove(&block.id) { + *block.build_style.lock() = build_style.map(|build_style| { + Arc::new(build_style) as Arc BlockStyle> + }); + } + } + } } fn push_isomorphic(tree: &mut SumTree, rows: u32) { @@ -421,7 +434,7 @@ impl<'a> BlockMapWriter<'a> { position, text: block.text.into(), build_runs: block.build_runs, - build_style: block.build_style, + build_style: Mutex::new(block.build_style), disposition: block.disposition, }), ); @@ -896,7 +909,7 @@ impl<'a> Iterator for BufferRows<'a> { if let Some(block) = &transform.block { let style = self .cx - .and_then(|cx| block.build_style.as_ref().map(|f| f(cx))); + .and_then(|cx| block.build_style.lock().as_ref().map(|f| f(cx))); Some(DisplayRow::Block(block.id, style)) } else { Some(self.input_buffer_rows.next().unwrap()) @@ -1017,7 +1030,7 @@ mod tests { id: BlockId(0), position: Anchor::min(), text: "one!\ntwo three\nfour".into(), - build_style: None, + build_style: Mutex::new(None), build_runs: Some(Arc::new(move |_| { vec![(3, red.into()), (6, Default::default()), (5, blue.into())] })), diff --git a/crates/editor/src/lib.rs b/crates/editor/src/lib.rs index 72cc75fa3948b2ac4728f6aa91b001ca9dd6266e..151dffa3242a0830a73fae0a2b616b1e1f239476 100644 --- a/crates/editor/src/lib.rs +++ b/crates/editor/src/lib.rs @@ -24,7 +24,7 @@ use smol::Timer; use std::{ cell::RefCell, cmp::{self, Ordering}, - collections::HashSet, + collections::{HashMap, HashSet}, iter, mem, ops::{Range, RangeInclusive}, rc::Rc, @@ -341,8 +341,10 @@ struct BracketPairState { #[derive(Debug)] struct ActiveDiagnosticGroup { primary_range: Range, + primary_message: String, + blocks: HashMap, group_range: Range, - block_ids: HashSet, + is_valid: bool, } #[derive(Serialize, Deserialize)] @@ -2268,13 +2270,17 @@ impl Editor { ..buffer.anchor_before(group_end); let primary_range = buffer.anchor_after(primary_range.start) ..buffer.anchor_before(primary_range.end); + let mut primary_message = None; - let block_ids = display_map + let blocks = display_map .insert_blocks( diagnostic_group.iter().map(|(range, diagnostic)| { let build_settings = self.build_settings.clone(); let message_len = diagnostic.message.len(); let severity = diagnostic.severity; + if diagnostic.is_primary { + primary_message = Some(diagnostic.message.clone()); + } BlockProperties { position: range.start, text: diagnostic.message.as_str(), @@ -2303,12 +2309,19 @@ impl Editor { cx, ) .into_iter() + .zip( + diagnostic_group + .into_iter() + .map(|(_, diagnostic)| diagnostic), + ) .collect(); Some(ActiveDiagnosticGroup { primary_range, + primary_message: primary_message.unwrap(), group_range, - block_ids, + blocks, + is_valid: true, }) }); @@ -2333,10 +2346,46 @@ impl Editor { } } + fn refresh_active_diagnostics(&mut self, cx: &mut ViewContext) { + if let Some(active_diagnostics) = self.active_diagnostics.as_mut() { + let buffer = self.buffer.read(cx); + let primary_range_start = active_diagnostics.primary_range.start.to_offset(buffer); + let matching_diagnostic = buffer + .diagnostics_in_range::<_, usize>(active_diagnostics.primary_range.clone()) + .find_map(|(range, diagnostic)| { + if diagnostic.is_primary + && range.start == primary_range_start + && diagnostic.message == active_diagnostics.primary_message + { + Some(diagnostic.group_id) + } else { + None + } + }); + if let Some(matching_diagnostic) = matching_diagnostic { + } else if active_diagnostics.is_valid { + let mut new_styles = HashMap::new(); + for (block_id, diagnostic) in &active_diagnostics.blocks { + let build_settings = self.build_settings.clone(); + let severity = diagnostic.severity; + new_styles.insert( + *block_id, + Some(move |cx: &AppContext| { + let settings = build_settings.borrow()(cx); + diagnostic_style(severity, false, &settings.style).block + }), + ); + } + self.display_map + .update(cx, |display_map, _| display_map.restyle_blocks(new_styles)); + } + } + } + fn dismiss_diagnostics(&mut self, cx: &mut ViewContext) { if let Some(active_diagnostic_group) = self.active_diagnostics.take() { self.display_map.update(cx, |display_map, cx| { - display_map.remove_blocks(active_diagnostic_group.block_ids, cx); + display_map.remove_blocks(active_diagnostic_group.blocks.into_keys().collect(), cx); }); cx.notify(); } @@ -2799,6 +2848,7 @@ impl Editor { } fn on_buffer_changed(&mut self, _: ModelHandle, cx: &mut ViewContext) { + self.refresh_active_diagnostics(cx); cx.notify(); } From 61d8848b3112a5a08cf9da46b0cbbac1ca86826d Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 19 Nov 2021 15:44:19 +0100 Subject: [PATCH 104/109] Make `BlockMap::sync` private Co-Authored-By: Nathan Sobo --- crates/editor/src/display_map.rs | 8 ++++---- crates/editor/src/display_map/block_map.rs | 2 +- crates/editor/src/lib.rs | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index d671773d259bae412601534c0452ad9ede9b0b6d..e0c11cafe9bc59203139233b8e3a825405f3f825 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -85,13 +85,13 @@ impl DisplayMap { let (snapshot, edits) = self .wrap_map .update(cx, |map, cx| map.sync(snapshot, edits, cx)); - self.block_map.sync(&snapshot, edits, cx); + self.block_map.read(snapshot, edits, cx); let (snapshot, edits) = fold_map.fold(ranges, cx); let (snapshot, edits) = self.tab_map.sync(snapshot, edits); let (snapshot, edits) = self .wrap_map .update(cx, |map, cx| map.sync(snapshot, edits, cx)); - self.block_map.sync(&snapshot, edits, cx); + self.block_map.read(snapshot, edits, cx); } pub fn unfold( @@ -104,13 +104,13 @@ impl DisplayMap { let (snapshot, edits) = self .wrap_map .update(cx, |map, cx| map.sync(snapshot, edits, cx)); - self.block_map.sync(&snapshot, edits, cx); + self.block_map.read(snapshot, edits, cx); let (snapshot, edits) = fold_map.unfold(ranges, cx); let (snapshot, edits) = self.tab_map.sync(snapshot, edits); let (snapshot, edits) = self .wrap_map .update(cx, |map, cx| map.sync(snapshot, edits, cx)); - self.block_map.sync(&snapshot, edits, cx); + self.block_map.read(snapshot, edits, cx); } pub fn insert_blocks( diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index 7e7cdc328f8981be9d063d5408cd0b4e4d70aa45..3d40bf74d7e78980cdab63adec464fff270a124f 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -163,7 +163,7 @@ impl BlockMap { BlockMapWriter(self) } - pub fn sync(&self, wrap_snapshot: &WrapSnapshot, edits: Vec, cx: &AppContext) { + fn sync(&self, wrap_snapshot: &WrapSnapshot, edits: Vec, cx: &AppContext) { if edits.is_empty() { return; } diff --git a/crates/editor/src/lib.rs b/crates/editor/src/lib.rs index 151dffa3242a0830a73fae0a2b616b1e1f239476..911e83b39b67a96b8d8a2484e8e1cea3e0628491 100644 --- a/crates/editor/src/lib.rs +++ b/crates/editor/src/lib.rs @@ -24,7 +24,7 @@ use smol::Timer; use std::{ cell::RefCell, cmp::{self, Ordering}, - collections::{HashMap, HashSet}, + collections::HashMap, iter, mem, ops::{Range, RangeInclusive}, rc::Rc, From 8e74cc178ed0118712407678882c586324696205 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 19 Nov 2021 16:15:22 +0100 Subject: [PATCH 105/109] Invalidate active diagnostics when they are removed Co-Authored-By: Nathan Sobo --- crates/editor/src/display_map.rs | 10 +- crates/editor/src/display_map/block_map.rs | 19 +- crates/editor/src/element.rs | 2 +- crates/editor/src/lib.rs | 229 ++++++++++++--------- crates/lsp/src/lib.rs | 1 - 5 files changed, 148 insertions(+), 113 deletions(-) diff --git a/crates/editor/src/display_map.rs b/crates/editor/src/display_map.rs index e0c11cafe9bc59203139233b8e3a825405f3f825..14fa4a9146da5665f8e75a0defba3c96a6d58680 100644 --- a/crates/editor/src/display_map.rs +++ b/crates/editor/src/display_map.rs @@ -8,7 +8,10 @@ pub use block_map::{BlockDisposition, BlockId, BlockProperties, BufferRows, Chun use block_map::{BlockMap, BlockPoint}; use buffer::Rope; use fold_map::{FoldMap, ToFoldPoint as _}; -use gpui::{fonts::FontId, AppContext, Entity, ModelContext, ModelHandle}; +use gpui::{ + fonts::{FontId, HighlightStyle}, + AppContext, Entity, ModelContext, ModelHandle, +}; use language::{Anchor, Buffer, Point, ToOffset, ToPoint}; use std::{ collections::{HashMap, HashSet}, @@ -131,9 +134,10 @@ impl DisplayMap { block_map.insert(blocks, cx) } - pub fn restyle_blocks(&mut self, styles: HashMap>) + pub fn restyle_blocks(&mut self, styles: HashMap, Option)>) where - F: 'static + Fn(&AppContext) -> BlockStyle, + F1: 'static + Fn(&AppContext) -> Vec<(usize, HighlightStyle)>, + F2: 'static + Fn(&AppContext) -> BlockStyle, { self.block_map.restyle(styles); } diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index 3d40bf74d7e78980cdab63adec464fff270a124f..1f116abe4aeab04f7918614f11903cf7ff101d08 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -52,7 +52,7 @@ pub struct Block { id: BlockId, position: Anchor, text: Rope, - build_runs: Option Vec<(usize, HighlightStyle)>>>, + build_runs: Mutex Vec<(usize, HighlightStyle)>>>>, build_style: Mutex BlockStyle>>>, disposition: BlockDisposition, } @@ -333,12 +333,16 @@ impl BlockMap { *transforms = new_transforms; } - pub fn restyle(&mut self, mut styles: HashMap>) + pub fn restyle(&mut self, mut styles: HashMap, Option)>) where - F: 'static + Fn(&AppContext) -> BlockStyle, + F1: 'static + Fn(&AppContext) -> Vec<(usize, HighlightStyle)>, + F2: 'static + Fn(&AppContext) -> BlockStyle, { for block in &self.blocks { - if let Some(build_style) = styles.remove(&block.id) { + if let Some((build_runs, build_style)) = styles.remove(&block.id) { + *block.build_runs.lock() = build_runs.map(|build_runs| { + Arc::new(build_runs) as Arc Vec<(usize, HighlightStyle)>> + }); *block.build_style.lock() = build_style.map(|build_style| { Arc::new(build_style) as Arc BlockStyle> }); @@ -433,7 +437,7 @@ impl<'a> BlockMapWriter<'a> { id, position, text: block.text.into(), - build_runs: block.build_runs, + build_runs: Mutex::new(block.build_runs), build_style: Mutex::new(block.build_style), disposition: block.disposition, }), @@ -800,6 +804,7 @@ impl<'a> BlockChunks<'a> { let mut runs = block .build_runs + .lock() .as_ref() .zip(cx) .map(|(build_runs, cx)| build_runs(cx)) @@ -1031,9 +1036,9 @@ mod tests { position: Anchor::min(), text: "one!\ntwo three\nfour".into(), build_style: Mutex::new(None), - build_runs: Some(Arc::new(move |_| { + build_runs: Mutex::new(Some(Arc::new(move |_| { vec![(3, red.into()), (6, Default::default()), (5, blue.into())] - })), + }))), disposition: BlockDisposition::Above, }), }; diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index e12e7e6f2f0277eb48a9f5cae4917aa3289f3840..8927f9efdd3392282ce9856853b1f864e6f348e5 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -613,7 +613,7 @@ impl EditorElement { } let underline = if let Some(severity) = chunk.diagnostic { - Some(super::diagnostic_style(severity, false, style).text) + Some(super::diagnostic_style(severity, true, style).text) } else { highlight_style.underline }; diff --git a/crates/editor/src/lib.rs b/crates/editor/src/lib.rs index 911e83b39b67a96b8d8a2484e8e1cea3e0628491..e8dfd479c199b994a4bad85dbdac9648b85801d6 100644 --- a/crates/editor/src/lib.rs +++ b/crates/editor/src/lib.rs @@ -345,6 +345,7 @@ struct ActiveDiagnosticGroup { blocks: HashMap, group_range: Range, is_valid: bool, + update_count: usize, } #[derive(Serialize, Deserialize)] @@ -2251,80 +2252,7 @@ impl Editor { }); if let Some((primary_range, group_id)) = next_group { - self.dismiss_diagnostics(cx); - self.active_diagnostics = self.display_map.update(cx, |display_map, cx| { - let buffer = self.buffer.read(cx); - - let mut group_end = Point::zero(); - let diagnostic_group = buffer - .diagnostic_group::(group_id) - .map(|(range, diagnostic)| { - if range.end > group_end { - group_end = range.end; - } - (range, diagnostic.clone()) - }) - .collect::>(); - - let group_range = buffer.anchor_after(diagnostic_group[0].0.start) - ..buffer.anchor_before(group_end); - let primary_range = buffer.anchor_after(primary_range.start) - ..buffer.anchor_before(primary_range.end); - let mut primary_message = None; - - let blocks = display_map - .insert_blocks( - diagnostic_group.iter().map(|(range, diagnostic)| { - let build_settings = self.build_settings.clone(); - let message_len = diagnostic.message.len(); - let severity = diagnostic.severity; - if diagnostic.is_primary { - primary_message = Some(diagnostic.message.clone()); - } - BlockProperties { - position: range.start, - text: diagnostic.message.as_str(), - build_runs: Some(Arc::new({ - let build_settings = build_settings.clone(); - move |cx| { - let settings = build_settings.borrow()(cx); - vec![( - message_len, - diagnostic_style(severity, false, &settings.style) - .text - .into(), - )] - } - })), - build_style: Some(Arc::new({ - let build_settings = build_settings.clone(); - move |cx| { - let settings = build_settings.borrow()(cx); - diagnostic_style(severity, false, &settings.style).block - } - })), - disposition: BlockDisposition::Below, - } - }), - cx, - ) - .into_iter() - .zip( - diagnostic_group - .into_iter() - .map(|(_, diagnostic)| diagnostic), - ) - .collect(); - - Some(ActiveDiagnosticGroup { - primary_range, - primary_message: primary_message.unwrap(), - group_range, - blocks, - is_valid: true, - }) - }); - + self.activate_diagnostics(group_id, cx); self.update_selections( vec![Selection { id: selection.id, @@ -2349,39 +2277,138 @@ impl Editor { fn refresh_active_diagnostics(&mut self, cx: &mut ViewContext) { if let Some(active_diagnostics) = self.active_diagnostics.as_mut() { let buffer = self.buffer.read(cx); - let primary_range_start = active_diagnostics.primary_range.start.to_offset(buffer); - let matching_diagnostic = buffer - .diagnostics_in_range::<_, usize>(active_diagnostics.primary_range.clone()) - .find_map(|(range, diagnostic)| { - if diagnostic.is_primary - && range.start == primary_range_start - && diagnostic.message == active_diagnostics.primary_message - { - Some(diagnostic.group_id) - } else { - None + let update_count = buffer.diagnostics_update_count(); + if update_count > active_diagnostics.update_count { + active_diagnostics.update_count = update_count; + let primary_range_start = active_diagnostics.primary_range.start.to_offset(buffer); + let is_valid = buffer + .diagnostics_in_range::<_, usize>(active_diagnostics.primary_range.clone()) + .any(|(range, diagnostic)| { + diagnostic.is_primary + && range.start == primary_range_start + && diagnostic.message == active_diagnostics.primary_message + }); + + if is_valid != active_diagnostics.is_valid { + active_diagnostics.is_valid = is_valid; + let mut new_styles = HashMap::new(); + for (block_id, diagnostic) in &active_diagnostics.blocks { + let severity = diagnostic.severity; + let message_len = diagnostic.message.len(); + new_styles.insert( + *block_id, + ( + Some({ + let build_settings = self.build_settings.clone(); + move |cx: &AppContext| { + let settings = build_settings.borrow()(cx); + vec![( + message_len, + diagnostic_style(severity, is_valid, &settings.style) + .text + .into(), + )] + } + }), + Some({ + let build_settings = self.build_settings.clone(); + move |cx: &AppContext| { + let settings = build_settings.borrow()(cx); + diagnostic_style(severity, is_valid, &settings.style).block + } + }), + ), + ); } - }); - if let Some(matching_diagnostic) = matching_diagnostic { - } else if active_diagnostics.is_valid { - let mut new_styles = HashMap::new(); - for (block_id, diagnostic) in &active_diagnostics.blocks { - let build_settings = self.build_settings.clone(); - let severity = diagnostic.severity; - new_styles.insert( - *block_id, - Some(move |cx: &AppContext| { - let settings = build_settings.borrow()(cx); - diagnostic_style(severity, false, &settings.style).block - }), - ); + self.display_map + .update(cx, |display_map, _| display_map.restyle_blocks(new_styles)); } - self.display_map - .update(cx, |display_map, _| display_map.restyle_blocks(new_styles)); } } } + fn activate_diagnostics(&mut self, group_id: usize, cx: &mut ViewContext) { + self.dismiss_diagnostics(cx); + self.active_diagnostics = self.display_map.update(cx, |display_map, cx| { + let buffer = self.buffer.read(cx); + + let update_count = buffer.diagnostics_update_count(); + let mut primary_range = None; + let mut primary_message = None; + let mut group_end = Point::zero(); + let diagnostic_group = buffer + .diagnostic_group::(group_id) + .map(|(range, diagnostic)| { + if range.end > group_end { + group_end = range.end; + } + if diagnostic.is_primary { + primary_range = Some(range.clone()); + primary_message = Some(diagnostic.message.clone()); + } + (range, diagnostic.clone()) + }) + .collect::>(); + let primary_range = primary_range.unwrap(); + let primary_message = primary_message.unwrap(); + + let group_range = + buffer.anchor_after(diagnostic_group[0].0.start)..buffer.anchor_before(group_end); + let primary_range = + buffer.anchor_after(primary_range.start)..buffer.anchor_before(primary_range.end); + + let blocks = display_map + .insert_blocks( + diagnostic_group.iter().map(|(range, diagnostic)| { + let build_settings = self.build_settings.clone(); + let message_len = diagnostic.message.len(); + let severity = diagnostic.severity; + BlockProperties { + position: range.start, + text: diagnostic.message.as_str(), + build_runs: Some(Arc::new({ + let build_settings = build_settings.clone(); + move |cx| { + let settings = build_settings.borrow()(cx); + vec![( + message_len, + diagnostic_style(severity, true, &settings.style) + .text + .into(), + )] + } + })), + build_style: Some(Arc::new({ + let build_settings = build_settings.clone(); + move |cx| { + let settings = build_settings.borrow()(cx); + diagnostic_style(severity, true, &settings.style).block + } + })), + disposition: BlockDisposition::Below, + } + }), + cx, + ) + .into_iter() + .zip( + diagnostic_group + .into_iter() + .map(|(_, diagnostic)| diagnostic), + ) + .collect(); + + Some(ActiveDiagnosticGroup { + primary_range, + primary_message, + group_range, + blocks, + is_valid: true, + update_count, + }) + }); + } + fn dismiss_diagnostics(&mut self, cx: &mut ViewContext) { if let Some(active_diagnostic_group) = self.active_diagnostics.take() { self.display_map.update(cx, |display_map, cx| { diff --git a/crates/lsp/src/lib.rs b/crates/lsp/src/lib.rs index 49653c25443ce3b23a0eb765b12413aac3385717..ef5435d80c59491f7c271311f6e8a3847a53bab6 100644 --- a/crates/lsp/src/lib.rs +++ b/crates/lsp/src/lib.rs @@ -136,7 +136,6 @@ impl LanguageServer { buffer.resize(message_len, 0); stdout.read_exact(&mut buffer).await?; - println!("{}", std::str::from_utf8(&buffer).unwrap()); if let Ok(AnyNotification { method, params }) = serde_json::from_slice(&buffer) { From a023950f282dd09d3787c1bad34dbe8ed04ec564 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 19 Nov 2021 16:17:50 +0100 Subject: [PATCH 106/109] Remove unused `group_range` field Co-Authored-By: Nathan Sobo --- crates/editor/src/lib.rs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/crates/editor/src/lib.rs b/crates/editor/src/lib.rs index e8dfd479c199b994a4bad85dbdac9648b85801d6..a3dba342c95a146a908bab58cd10e34b6de015d7 100644 --- a/crates/editor/src/lib.rs +++ b/crates/editor/src/lib.rs @@ -343,7 +343,6 @@ struct ActiveDiagnosticGroup { primary_range: Range, primary_message: String, blocks: HashMap, - group_range: Range, is_valid: bool, update_count: usize, } @@ -2351,9 +2350,6 @@ impl Editor { .collect::>(); let primary_range = primary_range.unwrap(); let primary_message = primary_message.unwrap(); - - let group_range = - buffer.anchor_after(diagnostic_group[0].0.start)..buffer.anchor_before(group_end); let primary_range = buffer.anchor_after(primary_range.start)..buffer.anchor_before(primary_range.end); @@ -2401,7 +2397,6 @@ impl Editor { Some(ActiveDiagnosticGroup { primary_range, primary_message, - group_range, blocks, is_valid: true, update_count, From b0afc8067846837a63686c8ef77b394fe2bb5634 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 19 Nov 2021 16:30:26 +0100 Subject: [PATCH 107/109] Ignore diagnostics with empty ranges Co-Authored-By: Nathan Sobo --- crates/editor/src/lib.rs | 1 + crates/workspace/src/items.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/crates/editor/src/lib.rs b/crates/editor/src/lib.rs index a3dba342c95a146a908bab58cd10e34b6de015d7..18a356d7cbbaa1b32d4830307b07cead05524a8d 100644 --- a/crates/editor/src/lib.rs +++ b/crates/editor/src/lib.rs @@ -2242,6 +2242,7 @@ impl Editor { .diagnostics_in_range::<_, usize>(search_start..buffer.len()) .find_map(|(range, diagnostic)| { if diagnostic.is_primary + && !range.is_empty() && Some(range.end) != active_primary_range.as_ref().map(|r| *r.end()) { Some((range, diagnostic.group_id)) diff --git a/crates/workspace/src/items.rs b/crates/workspace/src/items.rs index 60e2e9d767a07ce2a25e5f142b7f94fccf3be6ac..58358cdf4722e1a913dfede81b61e419e425dcdb 100644 --- a/crates/workspace/src/items.rs +++ b/crates/workspace/src/items.rs @@ -263,6 +263,7 @@ impl DiagnosticMessage { .buffer() .read(cx) .diagnostics_in_range::(cursor_position..cursor_position) + .filter(|(range, _)| !range.is_empty()) .min_by_key(|(range, diagnostic)| (diagnostic.severity, range.len())) .map(|(_, diagnostic)| diagnostic.clone()); if new_diagnostic != self.diagnostic { From c844fcdc09ff0eddc861accd2ef51a88e4a837c8 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 19 Nov 2021 16:38:23 +0100 Subject: [PATCH 108/109] Invalidate active diagnostic when its primary range collapses Co-Authored-By: Nathan Sobo --- crates/editor/src/lib.rs | 88 +++++++++++++++++++--------------------- 1 file changed, 41 insertions(+), 47 deletions(-) diff --git a/crates/editor/src/lib.rs b/crates/editor/src/lib.rs index 18a356d7cbbaa1b32d4830307b07cead05524a8d..09ca22f76081c20969524da0cae28edf4f066006 100644 --- a/crates/editor/src/lib.rs +++ b/crates/editor/src/lib.rs @@ -344,7 +344,6 @@ struct ActiveDiagnosticGroup { primary_message: String, blocks: HashMap, is_valid: bool, - update_count: usize, } #[derive(Serialize, Deserialize)] @@ -2277,52 +2276,49 @@ impl Editor { fn refresh_active_diagnostics(&mut self, cx: &mut ViewContext) { if let Some(active_diagnostics) = self.active_diagnostics.as_mut() { let buffer = self.buffer.read(cx); - let update_count = buffer.diagnostics_update_count(); - if update_count > active_diagnostics.update_count { - active_diagnostics.update_count = update_count; - let primary_range_start = active_diagnostics.primary_range.start.to_offset(buffer); - let is_valid = buffer - .diagnostics_in_range::<_, usize>(active_diagnostics.primary_range.clone()) - .any(|(range, diagnostic)| { - diagnostic.is_primary - && range.start == primary_range_start - && diagnostic.message == active_diagnostics.primary_message - }); + let primary_range_start = active_diagnostics.primary_range.start.to_offset(buffer); + let is_valid = buffer + .diagnostics_in_range::<_, usize>(active_diagnostics.primary_range.clone()) + .any(|(range, diagnostic)| { + diagnostic.is_primary + && !range.is_empty() + && range.start == primary_range_start + && diagnostic.message == active_diagnostics.primary_message + }); - if is_valid != active_diagnostics.is_valid { - active_diagnostics.is_valid = is_valid; - let mut new_styles = HashMap::new(); - for (block_id, diagnostic) in &active_diagnostics.blocks { - let severity = diagnostic.severity; - let message_len = diagnostic.message.len(); - new_styles.insert( - *block_id, - ( - Some({ - let build_settings = self.build_settings.clone(); - move |cx: &AppContext| { - let settings = build_settings.borrow()(cx); - vec![( - message_len, - diagnostic_style(severity, is_valid, &settings.style) - .text - .into(), - )] - } - }), - Some({ - let build_settings = self.build_settings.clone(); - move |cx: &AppContext| { - let settings = build_settings.borrow()(cx); - diagnostic_style(severity, is_valid, &settings.style).block - } - }), - ), - ); - } - self.display_map - .update(cx, |display_map, _| display_map.restyle_blocks(new_styles)); + if is_valid != active_diagnostics.is_valid { + active_diagnostics.is_valid = is_valid; + let mut new_styles = HashMap::new(); + for (block_id, diagnostic) in &active_diagnostics.blocks { + let severity = diagnostic.severity; + let message_len = diagnostic.message.len(); + new_styles.insert( + *block_id, + ( + Some({ + let build_settings = self.build_settings.clone(); + move |cx: &AppContext| { + let settings = build_settings.borrow()(cx); + vec![( + message_len, + diagnostic_style(severity, is_valid, &settings.style) + .text + .into(), + )] + } + }), + Some({ + let build_settings = self.build_settings.clone(); + move |cx: &AppContext| { + let settings = build_settings.borrow()(cx); + diagnostic_style(severity, is_valid, &settings.style).block + } + }), + ), + ); } + self.display_map + .update(cx, |display_map, _| display_map.restyle_blocks(new_styles)); } } } @@ -2332,7 +2328,6 @@ impl Editor { self.active_diagnostics = self.display_map.update(cx, |display_map, cx| { let buffer = self.buffer.read(cx); - let update_count = buffer.diagnostics_update_count(); let mut primary_range = None; let mut primary_message = None; let mut group_end = Point::zero(); @@ -2400,7 +2395,6 @@ impl Editor { primary_message, blocks, is_valid: true, - update_count, }) }); } From 6f5ca6064be2f925744716a571eff00f99b55e64 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Fri, 19 Nov 2021 18:04:31 +0100 Subject: [PATCH 109/109] Use `anchor_after` in randomized tests to match `BlockMap` --- crates/editor/src/display_map/block_map.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/editor/src/display_map/block_map.rs b/crates/editor/src/display_map/block_map.rs index 1f116abe4aeab04f7918614f11903cf7ff101d08..b45274d691ef8c4c3885b59ee0be221bb16331fc 100644 --- a/crates/editor/src/display_map/block_map.rs +++ b/crates/editor/src/display_map/block_map.rs @@ -1340,7 +1340,7 @@ mod tests { let block_properties = (0..block_count) .map(|_| { let buffer = buffer.read(cx); - let position = buffer.anchor_before( + let position = buffer.anchor_after( buffer.clip_offset(rng.gen_range(0..=buffer.len()), Bias::Left), );