1 //! This modules defines type to represent changes to the source code, that flow
2 //! from the server to the client.
4 //! It can be viewed as a dual for `Change`.
6 use std
::{collections::hash_map::Entry, iter, mem}
;
8 use base_db
::{AnchoredPathBuf, FileId}
;
9 use stdx
::{hash::NoHashHashMap, never}
;
10 use syntax
::{algo, AstNode, SyntaxNode, SyntaxNodePtr, TextRange, TextSize}
;
11 use text_edit
::{TextEdit, TextEditBuilder}
;
13 use crate::SnippetCap
;
15 #[derive(Default, Debug, Clone)]
16 pub struct SourceChange
{
17 pub source_file_edits
: NoHashHashMap
<FileId
, TextEdit
>,
18 pub file_system_edits
: Vec
<FileSystemEdit
>,
23 /// Creates a new SourceChange with the given label
26 source_file_edits
: NoHashHashMap
<FileId
, TextEdit
>,
27 file_system_edits
: Vec
<FileSystemEdit
>,
29 SourceChange { source_file_edits, file_system_edits, is_snippet: false }
32 pub fn from_text_edit(file_id
: FileId
, edit
: TextEdit
) -> Self {
34 source_file_edits
: iter
::once((file_id
, edit
)).collect(),
39 /// Inserts a [`TextEdit`] for the given [`FileId`]. This properly handles merging existing
40 /// edits for a file if some already exist.
41 pub fn insert_source_edit(&mut self, file_id
: FileId
, edit
: TextEdit
) {
42 match self.source_file_edits
.entry(file_id
) {
43 Entry
::Occupied(mut entry
) => {
44 never
!(entry
.get_mut().union(edit
).is_err(), "overlapping edits for same file");
46 Entry
::Vacant(entry
) => {
52 pub fn push_file_system_edit(&mut self, edit
: FileSystemEdit
) {
53 self.file_system_edits
.push(edit
);
56 pub fn get_source_edit(&self, file_id
: FileId
) -> Option
<&TextEdit
> {
57 self.source_file_edits
.get(&file_id
)
60 pub fn merge(mut self, other
: SourceChange
) -> SourceChange
{
61 self.extend(other
.source_file_edits
);
62 self.extend(other
.file_system_edits
);
63 self.is_snippet
|= other
.is_snippet
;
68 impl Extend
<(FileId
, TextEdit
)> for SourceChange
{
69 fn extend
<T
: IntoIterator
<Item
= (FileId
, TextEdit
)>>(&mut self, iter
: T
) {
70 iter
.into_iter().for_each(|(file_id
, edit
)| self.insert_source_edit(file_id
, edit
));
74 impl Extend
<FileSystemEdit
> for SourceChange
{
75 fn extend
<T
: IntoIterator
<Item
= FileSystemEdit
>>(&mut self, iter
: T
) {
76 iter
.into_iter().for_each(|edit
| self.push_file_system_edit(edit
));
80 impl From
<NoHashHashMap
<FileId
, TextEdit
>> for SourceChange
{
81 fn from(source_file_edits
: NoHashHashMap
<FileId
, TextEdit
>) -> SourceChange
{
82 SourceChange { source_file_edits, file_system_edits: Vec::new(), is_snippet: false }
86 pub struct SourceChangeBuilder
{
87 pub edit
: TextEditBuilder
,
89 pub source_change
: SourceChange
,
90 pub trigger_signature_help
: bool
,
92 /// Maps the original, immutable `SyntaxNode` to a `clone_for_update` twin.
93 pub mutated_tree
: Option
<TreeMutator
>,
96 pub struct TreeMutator
{
97 immutable
: SyntaxNode
,
98 mutable_clone
: SyntaxNode
,
102 pub fn new(immutable
: &SyntaxNode
) -> TreeMutator
{
103 let immutable
= immutable
.ancestors().last().unwrap();
104 let mutable_clone
= immutable
.clone_for_update();
105 TreeMutator { immutable, mutable_clone }
108 pub fn make_mut
<N
: AstNode
>(&self, node
: &N
) -> N
{
109 N
::cast(self.make_syntax_mut(node
.syntax())).unwrap()
112 pub fn make_syntax_mut(&self, node
: &SyntaxNode
) -> SyntaxNode
{
113 let ptr
= SyntaxNodePtr
::new(node
);
114 ptr
.to_node(&self.mutable_clone
)
118 impl SourceChangeBuilder
{
119 pub fn new(file_id
: FileId
) -> SourceChangeBuilder
{
120 SourceChangeBuilder
{
121 edit
: TextEdit
::builder(),
123 source_change
: SourceChange
::default(),
124 trigger_signature_help
: false,
129 pub fn edit_file(&mut self, file_id
: FileId
) {
131 self.file_id
= file_id
;
134 fn commit(&mut self) {
135 if let Some(tm
) = self.mutated_tree
.take() {
136 algo
::diff(&tm
.immutable
, &tm
.mutable_clone
).into_text_edit(&mut self.edit
)
139 let edit
= mem
::take(&mut self.edit
).finish();
140 if !edit
.is_empty() {
141 self.source_change
.insert_source_edit(self.file_id
, edit
);
145 pub fn make_mut
<N
: AstNode
>(&mut self, node
: N
) -> N
{
146 self.mutated_tree
.get_or_insert_with(|| TreeMutator
::new(node
.syntax())).make_mut(&node
)
148 /// Returns a copy of the `node`, suitable for mutation.
150 /// Syntax trees in rust-analyzer are typically immutable, and mutating
151 /// operations panic at runtime. However, it is possible to make a copy of
152 /// the tree and mutate the copy freely. Mutation is based on interior
153 /// mutability, and different nodes in the same tree see the same mutations.
155 /// The typical pattern for an assist is to find specific nodes in the read
156 /// phase, and then get their mutable couterparts using `make_mut` in the
158 pub fn make_syntax_mut(&mut self, node
: SyntaxNode
) -> SyntaxNode
{
159 self.mutated_tree
.get_or_insert_with(|| TreeMutator
::new(&node
)).make_syntax_mut(&node
)
162 /// Remove specified `range` of text.
163 pub fn delete(&mut self, range
: TextRange
) {
164 self.edit
.delete(range
)
166 /// Append specified `text` at the given `offset`
167 pub fn insert(&mut self, offset
: TextSize
, text
: impl Into
<String
>) {
168 self.edit
.insert(offset
, text
.into())
170 /// Append specified `snippet` at the given `offset`
171 pub fn insert_snippet(
175 snippet
: impl Into
<String
>,
177 self.source_change
.is_snippet
= true;
178 self.insert(offset
, snippet
);
180 /// Replaces specified `range` of text with a given string.
181 pub fn replace(&mut self, range
: TextRange
, replace_with
: impl Into
<String
>) {
182 self.edit
.replace(range
, replace_with
.into())
184 /// Replaces specified `range` of text with a given `snippet`.
185 pub fn replace_snippet(
189 snippet
: impl Into
<String
>,
191 self.source_change
.is_snippet
= true;
192 self.replace(range
, snippet
);
194 pub fn replace_ast
<N
: AstNode
>(&mut self, old
: N
, new
: N
) {
195 algo
::diff(old
.syntax(), new
.syntax()).into_text_edit(&mut self.edit
)
197 pub fn create_file(&mut self, dst
: AnchoredPathBuf
, content
: impl Into
<String
>) {
198 let file_system_edit
= FileSystemEdit
::CreateFile { dst, initial_contents: content.into() }
;
199 self.source_change
.push_file_system_edit(file_system_edit
);
201 pub fn move_file(&mut self, src
: FileId
, dst
: AnchoredPathBuf
) {
202 let file_system_edit
= FileSystemEdit
::MoveFile { src, dst }
;
203 self.source_change
.push_file_system_edit(file_system_edit
);
205 pub fn trigger_signature_help(&mut self) {
206 self.trigger_signature_help
= true;
209 pub fn finish(mut self) -> SourceChange
{
211 mem
::take(&mut self.source_change
)
215 #[derive(Debug, Clone)]
216 pub enum FileSystemEdit
{
217 CreateFile { dst: AnchoredPathBuf, initial_contents: String }
,
218 MoveFile { src: FileId, dst: AnchoredPathBuf }
,
219 MoveDir { src: AnchoredPathBuf, src_id: FileId, dst: AnchoredPathBuf }
,
222 impl From
<FileSystemEdit
> for SourceChange
{
223 fn from(edit
: FileSystemEdit
) -> SourceChange
{
225 source_file_edits
: Default
::default(),
226 file_system_edits
: vec
![edit
],