move_path_tool.rs

  1use anyhow::{anyhow, Result};
  2use assistant_tool::{ActionLog, Tool};
  3use gpui::{App, AppContext, Entity, Task};
  4use language_model::LanguageModelRequestMessage;
  5use project::Project;
  6use schemars::JsonSchema;
  7use serde::{Deserialize, Serialize};
  8use std::{path::Path, sync::Arc};
  9
 10#[derive(Debug, Serialize, Deserialize, JsonSchema)]
 11pub struct MovePathToolInput {
 12    /// The source path of the file or directory to move/rename.
 13    ///
 14    /// <example>
 15    /// If the project has the following files:
 16    ///
 17    /// - directory1/a/something.txt
 18    /// - directory2/a/things.txt
 19    /// - directory3/a/other.txt
 20    ///
 21    /// You can move the first file by providing a source_path of "directory1/a/something.txt"
 22    /// </example>
 23    pub source_path: String,
 24
 25    /// The destination path where the file or directory should be moved/renamed to.
 26    /// If the paths are the same except for the filename, then this will be a rename.
 27    ///
 28    /// <example>
 29    /// To move "directory1/a/something.txt" to "directory2/b/renamed.txt",
 30    /// provide a destination_path of "directory2/b/renamed.txt"
 31    /// </example>
 32    pub destination_path: String,
 33}
 34
 35pub struct MovePathTool;
 36
 37impl Tool for MovePathTool {
 38    fn name(&self) -> String {
 39        "move-path".into()
 40    }
 41
 42    fn needs_confirmation(&self) -> bool {
 43        true
 44    }
 45
 46    fn description(&self) -> String {
 47        include_str!("./move_path_tool/description.md").into()
 48    }
 49
 50    fn input_schema(&self) -> serde_json::Value {
 51        let schema = schemars::schema_for!(MovePathToolInput);
 52        serde_json::to_value(&schema).unwrap()
 53    }
 54
 55    fn ui_text(&self, input: &serde_json::Value) -> String {
 56        match serde_json::from_value::<MovePathToolInput>(input.clone()) {
 57            Ok(input) => {
 58                let src = input.source_path.as_str();
 59                let dest = input.destination_path.as_str();
 60                let src_path = Path::new(src);
 61                let dest_path = Path::new(dest);
 62
 63                match dest_path
 64                    .file_name()
 65                    .and_then(|os_str| os_str.to_os_string().into_string().ok())
 66                {
 67                    Some(filename) if src_path.parent() == dest_path.parent() => {
 68                        format!("Rename `{src}` to `{filename}`")
 69                    }
 70                    _ => {
 71                        format!("Move `{src}` to `{dest}`")
 72                    }
 73                }
 74            }
 75            Err(_) => "Move path".to_string(),
 76        }
 77    }
 78
 79    fn run(
 80        self: Arc<Self>,
 81        input: serde_json::Value,
 82        _messages: &[LanguageModelRequestMessage],
 83        project: Entity<Project>,
 84        _action_log: Entity<ActionLog>,
 85        cx: &mut App,
 86    ) -> Task<Result<String>> {
 87        let input = match serde_json::from_value::<MovePathToolInput>(input) {
 88            Ok(input) => input,
 89            Err(err) => return Task::ready(Err(anyhow!(err))),
 90        };
 91        let rename_task = project.update(cx, |project, cx| {
 92            match project
 93                .find_project_path(&input.source_path, cx)
 94                .and_then(|project_path| project.entry_for_path(&project_path, cx))
 95            {
 96                Some(entity) => match project.find_project_path(&input.destination_path, cx) {
 97                    Some(project_path) => project.rename_entry(entity.id, project_path.path, cx),
 98                    None => Task::ready(Err(anyhow!(
 99                        "Destination path {} was outside the project.",
100                        input.destination_path
101                    ))),
102                },
103                None => Task::ready(Err(anyhow!(
104                    "Source path {} was not found in the project.",
105                    input.source_path
106                ))),
107            }
108        });
109
110        cx.background_spawn(async move {
111            match rename_task.await {
112                Ok(_) => Ok(format!(
113                    "Moved {} to {}",
114                    input.source_path, input.destination_path
115                )),
116                Err(err) => Err(anyhow!(
117                    "Failed to move {} to {}: {}",
118                    input.source_path,
119                    input.destination_path,
120                    err
121                )),
122            }
123        })
124    }
125}