markdansi 0.1.4 → 0.1.5

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.
package/dist/index.d.ts CHANGED
@@ -1,8 +1,11 @@
1
+ import type { LiveRenderer, LiveRendererOptions } from "./live.js";
2
+ import { createLiveRenderer } from "./live.js";
1
3
  import { createRenderer, render as renderMarkdown } from "./render.js";
2
4
  import { themes } from "./theme.js";
3
5
  import type { RenderOptions, Theme, ThemeName } from "./types.js";
4
- export { renderMarkdown as render, createRenderer, themes };
6
+ export { createLiveRenderer, createRenderer, renderMarkdown as render, themes };
5
7
  export type { RenderOptions, Theme, ThemeName };
8
+ export type { LiveRenderer, LiveRendererOptions };
6
9
  /**
7
10
  * Render Markdown to plain text (no ANSI, no hyperlinks) while preserving layout/wrapping.
8
11
  */
package/dist/index.js CHANGED
@@ -1,6 +1,7 @@
1
+ import { createLiveRenderer } from "./live.js";
1
2
  import { createRenderer, render as renderMarkdown } from "./render.js";
2
3
  import { themes } from "./theme.js";
3
- export { renderMarkdown as render, createRenderer, themes };
4
+ export { createLiveRenderer, createRenderer, renderMarkdown as render, themes };
4
5
  /**
5
6
  * Render Markdown to plain text (no ANSI, no hyperlinks) while preserving layout/wrapping.
6
7
  */
package/dist/live.d.ts ADDED
@@ -0,0 +1,35 @@
1
+ export type LiveRenderer = {
2
+ render: (input: string) => void;
3
+ finish: () => void;
4
+ };
5
+ export type LiveRendererOptions = {
6
+ /**
7
+ * Function that converts the current full input into a rendered frame.
8
+ * Typically this is Markdansi's `render()` or an app-specific wrapper.
9
+ */
10
+ renderFrame: (input: string) => string;
11
+ /**
12
+ * Where to write ANSI output (usually `process.stdout.write.bind(process.stdout)`).
13
+ */
14
+ write: (chunk: string) => void;
15
+ /**
16
+ * Enable terminal "synchronized output" framing (DEC private mode 2026).
17
+ * Most terminals ignore this sequence if unsupported.
18
+ */
19
+ synchronizedOutput?: boolean;
20
+ /**
21
+ * Hide cursor during live updates.
22
+ */
23
+ hideCursor?: boolean;
24
+ /**
25
+ * Terminal width in columns used for row accounting.
26
+ * If omitted, defaults to 80.
27
+ */
28
+ width?: number;
29
+ };
30
+ /**
31
+ * Create a live renderer that repeatedly re-renders the entire buffer and redraws in-place.
32
+ *
33
+ * This is intentionally "terminal plumbing" and renderer-agnostic: you inject `renderFrame()`.
34
+ */
35
+ export declare function createLiveRenderer(options: LiveRendererOptions): LiveRenderer;
package/dist/live.js ADDED
@@ -0,0 +1,75 @@
1
+ import stringWidth from "string-width";
2
+ import stripAnsi from "strip-ansi";
3
+ const BSU = "\u001b[?2026h";
4
+ const ESU = "\u001b[?2026l";
5
+ const HIDE_CURSOR = "\u001b[?25l";
6
+ const SHOW_CURSOR = "\u001b[?25h";
7
+ const CLEAR_TO_END = "\u001b[0J";
8
+ function cursorUp(lines) {
9
+ if (lines <= 0)
10
+ return "";
11
+ return `\u001b[${lines}A`;
12
+ }
13
+ /**
14
+ * Create a live renderer that repeatedly re-renders the entire buffer and redraws in-place.
15
+ *
16
+ * This is intentionally "terminal plumbing" and renderer-agnostic: you inject `renderFrame()`.
17
+ */
18
+ export function createLiveRenderer(options) {
19
+ let previousRows = 0;
20
+ let cursorHidden = false;
21
+ const synchronizedOutput = options.synchronizedOutput !== false;
22
+ const hideCursor = options.hideCursor !== false;
23
+ const width = typeof options.width === "number" &&
24
+ Number.isFinite(options.width) &&
25
+ options.width > 0
26
+ ? Math.floor(options.width)
27
+ : 80;
28
+ const countRows = (text) => {
29
+ const lines = text.split("\n");
30
+ if (lines.length > 0 && lines.at(-1) === "")
31
+ lines.pop();
32
+ let rows = 0;
33
+ for (const line of lines) {
34
+ const visible = stripAnsi(line);
35
+ const w = stringWidth(visible);
36
+ rows += Math.max(1, Math.ceil(Math.max(0, w) / width));
37
+ }
38
+ return rows;
39
+ };
40
+ const render = (input) => {
41
+ const renderedRaw = options.renderFrame(input);
42
+ const rendered = renderedRaw.endsWith("\n")
43
+ ? renderedRaw
44
+ : `${renderedRaw}\n`;
45
+ const lines = rendered.split("\n");
46
+ if (lines.length > 0 && lines.at(-1) === "")
47
+ lines.pop();
48
+ const newRows = countRows(rendered);
49
+ let frame = "";
50
+ if (hideCursor && !cursorHidden) {
51
+ frame += HIDE_CURSOR;
52
+ cursorHidden = true;
53
+ }
54
+ if (synchronizedOutput)
55
+ frame += BSU;
56
+ frame += previousRows > 0 ? `${cursorUp(previousRows)}\r` : "\r";
57
+ frame += CLEAR_TO_END;
58
+ for (const line of lines) {
59
+ frame += "\r";
60
+ frame += line;
61
+ frame += "\r\n";
62
+ }
63
+ if (synchronizedOutput)
64
+ frame += ESU;
65
+ options.write(frame);
66
+ previousRows = newRows;
67
+ };
68
+ const finish = () => {
69
+ if (hideCursor && cursorHidden) {
70
+ options.write(SHOW_CURSOR);
71
+ cursorHidden = false;
72
+ }
73
+ };
74
+ return { render, finish };
75
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "markdansi",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "Tiny dependency-light markdown to ANSI converter.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",