markdansi 0.1.5 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +20 -1
  2. package/dist/render.js +43 -2
  3. package/package.json +11 -6
package/README.md CHANGED
@@ -6,7 +6,7 @@
6
6
 
7
7
  ![npm](https://img.shields.io/npm/v/markdansi) ![license MIT](https://img.shields.io/badge/license-MIT-blue.svg) ![node >=22](https://img.shields.io/badge/node-%3E%3D22-brightgreen) ![tests vitest](https://img.shields.io/badge/tests-vitest-blue?logo=vitest)
8
8
 
9
- Tiny, dependency-light Markdown → ANSI renderer and CLI for modern Node (>=22). Focuses on readable terminal output with sensible wrapping, GFM support (tables, task lists, strikethrough), optional OSC‑8 hyperlinks, and zero built‑in syntax highlighting (pluggable hook). Written in TypeScript, ships ESM.
9
+ Tiny, dependency-light Markdown → ANSI renderer and CLI for modern Node (>=22). Focuses on readable terminal output with sensible wrapping, GFM support (tables, task lists, strikethrough), optional OSC‑8 hyperlinks, and zero built‑in syntax highlighting (pluggable hook). Includes live in-place terminal rendering for streaming updates (`createLiveRenderer`). Written in TypeScript, ships ESM.
10
10
 
11
11
  Published on npm as `markdansi`.
12
12
 
@@ -46,6 +46,25 @@ const { render } = await import('markdansi');
46
46
  console.log(render('# hello'));
47
47
  ```
48
48
 
49
+ ### Live streaming / in-place rendering
50
+ For streaming output (LLM responses, logs, progress), use `createLiveRenderer` to re-render and redraw in-place. Uses terminal “synchronized output” when supported.
51
+
52
+ ```js
53
+ import { createLiveRenderer, render } from 'markdansi';
54
+
55
+ const live = createLiveRenderer({
56
+ renderFrame: (markdown) => render(markdown),
57
+ write: process.stdout.write.bind(process.stdout),
58
+ });
59
+
60
+ let buffer = '';
61
+ buffer += '# Hello\\n';
62
+ live.render(buffer);
63
+ buffer += '\\nMore…\\n';
64
+ live.render(buffer);
65
+ live.finish();
66
+ ```
67
+
49
68
  ```js
50
69
  import { render, createRenderer, strip, themes } from 'markdansi';
51
70
 
package/dist/render.js CHANGED
@@ -322,7 +322,7 @@ function renderNode(node, ctx, indentLevel, isTightList) {
322
322
  }
323
323
  }
324
324
  function renderParagraph(node, ctx, indentLevel) {
325
- const text = renderInline(node.children, ctx);
325
+ const text = normalizeParagraphInlineText(renderInline(node.children, ctx));
326
326
  const prefix = " ".repeat(ctx.options.listIndent * indentLevel);
327
327
  const rawLines = text.split("\n");
328
328
  const normalized = [];
@@ -489,7 +489,7 @@ function renderInline(children, ctx) {
489
489
  out += renderLink(node, ctx);
490
490
  break;
491
491
  case "break":
492
- out += "\n";
492
+ out += HARD_BREAK;
493
493
  break;
494
494
  default:
495
495
  if ("value" in node && typeof node.value === "string")
@@ -498,6 +498,47 @@ function renderInline(children, ctx) {
498
498
  }
499
499
  return out;
500
500
  }
501
+ const HARD_BREAK = "\u000B";
502
+ function normalizeParagraphInlineText(text) {
503
+ if (!text.includes("\n") && !text.includes(HARD_BREAK))
504
+ return text;
505
+ const segments = [];
506
+ let current = "";
507
+ for (let i = 0; i < text.length; i += 1) {
508
+ const ch = text[i];
509
+ if (ch === "\n" || ch === HARD_BREAK) {
510
+ segments.push({
511
+ text: current,
512
+ breakAfter: ch === HARD_BREAK ? "hard" : "soft",
513
+ });
514
+ current = "";
515
+ continue;
516
+ }
517
+ current += ch;
518
+ }
519
+ segments.push({ text: current });
520
+ const defPattern = /^\[[^\]]+]:\s+\S/;
521
+ let out = segments[0]?.text ?? "";
522
+ for (let i = 0; i < segments.length - 1; i += 1) {
523
+ const kind = segments[i]?.breakAfter ?? "soft";
524
+ const left = segments[i]?.text ?? "";
525
+ const right = segments[i + 1]?.text ?? "";
526
+ if (kind === "hard") {
527
+ out += "\n";
528
+ out += right;
529
+ continue;
530
+ }
531
+ const leftTrim = left.trimStart();
532
+ const rightTrim = right.trimStart();
533
+ const keepNewline = left === "" ||
534
+ right === "" ||
535
+ defPattern.test(leftTrim) ||
536
+ defPattern.test(rightTrim);
537
+ out += keepNewline ? "\n" : " ";
538
+ out += rightTrim;
539
+ }
540
+ return out;
541
+ }
501
542
  function renderLink(node, ctx) {
502
543
  const label = renderInline(node.children, ctx) || node.url;
503
544
  const url = node.url || "";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "markdansi",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
4
  "description": "Tiny dependency-light markdown to ANSI converter.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -67,12 +67,17 @@
67
67
  "supports-hyperlinks": "^4.3.0"
68
68
  },
69
69
  "devDependencies": {
70
- "@biomejs/biome": "^2.3.5",
70
+ "@biomejs/biome": "^2.3.10",
71
71
  "@types/mdast": "^4.0.4",
72
- "@types/node": "^24.10.1",
73
- "@vitest/coverage-v8": "^4.0.9",
74
- "tsx": "^4.20.6",
72
+ "@types/node": "^25.0.3",
73
+ "@vitest/coverage-v8": "^4.0.16",
74
+ "tsx": "^4.21.0",
75
75
  "typescript": "^5.9.3",
76
- "vitest": "^4.0.9"
76
+ "vitest": "^4.0.16"
77
+ },
78
+ "pnpm": {
79
+ "onlyBuiltDependencies": [
80
+ "esbuild"
81
+ ]
77
82
  }
78
83
  }