use std::sync::Arc;
use dashmap::DashMap;
use ropey::Rope;
use crate::edit::{self, EditError, TextEdit};
use crate::encoding::PositionEncoding;
#[non_exhaustive]
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct DocumentUri(String);
impl DocumentUri {
#[must_use]
pub fn new(uri: impl Into<String>) -> Self {
Self(uri.into())
}
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct DocumentVersion(i32);
impl DocumentVersion {
#[must_use]
pub const fn new(value: i32) -> Self {
Self(value)
}
#[must_use]
pub const fn get(self) -> i32 {
self.0
}
}
#[non_exhaustive]
#[derive(Debug, thiserror::Error)]
pub enum VfsError {
#[error("document not found: {0}")]
NotFound(String),
#[error("stale version: have {have}, got {got}")]
StaleVersion {
have: i32,
got: i32,
},
#[error(transparent)]
Edit(#[from] EditError),
}
#[derive(Debug)]
struct DocumentEntry {
rope: Rope,
version: DocumentVersion,
}
#[derive(Debug, Default, Clone)]
pub struct Vfs {
docs: Arc<DashMap<DocumentUri, DocumentEntry>>,
encoding: PositionEncoding,
}
impl Vfs {
#[must_use]
pub fn new(encoding: PositionEncoding) -> Self {
Self {
docs: Arc::new(DashMap::new()),
encoding,
}
}
pub fn open(&self, uri: DocumentUri, text: &str, version: DocumentVersion) {
let entry = DocumentEntry {
rope: Rope::from_str(text),
version,
};
self.docs.insert(uri, entry);
}
pub fn change(
&self,
uri: &DocumentUri,
edits: &[TextEdit],
version: DocumentVersion,
) -> Result<(), VfsError> {
let mut entry = self
.docs
.get_mut(uri)
.ok_or_else(|| VfsError::NotFound(uri.as_str().to_owned()))?;
if version.get() <= entry.version.get() {
return Err(VfsError::StaleVersion {
have: entry.version.get(),
got: version.get(),
});
}
edit::apply(&mut entry.rope, edits, self.encoding)?;
entry.version = version;
Ok(())
}
pub fn close(&self, uri: &DocumentUri) {
self.docs.remove(uri);
}
#[must_use]
pub fn text(&self, uri: &DocumentUri) -> Option<String> {
self.docs.get(uri).map(|entry| entry.rope.to_string())
}
#[must_use]
pub fn version(&self, uri: &DocumentUri) -> Option<DocumentVersion> {
self.docs.get(uri).map(|entry| entry.version)
}
#[must_use]
pub fn encoding(&self) -> PositionEncoding {
self.encoding
}
#[must_use]
pub fn len(&self) -> usize {
self.docs.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.docs.is_empty()
}
}