use std::collections::HashMap; use std::time::SystemTime; use serde::Deserialize; use serde_json::json; use serde_json::Value; use erg_common::dict::Dict; use erg_compiler::artifact::BuildRunnable; use erg_compiler::varinfo::{AbsLocation, VarKind}; use lsp_types::{RenameParams, TextEdit, Url, WorkspaceEdit}; use crate::server::{ELSResult, Server}; use crate::util; impl Server { pub(crate) fn rename(&mut self, msg: &Value) -> ELSResult<()> { let params = RenameParams::deserialize(&msg["params"])?; Self::send_log(format!("rename request: {params:?}"))?; let uri = util::normalize_url(params.text_document_position.text_document.uri); let pos = params.text_document_position.position; if let Some(tok) = util::get_token(uri.clone(), pos)? { // Self::send_log(format!("token: {tok}"))?; if let Some(visitor) = self.get_visitor(&uri) { if let Some(vi) = visitor.get_info(&tok) { // Self::send_log(format!("vi: {vi}"))?; let is_std = vi .def_loc .module .as_ref() .map(|path| path.starts_with(&self.erg_path)) .unwrap_or(false); if vi.def_loc.loc.is_unknown() || is_std { let error_reason = match vi.kind { VarKind::Builtin => "this is a builtin variable and cannot be renamed", VarKind::FixedAuto => { "this is a fixed auto variable and cannot be renamed" } _ if is_std => "this is a standard library API and cannot be renamed", _ => "this name cannot be renamed", }; return Self::send_error(msg["id"].as_i64(), 0, error_reason); } let mut changes: HashMap> = HashMap::new(); Self::commit_change(&mut changes, &vi.def_loc, params.new_name.clone()); if let Some(referrers) = self.get_index().get_refs(&vi.def_loc) { // Self::send_log(format!("referrers: {referrers:?}"))?; for referrer in referrers.iter() { Self::commit_change(&mut changes, referrer, params.new_name.clone()); } } let dependencies = self.dependencies_of(&uri); for uri in changes.keys() { self.clear_cache(uri); } let timestamps = self.get_timestamps(changes.keys()); let edit = WorkspaceEdit::new(changes); Self::send( &json!({ "jsonrpc": "2.0", "id": msg["id"].as_i64().unwrap(), "result": edit }), )?; for _ in 0..20 { Self::send_log("waiting for file to be modified...")?; if self.all_changed(×tamps) { break; } std::thread::sleep(std::time::Duration::from_millis(50)); } // recheck dependencies and finally the file itself for dep in dependencies { let code = util::get_code_from_uri(&dep)?; self.check_file(dep, code)?; } // dependents are checked after changes are committed return Ok(()); } } } Self::send( &json!({ "jsonrpc": "2.0", "id": msg["id"].as_i64().unwrap(), "result": Value::Null }), ) } fn commit_change( changes: &mut HashMap>, abs_loc: &AbsLocation, new_name: String, ) { if let Some(path) = &abs_loc.module { let def_uri = util::normalize_url(Url::from_file_path(path).unwrap()); let edit = TextEdit::new(util::loc_to_range(abs_loc.loc).unwrap(), new_name); if let Some(edits) = changes.get_mut(&def_uri) { edits.push(edit); } else { changes.insert(def_uri, vec![edit]); } } } fn get_timestamps<'a, I: Iterator>(&self, urls: I) -> Dict { urls.map(|url| { let timestamp = util::get_metadata_from_uri(url) .and_then(|md| Ok(md.modified()?)) .unwrap(); (url.clone(), timestamp) }) .collect() } fn all_changed(&self, timestamps: &Dict) -> bool { timestamps.iter().all(|(url, timestamp)| { util::get_metadata_from_uri(url) .and_then(|md| Ok(md.modified()? != *timestamp)) .unwrap_or(false) }) } /// self is __included__ pub fn dependencies_of(&self, uri: &Url) -> Vec { let graph = &self.get_shared().unwrap().graph; let path = util::uri_to_path(uri); graph.sort().unwrap(); let self_node = graph.get_node(&path).unwrap(); graph .iter() .filter(|node| node.id == path || self_node.depends_on(&node.id)) .map(|node| util::normalize_url(Url::from_file_path(&node.id).unwrap())) .collect() } /// self is __not included__ pub fn dependents_of(&self, uri: &Url) -> Vec { let graph = &self.get_shared().unwrap().graph; let path = util::uri_to_path(uri); graph .iter() .filter(|node| node.depends_on(&path)) .map(|node| util::normalize_url(Url::from_file_path(&node.id).unwrap())) .collect() } }