// Copyright 2018-2021 the Deno authors. All rights reserved. MIT license. use super::analysis; use super::text::LineIndex; use super::tsc::NavigationTree; use crate::import_map::ImportMap; use crate::media_type::MediaType; 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, navigation_tree: 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)) }; self.navigation_tree = None; Ok(()) } pub fn content(&self) -> Result, AnyError> { if let Some(bytes) = self.bytes.clone() { Ok(Some( String::from_utf8(bytes).context("cannot decode bytes to string")?, )) } else { Ok(None) } } } #[derive(Debug, Clone, Default)] pub struct DocumentCache { docs: HashMap, } impl DocumentCache { pub fn analyze_dependencies( &mut self, specifier: &ModuleSpecifier, maybe_import_map: &Option, ) -> Result<(), AnyError> { if !self.contains(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(); if let Some(source) = &doc.content()? { if let Some((dependencies, _)) = analysis::analyze_dependencies( specifier, source, &MediaType::from(specifier), maybe_import_map, ) { doc.dependencies = Some(dependencies); } else { doc.dependencies = None; } } else { doc.dependencies = None; } Ok(()) } pub fn change( &mut self, specifier: &ModuleSpecifier, version: i32, content_changes: Vec, ) -> Result<(), AnyError> { if !self.contains(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); Ok(()) } 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(&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 navigation_tree( &self, specifier: &ModuleSpecifier, ) -> Option { let doc = self.docs.get(specifier)?; doc.navigation_tree.clone() } pub fn open( &mut self, specifier: ModuleSpecifier, version: i32, text: String, ) { 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_navigation_tree( &mut self, specifier: &ModuleSpecifier, navigation_tree: NavigationTree, ) -> Result<(), AnyError> { if let Some(mut doc) = self.docs.get_mut(specifier) { doc.navigation_tree = Some(navigation_tree); Ok(()) } else { Err(custom_error( "NotFound", "The document \"{}\" was unexpectedly missing.", )) } } pub fn version(&self, specifier: &ModuleSpecifier) -> Option { self.docs.get(specifier).and_then(|doc| doc.version) } } #[cfg(test)] mod tests { use super::*; use lspower::lsp; #[test] fn test_document_cache_contains() { let mut document_cache = DocumentCache::default(); let specifier = ModuleSpecifier::resolve_url("file:///a/b.ts").unwrap(); let missing_specifier = ModuleSpecifier::resolve_url("file:///a/c.ts").unwrap(); document_cache.open( specifier.clone(), 1, "console.log(\"Hello Deno\");\n".to_owned(), ); assert!(document_cache.contains(&specifier)); assert!(!document_cache.contains(&missing_specifier)); } #[test] fn test_document_cache_change() { let mut document_cache = DocumentCache::default(); let specifier = ModuleSpecifier::resolve_url("file:///a/b.ts").unwrap(); document_cache.open( specifier.clone(), 1, "console.log(\"Hello deno\");\n".to_owned(), ); 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 = ModuleSpecifier::resolve_url("file:///a/b.ts").unwrap(); document_cache.open( specifier.clone(), 1, "console.log(\"Hello 🦕\");\n".to_owned(), ); 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())); } }