mirror of
https://github.com/earendil-works/pi.git
synced 2026-06-18 15:54:04 +08:00
fix(ui): preserve user ordered-list markers (closes #5013)
This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed user message transcript rendering to preserve user-authored ordered-list markers ([#5013](https://github.com/earendil-works/pi/issues/5013)).
|
||||
- Fixed self-update commands to bypass npm, pnpm, and Bun minimum release age gates for explicit `pi update` runs ([#4929](https://github.com/earendil-works/pi/issues/4929)).
|
||||
- Fixed context token estimates to count user image attachments consistently with tool result images ([#4983](https://github.com/earendil-works/pi/issues/4983)).
|
||||
- Fixed `RpcClient` to reject pending requests and consume stdin pipe errors when the child process exits unexpectedly ([#4764](https://github.com/earendil-works/pi/issues/4764)).
|
||||
|
||||
@@ -15,9 +15,16 @@ export class UserMessageComponent extends Container {
|
||||
super();
|
||||
this.contentBox = new Box(1, 1, (content: string) => theme.bg("userMessageBg", content));
|
||||
this.contentBox.addChild(
|
||||
new Markdown(text, 0, 0, markdownTheme, {
|
||||
color: (content: string) => theme.fg("userMessageText", content),
|
||||
}),
|
||||
new Markdown(
|
||||
text,
|
||||
0,
|
||||
0,
|
||||
markdownTheme,
|
||||
{
|
||||
color: (content: string) => theme.fg("userMessageText", content),
|
||||
},
|
||||
{ preserveOrderedListMarkers: true },
|
||||
),
|
||||
);
|
||||
this.addChild(this.contentBox);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,10 @@
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
|
||||
- Added an opt-in Markdown renderer option to preserve source ordered-list markers for transcript rendering ([#5013](https://github.com/earendil-works/pi/issues/5013)).
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed `Shift+Enter` in Apple Terminal by detecting local macOS modifier state when Terminal.app sends plain Return.
|
||||
|
||||
@@ -70,6 +70,11 @@ export interface MarkdownTheme {
|
||||
codeBlockIndent?: string;
|
||||
}
|
||||
|
||||
export interface MarkdownOptions {
|
||||
/** Preserve source ordered-list markers instead of normalizing them from the list start. */
|
||||
preserveOrderedListMarkers?: boolean;
|
||||
}
|
||||
|
||||
interface InlineStyleContext {
|
||||
applyText: (text: string) => string;
|
||||
stylePrefix: string;
|
||||
@@ -81,6 +86,7 @@ export class Markdown implements Component {
|
||||
private paddingY: number; // Top/bottom padding
|
||||
private defaultTextStyle?: DefaultTextStyle;
|
||||
private theme: MarkdownTheme;
|
||||
private options: MarkdownOptions;
|
||||
private defaultStylePrefix?: string;
|
||||
|
||||
// Cache for rendered output
|
||||
@@ -94,12 +100,14 @@ export class Markdown implements Component {
|
||||
paddingY: number,
|
||||
theme: MarkdownTheme,
|
||||
defaultTextStyle?: DefaultTextStyle,
|
||||
options?: MarkdownOptions,
|
||||
) {
|
||||
this.text = text;
|
||||
this.paddingX = paddingX;
|
||||
this.paddingY = paddingY;
|
||||
this.theme = theme;
|
||||
this.defaultTextStyle = defaultTextStyle;
|
||||
this.options = options ? { ...options } : {};
|
||||
}
|
||||
|
||||
setText(text: string): void {
|
||||
@@ -548,6 +556,11 @@ export class Markdown implements Component {
|
||||
return result;
|
||||
}
|
||||
|
||||
private getOrderedListMarker(item: Tokens.ListItem): string | undefined {
|
||||
const match = /^(?: {0,3})(\d{1,9}[.)])[ \t]+/.exec(item.raw);
|
||||
return match ? `${match[1]} ` : undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a list with proper nesting support
|
||||
*/
|
||||
@@ -559,7 +572,11 @@ export class Markdown implements Component {
|
||||
|
||||
for (let i = 0; i < token.items.length; i++) {
|
||||
const item = token.items[i];
|
||||
const bullet = token.ordered ? `${startNumber + i}. ` : "- ";
|
||||
const bullet = token.ordered
|
||||
? this.options.preserveOrderedListMarkers
|
||||
? (this.getOrderedListMarker(item) ?? `${startNumber + i}. `)
|
||||
: `${startNumber + i}. `
|
||||
: "- ";
|
||||
const taskMarker = item.task ? `[${item.checked ? "x" : " "}] ` : "";
|
||||
const marker = bullet + taskMarker;
|
||||
const firstPrefix = indent + this.theme.listBullet(marker);
|
||||
|
||||
@@ -15,7 +15,7 @@ export { Editor, type EditorOptions, type EditorTheme } from "./components/edito
|
||||
export { Image, type ImageOptions, type ImageTheme } from "./components/image.ts";
|
||||
export { Input } from "./components/input.ts";
|
||||
export { Loader, type LoaderIndicatorOptions } from "./components/loader.ts";
|
||||
export { type DefaultTextStyle, Markdown, type MarkdownTheme } from "./components/markdown.ts";
|
||||
export { type DefaultTextStyle, Markdown, type MarkdownOptions, type MarkdownTheme } from "./components/markdown.ts";
|
||||
export {
|
||||
type SelectItem,
|
||||
SelectList,
|
||||
|
||||
@@ -104,6 +104,31 @@ describe("Markdown component", () => {
|
||||
assert.ok(plainLines.some((line) => line.includes("2. Second")));
|
||||
});
|
||||
|
||||
it("should normalize ordered list markers by default", () => {
|
||||
const markdown = new Markdown("1. alpha\n1. beta\n1. gamma", 0, 0, defaultMarkdownTheme);
|
||||
|
||||
const lines = markdown.render(80).map((line) => stripAnsi(line).trimEnd());
|
||||
|
||||
assert.deepStrictEqual(lines, ["1. alpha", "2. beta", "3. gamma"]);
|
||||
});
|
||||
|
||||
it("should preserve source ordered list markers when configured", () => {
|
||||
const markdown = new Markdown(
|
||||
" 4. forth\n 3. third\n\n10) ten\n7) seven",
|
||||
0,
|
||||
0,
|
||||
defaultMarkdownTheme,
|
||||
undefined,
|
||||
{
|
||||
preserveOrderedListMarkers: true,
|
||||
},
|
||||
);
|
||||
|
||||
const lines = markdown.render(80).map((line) => stripAnsi(line).trimEnd());
|
||||
|
||||
assert.deepStrictEqual(lines, ["4. forth", "3. third", "", "10) ten", "7) seven"]);
|
||||
});
|
||||
|
||||
it("should render mixed ordered and unordered nested lists", () => {
|
||||
const markdown = new Markdown(
|
||||
`1. Ordered item
|
||||
|
||||
Reference in New Issue
Block a user