From 725e6b2051dbae02ce924e6a0c7bd3ef3faafb70 Mon Sep 17 00:00:00 2001 From: Kaz Wesley Date: Tue, 4 Mar 2025 08:56:26 -0800 Subject: [PATCH] Integrate @lezer/markdown and codemirror/lang-markdown as subtrees (#12371) Integrate @lezer/markdown and codemirror/lang-markdown with our repo; port our customizations to direct modifications to the implementation; reorganize and extend the `ensoMarkdown` tests covering @lezer/markdown customizations. --- .bazelignore | 4 +- app/gui/package.json | 2 +- .../codemirror/formatting/block.ts | 2 +- .../MarkdownEditor/markdown/syntax.ts | 84 +-- app/lang-markdown/BUILD.bazel | 34 + app/lang-markdown/package.json | 3 +- app/lang-markdown/src/markdown.ts | 6 +- app/lang-markdown/tsconfig.json | 15 + app/lezer-markdown/BUILD.bazel | 34 + app/lezer-markdown/package.json | 6 +- app/lezer-markdown/src/extension.ts | 14 +- app/lezer-markdown/src/markdown.ts | 34 +- app/lezer-markdown/tsconfig.json | 1 + app/ydoc-shared/package.json | 2 +- .../src/ast/__tests__/ensoMarkdown.test.ts | 378 +++++++---- app/ydoc-shared/src/ast/ensoMarkdown.ts | 596 +----------------- app/ydoc-shared/src/util/lezer.ts | 23 + eslint.config.mjs | 4 + pnpm-lock.yaml | 569 +++++++++++++++-- pnpm-workspace.yaml | 2 + 20 files changed, 935 insertions(+), 878 deletions(-) create mode 100644 app/lang-markdown/BUILD.bazel create mode 100644 app/lang-markdown/tsconfig.json create mode 100644 app/lezer-markdown/BUILD.bazel diff --git a/.bazelignore b/.bazelignore index e22ffa1c776c..5af5a7a46c1b 100644 --- a/.bazelignore +++ b/.bazelignore @@ -13,6 +13,8 @@ app/ide-desktop/lib/esbuild-plugin-copy-directories/node_modules app/ide-desktop/lib/icons/node_modules app/ide-desktop/lib/ts-plugin-namespace-auto-import/node_modules app/ide-desktop/node_modules +app/lang-markdown/node_modules +app/lezer-markdown/node_modules app/rust-ffi/node_modules app/ydoc-server/node_modules app/ydoc-server-nodejs/node_modules @@ -33,4 +35,4 @@ build-cache .dist target build -.git \ No newline at end of file +.git diff --git a/app/gui/package.json b/app/gui/package.json index a6da85781390..e53d8f796ee7 100644 --- a/app/gui/package.json +++ b/app/gui/package.json @@ -53,7 +53,7 @@ "@aws-amplify/core": "5.8.5", "@babel/parser": "^7.26.3", "@codemirror/commands": "^6.7.1", - "@codemirror/lang-markdown": "^6.3.1", + "@codemirror/lang-markdown": "workspace:*", "@codemirror/language": "^6.10.8", "@codemirror/lint": "^6.8.4", "@codemirror/search": "^6.5.8", diff --git a/app/gui/src/project-view/components/MarkdownEditor/codemirror/formatting/block.ts b/app/gui/src/project-view/components/MarkdownEditor/codemirror/formatting/block.ts index 2d4228eb6937..eb198846e1ca 100644 --- a/app/gui/src/project-view/components/MarkdownEditor/codemirror/formatting/block.ts +++ b/app/gui/src/project-view/components/MarkdownEditor/codemirror/formatting/block.ts @@ -275,7 +275,7 @@ function toggleQuoteInner( function findQuoteMark(node: SyntaxNode): { from: number; to: number } | null { const cursor = node.cursor() do { - if (cursor.type.name === 'EnsoBlockquote') { + if (cursor.type.name === 'Blockquote') { const quoteMark = cursor.node.getChild('QuoteMark') if (quoteMark == null) return null return { from: quoteMark.from, to: quoteMark.to } diff --git a/app/gui/src/project-view/components/MarkdownEditor/markdown/syntax.ts b/app/gui/src/project-view/components/MarkdownEditor/markdown/syntax.ts index 96bf4f1e85f8..ae7953b0c610 100644 --- a/app/gui/src/project-view/components/MarkdownEditor/markdown/syntax.ts +++ b/app/gui/src/project-view/components/MarkdownEditor/markdown/syntax.ts @@ -1,76 +1,7 @@ -import { markdown as markdownExtension } from '@codemirror/lang-markdown' -import { - defineLanguageFacet, - foldNodeProp, - foldService, - indentNodeProp, - Language, - languageDataProp, - syntaxTree, -} from '@codemirror/language' +import { markdown } from '@codemirror/lang-markdown' +import { foldNodeProp } from '@codemirror/language' import { type Extension } from '@codemirror/state' -import { NodeProp, type NodeType, type Parser, type SyntaxNode } from '@lezer/common' -import { ensoMarkdownParser } from 'ydoc-shared/ast/ensoMarkdown' - -function mkLang(parser: Parser) { - return new Language(data, parser, [headerIndent], 'markdown') -} - -const data = defineLanguageFacet({ commentTokens: { block: { open: '' } } }) - -const headingProp = new NodeProp() - -const commonmarkCodemirrorLanguageExtension = { - props: [ - foldNodeProp.add((type) => { - return !type.is('Block') || type.is('Document') || isHeading(type) != null || isList(type) ? - undefined - : (tree, state) => ({ from: state.doc.lineAt(tree.from).to, to: tree.to }) - }), - headingProp.add(isHeading), - indentNodeProp.add({ - Document: () => null, - }), - languageDataProp.add({ - Document: data, - }), - ], -} - -function isHeading(type: NodeType) { - const match = /^(?:ATX|Setext)Heading(\d)$/.exec(type.name) - return match ? +match[1]! : undefined -} - -function isList(type: NodeType) { - return type.name == 'OrderedList' || type.name == 'BulletList' -} - -function findSectionEnd(headerNode: SyntaxNode, level: number) { - let last = headerNode - for (;;) { - const next = last.nextSibling - let heading - if (!next || ((heading = isHeading(next.type)) != null && heading <= level)) break - last = next - } - return last.to -} - -const headerIndent = foldService.of((state, start, end) => { - for ( - let node: SyntaxNode | null = syntaxTree(state).resolveInner(end, -1); - node; - node = node.parent - ) { - if (node.from < start) break - const heading = node.type.prop(headingProp) - if (heading == null) continue - const upto = findSectionEnd(node, heading) - if (upto > end) return { from: end, to: upto } - } - return null -}) +import { ensoMarkdownExtension } from 'ydoc-shared/ast/ensoMarkdown' const tableCodemirrorLanguageExtension = { props: [ @@ -80,13 +11,8 @@ const tableCodemirrorLanguageExtension = { ], } -const extension = markdownExtension({ - base: mkLang( - ensoMarkdownParser.configure([ - commonmarkCodemirrorLanguageExtension, - tableCodemirrorLanguageExtension, - ]), - ), +const extension = markdown({ + extensions: [ensoMarkdownExtension, tableCodemirrorLanguageExtension], }) export const ensoMarkdownSyntax = (): Extension => extension diff --git a/app/lang-markdown/BUILD.bazel b/app/lang-markdown/BUILD.bazel new file mode 100644 index 000000000000..335b1f60a7b1 --- /dev/null +++ b/app/lang-markdown/BUILD.bazel @@ -0,0 +1,34 @@ +load("@aspect_rules_js//npm:defs.bzl", "npm_package") +load("@aspect_rules_ts//ts:defs.bzl", "ts_config", "ts_project") +load("@npm//:defs.bzl", "npm_link_all_packages", "npm_link_targets") + +npm_link_all_packages(name = "node_modules") + +ts_config( + name = "tsconfig", + src = "tsconfig.json", + deps = ["//:tsconfig"], +) + +ts_project( + name = "tsc", + srcs = glob(["src/*.ts"]), + composite = True, + out_dir = "dist", + root_dir = "src", + tsconfig = ":tsconfig", + validate = select({ + "@platforms//os:windows": False, + "//conditions:default": True, + }), + deps = npm_link_targets(), +) + +npm_package( + name = "pkg", + srcs = [ + "package.json", + ":tsc", + ], + visibility = ["//visibility:public"], +) diff --git a/app/lang-markdown/package.json b/app/lang-markdown/package.json index 2d548fa0b1c5..7767d1b37cfb 100644 --- a/app/lang-markdown/package.json +++ b/app/lang-markdown/package.json @@ -1,6 +1,7 @@ { "name": "@codemirror/lang-markdown", "version": "6.3.2", + "private": true, "description": "Markdown language support for the CodeMirror code editor", "scripts": { "test": "cm-runtests", @@ -31,7 +32,7 @@ "@codemirror/language": "^6.3.0", "@codemirror/state": "^6.0.0", "@codemirror/view": "^6.0.0", - "@lezer/markdown": "^1.0.0", + "@lezer/markdown": "workspace:*", "@lezer/common": "^1.2.1" }, "devDependencies": { diff --git a/app/lang-markdown/src/markdown.ts b/app/lang-markdown/src/markdown.ts index 128dfcd9e96a..659ef18ca6d7 100644 --- a/app/lang-markdown/src/markdown.ts +++ b/app/lang-markdown/src/markdown.ts @@ -1,7 +1,7 @@ import {Language, defineLanguageFacet, languageDataProp, foldNodeProp, indentNodeProp, foldService, syntaxTree, LanguageDescription, ParseContext} from "@codemirror/language" import {parser as baseParser, MarkdownParser, GFM, Subscript, Superscript, Emoji} from "@lezer/markdown" -import {SyntaxNode, NodeType, NodeProp} from "@lezer/common" +import {SyntaxNode, NodeType, NodeProp, Parser} from "@lezer/common" const data = defineLanguageFacet({commentTokens: {block: {open: ""}}}) @@ -25,7 +25,7 @@ const commonmark = baseParser.configure({ function isHeading(type: NodeType) { let match = /^(?:ATX|Setext)Heading(\d)$/.exec(type.name) - return match ? +match[1] : undefined + return match ? +match[1]! : undefined } function isList(type: NodeType) { @@ -75,7 +75,7 @@ export const markdownLanguage = mkLang(extended) export function getCodeParser( languages: readonly LanguageDescription[] | ((info: string) => Language | LanguageDescription | null) | undefined, defaultLanguage?: Language -) { +): (info: string) => Parser | null { return (info: string) => { if (info && languages) { let found = null diff --git a/app/lang-markdown/tsconfig.json b/app/lang-markdown/tsconfig.json new file mode 100644 index 000000000000..20a359813f83 --- /dev/null +++ b/app/lang-markdown/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "lib": ["es2017"], + "noImplicitReturns": true, + "noUnusedLocals": true, + "strict": true, + "target": "es6", + "composite": true, + "module": "es2015", + "newLine": "lf", + "stripInternal": true, + "moduleResolution": "node" + }, + "include": ["src/*.ts"] +} diff --git a/app/lezer-markdown/BUILD.bazel b/app/lezer-markdown/BUILD.bazel new file mode 100644 index 000000000000..335b1f60a7b1 --- /dev/null +++ b/app/lezer-markdown/BUILD.bazel @@ -0,0 +1,34 @@ +load("@aspect_rules_js//npm:defs.bzl", "npm_package") +load("@aspect_rules_ts//ts:defs.bzl", "ts_config", "ts_project") +load("@npm//:defs.bzl", "npm_link_all_packages", "npm_link_targets") + +npm_link_all_packages(name = "node_modules") + +ts_config( + name = "tsconfig", + src = "tsconfig.json", + deps = ["//:tsconfig"], +) + +ts_project( + name = "tsc", + srcs = glob(["src/*.ts"]), + composite = True, + out_dir = "dist", + root_dir = "src", + tsconfig = ":tsconfig", + validate = select({ + "@platforms//os:windows": False, + "//conditions:default": True, + }), + deps = npm_link_targets(), +) + +npm_package( + name = "pkg", + srcs = [ + "package.json", + ":tsc", + ], + visibility = ["//visibility:public"], +) diff --git a/app/lezer-markdown/package.json b/app/lezer-markdown/package.json index f57ca9a4cd73..2839a5c1782c 100644 --- a/app/lezer-markdown/package.json +++ b/app/lezer-markdown/package.json @@ -16,8 +16,6 @@ "ist": "^1.1.1", "mocha": "^10.2.0", "@lezer/html": "^1.0.0", - "getdocs-ts": "^0.1.0", - "builddocs": "^1.0.0", "@marijn/buildtool": "^0.1.6" }, "dependencies": { @@ -29,9 +27,7 @@ "url" : "https://github.com/lezer-parser/markdown.git" }, "scripts": { - "watch": "node build.js --watch", "prepare": "node build.js", - "test": "mocha", - "build-readme": "node bin/build-readme.cjs" + "test": "mocha" } } diff --git a/app/lezer-markdown/src/extension.ts b/app/lezer-markdown/src/extension.ts index 886c56880170..6dc0efa7c61c 100644 --- a/app/lezer-markdown/src/extension.ts +++ b/app/lezer-markdown/src/extension.ts @@ -2,6 +2,9 @@ import {InlineContext, BlockContext, MarkdownConfig, LeafBlockParser, LeafBlock, Line, Element, space, Punctuation} from "./markdown" import {tags as t} from "@lezer/highlight" +const allowEmptyFormatNodes = true +const allowSpaceBeforeCloseDelimiter = true + const StrikethroughDelim = {resolve: "Strikethrough", mark: "StrikethroughMark"} /// An extension that implements @@ -18,13 +21,16 @@ export const Strikethrough: MarkdownConfig = { parseInline: [{ name: "Strikethrough", parse(cx, next, pos) { - if (next != 126 /* '~' */ || cx.char(pos + 1) != 126 || cx.char(pos + 2) == 126) return -1 - let before = cx.slice(pos - 1, pos), after = cx.slice(pos + 2, pos + 3) - let sBefore = /\s|^$/.test(before), sAfter = /\s|^$/.test(after) + if (next != 126 /* '~' */ || cx.char(pos + 1) != 126 || + (!allowEmptyFormatNodes && cx.char(pos + 2) == 126)) return -1 + let before = cx.slice(pos - 1, pos), after = cx.slice(pos + 2, cx.end) + let sBefore = /\s|^$/.test(before), sAfter = /^(?:\s|$)/.test(after) let pBefore = Punctuation.test(before), pAfter = Punctuation.test(after) + let wAfter = /^[\p{S}|\p{P}]*\p{L}/u.test(after) return cx.addDelimiter(StrikethroughDelim, pos, pos + 2, !sAfter && (!pAfter || sBefore || pBefore), - !sBefore && (!pBefore || sAfter || pAfter)) + (!sBefore && (!pBefore || sAfter || pAfter)) || + (allowSpaceBeforeCloseDelimiter && sBefore && !wAfter)) }, after: "Emphasis" }] diff --git a/app/lezer-markdown/src/markdown.ts b/app/lezer-markdown/src/markdown.ts index 2406d893be7a..e553c93e3f65 100644 --- a/app/lezer-markdown/src/markdown.ts +++ b/app/lezer-markdown/src/markdown.ts @@ -2,6 +2,8 @@ import {Tree, TreeBuffer, NodeType, NodeProp, NodePropSource, TreeFragment, Node Input, Parser, PartialParse, SyntaxNode, ParseWrapper} from "@lezer/common" import {styleTags, tags as t, Tag} from "@lezer/highlight" +const includeSpaceInDelimiterNode = true + class CompositeBlock { static create(type: number, value: number, from: number, parentHash: number, end: number) { let hash = (parentHash + (parentHash << 8) + type + (value << 4)) | 0 @@ -437,7 +439,8 @@ const DefaultBlockParsers: {[name: string]: ((cx: BlockContext, line: Line) => B let size = isBlockquote(line) if (size < 0) return false cx.startContext(Type.Blockquote, line.pos) - cx.addNode(Type.QuoteMark, cx.lineStart + line.pos, cx.lineStart + line.pos + 1) + cx.addNode(Type.QuoteMark, cx.lineStart + line.pos, + cx.lineStart + line.pos + (includeSpaceInDelimiterNode ? size : 1)) line.moveBase(line.pos + size) return null }, @@ -453,6 +456,8 @@ const DefaultBlockParsers: {[name: string]: ((cx: BlockContext, line: Line) => B BulletList(cx, line) { let size = isBulletList(line, cx, false) if (size < 0) return false + if (includeSpaceInDelimiterNode && space(line.text.charCodeAt(line.pos + 1))) + size++ if (cx.block.type != Type.BulletList) cx.startContext(Type.BulletList, line.basePos, line.next) let newBase = getListIndent(line, line.pos + 1) @@ -465,11 +470,12 @@ const DefaultBlockParsers: {[name: string]: ((cx: BlockContext, line: Line) => B OrderedList(cx, line) { let size = isOrderedList(line, cx, false) if (size < 0) return false + let length = size + (includeSpaceInDelimiterNode && space(line.text.charCodeAt(line.pos + size)) ? 1 : 0) if (cx.block.type != Type.OrderedList) cx.startContext(Type.OrderedList, line.basePos, line.text.charCodeAt(line.pos + size - 1)) let newBase = getListIndent(line, line.pos + size) cx.startContext(Type.ListItem, line.basePos, newBase - line.baseIndent) - cx.addNode(Type.ListMark, cx.lineStart + line.pos, cx.lineStart + line.pos + size) + cx.addNode(Type.ListMark, cx.lineStart + line.pos, cx.lineStart + line.pos + length) line.moveBaseColumn(newBase) return null }, @@ -477,6 +483,8 @@ const DefaultBlockParsers: {[name: string]: ((cx: BlockContext, line: Line) => B ATXHeading(cx, line) { let size = isAtxHeading(line) if (size < 0) return false + let level = size + if (includeSpaceInDelimiterNode && space(line.text.charCodeAt(size))) size += 1 let off = line.pos, from = cx.lineStart + off let endOfSpace = skipSpaceBack(line.text, line.text.length, off), after = endOfSpace while (after > off && line.text.charCodeAt(after - 1) == line.next) after-- @@ -485,7 +493,7 @@ const DefaultBlockParsers: {[name: string]: ((cx: BlockContext, line: Line) => B .write(Type.HeaderMark, 0, size) .writeElements(cx.parser.parseInline(line.text.slice(off + size + 1, after), from + size + 1), -from) if (after < line.text.length) buf.write(Type.HeaderMark, after - off, endOfSpace - off) - let node = buf.finish(Type.ATXHeading1 - 1 + size, line.text.length - off) + let node = buf.finish(Type.ATXHeading1 - 1 + level, line.text.length - off) cx.nextLine() cx.addNode(node, from) return true @@ -568,7 +576,8 @@ class LinkReferenceParser implements LeafBlockParser { this.elts.push(elt(Type.LinkMark, this.pos + this.start, this.pos + this.start + 1)) this.pos++ } else if (this.stage == RefStage.Label) { - if (!this.nextStage(parseURL(content, skipSpace(content, this.pos), this.start))) return -1 + let url = parseURL(content, skipSpace(content, this.pos), this.start) + if (!this.nextStage(Array.isArray(url) ? url[1] : url)) return -1 } else if (this.stage == RefStage.Link) { let skip = skipSpace(content, this.pos), end = 0 if (skip > this.pos) { @@ -1538,9 +1547,10 @@ function finishLink(cx: InlineContext, content: Element[], type: Type, start: nu let pos = cx.skipSpace(startPos + 1) let dest = parseURL(text, pos - cx.offset, cx.offset), title if (dest) { - pos = cx.skipSpace(dest.to) + let last = dest[dest.length - 1] + pos = cx.skipSpace(last.to) // The destination and title must be separated by whitespace - if (pos != dest.to) { + if (pos != last.to) { title = parseLinkTitle(text, pos - cx.offset, cx.offset) if (title) pos = cx.skipSpace(title.to) } @@ -1548,7 +1558,7 @@ function finishLink(cx: InlineContext, content: Element[], type: Type, start: nu if (cx.char(pos) == 41 /* ')' */) { content.push(elt(Type.LinkMark, startPos, startPos + 1)) endPos = pos + 1 - if (dest) content.push(dest) + if (dest) content.push(...dest) if (title) content.push(title) content.push(elt(Type.LinkMark, pos, endPos)) } @@ -1566,12 +1576,16 @@ function finishLink(cx: InlineContext, content: Element[], type: Type, start: nu // when parsing fails otherwise (for use in the incremental link // reference parser). -function parseURL(text: string, start: number, offset: number): null | false | Element { +function parseURL(text: string, start: number, offset: number): null | false | Element[] { let next = text.charCodeAt(start) if (next == 60 /* '<' */) { for (let pos = start + 1; pos < text.length; pos++) { let ch = text.charCodeAt(pos) - if (ch == 62 /* '>' */) return elt(Type.URL, start + offset, pos + 1 + offset) + if (ch == 62 /* '>' */) return [ + elt(Type.LinkMark, start + offset, start + offset + 1), + elt(Type.URL, start + offset + 1, pos + offset), + elt(Type.LinkMark, pos + offset, pos + offset + 1), + ] if (ch == 60 || ch == 10 /* '<\n' */) return false } return null @@ -1592,7 +1606,7 @@ function parseURL(text: string, start: number, offset: number): null | false | E escaped = true } } - return pos > start ? elt(Type.URL, start + offset, pos + offset) : pos == text.length ? null : false + return pos > start ? [elt(Type.URL, start + offset, pos + offset)] : pos == text.length ? null : false } } diff --git a/app/lezer-markdown/tsconfig.json b/app/lezer-markdown/tsconfig.json index 82ecf132d69d..20a359813f83 100644 --- a/app/lezer-markdown/tsconfig.json +++ b/app/lezer-markdown/tsconfig.json @@ -5,6 +5,7 @@ "noUnusedLocals": true, "strict": true, "target": "es6", + "composite": true, "module": "es2015", "newLine": "lf", "stripInternal": true, diff --git a/app/ydoc-shared/package.json b/app/ydoc-shared/package.json index 9b568aaf20f8..8c11a6571e63 100644 --- a/app/ydoc-shared/package.json +++ b/app/ydoc-shared/package.json @@ -27,7 +27,7 @@ }, "dependencies": { "@lezer/common": "^1.2.3", - "@lezer/markdown": "^1.3.2", + "@lezer/markdown": "workspace:*", "@lezer/highlight": "^1.2.1", "@noble/hashes": "^1.6.1", "@open-rpc/client-js": "^1.8.1", diff --git a/app/ydoc-shared/src/ast/__tests__/ensoMarkdown.test.ts b/app/ydoc-shared/src/ast/__tests__/ensoMarkdown.test.ts index e07273bd0e8c..f86f41cf02e0 100644 --- a/app/ydoc-shared/src/ast/__tests__/ensoMarkdown.test.ts +++ b/app/ydoc-shared/src/ast/__tests__/ensoMarkdown.test.ts @@ -1,44 +1,152 @@ import { expect, test } from 'vitest' -import { type DebugTree, debugTree, ensoMarkdownParser } from '../ensoMarkdown' +import { type DebugTree, debugTree } from '../../util/lezer' +import { ensoMarkdownParser } from '../ensoMarkdown' -const miscCases = [ +function checkTree({ source, expected }: { source: string; expected: DebugTree }) { + expect(debugTree(ensoMarkdownParser.parse(source), source)).toEqual(expected) +} + +// === Prerendered newlines === + +// Test cases for prerendered newlines. In order to support live-preview editing, our working representation differs +// from standard Markdown in its treatment of newlines. These tests cover the modifications to the parser to operate on +// this internal representation. + +test.each([ { - source: 'some text', - expected: ['Document', ['Paragraph', 'some text']], + source: 'Newline\nstarts new paragraph', + expected: ['Document', ['Paragraph', 'Newline'], ['Paragraph', 'starts new paragraph']], }, { - source: '[Link without URL]', - expected: ['Document', ['Paragraph', ['Link', ['LinkMark', '['], ['LinkMark', ']']]]], + source: '*No multiline\nitalic*', + expected: ['Document', ['Paragraph', '*No multiline'], ['Paragraph', 'italic*']], }, { - source: '[Link text](https://url)', + source: '- List\n Non-list child', expected: [ 'Document', [ - 'Paragraph', - [ - 'Link', - ['LinkMark', '['], - ['LinkMark', ']'], - ['LinkMark', '('], - ['URL', 'https://url'], - ['LinkMark', ')'], - ], + 'BulletList', + ['ListItem', ['ListMark', '- '], ['Paragraph', 'List'], ['Paragraph', 'Non-list child']], ], ], }, { - source: '[Link text](https://url*bold here prevents the parens from being a URL*)', + source: '- List\n - Sublist\n Non-list child', expected: [ 'Document', [ - 'Paragraph', - ['Link', ['LinkMark', '['], ['LinkMark', ']']], - ['Emphasis', ['EmphasisMark', '*'], ['EmphasisMark', '*']], + 'BulletList', + [ + 'ListItem', + ['ListMark', '- '], + ['Paragraph', 'List'], + ['BulletList', ['ListItem', ['ListMark', '- '], ['Paragraph', 'Sublist']]], + ['Paragraph', 'Non-list child'], + ], ], ], }, +])('Syntax nonstandardism: Prerendered newlines: $source', checkTree) + +// === Whitespace handling === + +// To support live-preview editing, we treat "syntactic" spaces as part of the delimiters so that they can be hidden or +// shown together. + +test.each([ + { + source: '# Header', + expected: ['Document', ['ATXHeading1', ['HeaderMark', '# ']]], + not: ['Document', ['ATXHeading1', ['HeaderMark', '#']]], + }, { + source: '## Header', + expected: ['Document', ['ATXHeading2', ['HeaderMark', '## ']]], + not: ['Document', ['ATXHeading2', ['HeaderMark', '##']]], + }, + { + source: '### Header', + expected: ['Document', ['ATXHeading3', ['HeaderMark', '### ']]], + not: ['Document', ['ATXHeading3', ['HeaderMark', '###']]], + }, + { + source: '> Quoted', + expected: ['Document', ['Blockquote', ['QuoteMark', '> '], ['Paragraph', 'Quoted']]], + not: ['Document', ['Blockquote', ['QuoteMark', '>'], ['Paragraph', 'Quoted']]], + }, + { + source: '- Bullet', + expected: [ + 'Document', + ['BulletList', ['ListItem', ['ListMark', '- '], ['Paragraph', 'Bullet']]], + ], + not: ['Document', ['BulletList', ['ListItem', ['ListMark', '-'], ['Paragraph', 'Bullet']]]], + }, + { + source: '1. Numbered', + expected: [ + 'Document', + ['OrderedList', ['ListItem', ['ListMark', '1. '], ['Paragraph', 'Numbered']]], + ], + not: ['Document', ['OrderedList', ['ListItem', ['ListMark', '1.'], ['Paragraph', 'Numbered']]]], + }, +])('Syntax extension: Delimiter tokens include syntactic spaces: $source', checkTree) + +// === "Incomplete" syntax special cases === + +// These cases cover modifications to the parser that improve the editing experience by improving the parsing of +// "incomplete" syntax cases. Some constructs that may occur while editing a document are interpreted by the standard +// parser as a different syntax than the syntax that will be recognized when the edit is completed. In a +// syntax-highlighting editor, or especially in a live-preview editor, these intermediate states can be distracting. +// These tests cover customizations to the parser to either not recognize these cases as a secondary syntax, or to +// recognize the case as the same syntax that will likely be present when the edit is completed. + +test.each([ + { + description: 'Empty bullet not parsed as setext heading', + source: '-\nEmpty bullet, not a setext heading', + expected: [ + 'Document', + ['BulletList', ['ListItem', ['ListMark', '-'], ['Paragraph', '']]], + ['Paragraph', 'Empty bullet, not a setext heading'], + ], + not: [ + /* TODO */ + ], + }, + { + description: 'Empty bold not parsed as HorizontalRule', + source: '****', + // TODO + expected: ['Document', ['HorizontalRule', '****']], + not: ['Document', ['HorizontalRule', '****']], + }, + { + description: 'Empty bold+italic not parsed as HorizontalRule', + source: '******', + // TODO + expected: ['Document', ['HorizontalRule', '******']], + not: ['Document', ['HorizontalRule', '******']], + }, + { + description: 'Empty strikethrough not parsed as FencedCode', + source: '~~~~', + // TODO + // expected: ['Document', ['Paragraph', '~~~~']], + expected: ['Document', ['FencedCode', ['CodeMark', '~~~~']]], + not: ['Document', ['FencedCode', ['CodeMark', '~~~~']]], + }, +])('Syntax extensions: Special cases: $description', checkTree) + +// === Generic parser improvements === + +// These cases cover improvements to the parser that are not specific to our use case; they may be considered for +// upstream PR, although AST changes are generally compatibility-breaking. + +test.each([ + { + description: 'LinkMarks distinguished from URL content', source: '[Link text]()', expected: [ 'Document', @@ -56,10 +164,7 @@ const miscCases = [ ], ], ], - }, - { - source: '[Link text]()', - expected: [ + not: [ 'Document', [ 'Paragraph', @@ -68,111 +173,52 @@ const miscCases = [ ['LinkMark', '['], ['LinkMark', ']'], ['LinkMark', '('], - ['LinkMark', '<'], - ['URL', 'https://url/*not bold*'], - ['LinkMark', '>'], + ['URL', ''], ['LinkMark', ')'], ], ], ], }, { - source: '[*Italic link text*](https://url)', + description: 'LinkMarks distinguished from URL content in image', + source: '![Image]()', expected: [ 'Document', [ 'Paragraph', [ - 'Link', - ['LinkMark', '['], - ['Emphasis', ['EmphasisMark', '*'], ['EmphasisMark', '*']], + 'Image', + ['LinkMark', '!['], ['LinkMark', ']'], ['LinkMark', '('], + ['LinkMark', '<'], ['URL', 'https://url'], + ['LinkMark', '>'], ['LinkMark', ')'], ], ], ], - }, - { - source: '', - expected: [ - 'Document', - ['Paragraph', ['Autolink', ['LinkMark', '<'], ['URL', 'https://url'], ['LinkMark', '>']]], - ], - }, - { - source: '', - expected: [ - 'Document', - ['Paragraph', ['Emphasis', ['EmphasisMark', '*'], ['EmphasisMark', '*']]], - ], - }, - { - source: '1. List', - expected: [ - 'Document', - ['OrderedList', ['ListItem', ['ListMark', '1. '], ['Paragraph', 'List']]], - ], - }, - { - source: '1. List\n 1. Sublist', - expected: [ - 'Document', - [ - 'OrderedList', - [ - 'ListItem', - ['ListMark', '1. '], - ['Paragraph', 'List'], - ['OrderedList', ['ListItem', ['ListMark', '1. '], ['Paragraph', 'Sublist']]], - ], - ], - ], - }, - { - source: '- List', - expected: ['Document', ['BulletList', ['ListItem', ['ListMark', '- '], ['Paragraph', 'List']]]], - }, - { - source: '- List\n - Sublist', - expected: [ + not: [ 'Document', [ - 'BulletList', + 'Paragraph', [ - 'ListItem', - ['ListMark', '- '], - ['Paragraph', 'List'], - ['BulletList', ['ListItem', ['ListMark', '- '], ['Paragraph', 'Sublist']]], + 'Image', + ['LinkMark', '!['], + ['LinkMark', ']'], + ['LinkMark', '('], + ['URL', ''], + ['LinkMark', ')'], ], ], ], }, - { - source: '```enso\nmain = 42\n```', - expected: [ - 'Document', - [ - 'FencedCode', - ['CodeMark', '```'], - ['CodeInfo', 'enso'], - ['CodeText', 'main = 42'], - ['CodeMark', '```'], - ], - ], - }, - { - source: ' main = 42', - expected: ['Document', ['CodeBlock', ['CodeText', 'main = 42']]], - }, -] +])('Standards-compatible AST refinements: $description', checkTree) -function checkTree({ source, expected }: { source: string; expected: DebugTree }) { - expect(debugTree(ensoMarkdownParser.parse(source), source)).toEqual(expected) -} +// === Standard syntax cases === -test.each(miscCases)('Enso Markdown tree structure: $source', checkTree) +// These cases are not affected by our parser customizations (except for inclusion of extensions, like Strikethrough). +// They are included here mainly as a reference for lezer-markdown's syntax trees. test.each([ { @@ -187,22 +233,6 @@ test.each([ source: 'Not emphasis without content: ******', expected: ['Document', ['Paragraph', 'Not emphasis without content: ******']], }, - { - // TODO: We should stop the horizontal rule parser from handling this, because it conflicts with cursor-formatting. - source: '****', - expected: ['Document', ['HorizontalRule', '****']], - }, - { - // TODO: We should stop the horizontal rule parser from handling this, because it conflicts with cursor-formatting. - source: '******', - expected: ['Document', ['HorizontalRule', '******']], - }, - { - // TODO: We should stop the code block parser from handling this, because it conflicts with cursor-formatting case. - source: '~~~~', - expected: ['Document', ['FencedCode', ['CodeMark', '~~~~']]], - // expected: ['Document', ['Paragraph', '~~~~']], - }, { source: 'Empty strikethrough: ~~~~', expected: [ @@ -519,25 +549,101 @@ test.each([ test.each([ { - source: 'Newline\nstarts new paragraph', - expected: ['Document', ['Paragraph', 'Newline'], ['Paragraph', 'starts new paragraph']], + source: 'some text', + expected: ['Document', ['Paragraph', 'some text']], }, { - source: '*No multiline\nitalic*', - expected: ['Document', ['Paragraph', '*No multiline'], ['Paragraph', 'italic*']], + source: '[Link without URL]', + expected: ['Document', ['Paragraph', ['Link', ['LinkMark', '['], ['LinkMark', ']']]]], }, { - source: '- List\n Non-list child', + source: '[Link text](https://url)', expected: [ 'Document', [ - 'BulletList', - ['ListItem', ['ListMark', '- '], ['Paragraph', 'List'], ['Paragraph', 'Non-list child']], + 'Paragraph', + [ + 'Link', + ['LinkMark', '['], + ['LinkMark', ']'], + ['LinkMark', '('], + ['URL', 'https://url'], + ['LinkMark', ')'], + ], ], ], }, { - source: '- List\n - Sublist\n Non-list child', + source: '[Link text](https://url*bold here prevents the parens from being a URL*)', + expected: [ + 'Document', + [ + 'Paragraph', + ['Link', ['LinkMark', '['], ['LinkMark', ']']], + ['Emphasis', ['EmphasisMark', '*'], ['EmphasisMark', '*']], + ], + ], + }, + { + source: '[*Italic link text*](https://url)', + expected: [ + 'Document', + [ + 'Paragraph', + [ + 'Link', + ['LinkMark', '['], + ['Emphasis', ['EmphasisMark', '*'], ['EmphasisMark', '*']], + ['LinkMark', ']'], + ['LinkMark', '('], + ['URL', 'https://url'], + ['LinkMark', ')'], + ], + ], + ], + }, + { + source: '', + expected: [ + 'Document', + ['Paragraph', ['Autolink', ['LinkMark', '<'], ['URL', 'https://url'], ['LinkMark', '>']]], + ], + }, + { + source: '', + expected: [ + 'Document', + ['Paragraph', ['Emphasis', ['EmphasisMark', '*'], ['EmphasisMark', '*']]], + ], + }, + { + source: '1. List', + expected: [ + 'Document', + ['OrderedList', ['ListItem', ['ListMark', '1. '], ['Paragraph', 'List']]], + ], + }, + { + source: '1. List\n 1. Sublist', + expected: [ + 'Document', + [ + 'OrderedList', + [ + 'ListItem', + ['ListMark', '1. '], + ['Paragraph', 'List'], + ['OrderedList', ['ListItem', ['ListMark', '1. '], ['Paragraph', 'Sublist']]], + ], + ], + ], + }, + { + source: '- List', + expected: ['Document', ['BulletList', ['ListItem', ['ListMark', '- '], ['Paragraph', 'List']]]], + }, + { + source: '- List\n - Sublist', expected: [ 'Document', [ @@ -547,9 +653,25 @@ test.each([ ['ListMark', '- '], ['Paragraph', 'List'], ['BulletList', ['ListItem', ['ListMark', '- '], ['Paragraph', 'Sublist']]], - ['Paragraph', 'Non-list child'], ], ], ], }, -])('Prerendered newlines: $source', checkTree) + { + source: '```enso\nmain = 42\n```', + expected: [ + 'Document', + [ + 'FencedCode', + ['CodeMark', '```'], + ['CodeInfo', 'enso'], + ['CodeText', 'main = 42'], + ['CodeMark', '```'], + ], + ], + }, + { + source: ' main = 42', + expected: ['Document', ['CodeBlock', ['CodeText', 'main = 42']]], + }, +])('Markdown syntax tree: $source', checkTree) diff --git a/app/ydoc-shared/src/ast/ensoMarkdown.ts b/app/ydoc-shared/src/ast/ensoMarkdown.ts index 239911440a6f..c502993852f0 100644 --- a/app/ydoc-shared/src/ast/ensoMarkdown.ts +++ b/app/ydoc-shared/src/ast/ensoMarkdown.ts @@ -1,156 +1,15 @@ -import { Tree, TreeBuffer, TreeCursor } from '@lezer/common' -import { tags } from '@lezer/highlight' +/** + * @file Define the Enso Markdown dialect. We customize @lezer/markdown in two different ways: Changes that can easily + * be made through its configuration interface are applied here. Changes that are impossible or overly complicated to + * achieve via the public interface are implemented by modifying our implementation of the package. + */ import { parser as commonmarkParser, - Element, + Strikethrough, Table, - type BlockContext, type BlockParser, - type DelimiterType, - type InlineContext, - type InlineDelimiter, - type InlineParser, - type Line, - type MarkdownConfig, - type MarkdownParser, - type NodeSpec, + type MarkdownExtension, } from '@lezer/markdown' -import { assertDefined } from '../util/assert' - -/** - * Private lezer-markdown symbols used by lezer-markdown parsers we have customized versions of. - */ -declare module '@lezer/markdown' { - export interface BlockContext { - block: CompositeBlock - stack: CompositeBlock[] - readonly buffer: Buffer - - addNode: (block: number | Tree, from: number, to?: number) => void - startContext: (type: number, start: number, value?: number) => void - } - - export interface CompositeBlock { - readonly type: number - // Used for indentation in list items, markup character in lists - readonly value: number - readonly from: number - readonly hash: number - end: number - readonly children: (Tree | TreeBuffer)[] - readonly positions: number[] - } - - export interface Buffer { - content: number[] - nodes: Tree[] - - write: (type: number, from: number, to: number, children?: number) => Buffer - writeElements: (elts: readonly Element[], offset?: number) => Buffer - finish: (type: number, length: number) => Tree - } - - export interface InlineDelimiter { - readonly type: DelimiterType - readonly from: number - readonly to: number - side: Mark - } - - export interface InlineContext { - parts: (Element | InlineDelimiter | null)[] - } -} - -const Punctuation = /[\p{S}|\p{P}]/u - -function getType({ parser }: { parser: MarkdownParser }, name: string) { - const ty = parser.nodeSet.types.find((ty) => ty.name === name) - assertDefined(ty) - return ty.id -} - -/** Parser override to include the space in the delimiter. */ -const headerParser: BlockParser = { - name: 'ATXHeading', - parse: (cx, line) => { - let size = isAtxHeading(line) - if (size < 0) return false - const level = size - // If the character after the hashes is a space, treat it as part of the `HeaderMark`. - if (isSpace(line.text.charCodeAt(size))) size += 1 - const off = line.pos - const from = cx.lineStart + off - // Trailing spaces at EOL - const endOfSpace = skipSpaceBack(line.text, line.text.length, off) - let after = endOfSpace - // Trailing sequence of # (before EOL spaces) - while (after > off && line.text.charCodeAt(after - 1) == line.next) after-- - if (after == endOfSpace || after == off || !isSpace(line.text.charCodeAt(after - 1))) - after = line.text.length - const headerMark = getType(cx, 'HeaderMark') - const buf = cx.buffer - .write(headerMark, 0, size) - .writeElements(cx.parser.parseInline(line.text.slice(off + size, after), from + size), -from) - if (after < line.text.length) buf.write(headerMark, after - off, endOfSpace - off) - const node = buf.finish(getType(cx, `ATXHeading${level}`), line.text.length - off) - cx.nextLine() - cx.addNode(node, from) - return true - }, -} - -/** Parser override to include the space in the delimiter. */ -const bulletList: BlockParser = { - name: 'BulletList', - parse: (cx, line) => { - const size = isBulletList(line, cx, false) - if (size < 0) return false - const length = size + (isSpace(line.text.charCodeAt(line.pos + 1)) ? 1 : 0) - const bulletList = getType(cx, 'BulletList') - if (cx.block.type != bulletList) cx.startContext(bulletList, line.basePos, line.next) - const newBase = getListIndent(line, line.pos + 1) - cx.startContext(getType(cx, 'ListItem'), line.basePos, newBase - line.baseIndent) - cx.addNode(getType(cx, 'ListMark'), cx.lineStart + line.pos, cx.lineStart + line.pos + length) - line.moveBaseColumn(newBase) - return null - }, -} - -/** Parser override to include the space in the delimiter. */ -const orderedList: BlockParser = { - name: 'OrderedList', - parse: (cx, line) => { - const size = isOrderedList(line, cx, false) - if (size < 0) return false - const length = size + (isSpace(line.text.charCodeAt(line.pos + size)) ? 1 : 0) - const orderedList = getType(cx, 'OrderedList') - if (cx.block.type != orderedList) - cx.startContext(orderedList, line.basePos, line.text.charCodeAt(line.pos + size - 1)) - const newBase = getListIndent(line, line.pos + size) - cx.startContext(getType(cx, 'ListItem'), line.basePos, newBase - line.baseIndent) - cx.addNode(getType(cx, 'ListMark'), cx.lineStart + line.pos, cx.lineStart + line.pos + length) - line.moveBaseColumn(newBase) - return null - }, -} - -const ENSO_BLOCKQUOTE_TYPE = 'EnsoBlockquote' - -/** Parser override to include the space in the delimiter. */ -const blockquoteParser: BlockParser = { - name: ENSO_BLOCKQUOTE_TYPE, - parse: (cx, line) => { - const size = isBlockquote(line) - if (size < 0) return false - const type = getType(cx, ENSO_BLOCKQUOTE_TYPE) - cx.startContext(type, line.pos) - cx.addNode(getType(cx, 'QuoteMark'), cx.lineStart + line.pos, cx.lineStart + line.pos + size) - line.moveBase(line.pos + size) - return null - }, - before: 'Blockquote', -} /** * End any element when a newline is encountered. This parser operates on preprocessed Markdown that has "prerendered" @@ -162,433 +21,18 @@ const newlineEndsBlock: BlockParser = { endLeaf: () => true, } -/** - * Replaces setext heading parser with a parser that never matches. - * - * When starting a bulleted list, the `SetextHeading` parser can match when a `-` has been typed and a following space - * hasn't been entered yet; the resulting style changes are distracting. To prevent this, we don't support setext - * headings; ATX headings seem to be much more popular anyway. - */ -const disableSetextHeading: BlockParser = { - name: 'SetextHeading', - parse: () => false, -} - -const blockquoteNode: NodeSpec = { - name: ENSO_BLOCKQUOTE_TYPE, - block: true, - composite: (cx, line) => { - if (line.next != 62 /* '>' */) return false - const size = isSpace(line.text.charCodeAt(line.pos + 1)) ? 2 : 1 - line.addMarker( - elt(getType(cx, 'QuoteMark'), cx.lineStart + line.pos, cx.lineStart + line.pos + size), - ) - line.moveBase(line.pos + size) - //bl.end = cx.lineStart + line.text.length - return true - }, -} - -function elt(type: number, from: number, to: number, children?: readonly Element[]): Element { - return new (Element as any)(type, from, to, children) -} - -function isBlockquote(line: Line) { - return ( - line.next != 62 /* '>' */ ? -1 - : line.text.charCodeAt(line.pos + 1) == 32 ? 2 - : 1 - ) -} - -function isBulletList(line: Line, cx: BlockContext, breaking: boolean) { - return ( - (line.next == 45 || line.next == 43 || line.next == 42) /* '-+*' */ && - (line.pos == line.text.length - 1 || isSpace(line.text.charCodeAt(line.pos + 1))) && - (!breaking || inList(cx, 'BulletList') || line.skipSpace(line.pos + 2) < line.text.length) - ) ? - 1 - : -1 -} - -function isOrderedList(line: Line, cx: BlockContext, breaking: boolean) { - let pos = line.pos - let next = line.next - for (;;) { - if (next >= 48 && next <= 57 /* '0-9' */) pos++ - else break - if (pos == line.text.length) return -1 - next = line.text.charCodeAt(pos) - } - if ( - pos == line.pos || - pos > line.pos + 9 || - (next != 46 && next != 41) /* '.)' */ || - (pos < line.text.length - 1 && !isSpace(line.text.charCodeAt(pos + 1))) || - (breaking && - !inList(cx, 'OrderedList') && - (line.skipSpace(pos + 1) == line.text.length || - pos > line.pos + 1 || - line.next != 49)) /* '1' */ - ) - return -1 - return pos + 1 - line.pos -} - -function inList(cx: BlockContext, typeName: string) { - const type = getType(cx, typeName) - for (let i = cx.stack.length - 1; i >= 0; i--) if (cx.stack[i]!.type == type) return true - return false -} - -function getListIndent(line: Line, pos: number) { - const indentAfter = line.countIndent(pos, line.pos, line.indent) - const indented = line.countIndent(line.skipSpace(pos), pos, indentAfter) - return indented >= indentAfter + 5 ? indentAfter + 1 : indented -} - -// === Link === - -const enum Mark { - None = 0, - Open = 1, - Close = 2, -} - -const LinkStart: DelimiterType = {} -const ImageStart: DelimiterType = {} - -const linkParser: InlineParser = { - name: 'Link', - parse: (cx, next, start) => { - return next == 91 /* '[' */ ? cx.addDelimiter(LinkStart, start, start + 1, true, false) : -1 - }, -} - -const imageParser: InlineParser = { - name: 'Image', - parse: (cx, next, start) => { - return next == 33 /* '!' */ && cx.char(start + 1) == 91 /* '[' */ ? - cx.addDelimiter(ImageStart, start, start + 2, true, false) - : -1 - }, -} - -const linkEndParser: InlineParser = { - name: 'LinkEnd', - parse: (cx, next, start) => { - if (next != 93 /* ']' */) return -1 - // Scanning back to the next link/image start marker - const openDelim = cx.findOpeningDelimiter(LinkStart) ?? cx.findOpeningDelimiter(ImageStart) - if (openDelim == null) return -1 - const part = cx.parts[openDelim] as InlineDelimiter - // If this one has been set invalid (because it would produce - // a nested link) or there's no valid link here ignore both. - if ( - !part.side || - (cx.skipSpace(part.to) == start && !/[([]/.test(cx.slice(start + 1, start + 2))) - ) { - cx.parts[openDelim] = null - return -1 - } - // Finish the content and replace the entire range in - // this.parts with the link/image node. - const content = cx.takeContent(openDelim) - const link = (cx.parts[openDelim] = finishLink( - cx, - content, - part.type == LinkStart ? getType(cx, 'Link') : getType(cx, 'Image'), - part.from, - start + 1, - )) - // Set any open-link markers before this link to invalid. - if (part.type == LinkStart) - for (let j = 0; j < openDelim; j++) { - const p = cx.parts[j] - if (p != null && !(p instanceof Element) && p.type == LinkStart) p.side = Mark.None - } - return link.to - }, -} - -function finishLink( - cx: InlineContext, - content: Element[], - type: number, - start: number, - startPos: number, -) { - const { text } = cx, - next = cx.char(startPos) - let endPos = startPos - const LinkMarkType = getType(cx, 'LinkMark') - const ImageType = getType(cx, 'Image') - content.unshift(elt(LinkMarkType, start, start + (type == ImageType ? 2 : 1))) - content.push(elt(LinkMarkType, startPos - 1, startPos)) - if (next == 40 /* '(' */) { - let pos = cx.skipSpace(startPos + 1) - const dest = parseURL(text, pos - cx.offset, cx.offset, getType(cx, 'URL'), LinkMarkType) - let title - if (dest) { - const last = dest.at(-1)! - pos = cx.skipSpace(last.to) - // The destination and title must be separated by whitespace - if (pos != last.to) { - title = parseLinkTitle(text, pos - cx.offset, cx.offset, getType(cx, 'LinkTitle')) - if (title) pos = cx.skipSpace(title.to) - } - } - if (cx.char(pos) == 41 /* ')' */) { - content.push(elt(LinkMarkType, startPos, startPos + 1)) - endPos = pos + 1 - if (dest) content.push(...dest) - if (title) content.push(title) - content.push(elt(LinkMarkType, pos, endPos)) - } - } else if (next == 91 /* '[' */) { - const label = parseLinkLabel( - text, - startPos - cx.offset, - cx.offset, - false, - getType(cx, 'LinkLabelType'), - ) - if (label) { - content.push(label) - endPos = label.to - } - } - return elt(type, start, endPos, content) -} - -// These return `null` when falling off the end of the input, `false` -// when parsing fails otherwise (for use in the incremental link -// reference parser). -function parseURL( - text: string, - start: number, - offset: number, - urlType: number, - linkMarkType: number, -): null | false | Element[] { - const next = text.charCodeAt(start) - if (next == 60 /* '<' */) { - for (let pos = start + 1; pos < text.length; pos++) { - const ch = text.charCodeAt(pos) - if (ch == 62 /* '>' */) - return [ - elt(linkMarkType, start + offset, start + offset + 1), - elt(urlType, start + offset + 1, pos + offset), - elt(linkMarkType, pos + offset, pos + offset + 1), - ] - if (ch == 60 || ch == 10 /* '<\n' */) return false - } - return null - } else { - let depth = 0, - pos = start - for (let escaped = false; pos < text.length; pos++) { - const ch = text.charCodeAt(pos) - if (isSpace(ch)) { - break - } else if (escaped) { - escaped = false - } else if (ch == 40 /* '(' */) { - depth++ - } else if (ch == 41 /* ')' */) { - if (!depth) break - depth-- - } else if (ch == 92 /* '\\' */) { - escaped = true - } - } - return ( - pos > start ? [elt(urlType, start + offset, pos + offset)] - : pos == text.length ? null - : false - ) - } -} - -function parseLinkTitle( - text: string, - start: number, - offset: number, - linkTitleType: number, -): null | false | Element { - const next = text.charCodeAt(start) - if (next != 39 && next != 34 && next != 40 /* '"\'(' */) return false - const end = next == 40 ? 41 : next - for (let pos = start + 1, escaped = false; pos < text.length; pos++) { - const ch = text.charCodeAt(pos) - if (escaped) escaped = false - else if (ch == end) return elt(linkTitleType, start + offset, pos + 1 + offset) - else if (ch == 92 /* '\\' */) escaped = true - } - return null -} - -function parseLinkLabel( - text: string, - start: number, - offset: number, - requireNonWS: boolean, - linkLabelType: number, -): null | false | Element { - for ( - let escaped = false, pos = start + 1, end = Math.min(text.length, pos + 999); - pos < end; - pos++ - ) { - const ch = text.charCodeAt(pos) - if (escaped) escaped = false - else if (ch == 93 /* ']' */) - return requireNonWS ? false : elt(linkLabelType, start + offset, pos + 1 + offset) - else { - if (requireNonWS && !isSpace(ch)) requireNonWS = false - if (ch == 91 /* '[' */) return false - else if (ch == 92 /* '\\' */) escaped = true - } - } - return null -} - -// === Debugging === - -/** Represents the structure of a @{link Tree} in a JSON-compatible format. */ -export type DebugTree = (string | DebugTree)[] - -/** @returns A debug representation of the provided {@link Tree} */ -export function debugTree(tree: { cursor: () => TreeCursor }, doc: string): DebugTree { - const cursor = tree.cursor() - let current: (string | DebugTree)[] = [] - const stack: (string | DebugTree)[][] = [] - cursor.iterate( - (node) => { - const child: (string | DebugTree)[] = [node.name] - current.push(child) - stack.push(current) - current = child - }, - (node) => { - if (current.length === 1) current.push(doc.slice(node.from, node.to)) - current = stack.pop()! - }, - ) - return current[0]! as DebugTree -} - -// === Helpers === - -function skipSpaceBack(line: string, i: number, to: number) { - while (i > to && isSpace(line.charCodeAt(i - 1))) i-- - return i -} - -/** Returns the number of hash marks at the beginning of the line, or -1 if it is not in the range [1, 6] */ -function isAtxHeading(line: Line) { - if (line.next != 35 /* '#' */) return -1 - let pos = line.pos + 1 - while (pos < line.text.length && line.text.charCodeAt(pos) == 35) pos++ - if (pos < line.text.length && line.text.charCodeAt(pos) != 32) return -1 - const size = pos - line.pos - return size > 6 ? -1 : size -} - -function isSpace(ch: number) { - return ch == 32 || ch == 9 || ch == 10 || ch == 13 -} - -/* -// FIXME: We can't override this parser because lezer-markdown doesn't expose its versions of `EmphasisUnderscore` and -// `EmphasisAsterisk`, which are special-cased in `resolveMarkers`. -const EmphasisUnderscore: DelimiterType = {resolve: "Emphasis", mark: "EmphasisMark"} -const EmphasisAsterisk: DelimiterType = {resolve: "Emphasis", mark: "EmphasisMark"} -const emphasisParser: InlineParser = { - name: 'Emphasis', - parse: (cx, next, start) => { - if (next != 95 && next != 42) return -1 - let pos = start + 1 - while (cx.char(pos) == next) pos++ - const before = cx.slice(start - 1, start), after = cx.slice(pos, pos + 1) - const pBefore = Punctuation.test(before), pAfter = Punctuation.test(after) - const sBefore = /\s|^$/.test(before), sAfter = /\s|^$/.test(after) - const leftFlanking = !sAfter && (!pAfter || sBefore || pBefore) - const rightFlanking = !sBefore && (!pBefore || sAfter || pAfter) - const canOpen = leftFlanking && (next == 42 || !rightFlanking || pBefore) - const canClose = rightFlanking && (next == 42 || !leftFlanking || pAfter) - return cx.addDelimiter(next == 95 ? EmphasisUnderscore : EmphasisAsterisk, start, pos, canOpen, canClose) - } -} - */ - -const ensoMarkdownLanguageExtension = { - parseBlock: [ - headerParser, - bulletList, - orderedList, - blockquoteParser, - disableSetextHeading, - newlineEndsBlock, - ], - parseInline: [linkParser, imageParser, linkEndParser], - defineNodes: [blockquoteNode], -} - -const StrikethroughDelim = { resolve: 'Strikethrough', mark: 'StrikethroughMark' } - -/** - * GFM-style strikethrough delimiters, with slightly relaxed syntax: - * - Empty format nodes are allowed. - * - A closing strikethrough mark is allowed to be preceded by a space if it isn't followed by a letter after zero or - * more punctuation characters. - */ -export const Strikethrough: MarkdownConfig = { - defineNodes: [ - { - name: 'Strikethrough', - style: { 'Strikethrough/...': tags.strikethrough }, - }, - { - name: 'StrikethroughMark', - style: tags.processingInstruction, - }, - ], - parseInline: [ - { - name: 'Strikethrough', - parse(cx, next, pos) { - if (next != 126 /* '~' */ || cx.char(pos + 1) != 126) return -1 - const before = cx.slice(pos - 1, pos), - after = cx.slice(pos + 2, cx.end) - const sBefore = /\s|^$/.test(before), - sAfter = /^(?:\s|$)/.test(after) - const pBefore = Punctuation.test(before), - pAfter = Punctuation.test(after) - const wAfter = /^[\p{S}|\p{P}]*\p{L}/u.test(after) - return cx.addDelimiter( - StrikethroughDelim, - pos, - pos + 2, - !sAfter && (!pAfter || sBefore || pBefore), - (!sBefore && (!pBefore || sAfter || pAfter)) || (sBefore && !wAfter), - ) - }, - after: 'Emphasis', - }, - ], -} - -/** - * Lezer (CodeMirror) parser for the Enso documentation Markdown dialect. - * Differences from CodeMirror's base Markdown language: - * - It defines the flavor of Markdown supported in Enso documentation. Currently, this is mostly CommonMark except we - * don't support setext headings. Planned features include support for some GFM extensions. - * - Many of the parsers differ from the `@lezer/markdown` parsers in their treatment of whitespace, in order to support - * a rendering mode where markup (and some associated spacing) is hidden. - */ -export const ensoMarkdownParser = commonmarkParser.configure([ +/** @lezer/markdown extension for the Markdown dialect used in the Enso documentation editor. */ +export const ensoMarkdownExtension: MarkdownExtension = [ Table, Strikethrough, - ensoMarkdownLanguageExtension, -]) + { parseBlock: [newlineEndsBlock] }, + /** + * When starting a bulleted list, the `SetextHeading` parser can match when a `-` has been typed and a following space + * hasn't been entered yet; the resulting style changes are distracting. To prevent this, we don't support setext + * headings; ATX headings seem to be much more popular anyway. + */ + { remove: ['SetextHeading'] }, +] + +/** Headless @lezer/markdown parser for the Markdown dialect used in the Enso documentation editor. */ +export const ensoMarkdownParser = commonmarkParser.configure(ensoMarkdownExtension) diff --git a/app/ydoc-shared/src/util/lezer.ts b/app/ydoc-shared/src/util/lezer.ts index da222c76bbd7..8d1c2a408b36 100644 --- a/app/ydoc-shared/src/util/lezer.ts +++ b/app/ydoc-shared/src/util/lezer.ts @@ -17,3 +17,26 @@ export function* syntaxNodeAncestors(syn: SyntaxNode | null) { currentSyn = currentSyn.parent } } + +/** Represents the structure of a @{link Tree} in a JSON-compatible format. */ +export type DebugTree = (string | DebugTree)[] + +/** @returns A debug representation of the provided {@link Tree} */ +export function debugTree(tree: { cursor: () => TreeCursor }, doc: string): DebugTree { + const cursor = tree.cursor() + let current: (string | DebugTree)[] = [] + const stack: (string | DebugTree)[][] = [] + cursor.iterate( + (node) => { + const child: (string | DebugTree)[] = [node.name] + current.push(child) + stack.push(current) + current = child + }, + (node) => { + if (current.length === 1) current.push(doc.slice(node.from, node.to)) + current = stack.pop()! + }, + ) + return current[0]! as DebugTree +} diff --git a/eslint.config.mjs b/eslint.config.mjs index 2dced4edaf8c..053403075949 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -204,6 +204,10 @@ const config = [ 'app/rust-ffi/pkg/', ], }, + { + // Based on a 3rd party library that doesn't use ESLint. + ignores: ['app/lang-markdown/', 'app/lezer-markdown/'], + }, eslintJs.configs.recommended, ...pluginVue.configs['flat/recommended'], ...vueTsEslintConfig(), diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 730faa95de4b..e0954f1599bb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -140,8 +140,8 @@ importers: specifier: ^6.7.1 version: 6.7.1 '@codemirror/lang-markdown': - specifier: ^6.3.1 - version: 6.3.1 + specifier: workspace:* + version: link:../lang-markdown '@codemirror/language': specifier: ^6.10.8 version: 6.10.8 @@ -724,13 +724,63 @@ importers: specifier: ^1.1.5 version: 1.1.5 + app/lang-markdown: + dependencies: + '@codemirror/autocomplete': + specifier: ^6.7.1 + version: 6.18.4 + '@codemirror/lang-html': + specifier: ^6.0.0 + version: 6.4.9 + '@codemirror/language': + specifier: ^6.3.0 + version: 6.10.8 + '@codemirror/state': + specifier: ^6.0.0 + version: 6.5.0 + '@codemirror/view': + specifier: ^6.0.0 + version: 6.36.1 + '@lezer/common': + specifier: ^1.2.1 + version: 1.2.3 + '@lezer/markdown': + specifier: workspace:* + version: link:../lezer-markdown + devDependencies: + '@codemirror/buildhelper': + specifier: ^1.0.0 + version: 1.0.2 + + app/lezer-markdown: + dependencies: + '@lezer/common': + specifier: ^1.0.0 + version: 1.2.3 + '@lezer/highlight': + specifier: ^1.0.0 + version: 1.2.1 + devDependencies: + '@lezer/html': + specifier: ^1.0.0 + version: 1.3.10 + '@marijn/buildtool': + specifier: ^0.1.6 + version: 0.1.6 + ist: + specifier: ^1.1.1 + version: 1.1.7 + mocha: + specifier: ^10.2.0 + version: 10.8.2 + app/rust-ffi: {} app/ydoc-server: dependencies: debug: specifier: ^4.4.0 - version: 4.4.0 + version: 4.4.0(supports-color@8.1.1) fast-diff: specifier: ^1.3.0 version: 1.3.0 @@ -828,8 +878,8 @@ importers: specifier: ^1.2.1 version: 1.2.1 '@lezer/markdown': - specifier: ^1.3.2 - version: 1.3.2 + specifier: workspace:* + version: link:../lezer-markdown '@noble/hashes': specifier: ^1.6.1 version: 1.6.1 @@ -844,7 +894,7 @@ importers: version: 4.1.2 debug: specifier: ^4.4.0 - version: 4.4.0 + version: 4.4.0(supports-color@8.1.1) enso-common: specifier: workspace:* version: link:../common @@ -1357,6 +1407,9 @@ packages: resolution: {integrity: sha512-dtosfsuZCSaqlUe5EyxNdaN7Gow0Y+ZJixdlciytcSieUcB/1lXPFTx6OihxhjgtTHkeFovlQ/QbvArRPnk+nQ==} hasBin: true + '@bazel/runfiles@6.3.1': + resolution: {integrity: sha512-1uLNT5NZsUVIGS4syuHwTzZ8HycMPyr6POA3FCE4GbMtc4rhoJk8aZKtNIRthJYfL+iioppi+rTfH3olMPr9nA==} + '@chromatic-com/storybook@3.2.3': resolution: {integrity: sha512-3+hfANx79kIjP1qrOSLxpoAXOiYUA0S7A0WI0A24kASrv7USFNNW8etR5TjUilMb0LmqKUn3wDwUK2h6aceQ9g==} engines: {node: '>=16.0.0', yarn: '>=1.22.18'} @@ -1366,6 +1419,10 @@ packages: '@codemirror/autocomplete@6.18.4': resolution: {integrity: sha512-sFAphGQIqyQZfP2ZBsSHV7xQvo9Py0rV0dW7W3IMRdS+zDuNb2l3no78CvUaWKGfzFjI4FTrLdUSj86IGb2hRA==} + '@codemirror/buildhelper@1.0.2': + resolution: {integrity: sha512-aVewtDPZptq9dTvYqIjpu9HTEmMaKAE4VL22z5E1ycgY5e1LdAiRd5YYjqzQeqLjxpWsHy+emO3n5UUcxpUmSg==} + hasBin: true + '@codemirror/commands@6.7.1': resolution: {integrity: sha512-llTrboQYw5H4THfhN4U3qCnSZ1SOJ60ohhz+SzU0ADGtwlc533DtklQP0vSFaQuCPDn3BPpOd1GbbnUtwNjsrw==} @@ -1378,9 +1435,6 @@ packages: '@codemirror/lang-javascript@6.2.2': resolution: {integrity: sha512-VGQfY+FCc285AhWuwjYxQyUQcYurWlxdKYT4bqwr3Twnd5wP5WSeu52t4tvvuWmljT4EmgEgZCqSieokhtY8hg==} - '@codemirror/lang-markdown@6.3.1': - resolution: {integrity: sha512-y3sSPuQjBKZQbQwe3ZJKrSW6Silyl9PnrU/Mf0m2OQgIlPoSYTtOvEL7xs94SVMkb8f4x+SQFnzXPdX4Wk2lsg==} - '@codemirror/language@6.10.8': resolution: {integrity: sha512-wcP8XPPhDH2vTqf181U8MbZnW+tDyPYy0UzVOa+oHORjyT+mhhom9vBd7dApJwoDz9Nb/a8kHjJIsuA/t8vNFw==} @@ -1963,6 +2017,10 @@ packages: '@lezer/css@1.1.9': resolution: {integrity: sha512-TYwgljcDv+YrV0MZFFvYFQHCfGgbPMR6nuqLabBdmZoFH3EP1gvw8t0vae326Ne3PszQkbXfVBjCnf3ZVCr0bA==} + '@lezer/generator@1.7.2': + resolution: {integrity: sha512-CwgULPOPPmH54tv4gki18bElLCdJ1+FBC+nGVSVD08vFWDsMjS7KEjNTph9JOypDnet90ujN3LzQiW3CyVODNQ==} + hasBin: true + '@lezer/highlight@1.2.1': resolution: {integrity: sha512-Z5duk4RN/3zuVO7Jq0pGLJ3qynpxUVsh7IbUbGj88+uV2ApSAn6kWg2au3iJb+0Zi7kKtqffIESgNcRXWZWmSA==} @@ -1975,9 +2033,6 @@ packages: '@lezer/lr@1.4.2': resolution: {integrity: sha512-pu0K1jCIdnQ12aWNaAVU5bzi7Bd1w54J3ECgANPmYLtQKP0HBj2cE/5coBD66MT10xbtIuUr7tg0Shbsvk0mDA==} - '@lezer/markdown@1.3.2': - resolution: {integrity: sha512-Wu7B6VnrKTbBEohqa63h5vxXjiC4pO5ZQJ/TDbhJxPQaaIoRD/6UVDhSDtVsCwVZV12vvN9KxuLL3ATMnlG0oQ==} - '@malept/cross-spawn-promise@1.1.1': resolution: {integrity: sha512-RTBGWL5FWQcg9orDOCcp4LvItNzUPcyEU9bwaeJX0rJ1IQxzucC48Y0/sQLp/g6t99IQgAlGIaesJS+gTn7tVQ==} engines: {node: '>= 10'} @@ -1986,9 +2041,19 @@ packages: resolution: {integrity: sha512-9QOtNffcOF/c1seMCDnjckb3R9WHcG34tky+FHpNKKCW0wc/scYLwMtO+ptyGUfMW0/b/n4qRiALlaFHc9Oj7Q==} engines: {node: '>= 10.0.0'} + '@marijn/buildtool@0.1.6': + resolution: {integrity: sha512-rcA2wljsM24MFAwx2U5vSBrt7IdIaPh4WPRfJPS8PuCUlbuQ8Pmky4c/ec00v3YFu90rZSbkVLnPuCeb/mUEng==} + + '@marijn/buildtool@1.1.0': + resolution: {integrity: sha512-4MplEHnyta/atrMFM8+Fn0fB5XVvM9umlUvqc9q9qwiV4uceGYW/nXIYMT2A27uxXxLoJX9Zb2rZdtGojvWpMw==} + '@marijn/find-cluster-break@1.0.2': resolution: {integrity: sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==} + '@marijn/testtool@0.1.3': + resolution: {integrity: sha512-Rdi3amfFyTZoUXxMc95k9x9Ult+DtQSuOHsZwN5wtIKQ5JdXQaErgtWgGjW0Fpg4Rj0YrUCpWOj0VqsumAt5JA==} + hasBin: true + '@mdx-js/react@3.1.0': resolution: {integrity: sha512-QjHtSaoameoalGnKDT3FoIl4+9RwyTmo9ZJGBdLOks/YOiWHoRDI3PUwEzOE7kEmGcV3AFcp9K6dYu9rEuKLAQ==} peerDependencies: @@ -3317,6 +3382,9 @@ packages: '@types/mime-types@2.1.4': resolution: {integrity: sha512-lfU4b34HOri+kAY5UheuFMWPDOI+OPceBSHZKp69gEyTL/mmJ4cnU6Y/rlme3UL3GyOn6Y42hyIEw0/q8sWx5w==} + '@types/mocha@9.1.1': + resolution: {integrity: sha512-Z61JK7DKDtdKTWwLeElSEBcWGRLY8g95ic5FoQqI9CMx0ns/Ghep3B4DfcEimiKMvtamNVULVNKEsiwV3aQmXw==} + '@types/ms@0.7.34': resolution: {integrity: sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==} @@ -3650,6 +3718,10 @@ packages: peerDependencies: acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + acorn-walk@8.3.4: + resolution: {integrity: sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==} + engines: {node: '>=0.4.0'} + acorn@7.4.1: resolution: {integrity: sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==} engines: {node: '>=0.4.0'} @@ -3703,6 +3775,10 @@ packages: amazon-cognito-identity-js@6.3.6: resolution: {integrity: sha512-kBq+GE6OkLrxtFj3ZduIOlKBFYeOqZK3EhxbDBkv476UTvy+uwfR0tlriTq2QzNdnvlQAjBIXnXuOM7DwR1UEQ==} + ansi-colors@4.1.3: + resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} + engines: {node: '>=6'} + ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} @@ -3935,6 +4011,9 @@ packages: browser-assert@1.2.1: resolution: {integrity: sha512-nfulgvOR6S4gt9UKCeGJOuSGBPGiFT6oQ/2UBnvTY/5aQ1PnksW72fhZkM30DzoRRv2WpwZf1vHHEr3mtuXIWQ==} + browser-stdout@1.3.1: + resolution: {integrity: sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==} + browserslist@4.24.3: resolution: {integrity: sha512-1CPmv8iobE2fyRMV97dAcMVegvvWKxmq94hkLiAkUGwKVTyDLw33K+ZxiFrREKmmps4rIw6grcCFCnTMSZ/YiA==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} @@ -4027,6 +4106,10 @@ packages: resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} engines: {node: '>= 6'} + camelcase@6.3.0: + resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} + engines: {node: '>=10'} + caniuse-lite@1.0.30001690: resolution: {integrity: sha512-5ExiE3qQN6oF8Clf8ifIDcMRCRE/dMGcETG/XGMD8/XiXm6HXQgQTh1yZYLXXpSOsEUlJm1Xr7kGULZTuGtP/w==} @@ -4113,6 +4196,9 @@ packages: client-only@0.0.1: resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} + cliui@7.0.4: + resolution: {integrity: sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==} + cliui@8.0.1: resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} engines: {node: '>=12'} @@ -4473,6 +4559,10 @@ packages: supports-color: optional: true + decamelize@4.0.0: + resolution: {integrity: sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==} + engines: {node: '>=10'} + decimal.js@10.4.3: resolution: {integrity: sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==} @@ -4553,6 +4643,10 @@ packages: didyoumean@1.2.2: resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} + diff@5.2.0: + resolution: {integrity: sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==} + engines: {node: '>=0.3.1'} + dir-compare@3.3.0: resolution: {integrity: sha512-J7/et3WlGUCxjdnD3HAAzQ6nsnc0WL6DD7WcwJb7c39iH1+AWfg+9OqzJNaI6PkBwBvm1mhZNL9iY/nRiZXlPg==} @@ -4680,6 +4774,10 @@ packages: resolution: {integrity: sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==} engines: {node: '>= 0.8'} + encodeurl@2.0.0: + resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} + engines: {node: '>= 0.8'} + encoding-down@6.3.0: resolution: {integrity: sha512-QKrV0iKR6MZVJV08QY0wp1e7vF6QbhnbQhb07bwpEyuz4uZiZgPlEGdkCROuFkUwdxlFaiPIhjyarH1ee/3vhw==} engines: {node: '>=6'} @@ -4866,6 +4964,10 @@ packages: esm-resolve@1.0.11: resolution: {integrity: sha512-LxF0wfUQm3ldUDHkkV2MIbvvY0TgzIpJ420jHSV1Dm+IlplBEWiJTKWM61GtxUfvjV6iD4OtTYFGAGM2uuIUWg==} + esmoduleserve@0.2.1: + resolution: {integrity: sha512-LeuOiyyCSc2sG0Clx9A/tzApfP2gz2/YPE7IBSQwP2JPZKm8S0WZ1b1DfH9eCYXo469k81od3lFvFloYJNpTYA==} + hasBin: true + espree@10.3.0: resolution: {integrity: sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -4901,6 +5003,10 @@ packages: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} + etag@1.8.1: + resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} + engines: {node: '>= 0.6'} + event-pubsub@4.3.0: resolution: {integrity: sha512-z7IyloorXvKbFx9Bpie2+vMJKKx1fH1EN5yiTfp8CiLOTptSYy1g8H4yDpGlEdshL1PBiFtBHepF2cNsqeEeFQ==} engines: {node: '>=4.0.0'} @@ -5017,6 +5123,10 @@ packages: resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} engines: {node: '>=16'} + flat@5.0.2: + resolution: {integrity: sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==} + hasBin: true + flatted@3.3.2: resolution: {integrity: sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==} @@ -5077,6 +5187,10 @@ packages: react-dom: optional: true + fresh@0.5.2: + resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} + engines: {node: '>= 0.6'} + fs-constants@1.0.0: resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} @@ -5185,6 +5299,11 @@ packages: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} deprecated: Glob versions prior to v9 are no longer supported + glob@8.1.0: + resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==} + engines: {node: '>=12'} + deprecated: Glob versions prior to v9 are no longer supported + glob@9.3.5: resolution: {integrity: sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q==} engines: {node: '>=16 || 14 >=14.17'} @@ -5540,6 +5659,10 @@ packages: resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} engines: {node: '>=0.12.0'} + is-plain-obj@2.1.0: + resolution: {integrity: sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==} + engines: {node: '>=8'} + is-plain-obj@4.1.0: resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} engines: {node: '>=12'} @@ -5581,6 +5704,10 @@ packages: is-typedarray@1.0.0: resolution: {integrity: sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==} + is-unicode-supported@0.1.0: + resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} + engines: {node: '>=10'} + is-unicode-supported@2.1.0: resolution: {integrity: sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==} engines: {node: '>=18'} @@ -5646,6 +5773,9 @@ packages: isstream@0.1.2: resolution: {integrity: sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==} + ist@1.1.7: + resolution: {integrity: sha512-ex9JyqY+tCjBlxN1pXlqxEgtGGUGp1TG83ll1xpu8SfPgOhfAhEGCuepNHlB+d7Le+hLoBcfCu/G0ZQaFbi9hA==} + iterator.prototype@1.1.5: resolution: {integrity: sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==} engines: {node: '>= 0.4'} @@ -5786,6 +5916,9 @@ packages: resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} engines: {node: '>=4.0'} + jszip@3.10.1: + resolution: {integrity: sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==} + keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} @@ -5860,6 +5993,9 @@ packages: lie@3.1.1: resolution: {integrity: sha512-RiNhHysUjhrDQntfYSfY4MU24coXXdEOgw9WGcKHNeEwffDYbF//u87M1EWaMGzuFoSbqW0C9C6lEEhDOAswfw==} + lie@3.3.0: + resolution: {integrity: sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==} + lilconfig@3.1.3: resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} engines: {node: '>=14'} @@ -5905,6 +6041,10 @@ packages: lodash@4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + log-symbols@4.1.0: + resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} + engines: {node: '>=10'} + loose-envify@1.4.0: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true @@ -6089,6 +6229,11 @@ packages: engines: {node: '>=10'} hasBin: true + mocha@10.8.2: + resolution: {integrity: sha512-VZlYo/WE8t1tstuRmqgeyBgCbJc/lEdopaa+axcKzTBJ+UIdlAB9XnmvTCAH4pwR4ElNInaedhEBmZD8iCSVEg==} + engines: {node: '>= 14.0.0'} + hasBin: true + modern-isomorphic-ws@1.0.5: resolution: {integrity: sha512-cgJzdkn1//XyYJsFZkjPYKN21CXeG+KC38Q5uFwTnbUwG3pmmsUDS9a5RRJ6lFnjsnEbUKx+rIXjxX/uCUFYvQ==} peerDependencies: @@ -6298,6 +6443,9 @@ packages: package-json-from-dist@1.0.1: resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + pako@1.0.11: + resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==} + papaparse@5.4.1: resolution: {integrity: sha512-HipMsgJkZu8br23pW15uvo6sib6wne/4woLZPlFf3rpDyMe9ywEXUsuD7+6K9PRkJlVT51j/sCOYDKGGS3ZJrw==} @@ -6747,6 +6895,13 @@ packages: resolution: {integrity: sha512-80WNmd9DA0tmZrw9qQa62GPPWfuXJknrmVmLcxvq4uZBdYqb1wYoKTmnlGUchvVWe0XiLupYkBoXVOxz3C8DYQ==} engines: {node: '>=0.12'} + randombytes@2.1.0: + resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==} + + range-parser@1.2.1: + resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} + engines: {node: '>= 0.6'} + raw-body@2.5.2: resolution: {integrity: sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==} engines: {node: '>= 0.8'} @@ -6970,6 +7125,25 @@ packages: robust-predicates@3.0.2: resolution: {integrity: sha512-IXgzBWvWQwE6PrDI05OvmXUIruQTcoMDzRsOd5CDvHCVLcLHMTSYvOK5Cm46kWqlV3yAbuSpBZdJ5oP5OUoStg==} + rollup-plugin-dts@5.3.1: + resolution: {integrity: sha512-gusMi+Z4gY/JaEQeXnB0RUdU82h1kF0WYzCWgVmV4p3hWXqelaKuCvcJawfeg+EKn2T1Ie+YWF2OiN1/L8bTVg==} + engines: {node: '>=v14.21.3'} + peerDependencies: + rollup: ^3.0 + typescript: ^4.1 || ^5.0 + + rollup-plugin-dts@6.1.1: + resolution: {integrity: sha512-aSHRcJ6KG2IHIioYlvAOcEq6U99sVtqDDKVhnwt70rW6tsz3tv5OSjEiWcgzfsHdLyGXZ/3b/7b/+Za3Y6r1XA==} + engines: {node: '>=16'} + peerDependencies: + rollup: ^3.29.4 || ^4 + typescript: ^4.5 || ^5.0 + + rollup@3.29.5: + resolution: {integrity: sha512-GVsDdsbJzzy4S/v3dqWPJ7EfvZJfCHiDqe80IyrF59LYuP+e6U1LJoUqeuqRbwAWoMNoXivMNeNAOf5E22VA1w==} + engines: {node: '>=14.18.0', npm: '>=8.0.0'} + hasBin: true + rollup@4.29.1: resolution: {integrity: sha512-RaJ45M/kmJUzSWDs1Nnd5DdV4eerC98idtUOVr6FfKcgxqvjwHmxc5upLF9qZU9EpsVzzhleFahrT3shLuJzIw==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} @@ -7025,6 +7199,10 @@ packages: seedrandom@2.4.4: resolution: {integrity: sha512-9A+PDmgm+2du77B5i0Ip2cxOqqHjgNxnBgglxLcX78A2D6c2rTo61z4jnVABpF4cKeDMDG+cmXXvdnqse2VqMA==} + selenium-webdriver@4.29.0: + resolution: {integrity: sha512-8XPGtDoji5xk7ZUCzFT1rqHmCp67DCzESsttId7DzmrJmlTRmRLF6X918rbwclcH89amcBNM4zB3lVPj404I0g==} + engines: {node: '>= 18.20.5'} + semver-compare@1.0.0: resolution: {integrity: sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==} @@ -7046,6 +7224,10 @@ packages: engines: {node: '>=10'} hasBin: true + send@0.19.0: + resolution: {integrity: sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==} + engines: {node: '>= 0.8.0'} + sentence-case@3.0.4: resolution: {integrity: sha512-8LS0JInaQMCRoQ7YUytAo/xUu5W2XnQxV2HI/6uM6U7CITS1RqPElr30V6uIqyMKM9lJGRVFy5/4CuzcixNYSg==} @@ -7053,6 +7235,13 @@ packages: resolution: {integrity: sha512-8I8TjW5KMOKsZQTvoxjuSIa7foAwPWGOts+6o7sgjz41/qMD9VQHEDxi6PBvK2l0MXUmqZyNpUK+T2tQaaElvw==} engines: {node: '>=10'} + serialize-javascript@6.0.2: + resolution: {integrity: sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==} + + serve-static@1.16.2: + resolution: {integrity: sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==} + engines: {node: '>= 0.8.0'} + set-function-length@1.2.2: resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} engines: {node: '>= 0.4'} @@ -7065,6 +7254,9 @@ packages: resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==} engines: {node: '>= 0.4'} + setimmediate@1.0.5: + resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==} + setprototypeof@1.2.0: resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} @@ -7335,6 +7527,10 @@ packages: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} + supports-color@8.1.1: + resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} + engines: {node: '>=10'} + supports-preserve-symlinks-flag@1.0.0: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} @@ -7820,8 +8016,8 @@ packages: vue-component-type-helpers@2.2.0: resolution: {integrity: sha512-cYrAnv2me7bPDcg9kIcGwjJiSB6Qyi08+jLDo9yuvoFQjzHiPTzML7RnkJB1+3P6KMsX/KbCD4QE3Tv/knEllw==} - vue-component-type-helpers@2.2.2: - resolution: {integrity: sha512-6lLY+n2xz2kCYshl59mL6gy8OUUTmkscmDFMO8i7Lj+QKwgnIFUZmM1i/iTYObtrczZVdw7UakPqDTGwVSGaRg==} + vue-component-type-helpers@2.2.4: + resolution: {integrity: sha512-F66p0XLbAu92BRz6kakHyAcaUSF7HWpWX/THCqL0TxySSj7z/nok5UUMohfNkkCm1pZtawsdzoJ4p1cjNqCx0Q==} vue-demi@0.14.10: resolution: {integrity: sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==} @@ -7955,6 +8151,9 @@ packages: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} + workerpool@6.5.1: + resolution: {integrity: sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==} + wrap-ansi@7.0.0: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'} @@ -8049,10 +8248,22 @@ packages: engines: {node: '>= 14'} hasBin: true + yargs-parser@20.2.9: + resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} + engines: {node: '>=10'} + yargs-parser@21.1.1: resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} engines: {node: '>=12'} + yargs-unparser@2.0.0: + resolution: {integrity: sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==} + engines: {node: '>=10'} + + yargs@16.2.0: + resolution: {integrity: sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==} + engines: {node: '>=10'} + yargs@17.6.2: resolution: {integrity: sha512-1/9UrdHjDZc0eOU0HxOHoS78C69UD3JRMvzlJ7S79S2nTaWRA/whGCTV8o9e/N/1Va9YIV7Q4sOxD8VV4pCWOw==} engines: {node: '>=12'} @@ -8521,7 +8732,7 @@ snapshots: '@babel/traverse': 7.26.4 '@babel/types': 7.26.3 convert-source-map: 2.0.0 - debug: 4.4.0 + debug: 4.4.0(supports-color@8.1.1) gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 @@ -8542,7 +8753,7 @@ snapshots: '@babel/types': 7.26.8 '@types/gensync': 1.0.4 convert-source-map: 2.0.0 - debug: 4.4.0 + debug: 4.4.0(supports-color@8.1.1) gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 @@ -8767,7 +8978,7 @@ snapshots: '@babel/parser': 7.26.3 '@babel/template': 7.25.9 '@babel/types': 7.26.3 - debug: 4.4.0 + debug: 4.4.0(supports-color@8.1.1) globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -8779,7 +8990,7 @@ snapshots: '@babel/parser': 7.26.8 '@babel/template': 7.26.8 '@babel/types': 7.26.8 - debug: 4.4.0 + debug: 4.4.0(supports-color@8.1.1) globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -8798,6 +9009,8 @@ snapshots: '@bazel/ibazel@0.25.0': {} + '@bazel/runfiles@6.3.1': {} + '@chromatic-com/storybook@3.2.3(react@18.3.1)(storybook@8.5.0)': dependencies: chromatic: 11.18.1 @@ -8818,6 +9031,16 @@ snapshots: '@codemirror/view': 6.36.1 '@lezer/common': 1.2.3 + '@codemirror/buildhelper@1.0.2': + dependencies: + '@lezer/generator': 1.7.2 + '@marijn/buildtool': 1.1.0 + '@marijn/testtool': 0.1.3 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + '@codemirror/commands@6.7.1': dependencies: '@codemirror/language': 6.10.8 @@ -8855,16 +9078,6 @@ snapshots: '@lezer/common': 1.2.3 '@lezer/javascript': 1.4.21 - '@codemirror/lang-markdown@6.3.1': - dependencies: - '@codemirror/autocomplete': 6.18.4 - '@codemirror/lang-html': 6.4.9 - '@codemirror/language': 6.10.8 - '@codemirror/state': 6.5.0 - '@codemirror/view': 6.36.1 - '@lezer/common': 1.2.3 - '@lezer/markdown': 1.3.2 - '@codemirror/language@6.10.8': dependencies: '@codemirror/state': 6.5.0 @@ -8930,7 +9143,7 @@ snapshots: '@electron/get@2.0.3': dependencies: - debug: 4.4.0 + debug: 4.4.0(supports-color@8.1.1) env-paths: 2.2.1 fs-extra: 8.1.0 got: 11.8.6 @@ -8944,7 +9157,7 @@ snapshots: '@electron/notarize@2.1.0': dependencies: - debug: 4.4.0 + debug: 4.4.0(supports-color@8.1.1) fs-extra: 9.1.0 promise-retry: 2.0.1 transitivePeerDependencies: @@ -8952,7 +9165,7 @@ snapshots: '@electron/notarize@2.2.1': dependencies: - debug: 4.4.0 + debug: 4.4.0(supports-color@8.1.1) fs-extra: 9.1.0 promise-retry: 2.0.1 transitivePeerDependencies: @@ -8961,7 +9174,7 @@ snapshots: '@electron/osx-sign@1.0.5': dependencies: compare-version: 0.1.2 - debug: 4.4.0 + debug: 4.4.0(supports-color@8.1.1) fs-extra: 10.1.0 isbinaryfile: 4.0.10 minimist: 1.2.8 @@ -8973,7 +9186,7 @@ snapshots: dependencies: '@electron/asar': 3.2.17 '@malept/cross-spawn-promise': 1.1.1 - debug: 4.4.0 + debug: 4.4.0(supports-color@8.1.1) dir-compare: 3.3.0 fs-extra: 9.1.0 minimatch: 3.1.2 @@ -9147,7 +9360,7 @@ snapshots: '@eslint/config-array@0.19.1': dependencies: '@eslint/object-schema': 2.1.5 - debug: 4.4.0 + debug: 4.4.0(supports-color@8.1.1) minimatch: 3.1.2 transitivePeerDependencies: - supports-color @@ -9159,7 +9372,7 @@ snapshots: '@eslint/eslintrc@3.2.0': dependencies: ajv: 6.12.6 - debug: 4.4.0 + debug: 4.4.0(supports-color@8.1.1) espree: 10.3.0 globals: 14.0.0 ignore: 5.3.2 @@ -9424,6 +9637,11 @@ snapshots: '@lezer/highlight': 1.2.1 '@lezer/lr': 1.4.2 + '@lezer/generator@1.7.2': + dependencies: + '@lezer/common': 1.2.3 + '@lezer/lr': 1.4.2 + '@lezer/highlight@1.2.1': dependencies: '@lezer/common': 1.2.3 @@ -9444,26 +9662,51 @@ snapshots: dependencies: '@lezer/common': 1.2.3 - '@lezer/markdown@1.3.2': - dependencies: - '@lezer/common': 1.2.3 - '@lezer/highlight': 1.2.1 - '@malept/cross-spawn-promise@1.1.1': dependencies: cross-spawn: 7.0.6 '@malept/flatpak-bundler@0.4.0': dependencies: - debug: 4.4.0 + debug: 4.4.0(supports-color@8.1.1) fs-extra: 9.1.0 lodash: 4.17.21 tmp-promise: 3.0.3 transitivePeerDependencies: - supports-color + '@marijn/buildtool@0.1.6': + dependencies: + '@types/mocha': 9.1.1 + acorn: 8.14.0 + acorn-walk: 8.3.4 + rollup: 3.29.5 + rollup-plugin-dts: 5.3.1(rollup@3.29.5)(typescript@5.7.2) + typescript: 5.7.2 + + '@marijn/buildtool@1.1.0': + dependencies: + '@types/mocha': 9.1.1 + acorn: 8.14.0 + acorn-walk: 8.3.4 + rollup: 4.29.1 + rollup-plugin-dts: 6.1.1(rollup@4.29.1)(typescript@5.7.2) + typescript: 5.7.2 + '@marijn/find-cluster-break@1.0.2': {} + '@marijn/testtool@0.1.3': + dependencies: + esmoduleserve: 0.2.1 + ist: 1.1.7 + mocha: 10.8.2 + selenium-webdriver: 4.29.0 + serve-static: 1.16.2 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + '@mdx-js/react@3.1.0(@types/react@18.3.18)(react@18.3.1)': dependencies: '@types/mdx': 2.0.13 @@ -11050,7 +11293,7 @@ snapshots: ts-dedent: 2.2.0 type-fest: 2.19.0 vue: 3.5.13(typescript@5.7.2) - vue-component-type-helpers: 2.2.2 + vue-component-type-helpers: 2.2.4 '@stripe/react-stripe-js@2.9.0(@stripe/stripe-js@3.5.0)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: @@ -11361,6 +11604,8 @@ snapshots: '@types/mime-types@2.1.4': {} + '@types/mocha@9.1.1': {} + '@types/ms@0.7.34': {} '@types/node-fetch@2.6.4': @@ -11481,7 +11726,7 @@ snapshots: '@typescript-eslint/types': 8.19.0 '@typescript-eslint/typescript-estree': 8.19.0(typescript@5.7.2) '@typescript-eslint/visitor-keys': 8.19.0 - debug: 4.4.0 + debug: 4.4.0(supports-color@8.1.1) eslint: 9.17.0(jiti@1.21.7) typescript: 5.7.2 transitivePeerDependencies: @@ -11496,7 +11741,7 @@ snapshots: dependencies: '@typescript-eslint/typescript-estree': 8.19.0(typescript@5.7.2) '@typescript-eslint/utils': 8.19.0(eslint@9.17.0(jiti@1.21.7))(typescript@5.7.2) - debug: 4.4.0 + debug: 4.4.0(supports-color@8.1.1) eslint: 9.17.0(jiti@1.21.7) ts-api-utils: 1.4.3(typescript@5.7.2) typescript: 5.7.2 @@ -11509,7 +11754,7 @@ snapshots: dependencies: '@typescript-eslint/types': 8.19.0 '@typescript-eslint/visitor-keys': 8.19.0 - debug: 4.4.0 + debug: 4.4.0(supports-color@8.1.1) fast-glob: 3.3.2 is-glob: 4.0.3 minimatch: 9.0.5 @@ -11851,6 +12096,10 @@ snapshots: dependencies: acorn: 8.14.0 + acorn-walk@8.3.4: + dependencies: + acorn: 8.14.0 + acorn@7.4.1: {} acorn@8.14.0: {} @@ -11882,7 +12131,7 @@ snapshots: agent-base@6.0.2: dependencies: - debug: 4.4.0 + debug: 4.4.0(supports-color@8.1.1) transitivePeerDependencies: - supports-color @@ -11918,6 +12167,8 @@ snapshots: transitivePeerDependencies: - encoding + ansi-colors@4.1.3: {} + ansi-regex@5.0.1: {} ansi-regex@6.1.0: {} @@ -11956,7 +12207,7 @@ snapshots: builder-util: 24.13.1 builder-util-runtime: 9.2.4 chromium-pickle-js: 0.2.0 - debug: 4.4.0 + debug: 4.4.0(supports-color@8.1.1) dmg-builder: 24.13.3(electron-builder-squirrel-windows@24.13.3) ejs: 3.1.10 electron-builder-squirrel-windows: 24.13.3(dmg-builder@24.13.3) @@ -12210,6 +12461,8 @@ snapshots: browser-assert@1.2.1: {} + browser-stdout@1.3.1: {} + browserslist@4.24.3: dependencies: caniuse-lite: 1.0.30001690 @@ -12254,7 +12507,7 @@ snapshots: builder-util-runtime@9.2.4: dependencies: - debug: 4.4.0 + debug: 4.4.0(supports-color@8.1.1) sax: 1.4.1 transitivePeerDependencies: - supports-color @@ -12268,7 +12521,7 @@ snapshots: builder-util-runtime: 9.2.4 chalk: 4.1.2 cross-spawn: 7.0.6 - debug: 4.4.0 + debug: 4.4.0(supports-color@8.1.1) fs-extra: 10.1.0 http-proxy-agent: 5.0.0 https-proxy-agent: 5.0.1 @@ -12328,6 +12581,8 @@ snapshots: camelcase-css@2.0.1: {} + camelcase@6.3.0: {} + caniuse-lite@1.0.30001690: {} caniuse-lite@1.0.30001699: {} @@ -12425,6 +12680,12 @@ snapshots: client-only@0.0.1: {} + cliui@7.0.4: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + cliui@8.0.1: dependencies: string-width: 4.2.3 @@ -12802,9 +13063,13 @@ snapshots: dependencies: ms: 2.1.3 - debug@4.4.0: + debug@4.4.0(supports-color@8.1.1): dependencies: ms: 2.1.3 + optionalDependencies: + supports-color: 8.1.1 + + decamelize@4.0.0: {} decimal.js@10.4.3: {} @@ -12867,6 +13132,8 @@ snapshots: didyoumean@1.2.2: {} + diff@5.2.0: {} + dir-compare@3.3.0: dependencies: buffer-equal: 1.0.1 @@ -13034,6 +13301,8 @@ snapshots: encodeurl@1.0.2: {} + encodeurl@2.0.0: {} + encoding-down@6.3.0: dependencies: abstract-leveldown: 6.3.0 @@ -13174,7 +13443,7 @@ snapshots: esbuild-register@3.6.0(esbuild@0.24.2): dependencies: - debug: 4.4.0 + debug: 4.4.0(supports-color@8.1.1) esbuild: 0.24.2 transitivePeerDependencies: - supports-color @@ -13253,7 +13522,7 @@ snapshots: '@es-joy/jsdoccomment': 0.49.0 are-docs-informative: 0.0.2 comment-parser: 1.4.1 - debug: 4.4.0 + debug: 4.4.0(supports-color@8.1.1) escape-string-regexp: 4.0.0 eslint: 9.17.0(jiti@1.21.7) espree: 10.3.0 @@ -13358,7 +13627,7 @@ snapshots: ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.6 - debug: 4.4.0 + debug: 4.4.0(supports-color@8.1.1) escape-string-regexp: 4.0.0 eslint-scope: 8.2.0 eslint-visitor-keys: 4.2.0 @@ -13384,6 +13653,15 @@ snapshots: esm-resolve@1.0.11: {} + esmoduleserve@0.2.1: + dependencies: + acorn: 8.14.0 + acorn-walk: 8.3.4 + resolve: 1.22.10 + serve-static: 1.16.2 + transitivePeerDependencies: + - supports-color + espree@10.3.0: dependencies: acorn: 8.14.0 @@ -13416,6 +13694,8 @@ snapshots: esutils@2.0.3: {} + etag@1.8.1: {} + event-pubsub@4.3.0: {} event-target-shim@6.0.2: {} @@ -13447,7 +13727,7 @@ snapshots: extract-zip@2.0.1: dependencies: - debug: 4.4.0 + debug: 4.4.0(supports-color@8.1.1) get-stream: 5.2.0 yauzl: 2.10.0 optionalDependencies: @@ -13537,6 +13817,8 @@ snapshots: flatted: 3.3.2 keyv: 4.5.4 + flat@5.0.2: {} + flatted@3.3.2: {} floating-vue@2.0.0(vue@3.5.13(typescript@5.7.2)): @@ -13585,6 +13867,8 @@ snapshots: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) + fresh@0.5.2: {} + fs-constants@1.0.0: {} fs-extra@10.1.0: @@ -13719,6 +14003,14 @@ snapshots: once: 1.4.0 path-is-absolute: 1.0.1 + glob@8.1.0: + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 5.1.6 + once: 1.4.0 + glob@9.3.5: dependencies: fs.realpath: 1.0.0 @@ -13863,14 +14155,14 @@ snapshots: dependencies: '@tootallnate/once': 2.0.0 agent-base: 6.0.2 - debug: 4.4.0 + debug: 4.4.0(supports-color@8.1.1) transitivePeerDependencies: - supports-color http-proxy-agent@7.0.2: dependencies: agent-base: 7.1.3 - debug: 4.4.0 + debug: 4.4.0(supports-color@8.1.1) transitivePeerDependencies: - supports-color @@ -13888,14 +14180,14 @@ snapshots: https-proxy-agent@5.0.1: dependencies: agent-base: 6.0.2 - debug: 4.4.0 + debug: 4.4.0(supports-color@8.1.1) transitivePeerDependencies: - supports-color https-proxy-agent@7.0.6: dependencies: agent-base: 7.1.3 - debug: 4.4.0 + debug: 4.4.0(supports-color@8.1.1) transitivePeerDependencies: - supports-color @@ -14074,6 +14366,8 @@ snapshots: is-number@7.0.0: {} + is-plain-obj@2.1.0: {} + is-plain-obj@4.1.0: {} is-potential-custom-element-name@1.0.1: {} @@ -14112,6 +14406,8 @@ snapshots: is-typedarray@1.0.0: {} + is-unicode-supported@0.1.0: {} + is-unicode-supported@2.1.0: {} is-url@1.2.4: {} @@ -14169,6 +14465,8 @@ snapshots: isstream@0.1.2: {} + ist@1.1.7: {} + iterator.prototype@1.1.5: dependencies: define-data-property: 1.1.4 @@ -14347,6 +14645,13 @@ snapshots: object.assign: 4.1.7 object.values: 1.2.1 + jszip@3.10.1: + dependencies: + lie: 3.3.0 + pako: 1.0.11 + readable-stream: 2.3.8 + setimmediate: 1.0.5 + keyv@4.5.4: dependencies: json-buffer: 3.0.1 @@ -14436,6 +14741,10 @@ snapshots: dependencies: immediate: 3.0.6 + lie@3.3.0: + dependencies: + immediate: 3.0.6 + lilconfig@3.1.3: {} lines-and-columns@1.2.4: {} @@ -14484,6 +14793,11 @@ snapshots: lodash@4.17.21: {} + log-symbols@4.1.0: + dependencies: + chalk: 4.1.2 + is-unicode-supported: 0.1.0 + loose-envify@1.4.0: dependencies: js-tokens: 4.0.0 @@ -14629,6 +14943,29 @@ snapshots: mkdirp@1.0.4: {} + mocha@10.8.2: + dependencies: + ansi-colors: 4.1.3 + browser-stdout: 1.3.1 + chokidar: 3.6.0 + debug: 4.4.0(supports-color@8.1.1) + diff: 5.2.0 + escape-string-regexp: 4.0.0 + find-up: 5.0.0 + glob: 8.1.0 + he: 1.2.0 + js-yaml: 4.1.0 + log-symbols: 4.1.0 + minimatch: 5.1.6 + ms: 2.1.3 + serialize-javascript: 6.0.2 + strip-json-comments: 3.1.1 + supports-color: 8.1.1 + workerpool: 6.5.1 + yargs: 16.2.0 + yargs-parser: 20.2.9 + yargs-unparser: 2.0.0 + modern-isomorphic-ws@1.0.5(@types/ws@8.5.13)(ws@8.18.0): dependencies: '@types/ws': 8.5.13 @@ -14840,6 +15177,8 @@ snapshots: package-json-from-dist@1.0.1: {} + pako@1.0.11: {} + papaparse@5.4.1: {} param-case@3.0.4: @@ -15235,6 +15574,12 @@ snapshots: discontinuous-range: 1.0.0 ret: 0.1.15 + randombytes@2.1.0: + dependencies: + safe-buffer: 5.2.1 + + range-parser@1.2.1: {} + raw-body@2.5.2: dependencies: bytes: 3.1.2 @@ -15612,6 +15957,26 @@ snapshots: robust-predicates@3.0.2: {} + rollup-plugin-dts@5.3.1(rollup@3.29.5)(typescript@5.7.2): + dependencies: + magic-string: 0.30.17 + rollup: 3.29.5 + typescript: 5.7.2 + optionalDependencies: + '@babel/code-frame': 7.26.2 + + rollup-plugin-dts@6.1.1(rollup@4.29.1)(typescript@5.7.2): + dependencies: + magic-string: 0.30.17 + rollup: 4.29.1 + typescript: 5.7.2 + optionalDependencies: + '@babel/code-frame': 7.26.2 + + rollup@3.29.5: + optionalDependencies: + fsevents: 2.3.3 + rollup@4.29.1: dependencies: '@types/estree': 1.0.6 @@ -15688,6 +16053,16 @@ snapshots: seedrandom@2.4.4: {} + selenium-webdriver@4.29.0: + dependencies: + '@bazel/runfiles': 6.3.1 + jszip: 3.10.1 + tmp: 0.2.3 + ws: 8.18.0 + transitivePeerDependencies: + - bufferutil + - utf-8-validate + semver-compare@1.0.0: optional: true @@ -15700,6 +16075,24 @@ snapshots: semver@7.7.1: optional: true + send@0.19.0: + dependencies: + debug: 2.6.9 + depd: 2.0.0 + destroy: 1.2.0 + encodeurl: 1.0.2 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 0.5.2 + http-errors: 2.0.0 + mime: 1.6.0 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.2.1 + statuses: 2.0.1 + transitivePeerDependencies: + - supports-color + sentence-case@3.0.4: dependencies: no-case: 3.0.4 @@ -15711,6 +16104,19 @@ snapshots: type-fest: 0.13.1 optional: true + serialize-javascript@6.0.2: + dependencies: + randombytes: 2.1.0 + + serve-static@1.16.2: + dependencies: + encodeurl: 2.0.0 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 0.19.0 + transitivePeerDependencies: + - supports-color + set-function-length@1.2.2: dependencies: define-data-property: 1.1.4 @@ -15733,6 +16139,8 @@ snapshots: es-errors: 1.3.0 es-object-atoms: 1.0.0 + setimmediate@1.0.5: {} + setprototypeof@1.2.0: {} sharp@0.31.3: @@ -16042,7 +16450,7 @@ snapshots: sumchecker@3.0.1: dependencies: - debug: 4.4.0 + debug: 4.4.0(supports-color@8.1.1) transitivePeerDependencies: - supports-color @@ -16058,6 +16466,10 @@ snapshots: dependencies: has-flag: 4.0.0 + supports-color@8.1.1: + dependencies: + has-flag: 4.0.0 + supports-preserve-symlinks-flag@1.0.0: {} svg-tags@1.0.0: {} @@ -16450,7 +16862,7 @@ snapshots: vite-node@3.0.0-beta.3(@types/node@22.10.4)(jiti@1.21.7)(yaml@2.7.0): dependencies: cac: 6.7.14 - debug: 4.4.0 + debug: 4.4.0(supports-color@8.1.1) es-module-lexer: 1.6.0 pathe: 1.1.2 vite: 6.0.11(@types/node@22.10.4)(jiti@1.21.7)(yaml@2.7.0) @@ -16471,7 +16883,7 @@ snapshots: vite-node@3.0.3(@types/node@22.10.4)(jiti@1.21.7)(yaml@2.7.0): dependencies: cac: 6.7.14 - debug: 4.4.0 + debug: 4.4.0(supports-color@8.1.1) es-module-lexer: 1.6.0 pathe: 2.0.2 vite: 6.0.11(@types/node@22.10.4)(jiti@1.21.7)(yaml@2.7.0) @@ -16492,7 +16904,7 @@ snapshots: vite-node@3.0.5(@types/node@20.17.11)(jiti@1.21.7)(yaml@2.7.0): dependencies: cac: 6.7.14 - debug: 4.4.0 + debug: 4.4.0(supports-color@8.1.1) es-module-lexer: 1.6.0 pathe: 2.0.2 vite: 6.0.11(@types/node@20.17.11)(jiti@1.21.7)(yaml@2.7.0) @@ -16513,7 +16925,7 @@ snapshots: vite-node@3.0.5(@types/node@22.10.4)(jiti@1.21.7)(yaml@2.7.0): dependencies: cac: 6.7.14 - debug: 4.4.0 + debug: 4.4.0(supports-color@8.1.1) es-module-lexer: 1.6.0 pathe: 2.0.2 vite: 6.0.11(@types/node@22.10.4)(jiti@1.21.7)(yaml@2.7.0) @@ -16535,7 +16947,7 @@ snapshots: dependencies: '@antfu/utils': 0.7.10 '@rollup/pluginutils': 5.1.4(rollup@4.29.1) - debug: 4.4.0 + debug: 4.4.0(supports-color@8.1.1) error-stack-parser-es: 0.1.5 fs-extra: 11.2.0 open: 10.1.0 @@ -16614,7 +17026,7 @@ snapshots: '@vitest/spy': 3.0.5 '@vitest/utils': 3.0.5 chai: 5.1.2 - debug: 4.4.0 + debug: 4.4.0(supports-color@8.1.1) expect-type: 1.1.0 magic-string: 0.30.17 pathe: 2.0.2 @@ -16654,7 +17066,7 @@ snapshots: '@vitest/spy': 3.0.5 '@vitest/utils': 3.0.5 chai: 5.1.2 - debug: 4.4.0 + debug: 4.4.0(supports-color@8.1.1) expect-type: 1.1.0 magic-string: 0.30.17 pathe: 2.0.2 @@ -16699,7 +17111,7 @@ snapshots: vue-component-type-helpers@2.2.0: {} - vue-component-type-helpers@2.2.2: {} + vue-component-type-helpers@2.2.4: {} vue-demi@0.14.10(vue@3.5.13(typescript@5.7.2)): dependencies: @@ -16723,7 +17135,7 @@ snapshots: vue-eslint-parser@9.4.3(eslint@9.17.0(jiti@1.21.7)): dependencies: - debug: 4.4.0 + debug: 4.4.0(supports-color@8.1.1) eslint: 9.17.0(jiti@1.21.7) eslint-scope: 7.2.2 eslint-visitor-keys: 3.4.3 @@ -16865,6 +17277,8 @@ snapshots: word-wrap@1.2.5: {} + workerpool@6.5.1: {} + wrap-ansi@7.0.0: dependencies: ansi-styles: 4.3.0 @@ -16944,8 +17358,27 @@ snapshots: yaml@2.7.0: {} + yargs-parser@20.2.9: {} + yargs-parser@21.1.1: {} + yargs-unparser@2.0.0: + dependencies: + camelcase: 6.3.0 + decamelize: 4.0.0 + flat: 5.0.2 + is-plain-obj: 2.1.0 + + yargs@16.2.0: + dependencies: + cliui: 7.0.4 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 20.2.9 + yargs@17.6.2: dependencies: cliui: 8.0.1 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 3557193fb94a..03a683062ef0 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -2,6 +2,8 @@ packages: - app/ide-desktop/* - app/common - app/gui + - app/lang-markdown + - app/lezer-markdown - app/rust-ffi - app/ydoc-server - app/ydoc-shared