Skip to content

Commit

Permalink
Merge pull request #3174 from continuedev/dallin/md-tables
Browse files Browse the repository at this point in the history
Markdown Table Rendering Support
  • Loading branch information
sestinj authored Dec 4, 2024
2 parents 1e6aca5 + df75931 commit 3649947
Show file tree
Hide file tree
Showing 2 changed files with 153 additions and 3 deletions.
15 changes: 12 additions & 3 deletions gui/src/components/markdown/StyledMarkdownPreview.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { SymbolWithRange } from "core";
import { ctxItemToRifWithContents } from "core/commands/util";
import { memo, useEffect, useRef } from "react";
import { useSelector } from "react-redux";
import { useRemark } from "react-remark";
import rehypeHighlight, { Options } from "rehype-highlight";
import rehypeKatex from "rehype-katex";
Expand All @@ -14,14 +13,15 @@ import {
vscEditorBackground,
vscForeground,
} from "..";
import useUpdatingRef from "../../hooks/useUpdatingRef";
import { getFontSize, isJetBrains } from "../../util";
import FilenameLink from "./FilenameLink";
import "./katex.css";
import "./markdown.css";
import StepContainerPreActionButtons from "./StepContainerPreActionButtons";
import StepContainerPreToolbar from "./StepContainerPreToolbar";
import SymbolLink from "./SymbolLink";
import useUpdatingRef from "../../hooks/useUpdatingRef";
import { remarkTables } from "./utils/remarkTables";
import { SyntaxHighlightedPre } from "./SyntaxHighlightedPre";
import { patchNestedMarkdown } from "./utils/patchNestedMarkdown";
import { useAppSelector } from "../../redux/hooks";
Expand Down Expand Up @@ -175,7 +175,16 @@ const StyledMarkdownPreview = memo(function StyledMarkdownPreview(
}, [symbols]);

const [reactContent, setMarkdownSource] = useRemark({
remarkPlugins: [remarkMath, () => processCodeBlocks],
remarkPlugins: [
remarkTables,
[
remarkMath,
{
singleDollarTextMath: false,
},
],
() => processCodeBlocks,
],
rehypePlugins: [
rehypeKatex as any,
{},
Expand Down
141 changes: 141 additions & 0 deletions gui/src/components/markdown/utils/remarkTables.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import { visit } from "unist-util-visit";

/*
Remark plugin for github-flavor markdown tables
Given a table such as this exists in a text node:
| Fruit | Color | Taste |
|----------|---------|-------------|
| Apple | Red | Sweet |
| Banana | Yellow | Sweet |
| Lemon | Yellow | Sour |
| Orange | Orange | Citrus |
| Grape | Purple | Sweet/Tart |
1. Find it and split it into groups using regex
- header
- alignment row
- body
2. Parse the groups to get table cell values and text alignment
3. Build an MDAST table node
4. Do this for each table found, until no tables are found
*/
export function remarkTables() {
return (tree: any) => {
visit(tree, "text", (node, index, parent) => {
const { value } = node;

const tableRegex =
/((?:\| *[^|\r\n]+ *)+\|)(?:\r?\n)((?:\|[ :]?-+[ :]?)+\|)((?:(?:\r?\n)(?:\| *[^|\r\n]+ *)+\|)+)/g;
//// header // newline // |:---|----:| // new line // table rows

let match: RegExpExecArray | null;
let lastIndex = 0;
const newNodes = [];
let failed = false;
while ((match = tableRegex.exec(value)) !== null) {
const fullTableString = match[0];
const headerGroup = match[1];
const separatorGroup = match[2];
const bodyGroup = match[3];

if (!fullTableString || !headerGroup || !separatorGroup || !bodyGroup) {
console.error("Markdown table regex failed to yield table groups");
failed = true;
break;
}

const headerCells = splitRow(headerGroup);
const alignments = splitRow(separatorGroup).map((cell) => {
if (cell.startsWith(":") && cell.endsWith(":")) return "center";
if (cell.endsWith(":")) return "right";
if (cell.startsWith(":")) return "left";
return null;
});

const bodyCells = bodyGroup
.trim()
.split("\n")
.map((bodyRow) => splitRow(bodyRow));

try {
const tableNode = {
type: "table",
align: alignments,
children: [
{
type: "tableRow",
children: headerCells.map((cell, i) => ({
type: "element",
tagName: "th",
align: alignments[i],
children: [{ type: "text", value: cell }],
})),
},
...bodyCells.map((row, i) => {
return {
type: "tableRow",
data: {
hProperties: {
class: "markdown-table",
key: i,
},
},
children: row.map((cell, i) => ({
type: "tableCell",
align: alignments[i],
children: [{ type: "text", value: cell.trim() }],
})),
};
}),
],
};

// Add any text before the table as a text node
if (match.index > lastIndex) {
newNodes.push({
type: "text",
value: value.slice(lastIndex, match.index),
});
}

// Add table node
newNodes.push(tableNode);
} catch (e) {
console.error("Failed to parse markdown table after regex match", e);
newNodes.push({
type: "text",
value: fullTableString,
});
}

lastIndex = tableRegex.lastIndex;
}

if (failed) {
return;
}

// Add any remaining text after the last table
if (lastIndex < value.length) {
newNodes.push({
type: "text",
value: value.slice(lastIndex),
});
}

// Replace the original text node with the new nodes
if (newNodes.length > 0) {
parent.children.splice(index, 1, ...newNodes);
}
});
};
}

function splitRow(row: string) {
return row
.trim()
.replace(/^\||\|$/g, "") // Remove leading and trailing pipes
.split("|")
.map((cell) => cell.trim());
}

0 comments on commit 3649947

Please sign in to comment.