diff --git a/crates/diagnostics/src/diagnostics.rs b/crates/diagnostics/src/diagnostics.rs index a2801a8502..ad527fe3c7 100644 --- a/crates/diagnostics/src/diagnostics.rs +++ b/crates/diagnostics/src/diagnostics.rs @@ -579,7 +579,7 @@ mod tests { use super::*; use client::{http::ServerResponse, test::FakeHttpClient, Client, UserStore}; use gpui::TestAppContext; - use language::{Diagnostic, DiagnosticEntry, DiagnosticSeverity, LanguageRegistry, PointUtf16}; + use language::{Diagnostic, DiagnosticEntry, DiagnosticSeverity, LanguageRegistry}; use project::FakeFs; use serde_json::json; use std::sync::Arc; @@ -637,13 +637,11 @@ mod tests { worktree.update(&mut cx, |worktree, cx| { worktree - .update_point_utf16_diagnostics( - "lsp".into(), + .update_diagnostics_from_provider( Arc::from("/test/main.rs".as_ref()), - None, vec![ DiagnosticEntry { - range: PointUtf16::new(1, 8)..PointUtf16::new(1, 9), + range: 20..21, diagnostic: Diagnostic { message: "move occurs because `x` has type `Vec`, which does not implement the `Copy` trait" @@ -655,7 +653,7 @@ mod tests { }, }, DiagnosticEntry { - range: PointUtf16::new(2, 8)..PointUtf16::new(2, 9), + range: 40..41, diagnostic: Diagnostic { message: "move occurs because `y` has type `Vec`, which does not implement the `Copy` trait" @@ -667,7 +665,7 @@ mod tests { }, }, DiagnosticEntry { - range: PointUtf16::new(3, 6)..PointUtf16::new(3, 7), + range: 58..59, diagnostic: Diagnostic { message: "value moved here".to_string(), severity: DiagnosticSeverity::INFORMATION, @@ -677,7 +675,7 @@ mod tests { }, }, DiagnosticEntry { - range: PointUtf16::new(4, 6)..PointUtf16::new(4, 7), + range: 68..69, diagnostic: Diagnostic { message: "value moved here".to_string(), severity: DiagnosticSeverity::INFORMATION, @@ -687,7 +685,7 @@ mod tests { }, }, DiagnosticEntry { - range: PointUtf16::new(7, 6)..PointUtf16::new(7, 7), + range: 112..113, diagnostic: Diagnostic { message: "use of moved value\nvalue used here after move".to_string(), severity: DiagnosticSeverity::ERROR, @@ -697,7 +695,7 @@ mod tests { }, }, DiagnosticEntry { - range: PointUtf16::new(8, 6)..PointUtf16::new(8, 7), + range: 122..123, diagnostic: Diagnostic { message: "use of moved value\nvalue used here after move".to_string(), severity: DiagnosticSeverity::ERROR, @@ -764,12 +762,10 @@ mod tests { worktree.update(&mut cx, |worktree, cx| { worktree - .update_point_utf16_diagnostics( - "lsp".into(), + .update_diagnostics_from_provider( Arc::from("/test/a.rs".as_ref()), - None, vec![DiagnosticEntry { - range: PointUtf16::new(0, 15)..PointUtf16::new(0, 15), + range: 15..15, diagnostic: Diagnostic { message: "mismatched types\nexpected `usize`, found `char`".to_string(), severity: DiagnosticSeverity::ERROR, diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 461c88ae42..8bc894bf2b 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -23,7 +23,7 @@ use std::{ ffi::OsString, future::Future, iter::{Iterator, Peekable}, - ops::{Deref, DerefMut, Range}, + ops::{Add, Deref, DerefMut, Range, Sub}, path::{Path, PathBuf}, str, sync::Arc, @@ -31,7 +31,7 @@ use std::{ vec, }; use sum_tree::TreeMap; -use text::operation_queue::OperationQueue; +use text::{operation_queue::OperationQueue, rope::TextDimension}; pub use text::{Buffer as TextBuffer, Operation as _, *}; use theme::SyntaxTheme; use tree_sitter::{InputEdit, Parser, QueryCursor, Tree}; @@ -85,6 +85,12 @@ pub struct BufferSnapshot { parse_count: usize, } +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct GroupId { + source: Arc, + id: usize, +} + #[derive(Clone, Debug, PartialEq, Eq)] pub struct Diagnostic { pub code: Option, @@ -740,13 +746,16 @@ impl Buffer { self.diagnostic_sets.iter().flat_map(|set| set.iter()) } - pub fn update_diagnostics( + pub fn update_diagnostics( &mut self, provider_name: Arc, version: Option, - mut diagnostics: Vec>, + mut diagnostics: Vec>, cx: &mut ModelContext, - ) -> Result { + ) -> Result + where + T: ToPoint + Ord + Clip + TextDimension + Add + Sub + Copy, + { fn compare_diagnostics(a: &Diagnostic, b: &Diagnostic) -> Ordering { Ordering::Equal .then_with(|| b.is_primary.cmp(&a.is_primary)) @@ -755,13 +764,6 @@ impl Buffer { .then_with(|| a.message.cmp(&b.message)) } - diagnostics.sort_unstable_by(|a, b| { - Ordering::Equal - .then_with(|| a.range.start.cmp(&b.range.start)) - .then_with(|| b.range.end.cmp(&a.range.end)) - .then_with(|| compare_diagnostics(&a.diagnostic, &b.diagnostic)) - }); - let version = version.map(|version| version as usize); let content = if let Some(version) = version { let language_server = self.language_server.as_mut().unwrap(); @@ -777,14 +779,18 @@ impl Buffer { self.deref() }; - let mut edits_since_save = content - .edits_since::(&self.saved_version) - .peekable(); - let mut last_edit_old_end = PointUtf16::zero(); - let mut last_edit_new_end = PointUtf16::zero(); - let mut ix = 0; - 'outer: while ix < diagnostics.len() { - let entry = &mut diagnostics[ix]; + diagnostics.sort_unstable_by(|a, b| { + Ordering::Equal + .then_with(|| a.range.start.cmp(&b.range.start)) + .then_with(|| b.range.end.cmp(&a.range.end)) + .then_with(|| compare_diagnostics(&a.diagnostic, &b.diagnostic)) + }); + + let mut sanitized_diagnostics = Vec::new(); + let mut edits_since_save = content.edits_since::(&self.saved_version).peekable(); + let mut last_edit_old_end = T::default(); + let mut last_edit_new_end = T::default(); + 'outer: for entry in diagnostics { let mut start = entry.range.start; let mut end = entry.range.end; @@ -798,7 +804,6 @@ impl Buffer { last_edit_new_end = edit.new.end; edits_since_save.next(); } else if edit.old.start <= end && edit.old.end >= start { - diagnostics.remove(ix); continue 'outer; } else { break; @@ -809,23 +814,26 @@ impl Buffer { end = last_edit_new_end + (end - last_edit_old_end); } - entry.range = content.clip_point_utf16(start, Bias::Left) - ..content.clip_point_utf16(end, Bias::Right); - + let range = start.clip(Bias::Left, content)..end.clip(Bias::Right, content); + let mut range = range.start.to_point(content)..range.end.to_point(content); // Expand empty ranges by one character - if entry.range.start == entry.range.end { - entry.range.end.column += 1; - entry.range.end = content.clip_point_utf16(entry.range.end, Bias::Right); - if entry.range.start == entry.range.end && entry.range.end.column > 0 { - entry.range.start.column -= 1; - entry.range.start = content.clip_point_utf16(entry.range.start, Bias::Left); + if range.start == range.end { + range.end.column += 1; + range.end = content.clip_point(range.end, Bias::Right); + if range.start == range.end && range.end.column > 0 { + range.start.column -= 1; + range.start = content.clip_point(range.start, Bias::Left); } } - ix += 1; + + sanitized_diagnostics.push(DiagnosticEntry { + range, + diagnostic: entry.diagnostic, + }); } drop(edits_since_save); - let set = DiagnosticSet::new(provider_name, diagnostics, content); + let set = DiagnosticSet::new(provider_name, sanitized_diagnostics, content); self.apply_diagnostic_update(set.clone(), cx); Ok(Operation::UpdateDiagnostics { provider_name: set.provider_name().to_string(), diff --git a/crates/language/src/diagnostic_set.rs b/crates/language/src/diagnostic_set.rs index 7cd13c00dd..05e19e635a 100644 --- a/crates/language/src/diagnostic_set.rs +++ b/crates/language/src/diagnostic_set.rs @@ -7,7 +7,7 @@ use std::{ sync::Arc, }; use sum_tree::{self, Bias, SumTree}; -use text::{Anchor, FromAnchor, PointUtf16, ToOffset}; +use text::{Anchor, FromAnchor, Point, ToOffset}; #[derive(Clone, Debug)] pub struct DiagnosticSet { @@ -56,7 +56,7 @@ impl DiagnosticSet { pub fn new(provider_name: Arc, iter: I, buffer: &text::BufferSnapshot) -> Self where - I: IntoIterator>, + I: IntoIterator>, { let mut entries = iter.into_iter().collect::>(); entries.sort_unstable_by_key(|entry| (entry.range.start, Reverse(entry.range.end))); diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index 6303cfd374..26c0e84261 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -65,9 +65,7 @@ pub struct BracketPair { } #[async_trait] -pub trait DiagnosticSource: 'static + Send + Sync { - fn name(&self) -> Arc; - +pub trait DiagnosticProvider: 'static + Send + Sync { async fn diagnose( &self, path: Arc, @@ -77,7 +75,7 @@ pub trait DiagnosticSource: 'static + Send + Sync { pub struct Language { pub(crate) config: LanguageConfig, pub(crate) grammar: Option>, - pub(crate) diagnostic_source: Option>, + pub(crate) diagnostic_provider: Option>, } pub struct Grammar { @@ -142,7 +140,7 @@ impl Language { highlight_map: Default::default(), }) }), - diagnostic_source: None, + diagnostic_provider: None, } } @@ -176,8 +174,8 @@ impl Language { Ok(self) } - pub fn with_diagnostic_source(mut self, source: impl DiagnosticSource) -> Self { - self.diagnostic_source = Some(Arc::new(source)); + pub fn with_diagnostic_provider(mut self, source: impl DiagnosticProvider) -> Self { + self.diagnostic_provider = Some(Arc::new(source)); self } @@ -214,8 +212,8 @@ impl Language { } } - pub fn diagnostic_source(&self) -> Option<&Arc> { - self.diagnostic_source.as_ref() + pub fn diagnostic_provider(&self) -> Option<&Arc> { + self.diagnostic_provider.as_ref() } pub fn disk_based_diagnostic_sources(&self) -> Option<&HashSet> { diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index 7d6a6d9d02..2a524216db 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -507,18 +507,16 @@ impl Project { for worktree_handle in &self.worktrees { if let Some(worktree) = worktree_handle.read(cx).as_local() { for language in worktree.languages() { - if let Some(diagnostic_source) = language.diagnostic_source().cloned() { + if let Some(provider) = language.diagnostic_provider().cloned() { let worktree_path = worktree.abs_path().clone(); let worktree_handle = worktree_handle.downgrade(); cx.spawn_weak(|_, mut cx| async move { - let diagnostics = - diagnostic_source.diagnose(worktree_path).await.log_err()?; + let diagnostics = provider.diagnose(worktree_path).await.log_err()?; let worktree_handle = worktree_handle.upgrade(&cx)?; worktree_handle.update(&mut cx, |worktree, cx| { for (path, diagnostics) in diagnostics { worktree - .update_offset_diagnostics( - diagnostic_source.name(), + .update_diagnostics_from_provider( path.into(), diagnostics, cx, diff --git a/crates/project/src/worktree.rs b/crates/project/src/worktree.rs index 1284300e96..4ec5679c61 100644 --- a/crates/project/src/worktree.rs +++ b/crates/project/src/worktree.rs @@ -50,6 +50,8 @@ use util::{post_inc, ResultExt, TryFutureExt}; lazy_static! { static ref GITIGNORE: &'static OsStr = OsStr::new(".gitignore"); + static ref DIAGNOSTIC_PROVIDER_NAME: Arc = Arc::from("diagnostic_source"); + static ref LSP_PROVIDER_NAME: Arc = Arc::from("lsp"); } #[derive(Clone, Debug)] @@ -672,9 +674,8 @@ impl Worktree { } } - pub fn update_lsp_diagnostics( + pub fn update_diagnostics_from_lsp( &mut self, - provider_name: Arc, mut params: lsp::PublishDiagnosticsParams, disk_based_sources: &HashSet, cx: &mut ModelContext, @@ -743,18 +744,42 @@ impl Worktree { }) .collect::>(); - self.update_point_utf16_diagnostics( - provider_name, - worktree_path, - params.version, - diagnostics, - cx, - ) + let this = self.as_local_mut().unwrap(); + for buffer in this.open_buffers.values() { + if let Some(buffer) = buffer.upgrade(cx) { + if buffer + .read(cx) + .file() + .map_or(false, |file| *file.path() == worktree_path) + { + let (remote_id, operation) = buffer.update(cx, |buffer, cx| { + ( + buffer.remote_id(), + buffer.update_diagnostics( + LSP_PROVIDER_NAME.clone(), + params.version, + diagnostics.clone(), + cx, + ), + ) + }); + self.send_buffer_update(remote_id, operation?, cx); + break; + } + } + } + + let this = self.as_local_mut().unwrap(); + this.diagnostic_summaries + .insert(worktree_path.clone(), DiagnosticSummary::new(&diagnostics)); + this.lsp_diagnostics + .insert(worktree_path.clone(), diagnostics); + cx.emit(Event::DiagnosticsUpdated(worktree_path.clone())); + Ok(()) } - pub fn update_offset_diagnostics( + pub fn update_diagnostics_from_provider( &mut self, - provider_name: Arc, path: Arc, diagnostics: Vec>, cx: &mut ModelContext, @@ -771,56 +796,8 @@ impl Worktree { ( buffer.remote_id(), buffer.update_diagnostics( - provider_name, + DIAGNOSTIC_PROVIDER_NAME.clone(), None, - diagnostics - .iter() - .map(|entry| DiagnosticEntry { - range: buffer.offset_to_point_utf16(entry.range.start) - ..buffer.offset_to_point_utf16(entry.range.end), - diagnostic: entry.diagnostic.clone(), - }) - .collect(), - cx, - ), - ) - }); - self.send_buffer_update(remote_id, operation?, cx); - break; - } - } - } - - let this = self.as_local_mut().unwrap(); - this.diagnostic_summaries - .insert(path.clone(), DiagnosticSummary::new(&diagnostics)); - this.offset_diagnostics.insert(path.clone(), diagnostics); - cx.emit(Event::DiagnosticsUpdated(path.clone())); - Ok(()) - } - - pub fn update_point_utf16_diagnostics( - &mut self, - provider_name: Arc, - path: Arc, - version: Option, - diagnostics: Vec>, - cx: &mut ModelContext, - ) -> Result<()> { - let this = self.as_local_mut().unwrap(); - for buffer in this.open_buffers.values() { - if let Some(buffer) = buffer.upgrade(cx) { - if buffer - .read(cx) - .file() - .map_or(false, |file| *file.path() == path) - { - let (remote_id, operation) = buffer.update(cx, |buffer, cx| { - ( - buffer.remote_id(), - buffer.update_diagnostics( - provider_name, - version, diagnostics.clone(), cx, ), @@ -835,26 +812,11 @@ impl Worktree { let this = self.as_local_mut().unwrap(); this.diagnostic_summaries .insert(path.clone(), DiagnosticSummary::new(&diagnostics)); - this.point_utf16_diagnostics - .insert(path.clone(), diagnostics); + this.provider_diagnostics.insert(path.clone(), diagnostics); cx.emit(Event::DiagnosticsUpdated(path.clone())); Ok(()) } - fn convert_diagnostics( - diagnostics: &[DiagnosticEntry], - buffer: &Buffer, - ) -> Vec> { - diagnostics - .iter() - .map(|entry| DiagnosticEntry { - range: buffer.offset_to_point_utf16(entry.range.start) - ..buffer.offset_to_point_utf16(entry.range.end), - diagnostic: entry.diagnostic.clone(), - }) - .collect() - } - fn send_buffer_update( &mut self, buffer_id: u64, @@ -925,8 +887,8 @@ pub struct LocalWorktree { loading_buffers: LoadingBuffers, open_buffers: HashMap>, shared_buffers: HashMap>>, - point_utf16_diagnostics: HashMap, Vec>>, - offset_diagnostics: HashMap, Vec>>, + lsp_diagnostics: HashMap, Vec>>, + provider_diagnostics: HashMap, Vec>>, diagnostic_summaries: BTreeMap, DiagnosticSummary>, queued_operations: Vec<(u64, Operation)>, language_registry: Arc, @@ -1034,8 +996,8 @@ impl LocalWorktree { loading_buffers: Default::default(), open_buffers: Default::default(), shared_buffers: Default::default(), - point_utf16_diagnostics: Default::default(), - offset_diagnostics: Default::default(), + lsp_diagnostics: Default::default(), + provider_diagnostics: Default::default(), diagnostic_summaries: Default::default(), queued_operations: Default::default(), language_registry: languages, @@ -1104,8 +1066,6 @@ impl LocalWorktree { return Some(server.clone()); } - let name: Arc = language.name().into(); - if let Some(language_server) = language .start_server(self.abs_path(), cx) .log_err() @@ -1121,23 +1081,15 @@ impl LocalWorktree { smol::block_on(diagnostics_tx.send(params)).ok(); }) .detach(); - cx.spawn_weak(|this, mut cx| { - let provider_name = name.clone(); - async move { - while let Ok(diagnostics) = diagnostics_rx.recv().await { - if let Some(handle) = cx.read(|cx| this.upgrade(cx)) { - handle.update(&mut cx, |this, cx| { - this.update_lsp_diagnostics( - provider_name.clone(), - diagnostics, - &disk_based_sources, - cx, - ) + cx.spawn_weak(|this, mut cx| async move { + while let Ok(diagnostics) = diagnostics_rx.recv().await { + if let Some(handle) = cx.read(|cx| this.upgrade(cx)) { + handle.update(&mut cx, |this, cx| { + this.update_diagnostics_from_lsp(diagnostics, &disk_based_sources, cx) .log_err(); - }); - } else { - break; - } + }); + } else { + break; } } }) @@ -1184,11 +1136,11 @@ impl LocalWorktree { .update(&mut cx, |t, cx| t.as_local().unwrap().load(&path, cx)) .await?; - let (point_utf16_diagnostics, offset_diagnostics, language, language_server) = this - .update(&mut cx, |this, cx| { + let (lsp_diagnostics, provider_diagnostics, language, language_server) = + this.update(&mut cx, |this, cx| { let this = this.as_local_mut().unwrap(); - let point_utf16_diagnostics = this.point_utf16_diagnostics.remove(&path); - let offset_diagnostics = this.offset_diagnostics.remove(&path); + let lsp_diagnostics = this.lsp_diagnostics.remove(&path); + let provider_diagnostics = this.provider_diagnostics.remove(&path); let language = this .language_registry .select_language(file.full_path()) @@ -1196,31 +1148,32 @@ impl LocalWorktree { let server = language .as_ref() .and_then(|language| this.register_language(language, cx)); - ( - point_utf16_diagnostics, - offset_diagnostics, - language, - server, - ) + (lsp_diagnostics, provider_diagnostics, language, server) }); + let mut buffer_operations = Vec::new(); let buffer = cx.add_model(|cx| { let mut buffer = Buffer::from_file(0, contents, Box::new(file), cx); buffer.set_language(language, language_server, cx); - if let Some(diagnostics) = point_utf16_diagnostics { - buffer - .update_diagnostics(todo!(), None, diagnostics, cx) + if let Some(diagnostics) = lsp_diagnostics { + let op = buffer + .update_diagnostics(LSP_PROVIDER_NAME.clone(), None, diagnostics, cx) .unwrap(); + buffer_operations.push(op); } - if let Some(diagnostics) = offset_diagnostics { - buffer - .update_offset_diagnostics(todo!(), None, diagnostics, cx) + if let Some(diagnostics) = provider_diagnostics { + let op = buffer + .update_diagnostics(DIAGNOSTIC_PROVIDER_NAME.clone(), None, diagnostics, cx) .unwrap(); + buffer_operations.push(op); } buffer }); - this.update(&mut cx, |this, _| { + this.update(&mut cx, |this, cx| { + for op in buffer_operations { + this.send_buffer_update(buffer.read(cx).remote_id(), op, cx); + } let this = this.as_local_mut().unwrap(); this.open_buffers.insert(buffer.id(), buffer.downgrade()); }); @@ -3936,7 +3889,7 @@ mod tests { worktree .update(&mut cx, |tree, cx| { - tree.update_lsp_diagnostics("lsp".into(), message, &Default::default(), cx) + tree.update_diagnostics_from_lsp(message, &Default::default(), cx) }) .unwrap(); let buffer = buffer.read_with(&cx, |buffer, _| buffer.snapshot()); diff --git a/crates/text/src/rope.rs b/crates/text/src/rope.rs index 5b9cef2cc6..70399e15f5 100644 --- a/crates/text/src/rope.rs +++ b/crates/text/src/rope.rs @@ -205,6 +205,19 @@ impl Rope { .map_or(0, |chunk| chunk.point_utf16_to_offset(overshoot)) } + pub fn point_utf16_to_point(&self, point: PointUtf16) -> Point { + if point >= self.summary().lines_utf16 { + return self.summary().lines; + } + let mut cursor = self.chunks.cursor::<(PointUtf16, Point)>(); + cursor.seek(&point, Bias::Left, &()); + let overshoot = point - cursor.start().0; + cursor.start().1 + + cursor + .item() + .map_or(Point::zero(), |chunk| chunk.point_utf16_to_point(overshoot)) + } + pub fn clip_offset(&self, mut offset: usize, bias: Bias) -> usize { let mut cursor = self.chunks.cursor::(); cursor.seek(&offset, Bias::Left, &()); @@ -583,6 +596,28 @@ impl Chunk { offset } + fn point_utf16_to_point(&self, target: PointUtf16) -> Point { + let mut point = Point::zero(); + let mut point_utf16 = PointUtf16::zero(); + for ch in self.0.chars() { + if point_utf16 >= target { + if point_utf16 > target { + panic!("point {:?} is inside of character {:?}", target, ch); + } + break; + } + + if ch == '\n' { + point_utf16 += PointUtf16::new(1, 0); + point += Point::new(1, 0); + } else { + point_utf16 += PointUtf16::new(0, ch.len_utf16() as u32); + point += Point::new(0, ch.len_utf8() as u32); + } + } + point + } + fn clip_point(&self, target: Point, bias: Bias) -> Point { for (row, line) in self.0.split('\n').enumerate() { if row == target.row as usize { diff --git a/crates/text/src/text.rs b/crates/text/src/text.rs index eec52d2fa5..5debb2f18d 100644 --- a/crates/text/src/text.rs +++ b/crates/text/src/text.rs @@ -1307,6 +1307,10 @@ impl BufferSnapshot { self.visible_text.point_utf16_to_offset(point) } + pub fn point_utf16_to_point(&self, point: PointUtf16) -> Point { + self.visible_text.point_utf16_to_point(point) + } + pub fn offset_to_point(&self, offset: usize) -> Point { self.visible_text.offset_to_point(offset) } @@ -2045,12 +2049,40 @@ impl ToPoint for usize { } } +impl ToPoint for PointUtf16 { + fn to_point<'a>(&self, snapshot: &BufferSnapshot) -> Point { + snapshot.point_utf16_to_point(*self) + } +} + impl ToPoint for Point { fn to_point<'a>(&self, _: &BufferSnapshot) -> Point { *self } } +pub trait Clip { + fn clip(&self, bias: Bias, snapshot: &BufferSnapshot) -> Self; +} + +impl Clip for usize { + fn clip(&self, bias: Bias, snapshot: &BufferSnapshot) -> Self { + snapshot.clip_offset(*self, bias) + } +} + +impl Clip for Point { + fn clip(&self, bias: Bias, snapshot: &BufferSnapshot) -> Self { + snapshot.clip_point(*self, bias) + } +} + +impl Clip for PointUtf16 { + fn clip(&self, bias: Bias, snapshot: &BufferSnapshot) -> Self { + snapshot.clip_point_utf16(*self, bias) + } +} + pub trait FromAnchor { fn from_anchor(anchor: &Anchor, snapshot: &BufferSnapshot) -> Self; }