mirror of
https://github.com/rust-lang/rust-analyzer
synced 2024-12-27 05:23:24 +00:00
Merge #4332
4332: Refactor TextEdit r=matklad a=matklad
bors r+
🤖
Co-authored-by: Aleksey Kladov <aleksey.kladov@gmail.com>
This commit is contained in:
commit
30eb458b4f
20 changed files with 273 additions and 229 deletions
|
@ -4,14 +4,13 @@ use ra_db::FileRange;
|
||||||
use ra_fmt::{leading_indent, reindent};
|
use ra_fmt::{leading_indent, reindent};
|
||||||
use ra_ide_db::RootDatabase;
|
use ra_ide_db::RootDatabase;
|
||||||
use ra_syntax::{
|
use ra_syntax::{
|
||||||
algo::{self, find_covering_element, find_node_at_offset},
|
algo::{self, find_covering_element, find_node_at_offset, SyntaxRewriter},
|
||||||
AstNode, SourceFile, SyntaxElement, SyntaxKind, SyntaxNode, SyntaxToken, TextRange, TextSize,
|
AstNode, SourceFile, SyntaxElement, SyntaxKind, SyntaxNode, SyntaxToken, TextRange, TextSize,
|
||||||
TokenAtOffset,
|
TokenAtOffset,
|
||||||
};
|
};
|
||||||
use ra_text_edit::TextEditBuilder;
|
use ra_text_edit::TextEditBuilder;
|
||||||
|
|
||||||
use crate::{AssistAction, AssistFile, AssistId, AssistLabel, GroupLabel, ResolvedAssist};
|
use crate::{AssistAction, AssistFile, AssistId, AssistLabel, GroupLabel, ResolvedAssist};
|
||||||
use algo::SyntaxRewriter;
|
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub(crate) struct Assist(pub(crate) Vec<AssistInfo>);
|
pub(crate) struct Assist(pub(crate) Vec<AssistInfo>);
|
||||||
|
@ -42,8 +41,6 @@ impl AssistInfo {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) type AssistHandler = fn(AssistCtx) -> Option<Assist>;
|
|
||||||
|
|
||||||
/// `AssistCtx` allows to apply an assist or check if it could be applied.
|
/// `AssistCtx` allows to apply an assist or check if it could be applied.
|
||||||
///
|
///
|
||||||
/// Assists use a somewhat over-engineered approach, given the current needs. The
|
/// Assists use a somewhat over-engineered approach, given the current needs. The
|
||||||
|
|
|
@ -30,6 +30,10 @@ fn check(assist_id: &str, before: &str, after: &str) {
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
|
|
||||||
let actual = assist.action.edit.apply(&before);
|
let actual = {
|
||||||
|
let mut actual = before.clone();
|
||||||
|
assist.action.edit.apply(&mut actual);
|
||||||
|
actual
|
||||||
|
};
|
||||||
assert_eq_text!(after, &actual);
|
assert_eq_text!(after, &actual);
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,7 @@ use ra_ide_db::RootDatabase;
|
||||||
use ra_syntax::{TextRange, TextSize};
|
use ra_syntax::{TextRange, TextSize};
|
||||||
use ra_text_edit::TextEdit;
|
use ra_text_edit::TextEdit;
|
||||||
|
|
||||||
pub(crate) use crate::assist_ctx::{Assist, AssistCtx, AssistHandler};
|
pub(crate) use crate::assist_ctx::{Assist, AssistCtx};
|
||||||
|
|
||||||
/// Unique identifier of the assist, should not be shown to the user
|
/// Unique identifier of the assist, should not be shown to the user
|
||||||
/// directly.
|
/// directly.
|
||||||
|
@ -109,7 +109,9 @@ pub fn resolved_assists(db: &RootDatabase, range: FileRange) -> Vec<ResolvedAssi
|
||||||
}
|
}
|
||||||
|
|
||||||
mod handlers {
|
mod handlers {
|
||||||
use crate::AssistHandler;
|
use crate::{Assist, AssistCtx};
|
||||||
|
|
||||||
|
pub(crate) type Handler = fn(AssistCtx) -> Option<Assist>;
|
||||||
|
|
||||||
mod add_custom_impl;
|
mod add_custom_impl;
|
||||||
mod add_derive;
|
mod add_derive;
|
||||||
|
@ -145,12 +147,13 @@ mod handlers {
|
||||||
mod reorder_fields;
|
mod reorder_fields;
|
||||||
mod unwrap_block;
|
mod unwrap_block;
|
||||||
|
|
||||||
pub(crate) fn all() -> &'static [AssistHandler] {
|
pub(crate) fn all() -> &'static [Handler] {
|
||||||
&[
|
&[
|
||||||
// These are alphabetic for the foolish consistency
|
// These are alphabetic for the foolish consistency
|
||||||
add_custom_impl::add_custom_impl,
|
add_custom_impl::add_custom_impl,
|
||||||
add_derive::add_derive,
|
add_derive::add_derive,
|
||||||
add_explicit_type::add_explicit_type,
|
add_explicit_type::add_explicit_type,
|
||||||
|
add_from_impl_for_enum::add_from_impl_for_enum,
|
||||||
add_function::add_function,
|
add_function::add_function,
|
||||||
add_impl::add_impl,
|
add_impl::add_impl,
|
||||||
add_new::add_new,
|
add_new::add_new,
|
||||||
|
@ -176,17 +179,18 @@ mod handlers {
|
||||||
raw_string::remove_hash,
|
raw_string::remove_hash,
|
||||||
remove_dbg::remove_dbg,
|
remove_dbg::remove_dbg,
|
||||||
remove_mut::remove_mut,
|
remove_mut::remove_mut,
|
||||||
|
reorder_fields::reorder_fields,
|
||||||
replace_if_let_with_match::replace_if_let_with_match,
|
replace_if_let_with_match::replace_if_let_with_match,
|
||||||
replace_let_with_if_let::replace_let_with_if_let,
|
replace_let_with_if_let::replace_let_with_if_let,
|
||||||
replace_qualified_name_with_use::replace_qualified_name_with_use,
|
replace_qualified_name_with_use::replace_qualified_name_with_use,
|
||||||
replace_unwrap_with_match::replace_unwrap_with_match,
|
replace_unwrap_with_match::replace_unwrap_with_match,
|
||||||
split_import::split_import,
|
split_import::split_import,
|
||||||
add_from_impl_for_enum::add_from_impl_for_enum,
|
|
||||||
unwrap_block::unwrap_block,
|
unwrap_block::unwrap_block,
|
||||||
// These are manually sorted for better priorities
|
// These are manually sorted for better priorities
|
||||||
add_missing_impl_members::add_missing_impl_members,
|
add_missing_impl_members::add_missing_impl_members,
|
||||||
add_missing_impl_members::add_missing_default_members,
|
add_missing_impl_members::add_missing_default_members,
|
||||||
reorder_fields::reorder_fields,
|
// Are you sure you want to add new assist here, and not to the
|
||||||
|
// sorted list above?
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -195,12 +199,12 @@ mod handlers {
|
||||||
mod helpers {
|
mod helpers {
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use hir::Semantics;
|
||||||
use ra_db::{fixture::WithFixture, FileId, FileRange, SourceDatabaseExt};
|
use ra_db::{fixture::WithFixture, FileId, FileRange, SourceDatabaseExt};
|
||||||
use ra_ide_db::{symbol_index::SymbolsDatabase, RootDatabase};
|
use ra_ide_db::{symbol_index::SymbolsDatabase, RootDatabase};
|
||||||
use test_utils::{add_cursor, assert_eq_text, extract_range_or_offset, RangeOrOffset};
|
use test_utils::{add_cursor, assert_eq_text, extract_range_or_offset, RangeOrOffset};
|
||||||
|
|
||||||
use crate::{AssistCtx, AssistFile, AssistHandler};
|
use crate::{handlers::Handler, AssistCtx, AssistFile};
|
||||||
use hir::Semantics;
|
|
||||||
|
|
||||||
pub(crate) fn with_single_file(text: &str) -> (RootDatabase, FileId) {
|
pub(crate) fn with_single_file(text: &str) -> (RootDatabase, FileId) {
|
||||||
let (mut db, file_id) = RootDatabase::with_single_file(text);
|
let (mut db, file_id) = RootDatabase::with_single_file(text);
|
||||||
|
@ -210,22 +214,18 @@ mod helpers {
|
||||||
(db, file_id)
|
(db, file_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn check_assist(
|
pub(crate) fn check_assist(assist: Handler, ra_fixture_before: &str, ra_fixture_after: &str) {
|
||||||
assist: AssistHandler,
|
|
||||||
ra_fixture_before: &str,
|
|
||||||
ra_fixture_after: &str,
|
|
||||||
) {
|
|
||||||
check(assist, ra_fixture_before, ExpectedResult::After(ra_fixture_after));
|
check(assist, ra_fixture_before, ExpectedResult::After(ra_fixture_after));
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: instead of having a separate function here, maybe use
|
// FIXME: instead of having a separate function here, maybe use
|
||||||
// `extract_ranges` and mark the target as `<target> </target>` in the
|
// `extract_ranges` and mark the target as `<target> </target>` in the
|
||||||
// fixuture?
|
// fixuture?
|
||||||
pub(crate) fn check_assist_target(assist: AssistHandler, ra_fixture: &str, target: &str) {
|
pub(crate) fn check_assist_target(assist: Handler, ra_fixture: &str, target: &str) {
|
||||||
check(assist, ra_fixture, ExpectedResult::Target(target));
|
check(assist, ra_fixture, ExpectedResult::Target(target));
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn check_assist_not_applicable(assist: AssistHandler, ra_fixture: &str) {
|
pub(crate) fn check_assist_not_applicable(assist: Handler, ra_fixture: &str) {
|
||||||
check(assist, ra_fixture, ExpectedResult::NotApplicable);
|
check(assist, ra_fixture, ExpectedResult::NotApplicable);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -235,7 +235,7 @@ mod helpers {
|
||||||
Target(&'a str),
|
Target(&'a str),
|
||||||
}
|
}
|
||||||
|
|
||||||
fn check(assist: AssistHandler, before: &str, expected: ExpectedResult) {
|
fn check(assist: Handler, before: &str, expected: ExpectedResult) {
|
||||||
let (text_without_caret, file_with_caret_id, range_or_offset, db) =
|
let (text_without_caret, file_with_caret_id, range_or_offset, db) =
|
||||||
if before.contains("//-") {
|
if before.contains("//-") {
|
||||||
let (mut db, position) = RootDatabase::with_position(before);
|
let (mut db, position) = RootDatabase::with_position(before);
|
||||||
|
@ -261,13 +261,13 @@ mod helpers {
|
||||||
(Some(assist), ExpectedResult::After(after)) => {
|
(Some(assist), ExpectedResult::After(after)) => {
|
||||||
let action = assist.0[0].action.clone().unwrap();
|
let action = assist.0[0].action.clone().unwrap();
|
||||||
|
|
||||||
let assisted_file_text = if let AssistFile::TargetFile(file_id) = action.file {
|
let mut actual = if let AssistFile::TargetFile(file_id) = action.file {
|
||||||
db.file_text(file_id).as_ref().to_owned()
|
db.file_text(file_id).as_ref().to_owned()
|
||||||
} else {
|
} else {
|
||||||
text_without_caret
|
text_without_caret
|
||||||
};
|
};
|
||||||
|
action.edit.apply(&mut actual);
|
||||||
|
|
||||||
let mut actual = action.edit.apply(&assisted_file_text);
|
|
||||||
match action.cursor_position {
|
match action.cursor_position {
|
||||||
None => {
|
None => {
|
||||||
if let RangeOrOffset::Offset(before_cursor_pos) = range_or_offset {
|
if let RangeOrOffset::Offset(before_cursor_pos) = range_or_offset {
|
||||||
|
|
|
@ -9,7 +9,7 @@ use ra_syntax::{
|
||||||
SyntaxKind::*,
|
SyntaxKind::*,
|
||||||
SyntaxNode, SyntaxToken, TextRange, TextSize,
|
SyntaxNode, SyntaxToken, TextRange, TextSize,
|
||||||
};
|
};
|
||||||
use ra_text_edit::AtomTextEdit;
|
use ra_text_edit::Indel;
|
||||||
|
|
||||||
use crate::{call_info::ActiveParameter, completion::CompletionConfig, FilePosition};
|
use crate::{call_info::ActiveParameter, completion::CompletionConfig, FilePosition};
|
||||||
|
|
||||||
|
@ -76,7 +76,7 @@ impl<'a> CompletionContext<'a> {
|
||||||
// actual completion.
|
// actual completion.
|
||||||
let file_with_fake_ident = {
|
let file_with_fake_ident = {
|
||||||
let parse = db.parse(position.file_id);
|
let parse = db.parse(position.file_id);
|
||||||
let edit = AtomTextEdit::insert(position.offset, "intellijRulezz".to_string());
|
let edit = Indel::insert(position.offset, "intellijRulezz".to_string());
|
||||||
parse.reparse(&edit).tree()
|
parse.reparse(&edit).tree()
|
||||||
};
|
};
|
||||||
let fake_ident_token =
|
let fake_ident_token =
|
||||||
|
|
|
@ -62,8 +62,8 @@ impl fmt::Debug for CompletionItem {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
let mut s = f.debug_struct("CompletionItem");
|
let mut s = f.debug_struct("CompletionItem");
|
||||||
s.field("label", &self.label()).field("source_range", &self.source_range());
|
s.field("label", &self.label()).field("source_range", &self.source_range());
|
||||||
if self.text_edit().as_atoms().len() == 1 {
|
if self.text_edit().as_indels().len() == 1 {
|
||||||
let atom = &self.text_edit().as_atoms()[0];
|
let atom = &self.text_edit().as_indels()[0];
|
||||||
s.field("delete", &atom.delete);
|
s.field("delete", &atom.delete);
|
||||||
s.field("insert", &atom.insert);
|
s.field("insert", &atom.insert);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -241,7 +241,11 @@ mod tests {
|
||||||
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_file_edits.pop().unwrap().edit;
|
||||||
let actual = edit.apply(&before);
|
let actual = {
|
||||||
|
let mut actual = before.to_string();
|
||||||
|
edit.apply(&mut actual);
|
||||||
|
actual
|
||||||
|
};
|
||||||
assert_eq_text!(after, &actual);
|
assert_eq_text!(after, &actual);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -256,7 +260,11 @@ mod tests {
|
||||||
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_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 = edit.apply(&target_file_contents);
|
let actual = {
|
||||||
|
let mut actual = target_file_contents.to_string();
|
||||||
|
edit.apply(&mut actual);
|
||||||
|
actual
|
||||||
|
};
|
||||||
|
|
||||||
// Strip indent and empty lines from `after`, to match the behaviour of
|
// Strip indent and empty lines from `after`, to match the behaviour of
|
||||||
// `parse_fixture` called from `analysis_and_position`.
|
// `parse_fixture` called from `analysis_and_position`.
|
||||||
|
@ -288,7 +296,11 @@ mod tests {
|
||||||
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_file_edits.pop().unwrap().edit;
|
||||||
let actual = edit.apply(&before);
|
let actual = {
|
||||||
|
let mut actual = before.to_string();
|
||||||
|
edit.apply(&mut actual);
|
||||||
|
actual
|
||||||
|
};
|
||||||
assert_eq_text!(after, &actual);
|
assert_eq_text!(after, &actual);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -662,10 +674,10 @@ mod tests {
|
||||||
1,
|
1,
|
||||||
),
|
),
|
||||||
edit: TextEdit {
|
edit: TextEdit {
|
||||||
atoms: [
|
indels: [
|
||||||
AtomTextEdit {
|
Indel {
|
||||||
delete: 3..9,
|
|
||||||
insert: "{a:42, b: ()}",
|
insert: "{a:42, b: ()}",
|
||||||
|
delete: 3..9,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
|
@ -569,7 +569,11 @@ fn foo() {
|
||||||
let (sel, before) = extract_range(before);
|
let (sel, before) = extract_range(before);
|
||||||
let parse = SourceFile::parse(&before);
|
let parse = SourceFile::parse(&before);
|
||||||
let result = join_lines(&parse.tree(), sel);
|
let result = join_lines(&parse.tree(), sel);
|
||||||
let actual = result.apply(&before);
|
let actual = {
|
||||||
|
let mut actual = before.to_string();
|
||||||
|
result.apply(&mut actual);
|
||||||
|
actual
|
||||||
|
};
|
||||||
assert_eq_text!(after, &actual);
|
assert_eq_text!(after, &actual);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -537,10 +537,10 @@ mod tests {
|
||||||
2,
|
2,
|
||||||
),
|
),
|
||||||
edit: TextEdit {
|
edit: TextEdit {
|
||||||
atoms: [
|
indels: [
|
||||||
AtomTextEdit {
|
Indel {
|
||||||
delete: 4..7,
|
|
||||||
insert: "foo2",
|
insert: "foo2",
|
||||||
|
delete: 4..7,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
@ -589,10 +589,10 @@ mod tests {
|
||||||
1,
|
1,
|
||||||
),
|
),
|
||||||
edit: TextEdit {
|
edit: TextEdit {
|
||||||
atoms: [
|
indels: [
|
||||||
AtomTextEdit {
|
Indel {
|
||||||
delete: 4..7,
|
|
||||||
insert: "foo2",
|
insert: "foo2",
|
||||||
|
delete: 4..7,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
@ -672,10 +672,10 @@ mod tests {
|
||||||
2,
|
2,
|
||||||
),
|
),
|
||||||
edit: TextEdit {
|
edit: TextEdit {
|
||||||
atoms: [
|
indels: [
|
||||||
AtomTextEdit {
|
Indel {
|
||||||
delete: 8..11,
|
|
||||||
insert: "foo2",
|
insert: "foo2",
|
||||||
|
delete: 8..11,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
@ -685,10 +685,10 @@ mod tests {
|
||||||
1,
|
1,
|
||||||
),
|
),
|
||||||
edit: TextEdit {
|
edit: TextEdit {
|
||||||
atoms: [
|
indels: [
|
||||||
AtomTextEdit {
|
Indel {
|
||||||
delete: 27..30,
|
|
||||||
insert: "foo2",
|
insert: "foo2",
|
||||||
|
delete: 27..30,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
@ -720,13 +720,13 @@ mod tests {
|
||||||
if let Some(change) = source_change {
|
if let Some(change) = source_change {
|
||||||
for edit in change.info.source_file_edits {
|
for edit in change.info.source_file_edits {
|
||||||
file_id = Some(edit.file_id);
|
file_id = Some(edit.file_id);
|
||||||
for atom in edit.edit.as_atoms() {
|
for indel in edit.edit.as_indels() {
|
||||||
text_edit_builder.replace(atom.delete, atom.insert.clone());
|
text_edit_builder.replace(indel.delete, indel.insert.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let result =
|
let mut result = analysis.file_text(file_id.unwrap()).unwrap().to_string();
|
||||||
text_edit_builder.finish().apply(&*analysis.file_text(file_id.unwrap()).unwrap());
|
text_edit_builder.finish().apply(&mut result);
|
||||||
assert_eq_text!(expected, &*result);
|
assert_eq_text!(expected, &*result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -401,16 +401,22 @@ fn render_replace(
|
||||||
ignored_comments: &Vec<Comment>,
|
ignored_comments: &Vec<Comment>,
|
||||||
template: &SsrTemplate,
|
template: &SsrTemplate,
|
||||||
) -> String {
|
) -> String {
|
||||||
let mut builder = TextEditBuilder::default();
|
let edit = {
|
||||||
for element in template.template.descendants() {
|
let mut builder = TextEditBuilder::default();
|
||||||
if let Some(var) = template.placeholders.get(&element) {
|
for element in template.template.descendants() {
|
||||||
builder.replace(element.text_range(), binding[var].to_string())
|
if let Some(var) = template.placeholders.get(&element) {
|
||||||
|
builder.replace(element.text_range(), binding[var].to_string())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
for comment in ignored_comments {
|
||||||
for comment in ignored_comments {
|
builder.insert(template.template.text_range().end(), comment.syntax().to_string())
|
||||||
builder.insert(template.template.text_range().end(), comment.syntax().to_string())
|
}
|
||||||
}
|
builder.finish()
|
||||||
builder.finish().apply(&template.template.text().to_string())
|
};
|
||||||
|
|
||||||
|
let mut text = template.template.text().to_string();
|
||||||
|
edit.apply(&mut text);
|
||||||
|
text
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -505,7 +511,9 @@ mod tests {
|
||||||
);
|
);
|
||||||
|
|
||||||
let edit = replace(&matches, &query.template);
|
let edit = replace(&matches, &query.template);
|
||||||
assert_eq!(edit.apply(input), "fn main() { bar(1+2); }");
|
let mut after = input.to_string();
|
||||||
|
edit.apply(&mut after);
|
||||||
|
assert_eq!(after, "fn main() { bar(1+2); }");
|
||||||
}
|
}
|
||||||
|
|
||||||
fn assert_ssr_transform(query: &str, input: &str, result: &str) {
|
fn assert_ssr_transform(query: &str, input: &str, result: &str) {
|
||||||
|
@ -513,7 +521,9 @@ mod tests {
|
||||||
let code = SourceFile::parse(input).tree();
|
let code = SourceFile::parse(input).tree();
|
||||||
let matches = find(&query.pattern, code.syntax());
|
let matches = find(&query.pattern, code.syntax());
|
||||||
let edit = replace(&matches, &query.template);
|
let edit = replace(&matches, &query.template);
|
||||||
assert_eq!(edit.apply(input), result);
|
let mut after = input.to_string();
|
||||||
|
edit.apply(&mut after);
|
||||||
|
assert_eq!(after, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
@ -13,7 +13,11 @@ pub fn check_action<F: Fn(&SourceFile, TextSize) -> Option<TextEdit>>(
|
||||||
let (before_cursor_pos, before) = extract_offset(before);
|
let (before_cursor_pos, before) = extract_offset(before);
|
||||||
let file = SourceFile::parse(&before).ok().unwrap();
|
let file = SourceFile::parse(&before).ok().unwrap();
|
||||||
let result = f(&file, before_cursor_pos).expect("code action is not applicable");
|
let result = f(&file, before_cursor_pos).expect("code action is not applicable");
|
||||||
let actual = result.apply(&before);
|
let actual = {
|
||||||
|
let mut actual = before.to_string();
|
||||||
|
result.apply(&mut actual);
|
||||||
|
actual
|
||||||
|
};
|
||||||
let actual_cursor_pos =
|
let actual_cursor_pos =
|
||||||
result.apply_to_offset(before_cursor_pos).expect("cursor position is affected by the edit");
|
result.apply_to_offset(before_cursor_pos).expect("cursor position is affected by the edit");
|
||||||
let actual = add_cursor(&actual, actual_cursor_pos);
|
let actual = add_cursor(&actual, actual_cursor_pos);
|
||||||
|
|
|
@ -142,10 +142,13 @@ mod tests {
|
||||||
fn do_type_char(char_typed: char, before: &str) -> Option<(String, SingleFileChange)> {
|
fn do_type_char(char_typed: char, before: &str) -> Option<(String, SingleFileChange)> {
|
||||||
let (offset, before) = extract_offset(before);
|
let (offset, before) = extract_offset(before);
|
||||||
let edit = TextEdit::insert(offset, char_typed.to_string());
|
let edit = TextEdit::insert(offset, char_typed.to_string());
|
||||||
let before = edit.apply(&before);
|
let mut before = before.to_string();
|
||||||
|
edit.apply(&mut before);
|
||||||
let parse = SourceFile::parse(&before);
|
let parse = SourceFile::parse(&before);
|
||||||
on_char_typed_inner(&parse.tree(), offset, char_typed)
|
on_char_typed_inner(&parse.tree(), offset, char_typed).map(|it| {
|
||||||
.map(|it| (it.edit.apply(&before), it))
|
it.edit.apply(&mut before);
|
||||||
|
(before.to_string(), it)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn type_char(char_typed: char, before: &str, after: &str) {
|
fn type_char(char_typed: char, before: &str, after: &str) {
|
||||||
|
|
|
@ -96,7 +96,8 @@ mod tests {
|
||||||
let result = analysis.on_enter(FilePosition { offset, file_id }).unwrap()?;
|
let result = analysis.on_enter(FilePosition { offset, file_id }).unwrap()?;
|
||||||
|
|
||||||
assert_eq!(result.source_file_edits.len(), 1);
|
assert_eq!(result.source_file_edits.len(), 1);
|
||||||
let actual = result.source_file_edits[0].edit.apply(&before);
|
let mut actual = before.to_string();
|
||||||
|
result.source_file_edits[0].edit.apply(&mut actual);
|
||||||
let actual = add_cursor(&actual, result.cursor_position.unwrap().offset);
|
let actual = add_cursor(&actual, result.cursor_position.unwrap().offset);
|
||||||
Some(actual)
|
Some(actual)
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
use std::convert::TryInto;
|
use std::convert::TryInto;
|
||||||
|
|
||||||
use ra_syntax::{TextRange, TextSize};
|
use ra_syntax::{TextRange, TextSize};
|
||||||
use ra_text_edit::{AtomTextEdit, TextEdit};
|
use ra_text_edit::{Indel, TextEdit};
|
||||||
|
|
||||||
use crate::line_index::{LineCol, LineIndex, Utf16Char};
|
use crate::line_index::{LineCol, LineIndex, Utf16Char};
|
||||||
|
|
||||||
|
@ -182,14 +182,14 @@ struct TranslatedEdit<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Edits<'a> {
|
struct Edits<'a> {
|
||||||
edits: &'a [AtomTextEdit],
|
edits: &'a [Indel],
|
||||||
current: Option<TranslatedEdit<'a>>,
|
current: Option<TranslatedEdit<'a>>,
|
||||||
acc_diff: i64,
|
acc_diff: i64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> Edits<'a> {
|
impl<'a> Edits<'a> {
|
||||||
fn from_text_edit(text_edit: &'a TextEdit) -> Edits<'a> {
|
fn from_text_edit(text_edit: &'a TextEdit) -> Edits<'a> {
|
||||||
let mut x = Edits { edits: text_edit.as_atoms(), current: None, acc_diff: 0 };
|
let mut x = Edits { edits: text_edit.as_indels(), current: None, acc_diff: 0 };
|
||||||
x.advance_edit();
|
x.advance_edit();
|
||||||
x
|
x
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ use std::{
|
||||||
str::{self, FromStr},
|
str::{self, FromStr},
|
||||||
};
|
};
|
||||||
|
|
||||||
use ra_text_edit::AtomTextEdit;
|
use ra_text_edit::Indel;
|
||||||
|
|
||||||
use crate::{validation, AstNode, SourceFile, TextRange};
|
use crate::{validation, AstNode, SourceFile, TextRange};
|
||||||
|
|
||||||
|
@ -22,7 +22,7 @@ pub fn check_parser(text: &str) {
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct CheckReparse {
|
pub struct CheckReparse {
|
||||||
text: String,
|
text: String,
|
||||||
edit: AtomTextEdit,
|
edit: Indel,
|
||||||
edited_text: String,
|
edited_text: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,7 +43,7 @@ impl CheckReparse {
|
||||||
TextRange::at(delete_start.try_into().unwrap(), delete_len.try_into().unwrap());
|
TextRange::at(delete_start.try_into().unwrap(), delete_len.try_into().unwrap());
|
||||||
let edited_text =
|
let edited_text =
|
||||||
format!("{}{}{}", &text[..delete_start], &insert, &text[delete_start + delete_len..]);
|
format!("{}{}{}", &text[..delete_start], &insert, &text[delete_start + delete_len..]);
|
||||||
let edit = AtomTextEdit { delete, insert };
|
let edit = Indel { delete, insert };
|
||||||
Some(CheckReparse { text, edit, edited_text })
|
Some(CheckReparse { text, edit, edited_text })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -39,7 +39,7 @@ pub mod fuzz;
|
||||||
|
|
||||||
use std::{marker::PhantomData, sync::Arc};
|
use std::{marker::PhantomData, sync::Arc};
|
||||||
|
|
||||||
use ra_text_edit::AtomTextEdit;
|
use ra_text_edit::Indel;
|
||||||
use stdx::format_to;
|
use stdx::format_to;
|
||||||
|
|
||||||
use crate::syntax_node::GreenNode;
|
use crate::syntax_node::GreenNode;
|
||||||
|
@ -126,13 +126,13 @@ impl Parse<SourceFile> {
|
||||||
buf
|
buf
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn reparse(&self, edit: &AtomTextEdit) -> Parse<SourceFile> {
|
pub fn reparse(&self, indel: &Indel) -> Parse<SourceFile> {
|
||||||
self.incremental_reparse(edit).unwrap_or_else(|| self.full_reparse(edit))
|
self.incremental_reparse(indel).unwrap_or_else(|| self.full_reparse(indel))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn incremental_reparse(&self, edit: &AtomTextEdit) -> Option<Parse<SourceFile>> {
|
fn incremental_reparse(&self, indel: &Indel) -> Option<Parse<SourceFile>> {
|
||||||
// FIXME: validation errors are not handled here
|
// FIXME: validation errors are not handled here
|
||||||
parsing::incremental_reparse(self.tree().syntax(), edit, self.errors.to_vec()).map(
|
parsing::incremental_reparse(self.tree().syntax(), indel, self.errors.to_vec()).map(
|
||||||
|(green_node, errors, _reparsed_range)| Parse {
|
|(green_node, errors, _reparsed_range)| Parse {
|
||||||
green: green_node,
|
green: green_node,
|
||||||
errors: Arc::new(errors),
|
errors: Arc::new(errors),
|
||||||
|
@ -141,8 +141,9 @@ impl Parse<SourceFile> {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn full_reparse(&self, edit: &AtomTextEdit) -> Parse<SourceFile> {
|
fn full_reparse(&self, indel: &Indel) -> Parse<SourceFile> {
|
||||||
let text = edit.apply(self.tree().syntax().text().to_string());
|
let mut text = self.tree().syntax().text().to_string();
|
||||||
|
indel.apply(&mut text);
|
||||||
SourceFile::parse(&text)
|
SourceFile::parse(&text)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
//! and try to parse only this block.
|
//! and try to parse only this block.
|
||||||
|
|
||||||
use ra_parser::Reparser;
|
use ra_parser::Reparser;
|
||||||
use ra_text_edit::AtomTextEdit;
|
use ra_text_edit::Indel;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
algo,
|
algo,
|
||||||
|
@ -24,7 +24,7 @@ use crate::{
|
||||||
|
|
||||||
pub(crate) fn incremental_reparse(
|
pub(crate) fn incremental_reparse(
|
||||||
node: &SyntaxNode,
|
node: &SyntaxNode,
|
||||||
edit: &AtomTextEdit,
|
edit: &Indel,
|
||||||
errors: Vec<SyntaxError>,
|
errors: Vec<SyntaxError>,
|
||||||
) -> Option<(GreenNode, Vec<SyntaxError>, TextRange)> {
|
) -> Option<(GreenNode, Vec<SyntaxError>, TextRange)> {
|
||||||
if let Some((green, new_errors, old_range)) = reparse_token(node, &edit) {
|
if let Some((green, new_errors, old_range)) = reparse_token(node, &edit) {
|
||||||
|
@ -39,7 +39,7 @@ pub(crate) fn incremental_reparse(
|
||||||
|
|
||||||
fn reparse_token<'node>(
|
fn reparse_token<'node>(
|
||||||
root: &'node SyntaxNode,
|
root: &'node SyntaxNode,
|
||||||
edit: &AtomTextEdit,
|
edit: &Indel,
|
||||||
) -> Option<(GreenNode, Vec<SyntaxError>, TextRange)> {
|
) -> Option<(GreenNode, Vec<SyntaxError>, TextRange)> {
|
||||||
let prev_token = algo::find_covering_element(root, edit.delete).as_token()?.clone();
|
let prev_token = algo::find_covering_element(root, edit.delete).as_token()?.clone();
|
||||||
let prev_token_kind = prev_token.kind();
|
let prev_token_kind = prev_token.kind();
|
||||||
|
@ -88,7 +88,7 @@ fn reparse_token<'node>(
|
||||||
|
|
||||||
fn reparse_block<'node>(
|
fn reparse_block<'node>(
|
||||||
root: &'node SyntaxNode,
|
root: &'node SyntaxNode,
|
||||||
edit: &AtomTextEdit,
|
edit: &Indel,
|
||||||
) -> Option<(GreenNode, Vec<SyntaxError>, TextRange)> {
|
) -> Option<(GreenNode, Vec<SyntaxError>, TextRange)> {
|
||||||
let (node, reparser) = find_reparsable_node(root, edit.delete)?;
|
let (node, reparser) = find_reparsable_node(root, edit.delete)?;
|
||||||
let text = get_text_after_edit(node.clone().into(), edit);
|
let text = get_text_after_edit(node.clone().into(), edit);
|
||||||
|
@ -108,15 +108,15 @@ fn reparse_block<'node>(
|
||||||
Some((node.replace_with(green), new_parser_errors, node.text_range()))
|
Some((node.replace_with(green), new_parser_errors, node.text_range()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_text_after_edit(element: SyntaxElement, edit: &AtomTextEdit) -> String {
|
fn get_text_after_edit(element: SyntaxElement, edit: &Indel) -> String {
|
||||||
let edit =
|
let edit = Indel::replace(edit.delete - element.text_range().start(), edit.insert.clone());
|
||||||
AtomTextEdit::replace(edit.delete - element.text_range().start(), edit.insert.clone());
|
|
||||||
|
|
||||||
let text = match element {
|
let mut text = match element {
|
||||||
NodeOrToken::Token(token) => token.text().to_string(),
|
NodeOrToken::Token(token) => token.text().to_string(),
|
||||||
NodeOrToken::Node(node) => node.text().to_string(),
|
NodeOrToken::Node(node) => node.text().to_string(),
|
||||||
};
|
};
|
||||||
edit.apply(text)
|
edit.apply(&mut text);
|
||||||
|
text
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_contextual_kw(text: &str) -> bool {
|
fn is_contextual_kw(text: &str) -> bool {
|
||||||
|
@ -167,7 +167,7 @@ fn merge_errors(
|
||||||
old_errors: Vec<SyntaxError>,
|
old_errors: Vec<SyntaxError>,
|
||||||
new_errors: Vec<SyntaxError>,
|
new_errors: Vec<SyntaxError>,
|
||||||
range_before_reparse: TextRange,
|
range_before_reparse: TextRange,
|
||||||
edit: &AtomTextEdit,
|
edit: &Indel,
|
||||||
) -> Vec<SyntaxError> {
|
) -> Vec<SyntaxError> {
|
||||||
let mut res = Vec::new();
|
let mut res = Vec::new();
|
||||||
|
|
||||||
|
@ -198,8 +198,12 @@ mod tests {
|
||||||
|
|
||||||
fn do_check(before: &str, replace_with: &str, reparsed_len: u32) {
|
fn do_check(before: &str, replace_with: &str, reparsed_len: u32) {
|
||||||
let (range, before) = extract_range(before);
|
let (range, before) = extract_range(before);
|
||||||
let edit = AtomTextEdit::replace(range, replace_with.to_owned());
|
let edit = Indel::replace(range, replace_with.to_owned());
|
||||||
let after = edit.apply(before.clone());
|
let after = {
|
||||||
|
let mut after = before.clone();
|
||||||
|
edit.apply(&mut after);
|
||||||
|
after
|
||||||
|
};
|
||||||
|
|
||||||
let fully_reparsed = SourceFile::parse(&after);
|
let fully_reparsed = SourceFile::parse(&after);
|
||||||
let incrementally_reparsed: Parse<SourceFile> = {
|
let incrementally_reparsed: Parse<SourceFile> = {
|
||||||
|
|
|
@ -1,36 +1,144 @@
|
||||||
//! FIXME: write short doc here
|
//! Representation of a `TextEdit`.
|
||||||
|
//!
|
||||||
mod text_edit;
|
//! `rust-analyzer` never mutates text itself and only sends diffs to clients,
|
||||||
|
//! so `TextEdit` is the ultimate representation of the work done by
|
||||||
|
//! rust-analyzer.
|
||||||
|
|
||||||
use text_size::{TextRange, TextSize};
|
use text_size::{TextRange, TextSize};
|
||||||
|
|
||||||
pub use crate::text_edit::{TextEdit, TextEditBuilder};
|
/// `InsertDelete` -- a single "atomic" change to text
|
||||||
|
///
|
||||||
/// Must not overlap with other `AtomTextEdit`s
|
/// Must not overlap with other `InDel`s
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct AtomTextEdit {
|
pub struct Indel {
|
||||||
|
pub insert: String,
|
||||||
/// Refers to offsets in the original text
|
/// Refers to offsets in the original text
|
||||||
pub delete: TextRange,
|
pub delete: TextRange,
|
||||||
pub insert: String,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AtomTextEdit {
|
#[derive(Debug, Clone)]
|
||||||
pub fn replace(range: TextRange, replace_with: String) -> AtomTextEdit {
|
pub struct TextEdit {
|
||||||
AtomTextEdit { delete: range, insert: replace_with }
|
indels: Vec<Indel>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub struct TextEditBuilder {
|
||||||
|
indels: Vec<Indel>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Indel {
|
||||||
|
pub fn insert(offset: TextSize, text: String) -> Indel {
|
||||||
|
Indel::replace(TextRange::empty(offset), text)
|
||||||
|
}
|
||||||
|
pub fn delete(range: TextRange) -> Indel {
|
||||||
|
Indel::replace(range, String::new())
|
||||||
|
}
|
||||||
|
pub fn replace(range: TextRange, replace_with: String) -> Indel {
|
||||||
|
Indel { delete: range, insert: replace_with }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn delete(range: TextRange) -> AtomTextEdit {
|
pub fn apply(&self, text: &mut String) {
|
||||||
AtomTextEdit::replace(range, String::new())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn insert(offset: TextSize, text: String) -> AtomTextEdit {
|
|
||||||
AtomTextEdit::replace(TextRange::empty(offset), text)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn apply(&self, mut text: String) -> String {
|
|
||||||
let start: usize = self.delete.start().into();
|
let start: usize = self.delete.start().into();
|
||||||
let end: usize = self.delete.end().into();
|
let end: usize = self.delete.end().into();
|
||||||
text.replace_range(start..end, &self.insert);
|
text.replace_range(start..end, &self.insert);
|
||||||
text
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TextEdit {
|
||||||
|
pub fn insert(offset: TextSize, text: String) -> TextEdit {
|
||||||
|
let mut builder = TextEditBuilder::default();
|
||||||
|
builder.insert(offset, text);
|
||||||
|
builder.finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn delete(range: TextRange) -> TextEdit {
|
||||||
|
let mut builder = TextEditBuilder::default();
|
||||||
|
builder.delete(range);
|
||||||
|
builder.finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn replace(range: TextRange, replace_with: String) -> TextEdit {
|
||||||
|
let mut builder = TextEditBuilder::default();
|
||||||
|
builder.replace(range, replace_with);
|
||||||
|
builder.finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn from_indels(mut indels: Vec<Indel>) -> TextEdit {
|
||||||
|
indels.sort_by_key(|a| (a.delete.start(), a.delete.end()));
|
||||||
|
for (a1, a2) in indels.iter().zip(indels.iter().skip(1)) {
|
||||||
|
assert!(a1.delete.end() <= a2.delete.start())
|
||||||
|
}
|
||||||
|
TextEdit { indels }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn as_indels(&self) -> &[Indel] {
|
||||||
|
&self.indels
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn apply(&self, text: &mut String) {
|
||||||
|
match self.indels.len() {
|
||||||
|
0 => return,
|
||||||
|
1 => {
|
||||||
|
self.indels[0].apply(text);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut total_len = TextSize::of(&*text);
|
||||||
|
for indel in self.indels.iter() {
|
||||||
|
total_len += TextSize::of(&indel.insert);
|
||||||
|
total_len -= indel.delete.end() - indel.delete.start();
|
||||||
|
}
|
||||||
|
let mut buf = String::with_capacity(total_len.into());
|
||||||
|
let mut prev = 0;
|
||||||
|
for indel in self.indels.iter() {
|
||||||
|
let start: usize = indel.delete.start().into();
|
||||||
|
let end: usize = indel.delete.end().into();
|
||||||
|
if start > prev {
|
||||||
|
buf.push_str(&text[prev..start]);
|
||||||
|
}
|
||||||
|
buf.push_str(&indel.insert);
|
||||||
|
prev = end;
|
||||||
|
}
|
||||||
|
buf.push_str(&text[prev..text.len()]);
|
||||||
|
assert_eq!(TextSize::of(&buf), total_len);
|
||||||
|
|
||||||
|
// FIXME: figure out a way to mutate the text in-place or reuse the
|
||||||
|
// memory in some other way
|
||||||
|
*text = buf
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn apply_to_offset(&self, offset: TextSize) -> Option<TextSize> {
|
||||||
|
let mut res = offset;
|
||||||
|
for indel in self.indels.iter() {
|
||||||
|
if indel.delete.start() >= offset {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if offset < indel.delete.end() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
res += TextSize::of(&indel.insert);
|
||||||
|
res -= indel.delete.len();
|
||||||
|
}
|
||||||
|
Some(res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TextEditBuilder {
|
||||||
|
pub fn replace(&mut self, range: TextRange, replace_with: String) {
|
||||||
|
self.indels.push(Indel::replace(range, replace_with))
|
||||||
|
}
|
||||||
|
pub fn delete(&mut self, range: TextRange) {
|
||||||
|
self.indels.push(Indel::delete(range))
|
||||||
|
}
|
||||||
|
pub fn insert(&mut self, offset: TextSize, text: String) {
|
||||||
|
self.indels.push(Indel::insert(offset, text))
|
||||||
|
}
|
||||||
|
pub fn finish(self) -> TextEdit {
|
||||||
|
TextEdit::from_indels(self.indels)
|
||||||
|
}
|
||||||
|
pub fn invalidates_offset(&self, offset: TextSize) -> bool {
|
||||||
|
self.indels.iter().any(|indel| indel.delete.contains_inclusive(offset))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,102 +0,0 @@
|
||||||
//! FIXME: write short doc here
|
|
||||||
|
|
||||||
use crate::AtomTextEdit;
|
|
||||||
|
|
||||||
use text_size::{TextRange, TextSize};
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct TextEdit {
|
|
||||||
atoms: Vec<AtomTextEdit>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
|
||||||
pub struct TextEditBuilder {
|
|
||||||
atoms: Vec<AtomTextEdit>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TextEditBuilder {
|
|
||||||
pub fn replace(&mut self, range: TextRange, replace_with: String) {
|
|
||||||
self.atoms.push(AtomTextEdit::replace(range, replace_with))
|
|
||||||
}
|
|
||||||
pub fn delete(&mut self, range: TextRange) {
|
|
||||||
self.atoms.push(AtomTextEdit::delete(range))
|
|
||||||
}
|
|
||||||
pub fn insert(&mut self, offset: TextSize, text: String) {
|
|
||||||
self.atoms.push(AtomTextEdit::insert(offset, text))
|
|
||||||
}
|
|
||||||
pub fn finish(self) -> TextEdit {
|
|
||||||
TextEdit::from_atoms(self.atoms)
|
|
||||||
}
|
|
||||||
pub fn invalidates_offset(&self, offset: TextSize) -> bool {
|
|
||||||
self.atoms.iter().any(|atom| atom.delete.contains_inclusive(offset))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TextEdit {
|
|
||||||
pub fn insert(offset: TextSize, text: String) -> TextEdit {
|
|
||||||
let mut builder = TextEditBuilder::default();
|
|
||||||
builder.insert(offset, text);
|
|
||||||
builder.finish()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn delete(range: TextRange) -> TextEdit {
|
|
||||||
let mut builder = TextEditBuilder::default();
|
|
||||||
builder.delete(range);
|
|
||||||
builder.finish()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn replace(range: TextRange, replace_with: String) -> TextEdit {
|
|
||||||
let mut builder = TextEditBuilder::default();
|
|
||||||
builder.replace(range, replace_with);
|
|
||||||
builder.finish()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn from_atoms(mut atoms: Vec<AtomTextEdit>) -> TextEdit {
|
|
||||||
atoms.sort_by_key(|a| (a.delete.start(), a.delete.end()));
|
|
||||||
for (a1, a2) in atoms.iter().zip(atoms.iter().skip(1)) {
|
|
||||||
assert!(a1.delete.end() <= a2.delete.start())
|
|
||||||
}
|
|
||||||
TextEdit { atoms }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn as_atoms(&self) -> &[AtomTextEdit] {
|
|
||||||
&self.atoms
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn apply(&self, text: &str) -> String {
|
|
||||||
let mut total_len = TextSize::of(text);
|
|
||||||
for atom in self.atoms.iter() {
|
|
||||||
total_len += TextSize::of(&atom.insert);
|
|
||||||
total_len -= atom.delete.end() - atom.delete.start();
|
|
||||||
}
|
|
||||||
let mut buf = String::with_capacity(total_len.into());
|
|
||||||
let mut prev = 0;
|
|
||||||
for atom in self.atoms.iter() {
|
|
||||||
let start: usize = atom.delete.start().into();
|
|
||||||
let end: usize = atom.delete.end().into();
|
|
||||||
if start > prev {
|
|
||||||
buf.push_str(&text[prev..start]);
|
|
||||||
}
|
|
||||||
buf.push_str(&atom.insert);
|
|
||||||
prev = end;
|
|
||||||
}
|
|
||||||
buf.push_str(&text[prev..text.len()]);
|
|
||||||
assert_eq!(TextSize::of(&buf), total_len);
|
|
||||||
buf
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn apply_to_offset(&self, offset: TextSize) -> Option<TextSize> {
|
|
||||||
let mut res = offset;
|
|
||||||
for atom in self.atoms.iter() {
|
|
||||||
if atom.delete.start() >= offset {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if offset < atom.delete.end() {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
res += TextSize::of(&atom.insert);
|
|
||||||
res -= atom.delete.len();
|
|
||||||
}
|
|
||||||
Some(res)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -15,7 +15,7 @@ use ra_ide::{
|
||||||
ReferenceAccess, Severity, SourceChange, SourceFileEdit,
|
ReferenceAccess, Severity, SourceChange, SourceFileEdit,
|
||||||
};
|
};
|
||||||
use ra_syntax::{SyntaxKind, TextRange, TextSize};
|
use ra_syntax::{SyntaxKind, TextRange, TextSize};
|
||||||
use ra_text_edit::{AtomTextEdit, TextEdit};
|
use ra_text_edit::{Indel, TextEdit};
|
||||||
use ra_vfs::LineEndings;
|
use ra_vfs::LineEndings;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
@ -124,23 +124,22 @@ impl ConvWith<(&LineIndex, LineEndings)> for CompletionItem {
|
||||||
let mut text_edit = None;
|
let mut text_edit = None;
|
||||||
// LSP does not allow arbitrary edits in completion, so we have to do a
|
// LSP does not allow arbitrary edits in completion, so we have to do a
|
||||||
// non-trivial mapping here.
|
// non-trivial mapping here.
|
||||||
for atom_edit in self.text_edit().as_atoms() {
|
for indel in self.text_edit().as_indels() {
|
||||||
if atom_edit.delete.contains_range(self.source_range()) {
|
if indel.delete.contains_range(self.source_range()) {
|
||||||
text_edit = Some(if atom_edit.delete == self.source_range() {
|
text_edit = Some(if indel.delete == self.source_range() {
|
||||||
atom_edit.conv_with((ctx.0, ctx.1))
|
indel.conv_with((ctx.0, ctx.1))
|
||||||
} else {
|
} else {
|
||||||
assert!(self.source_range().end() == atom_edit.delete.end());
|
assert!(self.source_range().end() == indel.delete.end());
|
||||||
let range1 =
|
let range1 = TextRange::new(indel.delete.start(), self.source_range().start());
|
||||||
TextRange::new(atom_edit.delete.start(), self.source_range().start());
|
|
||||||
let range2 = self.source_range();
|
let range2 = self.source_range();
|
||||||
let edit1 = AtomTextEdit::replace(range1, String::new());
|
let edit1 = Indel::replace(range1, String::new());
|
||||||
let edit2 = AtomTextEdit::replace(range2, atom_edit.insert.clone());
|
let edit2 = Indel::replace(range2, indel.insert.clone());
|
||||||
additional_text_edits.push(edit1.conv_with((ctx.0, ctx.1)));
|
additional_text_edits.push(edit1.conv_with((ctx.0, ctx.1)));
|
||||||
edit2.conv_with((ctx.0, ctx.1))
|
edit2.conv_with((ctx.0, ctx.1))
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
assert!(self.source_range().intersect(atom_edit.delete).is_none());
|
assert!(self.source_range().intersect(indel.delete).is_none());
|
||||||
additional_text_edits.push(atom_edit.conv_with((ctx.0, ctx.1)));
|
additional_text_edits.push(indel.conv_with((ctx.0, ctx.1)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let text_edit = text_edit.unwrap();
|
let text_edit = text_edit.unwrap();
|
||||||
|
@ -257,11 +256,11 @@ impl ConvWith<(&LineIndex, LineEndings)> for TextEdit {
|
||||||
type Output = Vec<lsp_types::TextEdit>;
|
type Output = Vec<lsp_types::TextEdit>;
|
||||||
|
|
||||||
fn conv_with(self, ctx: (&LineIndex, LineEndings)) -> Vec<lsp_types::TextEdit> {
|
fn conv_with(self, ctx: (&LineIndex, LineEndings)) -> Vec<lsp_types::TextEdit> {
|
||||||
self.as_atoms().iter().map_conv_with(ctx).collect()
|
self.as_indels().iter().map_conv_with(ctx).collect()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ConvWith<(&LineIndex, LineEndings)> for &AtomTextEdit {
|
impl ConvWith<(&LineIndex, LineEndings)> for &Indel {
|
||||||
type Output = lsp_types::TextEdit;
|
type Output = lsp_types::TextEdit;
|
||||||
|
|
||||||
fn conv_with(
|
fn conv_with(
|
||||||
|
@ -522,7 +521,7 @@ impl TryConvWith<&WorldSnapshot> for SourceFileEdit {
|
||||||
let line_index = world.analysis().file_line_index(self.file_id)?;
|
let line_index = world.analysis().file_line_index(self.file_id)?;
|
||||||
let line_endings = world.file_line_endings(self.file_id);
|
let line_endings = world.file_line_endings(self.file_id);
|
||||||
let edits =
|
let edits =
|
||||||
self.edit.as_atoms().iter().map_conv_with((&line_index, line_endings)).collect();
|
self.edit.as_indels().iter().map_conv_with((&line_index, line_endings)).collect();
|
||||||
Ok(TextDocumentEdit { text_document, edits })
|
Ok(TextDocumentEdit { text_document, edits })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -115,7 +115,6 @@ impl TidyDocs {
|
||||||
"ra_prof",
|
"ra_prof",
|
||||||
"ra_project_model",
|
"ra_project_model",
|
||||||
"ra_syntax",
|
"ra_syntax",
|
||||||
"ra_text_edit",
|
|
||||||
"ra_tt",
|
"ra_tt",
|
||||||
"ra_hir_ty",
|
"ra_hir_ty",
|
||||||
];
|
];
|
||||||
|
|
Loading…
Reference in a new issue