Skip to content

Commit 3975952

Browse files
SSR: Pass current file position through to SSR code.
In a subsequent commit, it will be used for resolving paths.
1 parent 02fc3d5 commit 3975952

File tree

11 files changed

+88
-32
lines changed

11 files changed

+88
-32
lines changed

crates/ra_ide/src/lib.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -505,9 +505,10 @@ impl Analysis {
505505
&self,
506506
query: &str,
507507
parse_only: bool,
508+
position: FilePosition,
508509
) -> Cancelable<Result<SourceChange, SsrError>> {
509510
self.with_db(|db| {
510-
let edits = ssr::parse_search_replace(query, parse_only, db)?;
511+
let edits = ssr::parse_search_replace(query, parse_only, db, position)?;
511512
Ok(SourceChange::from(edits))
512513
})
513514
}

crates/ra_ide/src/ssr.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use ra_db::FilePosition;
12
use ra_ide_db::RootDatabase;
23

34
use crate::SourceFileEdit;
@@ -42,12 +43,13 @@ pub fn parse_search_replace(
4243
rule: &str,
4344
parse_only: bool,
4445
db: &RootDatabase,
46+
position: FilePosition,
4547
) -> Result<Vec<SourceFileEdit>, SsrError> {
4648
let rule: SsrRule = rule.parse()?;
49+
let mut match_finder = MatchFinder::in_context(db, position);
50+
match_finder.add_rule(rule);
4751
if parse_only {
4852
return Ok(Vec::new());
4953
}
50-
let mut match_finder = MatchFinder::new(db);
51-
match_finder.add_rule(rule);
5254
Ok(match_finder.edits())
5355
}

crates/ra_ssr/src/lib.rs

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,12 @@ mod errors;
1313
#[cfg(test)]
1414
mod tests;
1515

16+
use crate::errors::bail;
1617
pub use crate::errors::SsrError;
1718
pub use crate::matching::Match;
1819
use crate::matching::MatchFailureReason;
1920
use hir::Semantics;
20-
use ra_db::{FileId, FileRange};
21+
use ra_db::{FileId, FilePosition, FileRange};
2122
use ra_ide_db::source_change::SourceFileEdit;
2223
use ra_syntax::{ast, AstNode, SyntaxNode, TextRange};
2324
use rustc_hash::FxHashMap;
@@ -51,10 +52,35 @@ pub struct MatchFinder<'db> {
5152
}
5253

