diff --git a/.vscode/settings.json b/.vscode/settings.json index 30bf8c2..ec20fef 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -7,5 +7,6 @@ "out": true // set this to false to include "out" folder in search results }, // Turn off tsc task auto detection since we have the necessary tasks as npm scripts - "typescript.tsc.autoDetect": "off" + "typescript.tsc.autoDetect": "off", + "cSpell.words": ["mermaidchart"] } \ No newline at end of file diff --git a/package.json b/package.json index 2658714..3f77cbd 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,9 @@ ], "main": "./out/extension.js", "contributes": { + "completionProvider": { + "triggerCharacters": ["m"] + }, "languages": [ { "id": "mermaid", @@ -429,11 +432,23 @@ "when": "view == mermaidChart" } ], + "view/item/context": [ + { + "command": "mermaidChart.downloadDiagram", + "when": "viewItem == document", + "group": "inline" + } + ], "editor/context": [ { "command": "mermaidChart.preview", "when": "resourceExtname =~ /^\\.(mmd|mermaid)$/ || resourceLangId =~ /^mermaid/", "group": "navigation" + }, + { + "command": "mermaidChart.connectDiagramToMermaidChart", + "when": "resourceExtname =~ /^\\.(mmd|mermaid)$/ || resourceLangId =~ /^mermaid/", + "group": "navigation" } ] }, @@ -448,9 +463,18 @@ "command": "mermaidChart.syncDiagramWithMermaid", "when": "editorLangId =~ /^mermaid/", "mac": "cmd+s" + }, + { + "command": "mermaidChart.showCompletions", + "key": "ctrl+shift+k", + "when": "editorLangId =~ /^mermaid/" } ], "commands": [ + { + "command": "mermaidChart.downloadDiagram", + "title": "Download" + }, { "command": "mermaidChart.syncDiagramWithMermaid", "title": "MermaidChart: Sync Diagram" @@ -483,6 +507,14 @@ { "command": "mermaidChart.refresh", "title": "Refresh" + }, + { + "command": "mermaidChart.connectDiagramToMermaidChart", + "title": "MermaidChart: Connect Diagram" + }, + { + "command": "mermaidChart.showCompletions", + "title": "Trigger Mermaid Completions" } ] }, @@ -514,8 +546,9 @@ "typescript": "^4.9.5" }, "dependencies": { - "axios": "^1.4.0", "uuid": "^9.0.0", + "@mermaidchart/sdk": "^0.2.0", "yaml": "^2.7.0" - } + }, + "packageManager": "pnpm@9.7.1+sha512.faf344af2d6ca65c4c5c8c2224ea77a81a5e8859cbc4e06b1511ddce2f0151512431dd19e6aff31f2c6a8f5f2aced9bd2273e1fed7dd4de1868984059d2c4247" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cc03314..711e608 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,9 +8,9 @@ importers: .: dependencies: - axios: - specifier: ^1.4.0 - version: 1.7.9 + '@mermaidchart/sdk': + specifier: ^0.2.0 + version: 0.2.0 uuid: specifier: ^9.0.0 version: 9.0.1 @@ -164,6 +164,10 @@ packages: resolution: {integrity: sha512-An7l1hEr0w1HMMh1LU+rtDtqL7/jw74ORlc9Wnh06v7TU/xpG39/Zdr1ZJu3QpjUfKJ+E0/OXMW8DRSWTlh7qQ==} engines: {node: '>=16'} + '@badgateway/oauth2-client@2.4.2': + resolution: {integrity: sha512-70Fmzlmn8EfCjjssls8N6E94quBUWnLhu4inPZU2pkwpc6ZvbErkLRvtkYl81KFCvVcuVC0X10QPZVNwjXo2KA==} + engines: {node: '>= 14'} + '@braintree/sanitize-url@7.1.1': resolution: {integrity: sha512-i1L7noDNxtFyL5DmZafWy1wRVhGehQmzZaz1HiN5e7iylJMSZR7ekOV7NsIqa5qBldlLrsKv4HbgFUVlQrz8Mw==} @@ -546,6 +550,10 @@ packages: '@mermaid-js/parser@0.3.1-rc.1': resolution: {integrity: sha512-gYXEGLui3Cfp+P37TBz2no4LuoEY2fEnK1MTh9YPbuAta7kVbZXPpTeay9ahtV7Zi6GkfW3yAUGM9fJ1KkoiWA==} + '@mermaidchart/sdk@0.2.0': + resolution: {integrity: sha512-ghIeLxw9FH6WAmw3lDjwGdk0ZCtd08DSEti5OJVKCDi1iIFa/MT8M0tnzBiQvVs/YOeBwW7lzqU+rkT0lxh43g==} + engines: {node: ^18.18.0 || >= 20.0.0} + '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} engines: {node: '>= 8'} @@ -2756,6 +2764,8 @@ snapshots: jsonwebtoken: 9.0.2 uuid: 8.3.2 + '@badgateway/oauth2-client@2.4.2': {} + '@braintree/sanitize-url@7.1.1': {} '@chevrotain/cst-dts-gen@11.0.3': @@ -3036,6 +3046,14 @@ snapshots: dependencies: langium: 3.0.0 + '@mermaidchart/sdk@0.2.0': + dependencies: + '@badgateway/oauth2-client': 2.4.2 + axios: 1.7.9 + uuid: 9.0.1 + transitivePeerDependencies: + - debug + '@nodelib/fs.scandir@2.1.5': dependencies: '@nodelib/fs.stat': 2.0.5 diff --git a/src/constants/condSnippets.ts b/src/constants/condSnippets.ts new file mode 100644 index 0000000..e12ad1c --- /dev/null +++ b/src/constants/condSnippets.ts @@ -0,0 +1,455 @@ +export interface SnippetData { + id: string; + diagram: "flowchart" | "sequenceDiagram"; + section: "shapes" | "notes" | "edges" | "other" | "messages"; + name: string; + code?: string; + completion?: string; + sample?: string; + image: string; +} + +const snippets: SnippetData[] = [ + { + id: "mmflrect", + diagram: "flowchart", + section: "shapes", + name: "Rectangle", + completion: '${1:rectId}["`${2:label}`"] $0', + sample: 'rectId["`label`"]', + image: "/img/flowchart/shape_rectangle", + }, + { + id: "mmflrounded", + diagram: "flowchart", + section: "shapes", + name: "Rounded", + completion: '${1:roundedId}("`${2:label}`") $0', + sample: 'roundedId("`label`")', + image: "/img/flowchart/shape_rounded", + }, + { + id: "mmflstadium", + diagram: "flowchart", + section: "shapes", + name: "Stadium", + completion: '${1:stadiumId}(["`${2:label}`"]) $0', + sample: 'stadiumId(["`label`"])', + image: "/img/flowchart/shape_stadium", + }, + { + id: "mmflsubroutine", + diagram: "flowchart", + section: "shapes", + name: "Subroutine", + completion: '${1:subId}[["`${2:label}`"]] $0', + sample: 'subId[["`label`"]]', + image: "/img/flowchart/shape_subroutine", + }, + { + id: "mmfldatabase", + diagram: "flowchart", + section: "shapes", + name: "Database", + completion: '${1:dbId}[("`${2:label}`")] $0', + sample: 'dbId[["`label`"]]', + image: "/img/flowchart/shape_database", + }, + { + id: "mmfldecision", + diagram: "flowchart", + section: "shapes", + name: "Decision", + completion: '${1:decisionId}{"`${2:label}`"} $0', + sample: 'decisionId{"`label`"}', + image: "/img/flowchart/shape_decision", + }, + { + id: "mmflcircle", + diagram: "flowchart", + section: "shapes", + name: "Circle", + completion: '${1:circleId}(("`${2:label}`")) $0', + sample: 'circleId(("`label`"))', + image: "/img/flowchart/shape_circle", + }, + { + id: "mmflasymetric", + diagram: "flowchart", + section: "shapes", + name: "Asymmetric", + completion: '${1:asymmetricId}>"`${2:label}`"] $0', + sample: 'asymmetricId>"`label`"]', + image: "/img/flowchart/shape_asymmetric", + }, + { + id: "mmflhexagon", + diagram: "flowchart", + section: "shapes", + name: "Hexagon", + code: "mmflhexagon", + completion: '${1:hexId}{{"`${2:label}`"}} $0', + sample: 'hexId{{"`label`"}}', + image: "/img/flowchart/shape_hexagon", + }, + { + id: "mmflparallelogram", + diagram: "flowchart", + section: "shapes", + name: "Parallelogram", + completion: '${1:paraId}[/"`${2:label}`"/]$0', + sample: 'paraId[/"`label`"/]', + image: "/img/flowchart/shape_parallelogram", + }, + { + id: "mmflparallelogramrev", + diagram: "flowchart", + section: "shapes", + name: "Parallelogram reversed", + completion: '${1:paraRevId}[\\"`${2:label}`"\\] $0', + sample: 'paraRevId[\\"`label`"\\]', + image: "/img/flowchart/shape_alt-parallelogram", + }, + { + id: "mmfltrapezoid", + diagram: "flowchart", + section: "shapes", + name: "Trapezoid", + completion: '${1:trapId}[/"`${2:label}`"\\]$0', + sample: 'trapId[/"`label`"\\]', + image: "/img/flowchart/shape_trapezoid", + }, + { + id: "mmfltrapezoidrev", + diagram: "flowchart", + section: "shapes", + name: "Trapezoid reversed", + completion: '${1:trapRevId}[\\"`${2:label}`"/]$0', + sample: 'trapRevId[\\"`label`"/]', + image: "/img/flowchart/shape_alt-trapezoid", + }, + { + id: "mmfldoublecircle", + diagram: "flowchart", + section: "shapes", + name: "Double Circle", + completion: '${1:doubleCircleId}((("`${2:label}`")))$0', + sample: 'doubleCircleId((("`label`")))', + image: "/img/flowchart/shape_double-circle", + }, + /* Arrows */ + { + id: "mmflarr", + diagram: "flowchart", + section: "edges", + name: "Arrow", + sample: "-->", + completion: "-->$0", + image: "/img/flowchart/edge_arrow", + }, + { + id: "mmflthkarr", + diagram: "flowchart", + section: "edges", + name: "Thick Arrow", + sample: "==>", + completion: "==>$0", + image: "/img/flowchart/edge_thick-arrow", + }, + { + id: "mmfldaarr", + diagram: "flowchart", + section: "edges", + name: "Dashed arrow", + sample: "-.->", + completion: "-.->$0", + image: "/img/flowchart/edge_dashed-arrow", + }, + { + id: "mmflarrlabel", + diagram: "flowchart", + section: "edges", + name: "Arrow with Label", + sample: "-- label -->", + completion: "-- ${1:label} -->$0", + image: "/img/flowchart/edge_arrow-with-label", + }, + { + id: "mmfltharrlabel", + diagram: "flowchart", + section: "edges", + name: "Thick Arrow with Label", + sample: "== label ==>", + completion: "== ${1:label} ==>$0", + image: "/img/flowchart/edge_thick-arrow-with-label", + }, + { + id: "mmfldaarrlabel", + diagram: "flowchart", + section: "edges", + name: "Dashed arrow with Label", + sample: "-. Label .->", + completion: "-. ${1:label} .->$0", + image: "/img/flowchart/edge_dashed-arrow-with-label", + }, + { + id: "mmflsubgraph", + diagram: "flowchart", + section: "other", + name: "Subgraph", + code: "mmflsubgraph", + sample: `subgraph one + a1-->a2 + end`, + completion: "subgraph ${1:subgraphName}\n ${2:nodeId}\nend$0", + image: "/img/flowchart/other_subgraph", + }, + { + id: "mmflclassadd", + diagram: "flowchart", + section: "other", + name: "Add class to a node", + sample: `A:::theclass`, + completion: "${1:A}:::${2:theclass}$0", + image: "/img/flowchart/other_add-class-to-a-node", + }, + { + id: "mmflclassdef", + diagram: "flowchart", + section: "other", + name: "Add class definition", + sample: `classDef theclass fill:#f96`, + completion: "classDef ${1:theclass} ${2: fill:#f96, stroke:#303;}$0", + image: "/img/flowchart/other_add-class-definition", + }, + /* Sequence Diagram */ + { + id: "mmsdpart", + diagram: "sequenceDiagram", + section: "shapes", + name: "Participant", + completion: "participant ${1:aId} as ${2:Alice}\n$0", + sample: "participant aId as Alice", + image: "/img/sequenceDiagram/shape_participant", + }, + { + id: "mmsdact", + diagram: "sequenceDiagram", + section: "shapes", + name: "Actor", + completion: "actor ${1:jId} as ${2:John}\n$0", + sample: "actor jId as John", + image: "/img/sequenceDiagram/shape_actor", + }, + { + id: "mmsdnoteleft", + diagram: "sequenceDiagram", + section: "notes", + name: "Note left of", + completion: "Note left of ${1:Alice}: ${2:A typical message}\n$0", + sample: "Note left of Alice: A typical message", + image: "/img/sequenceDiagram/note_left_of", + }, + /* Notes */ + { + id: "mmsdnoteover", + diagram: "sequenceDiagram", + section: "notes", + name: "Note over life line", + code: "mmsdnoteover", + completion: "Note over ${1:Alice},${2:John}: ${3:A typical message}\n$0", + sample: "Note over Alice,John: A typical message", + image: "/img/sequenceDiagram/note_over", + }, + { + id: "mmsdnoteright", + diagram: "sequenceDiagram", + section: "notes", + name: "Note right of", + completion: "Note right of ${1:Alice}: ${2:A typical message}\n$0", + sample: "Note right of Alice: A typical message", + image: "/img/sequenceDiagram/note_right_of", + }, + /* Other */ + { + id: "mmsdloop", + diagram: "sequenceDiagram", + section: "other", + name: "Loop", + completion: + "loop ${1:Title} \n ${2:From} ->> ${3:To}:${4:Message text}\nend\n$0", + sample: `loop Loop text + Alice->>John: Normal line + end`, + image: "/img/sequenceDiagram/other_loop", + }, + { + id: "mmsdalt", + diagram: "sequenceDiagram", + section: "other", + name: "Alt", + completion: + "alt ${1:Title}\n ${2:From} ->> ${3:To}:${4:Message text}\nelse ${5:Another Title}\n ${6:From} ->> ${7:To}:${8:Message text}\nend\n$0", + sample: `alt Alt text + Alice->>John: First case + else + Alice->>John: Second case + end + `, + image: "/img/sequenceDiagram/other_alt", + }, + { + id: "mmsdopt", + diagram: "sequenceDiagram", + section: "other", + name: "Opt", + completion: + "opt ${1:Title} \n ${2:From} ->> ${3:To}:${4:Message text} \nend\n$0", + sample: `opt Opt text + Alice->>John: First case + end + `, + image: "/img/sequenceDiagram/other_opt", + }, + { + id: "mmsdpar", + diagram: "sequenceDiagram", + section: "other", + name: "Par", + completion: + "par ${1:Title} \n ${2:From} ->> ${3:To}:${4:Message text} \nand\n ${5:From} ->> ${7:To}:${8:Message text}\nend\n$0", + sample: `par Title + Alice->>John: First parallel flow + and + Alice->>John: Second parallel flow + end + `, + image: "/img/sequenceDiagram/other_par", + }, + { + id: "mmsdhigh", + diagram: "sequenceDiagram", + section: "other", + name: "Highlight", + completion: + "rect rgb(191, 223, 255)\n ${1:From} ->> ${2:To}:${3:Message text}\nend\n$0", + sample: `rect rgb(191, 223, 255)\n + Alice->>John: Normal line + end + `, + image: "/img/sequenceDiagram/highlight", + }, + { + id: "mmsdcrit", + diagram: "sequenceDiagram", + section: "other", + name: "Critical Region", + completion: + "critical ${1:Action} \n ${2:From} ->> ${3:To}:${4:Message text}\noption ${5: Action}\n ${6:From} ->> ${7:To}:${8:Message text}\nend\n$0", + sample: `critical Action + Alice->>John: First parallel flow + option Another action + Alice->>John: Second parallel flow + end`, + image: "/img/sequenceDiagram/other_critical-region", + }, + { + id: "mmsdbreak", + diagram: "sequenceDiagram", + section: "other", + name: "Break", + completion: + "break ${1:Some reason} \n ${2:From} ->> ${3:To}:${4:Message text}\nend\n$0", + sample: `break Some reason + Alice->>John: First parallel flow + end + `, + image: "/img/sequenceDiagram/other_break", + }, + + /* Messages */ + { + id: "mmsdsolid", + diagram: "sequenceDiagram", + section: "messages", + name: "Solid Line", + completion: "${1:aId} -> ${2:jId}: ${3:Message text}\n$0", + sample: "aId -> jId: Message text\n", + image: "/img/sequenceDiagram/message_solid", + }, + { + id: "mmsddotted", + diagram: "sequenceDiagram", + section: "messages", + name: "Dotted Line", + completion: "${1:aId} --> ${2:jId}: ${3:Message text}\n$0", + sample: "aId --> jId: Message text\n", + image: "/img/sequenceDiagram/message_dotted", + }, + { + id: "mmsdsolidarr", + diagram: "sequenceDiagram", + section: "messages", + name: "Solid Line Arrow", + completion: "${1:aId} ->> ${2:jId}: ${3:Message text}\n$0", + sample: "aId ->> jId: Message text\n", + image: "/img/sequenceDiagram/message_solid_arrow", + }, + { + id: "mmsddotarr", + diagram: "sequenceDiagram", + section: "messages", + name: "Dotted Line Arrow", + completion: "${1:aId} -->> ${2:jId}: ${3:Message text}\n$0", + sample: "aId -->> jId: Message text\n", + image: "/img/sequenceDiagram/message_dotted_arrow", + }, + { + id: "mmsdmsolcross", + diagram: "sequenceDiagram", + section: "messages", + name: "Solid Line Cross", + completion: "${1:aId} -x ${2:jId}: ${3: Message text}\n$0", + sample: "aId -x jId: Message text\n", + image: "/img/sequenceDiagram/message_solid_cross", + }, + { + id: "mmsdmdotcross", + diagram: "sequenceDiagram", + section: "messages", + name: "Dotted Line Cross", + completion: "${1:aId} --x ${2:jId}: ${3:Message text}\n$0", + sample: "aId --x jId: Message text\n", + image: "/img/sequenceDiagram/message_dotted_cross", + }, + { + id: "mmsdmsolasync", + diagram: "sequenceDiagram", + section: "messages", + name: "Solid Line Async", + completion: "${1:aId} -) ${2:jId}: ${3:Message text}\n$0", + sample: "aId -) jId: Message text\n", + image: "/img/sequenceDiagram/message_solid_async", + }, + { + id: "mmsdmdotasync", + diagram: "sequenceDiagram", + section: "messages", + name: "Dotted Line Async", + completion: "${1:aId} --) ${2:jId}: ${3:Message text}\n$0", + sample: "aId --) jId: Message text\n", + image: "/img/sequenceDiagram/message_dotted_async", + }, +]; + +export const getSnippetsBasedOnDiagram = (languageId: string) => { + const parts = languageId.split('.'); + + // Ensure there is a second part (diagram type) + if (parts.length < 2) { + return []; // Return an empty array if no diagram type is found + } + + const diagramType = parts[1]; + return snippets.filter((item) => item.diagram.toLocaleLowerCase() === diagramType.toLocaleLowerCase()); +}; \ No newline at end of file diff --git a/src/extension.ts b/src/extension.ts index 4169223..bbea79f 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1,5 +1,5 @@ import * as vscode from "vscode"; -import { MermaidChartProvider, MCTreeItem, getAllTreeViewProjectsCache } from "./mermaidChartProvider"; +import { MermaidChartProvider, MCTreeItem, getAllTreeViewProjectsCache, getProjectIdForDocument, Document } from "./mermaidChartProvider"; import { MermaidChartVSCode } from "./mermaidChartVSCode"; import { applyMermaidChartTokenHighlighting, @@ -17,6 +17,8 @@ import { createMermaidFile, getPreview } from "./commands/createFile"; import { handleTextDocumentChange } from "./eventHandlers"; import path = require("path"); import { TempFileCache } from "./cache/tempFileCache"; +import { PreviewPanel } from "./panels/previewPanel"; +import { getSnippetsBasedOnDiagram } from "./constants/condSnippets"; import { ensureIdField, extractIdFromCode, extractMermaidCode } from "./frontmatter"; let diagramMappings: { [key: string]: string[] } = require('../src/diagramTypeWords.json'); @@ -193,7 +195,7 @@ context.subscriptions.push( ); context.subscriptions.push( - vscode.commands.registerCommand('mermaid.connectDiagram',async(uri:vscode.Uri, range:vscode.Range)=>{ + vscode.commands.registerCommand('mermaid.connectDiagram', async (uri: vscode.Uri, range: vscode.Range) => { const document = await vscode.workspace.openTextDocument(uri); const content = document.getText(); const fileExt = path.extname(document.fileName); @@ -205,31 +207,58 @@ context.subscriptions.push( projects.map((p) => ({ label: p.title, description: p.title, projectId: p.uuid })), { placeHolder: "Select a project to save the diagram" } ); - + if (!selectedProject || !selectedProject?.projectId) { - vscode.window.showInformationMessage("Operation cancelled."); - return; + vscode.window.showInformationMessage("Operation cancelled."); + return; } - const response = await mcAPI.createDocumentWithDiagram(diagramCode, selectedProject.projectId) + try { + const newDocument = await mcAPI.createDocument(selectedProject.projectId); + + if (!newDocument || !newDocument.documentID) { + vscode.window.showErrorMessage("Failed to create a new document."); + return; + } + + await mcAPI.setDocument({ + documentID: newDocument.documentID, + projectID: selectedProject.projectId, + code: diagramCode, + }); - const processedCode = ensureIdField(diagramCode, response.documentID); - const editor= await await createMermaidFile(context, processedCode, true); - if(editor){ - syncAuxFile(editor.document.uri.toString(), uri,range); - } + const processedCode = ensureIdField(diagramCode, newDocument.documentID); + mermaidChartProvider.refresh(); + const editor = await createMermaidFile(context, processedCode, true); + + if (editor) { + syncAuxFile(editor.document.uri.toString(), uri, range); + } + } catch (error) { + vscode.window.showErrorMessage(`Error creating or connecting document: ${error instanceof Error ? error.message : "Unknown error occurred."}`); + } }) -) - vscode.workspace.onWillSaveTextDocument(async (event) => { - if (event.document.languageId.startsWith("mermaid")) { - event.waitUntil(Promise.resolve([])); - const content = event.document.getText(); - const diagramId = extractIdFromCode(content); - if (diagramId) { - await mcAPI.saveDocumentCode(content, diagramId); +); + +vscode.workspace.onWillSaveTextDocument(async (event) => { + if (event.document.languageId.startsWith("mermaid")) { + event.waitUntil(Promise.resolve([])); + const content = event.document.getText(); + const diagramId = extractIdFromCode(content); + if (diagramId) { + const projectId = getProjectIdForDocument(diagramId); + + if (projectId) { + await mcAPI.setDocument({ + documentID: diagramId, + projectID: projectId, + code: content, + }); + vscode.window.showInformationMessage(`Diagram synced successfully with Mermaid chart. Diagram ID: ${diagramId}`); } } - }); + } +}); context.subscriptions.push( vscode.commands.registerCommand('mermaidChart.syncDiagramWithMermaid', async () => { @@ -245,14 +274,20 @@ context.subscriptions.push( try { const diagramId = extractIdFromCode(content); if (TempFileCache.hasTempUri(context, document.uri.toString()) && diagramId) { - await mcAPI.saveDocumentCode(content, diagramId); + const projectId = getProjectIdForDocument(diagramId); + + if (projectId) { + await mcAPI.setDocument({ + documentID: diagramId, + projectID: projectId, + code: content, + }); + } vscode.window.showInformationMessage(`Diagram synced successfully with Mermaid chart. Diagram ID: ${diagramId}`); } else if (TempFileCache.hasTempUri(context, document.uri.toString())){ vscode.window.showInformationMessage('This is temporary buffer, this can not be saved locally'); } else if (!TempFileCache.hasTempUri(context, document.uri.toString()) && diagramId) { - await vscode.commands.executeCommand('workbench.action.files.save'); - await mcAPI.saveDocumentCode(content, diagramId); - vscode.window.showInformationMessage(`Diagram synced successfully with Mermaid chart. Diagram ID: ${diagramId}`); + await vscode.commands.executeCommand('workbench.action.files.save'); } else { await vscode.commands.executeCommand('workbench.action.files.save'); } @@ -262,27 +297,75 @@ context.subscriptions.push( } }; - function showSyncWarning(editor: vscode.TextEditor) { - const panel = vscode.window.createWebviewPanel( - "syncWarning", - "", - { viewColumn: vscode.ViewColumn.Beside, preserveFocus: true }, - { enableScripts: true, retainContextWhenHidden: true } +context.subscriptions.push( + vscode.commands.registerCommand('mermaidChart.connectDiagramToMermaidChart', async () => { + const activeEditor = vscode.window.activeTextEditor; + const document = activeEditor?.document; + + if (!document) { + vscode.window.showErrorMessage("No active document found."); + return; + } + + const content = document.getText(); + const id = extractIdFromCode(content); + + // Check if the document is already connected + if (id) { + vscode.window.showWarningMessage("This diagram is already connected to Mermaid Chart."); + return; + } + + const projects = getAllTreeViewProjectsCache(); + const selectedProject = await vscode.window.showQuickPick( + projects.map((p) => ({ label: p.title, description: p.title, projectId: p.uuid })), + { placeHolder: "Select a project to save the diagram" } + ); + + if (!selectedProject || !selectedProject.projectId) { + vscode.window.showInformationMessage("Operation cancelled."); + return; + } + + const newDocument = await mcAPI.createDocument(selectedProject.projectId); + + if (!newDocument || !newDocument.documentID) { + vscode.window.showErrorMessage("Failed to create a new document."); + return; + } + + await mcAPI.setDocument({ + documentID: newDocument.documentID, + projectID: selectedProject.projectId, + code: content, + }); + + const processedCode = ensureIdField(content, newDocument.documentID); + mermaidChartProvider.refresh(); + + // Apply the new processedCode to the document + await activeEditor.edit((editBuilder) => { + const fullRange = new vscode.Range( + activeEditor.document.positionAt(0), + activeEditor.document.positionAt(content.length) ); - - panel.webview.html = ` - -
- ⚡ This file is in sync with the remote Mermaid chart. You cannot save it locally. Changes will be saved remotely. - - `; - } - - // vscode.window.onDidChangeActiveTextEditor((editor) => { - // if (editor) { - // showSyncWarning(editor); - // } - // }); + editBuilder.replace(fullRange, processedCode); + }); + + PreviewPanel.createOrShow(document); + vscode.window.showInformationMessage(`Diagram connected successfully with Mermaid chart.`); + + }) +); + + vscode.commands.registerCommand("mermaidChart.downloadDiagram", async (item: Document) => { + if (!item || !item.code) { + vscode.window.showErrorMessage("No code found for this diagram."); + return; + } + const processedCode = ensureIdField(item.code, item.uuid); + createMermaidFile(context, processedCode, false) + }); context.subscriptions.push( vscode.commands.registerCommand("mermaidChart.focus", () => { @@ -332,6 +415,54 @@ context.subscriptions.push( ); mermaidChartProvider.refresh(); + + const provider = vscode.languages.registerCompletionItemProvider( + [ + { scheme: 'file' }, + { scheme: 'untitled' } + ], + { + provideCompletionItems(document, position, token, context) { + const languageId = document.languageId.toLowerCase(); + + // Ensure the languageId is exactly "mermaid" or starts with "mermaid" + if (!(languageId === 'mermaid' || languageId.startsWith('mermaid'))) { + return []; + } + + const snippets = getSnippetsBasedOnDiagram(languageId); + + const suggestions: vscode.CompletionItem[] = snippets.map(snippet => { + const item = new vscode.CompletionItem( + snippet.id, + vscode.CompletionItemKind.Snippet + ); + item.insertText = new vscode.SnippetString(snippet.completion); + item.documentation = new vscode.MarkdownString( + `**${snippet.name}**\n\n\`\`\`mermaid\n${snippet.sample}\n\`\`\`` + ); + return item; + }); + + return suggestions; + }, + }, + 'm' + ); + context.subscriptions.push(provider); + + const triggerCompletions = vscode.commands.registerCommand( + 'mermaidChart.showCompletions', + () => { + const editor = vscode.window.activeTextEditor; + if (editor) { + vscode.commands.executeCommand('editor.action.triggerSuggest'); + } + } + ); + + context.subscriptions.push(provider, triggerCompletions); + console.log("Mermaid Charts view registered"); } diff --git a/src/mermaidAPI.ts b/src/mermaidAPI.ts deleted file mode 100644 index b85fd81..0000000 --- a/src/mermaidAPI.ts +++ /dev/null @@ -1,317 +0,0 @@ -/* eslint-disable @typescript-eslint/naming-convention */ -import { v4 as uuid } from "uuid"; -import defaultAxios, { AxiosInstance } from "axios"; -import { createHash } from "crypto"; - -const defaultBaseURL = "https://www.mermaidchart.com"; // "http://127.0.0.1:5174" -const authorizationURLTimeout = 60_000; - -export interface InitParams { - clientID: string; - redirectURI?: string; - baseURL?: string; -} - -export interface OAuthAuthorizationParams { - response_type: "code"; - client_id: string; - redirect_uri: string; - code_challenge_method: "S256"; - code_challenge: string; - state: string; - scope: string; -} - -interface AuthState { - codeVerifier: string; -} - -export interface MCUser { - fullName: string; - emailAddress: string; -} - -export interface MCProject { - id: string; - title: string; -} - -export interface MCDocument { - documentID: string; - projectID: string; - code: string; - major: string; - minor: string; - title: string; -} - -export interface AuthorizationData { - url: string; - state: string; - scope: string; -} -export class MermaidChart { - private clientID: string; - private baseURL!: string; - private axios!: AxiosInstance; - private pendingStates: Record