// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. use super::analysis; use super::text::LineIndex; use deno_core::error::custom_error; use deno_core::error::AnyError; use deno_core::error::Context; use deno_core::ModuleSpecifier; use lspower::lsp::TextDocumentContentChangeEvent; use std::collections::HashMap; use std::ops::Range; #[derive(Debug, PartialEq, Eq)] enum IndexValid { All, UpTo(u32), } impl IndexValid { fn covers(&self, line: u32) -> bool { match *self { IndexValid::UpTo(to) => to > line, IndexValid::All => true, } } } #[derive(Debug, Clone, Default)] pub struct DocumentData { bytes: Option>, line_index: Option, dependencies: Option>, version: Option, } impl DocumentData { pub fn apply_content_changes( &mut self, content_changes: Vec, ) -> Result<(), AnyError> { if self.bytes.is_none() { return Ok(()); } let content = &mut String::from_utf8(self.bytes.clone().unwrap()) .context("unable to parse bytes to string")?; let mut line_index = if let Some(line_index) = &self.line_index { line_index.clone() } else { LineIndex::new(&content) }; let mut index_valid = IndexValid::All; for change in content_changes { if let Some(range) = change.range { if !index_valid.covers(range.start.line) { line_index = LineIndex::new(&content); } index_valid = IndexValid::UpTo(range.start.line); let range = line_index.get_text_range(range)?; content.replace_range(Range::::from(range), &change.text); } else { *content = change.text; index_valid = IndexValid::UpTo(0); } } self.bytes = Some(content.as_bytes().to_owned()); self.line_index = if index_valid == IndexValid::All { Some(line_index) } else { Some(LineIndex::new(&content)) }; Ok(()) } pub fn content(&self) -> Result, AnyError> { if let Some(bytes) = &self.bytes { Ok(Some( String::from_utf8(bytes.clone()) .context("cannot decode bytes to string")?, )) } else { Ok(None) } } } #[derive(Debug, Clone, Default)] pub struct DocumentCache { docs: HashMap, } impl DocumentCache { pub fn change( &mut self, specifier: &ModuleSpecifier, version: i32, content_changes: Vec, ) -> Result, AnyError> { if !self.contains_key(specifier) { return Err(custom_error( "NotFound", format!( "The specifier (\"{}\") does not exist in the document cache.", specifier ), )); } let doc = self.docs.get_mut(specifier).unwrap(); doc.apply_content_changes(content_changes)?; doc.version = Some(version); doc.content() } pub fn close(&mut self, specifier: &ModuleSpecifier) { if let Some(mut doc) = self.docs.get_mut(specifier) { doc.version = None; doc.dependencies = None; } } pub fn contains_key(&self, specifier: &ModuleSpecifier) -> bool { self.docs.contains_key(specifier) } pub fn content( &self, specifier: &ModuleSpecifier, ) -> Result, AnyError> { if let Some(doc) = self.docs.get(specifier) { doc.content() } else { Ok(None) } } pub fn dependencies( &self, specifier: &ModuleSpecifier, ) -> Option> { let doc = self.docs.get(specifier)?; doc.dependencies.clone() } pub fn len(&self) -> usize { self.docs.iter().count() } pub fn line_index(&self, specifier: &ModuleSpecifier) -> Option { let doc = self.docs.get(specifier)?; doc.line_index.clone() } pub fn open(&mut self, specifier: ModuleSpecifier, version: i32, text: &str) { self.docs.insert( specifier, DocumentData { bytes: Some(text.as_bytes().to_owned()), version: Some(version), line_index: Some(LineIndex::new(&text)), ..Default::default() }, ); } pub fn open_specifiers(&self) -> Vec<&ModuleSpecifier> { self .docs .iter() .filter_map(|(key, data)| { if data.version.is_some() { Some(key) } else { None } }) .collect() } pub fn set_dependencies( &mut self, specifier: &ModuleSpecifier, maybe_dependencies: Option>, ) -> Result<(), AnyError> { if let Some(doc) = self.docs.get_mut(specifier) { doc.dependencies = maybe_dependencies; Ok(()) } else { Err(custom_error( "NotFound", format!( "The specifier (\"{}\") does not exist in the document cache.", specifier ), )) } } pub fn version(&self, specifier: &ModuleSpecifier) -> Option { self.docs.get(specifier).and_then(|doc| doc.version) } } #[cfg(test)] mod tests { use super::*; use deno_core::resolve_url; use lspower::lsp; #[test] fn test_document_cache_contains() { let mut document_cache = DocumentCache::default(); let specifier = resolve_url("file:///a/b.ts").unwrap(); let missing_specifier = resolve_url("file:///a/c.ts").unwrap(); document_cache.open(specifier.clone(), 1, "console.log(\"Hello Deno\");\n"); assert!(document_cache.contains_key(&specifier)); assert!(!document_cache.contains_key(&missing_specifier)); } #[test] fn test_document_cache_change() { let mut document_cache = DocumentCache::default(); let specifier = resolve_url("file:///a/b.ts").unwrap(); document_cache.open(specifier.clone(), 1, "console.log(\"Hello deno\");\n"); document_cache .change( &specifier, 2, vec![lsp::TextDocumentContentChangeEvent { range: Some(lsp::Range { start: lsp::Position { line: 0, character: 19, }, end: lsp::Position { line: 0, character: 20, }, }), range_length: Some(1), text: "D".to_string(), }], ) .expect("failed to make changes"); let actual = document_cache .content(&specifier) .expect("failed to get content"); assert_eq!(actual, Some("console.log(\"Hello Deno\");\n".to_string())); } #[test] fn test_document_cache_change_utf16() { let mut document_cache = DocumentCache::default(); let specifier = resolve_url("file:///a/b.ts").unwrap(); document_cache.open(specifier.clone(), 1, "console.log(\"Hello 🦕\");\n"); document_cache .change( &specifier, 2, vec![lsp::TextDocumentContentChangeEvent { range: Some(lsp::Range { start: lsp::Position { line: 0, character: 19, }, end: lsp::Position { line: 0, character: 21, }, }), range_length: Some(2), text: "Deno".to_string(), }], ) .expect("failed to make changes"); let actual = document_cache .content(&specifier) .expect("failed to get content"); assert_eq!(actual, Some("console.log(\"Hello Deno\");\n".to_string())); } }