summaryrefslogtreecommitdiff
path: root/support/texlab/src/features/rename/label.rs
blob: f24b86810b048e5be1b3f7f1b73e4c99077ba991 (plain)
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 lsp_types::{Range, RenameParams, TextEdit, WorkspaceEdit};
use rowan::ast::AstNode;

use crate::{
    features::cursor::{CursorContext, HasPosition},
    syntax::latex,
    LineIndexExt,
};

pub fn prepare_label_rename<P: HasPosition>(context: &CursorContext<P>) -> Option<Range> {
    let (_, range) = context.find_label_name_key()?;

    Some(
        context
            .request
            .main_document()
            .line_index
            .line_col_lsp_range(range),
    )
}

pub fn rename_label(context: &CursorContext<RenameParams>) -> Option<WorkspaceEdit> {
    prepare_label_rename(context)?;
    let (name_text, _) = context.find_label_name_key()?;

    let mut changes = HashMap::new();
    for document in context.request.workspace.documents_by_uri.values() {
        if let Some(data) = document.data.as_latex() {
            let mut edits = Vec::new();
            for node in latex::SyntaxNode::new_root(data.green.clone()).descendants() {
                if let Some(range) = latex::LabelDefinition::cast(node.clone())
                    .and_then(|label| label.name())
                    .and_then(|name| name.key())
                    .filter(|name| name.to_string() == name_text)
                    .map(|name| {
                        document
                            .line_index
                            .line_col_lsp_range(latex::small_range(&name))
                    })
                {
                    edits.push(TextEdit::new(
                        range,
                        context.request.params.new_name.clone(),
                    ));
                }

                latex::LabelReference::cast(node.clone())
                    .and_then(|label| label.name_list())
                    .into_iter()
                    .flat_map(|label| label.keys())
                    .filter(|name| name.to_string() == name_text)
                    .map(|name| {
                        document
                            .line_index
                            .line_col_lsp_range(latex::small_range(&name))
                    })
                    .for_each(|range| {
                        edits.push(TextEdit::new(
                            range,
                            context.request.params.new_name.clone(),
                        ));
                    });

                if let Some(label) = latex::LabelReferenceRange::cast(node.clone()) {
                    if let Some(name1) = label
                        .from()
                        .and_then(|name| name.key())
                        .filter(|name| name.to_string() == name_text)
                    {
                        edits.push(TextEdit::new(
                            document
                                .line_index
                                .line_col_lsp_range(latex::small_range(&name1)),
                            context.request.params.new_name.clone(),
                        ));
                    }

                    if let Some(name2) = label
                        .from()
                        .and_then(|name| name.key())
                        .filter(|name| name.to_string() == name_text)
                    {
                        edits.push(TextEdit::new(
                            document
                                .line_index
                                .line_col_lsp_range(latex::small_range(&name2)),
                            context.request.params.new_name.clone(),
                        ));
                    }
                }
            }

            changes.insert(document.uri.as_ref().clone(), edits);
        }
    }

    Some(WorkspaceEdit::new(changes))
}

#[cfg(test)]
mod tests {
    use crate::{features::testing::FeatureTester, RangeExt};

    use super::*;

    #[test]
    fn test_label() {
        let tester = FeatureTester::builder()
            .files(vec![
                ("foo.tex", r#"\label{foo}\include{bar}"#),
                ("bar.tex", r#"\ref{foo}"#),
                ("baz.tex", r#"\ref{foo}"#),
            ])
            .main("foo.tex")
            .line(0)
            .character(7)
            .new_name("bar")
            .build();

        let uri1 = tester.uri("foo.tex");
        let uri2 = tester.uri("bar.tex");
        let request = tester.rename();

        let context = CursorContext::new(request);
        let actual_edit = rename_label(&context).unwrap();

        let mut expected_changes = HashMap::new();
        expected_changes.insert(
            uri1.as_ref().clone(),
            vec![TextEdit::new(Range::new_simple(0, 7, 0, 10), "bar".into())],
        );
        expected_changes.insert(
            uri2.as_ref().clone(),
            vec![TextEdit::new(Range::new_simple(0, 5, 0, 8), "bar".into())],
        );
        let expected_edit = WorkspaceEdit::new(expected_changes);

        assert_eq!(actual_edit, expected_edit);
    }
}