diff --git a/.gitignore b/.gitignore index 3ee7f1c..5c08c02 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ # Dependencies node_modules/ +package-lock.json .pnp .pnp.js diff --git a/index.ts b/index.ts index 4062098..2d3c922 100644 --- a/index.ts +++ b/index.ts @@ -2,18 +2,39 @@ import type { Plugin, Hooks } from "@opencode-ai/plugin" declare const Bun: any +// Box-drawing characters +const BOX = { + topLeft: "┌", + topRight: "┐", + bottomLeft: "└", + bottomRight: "┘", + horizontal: "─", + vertical: "│", + topTee: "┬", + bottomTee: "┴", + leftTee: "├", + rightTee: "┤", + cross: "┼", +} + // Width cache for performance optimization const widthCache = new Map() let cacheOperationCount = 0 -export const FormatTables: Plugin = async () => { +interface TableFormatterOptions { + style?: "markdown" | "box" +} + +export const FormatTables: Plugin = async (options?: TableFormatterOptions) => { + const style = options?.style ?? "markdown" + return { "experimental.text.complete": async ( input: { sessionID: string; messageID: string; partID: string }, output: { text: string }, ) => { try { - output.text = formatMarkdownTables(output.text) + output.text = formatMarkdownTables(output.text, style) } catch (error) { // If formatting fails, keep original md text output.text = output.text + "\n\n" @@ -22,7 +43,7 @@ export const FormatTables: Plugin = async () => { } as Hooks } -function formatMarkdownTables(text: string): string { +function formatMarkdownTables(text: string, style: "markdown" | "box"): string { const lines = text.split("\n") const result: string[] = [] let i = 0 @@ -40,7 +61,7 @@ function formatMarkdownTables(text: string): string { } if (isValidTable(tableLines)) { - result.push(...formatTable(tableLines)) + result.push(...formatTable(tableLines, style)) } else { result.push(...tableLines) result.push("") @@ -87,7 +108,17 @@ function isValidTable(lines: string[]): boolean { return hasSeparator } -function formatTable(lines: string[]): string[] { +function buildHorizontalLine( + colWidths: number[], + left: string, + mid: string, + right: string, +): string { + const segments = colWidths.map((w) => BOX.horizontal.repeat(w + 2)) + return left + segments.join(mid) + right +} + +function formatTable(lines: string[], style: "markdown" | "box"): string[] { const separatorIndices = new Set() for (let i = 0; i < lines.length; i++) { if (isSeparatorRow(lines[i])) separatorIndices.add(i) @@ -122,20 +153,50 @@ function formatTable(lines: string[]): string[] { } } - return rows.map((row, rowIndex) => { - const cells: string[] = [] - for (let col = 0; col < colCount; col++) { - const cell = row[col] ?? "" - const align = colAlignments[col] + if (style === "box") { + // Build the box-drawing table + const result: string[] = [] + + // Top border + result.push(buildHorizontalLine(colWidths, BOX.topLeft, BOX.topTee, BOX.topRight)) + // Data rows (skip separator rows, replace them with box-drawing middle borders) + for (let rowIndex = 0; rowIndex < rows.length; rowIndex++) { if (separatorIndices.has(rowIndex)) { - cells.push(formatSeparatorCell(colWidths[col], align)) + // Replace markdown separator with box-drawing middle border + result.push(buildHorizontalLine(colWidths, BOX.leftTee, BOX.cross, BOX.rightTee)) } else { - cells.push(padCell(cell, colWidths[col], align)) + // Data row with box-drawing vertical borders + const cells: string[] = [] + for (let col = 0; col < colCount; col++) { + const cell = rows[rowIndex][col] ?? "" + const align = colAlignments[col] + cells.push(padCell(cell, colWidths[col], align)) + } + result.push(BOX.vertical + " " + cells.join(" " + BOX.vertical + " ") + " " + BOX.vertical) } } - return "| " + cells.join(" | ") + " |" - }) + + // Bottom border + result.push(buildHorizontalLine(colWidths, BOX.bottomLeft, BOX.bottomTee, BOX.bottomRight)) + + return result + } else { + return rows.map((row, rowIndex) => { + const cells: string[] = [] + for (let col = 0; col < colCount; col++) { + const cell = row[col] ?? "" + const align = colAlignments[col] + + if (separatorIndices.has(rowIndex)) { + cells.push(formatSeparatorCell(colWidths[col], align)) + } else { + cells.push(padCell(cell, colWidths[col], align)) + } + } + return "| " + cells.join(" | ") + " |" + }) + } } function getAlignment(delimiterCell: string): "left" | "center" | "right" { @@ -169,7 +230,7 @@ function getStringWidth(text: string): number { const codeBlocks: string[] = [] let textWithPlaceholders = text.replace(/`(.+?)`/g, (match, content) => { codeBlocks.push(content) - return `\x00CODE${codeBlocks.length - 1}\x00` + return `\u0000CODE${codeBlocks.length - 1}\u0000` }) // Step 2: Strip markdown from non-code parts @@ -188,7 +249,8 @@ function getStringWidth(text: string): number { } // Step 3: Restore code content (with its original markdown preserved) - visualText = visualText.replace(/\x00CODE(\d+)\x00/g, (match, index) => { + const restoreRegex = new RegExp("\\u0000CODE(\\d+)\\u0000", "g") + visualText = visualText.replace(restoreRegex, (match, index) => { return codeBlocks[parseInt(index)] }) diff --git a/package.json b/package.json index 7666616..5e82433 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@franlol/opencode-md-table-formatter", - "version": "0.0.3", + "version": "0.1.0", "description": "Markdown table formatter plugin for OpenCode with concealment mode support", "keywords": [ "opencode",