mirror of
https://github.com/rust-lang/rust.git
synced 2025-05-09 16:37:36 +00:00
Merge #4571
4571: KISS SourceChange r=matklad a=matklad The idea behind requiring the label is a noble one, but we are not really using it consistently anyway, and it should be easy to retrofit later, should we need it. bors r+ Co-authored-by: Aleksey Kladov <aleksey.kladov@gmail.com>
This commit is contained in:
commit
a95bb1355d
@ -5,7 +5,7 @@ use hir::Semantics;
|
|||||||
use ra_db::{FileId, FileRange};
|
use ra_db::{FileId, FileRange};
|
||||||
use ra_fmt::{leading_indent, reindent};
|
use ra_fmt::{leading_indent, reindent};
|
||||||
use ra_ide_db::{
|
use ra_ide_db::{
|
||||||
source_change::{SingleFileChange, SourceChange},
|
source_change::{SourceChange, SourceFileEdit},
|
||||||
RootDatabase,
|
RootDatabase,
|
||||||
};
|
};
|
||||||
use ra_syntax::{
|
use ra_syntax::{
|
||||||
@ -150,11 +150,10 @@ impl Assists {
|
|||||||
self.add_impl(label, f)
|
self.add_impl(label, f)
|
||||||
}
|
}
|
||||||
fn add_impl(&mut self, label: Assist, f: impl FnOnce(&mut AssistBuilder)) -> Option<()> {
|
fn add_impl(&mut self, label: Assist, f: impl FnOnce(&mut AssistBuilder)) -> Option<()> {
|
||||||
let change_label = label.label.clone();
|
|
||||||
let source_change = if self.resolve {
|
let source_change = if self.resolve {
|
||||||
let mut builder = AssistBuilder::new(self.file);
|
let mut builder = AssistBuilder::new(self.file);
|
||||||
f(&mut builder);
|
f(&mut builder);
|
||||||
Some(builder.finish(change_label))
|
Some(builder.finish())
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
};
|
};
|
||||||
@ -246,9 +245,10 @@ impl AssistBuilder {
|
|||||||
&mut self.edit
|
&mut self.edit
|
||||||
}
|
}
|
||||||
|
|
||||||
fn finish(self, change_label: String) -> SourceChange {
|
fn finish(self) -> SourceChange {
|
||||||
let edit = self.edit.finish();
|
let edit = self.edit.finish();
|
||||||
let mut res = SingleFileChange { label: change_label, edit }.into_source_change(self.file);
|
let source_file_edit = SourceFileEdit { file_id: self.file, edit };
|
||||||
|
let mut res: SourceChange = source_file_edit.into();
|
||||||
if self.is_snippet {
|
if self.is_snippet {
|
||||||
res.is_snippet = true;
|
res.is_snippet = true;
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,7 @@ use ra_syntax::{
|
|||||||
};
|
};
|
||||||
use ra_text_edit::{TextEdit, TextEditBuilder};
|
use ra_text_edit::{TextEdit, TextEditBuilder};
|
||||||
|
|
||||||
use crate::{Diagnostic, FileId, FileSystemEdit, SourceChange, SourceFileEdit};
|
use crate::{Diagnostic, FileId, FileSystemEdit, Fix, SourceChange, SourceFileEdit};
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone)]
|
#[derive(Debug, Copy, Clone)]
|
||||||
pub enum Severity {
|
pub enum Severity {
|
||||||
@ -63,8 +63,8 @@ pub(crate) fn diagnostics(db: &RootDatabase, file_id: FileId) -> Vec<Diagnostic>
|
|||||||
.parent()
|
.parent()
|
||||||
.unwrap_or_else(|| RelativePath::new(""))
|
.unwrap_or_else(|| RelativePath::new(""))
|
||||||
.join(&d.candidate);
|
.join(&d.candidate);
|
||||||
let create_file = FileSystemEdit::CreateFile { source_root, path };
|
let fix =
|
||||||
let fix = SourceChange::file_system_edit("Create module", create_file);
|
Fix::new("Create module", FileSystemEdit::CreateFile { source_root, path }.into());
|
||||||
res.borrow_mut().push(Diagnostic {
|
res.borrow_mut().push(Diagnostic {
|
||||||
range: sema.diagnostics_range(d).range,
|
range: sema.diagnostics_range(d).range,
|
||||||
message: d.message(),
|
message: d.message(),
|
||||||
@ -88,14 +88,12 @@ pub(crate) fn diagnostics(db: &RootDatabase, file_id: FileId) -> Vec<Diagnostic>
|
|||||||
field_list = field_list.append_field(&field);
|
field_list = field_list.append_field(&field);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut builder = TextEditBuilder::default();
|
let edit = {
|
||||||
algo::diff(&d.ast(db).syntax(), &field_list.syntax()).into_text_edit(&mut builder);
|
let mut builder = TextEditBuilder::default();
|
||||||
|
algo::diff(&d.ast(db).syntax(), &field_list.syntax()).into_text_edit(&mut builder);
|
||||||
Some(SourceChange::source_file_edit_from(
|
builder.finish()
|
||||||
"Fill struct fields",
|
};
|
||||||
file_id,
|
Some(Fix::new("Fill struct fields", SourceFileEdit { file_id, edit }.into()))
|
||||||
builder.finish(),
|
|
||||||
))
|
|
||||||
};
|
};
|
||||||
|
|
||||||
res.borrow_mut().push(Diagnostic {
|
res.borrow_mut().push(Diagnostic {
|
||||||
@ -117,7 +115,8 @@ pub(crate) fn diagnostics(db: &RootDatabase, file_id: FileId) -> Vec<Diagnostic>
|
|||||||
let node = d.ast(db);
|
let node = d.ast(db);
|
||||||
let replacement = format!("Ok({})", node.syntax());
|
let replacement = format!("Ok({})", node.syntax());
|
||||||
let edit = TextEdit::replace(node.syntax().text_range(), replacement);
|
let edit = TextEdit::replace(node.syntax().text_range(), replacement);
|
||||||
let fix = SourceChange::source_file_edit_from("Wrap with ok", file_id, edit);
|
let source_change = SourceChange::source_file_edit_from(file_id, edit);
|
||||||
|
let fix = Fix::new("Wrap with ok", source_change);
|
||||||
res.borrow_mut().push(Diagnostic {
|
res.borrow_mut().push(Diagnostic {
|
||||||
range: sema.diagnostics_range(d).range,
|
range: sema.diagnostics_range(d).range,
|
||||||
message: d.message(),
|
message: d.message(),
|
||||||
@ -154,9 +153,9 @@ fn check_unnecessary_braces_in_use_statement(
|
|||||||
range,
|
range,
|
||||||
message: "Unnecessary braces in use statement".to_string(),
|
message: "Unnecessary braces in use statement".to_string(),
|
||||||
severity: Severity::WeakWarning,
|
severity: Severity::WeakWarning,
|
||||||
fix: Some(SourceChange::source_file_edit(
|
fix: Some(Fix::new(
|
||||||
"Remove unnecessary braces",
|
"Remove unnecessary braces",
|
||||||
SourceFileEdit { file_id, edit },
|
SourceFileEdit { file_id, edit }.into(),
|
||||||
)),
|
)),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -198,9 +197,9 @@ fn check_struct_shorthand_initialization(
|
|||||||
range: record_field.syntax().text_range(),
|
range: record_field.syntax().text_range(),
|
||||||
message: "Shorthand struct initialization".to_string(),
|
message: "Shorthand struct initialization".to_string(),
|
||||||
severity: Severity::WeakWarning,
|
severity: Severity::WeakWarning,
|
||||||
fix: Some(SourceChange::source_file_edit(
|
fix: Some(Fix::new(
|
||||||
"Use struct shorthand initialization",
|
"Use struct shorthand initialization",
|
||||||
SourceFileEdit { file_id, edit },
|
SourceFileEdit { file_id, edit }.into(),
|
||||||
)),
|
)),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -240,7 +239,7 @@ mod tests {
|
|||||||
let diagnostic =
|
let diagnostic =
|
||||||
diagnostics.pop().unwrap_or_else(|| panic!("no diagnostics for:\n{}\n", before));
|
diagnostics.pop().unwrap_or_else(|| panic!("no diagnostics for:\n{}\n", before));
|
||||||
let mut fix = diagnostic.fix.unwrap();
|
let mut fix = diagnostic.fix.unwrap();
|
||||||
let edit = fix.source_file_edits.pop().unwrap().edit;
|
let edit = fix.source_change.source_file_edits.pop().unwrap().edit;
|
||||||
let actual = {
|
let actual = {
|
||||||
let mut actual = before.to_string();
|
let mut actual = before.to_string();
|
||||||
edit.apply(&mut actual);
|
edit.apply(&mut actual);
|
||||||
@ -258,7 +257,7 @@ mod tests {
|
|||||||
let (analysis, file_position) = analysis_and_position(fixture);
|
let (analysis, file_position) = analysis_and_position(fixture);
|
||||||
let diagnostic = analysis.diagnostics(file_position.file_id).unwrap().pop().unwrap();
|
let diagnostic = analysis.diagnostics(file_position.file_id).unwrap().pop().unwrap();
|
||||||
let mut fix = diagnostic.fix.unwrap();
|
let mut fix = diagnostic.fix.unwrap();
|
||||||
let edit = fix.source_file_edits.pop().unwrap().edit;
|
let edit = fix.source_change.source_file_edits.pop().unwrap().edit;
|
||||||
let target_file_contents = analysis.file_text(file_position.file_id).unwrap();
|
let target_file_contents = analysis.file_text(file_position.file_id).unwrap();
|
||||||
let actual = {
|
let actual = {
|
||||||
let mut actual = target_file_contents.to_string();
|
let mut actual = target_file_contents.to_string();
|
||||||
@ -295,7 +294,7 @@ mod tests {
|
|||||||
let (analysis, file_id) = single_file(before);
|
let (analysis, file_id) = single_file(before);
|
||||||
let diagnostic = analysis.diagnostics(file_id).unwrap().pop().unwrap();
|
let diagnostic = analysis.diagnostics(file_id).unwrap().pop().unwrap();
|
||||||
let mut fix = diagnostic.fix.unwrap();
|
let mut fix = diagnostic.fix.unwrap();
|
||||||
let edit = fix.source_file_edits.pop().unwrap().edit;
|
let edit = fix.source_change.source_file_edits.pop().unwrap().edit;
|
||||||
let actual = {
|
let actual = {
|
||||||
let mut actual = before.to_string();
|
let mut actual = before.to_string();
|
||||||
edit.apply(&mut actual);
|
edit.apply(&mut actual);
|
||||||
@ -616,22 +615,24 @@ mod tests {
|
|||||||
Diagnostic {
|
Diagnostic {
|
||||||
message: "unresolved module",
|
message: "unresolved module",
|
||||||
range: 0..8,
|
range: 0..8,
|
||||||
|
severity: Error,
|
||||||
fix: Some(
|
fix: Some(
|
||||||
SourceChange {
|
Fix {
|
||||||
label: "Create module",
|
label: "Create module",
|
||||||
source_file_edits: [],
|
source_change: SourceChange {
|
||||||
file_system_edits: [
|
source_file_edits: [],
|
||||||
CreateFile {
|
file_system_edits: [
|
||||||
source_root: SourceRootId(
|
CreateFile {
|
||||||
0,
|
source_root: SourceRootId(
|
||||||
),
|
0,
|
||||||
path: "foo.rs",
|
),
|
||||||
},
|
path: "foo.rs",
|
||||||
],
|
},
|
||||||
is_snippet: false,
|
],
|
||||||
|
is_snippet: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
severity: Error,
|
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
"###);
|
"###);
|
||||||
@ -665,29 +666,31 @@ mod tests {
|
|||||||
Diagnostic {
|
Diagnostic {
|
||||||
message: "Missing structure fields:\n- b",
|
message: "Missing structure fields:\n- b",
|
||||||
range: 224..233,
|
range: 224..233,
|
||||||
|
severity: Error,
|
||||||
fix: Some(
|
fix: Some(
|
||||||
SourceChange {
|
Fix {
|
||||||
label: "Fill struct fields",
|
label: "Fill struct fields",
|
||||||
source_file_edits: [
|
source_change: SourceChange {
|
||||||
SourceFileEdit {
|
source_file_edits: [
|
||||||
file_id: FileId(
|
SourceFileEdit {
|
||||||
1,
|
file_id: FileId(
|
||||||
),
|
1,
|
||||||
edit: TextEdit {
|
),
|
||||||
indels: [
|
edit: TextEdit {
|
||||||
Indel {
|
indels: [
|
||||||
insert: "{a:42, b: ()}",
|
Indel {
|
||||||
delete: 3..9,
|
insert: "{a:42, b: ()}",
|
||||||
},
|
delete: 3..9,
|
||||||
],
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
],
|
||||||
],
|
file_system_edits: [],
|
||||||
file_system_edits: [],
|
is_snippet: false,
|
||||||
is_snippet: false,
|
},
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
severity: Error,
|
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
"###);
|
"###);
|
||||||
|
@ -97,8 +97,22 @@ pub type Cancelable<T> = Result<T, Canceled>;
|
|||||||
pub struct Diagnostic {
|
pub struct Diagnostic {
|
||||||
pub message: String,
|
pub message: String,
|
||||||
pub range: TextRange,
|
pub range: TextRange,
|
||||||
pub fix: Option<SourceChange>,
|
|
||||||
pub severity: Severity,
|
pub severity: Severity,
|
||||||
|
pub fix: Option<Fix>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Fix {
|
||||||
|
pub label: String,
|
||||||
|
pub source_change: SourceChange,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Fix {
|
||||||
|
pub fn new(label: impl Into<String>, source_change: SourceChange) -> Self {
|
||||||
|
let label = label.into();
|
||||||
|
assert!(label.starts_with(char::is_uppercase) && !label.ends_with('.'));
|
||||||
|
Self { label, source_change }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Info associated with a text range.
|
/// Info associated with a text range.
|
||||||
@ -493,7 +507,7 @@ impl Analysis {
|
|||||||
) -> Cancelable<Result<SourceChange, SsrError>> {
|
) -> Cancelable<Result<SourceChange, SsrError>> {
|
||||||
self.with_db(|db| {
|
self.with_db(|db| {
|
||||||
let edits = ssr::parse_search_replace(query, parse_only, db)?;
|
let edits = ssr::parse_search_replace(query, parse_only, db)?;
|
||||||
Ok(SourceChange::source_file_edits("Structural Search Replace", edits))
|
Ok(SourceChange::source_file_edits(edits))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -128,7 +128,7 @@ fn rename_mod(
|
|||||||
source_file_edits.extend(ref_edits);
|
source_file_edits.extend(ref_edits);
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(SourceChange::from_edits("Rename", source_file_edits, file_system_edits))
|
Some(SourceChange::from_edits(source_file_edits, file_system_edits))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn rename_to_self(db: &RootDatabase, position: FilePosition) -> Option<RangeInfo<SourceChange>> {
|
fn rename_to_self(db: &RootDatabase, position: FilePosition) -> Option<RangeInfo<SourceChange>> {
|
||||||
@ -171,7 +171,7 @@ fn rename_to_self(db: &RootDatabase, position: FilePosition) -> Option<RangeInfo
|
|||||||
),
|
),
|
||||||
});
|
});
|
||||||
|
|
||||||
Some(RangeInfo::new(range, SourceChange::source_file_edits("Rename", edits)))
|
Some(RangeInfo::new(range, SourceChange::source_file_edits(edits)))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn text_edit_from_self_param(
|
fn text_edit_from_self_param(
|
||||||
@ -234,7 +234,7 @@ fn rename_self_to_param(
|
|||||||
let range = ast::SelfParam::cast(self_token.parent())
|
let range = ast::SelfParam::cast(self_token.parent())
|
||||||
.map_or(self_token.text_range(), |p| p.syntax().text_range());
|
.map_or(self_token.text_range(), |p| p.syntax().text_range());
|
||||||
|
|
||||||
Some(RangeInfo::new(range, SourceChange::source_file_edits("Rename", edits)))
|
Some(RangeInfo::new(range, SourceChange::source_file_edits(edits)))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn rename_reference(
|
fn rename_reference(
|
||||||
@ -253,7 +253,7 @@ fn rename_reference(
|
|||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(RangeInfo::new(range, SourceChange::source_file_edits("Rename", edit)))
|
Some(RangeInfo::new(range, SourceChange::source_file_edits(edit)))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@ -642,7 +642,6 @@ mod tests {
|
|||||||
RangeInfo {
|
RangeInfo {
|
||||||
range: 4..7,
|
range: 4..7,
|
||||||
info: SourceChange {
|
info: SourceChange {
|
||||||
label: "Rename",
|
|
||||||
source_file_edits: [
|
source_file_edits: [
|
||||||
SourceFileEdit {
|
SourceFileEdit {
|
||||||
file_id: FileId(
|
file_id: FileId(
|
||||||
@ -694,7 +693,6 @@ mod tests {
|
|||||||
RangeInfo {
|
RangeInfo {
|
||||||
range: 4..7,
|
range: 4..7,
|
||||||
info: SourceChange {
|
info: SourceChange {
|
||||||
label: "Rename",
|
|
||||||
source_file_edits: [
|
source_file_edits: [
|
||||||
SourceFileEdit {
|
SourceFileEdit {
|
||||||
file_id: FileId(
|
file_id: FileId(
|
||||||
@ -777,7 +775,6 @@ mod tests {
|
|||||||
RangeInfo {
|
RangeInfo {
|
||||||
range: 8..11,
|
range: 8..11,
|
||||||
info: SourceChange {
|
info: SourceChange {
|
||||||
label: "Rename",
|
|
||||||
source_file_edits: [
|
source_file_edits: [
|
||||||
SourceFileEdit {
|
SourceFileEdit {
|
||||||
file_id: FileId(
|
file_id: FileId(
|
||||||
|
@ -17,7 +17,7 @@ mod on_enter;
|
|||||||
|
|
||||||
use ra_db::{FilePosition, SourceDatabase};
|
use ra_db::{FilePosition, SourceDatabase};
|
||||||
use ra_fmt::leading_indent;
|
use ra_fmt::leading_indent;
|
||||||
use ra_ide_db::{source_change::SingleFileChange, RootDatabase};
|
use ra_ide_db::RootDatabase;
|
||||||
use ra_syntax::{
|
use ra_syntax::{
|
||||||
algo::find_node_at_offset,
|
algo::find_node_at_offset,
|
||||||
ast::{self, AstToken},
|
ast::{self, AstToken},
|
||||||
@ -40,15 +40,11 @@ pub(crate) fn on_char_typed(
|
|||||||
assert!(TRIGGER_CHARS.contains(char_typed));
|
assert!(TRIGGER_CHARS.contains(char_typed));
|
||||||
let file = &db.parse(position.file_id).tree();
|
let file = &db.parse(position.file_id).tree();
|
||||||
assert_eq!(file.syntax().text().char_at(position.offset), Some(char_typed));
|
assert_eq!(file.syntax().text().char_at(position.offset), Some(char_typed));
|
||||||
let single_file_change = on_char_typed_inner(file, position.offset, char_typed)?;
|
let text_edit = on_char_typed_inner(file, position.offset, char_typed)?;
|
||||||
Some(single_file_change.into_source_change(position.file_id))
|
Some(SourceChange::source_file_edit_from(position.file_id, text_edit))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_char_typed_inner(
|
fn on_char_typed_inner(file: &SourceFile, offset: TextSize, char_typed: char) -> Option<TextEdit> {
|
||||||
file: &SourceFile,
|
|
||||||
offset: TextSize,
|
|
||||||
char_typed: char,
|
|
||||||
) -> Option<SingleFileChange> {
|
|
||||||
assert!(TRIGGER_CHARS.contains(char_typed));
|
assert!(TRIGGER_CHARS.contains(char_typed));
|
||||||
match char_typed {
|
match char_typed {
|
||||||
'.' => on_dot_typed(file, offset),
|
'.' => on_dot_typed(file, offset),
|
||||||
@ -61,7 +57,7 @@ fn on_char_typed_inner(
|
|||||||
/// Returns an edit which should be applied after `=` was typed. Primarily,
|
/// Returns an edit which should be applied after `=` was typed. Primarily,
|
||||||
/// this works when adding `let =`.
|
/// this works when adding `let =`.
|
||||||
// FIXME: use a snippet completion instead of this hack here.
|
// FIXME: use a snippet completion instead of this hack here.
|
||||||
fn on_eq_typed(file: &SourceFile, offset: TextSize) -> Option<SingleFileChange> {
|
fn on_eq_typed(file: &SourceFile, offset: TextSize) -> Option<TextEdit> {
|
||||||
assert_eq!(file.syntax().text().char_at(offset), Some('='));
|
assert_eq!(file.syntax().text().char_at(offset), Some('='));
|
||||||
let let_stmt: ast::LetStmt = find_node_at_offset(file.syntax(), offset)?;
|
let let_stmt: ast::LetStmt = find_node_at_offset(file.syntax(), offset)?;
|
||||||
if let_stmt.semicolon_token().is_some() {
|
if let_stmt.semicolon_token().is_some() {
|
||||||
@ -79,14 +75,11 @@ fn on_eq_typed(file: &SourceFile, offset: TextSize) -> Option<SingleFileChange>
|
|||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
let offset = let_stmt.syntax().text_range().end();
|
let offset = let_stmt.syntax().text_range().end();
|
||||||
Some(SingleFileChange {
|
Some(TextEdit::insert(offset, ";".to_string()))
|
||||||
label: "add semicolon".to_string(),
|
|
||||||
edit: TextEdit::insert(offset, ";".to_string()),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns an edit which should be applied when a dot ('.') is typed on a blank line, indenting the line appropriately.
|
/// Returns an edit which should be applied when a dot ('.') is typed on a blank line, indenting the line appropriately.
|
||||||
fn on_dot_typed(file: &SourceFile, offset: TextSize) -> Option<SingleFileChange> {
|
fn on_dot_typed(file: &SourceFile, offset: TextSize) -> Option<TextEdit> {
|
||||||
assert_eq!(file.syntax().text().char_at(offset), Some('.'));
|
assert_eq!(file.syntax().text().char_at(offset), Some('.'));
|
||||||
let whitespace =
|
let whitespace =
|
||||||
file.syntax().token_at_offset(offset).left_biased().and_then(ast::Whitespace::cast)?;
|
file.syntax().token_at_offset(offset).left_biased().and_then(ast::Whitespace::cast)?;
|
||||||
@ -107,14 +100,11 @@ fn on_dot_typed(file: &SourceFile, offset: TextSize) -> Option<SingleFileChange>
|
|||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(SingleFileChange {
|
Some(TextEdit::replace(TextRange::new(offset - current_indent_len, offset), target_indent))
|
||||||
label: "reindent dot".to_string(),
|
|
||||||
edit: TextEdit::replace(TextRange::new(offset - current_indent_len, offset), target_indent),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adds a space after an arrow when `fn foo() { ... }` is turned into `fn foo() -> { ... }`
|
/// Adds a space after an arrow when `fn foo() { ... }` is turned into `fn foo() -> { ... }`
|
||||||
fn on_arrow_typed(file: &SourceFile, offset: TextSize) -> Option<SingleFileChange> {
|
fn on_arrow_typed(file: &SourceFile, offset: TextSize) -> Option<TextEdit> {
|
||||||
let file_text = file.syntax().text();
|
let file_text = file.syntax().text();
|
||||||
assert_eq!(file_text.char_at(offset), Some('>'));
|
assert_eq!(file_text.char_at(offset), Some('>'));
|
||||||
let after_arrow = offset + TextSize::of('>');
|
let after_arrow = offset + TextSize::of('>');
|
||||||
@ -125,10 +115,7 @@ fn on_arrow_typed(file: &SourceFile, offset: TextSize) -> Option<SingleFileChang
|
|||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
Some(SingleFileChange {
|
Some(TextEdit::insert(after_arrow, " ".to_string()))
|
||||||
label: "add space after return type".to_string(),
|
|
||||||
edit: TextEdit::insert(after_arrow, " ".to_string()),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@ -144,7 +131,7 @@ mod tests {
|
|||||||
edit.apply(&mut before);
|
edit.apply(&mut before);
|
||||||
let parse = SourceFile::parse(&before);
|
let parse = SourceFile::parse(&before);
|
||||||
on_char_typed_inner(&parse.tree(), offset, char_typed).map(|it| {
|
on_char_typed_inner(&parse.tree(), offset, char_typed).map(|it| {
|
||||||
it.edit.apply(&mut before);
|
it.apply(&mut before);
|
||||||
before.to_string()
|
before.to_string()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -41,10 +41,7 @@ pub(crate) fn on_enter(db: &RootDatabase, position: FilePosition) -> Option<Sour
|
|||||||
let inserted = format!("\n{}{} $0", indent, prefix);
|
let inserted = format!("\n{}{} $0", indent, prefix);
|
||||||
let edit = TextEdit::insert(position.offset, inserted);
|
let edit = TextEdit::insert(position.offset, inserted);
|
||||||
|
|
||||||
let mut res = SourceChange::source_file_edit(
|
let mut res = SourceChange::from(SourceFileEdit { edit, file_id: position.file_id });
|
||||||
"On enter",
|
|
||||||
SourceFileEdit { edit, file_id: position.file_id },
|
|
||||||
);
|
|
||||||
res.is_snippet = true;
|
res.is_snippet = true;
|
||||||
Some(res)
|
Some(res)
|
||||||
}
|
}
|
||||||
|
@ -8,8 +8,6 @@ use ra_text_edit::TextEdit;
|
|||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct SourceChange {
|
pub struct SourceChange {
|
||||||
/// For display in the undo log in the editor
|
|
||||||
pub label: String,
|
|
||||||
pub source_file_edits: Vec<SourceFileEdit>,
|
pub source_file_edits: Vec<SourceFileEdit>,
|
||||||
pub file_system_edits: Vec<FileSystemEdit>,
|
pub file_system_edits: Vec<FileSystemEdit>,
|
||||||
pub is_snippet: bool,
|
pub is_snippet: bool,
|
||||||
@ -18,63 +16,22 @@ pub struct SourceChange {
|
|||||||
impl SourceChange {
|
impl SourceChange {
|
||||||
/// Creates a new SourceChange with the given label
|
/// Creates a new SourceChange with the given label
|
||||||
/// from the edits.
|
/// from the edits.
|
||||||
pub fn from_edits<L: Into<String>>(
|
pub fn from_edits(
|
||||||
label: L,
|
|
||||||
source_file_edits: Vec<SourceFileEdit>,
|
source_file_edits: Vec<SourceFileEdit>,
|
||||||
file_system_edits: Vec<FileSystemEdit>,
|
file_system_edits: Vec<FileSystemEdit>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
SourceChange {
|
SourceChange { source_file_edits, file_system_edits, is_snippet: false }
|
||||||
label: label.into(),
|
|
||||||
source_file_edits,
|
|
||||||
file_system_edits,
|
|
||||||
is_snippet: false,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a new SourceChange with the given label,
|
/// Creates a new SourceChange with the given label,
|
||||||
/// containing only the given `SourceFileEdits`.
|
/// containing only the given `SourceFileEdits`.
|
||||||
pub fn source_file_edits<L: Into<String>>(label: L, edits: Vec<SourceFileEdit>) -> Self {
|
pub fn source_file_edits(edits: Vec<SourceFileEdit>) -> Self {
|
||||||
let label = label.into();
|
SourceChange { source_file_edits: edits, file_system_edits: vec![], is_snippet: false }
|
||||||
assert!(label.starts_with(char::is_uppercase));
|
|
||||||
SourceChange {
|
|
||||||
label: label,
|
|
||||||
source_file_edits: edits,
|
|
||||||
file_system_edits: vec![],
|
|
||||||
is_snippet: false,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a new SourceChange with the given label,
|
|
||||||
/// containing only the given `FileSystemEdits`.
|
|
||||||
pub(crate) fn file_system_edits<L: Into<String>>(label: L, edits: Vec<FileSystemEdit>) -> Self {
|
|
||||||
SourceChange {
|
|
||||||
label: label.into(),
|
|
||||||
source_file_edits: vec![],
|
|
||||||
file_system_edits: edits,
|
|
||||||
is_snippet: false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates a new SourceChange with the given label,
|
|
||||||
/// containing only a single `SourceFileEdit`.
|
|
||||||
pub fn source_file_edit<L: Into<String>>(label: L, edit: SourceFileEdit) -> Self {
|
|
||||||
SourceChange::source_file_edits(label, vec![edit])
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates a new SourceChange with the given label
|
/// Creates a new SourceChange with the given label
|
||||||
/// from the given `FileId` and `TextEdit`
|
/// from the given `FileId` and `TextEdit`
|
||||||
pub fn source_file_edit_from<L: Into<String>>(
|
pub fn source_file_edit_from(file_id: FileId, edit: TextEdit) -> Self {
|
||||||
label: L,
|
SourceFileEdit { file_id, edit }.into()
|
||||||
file_id: FileId,
|
|
||||||
edit: TextEdit,
|
|
||||||
) -> Self {
|
|
||||||
SourceChange::source_file_edit(label, SourceFileEdit { file_id, edit })
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates a new SourceChange with the given label
|
|
||||||
/// from the given `FileId` and `TextEdit`
|
|
||||||
pub fn file_system_edit<L: Into<String>>(label: L, edit: FileSystemEdit) -> Self {
|
|
||||||
SourceChange::file_system_edits(label, vec![edit])
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -84,23 +41,27 @@ pub struct SourceFileEdit {
|
|||||||
pub edit: TextEdit,
|
pub edit: TextEdit,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<SourceFileEdit> for SourceChange {
|
||||||
|
fn from(edit: SourceFileEdit) -> SourceChange {
|
||||||
|
SourceChange {
|
||||||
|
source_file_edits: vec![edit],
|
||||||
|
file_system_edits: Vec::new(),
|
||||||
|
is_snippet: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum FileSystemEdit {
|
pub enum FileSystemEdit {
|
||||||
CreateFile { source_root: SourceRootId, path: RelativePathBuf },
|
CreateFile { source_root: SourceRootId, path: RelativePathBuf },
|
||||||
MoveFile { src: FileId, dst_source_root: SourceRootId, dst_path: RelativePathBuf },
|
MoveFile { src: FileId, dst_source_root: SourceRootId, dst_path: RelativePathBuf },
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct SingleFileChange {
|
impl From<FileSystemEdit> for SourceChange {
|
||||||
pub label: String,
|
fn from(edit: FileSystemEdit) -> SourceChange {
|
||||||
pub edit: TextEdit,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SingleFileChange {
|
|
||||||
pub fn into_source_change(self, file_id: FileId) -> SourceChange {
|
|
||||||
SourceChange {
|
SourceChange {
|
||||||
label: self.label,
|
source_file_edits: Vec::new(),
|
||||||
source_file_edits: vec![SourceFileEdit { file_id, edit: self.edit }],
|
file_system_edits: vec![edit],
|
||||||
file_system_edits: Vec::new(),
|
|
||||||
is_snippet: false,
|
is_snippet: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -731,9 +731,9 @@ pub fn handle_code_action(
|
|||||||
.filter(|(diag_range, _fix)| diag_range.intersect(range).is_some())
|
.filter(|(diag_range, _fix)| diag_range.intersect(range).is_some())
|
||||||
.map(|(_range, fix)| fix);
|
.map(|(_range, fix)| fix);
|
||||||
|
|
||||||
for source_edit in fixes_from_diagnostics {
|
for fix in fixes_from_diagnostics {
|
||||||
let title = source_edit.label.clone();
|
let title = fix.label;
|
||||||
let edit = to_proto::snippet_workspace_edit(&world, source_edit)?;
|
let edit = to_proto::snippet_workspace_edit(&world, fix.source_change)?;
|
||||||
let action =
|
let action =
|
||||||
lsp_ext::CodeAction { title, group: None, kind: None, edit: Some(edit), command: None };
|
lsp_ext::CodeAction { title, group: None, kind: None, edit: Some(edit), command: None };
|
||||||
res.push(action);
|
res.push(action);
|
||||||
|
Loading…
Reference in New Issue
Block a user