use ra_fmt::{leading_indent, reindent};
use ra_ide_db::RootDatabase;
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,
TokenAtOffset,
};
use ra_text_edit::TextEditBuilder;
use crate::{AssistAction, AssistFile, AssistId, AssistLabel, GroupLabel, ResolvedAssist};
-use algo::SyntaxRewriter;
#[derive(Clone, Debug)]
pub(crate) struct Assist(pub(crate) Vec<AssistInfo>);
}
}
-
/// `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
SyntaxKind::*,
SyntaxNode, SyntaxToken, TextRange, TextSize,
};
-use ra_text_edit::AtomTextEdit;
+use ra_text_edit::Indel;
use crate::{call_info::ActiveParameter, completion::CompletionConfig, FilePosition};
// actual completion.
let file_with_fake_ident = {
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()
};
let fake_ident_token =
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut s = f.debug_struct("CompletionItem");
s.field("label", &self.label()).field("source_range", &self.source_range());
- if self.text_edit().as_atoms().len() == 1 {
- let atom = &self.text_edit().as_atoms()[0];
+ if self.text_edit().as_indels().len() == 1 {
+ let atom = &self.text_edit().as_indels()[0];
s.field("delete", &atom.delete);
s.field("insert", &atom.insert);
} else {
use std::convert::TryInto;
use ra_syntax::{TextRange, TextSize};
-use ra_text_edit::{AtomTextEdit, TextEdit};
+use ra_text_edit::{Indel, TextEdit};
use crate::line_index::{LineCol, LineIndex, Utf16Char};
}
struct Edits<'a> {
- edits: &'a [AtomTextEdit],
+ edits: &'a [Indel],
current: Option<TranslatedEdit<'a>>,
acc_diff: i64,
}
impl<'a> 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
}
str::{self, FromStr},
};
-use ra_text_edit::AtomTextEdit;
+use ra_text_edit::Indel;
use crate::{validation, AstNode, SourceFile, TextRange};
#[derive(Debug, Clone)]
pub struct CheckReparse {
text: String,
- edit: AtomTextEdit,
+ edit: Indel,
edited_text: String,
}
TextRange::at(delete_start.try_into().unwrap(), delete_len.try_into().unwrap());
let edited_text =
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 })
}
use std::{marker::PhantomData, sync::Arc};
-use ra_text_edit::AtomTextEdit;
+use ra_text_edit::Indel;
use stdx::format_to;
use crate::syntax_node::GreenNode;
buf
}
- pub fn reparse(&self, edit: &AtomTextEdit) -> Parse<SourceFile> {
- self.incremental_reparse(edit).unwrap_or_else(|| self.full_reparse(edit))
+ pub fn reparse(&self, indel: &Indel) -> Parse<SourceFile> {
+ 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
- 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: green_node,
errors: Arc::new(errors),
)
}
- fn full_reparse(&self, edit: &AtomTextEdit) -> Parse<SourceFile> {
- let text = edit.apply(self.tree().syntax().text().to_string());
+ fn full_reparse(&self, indel: &Indel) -> Parse<SourceFile> {
+ let text = indel.apply(self.tree().syntax().text().to_string());
SourceFile::parse(&text)
}
}
//! and try to parse only this block.
use ra_parser::Reparser;
-use ra_text_edit::AtomTextEdit;
+use ra_text_edit::Indel;
use crate::{
algo,
pub(crate) fn incremental_reparse(
node: &SyntaxNode,
- edit: &AtomTextEdit,
+ edit: &Indel,
errors: Vec<SyntaxError>,
) -> Option<(GreenNode, Vec<SyntaxError>, TextRange)> {
if let Some((green, new_errors, old_range)) = reparse_token(node, &edit) {
fn reparse_token<'node>(
root: &'node SyntaxNode,
- edit: &AtomTextEdit,
+ edit: &Indel,
) -> Option<(GreenNode, Vec<SyntaxError>, TextRange)> {
let prev_token = algo::find_covering_element(root, edit.delete).as_token()?.clone();
let prev_token_kind = prev_token.kind();
fn reparse_block<'node>(
root: &'node SyntaxNode,
- edit: &AtomTextEdit,
+ edit: &Indel,
) -> Option<(GreenNode, Vec<SyntaxError>, TextRange)> {
let (node, reparser) = find_reparsable_node(root, edit.delete)?;
let text = get_text_after_edit(node.clone().into(), edit);
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 =
- AtomTextEdit::replace(edit.delete - element.text_range().start(), edit.insert.clone());
+ Indel::replace(edit.delete - element.text_range().start(), edit.insert.clone());
let text = match element {
NodeOrToken::Token(token) => token.text().to_string(),
old_errors: Vec<SyntaxError>,
new_errors: Vec<SyntaxError>,
range_before_reparse: TextRange,
- edit: &AtomTextEdit,
+ edit: &Indel,
) -> Vec<SyntaxError> {
let mut res = Vec::new();
fn do_check(before: &str, replace_with: &str, reparsed_len: u32) {
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 fully_reparsed = SourceFile::parse(&after);
-//! FIXME: write short doc here
-
-mod text_edit;
+//! Representation of a `TextEdit`.
+//!
+//! `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};
-pub use crate::text_edit::{TextEdit, TextEditBuilder};
-
-/// Must not overlap with other `AtomTextEdit`s
+/// `InsertDelete` -- a single "atomic" change to text
+///
+/// Must not overlap with other `InDel`s
#[derive(Debug, Clone)]
-pub struct AtomTextEdit {
+pub struct Indel {
+ pub insert: String,
/// Refers to offsets in the original text
pub delete: TextRange,
- pub insert: String,
}
-impl AtomTextEdit {
- pub fn replace(range: TextRange, replace_with: String) -> AtomTextEdit {
- AtomTextEdit { delete: range, insert: replace_with }
- }
+#[derive(Debug, Clone)]
+pub struct TextEdit {
+ indels: Vec<Indel>,
+}
- pub fn delete(range: TextRange) -> AtomTextEdit {
- AtomTextEdit::replace(range, String::new())
- }
+#[derive(Debug, Default)]
+pub struct TextEditBuilder {
+ indels: Vec<Indel>,
+}
- pub fn insert(offset: TextSize, text: String) -> AtomTextEdit {
- AtomTextEdit::replace(TextRange::empty(offset), text)
+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 apply(&self, mut text: String) -> String {
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: &str) -> String {
+ 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);
+ 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))
+ }
+}
+++ /dev/null
-//! 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)
- }
-}
ReferenceAccess, Severity, SourceChange, SourceFileEdit,
};
use ra_syntax::{SyntaxKind, TextRange, TextSize};
-use ra_text_edit::{AtomTextEdit, TextEdit};
+use ra_text_edit::{Indel, TextEdit};
use ra_vfs::LineEndings;
use crate::{
let mut text_edit = None;
// LSP does not allow arbitrary edits in completion, so we have to do a
// non-trivial mapping here.
- for atom_edit in self.text_edit().as_atoms() {
- if atom_edit.delete.contains_range(self.source_range()) {
- text_edit = Some(if atom_edit.delete == self.source_range() {
- atom_edit.conv_with((ctx.0, ctx.1))
+ for indel in self.text_edit().as_indels() {
+ if indel.delete.contains_range(self.source_range()) {
+ text_edit = Some(if indel.delete == self.source_range() {
+ indel.conv_with((ctx.0, ctx.1))
} else {
- assert!(self.source_range().end() == atom_edit.delete.end());
- let range1 =
- TextRange::new(atom_edit.delete.start(), self.source_range().start());
+ assert!(self.source_range().end() == indel.delete.end());
+ let range1 = TextRange::new(indel.delete.start(), self.source_range().start());
let range2 = self.source_range();
- let edit1 = AtomTextEdit::replace(range1, String::new());
- let edit2 = AtomTextEdit::replace(range2, atom_edit.insert.clone());
+ let edit1 = Indel::replace(range1, String::new());
+ let edit2 = Indel::replace(range2, indel.insert.clone());
additional_text_edits.push(edit1.conv_with((ctx.0, ctx.1)));
edit2.conv_with((ctx.0, ctx.1))
})
} else {
- assert!(self.source_range().intersect(atom_edit.delete).is_none());
- additional_text_edits.push(atom_edit.conv_with((ctx.0, ctx.1)));
+ assert!(self.source_range().intersect(indel.delete).is_none());
+ additional_text_edits.push(indel.conv_with((ctx.0, ctx.1)));
}
}
let text_edit = text_edit.unwrap();
type Output = 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;
fn conv_with(
let line_index = world.analysis().file_line_index(self.file_id)?;
let line_endings = world.file_line_endings(self.file_id);
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 })
}
}
"ra_prof",
"ra_project_model",
"ra_syntax",
- "ra_text_edit",
"ra_tt",
"ra_hir_ty",
];