Detailed changes
@@ -8,6 +8,7 @@ use serde::{Deserialize, Serialize};
use std::sync::Arc;
use ui::IconName;
use util::command::new_smol_command;
+use util::markdown::MarkdownString;
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
pub struct BashToolInput {
@@ -43,7 +44,14 @@ impl Tool for BashTool {
fn ui_text(&self, input: &serde_json::Value) -> String {
match serde_json::from_value::<BashToolInput>(input.clone()) {
- Ok(input) => format!("`{}`", input.command),
+ Ok(input) => {
+ let cmd = MarkdownString::escape(&input.command);
+ if input.command.contains('\n') {
+ format!("```bash\n{cmd}\n```")
+ } else {
+ format!("`{cmd}`")
+ }
+ }
Err(_) => "Run bash command".to_string(),
}
}
@@ -7,6 +7,7 @@ use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use ui::IconName;
+use util::markdown::MarkdownString;
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
pub struct CopyPathToolInput {
@@ -60,8 +61,8 @@ impl Tool for CopyPathTool {
fn ui_text(&self, input: &serde_json::Value) -> String {
match serde_json::from_value::<CopyPathToolInput>(input.clone()) {
Ok(input) => {
- let src = input.source_path.as_str();
- let dest = input.destination_path.as_str();
+ let src = MarkdownString::escape(&input.source_path);
+ let dest = MarkdownString::escape(&input.destination_path);
format!("Copy `{src}` to `{dest}`")
}
Err(_) => "Copy path".to_string(),
@@ -7,6 +7,7 @@ use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use ui::IconName;
+use util::markdown::MarkdownString;
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
pub struct CreateFileToolInput {
@@ -57,7 +58,7 @@ impl Tool for CreateFileTool {
fn ui_text(&self, input: &serde_json::Value) -> String {
match serde_json::from_value::<CreateFileToolInput>(input.clone()) {
Ok(input) => {
- let path = input.path.as_str();
+ let path = MarkdownString::escape(&input.path);
format!("Create file `{path}`")
}
Err(_) => "Create file".to_string(),
@@ -6,12 +6,9 @@ use language_model::LanguageModelRequestMessage;
use project::Project;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
-use std::{
- fmt::Write,
- path::{Path, PathBuf},
- sync::Arc,
-};
+use std::{fmt::Write, path::Path, sync::Arc};
use ui::IconName;
+use util::markdown::MarkdownString;
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
pub struct DiagnosticsToolInput {
@@ -28,7 +25,7 @@ pub struct DiagnosticsToolInput {
///
/// If you wanna access diagnostics for `dolor.txt` in `ipsum`, you should use the path `ipsum/dolor.txt`.
/// </example>
- pub path: Option<PathBuf>,
+ pub path: Option<String>,
}
pub struct DiagnosticsTool;
@@ -58,9 +55,12 @@ impl Tool for DiagnosticsTool {
fn ui_text(&self, input: &serde_json::Value) -> String {
if let Some(path) = serde_json::from_value::<DiagnosticsToolInput>(input.clone())
.ok()
- .and_then(|input| input.path)
+ .and_then(|input| match input.path {
+ Some(path) if !path.is_empty() => Some(MarkdownString::escape(&path)),
+ _ => None,
+ })
{
- format!("Check diagnostics for “`{}`”", path.display())
+ format!("Check diagnostics for `{path}`")
} else {
"Check project diagnostics".to_string()
}
@@ -74,76 +74,78 @@ impl Tool for DiagnosticsTool {
_action_log: Entity<ActionLog>,
cx: &mut App,
) -> Task<Result<String>> {
- if let Some(path) = serde_json::from_value::<DiagnosticsToolInput>(input)
+ match serde_json::from_value::<DiagnosticsToolInput>(input)
.ok()
.and_then(|input| input.path)
{
- let Some(project_path) = project.read(cx).find_project_path(&path, cx) else {
- return Task::ready(Err(anyhow!(
- "Could not find path {} in project",
- path.display()
- )));
- };
- let buffer = project.update(cx, |project, cx| project.open_buffer(project_path, cx));
-
- cx.spawn(async move |cx| {
+ Some(path) if !path.is_empty() => {
+ let Some(project_path) = project.read(cx).find_project_path(&path, cx) else {
+ return Task::ready(Err(anyhow!("Could not find path {path} in project",)));
+ };
+
+ let buffer =
+ project.update(cx, |project, cx| project.open_buffer(project_path, cx));
+
+ cx.spawn(async move |cx| {
+ let mut output = String::new();
+ let buffer = buffer.await?;
+ let snapshot = buffer.read_with(cx, |buffer, _cx| buffer.snapshot())?;
+
+ for (_, group) in snapshot.diagnostic_groups(None) {
+ let entry = &group.entries[group.primary_ix];
+ let range = entry.range.to_point(&snapshot);
+ let severity = match entry.diagnostic.severity {
+ DiagnosticSeverity::ERROR => "error",
+ DiagnosticSeverity::WARNING => "warning",
+ _ => continue,
+ };
+
+ writeln!(
+ output,
+ "{} at line {}: {}",
+ severity,
+ range.start.row + 1,
+ entry.diagnostic.message
+ )?;
+ }
+
+ if output.is_empty() {
+ Ok("File doesn't have errors or warnings!".to_string())
+ } else {
+ Ok(output)
+ }
+ })
+ }
+ _ => {
+ let project = project.read(cx);
let mut output = String::new();
- let buffer = buffer.await?;
- let snapshot = buffer.read_with(cx, |buffer, _cx| buffer.snapshot())?;
-
- for (_, group) in snapshot.diagnostic_groups(None) {
- let entry = &group.entries[group.primary_ix];
- let range = entry.range.to_point(&snapshot);
- let severity = match entry.diagnostic.severity {
- DiagnosticSeverity::ERROR => "error",
- DiagnosticSeverity::WARNING => "warning",
- _ => continue,
- };
-
- writeln!(
- output,
- "{} at line {}: {}",
- severity,
- range.start.row + 1,
- entry.diagnostic.message
- )?;
+ let mut has_diagnostics = false;
+
+ for (project_path, _, summary) in project.diagnostic_summaries(true, cx) {
+ if summary.error_count > 0 || summary.warning_count > 0 {
+ let Some(worktree) = project.worktree_for_id(project_path.worktree_id, cx)
+ else {
+ continue;
+ };
+
+ has_diagnostics = true;
+ output.push_str(&format!(
+ "{}: {} error(s), {} warning(s)\n",
+ Path::new(worktree.read(cx).root_name())
+ .join(project_path.path)
+ .display(),
+ summary.error_count,
+ summary.warning_count
+ ));
+ }
}
- if output.is_empty() {
- Ok("File doesn't have errors or warnings!".to_string())
+ if has_diagnostics {
+ Task::ready(Ok(output))
} else {
- Ok(output)
- }
- })
- } else {
- let project = project.read(cx);
- let mut output = String::new();
- let mut has_diagnostics = false;
-
- for (project_path, _, summary) in project.diagnostic_summaries(true, cx) {
- if summary.error_count > 0 || summary.warning_count > 0 {
- let Some(worktree) = project.worktree_for_id(project_path.worktree_id, cx)
- else {
- continue;
- };
-
- has_diagnostics = true;
- output.push_str(&format!(
- "{}: {} error(s), {} warning(s)\n",
- Path::new(worktree.read(cx).root_name())
- .join(project_path.path)
- .display(),
- summary.error_count,
- summary.warning_count
- ));
+ Task::ready(Ok("No errors or warnings found in the project.".to_string()))
}
}
-
- if has_diagnostics {
- Task::ready(Ok(output))
- } else {
- Task::ready(Ok("No errors or warnings found in the project.".to_string()))
- }
}
}
}
@@ -13,6 +13,7 @@ use project::Project;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use ui::IconName;
+use util::markdown::MarkdownString;
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
enum ContentType {
@@ -133,7 +134,7 @@ impl Tool for FetchTool {
fn ui_text(&self, input: &serde_json::Value) -> String {
match serde_json::from_value::<FetchToolInput>(input.clone()) {
- Ok(input) => format!("Fetch `{}`", input.url),
+ Ok(input) => format!("Fetch {}", MarkdownString::escape(&input.url)),
Err(_) => "Fetch URL".to_string(),
}
}
@@ -33,10 +33,10 @@ pub struct FindReplaceFileToolInput {
/// </example>
pub path: PathBuf,
- /// A user-friendly description of what's being replaced. This will be shown in the UI.
+ /// A user-friendly markdown description of what's being replaced. This will be shown in the UI.
///
/// <example>Fix API endpoint URLs</example>
- /// <example>Update copyright year</example>
+ /// <example>Update copyright year in `page_footer`</example>
pub display_description: String,
/// The unique string to find in the file. This string cannot be empty;
@@ -7,6 +7,7 @@ use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::{fmt::Write, path::Path, sync::Arc};
use ui::IconName;
+use util::markdown::MarkdownString;
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
pub struct ListDirectoryToolInput {
@@ -61,7 +62,10 @@ impl Tool for ListDirectoryTool {
fn ui_text(&self, input: &serde_json::Value) -> String {
match serde_json::from_value::<ListDirectoryToolInput>(input.clone()) {
- Ok(input) => format!("List the `{}` directory's contents", input.path),
+ Ok(input) => {
+ let path = MarkdownString::escape(&input.path);
+ format!("List the `{path}` directory's contents")
+ }
Err(_) => "List directory".to_string(),
}
}
@@ -7,6 +7,7 @@ use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::{path::Path, sync::Arc};
use ui::IconName;
+use util::markdown::MarkdownString;
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
pub struct MovePathToolInput {
@@ -60,16 +61,17 @@ impl Tool for MovePathTool {
fn ui_text(&self, input: &serde_json::Value) -> String {
match serde_json::from_value::<MovePathToolInput>(input.clone()) {
Ok(input) => {
- let src = input.source_path.as_str();
- let dest = input.destination_path.as_str();
- let src_path = Path::new(src);
- let dest_path = Path::new(dest);
+ let src = MarkdownString::escape(&input.source_path);
+ let dest = MarkdownString::escape(&input.destination_path);
+ let src_path = Path::new(&input.source_path);
+ let dest_path = Path::new(&input.destination_path);
match dest_path
.file_name()
.and_then(|os_str| os_str.to_os_string().into_string().ok())
{
Some(filename) if src_path.parent() == dest_path.parent() => {
+ let filename = MarkdownString::escape(&filename);
format!("Rename `{src}` to `{filename}`")
}
_ => {
@@ -10,6 +10,7 @@ use project::Project;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use ui::IconName;
+use util::markdown::MarkdownString;
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
pub struct ReadFileToolInput {
@@ -64,7 +65,10 @@ impl Tool for ReadFileTool {
fn ui_text(&self, input: &serde_json::Value) -> String {
match serde_json::from_value::<ReadFileToolInput>(input.clone()) {
- Ok(input) => format!("Read file `{}`", input.path.display()),
+ Ok(input) => {
+ let path = MarkdownString::escape(&input.path.display().to_string());
+ format!("Read file `{path}`")
+ }
Err(_) => "Read file".to_string(),
}
}
@@ -12,6 +12,7 @@ use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::{cmp, fmt::Write, sync::Arc};
use ui::IconName;
+use util::markdown::MarkdownString;
use util::paths::PathMatcher;
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
@@ -63,14 +64,12 @@ impl Tool for RegexSearchTool {
match serde_json::from_value::<RegexSearchToolInput>(input.clone()) {
Ok(input) => {
let page = input.page();
+ let regex = MarkdownString::escape(&input.regex);
if page > 1 {
- format!(
- "Get page {page} of search results for regex “`{}`”",
- input.regex
- )
+ format!("Get page {page} of search results for regex “`{regex}`”")
} else {
- format!("Search files for regex “`{}`”", input.regex)
+ format!("Search files for regex “`{regex}`”")
}
}
Err(_) => "Search with regex".to_string(),
@@ -19,7 +19,7 @@ impl MarkdownString {
/// * `$` for inline math
/// * `~` for strikethrough
///
- /// Escape of some character is unnecessary because while they are involved in markdown syntax,
+ /// Escape of some characters is unnecessary, because while they are involved in markdown syntax,
/// the other characters involved are escaped:
///
/// * `!`, `]`, `(`, and `)` are used in link syntax, but `[` is escaped so these are parsed as