Skip to content
This repository was archived by the owner on May 28, 2025. It is now read-only.

Commit a284a0c

Browse files
committed
internal: Introduce Structured Snippet API
1 parent b99d5eb commit a284a0c

File tree

1 file changed

+111
-1
lines changed

1 file changed

+111
-1
lines changed

crates/ide-db/src/source_change.rs

Lines changed: 111 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use std::{collections::hash_map::Entry, iter, mem};
77

88
use base_db::{AnchoredPathBuf, FileId};
99
use stdx::{hash::NoHashHashMap, never};
10-
use syntax::{algo, AstNode, SyntaxNode, SyntaxNodePtr, TextRange, TextSize};
10+
use syntax::{algo, ast, ted, AstNode, SyntaxNode, SyntaxNodePtr, TextRange, TextSize};
1111
use text_edit::{TextEdit, TextEditBuilder};
1212

1313
use crate::SnippetCap;
@@ -99,13 +99,21 @@ pub struct SourceChangeBuilder {
9999

100100
/// Maps the original, immutable `SyntaxNode` to a `clone_for_update` twin.
101101
pub mutated_tree: Option<TreeMutator>,
102+
/// Keeps track of where to place snippets
103+
pub snippet_builder: Option<SnippetBuilder>,
102104
}
103105

104106
pub struct TreeMutator {
105107
immutable: SyntaxNode,
106108
mutable_clone: SyntaxNode,
107109
}
108110

111+
#[derive(Default)]
112+
pub struct SnippetBuilder {
113+
/// Where to place snippets at
114+
places: Vec<PlaceSnippet>,
115+
}
116+
109117
impl TreeMutator {
110118
pub fn new(immutable: &SyntaxNode) -> TreeMutator {
111119
let immutable = immutable.ancestors().last().unwrap();
@@ -131,6 +139,7 @@ impl SourceChangeBuilder {
131139
source_change: SourceChange::default(),
132140
trigger_signature_help: false,
133141
mutated_tree: None,
142+
snippet_builder: None,
134143
}
135144
}
136145

@@ -140,6 +149,17 @@ impl SourceChangeBuilder {
140149
}
141150

142151
fn commit(&mut self) {
152+
// Render snippets first so that they get bundled into the tree diff
153+
if let Some(mut snippets) = self.snippet_builder.take() {
154+
// Last snippet always has stop index 0
155+
let last_stop = snippets.places.pop().unwrap();
156+
last_stop.place(0);
157+
158+
for (index, stop) in snippets.places.into_iter().enumerate() {
159+
stop.place(index + 1)
160+
}
161+
}
162+
143163
if let Some(tm) = self.mutated_tree.take() {
144164
algo::diff(&tm.immutable, &tm.mutable_clone).into_text_edit(&mut self.edit)
145165
}
@@ -214,6 +234,33 @@ impl SourceChangeBuilder {
214234
self.trigger_signature_help = true;
215235
}
216236

237+
/// Adds a tabstop snippet to place the cursor before `node`
238+
pub fn add_tabstop_before(&mut self, _cap: SnippetCap, node: impl AstNode) {
239+
assert!(node.syntax().parent().is_some());
240+
241+
let snippet_builder = self.snippet_builder.get_or_insert(SnippetBuilder { places: vec![] });
242+
snippet_builder.places.push(PlaceSnippet::Before(node.syntax().clone()));
243+
self.source_change.is_snippet = true;
244+
}
245+
246+
/// Adds a tabstop snippet to place the cursor after `node`
247+
pub fn add_tabstop_after(&mut self, _cap: SnippetCap, node: impl AstNode) {
248+
assert!(node.syntax().parent().is_some());
249+
250+
let snippet_builder = self.snippet_builder.get_or_insert(SnippetBuilder { places: vec![] });
251+
snippet_builder.places.push(PlaceSnippet::After(node.syntax().clone()));
252+
self.source_change.is_snippet = true;
253+
}
254+
255+
/// Adds a snippet to move the cursor selected over `node`
256+
pub fn add_placeholder_snippet(&mut self, _cap: SnippetCap, node: impl AstNode) {
257+
assert!(node.syntax().parent().is_some());
258+
259+
let snippet_builder = self.snippet_builder.get_or_insert(SnippetBuilder { places: vec![] });
260+
snippet_builder.places.push(PlaceSnippet::Over(node.syntax().clone()));
261+
self.source_change.is_snippet = true;
262+
}
263+
217264
pub fn finish(mut self) -> SourceChange {
218265
self.commit();
219266
mem::take(&mut self.source_change)
@@ -236,3 +283,66 @@ impl From<FileSystemEdit> for SourceChange {
236283
}
237284
}
238285
}
286+
287+
enum PlaceSnippet {
288+
/// Place a tabstop before a node
289+
Before(SyntaxNode),
290+
/// Place a tabstop before a node
291+
After(SyntaxNode),
292+
/// Place a placeholder snippet in place of the node
293+
Over(SyntaxNode),
294+
}
295+
296+
impl PlaceSnippet {
297+
/// Places the snippet before or over a node with the given tab stop index
298+
fn place(self, order: usize) {
299+
// ensure the target node is still attached
300+
match &self {
301+
PlaceSnippet::Before(node) | PlaceSnippet::After(node) | PlaceSnippet::Over(node) => {
302+
// node should still be in the tree, but if it isn't
303+
// then it's okay to just ignore this place
304+
if stdx::never!(node.parent().is_none()) {
305+
return;
306+
}
307+
}
308+
}
309+
310+
match self {
311+
PlaceSnippet::Before(node) => {
312+
ted::insert_raw(ted::Position::before(&node), Self::make_tab_stop(order));
313+
}
314+
PlaceSnippet::After(node) => {
315+
ted::insert_raw(ted::Position::after(&node), Self::make_tab_stop(order));
316+
}
317+
PlaceSnippet::Over(node) => {
318+
let position = ted::Position::before(&node);
319+
node.detach();
320+
321+
let snippet = ast::SourceFile::parse(&format!("${{{order}:_}}"))
322+
.syntax_node()
323+
.clone_for_update();
324+
325+
let placeholder =
326+
snippet.descendants().find_map(ast::UnderscoreExpr::cast).unwrap();
327+
ted::replace(placeholder.syntax(), node);
328+
329+
ted::insert_raw(position, snippet);
330+
}
331+
}
332+
}
333+
334+
fn make_tab_stop(order: usize) -> SyntaxNode {
335+
let stop = ast::SourceFile::parse(&format!("stop!(${order})"))
336+
.syntax_node()
337+
.descendants()
338+
.find_map(ast::TokenTree::cast)
339+
.unwrap()
340+
.syntax()
341+
.clone_for_update();
342+
343+
stop.first_token().unwrap().detach();
344+
stop.last_token().unwrap().detach();
345+
346+
stop
347+
}
348+
}

0 commit comments

Comments
 (0)