Refactor markdown formatting utilities to avoid building intermediate strings (#29511)
These were nearly always used when using `format!` / `write!` etc, so it makes sense to not have an intermediate `String`. Release Notes: - N/A
This commit is contained in:
parent
6db974dd32
commit
609c528ceb
19 changed files with 154 additions and 137 deletions
|
@ -43,7 +43,7 @@ use ui::{
|
|||
Disclosure, IconButton, KeyBinding, Scrollbar, ScrollbarState, TextSize, Tooltip, prelude::*,
|
||||
};
|
||||
use util::ResultExt as _;
|
||||
use util::markdown::MarkdownString;
|
||||
use util::markdown::MarkdownCodeBlock;
|
||||
use workspace::Workspace;
|
||||
use zed_actions::assistant::OpenRulesLibrary;
|
||||
|
||||
|
@ -882,7 +882,11 @@ impl ActiveThread {
|
|||
});
|
||||
rendered.input.update(cx, |this, cx| {
|
||||
this.replace(
|
||||
MarkdownString::code_block("json", tool_input).to_string(),
|
||||
MarkdownCodeBlock {
|
||||
tag: "json",
|
||||
text: tool_input,
|
||||
}
|
||||
.to_string(),
|
||||
cx,
|
||||
);
|
||||
});
|
||||
|
|
|
@ -14,7 +14,7 @@ use regex::{Regex, RegexBuilder};
|
|||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use ui::IconName;
|
||||
use util::markdown::MarkdownString;
|
||||
use util::markdown::MarkdownInlineCode;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct CodeSymbolsInput {
|
||||
|
@ -102,7 +102,7 @@ impl Tool for CodeSymbolsTool {
|
|||
|
||||
match &input.path {
|
||||
Some(path) => {
|
||||
let path = MarkdownString::inline_code(path);
|
||||
let path = MarkdownInlineCode(path);
|
||||
if page > 1 {
|
||||
format!("List page {page} of code symbols for {path}")
|
||||
} else {
|
||||
|
|
|
@ -11,7 +11,7 @@ use schemars::JsonSchema;
|
|||
use serde::{Deserialize, Serialize};
|
||||
use std::{fmt::Write, path::Path};
|
||||
use ui::IconName;
|
||||
use util::markdown::MarkdownString;
|
||||
use util::markdown::MarkdownInlineCode;
|
||||
|
||||
/// If the model requests to read a file whose size exceeds this, then
|
||||
/// the tool will return the file's symbol outline instead of its contents,
|
||||
|
@ -82,7 +82,7 @@ impl Tool for ContentsTool {
|
|||
fn ui_text(&self, input: &serde_json::Value) -> String {
|
||||
match serde_json::from_value::<ContentsToolInput>(input.clone()) {
|
||||
Ok(input) => {
|
||||
let path = MarkdownString::inline_code(&input.path);
|
||||
let path = MarkdownInlineCode(&input.path);
|
||||
|
||||
match (input.start, input.end) {
|
||||
(Some(start), None) => format!("Read {path} (from line {start})"),
|
||||
|
|
|
@ -10,7 +10,7 @@ use schemars::JsonSchema;
|
|||
use serde::{Deserialize, Serialize};
|
||||
use std::sync::Arc;
|
||||
use ui::IconName;
|
||||
use util::markdown::MarkdownString;
|
||||
use util::markdown::MarkdownInlineCode;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct CopyPathToolInput {
|
||||
|
@ -63,8 +63,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 = MarkdownString::inline_code(&input.source_path);
|
||||
let dest = MarkdownString::inline_code(&input.destination_path);
|
||||
let src = MarkdownInlineCode(&input.source_path);
|
||||
let dest = MarkdownInlineCode(&input.destination_path);
|
||||
format!("Copy {src} to {dest}")
|
||||
}
|
||||
Err(_) => "Copy path".to_string(),
|
||||
|
|
|
@ -10,7 +10,7 @@ use schemars::JsonSchema;
|
|||
use serde::{Deserialize, Serialize};
|
||||
use std::sync::Arc;
|
||||
use ui::IconName;
|
||||
use util::markdown::MarkdownString;
|
||||
use util::markdown::MarkdownInlineCode;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct CreateDirectoryToolInput {
|
||||
|
@ -53,10 +53,7 @@ impl Tool for CreateDirectoryTool {
|
|||
fn ui_text(&self, input: &serde_json::Value) -> String {
|
||||
match serde_json::from_value::<CreateDirectoryToolInput>(input.clone()) {
|
||||
Ok(input) => {
|
||||
format!(
|
||||
"Create directory {}",
|
||||
MarkdownString::inline_code(&input.path)
|
||||
)
|
||||
format!("Create directory {}", MarkdownInlineCode(&input.path))
|
||||
}
|
||||
Err(_) => "Create directory".to_string(),
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ use schemars::JsonSchema;
|
|||
use serde::{Deserialize, Serialize};
|
||||
use std::sync::Arc;
|
||||
use ui::IconName;
|
||||
use util::markdown::MarkdownString;
|
||||
use util::markdown::MarkdownInlineCode;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct CreateFileToolInput {
|
||||
|
@ -73,7 +73,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 = MarkdownString::inline_code(&input.path);
|
||||
let path = MarkdownInlineCode(&input.path);
|
||||
format!("Create file {path}")
|
||||
}
|
||||
Err(_) => DEFAULT_UI_TEXT.to_string(),
|
||||
|
|
|
@ -9,7 +9,7 @@ use schemars::JsonSchema;
|
|||
use serde::{Deserialize, Serialize};
|
||||
use std::{fmt::Write, path::Path, sync::Arc};
|
||||
use ui::IconName;
|
||||
use util::markdown::MarkdownString;
|
||||
use util::markdown::MarkdownInlineCode;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct DiagnosticsToolInput {
|
||||
|
@ -66,11 +66,11 @@ impl Tool for DiagnosticsTool {
|
|||
if let Some(path) = serde_json::from_value::<DiagnosticsToolInput>(input.clone())
|
||||
.ok()
|
||||
.and_then(|input| match input.path {
|
||||
Some(path) if !path.is_empty() => Some(MarkdownString::inline_code(&path)),
|
||||
Some(path) if !path.is_empty() => Some(path),
|
||||
_ => None,
|
||||
})
|
||||
{
|
||||
format!("Check diagnostics for {path}")
|
||||
format!("Check diagnostics for {}", MarkdownInlineCode(&path))
|
||||
} else {
|
||||
"Check project diagnostics".to_string()
|
||||
}
|
||||
|
|
|
@ -14,7 +14,7 @@ use project::Project;
|
|||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use ui::IconName;
|
||||
use util::markdown::MarkdownString;
|
||||
use util::markdown::MarkdownEscaped;
|
||||
|
||||
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Clone, Copy)]
|
||||
enum ContentType {
|
||||
|
@ -134,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 {}", MarkdownString::escape(&input.url)),
|
||||
Ok(input) => format!("Fetch {}", MarkdownEscaped(&input.url)),
|
||||
Err(_) => "Fetch URL".to_string(),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ use schemars::JsonSchema;
|
|||
use serde::{Deserialize, Serialize};
|
||||
use std::{cmp, fmt::Write, sync::Arc};
|
||||
use ui::IconName;
|
||||
use util::markdown::MarkdownString;
|
||||
use util::markdown::MarkdownInlineCode;
|
||||
use util::paths::PathMatcher;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
||||
|
@ -75,7 +75,7 @@ impl Tool for GrepTool {
|
|||
match serde_json::from_value::<GrepToolInput>(input.clone()) {
|
||||
Ok(input) => {
|
||||
let page = input.page();
|
||||
let regex_str = MarkdownString::inline_code(&input.regex);
|
||||
let regex_str = MarkdownInlineCode(&input.regex);
|
||||
let case_info = if input.case_sensitive {
|
||||
" (case-sensitive)"
|
||||
} else {
|
||||
|
|
|
@ -8,7 +8,7 @@ use schemars::JsonSchema;
|
|||
use serde::{Deserialize, Serialize};
|
||||
use std::{fmt::Write, path::Path, sync::Arc};
|
||||
use ui::IconName;
|
||||
use util::markdown::MarkdownString;
|
||||
use util::markdown::MarkdownInlineCode;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct ListDirectoryToolInput {
|
||||
|
@ -63,7 +63,7 @@ impl Tool for ListDirectoryTool {
|
|||
fn ui_text(&self, input: &serde_json::Value) -> String {
|
||||
match serde_json::from_value::<ListDirectoryToolInput>(input.clone()) {
|
||||
Ok(input) => {
|
||||
let path = MarkdownString::inline_code(&input.path);
|
||||
let path = MarkdownInlineCode(&input.path);
|
||||
format!("List the {path} directory's contents")
|
||||
}
|
||||
Err(_) => "List directory".to_string(),
|
||||
|
|
|
@ -8,7 +8,7 @@ use schemars::JsonSchema;
|
|||
use serde::{Deserialize, Serialize};
|
||||
use std::{path::Path, sync::Arc};
|
||||
use ui::IconName;
|
||||
use util::markdown::MarkdownString;
|
||||
use util::markdown::MarkdownInlineCode;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct MovePathToolInput {
|
||||
|
@ -61,8 +61,8 @@ 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 = MarkdownString::inline_code(&input.source_path);
|
||||
let dest = MarkdownString::inline_code(&input.destination_path);
|
||||
let src = MarkdownInlineCode(&input.source_path);
|
||||
let dest = MarkdownInlineCode(&input.destination_path);
|
||||
let src_path = Path::new(&input.source_path);
|
||||
let dest_path = Path::new(&input.destination_path);
|
||||
|
||||
|
@ -71,7 +71,7 @@ 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::inline_code(&filename);
|
||||
let filename = MarkdownInlineCode(&filename);
|
||||
format!("Rename {src} to {filename}")
|
||||
}
|
||||
_ => {
|
||||
|
|
|
@ -8,7 +8,7 @@ use schemars::JsonSchema;
|
|||
use serde::{Deserialize, Serialize};
|
||||
use std::sync::Arc;
|
||||
use ui::IconName;
|
||||
use util::markdown::MarkdownString;
|
||||
use util::markdown::MarkdownEscaped;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct OpenToolInput {
|
||||
|
@ -41,7 +41,7 @@ impl Tool for OpenTool {
|
|||
|
||||
fn ui_text(&self, input: &serde_json::Value) -> String {
|
||||
match serde_json::from_value::<OpenToolInput>(input.clone()) {
|
||||
Ok(input) => format!("Open `{}`", MarkdownString::escape(&input.path_or_url)),
|
||||
Ok(input) => format!("Open `{}`", MarkdownEscaped(&input.path_or_url)),
|
||||
Err(_) => "Open file or URL".to_string(),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ use schemars::JsonSchema;
|
|||
use serde::{Deserialize, Serialize};
|
||||
use std::sync::Arc;
|
||||
use ui::IconName;
|
||||
use util::markdown::MarkdownString;
|
||||
use util::markdown::MarkdownInlineCode;
|
||||
|
||||
/// If the model requests to read a file whose size exceeds this, then
|
||||
/// the tool will return an error along with the model's symbol outline,
|
||||
|
@ -71,7 +71,7 @@ impl Tool for ReadFileTool {
|
|||
fn ui_text(&self, input: &serde_json::Value) -> String {
|
||||
match serde_json::from_value::<ReadFileToolInput>(input.clone()) {
|
||||
Ok(input) => {
|
||||
let path = MarkdownString::inline_code(&input.path);
|
||||
let path = MarkdownInlineCode(&input.path);
|
||||
match (input.start_line, input.end_line) {
|
||||
(Some(start), None) => format!("Read file {path} (from line {start})"),
|
||||
(Some(start), Some(end)) => format!("Read file {path} (lines {start}-{end})"),
|
||||
|
|
|
@ -8,7 +8,7 @@ use schemars::JsonSchema;
|
|||
use serde::{Deserialize, Serialize};
|
||||
use std::{fmt::Write, ops::Range, sync::Arc};
|
||||
use ui::IconName;
|
||||
use util::markdown::MarkdownString;
|
||||
use util::markdown::MarkdownInlineCode;
|
||||
|
||||
use crate::schema::json_schema_for;
|
||||
|
||||
|
@ -91,7 +91,7 @@ impl Tool for SymbolInfoTool {
|
|||
fn ui_text(&self, input: &serde_json::Value) -> String {
|
||||
match serde_json::from_value::<SymbolInfoToolInput>(input.clone()) {
|
||||
Ok(input) => {
|
||||
let symbol = MarkdownString::inline_code(&input.symbol);
|
||||
let symbol = MarkdownInlineCode(&input.symbol);
|
||||
|
||||
match input.command {
|
||||
Info::Definition => {
|
||||
|
|
|
@ -15,7 +15,7 @@ use std::path::Path;
|
|||
use std::sync::Arc;
|
||||
use ui::IconName;
|
||||
use util::command::new_smol_command;
|
||||
use util::markdown::MarkdownString;
|
||||
use util::markdown::MarkdownInlineCode;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct TerminalToolInput {
|
||||
|
@ -55,17 +55,14 @@ impl Tool for TerminalTool {
|
|||
let first_line = lines.next().unwrap_or_default();
|
||||
let remaining_line_count = lines.count();
|
||||
match remaining_line_count {
|
||||
0 => MarkdownString::inline_code(&first_line).0,
|
||||
1 => {
|
||||
MarkdownString::inline_code(&format!(
|
||||
0 => MarkdownInlineCode(&first_line).to_string(),
|
||||
1 => MarkdownInlineCode(&format!(
|
||||
"{} - {} more line",
|
||||
first_line, remaining_line_count
|
||||
))
|
||||
.0
|
||||
}
|
||||
n => {
|
||||
MarkdownString::inline_code(&format!("{} - {} more lines", first_line, n)).0
|
||||
}
|
||||
.to_string(),
|
||||
n => MarkdownInlineCode(&format!("{} - {} more lines", first_line, n))
|
||||
.to_string(),
|
||||
}
|
||||
}
|
||||
Err(_) => "Run terminal command".to_string(),
|
||||
|
|
|
@ -27,7 +27,7 @@ use std::time::Duration;
|
|||
use unindent::Unindent as _;
|
||||
use util::ResultExt as _;
|
||||
use util::command::new_smol_command;
|
||||
use util::markdown::MarkdownString;
|
||||
use util::markdown::MarkdownCodeBlock;
|
||||
|
||||
use crate::assertions::{AssertionsReport, RanAssertion, RanAssertionResult};
|
||||
use crate::example::{Example, ExampleContext, FailedAssertion, JudgeAssertion};
|
||||
|
@ -863,7 +863,10 @@ impl RequestMarkdown {
|
|||
write!(
|
||||
&mut tools,
|
||||
"{}\n",
|
||||
MarkdownString::code_block("json", &format!("{:#}", tool.input_schema))
|
||||
MarkdownCodeBlock {
|
||||
tag: "json",
|
||||
text: &format!("{:#}", tool.input_schema)
|
||||
}
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
@ -910,7 +913,10 @@ impl RequestMarkdown {
|
|||
));
|
||||
messages.push_str(&format!(
|
||||
"{}\n",
|
||||
MarkdownString::code_block("json", &format!("{:#}", tool_use.input))
|
||||
MarkdownCodeBlock {
|
||||
tag: "json",
|
||||
text: &format!("{:#}", tool_use.input)
|
||||
}
|
||||
));
|
||||
}
|
||||
MessageContent::ToolResult(tool_result) => {
|
||||
|
@ -972,7 +978,10 @@ pub fn response_events_to_markdown(
|
|||
));
|
||||
response.push_str(&format!(
|
||||
"{}\n",
|
||||
MarkdownString::code_block("json", &format!("{:#}", tool_use.input))
|
||||
MarkdownCodeBlock {
|
||||
tag: "json",
|
||||
text: &format!("{:#}", tool_use.input)
|
||||
}
|
||||
));
|
||||
}
|
||||
Ok(
|
||||
|
|
|
@ -61,7 +61,7 @@ use serde_json::Value;
|
|||
use settings::Settings;
|
||||
use theme::ThemeSettings;
|
||||
use ui::{IntoElement, Styled, div, prelude::*, v_flex};
|
||||
use util::markdown::MarkdownString;
|
||||
use util::markdown::MarkdownEscaped;
|
||||
|
||||
use crate::outputs::OutputContent;
|
||||
|
||||
|
@ -170,7 +170,7 @@ impl TableView {
|
|||
let row_content = schema
|
||||
.fields
|
||||
.iter()
|
||||
.map(|field| MarkdownString::escape(&cell_content(record, &field.name)).0)
|
||||
.map(|field| MarkdownEscaped(&cell_content(record, &field.name)).to_string())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
row_content.join(" | ")
|
||||
|
|
|
@ -13,7 +13,10 @@ use schemars::{
|
|||
use serde::Deserialize;
|
||||
use serde_json::Value;
|
||||
use std::{any::TypeId, fmt::Write, rc::Rc, sync::Arc, sync::LazyLock};
|
||||
use util::{asset_str, markdown::MarkdownString};
|
||||
use util::{
|
||||
asset_str,
|
||||
markdown::{MarkdownEscaped, MarkdownInlineCode, MarkdownString},
|
||||
};
|
||||
|
||||
use crate::{SettingsAssets, settings_store::parse_json_with_comments};
|
||||
|
||||
|
@ -152,7 +155,7 @@ impl KeymapFile {
|
|||
match Self::load(asset_str::<SettingsAssets>(asset_path).as_ref(), cx) {
|
||||
KeymapFileLoadResult::Success { key_bindings } => Ok(key_bindings),
|
||||
KeymapFileLoadResult::SomeFailedToLoad { error_message, .. } => Err(anyhow!(
|
||||
"Error loading built-in keymap \"{asset_path}\": {error_message}"
|
||||
"Error loading built-in keymap \"{asset_path}\": {error_message}",
|
||||
)),
|
||||
KeymapFileLoadResult::JsonParseFailure { error } => Err(anyhow!(
|
||||
"JSON parse error in built-in keymap \"{asset_path}\": {error}"
|
||||
|
@ -171,7 +174,7 @@ impl KeymapFile {
|
|||
error_message,
|
||||
..
|
||||
} if key_bindings.is_empty() => Err(anyhow!(
|
||||
"Error loading built-in keymap \"{asset_path}\": {error_message}"
|
||||
"Error loading built-in keymap \"{asset_path}\": {error_message}",
|
||||
)),
|
||||
KeymapFileLoadResult::Success { key_bindings, .. }
|
||||
| KeymapFileLoadResult::SomeFailedToLoad { key_bindings, .. } => Ok(key_bindings),
|
||||
|
@ -251,7 +254,7 @@ impl KeymapFile {
|
|||
write!(
|
||||
section_errors,
|
||||
"\n\n - Unrecognized fields: {}",
|
||||
MarkdownString::inline_code(&format!("{:?}", unrecognized_fields.keys()))
|
||||
MarkdownInlineCode(&format!("{:?}", unrecognized_fields.keys()))
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
@ -280,7 +283,7 @@ impl KeymapFile {
|
|||
write!(
|
||||
section_errors,
|
||||
"\n\n- In binding {}, {indented_err}",
|
||||
inline_code_string(keystrokes),
|
||||
MarkdownInlineCode(&format!("\"{}\"", keystrokes))
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
@ -299,16 +302,15 @@ impl KeymapFile {
|
|||
let mut error_message = "Errors in user keymap file.\n".to_owned();
|
||||
for (context, section_errors) in errors {
|
||||
if context.is_empty() {
|
||||
write!(error_message, "\n\nIn section without context predicate:").unwrap()
|
||||
let _ = write!(error_message, "\n\nIn section without context predicate:");
|
||||
} else {
|
||||
write!(
|
||||
let _ = write!(
|
||||
error_message,
|
||||
"\n\nIn section with {}:",
|
||||
MarkdownString::inline_code(&format!("context = \"{}\"", context))
|
||||
)
|
||||
.unwrap()
|
||||
MarkdownInlineCode(&format!("context = \"{}\"", context))
|
||||
);
|
||||
}
|
||||
write!(error_message, "{section_errors}").unwrap();
|
||||
let _ = write!(error_message, "{section_errors}");
|
||||
}
|
||||
KeymapFileLoadResult::SomeFailedToLoad {
|
||||
key_bindings,
|
||||
|
@ -330,14 +332,14 @@ impl KeymapFile {
|
|||
return Err(format!(
|
||||
"expected two-element array of `[name, input]`. \
|
||||
Instead found {}.",
|
||||
MarkdownString::inline_code(&action.0.to_string())
|
||||
MarkdownInlineCode(&action.0.to_string())
|
||||
));
|
||||
}
|
||||
let serde_json::Value::String(ref name) = items[0] else {
|
||||
return Err(format!(
|
||||
"expected two-element array of `[name, input]`, \
|
||||
but the first element is not a string in {}.",
|
||||
MarkdownString::inline_code(&action.0.to_string())
|
||||
MarkdownInlineCode(&action.0.to_string())
|
||||
));
|
||||
};
|
||||
let action_input = items[1].clone();
|
||||
|
@ -353,7 +355,7 @@ impl KeymapFile {
|
|||
return Err(format!(
|
||||
"expected two-element array of `[name, input]`. \
|
||||
Instead found {}.",
|
||||
MarkdownString::inline_code(&action.0.to_string())
|
||||
MarkdownInlineCode(&action.0.to_string())
|
||||
));
|
||||
}
|
||||
};
|
||||
|
@ -363,23 +365,23 @@ impl KeymapFile {
|
|||
Err(ActionBuildError::NotFound { name }) => {
|
||||
return Err(format!(
|
||||
"didn't find an action named {}.",
|
||||
inline_code_string(&name)
|
||||
MarkdownInlineCode(&format!("\"{}\"", &name))
|
||||
));
|
||||
}
|
||||
Err(ActionBuildError::BuildError { name, error }) => match action_input_string {
|
||||
Some(action_input_string) => {
|
||||
return Err(format!(
|
||||
"can't build {} action from input value {}: {}",
|
||||
inline_code_string(&name),
|
||||
MarkdownString::inline_code(&action_input_string),
|
||||
MarkdownString::escape(&error.to_string())
|
||||
MarkdownInlineCode(&format!("\"{}\"", &name)),
|
||||
MarkdownInlineCode(&action_input_string),
|
||||
MarkdownEscaped(&error.to_string())
|
||||
));
|
||||
}
|
||||
None => {
|
||||
return Err(format!(
|
||||
"can't build {} action - it requires input data via [name, input]: {}",
|
||||
inline_code_string(&name),
|
||||
MarkdownString::escape(&error.to_string())
|
||||
MarkdownInlineCode(&format!("\"{}\"", &name)),
|
||||
MarkdownEscaped(&error.to_string())
|
||||
));
|
||||
}
|
||||
},
|
||||
|
@ -390,7 +392,7 @@ impl KeymapFile {
|
|||
Err(InvalidKeystrokeError { keystroke }) => {
|
||||
return Err(format!(
|
||||
"invalid keystroke {}. {}",
|
||||
inline_code_string(&keystroke),
|
||||
MarkdownInlineCode(&format!("\"{}\"", &keystroke)),
|
||||
KEYSTROKE_PARSE_EXPECTED_MESSAGE
|
||||
));
|
||||
}
|
||||
|
@ -606,11 +608,6 @@ impl KeymapFile {
|
|||
}
|
||||
}
|
||||
|
||||
// Double quotes a string and wraps it in backticks for markdown inline code..
|
||||
fn inline_code_string(text: &str) -> MarkdownString {
|
||||
MarkdownString::inline_code(&format!("\"{}\"", text))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::KeymapFile;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use std::fmt::{Display, Formatter};
|
||||
|
||||
/// Markdown text.
|
||||
/// Indicates that the wrapped `String` is markdown text.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MarkdownString(pub String);
|
||||
|
||||
|
@ -10,31 +10,45 @@ impl Display for MarkdownString {
|
|||
}
|
||||
}
|
||||
|
||||
impl MarkdownString {
|
||||
/// 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:
|
||||
///
|
||||
/// * `^` for superscripts
|
||||
/// * `$` for inline math
|
||||
/// * `~` for strikethrough
|
||||
///
|
||||
/// 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
|
||||
/// plaintext.
|
||||
///
|
||||
/// * `;` is used in HTML entity syntax, but `&` is escaped, so they are parsed as plaintext.
|
||||
///
|
||||
/// TODO: There is one escape this doesn't do currently. Period after numbers at the start of the
|
||||
/// line (`[0-9]*\.`) should also be escaped to avoid it being interpreted as a list item.
|
||||
pub fn escape(text: &str) -> Self {
|
||||
let mut chunks = Vec::new();
|
||||
/// Escapes markdown special characters in markdown text blocks. Markdown code blocks follow
|
||||
/// different rules and `MarkdownInlineCode` or `MarkdownCodeBlock` should be used in that case.
|
||||
///
|
||||
/// Also escapes the following markdown extensions:
|
||||
///
|
||||
/// * `^` for superscripts
|
||||
/// * `$` for inline math
|
||||
/// * `~` for strikethrough
|
||||
///
|
||||
/// 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
|
||||
/// plaintext.
|
||||
///
|
||||
/// * `;` is used in HTML entity syntax, but `&` is escaped, so they are parsed as plaintext.
|
||||
///
|
||||
/// TODO: There is one escape this doesn't do currently. Period after numbers at the start of the
|
||||
/// line (`[0-9]*\.`) should also be escaped to avoid it being interpreted as a list item.
|
||||
pub struct MarkdownEscaped<'a>(pub &'a str);
|
||||
|
||||
/// Implements `Display` to format markdown inline code (wrapped in backticks), handling code that
|
||||
/// contains backticks and spaces. All whitespace is treated as a single space character. For text
|
||||
/// that does not contain whitespace other than ' ', this escaping roundtrips through
|
||||
/// pulldown-cmark.
|
||||
///
|
||||
/// When used in tables, `|` should be escaped like `\|` in the text provided to this function.
|
||||
pub struct MarkdownInlineCode<'a>(pub &'a str);
|
||||
|
||||
/// Implements `Display` to format markdown code blocks, wrapped in 3 or more backticks as needed.
|
||||
pub struct MarkdownCodeBlock<'a> {
|
||||
pub tag: &'a str,
|
||||
pub text: &'a str,
|
||||
}
|
||||
|
||||
impl Display for MarkdownEscaped<'_> {
|
||||
fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
let mut start_of_unescaped = None;
|
||||
for (ix, c) in text.char_indices() {
|
||||
for (ix, c) in self.0.char_indices() {
|
||||
match c {
|
||||
// Always escaped.
|
||||
'\\' | '`' | '*' | '_' | '[' | '^' | '$' | '~' | '&' |
|
||||
|
@ -45,10 +59,10 @@ impl MarkdownString {
|
|||
match start_of_unescaped {
|
||||
None => {}
|
||||
Some(start_of_unescaped) => {
|
||||
chunks.push(&text[start_of_unescaped..ix]);
|
||||
write!(formatter, "{}", &self.0[start_of_unescaped..ix])?;
|
||||
}
|
||||
}
|
||||
chunks.push("\\");
|
||||
write!(formatter, "\\")?;
|
||||
// Can include this char in the "unescaped" text since a
|
||||
// backslash was just emitted.
|
||||
start_of_unescaped = Some(ix);
|
||||
|
@ -59,10 +73,10 @@ impl MarkdownString {
|
|||
match start_of_unescaped {
|
||||
None => {}
|
||||
Some(start_of_unescaped) => {
|
||||
chunks.push(&text[start_of_unescaped..ix]);
|
||||
write!(formatter, "{}", &self.0[start_of_unescaped..ix])?;
|
||||
}
|
||||
}
|
||||
chunks.push("<");
|
||||
write!(formatter, "<")?;
|
||||
start_of_unescaped = None;
|
||||
}
|
||||
// Escaped since `>` is used for blockquotes. `>` is used since Markdown supports
|
||||
|
@ -71,10 +85,10 @@ impl MarkdownString {
|
|||
match start_of_unescaped {
|
||||
None => {}
|
||||
Some(start_of_unescaped) => {
|
||||
chunks.push(&text[start_of_unescaped..ix]);
|
||||
write!(formatter, "{}", &self.0[start_of_unescaped..ix])?;
|
||||
}
|
||||
}
|
||||
chunks.push("gt;");
|
||||
write!(formatter, ">")?;
|
||||
start_of_unescaped = None;
|
||||
}
|
||||
_ => {
|
||||
|
@ -85,17 +99,14 @@ impl MarkdownString {
|
|||
}
|
||||
}
|
||||
if let Some(start_of_unescaped) = start_of_unescaped {
|
||||
chunks.push(&text[start_of_unescaped..])
|
||||
write!(formatter, "{}", &self.0[start_of_unescaped..])?;
|
||||
}
|
||||
Self(chunks.concat())
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns markdown for inline code (wrapped in backticks), handling code that contains backticks
|
||||
/// and spaces. All whitespace is treated as a single space character. For text that does not
|
||||
/// contain whitespace other than ' ', this escaping roundtrips through pulldown-cmark.
|
||||
///
|
||||
/// When used in tables, `|` should be escaped like `\|` in the text provided to this function.
|
||||
pub fn inline_code(text: &str) -> Self {
|
||||
impl Display for MarkdownInlineCode<'_> {
|
||||
fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
// Apache License 2.0, same as this crate.
|
||||
//
|
||||
// Copied from `pulldown-cmark-to-cmark-20.0.0` with modifications:
|
||||
|
@ -103,12 +114,11 @@ impl MarkdownString {
|
|||
// * Handling of all whitespace. pulldown-cmark-to-cmark is anticipating
|
||||
// `Code` events parsed by pulldown-cmark.
|
||||
//
|
||||
// * Direct return of string.
|
||||
//
|
||||
// https://github.com/Byron/pulldown-cmark-to-cmark/blob/3c850de2d3d1d79f19ca5f375e1089a653cf3ff7/src/lib.rs#L290
|
||||
|
||||
let mut all_whitespace = true;
|
||||
let text = text
|
||||
let text = self
|
||||
.0
|
||||
.chars()
|
||||
.map(|c| {
|
||||
if c.is_whitespace() {
|
||||
|
@ -123,7 +133,7 @@ impl MarkdownString {
|
|||
// When inline code has leading and trailing ' ' characters, additional space is needed
|
||||
// to escape it, unless all characters are space.
|
||||
if all_whitespace {
|
||||
Self(format!("`{text}`"))
|
||||
write!(formatter, "`{text}`")
|
||||
} else {
|
||||
// More backticks are needed to delimit the inline code than the maximum number of
|
||||
// backticks in a consecutive run.
|
||||
|
@ -133,14 +143,17 @@ impl MarkdownString {
|
|||
&[b' ', .., b' '] => " ", // Space needed to escape inner space.
|
||||
_ => "", // No space needed.
|
||||
};
|
||||
Self(format!("{backticks}{space}{text}{space}{backticks}"))
|
||||
write!(formatter, "{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 {
|
||||
impl Display for MarkdownCodeBlock<'_> {
|
||||
fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
let tag = self.tag;
|
||||
let text = self.text;
|
||||
let backticks = "`".repeat(3.max(count_max_consecutive_chars(text, '`') + 1));
|
||||
Self(format!("{backticks}{tag}\n{text}\n{backticks}\n"))
|
||||
write!(formatter, "{backticks}{tag}\n{text}\n{backticks}\n")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -170,7 +183,7 @@ mod tests {
|
|||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_markdown_string_escape() {
|
||||
fn test_markdown_escaped() {
|
||||
let input = r#"
|
||||
# Heading
|
||||
|
||||
|
@ -221,20 +234,20 @@ mod tests {
|
|||
HTML entity: \
|
||||
"#;
|
||||
|
||||
assert_eq!(MarkdownString::escape(input).0, expected);
|
||||
assert_eq!(MarkdownEscaped(input).to_string(), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_markdown_string_inline_code() {
|
||||
assert_eq!(MarkdownString::inline_code(" ").0, "` `");
|
||||
assert_eq!(MarkdownString::inline_code("text").0, "`text`");
|
||||
assert_eq!(MarkdownString::inline_code("text ").0, "`text `");
|
||||
assert_eq!(MarkdownString::inline_code(" text ").0, "` text `");
|
||||
assert_eq!(MarkdownString::inline_code("`").0, "`` ` ``");
|
||||
assert_eq!(MarkdownString::inline_code("``").0, "``` `` ```");
|
||||
assert_eq!(MarkdownString::inline_code("`text`").0, "`` `text` ``");
|
||||
fn test_markdown_inline_code() {
|
||||
assert_eq!(MarkdownInlineCode(" ").to_string(), "` `");
|
||||
assert_eq!(MarkdownInlineCode("text").to_string(), "`text`");
|
||||
assert_eq!(MarkdownInlineCode("text ").to_string(), "`text `");
|
||||
assert_eq!(MarkdownInlineCode(" text ").to_string(), "` text `");
|
||||
assert_eq!(MarkdownInlineCode("`").to_string(), "`` ` ``");
|
||||
assert_eq!(MarkdownInlineCode("``").to_string(), "``` `` ```");
|
||||
assert_eq!(MarkdownInlineCode("`text`").to_string(), "`` `text` ``");
|
||||
assert_eq!(
|
||||
MarkdownString::inline_code("some `text` no leading or trailing backticks").0,
|
||||
MarkdownInlineCode("some `text` no leading or trailing backticks").to_string(),
|
||||
"``some `text` no leading or trailing backticks``"
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue