rust_analyzer/cli/
ssr.rs

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
//! Applies structured search replace rules from the command line.

use anyhow::Context;
use ide_db::{base_db::SourceDatabase, EditionedFileId};
use ide_ssr::MatchFinder;
use load_cargo::{load_workspace_at, LoadCargoConfig, ProcMacroServerChoice};
use project_model::{CargoConfig, RustLibSource};

use crate::cli::flags;

impl flags::Ssr {
    pub fn run(self) -> anyhow::Result<()> {
        let cargo_config = CargoConfig {
            sysroot: Some(RustLibSource::Discover),
            all_targets: true,
            set_test: true,
            ..Default::default()
        };
        let load_cargo_config = LoadCargoConfig {
            load_out_dirs_from_check: true,
            with_proc_macro_server: ProcMacroServerChoice::Sysroot,
            prefill_caches: false,
        };
        let (ref db, vfs, _proc_macro) = load_workspace_at(
            &std::env::current_dir()?,
            &cargo_config,
            &load_cargo_config,
            &|_| {},
        )?;
        let mut match_finder = MatchFinder::at_first_file(db)?;
        for rule in self.rule {
            match_finder.add_rule(rule)?;
        }
        let edits = match_finder.edits();
        for (file_id, edit) in edits {
            if let Some(path) = vfs.file_path(file_id).as_path() {
                let mut contents = db.file_text(file_id).to_string();
                edit.apply(&mut contents);
                std::fs::write(path, contents)
                    .with_context(|| format!("failed to write {path}"))?;
            }
        }
        Ok(())
    }
}

impl flags::Search {
    /// Searches for `patterns`, printing debug information for any nodes whose text exactly matches
    /// `debug_snippet`. This is intended for debugging and probably isn't in it's current form useful
    /// for much else.
    pub fn run(self) -> anyhow::Result<()> {
        use ide_db::base_db::SourceRootDatabase;
        use ide_db::symbol_index::SymbolsDatabase;
        let cargo_config =
            CargoConfig { all_targets: true, set_test: true, ..CargoConfig::default() };
        let load_cargo_config = LoadCargoConfig {
            load_out_dirs_from_check: true,
            with_proc_macro_server: ProcMacroServerChoice::Sysroot,
            prefill_caches: false,
        };
        let (ref db, _vfs, _proc_macro) = load_workspace_at(
            &std::env::current_dir()?,
            &cargo_config,
            &load_cargo_config,
            &|_| {},
        )?;
        let mut match_finder = MatchFinder::at_first_file(db)?;
        for pattern in self.pattern {
            match_finder.add_search_pattern(pattern)?;
        }
        if let Some(debug_snippet) = &self.debug {
            for &root in db.local_roots().iter() {
                let sr = db.source_root(root);
                for file_id in sr.iter() {
                    for debug_info in match_finder.debug_where_text_equal(
                        EditionedFileId::current_edition(file_id),
                        debug_snippet,
                    ) {
                        println!("{debug_info:#?}");
                    }
                }
            }
        } else {
            for m in match_finder.matches().flattened().matches {
                // We could possibly at some point do something more useful than just printing
                // the matched text. For now though, that's the easiest thing to do.
                println!("{}", m.matched_text());
            }
        }
        Ok(())
    }
}