1mod step_view;
2
3use crate::{
4 prompts::StepResolutionContext, AssistantPanel, Context, InlineAssistId, InlineAssistant,
5};
6use anyhow::{anyhow, Error, Result};
7use collections::HashMap;
8use editor::Editor;
9use futures::future;
10use gpui::{
11 Model, ModelContext, Task, UpdateGlobal as _, View, WeakModel, WeakView, WindowContext,
12};
13use language::{Anchor, Buffer, BufferSnapshot, SymbolPath};
14use language_model::{LanguageModelRegistry, LanguageModelRequestMessage, Role};
15use project::Project;
16use rope::Point;
17use serde::{Deserialize, Serialize};
18use smol::stream::StreamExt;
19use std::{cmp, fmt::Write, ops::Range, sync::Arc};
20use text::{AnchorRangeExt as _, OffsetRangeExt as _};
21use util::ResultExt as _;
22use workspace::Workspace;
23
24pub use step_view::WorkflowStepView;
25
26pub struct WorkflowStep {
27 context: WeakModel<Context>,
28 context_buffer_range: Range<Anchor>,
29 tool_output: String,
30 resolve_task: Option<Task<()>>,
31 pub resolution: Option<Result<WorkflowStepResolution, Arc<Error>>>,
32}
33
34#[derive(Clone, Debug, Eq, PartialEq)]
35pub struct WorkflowStepResolution {
36 pub title: String,
37 pub suggestion_groups: HashMap<Model<Buffer>, Vec<WorkflowSuggestionGroup>>,
38}
39
40#[derive(Clone, Debug, Eq, PartialEq)]
41pub struct WorkflowSuggestionGroup {
42 pub context_range: Range<language::Anchor>,
43 pub suggestions: Vec<WorkflowSuggestion>,
44}
45
46#[derive(Clone, Debug, Eq, PartialEq)]
47pub enum WorkflowSuggestion {
48 Update {
49 symbol_path: SymbolPath,
50 range: Range<language::Anchor>,
51 description: String,
52 },
53 CreateFile {
54 description: String,
55 },
56 InsertSiblingBefore {
57 symbol_path: SymbolPath,
58 position: language::Anchor,
59 description: String,
60 },
61 InsertSiblingAfter {
62 symbol_path: SymbolPath,
63 position: language::Anchor,
64 description: String,
65 },
66 PrependChild {
67 symbol_path: Option<SymbolPath>,
68 position: language::Anchor,
69 description: String,
70 },
71 AppendChild {
72 symbol_path: Option<SymbolPath>,
73 position: language::Anchor,
74 description: String,
75 },
76 Delete {
77 symbol_path: SymbolPath,
78 range: Range<language::Anchor>,
79 },
80}
81
82impl WorkflowStep {
83 pub fn new(range: Range<Anchor>, context: WeakModel<Context>) -> Self {
84 Self {
85 context_buffer_range: range,
86 tool_output: String::new(),
87 context,
88 resolution: None,
89 resolve_task: None,
90 }
91 }
92
93 pub fn resolve(&mut self, cx: &mut ModelContext<WorkflowStep>) -> Option<()> {
94 let range = self.context_buffer_range.clone();
95 let context = self.context.upgrade()?;
96 let context = context.read(cx);
97 let project = context.project()?;
98 let prompt_builder = context.prompt_builder();
99 let mut request = context.to_completion_request(cx);
100 let model = LanguageModelRegistry::read_global(cx).active_model();
101 let context_buffer = context.buffer();
102 let step_text = context_buffer
103 .read(cx)
104 .text_for_range(range.clone())
105 .collect::<String>();
106
107 let mut workflow_context = String::new();
108 for message in context.messages(cx) {
109 write!(&mut workflow_context, "<message role={}>", message.role).unwrap();
110 for chunk in context_buffer.read(cx).text_for_range(message.offset_range) {
111 write!(&mut workflow_context, "{chunk}").unwrap();
112 }
113 write!(&mut workflow_context, "</message>").unwrap();
114 }
115
116 self.resolve_task = Some(cx.spawn(|this, mut cx| async move {
117 let result = async {
118 let Some(model) = model else {
119 return Err(anyhow!("no model selected"));
120 };
121
122 this.update(&mut cx, |this, cx| {
123 this.tool_output.clear();
124 this.resolution = None;
125 this.result_updated(cx);
126 cx.notify();
127 })?;
128
129 let resolution_context = StepResolutionContext {
130 workflow_context,
131 step_to_resolve: step_text.clone(),
132 };
133 let mut prompt =
134 prompt_builder.generate_step_resolution_prompt(&resolution_context)?;
135 prompt.push_str(&step_text);
136 request.messages.push(LanguageModelRequestMessage {
137 role: Role::User,
138 content: vec![prompt.into()],
139 });
140
141 // Invoke the model to get its edit suggestions for this workflow step.
142 let mut stream = model
143 .use_tool_stream::<tool::WorkflowStepResolutionTool>(request, &cx)
144 .await?;
145 while let Some(chunk) = stream.next().await {
146 let chunk = chunk?;
147 this.update(&mut cx, |this, cx| {
148 this.tool_output.push_str(&chunk);
149 cx.notify();
150 })?;
151 }
152
153 let resolution = this.update(&mut cx, |this, _| {
154 serde_json::from_str::<tool::WorkflowStepResolutionTool>(&this.tool_output)
155 })??;
156
157 this.update(&mut cx, |this, cx| {
158 this.tool_output = serde_json::to_string_pretty(&resolution).unwrap();
159 cx.notify();
160 })?;
161
162 // Translate the parsed suggestions to our internal types, which anchor the suggestions to locations in the code.
163 let suggestion_tasks: Vec<_> = resolution
164 .suggestions
165 .iter()
166 .map(|suggestion| suggestion.resolve(project.clone(), cx.clone()))
167 .collect();
168
169 // Expand the context ranges of each suggestion and group suggestions with overlapping context ranges.
170 let suggestions = future::join_all(suggestion_tasks)
171 .await
172 .into_iter()
173 .filter_map(|task| task.log_err())
174 .collect::<Vec<_>>();
175
176 let mut suggestions_by_buffer = HashMap::default();
177 for (buffer, suggestion) in suggestions {
178 suggestions_by_buffer
179 .entry(buffer)
180 .or_insert_with(Vec::new)
181 .push(suggestion);
182 }
183
184 let mut suggestion_groups_by_buffer = HashMap::default();
185 for (buffer, mut suggestions) in suggestions_by_buffer {
186 let mut suggestion_groups = Vec::<WorkflowSuggestionGroup>::new();
187 let snapshot = buffer.update(&mut cx, |buffer, _| buffer.snapshot())?;
188 // Sort suggestions by their range so that earlier, larger ranges come first
189 suggestions.sort_by(|a, b| a.range().cmp(&b.range(), &snapshot));
190
191 // Merge overlapping suggestions
192 suggestions.dedup_by(|a, b| b.try_merge(a, &snapshot));
193
194 // Create context ranges for each suggestion
195 for suggestion in suggestions {
196 let context_range = {
197 let suggestion_point_range = suggestion.range().to_point(&snapshot);
198 let start_row = suggestion_point_range.start.row.saturating_sub(5);
199 let end_row = cmp::min(
200 suggestion_point_range.end.row + 5,
201 snapshot.max_point().row,
202 );
203 let start = snapshot.anchor_before(Point::new(start_row, 0));
204 let end = snapshot
205 .anchor_after(Point::new(end_row, snapshot.line_len(end_row)));
206 start..end
207 };
208
209 if let Some(last_group) = suggestion_groups.last_mut() {
210 if last_group
211 .context_range
212 .end
213 .cmp(&context_range.start, &snapshot)
214 .is_ge()
215 {
216 // Merge with the previous group if context ranges overlap
217 last_group.context_range.end = context_range.end;
218 last_group.suggestions.push(suggestion);
219 } else {
220 // Create a new group
221 suggestion_groups.push(WorkflowSuggestionGroup {
222 context_range,
223 suggestions: vec![suggestion],
224 });
225 }
226 } else {
227 // Create the first group
228 suggestion_groups.push(WorkflowSuggestionGroup {
229 context_range,
230 suggestions: vec![suggestion],
231 });
232 }
233 }
234
235 suggestion_groups_by_buffer.insert(buffer, suggestion_groups);
236 }
237
238 Ok((resolution.step_title, suggestion_groups_by_buffer))
239 };
240
241 let result = result.await;
242 this.update(&mut cx, |this, cx| {
243 this.resolution = Some(match result {
244 Ok((title, suggestion_groups)) => Ok(WorkflowStepResolution {
245 title,
246 suggestion_groups,
247 }),
248 Err(error) => Err(Arc::new(error)),
249 });
250 this.context
251 .update(cx, |context, cx| context.workflow_step_updated(range, cx))
252 .ok();
253 cx.notify();
254 })
255 .ok();
256 }));
257 None
258 }
259
260 fn result_updated(&mut self, cx: &mut ModelContext<Self>) {
261 self.context
262 .update(cx, |context, cx| {
263 context.workflow_step_updated(self.context_buffer_range.clone(), cx)
264 })
265 .ok();
266 }
267}
268
269impl WorkflowSuggestion {
270 pub fn range(&self) -> Range<language::Anchor> {
271 match self {
272 Self::Update { range, .. } => range.clone(),
273 Self::CreateFile { .. } => language::Anchor::MIN..language::Anchor::MAX,
274 Self::InsertSiblingBefore { position, .. }
275 | Self::InsertSiblingAfter { position, .. }
276 | Self::PrependChild { position, .. }
277 | Self::AppendChild { position, .. } => *position..*position,
278 Self::Delete { range, .. } => range.clone(),
279 }
280 }
281
282 pub fn description(&self) -> Option<&str> {
283 match self {
284 Self::Update { description, .. }
285 | Self::CreateFile { description }
286 | Self::InsertSiblingBefore { description, .. }
287 | Self::InsertSiblingAfter { description, .. }
288 | Self::PrependChild { description, .. }
289 | Self::AppendChild { description, .. } => Some(description),
290 Self::Delete { .. } => None,
291 }
292 }
293
294 fn description_mut(&mut self) -> Option<&mut String> {
295 match self {
296 Self::Update { description, .. }
297 | Self::CreateFile { description }
298 | Self::InsertSiblingBefore { description, .. }
299 | Self::InsertSiblingAfter { description, .. }
300 | Self::PrependChild { description, .. }
301 | Self::AppendChild { description, .. } => Some(description),
302 Self::Delete { .. } => None,
303 }
304 }
305
306 fn symbol_path(&self) -> Option<&SymbolPath> {
307 match self {
308 Self::Update { symbol_path, .. } => Some(symbol_path),
309 Self::InsertSiblingBefore { symbol_path, .. } => Some(symbol_path),
310 Self::InsertSiblingAfter { symbol_path, .. } => Some(symbol_path),
311 Self::PrependChild { symbol_path, .. } => symbol_path.as_ref(),
312 Self::AppendChild { symbol_path, .. } => symbol_path.as_ref(),
313 Self::Delete { symbol_path, .. } => Some(symbol_path),
314 Self::CreateFile { .. } => None,
315 }
316 }
317
318 fn kind(&self) -> &str {
319 match self {
320 Self::Update { .. } => "Update",
321 Self::CreateFile { .. } => "CreateFile",
322 Self::InsertSiblingBefore { .. } => "InsertSiblingBefore",
323 Self::InsertSiblingAfter { .. } => "InsertSiblingAfter",
324 Self::PrependChild { .. } => "PrependChild",
325 Self::AppendChild { .. } => "AppendChild",
326 Self::Delete { .. } => "Delete",
327 }
328 }
329
330 fn try_merge(&mut self, other: &Self, buffer: &BufferSnapshot) -> bool {
331 let range = self.range();
332 let other_range = other.range();
333
334 // Don't merge if we don't contain the other suggestion.
335 if range.start.cmp(&other_range.start, buffer).is_gt()
336 || range.end.cmp(&other_range.end, buffer).is_lt()
337 {
338 return false;
339 }
340
341 if let Some(description) = self.description_mut() {
342 if let Some(other_description) = other.description() {
343 description.push('\n');
344 description.push_str(other_description);
345 }
346 }
347 true
348 }
349
350 pub fn show(
351 &self,
352 editor: &View<Editor>,
353 excerpt_id: editor::ExcerptId,
354 workspace: &WeakView<Workspace>,
355 assistant_panel: &View<AssistantPanel>,
356 cx: &mut WindowContext,
357 ) -> Option<InlineAssistId> {
358 let mut initial_transaction_id = None;
359 let initial_prompt;
360 let suggestion_range;
361 let buffer = editor.read(cx).buffer().clone();
362 let snapshot = buffer.read(cx).snapshot(cx);
363
364 match self {
365 Self::Update {
366 range, description, ..
367 } => {
368 initial_prompt = description.clone();
369 suggestion_range = snapshot.anchor_in_excerpt(excerpt_id, range.start)?
370 ..snapshot.anchor_in_excerpt(excerpt_id, range.end)?;
371 }
372 Self::CreateFile { description } => {
373 initial_prompt = description.clone();
374 suggestion_range = editor::Anchor::min()..editor::Anchor::min();
375 }
376 Self::InsertSiblingBefore {
377 position,
378 description,
379 ..
380 } => {
381 let position = snapshot.anchor_in_excerpt(excerpt_id, *position)?;
382 initial_prompt = description.clone();
383 suggestion_range = buffer.update(cx, |buffer, cx| {
384 buffer.start_transaction(cx);
385 let line_start = buffer.insert_empty_line(position, true, true, cx);
386 initial_transaction_id = buffer.end_transaction(cx);
387 buffer.refresh_preview(cx);
388
389 let line_start = buffer.read(cx).anchor_before(line_start);
390 line_start..line_start
391 });
392 }
393 Self::InsertSiblingAfter {
394 position,
395 description,
396 ..
397 } => {
398 let position = snapshot.anchor_in_excerpt(excerpt_id, *position)?;
399 initial_prompt = description.clone();
400 suggestion_range = buffer.update(cx, |buffer, cx| {
401 buffer.start_transaction(cx);
402 let line_start = buffer.insert_empty_line(position, true, true, cx);
403 initial_transaction_id = buffer.end_transaction(cx);
404 buffer.refresh_preview(cx);
405
406 let line_start = buffer.read(cx).anchor_before(line_start);
407 line_start..line_start
408 });
409 }
410 Self::PrependChild {
411 position,
412 description,
413 ..
414 } => {
415 let position = snapshot.anchor_in_excerpt(excerpt_id, *position)?;
416 initial_prompt = description.clone();
417 suggestion_range = buffer.update(cx, |buffer, cx| {
418 buffer.start_transaction(cx);
419 let line_start = buffer.insert_empty_line(position, false, true, cx);
420 initial_transaction_id = buffer.end_transaction(cx);
421 buffer.refresh_preview(cx);
422
423 let line_start = buffer.read(cx).anchor_before(line_start);
424 line_start..line_start
425 });
426 }
427 Self::AppendChild {
428 position,
429 description,
430 ..
431 } => {
432 let position = snapshot.anchor_in_excerpt(excerpt_id, *position)?;
433 initial_prompt = description.clone();
434 suggestion_range = buffer.update(cx, |buffer, cx| {
435 buffer.start_transaction(cx);
436 let line_start = buffer.insert_empty_line(position, true, false, cx);
437 initial_transaction_id = buffer.end_transaction(cx);
438 buffer.refresh_preview(cx);
439
440 let line_start = buffer.read(cx).anchor_before(line_start);
441 line_start..line_start
442 });
443 }
444 Self::Delete { range, .. } => {
445 initial_prompt = "Delete".to_string();
446 suggestion_range = snapshot.anchor_in_excerpt(excerpt_id, range.start)?
447 ..snapshot.anchor_in_excerpt(excerpt_id, range.end)?;
448 }
449 }
450
451 InlineAssistant::update_global(cx, |inline_assistant, cx| {
452 Some(inline_assistant.suggest_assist(
453 editor,
454 suggestion_range,
455 initial_prompt,
456 initial_transaction_id,
457 Some(workspace.clone()),
458 Some(assistant_panel),
459 cx,
460 ))
461 })
462 }
463}
464
465pub mod tool {
466 use super::*;
467 use anyhow::Context as _;
468 use gpui::AsyncAppContext;
469 use language::ParseStatus;
470 use language_model::LanguageModelTool;
471 use project::ProjectPath;
472 use schemars::JsonSchema;
473 use std::path::Path;
474
475 #[derive(Debug, Serialize, Deserialize, JsonSchema)]
476 pub struct WorkflowStepResolutionTool {
477 /// An extremely short title for the edit step represented by these operations.
478 pub step_title: String,
479 /// A sequence of operations to apply to the codebase.
480 /// When multiple operations are required for a step, be sure to include multiple operations in this list.
481 pub suggestions: Vec<WorkflowSuggestionTool>,
482 }
483
484 impl LanguageModelTool for WorkflowStepResolutionTool {
485 fn name() -> String {
486 "edit".into()
487 }
488
489 fn description() -> String {
490 "suggest edits to one or more locations in the codebase".into()
491 }
492 }
493
494 /// A description of an operation to apply to one location in the codebase.
495 ///
496 /// This object represents a single edit operation that can be performed on a specific file
497 /// in the codebase. It encapsulates both the location (file path) and the nature of the
498 /// edit to be made.
499 ///
500 /// # Fields
501 ///
502 /// * `path`: A string representing the file path where the edit operation should be applied.
503 /// This path is relative to the root of the project or repository.
504 ///
505 /// * `kind`: An enum representing the specific type of edit operation to be performed.
506 ///
507 /// # Usage
508 ///
509 /// `EditOperation` is used within a code editor to represent and apply
510 /// programmatic changes to source code. It provides a structured way to describe
511 /// edits for features like refactoring tools or AI-assisted coding suggestions.
512 #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
513 pub struct WorkflowSuggestionTool {
514 /// The path to the file containing the relevant operation
515 pub path: String,
516 #[serde(flatten)]
517 pub kind: WorkflowSuggestionToolKind,
518 }
519
520 impl WorkflowSuggestionTool {
521 pub(super) async fn resolve(
522 &self,
523 project: Model<Project>,
524 mut cx: AsyncAppContext,
525 ) -> Result<(Model<Buffer>, super::WorkflowSuggestion)> {
526 let path = self.path.clone();
527 let kind = self.kind.clone();
528 let buffer = project
529 .update(&mut cx, |project, cx| {
530 let project_path = project
531 .find_project_path(Path::new(&path), cx)
532 .or_else(|| {
533 // If we couldn't find a project path for it, put it in the active worktree
534 // so that when we create the buffer, it can be saved.
535 let worktree = project
536 .active_entry()
537 .and_then(|entry_id| project.worktree_for_entry(entry_id, cx))
538 .or_else(|| project.worktrees(cx).next())?;
539 let worktree = worktree.read(cx);
540
541 Some(ProjectPath {
542 worktree_id: worktree.id(),
543 path: Arc::from(Path::new(&path)),
544 })
545 })
546 .with_context(|| format!("worktree not found for {:?}", path))?;
547 anyhow::Ok(project.open_buffer(project_path, cx))
548 })??
549 .await?;
550
551 let mut parse_status = buffer.read_with(&cx, |buffer, _cx| buffer.parse_status())?;
552 while *parse_status.borrow() != ParseStatus::Idle {
553 parse_status.changed().await?;
554 }
555
556 let snapshot = buffer.update(&mut cx, |buffer, _| buffer.snapshot())?;
557 let outline = snapshot.outline(None).context("no outline for buffer")?;
558
559 let suggestion = match kind {
560 WorkflowSuggestionToolKind::Update {
561 symbol,
562 description,
563 } => {
564 let (symbol_path, symbol) = outline
565 .find_most_similar(&symbol)
566 .with_context(|| format!("symbol not found: {:?}", symbol))?;
567 let symbol = symbol.to_point(&snapshot);
568 let start = symbol
569 .annotation_range
570 .map_or(symbol.range.start, |range| range.start);
571 let start = Point::new(start.row, 0);
572 let end = Point::new(
573 symbol.range.end.row,
574 snapshot.line_len(symbol.range.end.row),
575 );
576 let range = snapshot.anchor_before(start)..snapshot.anchor_after(end);
577 WorkflowSuggestion::Update {
578 range,
579 description,
580 symbol_path,
581 }
582 }
583 WorkflowSuggestionToolKind::Create { description } => {
584 WorkflowSuggestion::CreateFile { description }
585 }
586 WorkflowSuggestionToolKind::InsertSiblingBefore {
587 symbol,
588 description,
589 } => {
590 let (symbol_path, symbol) = outline
591 .find_most_similar(&symbol)
592 .with_context(|| format!("symbol not found: {:?}", symbol))?;
593 let symbol = symbol.to_point(&snapshot);
594 let position = snapshot.anchor_before(
595 symbol
596 .annotation_range
597 .map_or(symbol.range.start, |annotation_range| {
598 annotation_range.start
599 }),
600 );
601 WorkflowSuggestion::InsertSiblingBefore {
602 position,
603 description,
604 symbol_path,
605 }
606 }
607 WorkflowSuggestionToolKind::InsertSiblingAfter {
608 symbol,
609 description,
610 } => {
611 let (symbol_path, symbol) = outline
612 .find_most_similar(&symbol)
613 .with_context(|| format!("symbol not found: {:?}", symbol))?;
614 let symbol = symbol.to_point(&snapshot);
615 let position = snapshot.anchor_after(symbol.range.end);
616 WorkflowSuggestion::InsertSiblingAfter {
617 position,
618 description,
619 symbol_path,
620 }
621 }
622 WorkflowSuggestionToolKind::PrependChild {
623 symbol,
624 description,
625 } => {
626 if let Some(symbol) = symbol {
627 let (symbol_path, symbol) = outline
628 .find_most_similar(&symbol)
629 .with_context(|| format!("symbol not found: {:?}", symbol))?;
630 let symbol = symbol.to_point(&snapshot);
631
632 let position = snapshot.anchor_after(
633 symbol
634 .body_range
635 .map_or(symbol.range.start, |body_range| body_range.start),
636 );
637 WorkflowSuggestion::PrependChild {
638 position,
639 description,
640 symbol_path: Some(symbol_path),
641 }
642 } else {
643 WorkflowSuggestion::PrependChild {
644 position: language::Anchor::MIN,
645 description,
646 symbol_path: None,
647 }
648 }
649 }
650 WorkflowSuggestionToolKind::AppendChild {
651 symbol,
652 description,
653 } => {
654 if let Some(symbol) = symbol {
655 let (symbol_path, symbol) = outline
656 .find_most_similar(&symbol)
657 .with_context(|| format!("symbol not found: {:?}", symbol))?;
658 let symbol = symbol.to_point(&snapshot);
659
660 let position = snapshot.anchor_before(
661 symbol
662 .body_range
663 .map_or(symbol.range.end, |body_range| body_range.end),
664 );
665 WorkflowSuggestion::AppendChild {
666 position,
667 description,
668 symbol_path: Some(symbol_path),
669 }
670 } else {
671 WorkflowSuggestion::PrependChild {
672 position: language::Anchor::MAX,
673 description,
674 symbol_path: None,
675 }
676 }
677 }
678 WorkflowSuggestionToolKind::Delete { symbol } => {
679 let (symbol_path, symbol) = outline
680 .find_most_similar(&symbol)
681 .with_context(|| format!("symbol not found: {:?}", symbol))?;
682 let symbol = symbol.to_point(&snapshot);
683 let start = symbol
684 .annotation_range
685 .map_or(symbol.range.start, |range| range.start);
686 let start = Point::new(start.row, 0);
687 let end = Point::new(
688 symbol.range.end.row,
689 snapshot.line_len(symbol.range.end.row),
690 );
691 let range = snapshot.anchor_before(start)..snapshot.anchor_after(end);
692 WorkflowSuggestion::Delete { range, symbol_path }
693 }
694 };
695
696 Ok((buffer, suggestion))
697 }
698 }
699
700 #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
701 #[serde(tag = "kind")]
702 pub enum WorkflowSuggestionToolKind {
703 /// Rewrites the specified symbol entirely based on the given description.
704 /// This operation completely replaces the existing symbol with new content.
705 Update {
706 /// A fully-qualified reference to the symbol, e.g. `mod foo impl Bar pub fn baz` instead of just `fn baz`.
707 /// The path should uniquely identify the symbol within the containing file.
708 symbol: String,
709 /// A brief description of the transformation to apply to the symbol.
710 description: String,
711 },
712 /// Creates a new file with the given path based on the provided description.
713 /// This operation adds a new file to the codebase.
714 Create {
715 /// A brief description of the file to be created.
716 description: String,
717 },
718 /// Inserts a new symbol based on the given description before the specified symbol.
719 /// This operation adds new content immediately preceding an existing symbol.
720 InsertSiblingBefore {
721 /// A fully-qualified reference to the symbol, e.g. `mod foo impl Bar pub fn baz` instead of just `fn baz`.
722 /// The new content will be inserted immediately before this symbol.
723 symbol: String,
724 /// A brief description of the new symbol to be inserted.
725 description: String,
726 },
727 /// Inserts a new symbol based on the given description after the specified symbol.
728 /// This operation adds new content immediately following an existing symbol.
729 InsertSiblingAfter {
730 /// A fully-qualified reference to the symbol, e.g. `mod foo impl Bar pub fn baz` instead of just `fn baz`.
731 /// The new content will be inserted immediately after this symbol.
732 symbol: String,
733 /// A brief description of the new symbol to be inserted.
734 description: String,
735 },
736 /// Inserts a new symbol as a child of the specified symbol at the start.
737 /// This operation adds new content as the first child of an existing symbol (or file if no symbol is provided).
738 PrependChild {
739 /// An optional fully-qualified reference to the symbol after the code you want to insert, e.g. `mod foo impl Bar pub fn baz` instead of just `fn baz`.
740 /// If provided, the new content will be inserted as the first child of this symbol.
741 /// If not provided, the new content will be inserted at the top of the file.
742 symbol: Option<String>,
743 /// A brief description of the new symbol to be inserted.
744 description: String,
745 },
746 /// Inserts a new symbol as a child of the specified symbol at the end.
747 /// This operation adds new content as the last child of an existing symbol (or file if no symbol is provided).
748 AppendChild {
749 /// An optional fully-qualified reference to the symbol before the code you want to insert, e.g. `mod foo impl Bar pub fn baz` instead of just `fn baz`.
750 /// If provided, the new content will be inserted as the last child of this symbol.
751 /// If not provided, the new content will be applied at the bottom of the file.
752 symbol: Option<String>,
753 /// A brief description of the new symbol to be inserted.
754 description: String,
755 },
756 /// Deletes the specified symbol from the containing file.
757 Delete {
758 /// An fully-qualified reference to the symbol to be deleted, e.g. `mod foo impl Bar pub fn baz` instead of just `fn baz`.
759 symbol: String,
760 },
761 }
762}