use crate::module_cache::ModuleCache;
use crate::symbols::SymbolInfo;
use dashmap::DashMap;
use ropey::Rope;
use std::collections::HashMap;
use std::path::PathBuf;
use std::sync::Arc;
use tower_lsp_server::ls_types::Uri;
#[derive(Debug, Clone)]
pub struct Document {
pub uri: Uri,
pub version: i32,
pub rope: Rope,
pub cached_symbols: Vec<SymbolInfo>,
pub cached_types: HashMap<String, String>,
}
impl Document {
pub fn new(uri: Uri, version: i32, text: String) -> Self {
Self {
uri,
version,
rope: Rope::from_str(&text),
cached_symbols: Vec::new(),
cached_types: HashMap::new(),
}
}
pub fn update_cached_symbols(&mut self, symbols: Vec<SymbolInfo>) {
self.cached_symbols = symbols;
}
pub fn update_cached_types(&mut self, types: HashMap<String, String>) {
self.cached_types = types;
}
pub fn get_cached_symbols(&self) -> &[SymbolInfo] {
&self.cached_symbols
}
pub fn get_cached_types(&self) -> &HashMap<String, String> {
&self.cached_types
}
pub fn text(&self) -> String {
self.rope.to_string()
}
pub fn line_count(&self) -> usize {
self.rope.len_lines()
}
pub fn line(&self, line_idx: usize) -> Option<String> {
if line_idx >= self.line_count() {
return None;
}
let start = self.rope.line_to_char(line_idx);
let end = if line_idx + 1 < self.line_count() {
self.rope.line_to_char(line_idx + 1)
} else {
self.rope.len_chars()
};
Some(self.rope.slice(start..end).to_string())
}
pub fn position_to_offset(&self, line: u32, character: u32) -> Option<usize> {
let line_idx = line as usize;
if line_idx >= self.line_count() {
return None;
}
let line_start = self.rope.line_to_char(line_idx);
let offset = line_start + character as usize;
if offset > self.rope.len_chars() {
return None;
}
Some(offset)
}
pub fn offset_to_position(&self, offset: usize) -> Option<(u32, u32)> {
if offset > self.rope.len_chars() {
return None;
}
let line = self.rope.char_to_line(offset);
let line_start = self.rope.line_to_char(line);
let column = offset - line_start;
Some((line as u32, column as u32))
}
}
#[derive(Debug)]
pub struct DocumentManager {
documents: DashMap<Uri, Document>,
module_cache: Arc<ModuleCache>,
}
impl Default for DocumentManager {
fn default() -> Self {
Self::new()
}
}
impl DocumentManager {
pub fn new() -> Self {
Self {
documents: DashMap::new(),
module_cache: Arc::new(ModuleCache::new()),
}
}
pub fn get_module_cache(&self) -> Arc<ModuleCache> {
self.module_cache.clone()
}
pub fn open(&self, uri: Uri, version: i32, text: String) {
let doc = Document::new(uri.clone(), version, text);
self.documents.insert(uri, doc);
}
pub fn close(&self, uri: &Uri) {
let path = PathBuf::from(uri.path().as_str());
self.module_cache.invalidate(&path);
self.documents.remove(uri);
}
pub fn update(&self, uri: &Uri, version: i32, text: String) {
let path = PathBuf::from(uri.path().as_str());
self.module_cache.invalidate(&path);
if let Some(mut doc) = self.documents.get_mut(uri) {
doc.version = version;
doc.rope = Rope::from_str(&text);
}
}
pub fn get(&self, uri: &Uri) -> Option<Document> {
self.documents.get(uri).map(|doc| doc.clone())
}
pub fn contains(&self, uri: &Uri) -> bool {
self.documents.contains_key(uri)
}
pub fn all_uris(&self) -> Vec<Uri> {
self.documents
.iter()
.map(|entry| entry.key().clone())
.collect()
}
pub fn update_cached_symbols(&self, uri: &Uri, symbols: Vec<SymbolInfo>) {
if let Some(mut doc) = self.documents.get_mut(uri) {
doc.update_cached_symbols(symbols);
}
}
pub fn update_cached_types(&self, uri: &Uri, types: HashMap<String, String>) {
if let Some(mut doc) = self.documents.get_mut(uri) {
doc.update_cached_types(types);
}
}
pub fn get_cached_symbols(&self, uri: &Uri) -> Vec<SymbolInfo> {
self.documents
.get(uri)
.map(|doc| doc.get_cached_symbols().to_vec())
.unwrap_or_default()
}
pub fn get_cached_types(&self, uri: &Uri) -> HashMap<String, String> {
self.documents
.get(uri)
.map(|doc| doc.get_cached_types().clone())
.unwrap_or_default()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_document_creation() {
let uri = Uri::from_file_path("/test.shape").unwrap();
let doc = Document::new(uri.clone(), 1, "let x = 5;\nlet y = 10;".to_string());
assert_eq!(doc.version, 1);
assert_eq!(doc.line_count(), 2);
assert_eq!(doc.text(), "let x = 5;\nlet y = 10;");
}
#[test]
fn test_position_conversion() {
let uri = Uri::from_file_path("/test.shape").unwrap();
let doc = Document::new(uri, 1, "let x = 5;\nlet y = 10;".to_string());
let offset = doc.position_to_offset(0, 4).unwrap();
assert_eq!(doc.text().chars().nth(offset), Some('x'));
let (line, col) = doc.offset_to_position(4).unwrap();
assert_eq!(line, 0);
assert_eq!(col, 4);
}
#[test]
fn test_document_manager() {
let manager = DocumentManager::new();
let uri = Uri::from_file_path("/test.shape").unwrap();
manager.open(uri.clone(), 1, "let x = 5;".to_string());
assert!(manager.contains(&uri));
let doc = manager.get(&uri).unwrap();
assert_eq!(doc.version, 1);
assert_eq!(doc.text(), "let x = 5;");
manager.update(&uri, 2, "let x = 10;".to_string());
let doc = manager.get(&uri).unwrap();
assert_eq!(doc.version, 2);
assert_eq!(doc.text(), "let x = 10;");
manager.close(&uri);
assert!(!manager.contains(&uri));
}
}