1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
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<Checker: BuildRunnable> Server<Checker> {
    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) = self.file_cache.get_token(&uri, 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<Url, Vec<TextEdit>> = HashMap::new();
                    Self::commit_change(&mut changes, &vi.def_loc, params.new_name.clone());
                    if let Some(value) = self.get_index().get_refs(&vi.def_loc) {
                        // Self::send_log(format!("referrers: {referrers:?}"))?;
                        for referrer in value.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(&timestamps) {
                            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<Url, Vec<TextEdit>>,
        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<Item = &'a Url>>(&self, urls: I) -> Dict<Url, SystemTime> {
        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<Url, SystemTime>) -> 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<Url> {
        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<Url> {
        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()
    }
}