diff --git a/.build/jsonSchema.ts b/.build/jsonSchema.ts index 6fd8ca3f54c..092e60ef2d3 100644 --- a/.build/jsonSchema.ts +++ b/.build/jsonSchema.ts @@ -25,6 +25,7 @@ const MERMAID_CONFIG_DIAGRAM_KEYS = [ 'sankey', 'block', 'packet', + 'fileTree', ] as const; /** diff --git a/cypress/integration/rendering/fileTree.spec.ts b/cypress/integration/rendering/fileTree.spec.ts new file mode 100644 index 00000000000..9143fb2a887 --- /dev/null +++ b/cypress/integration/rendering/fileTree.spec.ts @@ -0,0 +1,45 @@ +import { imgSnapshotTest } from '../../helpers/util'; + +describe('FileTree Diagram', () => { + it('should render a simple fileTree diagram', () => { + imgSnapshotTest( + `fileTree-beta + file1.ts` + ); + }); + + it('should render a complex fileTree diagram', () => { + imgSnapshotTest( + `fileTree-beta + root + folder1 + file1.js + file2.ts + folder2 + file3.spec.ts + folder3 + file4.ts + file5.ts + folder4 + file6.ts + file7.ts` + ); + }); + + it('should render a complex fileTree diagram with multiple roots', () => { + imgSnapshotTest( + `fileTree-beta + folder1 + file1.js + file2.ts + folder2 + file3.spec.ts + folder3 + file4.ts + file5.ts + folder4 + file6.ts + file7.ts` + ); + }); +}); diff --git a/demos/fileTree.html b/demos/fileTree.html new file mode 100644 index 00000000000..c03ee62debb --- /dev/null +++ b/demos/fileTree.html @@ -0,0 +1,66 @@ + + + + + + Mermaid Quick Test Page + + + + + +

FileTree Diagram Demo

+ +
+
+        fileTree-beta
+            packages
+                mermaid
+                    src
+                parser
+    
+ +
+        ---
+        config:
+            fileTree:
+                rowIndent: 40
+                lineThickness: 2
+            themeVariables:
+                fileTree:
+                    fontSize: '30px'
+        ---
+        fileTree-beta
+            packages
+                mermaid
+                    src
+                parser
+
+    
+
+ + + + + diff --git a/demos/index.html b/demos/index.html index 61a86a2aa04..5969e10f089 100644 --- a/demos/index.html +++ b/demos/index.html @@ -88,6 +88,9 @@

Packet

  • Layered Blocks

  • +
  • +

    FileTree

    +
  • diff --git a/docs/config/setup/interfaces/mermaid.MermaidConfig.md b/docs/config/setup/interfaces/mermaid.MermaidConfig.md index ef5a4313a94..a719ee2042b 100644 --- a/docs/config/setup/interfaces/mermaid.MermaidConfig.md +++ b/docs/config/setup/interfaces/mermaid.MermaidConfig.md @@ -111,7 +111,7 @@ should not change unless content is changed. #### Defined in -[packages/mermaid/src/config.type.ts:163](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L163) +[packages/mermaid/src/config.type.ts:164](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L164) --- @@ -155,7 +155,7 @@ See #### Defined in -[packages/mermaid/src/config.type.ts:165](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L165) +[packages/mermaid/src/config.type.ts:166](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L166) --- @@ -246,7 +246,7 @@ This option decides the amount of logging to be used by mermaid. #### Defined in -[packages/mermaid/src/config.type.ts:166](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L166) +[packages/mermaid/src/config.type.ts:167](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L167) --- @@ -401,7 +401,7 @@ This is useful when you want to control how to handle syntax errors in your appl #### Defined in -[packages/mermaid/src/config.type.ts:172](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L172) +[packages/mermaid/src/config.type.ts:173](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L173) --- @@ -448,13 +448,23 @@ You may also use `themeCSS` to override this value. --- +### fileTree + +• `Optional` **fileTree**: `FileTreeDiagramConfig` + +#### Defined in + +[packages/mermaid/src/config.type.ts:163](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L163) + +--- + ### wrap • `Optional` **wrap**: `boolean` #### Defined in -[packages/mermaid/src/config.type.ts:164](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L164) +[packages/mermaid/src/config.type.ts:165](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L165) --- diff --git a/docs/config/setup/modules/defaultConfig.md b/docs/config/setup/modules/defaultConfig.md index 512d240362d..0f0ace33caa 100644 --- a/docs/config/setup/modules/defaultConfig.md +++ b/docs/config/setup/modules/defaultConfig.md @@ -14,7 +14,7 @@ #### Defined in -[packages/mermaid/src/defaultConfig.ts:275](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/defaultConfig.ts#L275) +[packages/mermaid/src/defaultConfig.ts:279](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/defaultConfig.ts#L279) --- diff --git a/docs/syntax/fileTree.md b/docs/syntax/fileTree.md new file mode 100644 index 00000000000..3a139f1778e --- /dev/null +++ b/docs/syntax/fileTree.md @@ -0,0 +1,90 @@ +> **Warning** +> +> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT. +> +> ## Please edit the corresponding file in [/packages/mermaid/src/docs/syntax/fileTree.md](../../packages/mermaid/src/docs/syntax/fileTree.md). + +# FileTree Diagram (v\+) + +## Introduction + +A FileTree diagram is used to represent hierarchical data in the form of a directory-like structure. + +## Syntax + +The structure of the tree depends only on indentation. + +``` +fileTree-beta + + + + + +``` + +## Examples + +```mermaid-example +fileTree-beta + packages + mermaid + src + parser +``` + +```mermaid +fileTree-beta + packages + mermaid + src + parser +``` + +```mermaid-example +--- +config: + fileTree: + rowIndent: 5 + themeVariables: + fileTree: + fontSize: '20px' +--- +fileTree-beta + packages + mermaid + src + parser +``` + +```mermaid +--- +config: + fileTree: + rowIndent: 5 + themeVariables: + fileTree: + fontSize: '20px' +--- +fileTree-beta + packages + mermaid + src + parser +``` + +## Config Variables + +| Property | Description | Default Value | +| ------------- | ------------------------- | ------------- | +| rowIndent | Indentation for each row | 10 | +| paddingX | Horizontal padding of row | 5 | +| paddingY | Vertical padding of row | 5 | +| lineThickness | Thickness of the line | 1 | + +### Theme Variables + +| Property | Description | Default Value | +| --------- | ---------------------- | ------------- | +| fontSize | Font size of the label | '16px' | +| lineColor | Color of the line | 'black' | diff --git a/packages/mermaid/src/config.type.ts b/packages/mermaid/src/config.type.ts index b7cf27e72bc..cf39b84ac10 100644 --- a/packages/mermaid/src/config.type.ts +++ b/packages/mermaid/src/config.type.ts @@ -160,6 +160,7 @@ export interface MermaidConfig { sankey?: SankeyDiagramConfig; packet?: PacketDiagramConfig; block?: BlockDiagramConfig; + fileTree?: FileTreeDiagramConfig; dompurifyConfig?: DOMPurifyConfiguration; wrap?: boolean; fontSize?: number; @@ -1460,6 +1461,30 @@ export interface PacketDiagramConfig extends BaseDiagramConfig { export interface BlockDiagramConfig extends BaseDiagramConfig { padding?: number; } +/** + * The object containing configurations specific for fileTree diagrams. + * + * This interface was referenced by `MermaidConfig`'s JSON-Schema + * via the `definition` "FileTreeDiagramConfig". + */ +export interface FileTreeDiagramConfig extends BaseDiagramConfig { + /** + * Horizontal distance between rows differing by one level + */ + rowIndent?: number; + /** + * Horizontal padding of label + */ + paddingX?: number; + /** + * Vertical padding of label + */ + paddingY?: number; + /** + * Thickness of the line + */ + lineThickness?: number; +} /** * This interface was referenced by `MermaidConfig`'s JSON-Schema * via the `definition` "FontConfig". diff --git a/packages/mermaid/src/defaultConfig.ts b/packages/mermaid/src/defaultConfig.ts index 727842bba1d..d07f74af33e 100644 --- a/packages/mermaid/src/defaultConfig.ts +++ b/packages/mermaid/src/defaultConfig.ts @@ -260,6 +260,10 @@ const config: RequiredDeep = { packet: { ...defaultConfigJson.packet, }, + fileTree: { + ...defaultConfigJson.fileTree, + useWidth: undefined, + }, }; const keyify = (obj: any, prefix = ''): string[] => diff --git a/packages/mermaid/src/diagram-api/diagram-orchestration.ts b/packages/mermaid/src/diagram-api/diagram-orchestration.ts index 55d05c9aaa2..fbd39ec66f3 100644 --- a/packages/mermaid/src/diagram-api/diagram-orchestration.ts +++ b/packages/mermaid/src/diagram-api/diagram-orchestration.ts @@ -22,6 +22,7 @@ import mindmap from '../diagrams/mindmap/detector.js'; import sankey from '../diagrams/sankey/sankeyDetector.js'; import { packet } from '../diagrams/packet/detector.js'; import block from '../diagrams/block/blockDetector.js'; +import fileTree from '../diagrams/fileTree/detector.js'; import { registerLazyLoadedDiagrams } from './detectType.js'; import { registerDiagram } from './diagramAPI.js'; @@ -90,6 +91,7 @@ export const addDiagrams = () => { sankey, packet, xychart, - block + block, + fileTree ); }; diff --git a/packages/mermaid/src/diagrams/fileTree/db.ts b/packages/mermaid/src/diagrams/fileTree/db.ts new file mode 100644 index 00000000000..c8678fc9ec9 --- /dev/null +++ b/packages/mermaid/src/diagrams/fileTree/db.ts @@ -0,0 +1,79 @@ +import type { FileTreeDiagramConfig } from '../../config.type.js'; +import type { FileTreeDB, Node } from './types.js'; +import { getConfig as getCommonConfig } from '../../config.js'; +import DEFAULT_CONFIG from '../../defaultConfig.js'; +import { + clear as commonClear, + getAccDescription, + getAccTitle, + getDiagramTitle, + setAccDescription, + setAccTitle, + setDiagramTitle, +} from '../common/commonDb.js'; +import { cleanAndMerge } from '../../utils.js'; +import { ImperativeState } from '../../utils/imperativeState.js'; + +interface FileTreeState { + cnt: number; + stack: Node[]; +} + +const state = new ImperativeState(() => ({ + cnt: 1, + stack: [ + { + id: 0, + level: -1, + name: '/', + children: [], + }, + ], +})); + +const clear = () => { + state.reset(); + commonClear(); +}; + +const getRoot = () => { + return state.records.stack[0]; +}; + +const getCount = () => state.records.cnt; + +const defaultConfig: Required = DEFAULT_CONFIG.fileTree; + +const getConfig = (): Required => { + return cleanAndMerge(defaultConfig, getCommonConfig().fileTree); +}; + +const addNode = (level: number, name: string) => { + while (level <= state.records.stack[state.records.stack.length - 1].level) { + state.records.stack.pop(); + } + const node = { + id: state.records.cnt++, + level, + name, + children: [], + }; + state.records.stack[state.records.stack.length - 1].children.push(node); + state.records.stack.push(node); +}; + +const db: FileTreeDB = { + clear, + addNode, + getRoot, + getCount, + getConfig, + getAccTitle, + getAccDescription, + getDiagramTitle, + setAccDescription, + setAccTitle, + setDiagramTitle, +}; + +export default db; diff --git a/packages/mermaid/src/diagrams/fileTree/detector.ts b/packages/mermaid/src/diagrams/fileTree/detector.ts new file mode 100644 index 00000000000..34d77986229 --- /dev/null +++ b/packages/mermaid/src/diagrams/fileTree/detector.ts @@ -0,0 +1,21 @@ +import type { DiagramDetector, DiagramLoader } from '../../diagram-api/types.js'; +import type { ExternalDiagramDefinition } from '../../diagram-api/types.js'; + +const id = 'fileTree'; + +const detector: DiagramDetector = (txt) => { + return /^\s*fileTree-beta/.test(txt); +}; + +const loader: DiagramLoader = async () => { + const { diagram } = await import('./diagram.js'); + return { id, diagram }; +}; + +const plugin: ExternalDiagramDefinition = { + id, + detector, + loader, +}; + +export default plugin; diff --git a/packages/mermaid/src/diagrams/fileTree/diagram.ts b/packages/mermaid/src/diagrams/fileTree/diagram.ts new file mode 100644 index 00000000000..19d432d9684 --- /dev/null +++ b/packages/mermaid/src/diagrams/fileTree/diagram.ts @@ -0,0 +1,12 @@ +import type { DiagramDefinition } from '../../diagram-api/types.js'; +import { parser } from './parser.js'; +import db from './db.js'; +import renderer from './renderer.js'; +import styles from './styles.js'; + +export const diagram: DiagramDefinition = { + db, + renderer, + parser, + styles, +}; diff --git a/packages/mermaid/src/diagrams/fileTree/parser.ts b/packages/mermaid/src/diagrams/fileTree/parser.ts new file mode 100644 index 00000000000..58974a2b573 --- /dev/null +++ b/packages/mermaid/src/diagrams/fileTree/parser.ts @@ -0,0 +1,18 @@ +import type { ParserDefinition } from '../../diagram-api/types.js'; +import { log } from '../../logger.js'; +import { populateCommonDb } from '../common/populateCommonDb.js'; +import db from './db.js'; +import { parse, type FileTree } from '@mermaid-js/parser'; + +const populate = (ast: FileTree) => { + populateCommonDb(ast, db); + ast.nodes.map((node) => db.addNode(node.indent.length, node.name)); +}; + +export const parser: ParserDefinition = { + parse: async (input: string): Promise => { + const ast = await parse('fileTree', input); + log.debug(ast); + populate(ast); + }, +}; diff --git a/packages/mermaid/src/diagrams/fileTree/renderer.ts b/packages/mermaid/src/diagrams/fileTree/renderer.ts new file mode 100644 index 00000000000..9c03be95f24 --- /dev/null +++ b/packages/mermaid/src/diagrams/fileTree/renderer.ts @@ -0,0 +1,126 @@ +import type { DiagramRenderer, DrawDefinition } from '../../diagram-api/types.js'; +import { log } from '../../logger.js'; +import { selectSvgElement } from '../../rendering-util/selectSvgElement.js'; +import type { D3SVGElement, FileTreeDB } from './types.js'; +import { configureSvgSize } from '../../setupGraphViewbox.js'; +import type { FileTreeDiagramConfig } from '../../config.type.js'; +import type { Node } from './types.js'; + +const positionLabel = ( + x: number, + y: number, + node: Node, + domElem: D3SVGElement, + config: Required +) => { + const label = domElem + .append('text') + .text(node.name) + .attr('dominant-baseline', 'middle') + .attr('class', 'fileTree-node-label'); + const { height: labelHeight, width: labelWidth } = label.node()!.getBBox(); + const height = labelHeight + config.paddingY * 2; + const width = labelWidth + config.paddingX * 2; + label.attr('x', x + config.paddingX); + label.attr('y', y + height / 2); + node.BBox = { + x, + y, + width, + height, + }; +}; + +const positionLine = ( + domElem: D3SVGElement, + x1: number, + y1: number, + x2: number, + y2: number, + lineThickness: number +) => { + return domElem + .append('line') + .attr('x1', x1) + .attr('y1', y1) + .attr('x2', x2) + .attr('y2', y2) + .attr('stroke-width', lineThickness) + .attr('class', 'fileTree-node-line'); +}; + +const drawTree = ( + elem: D3SVGElement, + root: Node, + config: Required +) => { + let totalHeight = 0; + let totalWidth = 0; + const drawNode = ( + elem: D3SVGElement, + node: Node, + config: Required, + depth: number + ) => { + const indent = depth * (config.rowIndent + config.paddingX); + positionLabel(indent, totalHeight, node, elem, config); + const { height, width } = node.BBox!; + positionLine( + elem, + indent - config.rowIndent, + totalHeight + height / 2, + indent, + totalHeight + height / 2, + config.lineThickness + ); + + totalWidth = Math.max(totalWidth, indent + width); + totalHeight += height; + }; + + const processNode = (node: Node, depth = 0) => { + drawNode(elem, node, config, depth); + node.children.forEach((child) => { + processNode(child, depth + 1); + }); + const { x, y, height } = node.BBox!; + if (node.children.length) { + const { y: endY, height: endHeight } = node.children[node.children.length - 1].BBox!; + positionLine( + elem, + x + config.paddingX, + y + height, + x + config.paddingX, + endY + endHeight / 2 + config.lineThickness / 2, + config.lineThickness + ); + } + }; + + processNode(root); + return { totalHeight, totalWidth }; +}; + +const draw: DrawDefinition = (text, id, _ver, diagObj) => { + log.debug('Rendering fileTree diagram\n' + text); + + const db = diagObj.db as FileTreeDB; + const root = db.getRoot(); + const config = db.getConfig(); + + const svg = selectSvgElement(id); + const treeElem = svg.append('g'); + treeElem.attr('class', 'tree-view'); + + const { totalHeight, totalWidth } = drawTree(treeElem, root, config); + /* -${config.lineThickness/2} is required for a line with x coordinate = 0 + as there is overflow to the left due to the line being centered */ + svg.attr('viewBox', `-${config.lineThickness / 2} 0 ${totalWidth} ${totalHeight}`); + configureSvgSize(svg, totalHeight, totalWidth, config.useMaxWidth); +}; + +const renderer: DiagramRenderer = { + draw, +}; + +export default renderer; diff --git a/packages/mermaid/src/diagrams/fileTree/styles.ts b/packages/mermaid/src/diagrams/fileTree/styles.ts new file mode 100644 index 00000000000..a6ed41da881 --- /dev/null +++ b/packages/mermaid/src/diagrams/fileTree/styles.ts @@ -0,0 +1,26 @@ +import type { DiagramStylesProvider } from '../../diagram-api/types.js'; +import { cleanAndMerge } from '../../utils.js'; +import type { FileTreeDiagramStyles } from './types.js'; + +const defaultFileTreeDiagramStyles: Required = { + fontSize: '16px', + lineColor: 'black', +}; + +const styles: DiagramStylesProvider = ({ + fileTree, +}: { + fileTree?: FileTreeDiagramStyles; +}): string => { + const { fontSize, lineColor } = cleanAndMerge(defaultFileTreeDiagramStyles, fileTree); + return ` + .fileTree-node-label { + font-size: ${fontSize}; + } + .fileTree-node-line { + stroke: ${lineColor}; + } + `; +}; + +export default styles; diff --git a/packages/mermaid/src/diagrams/fileTree/types.ts b/packages/mermaid/src/diagrams/fileTree/types.ts new file mode 100644 index 00000000000..1ce91337835 --- /dev/null +++ b/packages/mermaid/src/diagrams/fileTree/types.ts @@ -0,0 +1,31 @@ +import type { FileTreeDiagramConfig } from '../../config.type.js'; +import type { DiagramDBBase } from '../../diagram-api/types.js'; +import type { Selection } from 'd3-selection'; + +interface BBox { + x: number; + y: number; + width: number; + height: number; +} + +export interface Node { + id: number; + level: number; + name: string; + BBox?: BBox; + children: Node[]; +} + +export interface FileTreeDB extends DiagramDBBase { + addNode: (level: number, name: string) => void; + getRoot: () => Node; + getCount: () => number; +} + +export interface FileTreeDiagramStyles { + fontSize?: string; + lineColor?: string; +} + +export type D3SVGElement = Selection; diff --git a/packages/mermaid/src/docs/.vitepress/config.ts b/packages/mermaid/src/docs/.vitepress/config.ts index 940fc6940a8..1f7e7adecb2 100644 --- a/packages/mermaid/src/docs/.vitepress/config.ts +++ b/packages/mermaid/src/docs/.vitepress/config.ts @@ -157,6 +157,7 @@ function sidebarSyntax() { { text: 'XY Chart 🔥', link: '/syntax/xyChart' }, { text: 'Block Diagram 🔥', link: '/syntax/block' }, { text: 'Packet 🔥', link: '/syntax/packet' }, + { text: 'FileTree 🔥', link: '/syntax/fileTree' }, { text: 'Other Examples', link: '/syntax/examples' }, ], }, diff --git a/packages/mermaid/src/docs/syntax/fileTree.md b/packages/mermaid/src/docs/syntax/fileTree.md new file mode 100644 index 00000000000..20d319b961a --- /dev/null +++ b/packages/mermaid/src/docs/syntax/fileTree.md @@ -0,0 +1,60 @@ +# FileTree Diagram (v+) + +## Introduction + +A FileTree diagram is used to represent hierarchical data in the form of a directory-like structure. + +## Syntax + +The structure of the tree depends only on indentation. + +``` +fileTree-beta + + + + + +``` + +## Examples + +```mermaid-example +fileTree-beta + packages + mermaid + src + parser +``` + +```mermaid-example +--- +config: + fileTree: + rowIndent: 5 + themeVariables: + fileTree: + fontSize: '20px' +--- +fileTree-beta + packages + mermaid + src + parser +``` + +## Config Variables + +| Property | Description | Default Value | +| ------------- | ------------------------- | ------------- | +| rowIndent | Indentation for each row | 10 | +| paddingX | Horizontal padding of row | 5 | +| paddingY | Vertical padding of row | 5 | +| lineThickness | Thickness of the line | 1 | + +### Theme Variables + +| Property | Description | Default Value | +| --------- | ---------------------- | ------------- | +| fontSize | Font size of the label | '16px' | +| lineColor | Color of the line | 'black' | diff --git a/packages/mermaid/src/mermaidAPI.spec.ts b/packages/mermaid/src/mermaidAPI.spec.ts index 7958f397e02..12c391cc1ef 100644 --- a/packages/mermaid/src/mermaidAPI.spec.ts +++ b/packages/mermaid/src/mermaidAPI.spec.ts @@ -31,6 +31,7 @@ vi.mock('./diagrams/xychart/xychartRenderer.js'); vi.mock('./diagrams/requirement/requirementRenderer.js'); vi.mock('./diagrams/sequence/sequenceRenderer.js'); vi.mock('./diagrams/state/stateRenderer-v2.js'); +vi.mock('./diagrams/fileTree/renderer.js'); // ------------------------------------- @@ -733,6 +734,7 @@ describe('mermaidAPI', () => { { textDiagramType: 'requirementDiagram', expectedType: 'requirement' }, { textDiagramType: 'sequenceDiagram', expectedType: 'sequence' }, { textDiagramType: 'stateDiagram-v2', expectedType: 'stateDiagram' }, + { textDiagramType: 'fileTree-beta', expectedType: 'fileTree' }, ]; describe('accessibility', () => { diff --git a/packages/mermaid/src/schemas/config.schema.yaml b/packages/mermaid/src/schemas/config.schema.yaml index d798ec63b22..5f797841623 100644 --- a/packages/mermaid/src/schemas/config.schema.yaml +++ b/packages/mermaid/src/schemas/config.schema.yaml @@ -52,6 +52,7 @@ required: - sankey - packet - block + - fileTree properties: theme: description: | @@ -237,6 +238,8 @@ properties: $ref: '#/$defs/PacketDiagramConfig' block: $ref: '#/$defs/BlockDiagramConfig' + fileTree: + $ref: '#/$defs/FileTreeDiagramConfig' dompurifyConfig: title: DOM Purify Configuration description: Configuration options to pass to the `dompurify` library. @@ -2093,6 +2096,34 @@ $defs: # JSON Schema definition (maybe we should move these to a separate file) minimum: 0 default: 8 + FileTreeDiagramConfig: + title: FileTree Diagram Config + allOf: [{ $ref: '#/$defs/BaseDiagramConfig' }] + description: The object containing configurations specific for fileTree diagrams. + type: object + unevaluatedProperties: false + properties: + rowIndent: + description: Horizontal distance between rows differing by one level + type: number + minimum: 0 + default: 10 + paddingX: + description: Horizontal padding of label + type: number + minimum: 0 + default: 5 + paddingY: + description: Vertical padding of label + type: number + minimum: 0 + default: 5 + lineThickness: + description: Thickness of the line + type: number + minimum: 0 + default: 1 + FontCalculator: title: Font Calculator description: | diff --git a/packages/mermaid/src/styles.spec.ts b/packages/mermaid/src/styles.spec.ts index 70e9e7ec54a..a436e8d231d 100644 --- a/packages/mermaid/src/styles.spec.ts +++ b/packages/mermaid/src/styles.spec.ts @@ -29,6 +29,7 @@ import timeline from './diagrams/timeline/styles.js'; import mindmap from './diagrams/mindmap/styles.js'; import packet from './diagrams/packet/styles.js'; import block from './diagrams/block/styles.js'; +import fileTree from './diagrams/fileTree/styles.js'; import themes from './themes/index.js'; function checkValidStylisCSSStyleSheet(stylisString: string) { @@ -99,6 +100,7 @@ describe('styles', () => { block, timeline, packet, + fileTree, })) { test(`should return a valid style for diagram ${diagramId} and theme ${themeId}`, async () => { const { default: getStyles, addStylesForDiagram } = await import('./styles.js'); diff --git a/packages/parser/langium-config.json b/packages/parser/langium-config.json index c750f049d50..5735fda9f8a 100644 --- a/packages/parser/langium-config.json +++ b/packages/parser/langium-config.json @@ -15,6 +15,11 @@ "id": "pie", "grammar": "src/language/pie/pie.langium", "fileExtensions": [".mmd", ".mermaid"] + }, + { + "id": "fileTree", + "grammar": "src/language/fileTree/fileTree.langium", + "fileExtensions": [".mmd", ".mermaid"] } ], "mode": "production", diff --git a/packages/parser/src/language/fileTree/fileTree.langium b/packages/parser/src/language/fileTree/fileTree.langium new file mode 100644 index 00000000000..e7107b2434f --- /dev/null +++ b/packages/parser/src/language/fileTree/fileTree.langium @@ -0,0 +1,34 @@ +grammar FileTree + +entry FileTree: + SPACELIST? + "fileTree-beta" + TitleAndAccessibilities? + nodes+=TreeNode* + SPACELIST? +; + +TreeNode: + indent=SPACELIST name=TEXT +; + +interface Common { + accDescr?: string; + accTitle?: string; + title?: string; +} + +fragment TitleAndAccessibilities: + ((accDescr=ACC_DESCR | accTitle=ACC_TITLE | title=TITLE))+ +; + +hidden terminal EOL: /\s*(\r?\n)+/; +terminal SPACELIST: /\s+/; +terminal TEXT: /\S+/; +terminal ACC_DESCR: /[\t ]*accDescr(?:[\t ]*:([^\n\r]*?(?=%%)|[^\n\r]*)|\s*{([^}]*)})/; +terminal ACC_TITLE: /[\t ]*accTitle[\t ]*:(?:[^\n\r]*?(?=%%)|[^\n\r]*)/; +terminal TITLE: /[\t ]*title(?:[\t ][^\n\r]*?(?=%%)|[\t ][^\n\r]*|)/; + + + + diff --git a/packages/parser/src/language/fileTree/index.ts b/packages/parser/src/language/fileTree/index.ts new file mode 100644 index 00000000000..fd3c604b084 --- /dev/null +++ b/packages/parser/src/language/fileTree/index.ts @@ -0,0 +1 @@ +export * from './module.js'; diff --git a/packages/parser/src/language/fileTree/module.ts b/packages/parser/src/language/fileTree/module.ts new file mode 100644 index 00000000000..fd0eba80f37 --- /dev/null +++ b/packages/parser/src/language/fileTree/module.ts @@ -0,0 +1,77 @@ +import type { + DefaultSharedCoreModuleContext, + LangiumCoreServices, + LangiumSharedCoreServices, + Module, + PartialLangiumCoreServices, +} from 'langium'; +import { + EmptyFileSystem, + createDefaultCoreModule, + createDefaultSharedCoreModule, + inject, +} from 'langium'; + +import { CommonValueConverter } from '../common/valueConverter.js'; +import { MermaidGeneratedSharedModule, FileTreeGeneratedModule } from '../generated/module.js'; +import { FileTreeTokenBuilder } from './tokenBuilder.js'; + +/** + * Declaration of `FileTree` services. + */ +interface FileTreeAddedServices { + parser: { + TokenBuilder: FileTreeTokenBuilder; + ValueConverter: CommonValueConverter; + }; +} + +/** + * Union of Langium default services and `FileTree` services. + */ +export type FileTreeServices = LangiumCoreServices & FileTreeAddedServices; + +/** + * Dependency injection module that overrides Langium default services and + * contributes the declared `FileTree` services. + */ +export const FileTreeModule: Module< + FileTreeServices, + PartialLangiumCoreServices & FileTreeAddedServices +> = { + parser: { + TokenBuilder: () => new FileTreeTokenBuilder(), + ValueConverter: () => new CommonValueConverter(), + }, +}; + +/** + * Create the full set of services required by Langium. + * + * First inject the shared services by merging two modules: + * - Langium default shared services + * - Services generated by langium-cli + * + * Then inject the language-specific services by merging three modules: + * - Langium default language-specific services + * - Services generated by langium-cli + * - Services specified in this file + * @param context - Optional module context with the LSP connection + * @returns An object wrapping the shared services and the language-specific services + */ +export function createFileTreeServices(context: DefaultSharedCoreModuleContext = EmptyFileSystem): { + shared: LangiumSharedCoreServices; + FileTree: FileTreeServices; +} { + const shared: LangiumSharedCoreServices = inject( + createDefaultSharedCoreModule(context), + MermaidGeneratedSharedModule + ); + const FileTree: FileTreeServices = inject( + createDefaultCoreModule({ shared }), + FileTreeGeneratedModule, + FileTreeModule + ); + shared.ServiceRegistry.register(FileTree); + return { shared, FileTree }; +} diff --git a/packages/parser/src/language/fileTree/tokenBuilder.ts b/packages/parser/src/language/fileTree/tokenBuilder.ts new file mode 100644 index 00000000000..c38b93447c0 --- /dev/null +++ b/packages/parser/src/language/fileTree/tokenBuilder.ts @@ -0,0 +1,7 @@ +import { AbstractMermaidTokenBuilder } from '../common/index.js'; + +export class FileTreeTokenBuilder extends AbstractMermaidTokenBuilder { + public constructor() { + super(['fileTree-beta']); + } +} diff --git a/packages/parser/src/language/index.ts b/packages/parser/src/language/index.ts index 9f1d92ba8a7..2212242eade 100644 --- a/packages/parser/src/language/index.ts +++ b/packages/parser/src/language/index.ts @@ -11,6 +11,8 @@ export { isPacketBlock, isPie, isPieSection, + FileTree, + TreeNode, } from './generated/ast.js'; export { InfoGeneratedModule, @@ -23,3 +25,4 @@ export * from './common/index.js'; export * from './info/index.js'; export * from './packet/index.js'; export * from './pie/index.js'; +export * from './fileTree/index.js'; diff --git a/packages/parser/src/parse.ts b/packages/parser/src/parse.ts index 992b9650609..f3b7864e7c7 100644 --- a/packages/parser/src/parse.ts +++ b/packages/parser/src/parse.ts @@ -1,8 +1,8 @@ import type { LangiumParser, ParseResult } from 'langium'; -import type { Info, Packet, Pie } from './index.js'; +import type { Info, Packet, Pie, FileTree } from './index.js'; -export type DiagramAST = Info | Packet | Pie; +export type DiagramAST = Info | Packet | Pie | FileTree; const parsers: Record = {}; const initializers = { @@ -21,11 +21,17 @@ const initializers = { const parser = createPieServices().Pie.parser.LangiumParser; parsers.pie = parser; }, + fileTree: async () => { + const { createFileTreeServices } = await import('./language/fileTree/index.js'); + const parser = createFileTreeServices().FileTree.parser.LangiumParser; + parsers.fileTree = parser; + }, } as const; export async function parse(diagramType: 'info', text: string): Promise; export async function parse(diagramType: 'packet', text: string): Promise; export async function parse(diagramType: 'pie', text: string): Promise; +export async function parse(diagramType: 'fileTree', text: string): Promise; export async function parse( diagramType: keyof typeof initializers, text: string