5354
impl<'db> MatchFinder<'db> {
54-
pub fn new(db: &'db ra_ide_db::RootDatabase) -> MatchFinder<'db> {
55+
/// Constructs a new instance where names will be looked up as if they appeared at
56+
/// `lookup_context`.
57+
pub fn in_context(
58+
db: &'db ra_ide_db::RootDatabase,
59+
_lookup_context: FilePosition,
60+
) -> MatchFinder<'db> {
61+
// FIXME: Use lookup_context
5562
MatchFinder { sema: Semantics::new(db), rules: Vec::new() }
5663
}
5764

65+
/// Constructs an instance using the start of the first file in `db` as the lookup context.
66+
pub fn at_first_file(db: &'db ra_ide_db::RootDatabase) -> Result<MatchFinder<'db>, SsrError> {
67+
use ra_db::SourceDatabaseExt;
68+
use ra_ide_db::symbol_index::SymbolsDatabase;
69+
if let Some(first_file_id) = db
70+
.local_roots()
71+
.iter()
72+
.next()
73+
.and_then(|root| db.source_root(root.clone()).iter().next())
74+
{
75+
Ok(MatchFinder::in_context(
76+
db,
77+
FilePosition { file_id: first_file_id, offset: 0.into() },
78+
))
79+
} else {
80+
bail!("No files to search");
81+
}
82+
}
83+
5884
/// Adds a rule to be applied. The order in which rules are added matters. Earlier rules take
5985
/// precedence. If a node is matched by an earlier rule, then later rules won't be permitted to
6086
/// match to it.

crates/ra_ssr/src/matching.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -576,8 +576,8 @@ mod tests {
576576
let rule: SsrRule = "foo($x) ==>> bar($x)".parse().unwrap();
577577
let input = "fn foo() {} fn bar() {} fn main() { foo(1+2); }";
578578

579-
let (db, _) = crate::tests::single_file(input);
580-
let mut match_finder = MatchFinder::new(&db);
579+
let (db, position) = crate::tests::single_file(input);
580+
let mut match_finder = MatchFinder::in_context(&db, position);
581581
match_finder.add_rule(rule);
582582
let matches = match_finder.matches();
583583
assert_eq!(matches.matches.len(), 1);

crates/ra_ssr/src/tests.rs

Lines changed: 24 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use crate::{MatchFinder, SsrRule};
22
use expect::{expect, Expect};
3-
use ra_db::{salsa::Durability, FileId, SourceDatabaseExt};
3+
use ra_db::{salsa::Durability, FileId, FilePosition, SourceDatabaseExt};
44
use rustc_hash::FxHashSet;
55
use std::sync::Arc;
66
use test_utils::mark;
@@ -59,24 +59,30 @@ fn parser_undefined_placeholder_in_replacement() {
5959
);
6060
}
6161

62-
pub(crate) fn single_file(code: &str) -> (ra_ide_db::RootDatabase, FileId) {
62+
/// `code` may optionally contain a cursor marker `<|>`. If it doesn't, then the position will be
63+
/// the start of the file.
64+
pub(crate) fn single_file(code: &str) -> (ra_ide_db::RootDatabase, FilePosition) {
6365
use ra_db::fixture::WithFixture;
6466
use ra_ide_db::symbol_index::SymbolsDatabase;
65-
let (db, file_id) = ra_ide_db::RootDatabase::with_single_file(code);
66-
let mut db = db;
67+
let (mut db, position) = if code.contains(test_utils::CURSOR_MARKER) {
68+
ra_ide_db::RootDatabase::with_position(code)
69+
} else {
70+
let (db, file_id) = ra_ide_db::RootDatabase::with_single_file(code);
71+
(db, FilePosition { file_id, offset: 0.into() })
72+
};
6773
let mut local_roots = FxHashSet::default();
6874
local_roots.insert(ra_db::fixture::WORKSPACE);
6975
db.set_local_roots_with_durability(Arc::new(local_roots), Durability::HIGH);
70-
(db, file_id)
76+
(db, position)
7177
}
7278

7379
fn assert_ssr_transform(rule: &str, input: &str, expected: Expect) {
7480
assert_ssr_transforms(&[rule], input, expected);
7581
}
7682

7783
fn assert_ssr_transforms(rules: &[&str], input: &str, expected: Expect) {
78-
let (db, file_id) = single_file(input);
79-
let mut match_finder = MatchFinder::new(&db);
84+
let (db, position) = single_file(input);
85+
let mut match_finder = MatchFinder::in_context(&db, position);
8086
for rule in rules {
8187
let rule: SsrRule = rule.parse().unwrap();
8288
match_finder.add_rule(rule);
@@ -85,10 +91,10 @@ fn assert_ssr_transforms(rules: &[&str], input: &str, expected: Expect) {
8591
if edits.is_empty() {
8692
panic!("No edits were made");
8793
}
88-
assert_eq!(edits[0].file_id, file_id);
94+
assert_eq!(edits[0].file_id, position.file_id);
8995
// Note, db.file_text is not necessarily the same as `input`, since fixture parsing alters
9096
// stuff.
91-
let mut actual = db.file_text(file_id).to_string();
97+
let mut actual = db.file_text(position.file_id).to_string();
9298
edits[0].edit.apply(&mut actual);
9399
expected.assert_eq(&actual);
94100
}
@@ -106,34 +112,34 @@ fn print_match_debug_info(match_finder: &MatchFinder, file_id: FileId, snippet:
106112
}
107113

108114
fn assert_matches(pattern: &str, code: &str, expected: &[&str]) {
109-
let (db, file_id) = single_file(code);
110-
let mut match_finder = MatchFinder::new(&db);
115+
let (db, position) = single_file(code);
116+
let mut match_finder = MatchFinder::in_context(&db, position);
111117
match_finder.add_search_pattern(pattern.parse().unwrap());
112118
let matched_strings: Vec<String> =
113119
match_finder.matches().flattened().matches.iter().map(|m| m.matched_text()).collect();
114120
if matched_strings != expected && !expected.is_empty() {
115-
print_match_debug_info(&match_finder, file_id, &expected[0]);
121+
print_match_debug_info(&match_finder, position.file_id, &expected[0]);
116122
}
117123
assert_eq!(matched_strings, expected);
118124
}
119125

120126
fn assert_no_match(pattern: &str, code: &str) {
121-
let (db, file_id) = single_file(code);
122-
let mut match_finder = MatchFinder::new(&db);
127+
let (db, position) = single_file(code);
128+
let mut match_finder = MatchFinder::in_context(&db, position);
123129
match_finder.add_search_pattern(pattern.parse().unwrap());
124130
let matches = match_finder.matches().flattened().matches;
125131
if !matches.is_empty() {
126-
print_match_debug_info(&match_finder, file_id, &matches[0].matched_text());
132+
print_match_debug_info(&match_finder, position.file_id, &matches[0].matched_text());
127133
panic!("Got {} matches when we expected none: {:#?}", matches.len(), matches);
128134
}
129135
}
130136

131137
fn assert_match_failure_reason(pattern: &str, code: &str, snippet: &str, expected_reason: &str) {
132-
let (db, file_id) = single_file(code);
133-
let mut match_finder = MatchFinder::new(&db);
138+
let (db, position) = single_file(code);
139+
let mut match_finder = MatchFinder::in_context(&db, position);
134140
match_finder.add_search_pattern(pattern.parse().unwrap());
135141
let mut reasons = Vec::new();
136-
for d in match_finder.debug_where_text_equal(file_id, snippet) {
142+
for d in match_finder.debug_where_text_equal(position.file_id, snippet) {
137143
if let Some(reason) = d.match_failure_reason() {
138144
reasons.push(reason.to_owned());
139145
}

crates/rust-analyzer/src/cli/ssr.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ pub fn apply_ssr_rules(rules: Vec<SsrRule>) -> Result<()> {
77
use ra_db::SourceDatabaseExt;
88
let (host, vfs) = load_cargo(&std::env::current_dir()?, true, true)?;
99
let db = host.raw_database();
10-
let mut match_finder = MatchFinder::new(db);
10+
let mut match_finder = MatchFinder::at_first_file(db)?;
1111
for rule in rules {
1212
match_finder.add_rule(rule);
1313
}
@@ -30,7 +30,7 @@ pub fn search_for_patterns(patterns: Vec<SsrPattern>, debug_snippet: Option<Stri
3030
use ra_ide_db::symbol_index::SymbolsDatabase;
3131
let (host, _vfs) = load_cargo(&std::env::current_dir()?, true, true)?;
3232
let db = host.raw_database();
33-
let mut match_finder = MatchFinder::new(db);
33+
let mut match_finder = MatchFinder::at_first_file(db)?;
3434
for pattern in patterns {
3535
match_finder.add_search_pattern(pattern);
3636
}

crates/rust-analyzer/src/handlers.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1026,8 +1026,9 @@ pub(crate) fn handle_ssr(
10261026
params: lsp_ext::SsrParams,
10271027
) -> Result<lsp_types::WorkspaceEdit> {
10281028
let _p = profile("handle_ssr");
1029+
let position = from_proto::file_position(&snap, params.position)?;
10291030
let source_change =
1030-
snap.analysis.structural_search_replace(&params.query, params.parse_only)??;
1031+
snap.analysis.structural_search_replace(&params.query, params.parse_only, position)??;
10311032
to_proto::workspace_edit(&snap, source_change)
10321033
}
10331034

crates/rust-analyzer/src/lsp_ext.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,11 @@ impl Request for Ssr {
216216
pub struct SsrParams {
217217
pub query: String,
218218
pub parse_only: bool,
219+
220+
/// File position where SSR was invoked. Paths in `query` will be resolved relative to this
221+
/// position.
222+
#[serde(flatten)]
223+
pub position: lsp_types::TextDocumentPositionParams,
219224
}
220225

221226
pub enum StatusNotification {}

docs/dev/lsp-extensions.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,11 @@ interface SsrParams {
274274
query: string,
275275
/// If true, only check the syntax of the query and don't compute the actual edit.
276276
parseOnly: bool,
277+
/// The current text document. This and `position` will be used to determine in what scope
278+
/// paths in `query` should be resolved.
279+
textDocument: lc.TextDocumentIdentifier;
280+
/// Position where SSR was invoked.
281+
position: lc.Position;
277282
}
278283
```
279284

@@ -285,7 +290,7 @@ WorkspaceEdit
285290

286291
### Example
287292

288-
SSR with query `foo($a:expr, $b:expr) ==>> ($a).foo($b)` will transform, eg `foo(y + 5, z)` into `(y + 5).foo(z)`.
293+
SSR with query `foo($a, $b) ==>> ($a).foo($b)` will transform, eg `foo(y + 5, z)` into `(y + 5).foo(z)`.
289294

290295
### Unresolved Question
291296

editors/code/src/commands.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -185,15 +185,21 @@ export function parentModule(ctx: Ctx): Cmd {
185185

186186
export function ssr(ctx: Ctx): Cmd {
187187
return async () => {
188+
const editor = vscode.window.activeTextEditor;
188189
const client = ctx.client;
189-
if (!client) return;
190+
if (!editor || !client) return;
191+
192+
const position = editor.selection.active;
193+
let textDocument = { uri: editor.document.uri.toString() };
190194

191195
const options: vscode.InputBoxOptions = {
192196
value: "() ==>> ()",
193197
prompt: "Enter request, for example 'Foo($a) ==> Foo::new($a)' ",
194198
validateInput: async (x: string) => {
195199
try {
196-
await client.sendRequest(ra.ssr, { query: x, parseOnly: true });
200+
await client.sendRequest(ra.ssr, {
201+
query: x, parseOnly: true, textDocument, position,
202+
});
197203
} catch (e) {
198204
return e.toString();
199205
}
@@ -208,7 +214,9 @@ export function ssr(ctx: Ctx): Cmd {
208214
title: "Structured search replace in progress...",
209215
cancellable: false,
210216
}, async (_progress, _token) => {
211-
const edit = await client.sendRequest(ra.ssr, { query: request, parseOnly: false });
217+
const edit = await client.sendRequest(ra.ssr, {
218+
query: request, parseOnly: false, textDocument, position
219+
});
212220

213221
await vscode.workspace.applyEdit(client.protocol2CodeConverter.asWorkspaceEdit(edit));
214222
});

0 commit comments

Comments
 (0)