Merge pull request #7171 from Lstarsky0/fix/parse-markup-inside-emphasis

fix(tui): render code and links inside emphasis
This commit is contained in:
Hunter Bown authored and GitHub committed 2026-11-01 03:48:48 +08:00
commit b2db2c0467
5 files changed
+15 -19

No files matched your search

+1 -1
View File
@@ -124,7 +124,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed
- **Notes stop swallowing a backslash:** a transcript note is machine-generated and names paths, but it went through the markdown renderer, where CommonMark §2.4 reads a backslash before ASCII punctuation as an escape. A note naming `C:\Users\asus\.codewhale\setup_state.json` therefore printed as `C:\Users\asus.codewhale\setup_state.json` — a directory that does not exist, in the one line a user would copy to find the real one. Notes are now escaped where they are rendered. Fenced blocks, and lines carrying inline code, emphasis or a table row, are passed through untouched, because the renderer already copies those verbatim and doubling a backslash inside one would print it doubled.
- **Notes stop swallowing a backslash:** a transcript note is machine-generated and names paths, but it went through the markdown renderer, where CommonMark §2.4 reads a backslash before ASCII punctuation as an escape. A note naming `C:\Users\asus\.codewhale\setup_state.json` therefore printed as `C:\Users\asus.codewhale\setup_state.json` — a directory that does not exist, in the one line a user would copy to find the real one. Notes are now escaped where they are rendered. Fenced blocks, and lines carrying inline code or a table row, are passed through untouched, because the renderer already copies those verbatim and doubling a backslash inside one would print it doubled.
- **Setup state survives a second instance:** two running Codewhale windows share one home, and both rewrite `setup_state.json` when the provider or model changes. The replacing rename is now retried briefly when the destination is held open for a read — Windows refuses it outright while that handle lives — instead of surfacing a write the caller could have made. The failure message also carries the underlying I/O error instead of only the outer context, so the next occurrence is diagnosable.
- **`doctor` stops flagging a user's own temp-named logs:** the `stale_setup_temp_files` check matched any file whose name starts with `.tmp`, so scratch files such as `.tmp-build-release.log` made a clean home report as half-applied. It now matches the shape a staged write leaves when it never lands (`.tmpXXXXXX`, no extension).
+5 -5
View File
@@ -1777,7 +1777,7 @@ fn parse_inline_spans(line: &str, base_style: Style, link_style: Style) -> Vec<I
if let Some(end) = rest.strip_prefix("**").and_then(|s| s.find("**")) {
let inner = &rest[2..2 + end];
if !inner.starts_with(char::is_whitespace) && !inner.ends_with(char::is_whitespace) {
out.push(InlineToken::new(inner.to_string(), bold_style, None));
out.extend(parse_inline_spans(inner, bold_style, link_style));
rest = &rest[2 + end + 2..];
continue;
}
@@ -1785,7 +1785,7 @@ fn parse_inline_spans(line: &str, base_style: Style, link_style: Style) -> Vec<I
// __bold__
if let Some(end) = rest.strip_prefix("__").and_then(|s| s.find("__")) {
let inner = &rest[2..2 + end];
out.push(InlineToken::new(inner.to_string(), bold_style, None));
out.extend(parse_inline_spans(inner, bold_style, link_style));
rest = &rest[2 + end + 2..];
continue;
}
@@ -1803,7 +1803,7 @@ fn parse_inline_spans(line: &str, base_style: Style, link_style: Style) -> Vec<I
&& !inner.ends_with(char::is_whitespace)
&& !after.starts_with(|c: char| c.is_alphanumeric() || c == '_')
{
out.push(InlineToken::new(inner.to_string(), italic_style, None));
out.extend(parse_inline_spans(inner, italic_style, link_style));
rest = after;
continue;
}
@@ -1828,7 +1828,7 @@ fn parse_inline_spans(line: &str, base_style: Style, link_style: Style) -> Vec<I
// Closing delimiter must not be immediately followed by a
// letter, digit, or underscore.
if openable && !after.starts_with(|c: char| c.is_alphanumeric() || c == '_') {
out.push(InlineToken::new(inner.to_string(), italic_style, None));
out.extend(parse_inline_spans(inner, italic_style, link_style));
rest = after;
continue;
}
@@ -1843,7 +1843,7 @@ fn parse_inline_spans(line: &str, base_style: Style, link_style: Style) -> Vec<I
// ~~strikethrough~~
if let Some(end) = rest.strip_prefix("~~").and_then(|s| s.find("~~")) {
let inner = &rest[2..2 + end];
out.push(InlineToken::new(inner.to_string(), strike_style, None));
out.extend(parse_inline_spans(inner, strike_style, link_style));
rest = &rest[2 + end + 2..];
continue;
}
+1 -1
View File
@@ -124,7 +124,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed
- **Notes stop swallowing a backslash:** a transcript note is machine-generated and names paths, but it went through the markdown renderer, where CommonMark §2.4 reads a backslash before ASCII punctuation as an escape. A note naming `C:\Users\asus\.codewhale\setup_state.json` therefore printed as `C:\Users\asus.codewhale\setup_state.json` — a directory that does not exist, in the one line a user would copy to find the real one. Notes are now escaped where they are rendered. Fenced blocks, and lines carrying inline code, emphasis or a table row, are passed through untouched, because the renderer already copies those verbatim and doubling a backslash inside one would print it doubled.
- **Notes stop swallowing a backslash:** a transcript note is machine-generated and names paths, but it went through the markdown renderer, where CommonMark §2.4 reads a backslash before ASCII punctuation as an escape. A note naming `C:\Users\asus\.codewhale\setup_state.json` therefore printed as `C:\Users\asus.codewhale\setup_state.json` — a directory that does not exist, in the one line a user would copy to find the real one. Notes are now escaped where they are rendered. Fenced blocks, and lines carrying inline code or a table row, are passed through untouched, because the renderer already copies those verbatim and doubling a backslash inside one would print it doubled.
- **Setup state survives a second instance:** two running Codewhale windows share one home, and both rewrite `setup_state.json` when the provider or model changes. The replacing rename is now retried briefly when the destination is held open for a read — Windows refuses it outright while that handle lives — instead of surfacing a write the caller could have made. The failure message also carries the underlying I/O error instead of only the outer context, so the next occurrence is diagnosable.
- **`doctor` stops flagging a user's own temp-named logs:** the `stale_setup_temp_files` check matched any file whose name starts with `.tmp`, so scratch files such as `.tmp-build-release.log` made a clean home report as half-applied. It now matches the shape a staged write leaves when it never lands (`.tmpXXXXXX`, no extension).
+3 -8
View File
@@ -365,9 +365,8 @@ pub(super) fn system_body_style() -> Style {
/// backslash:
///
/// - a fenced block, closed only by a run at least as long as its opener;
/// - a line carrying an inline delimiter the renderer treats as a passthrough
/// (`` ` ``, `**`, `~~`) — inline code, bold and strikethrough are the shapes
/// a note's third-party text (MCP guidance, extension prompts) brings with it;
/// - a line carrying inline code (`` ` ``), a shape a note's third-party text
/// (MCP guidance, extension prompts) brings with it;
/// - a table row.
///
/// A line that is both prose and delimited keeps today's behavior rather than
@@ -419,9 +418,5 @@ fn carries_verbatim_inline_markdown(line: &str) -> bool {
if trimmed.starts_with('|') && trimmed.ends_with('|') && trimmed.len() > 1 {
return true;
}
// `__bold__` and `_italic_` are deliberately absent: `_` is everywhere in
// identifiers (`setup_state.json`), and a false positive there would leave
// the paths this pass exists to protect untouched. `*` covers both the bold
// and italic forms and is rare enough in a machine note to keep this cheap.
line.contains('`') || line.contains('*') || line.contains("~~")
line.contains('`')
}
+5 -4
View File
@@ -177,10 +177,11 @@ fn a_system_note_leaves_a_fenced_block_verbatim() {
#[test]
fn a_system_note_leaves_inline_markup_and_table_rows_verbatim() {
// The renderer copies inline code, bold, strikethrough and table cells
// verbatim, so doubling a backslash inside one prints it doubled. These are
// the shapes third-party guidance brings into a note (MCP servers,
// extension prompts), and they already render a Windows path correctly.
// The renderer copies inline code and table cells verbatim, so doubling a
// backslash inside one prints it doubled; bold and strikethrough read a
// doubled backslash back as one. These are the shapes third-party guidance
// brings into a note (MCP servers, extension prompts), and each renders a
// Windows path correctly.
for content in [
r"see `C:\Users\asus\.codewhale` for the file".to_string(),
r"**C:\Users\asus** is the home".to_string(),