From 44aff7cd46e61c99e5c2e1971e5728f39df553ef Mon Sep 17 00:00:00 2001 From: Michael Sloan Date: Wed, 26 Mar 2025 15:49:51 -0600 Subject: [PATCH] Fix tools' `ui_text` to use inline code escaping (#27543) Markdown escaping was added in #27502. Release Notes: - N/A --- crates/assistant_tools/src/bash_tool.rs | 5 ++--- crates/assistant_tools/src/copy_path_tool.rs | 6 +++--- crates/assistant_tools/src/create_directory_tool.rs | 5 ++++- crates/assistant_tools/src/create_file_tool.rs | 4 ++-- crates/assistant_tools/src/diagnostics_tool.rs | 4 ++-- crates/assistant_tools/src/list_directory_tool.rs | 4 ++-- crates/assistant_tools/src/move_path_tool.rs | 10 +++++----- crates/assistant_tools/src/read_file_tool.rs | 4 ++-- crates/assistant_tools/src/regex_search_tool.rs | 6 +++--- crates/util/src/markdown.rs | 10 +++++++++- 10 files changed, 34 insertions(+), 24 deletions(-) diff --git a/crates/assistant_tools/src/bash_tool.rs b/crates/assistant_tools/src/bash_tool.rs index 5045eb6ee3..0e9a3a1cb4 100644 --- a/crates/assistant_tools/src/bash_tool.rs +++ b/crates/assistant_tools/src/bash_tool.rs @@ -45,11 +45,10 @@ impl Tool for BashTool { fn ui_text(&self, input: &serde_json::Value) -> String { match serde_json::from_value::(input.clone()) { Ok(input) => { - let cmd = MarkdownString::escape(&input.command); if input.command.contains('\n') { - format!("```bash\n{cmd}\n```") + MarkdownString::code_block("bash", &input.command).0 } else { - format!("`{cmd}`") + MarkdownString::inline_code(&input.command).0 } } Err(_) => "Run bash command".to_string(), diff --git a/crates/assistant_tools/src/copy_path_tool.rs b/crates/assistant_tools/src/copy_path_tool.rs index 337cd177c1..c5adb46648 100644 --- a/crates/assistant_tools/src/copy_path_tool.rs +++ b/crates/assistant_tools/src/copy_path_tool.rs @@ -61,9 +61,9 @@ impl Tool for CopyPathTool { fn ui_text(&self, input: &serde_json::Value) -> String { match serde_json::from_value::(input.clone()) { Ok(input) => { - let src = MarkdownString::escape(&input.source_path); - let dest = MarkdownString::escape(&input.destination_path); - format!("Copy `{src}` to `{dest}`") + let src = MarkdownString::inline_code(&input.source_path); + let dest = MarkdownString::inline_code(&input.destination_path); + format!("Copy {src} to {dest}") } Err(_) => "Copy path".to_string(), } diff --git a/crates/assistant_tools/src/create_directory_tool.rs b/crates/assistant_tools/src/create_directory_tool.rs index c3e1ef513e..28609f46a6 100644 --- a/crates/assistant_tools/src/create_directory_tool.rs +++ b/crates/assistant_tools/src/create_directory_tool.rs @@ -51,7 +51,10 @@ impl Tool for CreateDirectoryTool { fn ui_text(&self, input: &serde_json::Value) -> String { match serde_json::from_value::(input.clone()) { Ok(input) => { - format!("Create directory `{}`", MarkdownString::escape(&input.path)) + format!( + "Create directory {}", + MarkdownString::inline_code(&input.path) + ) } Err(_) => "Create directory".to_string(), } diff --git a/crates/assistant_tools/src/create_file_tool.rs b/crates/assistant_tools/src/create_file_tool.rs index 5f0c67745f..9997fe2da0 100644 --- a/crates/assistant_tools/src/create_file_tool.rs +++ b/crates/assistant_tools/src/create_file_tool.rs @@ -58,8 +58,8 @@ impl Tool for CreateFileTool { fn ui_text(&self, input: &serde_json::Value) -> String { match serde_json::from_value::(input.clone()) { Ok(input) => { - let path = MarkdownString::escape(&input.path); - format!("Create file `{path}`") + let path = MarkdownString::inline_code(&input.path); + format!("Create file {path}") } Err(_) => "Create file".to_string(), } diff --git a/crates/assistant_tools/src/diagnostics_tool.rs b/crates/assistant_tools/src/diagnostics_tool.rs index c7c944dafe..ab0dd7098b 100644 --- a/crates/assistant_tools/src/diagnostics_tool.rs +++ b/crates/assistant_tools/src/diagnostics_tool.rs @@ -66,11 +66,11 @@ impl Tool for DiagnosticsTool { if let Some(path) = serde_json::from_value::(input.clone()) .ok() .and_then(|input| match input.path { - Some(path) if !path.is_empty() => Some(MarkdownString::escape(&path)), + Some(path) if !path.is_empty() => Some(MarkdownString::inline_code(&path)), _ => None, }) { - format!("Check diagnostics for `{path}`") + format!("Check diagnostics for {path}") } else { "Check project diagnostics".to_string() } diff --git a/crates/assistant_tools/src/list_directory_tool.rs b/crates/assistant_tools/src/list_directory_tool.rs index c7ed123e66..12d9638471 100644 --- a/crates/assistant_tools/src/list_directory_tool.rs +++ b/crates/assistant_tools/src/list_directory_tool.rs @@ -63,8 +63,8 @@ impl Tool for ListDirectoryTool { fn ui_text(&self, input: &serde_json::Value) -> String { match serde_json::from_value::(input.clone()) { Ok(input) => { - let path = MarkdownString::escape(&input.path); - format!("List the `{path}` directory's contents") + let path = MarkdownString::inline_code(&input.path); + format!("List the {path} directory's contents") } Err(_) => "List directory".to_string(), } diff --git a/crates/assistant_tools/src/move_path_tool.rs b/crates/assistant_tools/src/move_path_tool.rs index 9da7baded5..89bc5ccca0 100644 --- a/crates/assistant_tools/src/move_path_tool.rs +++ b/crates/assistant_tools/src/move_path_tool.rs @@ -61,8 +61,8 @@ impl Tool for MovePathTool { fn ui_text(&self, input: &serde_json::Value) -> String { match serde_json::from_value::(input.clone()) { Ok(input) => { - let src = MarkdownString::escape(&input.source_path); - let dest = MarkdownString::escape(&input.destination_path); + let src = MarkdownString::inline_code(&input.source_path); + let dest = MarkdownString::inline_code(&input.destination_path); let src_path = Path::new(&input.source_path); let dest_path = Path::new(&input.destination_path); @@ -71,11 +71,11 @@ impl Tool for MovePathTool { .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}`") + let filename = MarkdownString::inline_code(&filename); + format!("Rename {src} to {filename}") } _ => { - format!("Move `{src}` to `{dest}`") + format!("Move {src} to {dest}") } } } diff --git a/crates/assistant_tools/src/read_file_tool.rs b/crates/assistant_tools/src/read_file_tool.rs index e195bc1106..5a8e208eb4 100644 --- a/crates/assistant_tools/src/read_file_tool.rs +++ b/crates/assistant_tools/src/read_file_tool.rs @@ -66,8 +66,8 @@ impl Tool for ReadFileTool { fn ui_text(&self, input: &serde_json::Value) -> String { match serde_json::from_value::(input.clone()) { Ok(input) => { - let path = MarkdownString::escape(&input.path.display().to_string()); - format!("Read file `{path}`") + let path = MarkdownString::inline_code(&input.path.display().to_string()); + format!("Read file {path}") } Err(_) => "Read file".to_string(), } diff --git a/crates/assistant_tools/src/regex_search_tool.rs b/crates/assistant_tools/src/regex_search_tool.rs index db2fd1967f..635f5439c6 100644 --- a/crates/assistant_tools/src/regex_search_tool.rs +++ b/crates/assistant_tools/src/regex_search_tool.rs @@ -64,12 +64,12 @@ impl Tool for RegexSearchTool { match serde_json::from_value::(input.clone()) { Ok(input) => { let page = input.page(); - let regex = MarkdownString::escape(&input.regex); + let regex = MarkdownString::inline_code(&input.regex); if page > 1 { - format!("Get page {page} of search results for regex “`{regex}`”") + format!("Get page {page} of search results for regex “{regex}”") } else { - format!("Search files for regex “`{regex}`”") + format!("Search files for regex “{regex}”") } } Err(_) => "Search with regex".to_string(), diff --git a/crates/util/src/markdown.rs b/crates/util/src/markdown.rs index aa3c51d41e..39a6ef6b1d 100644 --- a/crates/util/src/markdown.rs +++ b/crates/util/src/markdown.rs @@ -11,7 +11,9 @@ impl Display for MarkdownString { } impl MarkdownString { - /// Escapes markdown special characters. + /// Escapes markdown special characters in markdown text blocks. Markdown code blocks follow + /// different rules and `MarkdownString::inline_code` or `MarkdownString::code_block` should be + /// used in that case. /// /// Also escapes the following markdown extensions: /// @@ -134,6 +136,12 @@ impl MarkdownString { Self(format!("{backticks}{space}{text}{space}{backticks}")) } } + + /// Returns markdown for code blocks, wrapped in 3 or more backticks as needed. + pub fn code_block(tag: &str, text: &str) -> Self { + let backticks = "`".repeat(3.max(count_max_consecutive_chars(text, '`') + 1)); + Self(format!("{backticks}{tag}\n{text}\n{backticks}\n")) + } } // Copied from `pulldown-cmark-to-cmark-20.0.0` with changed names.