feat(markdown): render AI translation results with formatting#1161
feat(markdown): render AI translation results with formatting#1161leozanee wants to merge 2 commits intotisfeng:devfrom
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".
|
|
||
| let source = text |
There was a problem hiding this comment.
Guard nil text before reading in Markdown label
This override reads text directly and then calls isEmpty, but EZLabel invokes updateDisplayedText during superclass setup (via its property setters) before text is guaranteed to be initialized. In that initialization path, the Obj-C backing ivar can still be nil, which can trap when bridged to Swift here and crash when creating streaming result cells. Please coalesce to an empty string before checking emptiness.
Useful? React with 👍 / 👎.
| if char == "*" || char == "_", | ||
| let close = findClose(of: String(char), in: scalars, after: index + 1), | ||
| close > index + 1 { |
There was a problem hiding this comment.
Avoid parsing in-word underscores as italic markers
The inline emphasis branch treats _ like * without word-boundary checks, so identifiers such as foo_bar_baz are rendered as foobarbaz with the middle segment italicized. This alters literal content that frequently appears in AI output (paths, variable names, filenames) and can make results inaccurate even when the source text is correct.
Useful? React with 👍 / 👎.
- 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.
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)