feat(markdown): render AI translation results with formatting#1161
feat(markdown): render AI translation results with formatting#1161leozanee wants to merge 4 commits into
Conversation
Render Markdown source from streaming AI/LLM services as styled NSAttributedString in result cells: headings, emphasis, blockquotes, lists, fenced/inline code, and links. Plain-text services (Google, Bing, DeepL, Youdao) keep their existing appearance. Add a global toggle in Settings → Display and a per-card icon button with fade transition for inline override.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 43b9d20b83
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
- Mark `EZLabel.text` as nullable to reflect the runtime contract: the ivar is nil during super init when font / line-spacing setters trigger `updateDisplayedText`, which previously would trap Swift bridging. - Require word boundaries around `_` in italic emphasis so identifiers like `foo_bar_baz` keep literal underscores instead of italicizing the middle segment. `*` is unchanged. - Cover both with new MarkdownRenderer tests.
|
Thanks your PR, we will review it soon |
Try a `***...***` match before the existing `**...**` bold check so the outer pair of stars does not bind first and leak a stray `*` as literal. Inner content is rendered with both bold and italic font traits applied via `NSFontManager`. Reported by @MoonMao42 in tisfeng#1161.
|
Fixed in 2a45aae. The triple-star path now wraps the inner content with both bold and italic traits via NSFontManager. Added a regression test in MarkdownRendererTests.swift. Thanks for catching this! |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 2a45aaeac3
ℹ️ About Codex in GitHub
Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".
| private func appendParagraph(_ text: String, to output: NSMutableAttributedString) { | ||
| let attrs = baseAttributes() | ||
| output.append(renderInline(text, base: attrs)) | ||
| output.append(NSAttributedString(string: "\n", attributes: attrs)) |
There was a problem hiding this comment.
Avoid appending a trailing blank line
When Markdown rendering is enabled, every non-empty paragraph path appends a newline and render(_:) never trims the final separator, so even a one-line streaming answer like Hello becomes Hello\n. EZWordResultView sizes the NSTextView from this attributed string via labelSize, so Markdown-enabled AI results get an extra empty line/vertical space compared with the same result rendered as plain text; this is especially visible for short answers and when toggling Markdown on/off.
Useful? React with 👍 / 👎.
# Conflicts: # Easydict/App/Easydict-Bridging-Header.h
Summary
Render Markdown emitted by streaming AI/LLM services (OpenAI, Claude Code,
Custom OpenAI, Gemini, etc.) into formatted NSAttributedString in result
cells, instead of showing the raw
##/**markers.Scope
MarkdownRenderer(Swift, no deps): block + inline pass withstreaming-safety (unterminated
**/ fences don't crash)MarkdownLabelextendsEZLabel, falls back to plain-text path whendisabled — non-AI services and dark mode keep working unchanged
MarkdownToggleButtonper-card override icon with 0.2s fade transitionNon-goals
Test plan
MarkdownRendererTests(16 cases: blocks, inline, partial input, perf)