use std::fs::File; use std::io::Read; use erg_compiler::erg_parser::ast::Module; use erg_compiler::erg_parser::Parser; use lsp_types::{ DidChangeTextDocumentParams, FileOperationFilter, FileOperationPattern, FileOperationPatternKind, FileOperationRegistrationOptions, OneOf, Position, Range, RenameFilesParams, SaveOptions, ServerCapabilities, TextDocumentSyncCapability, TextDocumentSyncKind, TextDocumentSyncOptions, Url, WorkspaceFileOperationsServerCapabilities, WorkspaceFoldersServerCapabilities, WorkspaceServerCapabilities, }; use erg_common::dict::Dict; use erg_common::shared::AtomicShared; use erg_common::traits::DequeStream; use erg_compiler::erg_parser::lex::Lexer; use erg_compiler::erg_parser::token::{Token, TokenStream}; use crate::_log; use crate::server::ELSResult; use crate::util::{self, NormalizedUrl}; fn _get_code_from_uri(uri: &Url) -> ELSResult { let path = uri .to_file_path() .or_else(|_| util::denormalize(uri.clone()).to_file_path()) .map_err(|_| format!("invalid file path: {uri}"))?; let mut code = String::new(); File::open(path.as_path())?.read_to_string(&mut code)?; Ok(code) } #[derive(Debug, Clone)] pub struct FileCacheEntry { pub code: String, pub ver: i32, pub token_stream: Option, } impl FileCacheEntry { /// line: 0-based pub fn get_line(&self, line0: u32) -> Option { let mut lines = self.code.lines(); lines.nth(line0 as usize).map(|s| s.to_string()) } } /// Stores the contents of the file on-memory. /// This struct can save changes in real-time & incrementally. #[derive(Debug, Clone)] pub struct FileCache { pub files: AtomicShared>, } impl FileCache { pub fn new() -> Self { Self { files: AtomicShared::new(Dict::new()), } } fn load_once(&self, uri: &NormalizedUrl) -> ELSResult<()> { if self.files.borrow_mut().get(uri).is_some() { return Ok(()); } let code = _get_code_from_uri(uri)?; self.update(uri, code, None); Ok(()) } pub(crate) fn set_capabilities(&mut self, capabilities: &mut ServerCapabilities) { let workspace_folders = WorkspaceFoldersServerCapabilities { supported: Some(true), change_notifications: Some(OneOf::Left(true)), }; let file_op = FileOperationRegistrationOptions { filters: vec![ FileOperationFilter { scheme: Some(String::from("file")), pattern: FileOperationPattern { glob: String::from("**/*.{er,py}"), matches: Some(FileOperationPatternKind::File), options: None, }, }, FileOperationFilter { scheme: Some(String::from("file")), pattern: FileOperationPattern { glob: String::from("**"), matches: Some(FileOperationPatternKind::Folder), options: None, }, }, ], }; capabilities.workspace = Some(WorkspaceServerCapabilities { workspace_folders: Some(workspace_folders), file_operations: Some(WorkspaceFileOperationsServerCapabilities { will_rename: Some(file_op), ..Default::default() }), }); let sync_option = TextDocumentSyncOptions { open_close: Some(true), change: Some(TextDocumentSyncKind::INCREMENTAL), save: Some(SaveOptions::default().into()), ..Default::default() }; capabilities.text_document_sync = Some(TextDocumentSyncCapability::Options(sync_option)); } /// This method clones and returns the entire file. /// If you only need part of the file, use `get_ranged` or `get_line` instead. pub fn get_entire_code(&self, uri: &NormalizedUrl) -> ELSResult { self.load_once(uri)?; Ok(self .files .borrow_mut() .get(uri) .ok_or("not found")? .code .clone()) } pub fn get_token_stream(&self, uri: &NormalizedUrl) -> Option { let _ = self.load_once(uri); self.files.borrow_mut().get(uri)?.token_stream.clone() } pub fn get_ast(&self, uri: &NormalizedUrl) -> Option { let ts = self.get_token_stream(uri)?; let mut parser = Parser::new(ts); parser.parse().ok() } pub fn get_token(&self, uri: &NormalizedUrl, pos: Position) -> Option { let _ = self.load_once(uri); let ent = self.files.borrow_mut(); let tokens = ent.get(uri)?.token_stream.as_ref()?; for tok in tokens.iter() { if util::pos_in_loc(tok, pos) { return Some(tok.clone()); } } None } pub fn get_token_relatively( &self, uri: &NormalizedUrl, pos: Position, offset: isize, ) -> Option { let _ = self.load_once(uri); let ent = self.files.borrow_mut(); let tokens = ent.get(uri)?.token_stream.as_ref()?; let index = (|| { for (i, tok) in tokens.iter().enumerate() { if util::pos_in_loc(tok, pos) { return Some(i); } } None })()?; let index = (index as isize + offset) as usize; if index < tokens.len() { Some(tokens[index].clone()) } else { None } } /// 0-based pub(crate) fn get_line(&self, uri: &NormalizedUrl, line0: u32) -> Option { let _ = self.load_once(uri); self.files.borrow_mut().get(uri)?.get_line(line0) } pub(crate) fn get_ranged( &self, uri: &NormalizedUrl, range: Range, ) -> ELSResult> { self.load_once(uri)?; let ent = self.files.borrow_mut(); let file = ent.get(uri).ok_or("not found")?; let mut code = String::new(); for (i, line) in file.code.lines().enumerate() { if i >= range.start.line as usize && i <= range.end.line as usize { if i == range.start.line as usize && i == range.end.line as usize { if line.len() < range.end.character as usize { return Ok(None); } code.push_str( &line[range.start.character as usize..range.end.character as usize], ); } else if i == range.start.line as usize { code.push_str(&line[range.start.character as usize..]); code.push('\n'); } else if i == range.end.line as usize { if line.len() < range.end.character as usize { return Ok(None); } code.push_str(&line[..range.end.character as usize]); } else { code.push_str(line); code.push('\n'); } } } Ok(Some(code)) } pub(crate) fn update(&self, uri: &NormalizedUrl, code: String, ver: Option) { let ent = self.files.borrow_mut(); let entry = ent.get(uri); if let Some(entry) = entry { if ver.map_or(false, |ver| ver <= entry.ver) { // crate::_log!("171: double update detected: {ver:?}, {}, code:\n{}", entry.ver, entry.code); return; } } let token_stream = Lexer::from_str(code.clone()).lex().ok(); let ver = ver.unwrap_or({ if let Some(entry) = entry { entry.ver } else { 1 } }); drop(ent); self.files.borrow_mut().insert( uri.clone(), FileCacheEntry { code, ver, token_stream, }, ); } pub(crate) fn ranged_update(&self, uri: &NormalizedUrl, old: Range, new_code: &str) { let mut ent = self.files.borrow_mut(); let Some(entry) = ent.get_mut(uri) else { return; }; let mut code = entry.code.clone(); let start = util::pos_to_byte_index(&code, old.start); let end = util::pos_to_byte_index(&code, old.end); code.replace_range(start..end, new_code); let token_stream = Lexer::from_str(code.clone()).lex().ok(); entry.code = code; // entry.ver += 1; entry.token_stream = token_stream; } pub(crate) fn incremental_update(&self, params: DidChangeTextDocumentParams) { let uri = NormalizedUrl::new(params.text_document.uri); let mut ent = self.files.borrow_mut(); let Some(entry) = ent.get_mut(&uri) else { return; }; if entry.ver >= params.text_document.version { // crate::_log!("212: double update detected {}, {}, code:\n{}", entry.ver, params.text_document.version, entry.code); return; } let mut code = entry.code.clone(); for change in params.content_changes { let Some(range) = change.range else { continue; }; let start = util::pos_to_byte_index(&code, range.start); let end = util::pos_to_byte_index(&code, range.end); code.replace_range(start..end, &change.text); } let token_stream = Lexer::from_str(code.clone()).lex().ok(); entry.code = code; entry.ver = params.text_document.version; entry.token_stream = token_stream; } #[allow(unused)] pub fn remove(&mut self, uri: &NormalizedUrl) { self.files.borrow_mut().remove(uri); } pub fn rename_files(&mut self, params: &RenameFilesParams) -> ELSResult<()> { for file in ¶ms.files { let Ok(old_uri) = NormalizedUrl::parse(&file.old_uri) else { _log!("failed to parse old uri: {}", file.old_uri); continue; }; let Ok(new_uri) = NormalizedUrl::parse(&file.new_uri) else { _log!("failed to parse new uri: {}", file.new_uri); continue; }; let Some(entry) = self.files.borrow_mut().remove(&old_uri) else { _log!("failed to find old uri: {}", file.old_uri); continue; }; self.files.borrow_mut().insert(new_uri, entry); } Ok(()) } }