From ae0de794ca7efda61f99683d60fae6f0d067941c Mon Sep 17 00:00:00 2001 From: "Wu, Zhenyu" Date: Sun, 5 Jan 2025 17:07:19 +0800 Subject: [PATCH] Support (Neo)Vim by coc.nvim Refer: https://github.com/valentjn/vscode-ltex/blob/3d0cb8cd9b4d0dc8ef6c08a4f376767820678cf6/src/extension.ts#L8-L14 Difference is vscode-ltex/coc-ltex uses a customized python script https://github.com/valentjn/vscode-ltex/blob/3d0cb8cd9b4d0dc8ef6c08a4f376767820678cf6/tools/patchForTarget.py to patch code we use c language macro preprocessor cpp --- coc-graphviz/.gitignore | 4 + coc-graphviz/.npmignore | 5 + coc-graphviz/README.md | 29 ++++++ coc-graphviz/package.json | 168 ++++++++++++++++++++++++++++++++ coc-graphviz/scripts/patch.pl | 2 + coc-graphviz/scripts/patch.sh | 8 ++ coc-graphviz/src/.gitignore | 4 + coc-graphviz/src/util.ts | 18 ++++ coc-graphviz/tsconfig.json | 8 ++ src/ContentUtils.ts | 20 +++- src/GraphvizPreviewGenerator.ts | 114 +++++++++++++++++++--- src/OpenInBrowser.ts | 7 +- src/SvgExporter.ts | 15 ++- src/extension.ts | 56 +++++++++-- 14 files changed, 430 insertions(+), 28 deletions(-) create mode 100644 coc-graphviz/.gitignore create mode 100644 coc-graphviz/.npmignore create mode 100644 coc-graphviz/README.md create mode 100644 coc-graphviz/package.json create mode 100755 coc-graphviz/scripts/patch.pl create mode 100755 coc-graphviz/scripts/patch.sh create mode 100644 coc-graphviz/src/.gitignore create mode 100644 coc-graphviz/src/util.ts create mode 100644 coc-graphviz/tsconfig.json diff --git a/coc-graphviz/.gitignore b/coc-graphviz/.gitignore new file mode 100644 index 0000000..1862f1e --- /dev/null +++ b/coc-graphviz/.gitignore @@ -0,0 +1,4 @@ +*.tgz +out +node_modules +yarn.lock diff --git a/coc-graphviz/.npmignore b/coc-graphviz/.npmignore new file mode 100644 index 0000000..1eefd39 --- /dev/null +++ b/coc-graphviz/.npmignore @@ -0,0 +1,5 @@ +/* +!/README.md +!/package.json +!/out/ +*.map diff --git a/coc-graphviz/README.md b/coc-graphviz/README.md new file mode 100644 index 0000000..cac2e86 --- /dev/null +++ b/coc-graphviz/README.md @@ -0,0 +1,29 @@ +# Graphviz Support + +Ported from [vscode-graphviz](https://github.com/joaompinto/vscode-graphviz). + +![graphvizPreview](https://github.com/user-attachments/assets/d3dac671-fa7d-46f2-9367-d28fa60c637c) + +Because some APIs of [vscode](github.com/microsoft/vscode) are missing in +[coc.nvim](https://github.com/neoclide/coc.nvim), disable some features +temporarily: + +- command `graphviz.previewToSide`: miss `vscode.ViewColumn` +- save as svg: miss `vscode.window.showSaveDiaglog()` + +## Install + +- [coc-marketplace](https://github.com/fannheyward/coc-marketplace) +- [npm](https://www.npmjs.com/package/coc-graphviz) +- vim: + +```vim +" command line +CocInstall coc-graphviz +" or add the following code to your vimrc +let g:coc_global_extensions = ['coc-graphviz', 'other coc-plugins'] +``` + +## Usage + +Refer [vscode-graphviz](https://github.com/joaompinto/vscode-graphviz). diff --git a/coc-graphviz/package.json b/coc-graphviz/package.json new file mode 100644 index 0000000..582f92f --- /dev/null +++ b/coc-graphviz/package.json @@ -0,0 +1,168 @@ +{ + "name": "coc-graphviz", + "displayName": "Graphviz (dot) language support for Visual Studio Code", + "description": "This extension provides GraphViz (dot) language support for Visual Studio Code", + "version": "0.0.9", + "publisher": "joaompinto", + "author": "João Pinto ", + "keywords": [ + "coc.nvim" + ], + "contributors": [ + "Jan Dolejsi" + ], + "license": "MIT", + "readme": "README.md", + "repository": { + "type": "git", + "url": "https://github.com/joaompinto/vscode-graphviz" + }, + "galleryBanner": { + "color": "#69c", + "theme": "light" + }, + "bugs": "https://github.com/joaompinto/vscode-graphviz/issues", + "homepage": "https://github.com/joaompinto/vscode-graphviz/blob/master/README.md", + "icon": "images/icon.png", + "main": "./out/src/extension.js", + "engines": { + "coc": "^0.0.82" + }, + "categories": [ + "Programming Languages", + "Snippets" + ], + "activationEvents": [ + "onLanguage:dot", + "onCommand:graphviz.preview" + ], + "contributes": { + "languages": [ + { + "id": "dot", + "aliases": [ + "Graphviz (DOT)" + ], + "extensions": [ + ".gv", + ".dot", + ".DOT" + ], + "configuration": "dot/dot.configuration.json" + } + ], + "grammars": [ + { + "language": "dot", + "scopeName": "source.dot", + "path": "dot/syntaxes/dot.tmLanguage" + } + ], + "snippets": [ + { + "language": "dot", + "path": "dot/snippets/snippets/dot.json" + } + ], + "configuration": { + "type": "object", + "title": "Graphviz Configuration", + "properties": { + "Graphviz.runInterval": { + "type": "number", + "default": 1000, + "description": "Control how often we will be checking for file changes" + } + } + }, + "keybindings": [ + { + "command": "graphviz.preview", + "key": "ctrl+shift+v", + "mac": "shift+cmd+v", + "when": "editorTextFocus && editorLangId == dot" + }, + { + "command": "graphviz.previewToSide", + "key": "ctrl+k v", + "mac": "cmd+k shift+v", + "when": "editorTextFocus && editorLangId == dot" + } + ], + "commands": [ + { + "command": "graphviz.preview", + "title": "Toggle Preview", + "category": "Graphviz" + }, + { + "command": "graphviz.previewToSide", + "title": "Open Preview to the Side", + "category": "Graphviz", + "icon": { + "light": "images/light/open-preview.svg", + "dark": "images/dark/open-preview.svg" + } + } + ], + "menus": { + "editor/title": [ + { + "command": "graphviz.previewToSide", + "when": "resourceLangId == dot", + "group": "navigation" + } + ], + "editor/context": [ + { + "command": "graphviz.previewToSide", + "when": "resourceLangId == dot" + } + ], + "editor/title/context": [ + { + "command": "graphviz.previewToSide", + "when": "resourceLangId == dot" + } + ], + "explorer/context": [ + { + "command": "graphviz.preview", + "when": "resourceLangId == dot" + } + ], + "commandPalette": [ + { + "command": "graphviz.preview", + "when": "resourceLangId == dot" + }, + { + "command": "graphviz.previewToSide", + "when": "resourceLangId == dot" + } + ] + } + }, + "scripts": { + "patch": "scripts/patch.sh ../src/*.ts", + "watch": "tsc -watch -p ./", + "prepack": "npm run patch && tsc -p ./" + }, + "devDependencies": { + "@types/mocha": "^8.2.1", + "@types/node": "^18.0.0", + "@types/tmp": "^0.2.0", + "coc-helper": "^0.16.3", + "coc-webview": "^0.12.0", + "coc.nvim": "^0.0.83-next.18", + "mocha": "^10.2.0", + "socket.io": "^4.7.4", + "typescript": "^4.2.3" + }, + "dependencies": { + "@hpcc-js/wasm": "^1.3.0", + "dot-language-server": "1.2.0", + "open": "^8.0.2", + "tmp": "^0.2.1" + } +} diff --git a/coc-graphviz/scripts/patch.pl b/coc-graphviz/scripts/patch.pl new file mode 100755 index 0000000..5363953 --- /dev/null +++ b/coc-graphviz/scripts/patch.pl @@ -0,0 +1,2 @@ +#!/usr/bin/env -S perl -p +s=//#==; diff --git a/coc-graphviz/scripts/patch.sh b/coc-graphviz/scripts/patch.sh new file mode 100755 index 0000000..3df0c90 --- /dev/null +++ b/coc-graphviz/scripts/patch.sh @@ -0,0 +1,8 @@ +#!/usr/bin/env bash +set -e +cd "$(dirname "$(dirname "$(readlink -f "$0")")")" + +for file; do + # https://stackoverflow.com/questions/30167851/how-to-suppress-a-missing-termination-character-warning-in-cpp/30174912#30174912 + scripts/patch.pl "$file" | cpp -DHAVE_COC_NVIM -xassembler-with-cpp -nostdinc -P -C -o"${file#*/}" +done diff --git a/coc-graphviz/src/.gitignore b/coc-graphviz/src/.gitignore new file mode 100644 index 0000000..5092c96 --- /dev/null +++ b/coc-graphviz/src/.gitignore @@ -0,0 +1,4 @@ +# all ts files are generated by `npm run patch` +* +!.gitignore +!util.ts diff --git a/coc-graphviz/src/util.ts b/coc-graphviz/src/util.ts new file mode 100644 index 0000000..3d9eb2a --- /dev/null +++ b/coc-graphviz/src/util.ts @@ -0,0 +1,18 @@ +import { WebviewAPI } from 'coc-webview' +import { Extension, extensions, window } from 'coc.nvim' + +let webviewExt: Extension | undefined + +export const getWebviewAPI = () => { + if (!webviewExt) { + webviewExt = extensions.all.find((ext) => ext.id === 'coc-webview') as + | Extension + | undefined + } + if (!webviewExt) { + const prompt = 'Please install the coc-webview extension' + void window.showErrorMessage(prompt) + throw new Error(prompt) + } + return webviewExt.exports +} diff --git a/coc-graphviz/tsconfig.json b/coc-graphviz/tsconfig.json new file mode 100644 index 0000000..5270f9c --- /dev/null +++ b/coc-graphviz/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../tsconfig", + "compilerOptions": { + "allowSyntheticDefaultImports": true, + "outDir": "out", + "rootDir": "." + } +} diff --git a/src/ContentUtils.ts b/src/ContentUtils.ts index 43ae0fa..22f7db9 100644 --- a/src/ContentUtils.ts +++ b/src/ContentUtils.ts @@ -1,10 +1,24 @@ -import { ExtensionContext, Uri, Webview } from "vscode"; +import { + ExtensionContext, + Uri, +//# #if HAVE_VSCODE + Webview +} from "vscode"; +//# #elif HAVE_COC_NVIM +//# } from "coc.nvim"; +//# import { Webview } from "coc-webview"; +//# import { resolve } from "path"; +//# #endif var fs = require("fs"); export const CONTENT_FOLDER = "content"; export async function getPreviewTemplate(context: ExtensionContext, templateName: string): Promise { + //# #if HAVE_VSCODE let previewPath = Uri.joinPath(context.extensionUri, CONTENT_FOLDER, templateName).fsPath; + //# #elif HAVE_COC_NVIM + //# let previewPath = /* Uri.joinPath */resolve(context.extensionPath/* extensionUri */, CONTENT_FOLDER, templateName)/* .fsPath */; + //# #endif return (await fs.promises.readFile(previewPath, { encoding: "utf-8", flag: 'r' })).toString(); } @@ -39,7 +53,11 @@ function isAbsoluteWebview(attribValue: string): boolean { } function getWebviewUri(extensionContext: ExtensionContext, relativePath: string, fileName: string, webview?: Webview): Uri { + //# #if HAVE_VSCODE return asWebviewUri(Uri.joinPath(extensionContext.extensionUri, relativePath, fileName), webview); + //# #elif HAVE_COC_NVIM + //# return asWebviewUri(/* Uri.joinPath */Uri.parse("file://" + resolve(extensionContext.extensionPath/* extensionUri */, relativePath, fileName)), webview); + //# #endif } function asWebviewUri(localUri: Uri, webview?: Webview): Uri { diff --git a/src/GraphvizPreviewGenerator.ts b/src/GraphvizPreviewGenerator.ts index 809cf50..9f5cce4 100644 --- a/src/GraphvizPreviewGenerator.ts +++ b/src/GraphvizPreviewGenerator.ts @@ -1,18 +1,40 @@ -import { ExtensionContext, TextDocument, window, ViewColumn, Uri, WebviewPanel, workspace, Disposable, Webview } from "vscode"; +import { + ExtensionContext, + TextDocument, + window, + Uri, + workspace, + Disposable, +//# #if HAVE_VSCODE + WebviewPanel, + Webview +} from "vscode"; +//# #elif HAVE_COC_NVIM +//# #define Thenable Promise +//# } from "coc.nvim"; +//# import { WebviewPanel, Webview } from "coc-webview"; +//# import { getWebviewAPI } from "./util"; +//# #endif import { graphviz } from "@hpcc-js/wasm"; import * as path from "path"; import { SvgExporter } from "./SvgExporter"; import { OpenInBrowser } from "./OpenInBrowser"; import { getPreviewTemplate, CONTENT_FOLDER, getHtml } from "./ContentUtils"; -export class GraphvizPreviewGenerator extends Disposable { +export class GraphvizPreviewGenerator +//# #if HAVE_VSCODE +extends Disposable +//# #endif +{ webviewPanels = new Map(); - timeout: NodeJS.Timer | undefined; + timeout: NodeJS.Timeout | undefined; constructor(private context: ExtensionContext) { + //# #if HAVE_VSCODE super(() => this.dispose()); + //# #endif } setNeedsRebuild(uri: Uri, needsRebuild: boolean): void { @@ -42,22 +64,53 @@ export class GraphvizPreviewGenerator extends Disposable { rebuild(): void { this.webviewPanels.forEach(panel => { if (panel.getNeedsRebuild() && panel.getPanel().visible) { - this.updateContent(panel, workspace.textDocuments.find(doc => doc.uri == panel.uri)); + this.updateContent(panel, workspace.textDocuments.find(doc => + //# #if HAVE_VSCODE + doc.uri + //# #elif HAVE_COC_NVIM + //# Uri.parse(doc.uri) + //# #endif + == panel.uri)); } }); } - async revealOrCreatePreview(doc: TextDocument, displayColumn: ViewColumn): Promise { - let previewPanel = this.webviewPanels.get(doc.uri); + async revealOrCreatePreview(doc: TextDocument, + displayColumn: + //# #if HAVE_VSCODE + ViewColumn + //# #elif HAVE_COC_NVIM + //# { openURL: boolean } + //# #endif + ): Promise { + let previewPanel = this.webviewPanels.get( + //# #if HAVE_VSCODE + doc.uri + //# #elif HAVE_COC_NVIM + //# Uri.parse(doc.uri) + //# #endif + ); if (previewPanel) { previewPanel.reveal(displayColumn); } else { - previewPanel = this.createPreviewPanel(doc, displayColumn); - this.webviewPanels.set(doc.uri, previewPanel); + previewPanel = await this.createPreviewPanel(doc, displayColumn); + this.webviewPanels.set( + //# #if HAVE_VSCODE + doc.uri, + //# #elif HAVE_COC_NVIM + //# Uri.parse(doc.uri), + //# #endif + previewPanel); // when the user closes the tab, remove the panel - previewPanel.getPanel().onDidDispose(() => this.webviewPanels.delete(doc.uri), undefined, this.context.subscriptions); + previewPanel.getPanel().onDidDispose(() => this.webviewPanels.delete( + //# #if HAVE_VSCODE + doc.uri, + //# #elif HAVE_COC_NVIM + //# Uri.parse(doc.uri), + //# #endif + ), undefined, this.context.subscriptions); // when the pane becomes visible again, refresh it previewPanel.getPanel().onDidChangeViewState(_ => this.rebuild()); @@ -95,18 +148,41 @@ export class GraphvizPreviewGenerator extends Disposable { } } - createPreviewPanel(doc: TextDocument, displayColumn: ViewColumn): PreviewPanel { - const previewTitle = `Preview: '${path.basename(doc.fileName)}'`; - - const webViewPanel = window.createWebviewPanel('graphvizPreview', previewTitle, displayColumn, { + async createPreviewPanel(doc: TextDocument, + //# #if HAVE_VSCODE + displayColumn: ViewColumn + //# #elif HAVE_COC_NVIM + //# displayColumn_: { openURL: boolean } + //# #endif + ): Promise { + const previewTitle = `Preview: '${path.basename(doc.uri)}'`; + //# #if HAVE_COC_NVIM + //# const displayColumn = { openURL: displayColumn_.openURL, routeName: 'graphvizPreview' }; + //# #endif + const webViewPanel = + //# #if HAVE_VSCODE + window + //# #elif HAVE_COC_NVIM + //# await getWebviewAPI() + //# #endif + .createWebviewPanel('graphvizPreview', previewTitle, displayColumn, { + //# #if HAVE_VSCODE enableFindWidget: true, + //# #endif enableScripts: true, localResourceRoots: [Uri.file(path.join(this.context.extensionPath, "content"))] }); webViewPanel.iconPath = Uri.file(this.context.asAbsolutePath("content/icon.svg")); - return new PreviewPanel(doc.uri, webViewPanel); + return new PreviewPanel( + //# #if HAVE_VSCODE + doc.uri, + //# #elif HAVE_COC_NVIM + //# Uri.parse(doc.uri), + //# #endif + webViewPanel + ); } async updateContent(previewPanel: PreviewPanel, doc: TextDocument) { @@ -192,7 +268,13 @@ class PreviewPanel { return this.fitToHeight; } - reveal(displayColumn: ViewColumn): void { + reveal(displayColumn: + //# #if HAVE_VSCODE + ViewColumn, + //# #elif HAVE_COC_NVIM + //# { openURL: boolean } + //# #endif + ): void { this.panel.reveal(displayColumn); } @@ -207,4 +289,4 @@ class PreviewPanel { getPanel(): WebviewPanel { return this.panel; } -} \ No newline at end of file +} diff --git a/src/OpenInBrowser.ts b/src/OpenInBrowser.ts index 5410359..ff2ede0 100644 --- a/src/OpenInBrowser.ts +++ b/src/OpenInBrowser.ts @@ -1,4 +1,9 @@ -import { Uri, ExtensionContext } from "vscode"; +import { Uri, ExtensionContext } from +//# #if HAVE_VSCODE +"vscode"; +//# #elif HAVE_COC_NVIM +//# "coc.nvim"; +//# #endif import { SvgExporter } from "./SvgExporter"; import { getPreviewTemplate } from "./ContentUtils"; import * as tmp from 'tmp'; diff --git a/src/SvgExporter.ts b/src/SvgExporter.ts index ba98690..ba32aae 100644 --- a/src/SvgExporter.ts +++ b/src/SvgExporter.ts @@ -1,5 +1,10 @@ import fs = require('fs'); -import { Uri, window, workspace } from 'vscode'; +import { Uri, window, workspace } from +//# #if HAVE_VSCODE +'vscode'; +//# #elif HAVE_COC_NVIM +//# 'coc.nvim'; +//# #endif import { graphviz } from "@hpcc-js/wasm"; export class SvgExporter { @@ -10,6 +15,7 @@ export class SvgExporter { async export(documentUri: Uri): Promise { let svgDefaultUri = documentUri.with({path: documentUri.path + '.svg'}); + //# #if HAVE_VSCODE let uri = await window.showSaveDialog({defaultUri: svgDefaultUri, saveLabel: "Save as SVG...", filters: { "SVG": ["svg"] @@ -25,12 +31,17 @@ export class SvgExporter { console.log(err); } }); + //# #endif } protected async renderSvgString(documentUri: Uri): Promise { let doc = await workspace.openTextDocument(documentUri); + //# #if HAVE_VSCODE let graphVizText = doc.getText(); + //# #elif HAVE_COC_NVIM + //# let graphVizText = doc.textDocument.getText(); + //# #endif let svg = await graphviz.dot(graphVizText); return svg; } -} \ No newline at end of file +} diff --git a/src/extension.ts b/src/extension.ts index b8bf7e2..69f8ffe 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -20,11 +20,16 @@ import { window, commands, ExtensionContext, - ViewColumn, - TextDocumentChangeEvent, TextDocument, - Uri + Uri, +//# #if HAVE_VSCODE + ViewColumn, + TextDocumentChangeEvent } from 'vscode'; +//# #elif HAVE_COC_NVIM +//# DidChangeTextDocumentParams, +//# } from 'coc.nvim'; +//# #endif import { GraphvizPreviewGenerator } from './GraphvizPreviewGenerator'; @@ -36,41 +41,76 @@ export function activate(context: ExtensionContext) { // When the active document is changed set the provider for rebuild // this only occurs after an edit in a document - context.subscriptions.push(workspace.onDidChangeTextDocument((e: TextDocumentChangeEvent) => { + context.subscriptions.push(workspace.onDidChangeTextDocument((e: + //# #if HAVE_VSCODE + TextDocumentChangeEvent + //# #elif HAVE_COC_NVIM + //# DidChangeTextDocumentParams + //# #endif + ) => { + //# #if HAVE_VSCODE if (e.document.languageId === DOT) { graphvizPreviewGenerator.setNeedsRebuild(e.document.uri, true); } + //# #elif HAVE_COC_NVIM + //# graphvizPreviewGenerator.setNeedsRebuild(Uri.parse(e.textDocument.uri), true); + //# #endif })); context.subscriptions.push(workspace.onDidSaveTextDocument((doc: TextDocument) => { if (doc.languageId === DOT) { - graphvizPreviewGenerator.setNeedsRebuild(doc.uri, true); + graphvizPreviewGenerator.setNeedsRebuild( + //# #if HAVE_VSCODE + doc.uri, + //# #elif HAVE_COC_NVIM + //# Uri.parse(doc.uri), + //# #endif + true + ); } })) + //# #if HAVE_VSCODE let previewToSide = commands.registerCommand("graphviz.previewToSide", async (dotDocumentUri: Uri) => { let dotDocument = await getDotDocument(dotDocumentUri); if (dotDocument) { return graphvizPreviewGenerator.revealOrCreatePreview(dotDocument, ViewColumn.Beside); } }) + context.subscriptions.push(previewToSide); + //# #endif let preview = commands.registerCommand("graphviz.preview", async (dotDocumentUri: Uri) => { let dotDocument = await getDotDocument(dotDocumentUri); if (dotDocument) { - return graphvizPreviewGenerator.revealOrCreatePreview(dotDocument, window.activeTextEditor?.viewColumn ?? ViewColumn.Active); + return graphvizPreviewGenerator.revealOrCreatePreview( + dotDocument, + //# #if HAVE_VSCODE + window.activeTextEditor?.viewColumn ?? ViewColumn.Active + //# #elif HAVE_COC_NVIM + //# {openURL: true} + //# #endif + ); } }) - context.subscriptions.push(previewToSide, preview, graphvizPreviewGenerator); + context.subscriptions.push(preview, graphvizPreviewGenerator); } async function getDotDocument(dotDocumentUri: Uri | undefined): Promise { if (dotDocumentUri) { + //# #if HAVE_VSCODE return await workspace.openTextDocument(dotDocumentUri); + //# #elif HAVE_COC_NVIM + //# return (await workspace.openTextDocument(dotDocumentUri)).textDocument; + //# #endif } else { if (window.activeTextEditor?.document.languageId === DOT) { + //# #if HAVE_VSCODE return window.activeTextEditor.document; + //# #elif HAVE_COC_NVIM + //# return window.activeTextEditor.document.textDocument; + //# #endif } else { return undefined; @@ -79,4 +119,4 @@ async function getDotDocument(dotDocumentUri: Uri | undefined): Promise