fix(ui): preserve user ordered-list markers (closes #5013)
This commit is contained in:
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
### Fixed
|
### 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 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 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)).
|
- 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();
|
super();
|
||||||
this.contentBox = new Box(1, 1, (content: string) => theme.bg("userMessageBg", content));
|
this.contentBox = new Box(1, 1, (content: string) => theme.bg("userMessageBg", content));
|
||||||
this.contentBox.addChild(
|
this.contentBox.addChild(
|
||||||
new Markdown(text, 0, 0, markdownTheme, {
|
new Markdown(
|
||||||
color: (content: string) => theme.fg("userMessageText", content),
|
text,
|
||||||
}),
|
0,
|
||||||
|
0,
|
||||||
|
markdownTheme,
|
||||||
|
{
|
||||||
|
color: (content: string) => theme.fg("userMessageText", content),
|
||||||
|
},
|
||||||
|
{ preserveOrderedListMarkers: true },
|
||||||
|
),
|
||||||
);
|
);
|
||||||
this.addChild(this.contentBox);
|
this.addChild(this.contentBox);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,10 @@
|
|||||||
|
|
||||||
## [Unreleased]
|
## [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
|
||||||
|
|
||||||
- Fixed `Shift+Enter` in Apple Terminal by detecting local macOS modifier state when Terminal.app sends plain Return.
|
- 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;
|
codeBlockIndent?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface MarkdownOptions {
|
||||||
|
/** Preserve source ordered-list markers instead of normalizing them from the list start. */
|
||||||
|
preserveOrderedListMarkers?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
interface InlineStyleContext {
|
interface InlineStyleContext {
|
||||||
applyText: (text: string) => string;
|
applyText: (text: string) => string;
|
||||||
stylePrefix: string;
|
stylePrefix: string;
|
||||||
@@ -81,6 +86,7 @@ export class Markdown implements Component {
|
|||||||
private paddingY: number; // Top/bottom padding
|
private paddingY: number; // Top/bottom padding
|
||||||
private defaultTextStyle?: DefaultTextStyle;
|
private defaultTextStyle?: DefaultTextStyle;
|
||||||
private theme: MarkdownTheme;
|
private theme: MarkdownTheme;
|
||||||
|
private options: MarkdownOptions;
|
||||||
private defaultStylePrefix?: string;
|
private defaultStylePrefix?: string;
|
||||||
|
|
||||||
// Cache for rendered output
|
// Cache for rendered output
|
||||||
@@ -94,12 +100,14 @@ export class Markdown implements Component {
|
|||||||
paddingY: number,
|
paddingY: number,
|
||||||
theme: MarkdownTheme,
|
theme: MarkdownTheme,
|
||||||
defaultTextStyle?: DefaultTextStyle,
|
defaultTextStyle?: DefaultTextStyle,
|
||||||
|
options?: MarkdownOptions,
|
||||||
) {
|
) {
|
||||||
this.text = text;
|
this.text = text;
|
||||||
this.paddingX = paddingX;
|
this.paddingX = paddingX;
|
||||||
this.paddingY = paddingY;
|
this.paddingY = paddingY;
|
||||||
this.theme = theme;
|
this.theme = theme;
|
||||||
this.defaultTextStyle = defaultTextStyle;
|
this.defaultTextStyle = defaultTextStyle;
|
||||||
|
this.options = options ? { ...options } : {};
|
||||||
}
|
}
|
||||||
|
|
||||||
setText(text: string): void {
|
setText(text: string): void {
|
||||||
@@ -548,6 +556,11 @@ export class Markdown implements Component {
|
|||||||
return result;
|
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
|
* 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++) {
|
for (let i = 0; i < token.items.length; i++) {
|
||||||
const item = token.items[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 taskMarker = item.task ? `[${item.checked ? "x" : " "}] ` : "";
|
||||||
const marker = bullet + taskMarker;
|
const marker = bullet + taskMarker;
|
||||||
const firstPrefix = indent + this.theme.listBullet(marker);
|
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 { Image, type ImageOptions, type ImageTheme } from "./components/image.ts";
|
||||||
export { Input } from "./components/input.ts";
|
export { Input } from "./components/input.ts";
|
||||||
export { Loader, type LoaderIndicatorOptions } from "./components/loader.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 {
|
export {
|
||||||
type SelectItem,
|
type SelectItem,
|
||||||
SelectList,
|
SelectList,
|
||||||
|
|||||||
@@ -104,6 +104,31 @@ describe("Markdown component", () => {
|
|||||||
assert.ok(plainLines.some((line) => line.includes("2. Second")));
|
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", () => {
|
it("should render mixed ordered and unordered nested lists", () => {
|
||||||
const markdown = new Markdown(
|
const markdown = new Markdown(
|
||||||
`1. Ordered item
|
`1. Ordered item
|
||||||
|
|||||||
Reference in New Issue
Block a user