use crate::{
buffer::BufferEdit,
text_stream::{TextByteStream, TextStreamError},
vim::VimCursor,
};
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct EditorBackendSnapshot {
synced_revision: u64,
text: String,
}
impl EditorBackendSnapshot {
#[must_use]
pub fn from_stream(stream: &TextByteStream) -> Self {
Self {
synced_revision: stream.revision(),
text: stream.as_str().to_owned(),
}
}
#[must_use]
pub const fn synced_revision(&self) -> u64 {
self.synced_revision
}
#[must_use]
pub fn text(&self) -> &str {
&self.text
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct EditorBackendAdapter {
snapshot: EditorBackendSnapshot,
}
impl EditorBackendAdapter {
#[must_use]
pub fn from_stream(stream: &TextByteStream) -> Self {
Self {
snapshot: EditorBackendSnapshot::from_stream(stream),
}
}
#[must_use]
pub const fn snapshot(&self) -> &EditorBackendSnapshot {
&self.snapshot
}
pub fn sync_from_stream(&mut self, stream: &TextByteStream) -> EditorSyncReport {
if self.snapshot.synced_revision == stream.revision()
&& self.snapshot.text == stream.as_str()
{
return EditorSyncReport::Unchanged;
}
self.snapshot = EditorBackendSnapshot::from_stream(stream);
EditorSyncReport::SyncedFromStream {
revision: stream.revision(),
}
}
pub fn apply_backend_text(
&mut self,
backend_text: impl Into<String>,
stream: &mut TextByteStream,
cursor: &mut VimCursor,
) -> Result<EditorSyncReport, TextStreamError> {
let backend_text = backend_text.into();
if self.snapshot.synced_revision == stream.revision()
&& self.snapshot.text == backend_text
&& stream.as_str() == backend_text
{
return Ok(EditorSyncReport::Unchanged);
}
if stream.as_str() == backend_text {
self.snapshot = EditorBackendSnapshot::from_stream(stream);
return Ok(EditorSyncReport::Unchanged);
}
let report = BufferEdit::SetAll(backend_text).apply_with_cursor(stream, cursor)?;
self.snapshot = EditorBackendSnapshot::from_stream(stream);
Ok(EditorSyncReport::AppliedBackendEdit {
previous_revision: report.previous_revision,
current_revision: report.current_revision,
})
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum EditorSyncReport {
Unchanged,
SyncedFromStream {
revision: u64,
},
AppliedBackendEdit {
previous_revision: u64,
current_revision: u64,
},
}
#[cfg(test)]
mod tests {
use super::{EditorBackendAdapter, EditorSyncReport};
use crate::{buffer::BufferEdit, text_stream::TextByteStream, vim::VimCursor};
#[test]
fn initializes_from_stream() {
let stream = TextByteStream::new("hello");
let adapter = EditorBackendAdapter::from_stream(&stream);
assert_eq!(adapter.snapshot().text(), "hello");
assert_eq!(adapter.snapshot().synced_revision(), 0);
}
#[test]
fn sync_from_stream_updates_changed_revision() {
let mut stream = TextByteStream::new("hello");
let mut adapter = EditorBackendAdapter::from_stream(&stream);
let _report = BufferEdit::Insert {
byte_index: 5,
text: String::from(" world"),
}
.apply(&mut stream)
.expect("insert should succeed");
assert_eq!(
adapter.sync_from_stream(&stream),
EditorSyncReport::SyncedFromStream { revision: 1 }
);
assert_eq!(adapter.snapshot().text(), "hello world");
}
#[test]
fn backend_text_applies_to_stream_once() {
let mut stream = TextByteStream::new("hello");
let mut cursor = VimCursor::new();
let mut adapter = EditorBackendAdapter::from_stream(&stream);
assert_eq!(
adapter
.apply_backend_text("hello!", &mut stream, &mut cursor)
.expect("backend edit should succeed"),
EditorSyncReport::AppliedBackendEdit {
previous_revision: 0,
current_revision: 1
}
);
assert_eq!(stream.as_str(), "hello!");
assert_eq!(
adapter
.apply_backend_text("hello!", &mut stream, &mut cursor)
.expect("repeat sync should succeed"),
EditorSyncReport::Unchanged
);
assert_eq!(stream.revision(), 1);
}
}