摸鱼时间写一个 VScode 插件 【给项目目录文件添加文本描述】

11 篇文章 0 订阅
1 篇文章 0 订阅

VScode 插件 对项目的文件、目录描述

第一次写vscode插件,遇到不少坑,插件还在更新中。。。

想法其实在刚刚接触web代码编程的时候,就有想过要找这种功能的插件,一直没有找到,就拖到了现在。

代码编程的灵感来自于idea文件描述插件【TreeInfotip】。期待高效率开发。

在公司写了项目才发现,项目结构是真的长,过了一个礼拜天回来,忘记项目中文件的作用了,还得找半天。摸鱼的时间,写个目录描述插件。

1. 插件效果

  • 右键文件或者目录即可添加描述
    在这里插入图片描述
  • 输入框输入描述
    在这里插入图片描述
  • 点击插件生成的 DirectoryV2.json 文件 即可查看,文件在项目根目录
  • 这样以后想要修改代码的时候,可以很快的找到需要修改的文件,不用在想半天这个文件时干嘛的,省了不少蛋白质。
    在这里插入图片描述
  • 支持右键菜单
    目前打开文件还有一些bug,遇到图片就无法打开了,还在优化中。

在这里插入图片描述

2. 本地安装【插件暂未发布微软,还在完善中】

  • Git 下载项目:
    不需要修改代码的朋友,下载之后可以直接将 vsix文件安装到软件中
    在这里插入图片描述

3. 部分代码:

项目配置文件,主要需要注意 activationEventscontributes

package.json

{
    "name": "file-desc",
    "displayName": "file-desc",
    "publisher": "file-descr",
    "description": "file-description",
    "version": "1.0.7",
	"private": true,
	"license": "MIT",
    "engines": {
        "vscode": "^1.70.0"
    },
    "categories": [
        "Other"
    ],
    "activationEvents": [
        "onCommand:vsplugin.fileDescJson",
        "onCustomEditor:catCustoms.catScratch"
    ],
    "main": "./dist/extension.js",
    "contributes": {
        "commands": [
            {
                "command": "vsplugin.fileDescJson",
                "title": "编辑文件或目录描述"
            }
        ],
        "menus": {
            "explorer/context": [
                {
                    "command": "vsplugin.fileDescJson",
                    "group": "MyGroup@1"
                }
            ]
        },
        "customEditors": [
            {
                "viewType": "catCustoms.catScratch",
                "displayName": "项目描述预览",
                "selector": [
                    {
                        "filenamePattern": "DirectoryV2.json"
                    }
                ]
            }
        ]
    },
    "scripts": {
        "vscode:prepublish": "npm run package",
        "compile": "webpack",
        "watch": "webpack --watch",
        "package": "webpack --mode production --devtool hidden-source-map",
        "compile-tests": "tsc -p . --outDir out",
        "watch-tests": "tsc -p . -w --outDir out",
        "pretest": "npm run compile-tests && npm run compile && npm run lint",
        "lint": "eslint src --ext ts"
    },
    "icon": "static/image/wxgzh_logo.png",
    "keywords": [
        "file",
        "fileDescJson",
        "description",
        "desc",
        "descr"
    ],
    "repository": {
        "type": "git",
        "url": "https://gitee.com/beautiful_corridors/file-desc/tree/main/"
    },
    "devDependencies": {
        "@types/vscode": "^1.70.0",
        "@types/glob": "^7.2.0",
        "@types/mocha": "^9.1.1",
        "@types/node": "16.x",
        "@typescript-eslint/eslint-plugin": "^5.31.0",
        "@typescript-eslint/parser": "^5.31.0",
        "eslint": "^8.20.0",
        "glob": "^8.0.3",
        "mocha": "^10.0.0",
        "typescript": "^4.7.4",
        "ts-loader": "^9.3.1",
        "webpack": "^5.74.0",
        "webpack-cli": "^4.10.0",
        "@vscode/test-electron": "^2.1.5",
        "vscode-test": "^1.4.0"
    },
    "dependencies": {
        "fast-xml-parser": "^3.3.4",
        "fs-extra": "^10.1.0",
        "he": "^1.2.0",
        "mime": "^3.0.0",
        "xml-js": "^1.6.11",
        "xml2js": "^0.4.23"
    }
}

原本此代码是按照idea的一个插件去实现的,原本想着可以适配idea的插件,可以实现idea,VScode共同一个描述文件,后来因为xml实在是太麻烦了,暂时放弃了,有时间在继续优化吧。

这是一个入口文件,一般注册所有的功能组件都写在这里

extension.ts

// The module 'vscode' contains the VS Code extensibility API
// Import the module and reference it with the alias vscode in your code below
import * as vscode from 'vscode';
import { Utility } from './main/utility';
// import { fileDesc } from './main/fileDesc';
import { fileDescJson } from './main/fileDescJson';
// 实例化描述类
// let filedesc = new fileDesc();
// filedesc.drawDesc();
// 插件激活时触发
// 执行一次
export function activate(context: vscode.ExtensionContext) {
	console.log('插件激活!');
	// 展示视图
	let filedescjson = new fileDescJson(context);
	context.subscriptions.push(fileDescJson.register(context));
	// 是否展示
	const isShowDesc = Utility.getConfiguration().get<number>('isShowDesc', 1);
	if (isShowDesc === 0) { return; }
	// 右键按钮
	const createIndexCommand = vscode.commands.registerCommand(
		'vsplugin.fileDescJson',
		async (uri: vscode.Uri) => {
			vscode.window.showInformationMessage("请在输入框中编辑描述");
			let dirPath = uri.fsPath;
			// 输入描述
			const input = await vscode.window.showInputBox({
				placeHolder: '编辑描述【' + dirPath + '】'
			});
			if (input) {
				filedescjson.createDesc(dirPath, input);
			} else {
				vscode.window.showErrorMessage('取消编辑');
			}
		}
	);
	context.subscriptions.push(createIndexCommand);
	// 读取配置文件【待 更新】
	// Utility.getConfiguration().update("isShowDesc", 0);
}


// 当插件失活时触发
export function deactivate() { }

fileDescJson.ts


import * as fs from 'fs-extra';
import * as vscode from 'vscode';
import * as path from 'path';
import { writeFile } from 'fs';
// import * as mime from 'mime'

// eslint-disable-next-line @typescript-eslint/naming-convention
export class fileDescJson implements vscode.CustomTextEditorProvider {

    // 插件所在路径
    private resourcesDir = path.posix.join(__dirname);
    // 项目路径
    private cwd = vscode.workspace.rootPath;
    // 项目路径下的配置文件
    private jsonName = "/DirectoryV2.json";
    // 保存所有的描述
    private nowJson: any;
    private static readonly viewType = 'catCustoms.catScratch';

    // 注册页面打开自定义重绘
    public static register(context: vscode.ExtensionContext): vscode.Disposable {
        const provider = new fileDescJson(context);
        const providerRegistration = vscode.window.registerCustomEditorProvider(fileDescJson.viewType, provider);
        return providerRegistration;
    }

    // 构造
    constructor(
        private readonly context: vscode.ExtensionContext
    ) {
        this.cwd = this.cwd.replace(/\\/g, '/');
    }


    // 定义更新页面数据函数
    updateWebview() {
    };
    // 实现重绘编辑器方法
    public async resolveCustomTextEditor(document: vscode.TextDocument, webviewPanel: vscode.WebviewPanel, token: vscode.CancellationToken): Promise<void> {
        webviewPanel.webview.options = {
            enableScripts: true,
        };

        // 接受页面事件
        webviewPanel.webview.onDidReceiveMessage(e => {
            switch (e.type) {
                case 'reDesc': // 编辑描述
                    this.reNameJson(e.path);
                    return;
                case 'openFile': // 打开文件
                    this.openFile(e.path);
                    return;
                case 'delJson': // 删除文件
                    this.delJson(e.path);
                    return;
            }
        });
        // 发送事件到页面
        this.updateWebview = async () => {
            console.log("更新");
            await this.readDesc();
            webviewPanel.webview.html = this.getHtmlForWebview(webviewPanel.webview);
        };
        const changeDocumentSubscription = vscode.workspace.onDidChangeTextDocument(e => {
            if (e.document.uri.toString() === document.uri.toString()) {
                this.updateWebview();
            }
        });
        this.updateWebview();
    }


    async delJson(path: any) {
        try {
            await this.readDesc();
        } catch (error) {
            this.nowJson = {};
        }
        if (this.nowJson && this.nowJson.trees) {
            let i: number;
            for (i = 0; i < this.nowJson.trees.length; i++) {
                if (this.nowJson.trees[i]["path"] === path) {
                    break;
                }
            }
            if (i < this.nowJson.trees.length) {
                this.nowJson.trees.splice(i, 1);
                await this.writeFileJson();
                this.updateWebview();
            }
        }
    }
    openFile(path: any) {
        throw new Error('Method not implemented.');
    }
    async reNameJson(path: any) {
        const input = await vscode.window.showInputBox({
            placeHolder: '编辑描述【' + path + '】'
        });
        if (input) {
            this.createDesc(this.cwd + path, input);
        } else {
            vscode.window.showErrorMessage('取消编辑');
        }
    }

    // 添加一项写入文件 dirPath 文件路径,内容input
    async createDesc(dirPath, input) {
        try {
            await this.setPathAndName(dirPath, input);
        } catch (error) {
            console.log("配置文件写入失败");
        }
        this.writeFileJson();
    }

    // 数据写入文件
    async writeFileJson() {
        try {
            await fs.writeFileSync(this.cwd + this.jsonName, JSON.stringify(this.nowJson, null, 4), {
                encoding: "utf8",
                flag: "w+"
            });
        } catch (error) {
            console.log("文件写入有点错了");
        }
    }

    // 读取xml并解析
    async readDesc() {
        let jsonStr: any;
        try {
            jsonStr = await fs.readFileSync(this.cwd + this.jsonName, "utf8");
        } catch (error) {
            this.nowJson = {};
            return;
        }
        if (jsonStr) {
            try {
                this.nowJson = JSON.parse(jsonStr);
                vscode.window.showInformationMessage("文件目录备注加载完成!");
            } catch (error) {
                console.log(error.message);
                this.nowJson = {};
            }
        }
    }


    // 编辑路径描述xml
    async setPathAndName(dirPath, title) {
        var extension = "";
        try {
            if (!fs.lstatSync(dirPath).isDirectory()) {
                extension = path.extname(dirPath).slice(1);
                if (extension === "") { extension = "*"; }
            }
        } catch (error) {
            console.log(error);
        }
        dirPath = dirPath.replace(/\\/g, '/');
        dirPath = dirPath.replace(this.cwd, '');
        // eslint-disable-next-line @typescript-eslint/naming-convention
        let tree = { "path": dirPath, "title": title, "extension": extension };
        try {
            await this.readDesc(); // 实时同步描述文件
        } catch (error) {
            this.nowJson = {};
        }
        if (this.nowJson && this.nowJson.trees) {
            let i: number;
            for (i = 0; i < this.nowJson.trees.length; i++) {
                if (this.nowJson.trees[i]["path"] === tree["path"]) {
                    break;
                }
            }
            if (i >= this.nowJson.trees.length) {
                this.nowJson.trees.push(tree);
            } else {
                this.nowJson.trees[i] = tree;
            }
        } else {
            this.nowJson = { trees: [tree] };
        }
        console.log(this.nowJson);
    }

    // json解析为html目录结构
    private getHtmlForWebview(webview: vscode.Webview): string {
        // Local path to script and css for the webview
        const jqueryUri = webview.asWebviewUri(vscode.Uri.joinPath(
            this.context.extensionUri, 'static', 'jquery.min.js'));

        const muneUri = webview.asWebviewUri(vscode.Uri.joinPath(
            this.context.extensionUri, 'static', 'mune.js'));

        const toolsUri = webview.asWebviewUri(vscode.Uri.joinPath(
            this.context.extensionUri, 'static', 'tools.js'));

        const handledataUri = webview.asWebviewUri(vscode.Uri.joinPath(
            this.context.extensionUri, 'static', 'handledata.js'));

        const indexUri = webview.asWebviewUri(vscode.Uri.joinPath(
            this.context.extensionUri, 'static', 'index.js'));

        const styleResetUri = webview.asWebviewUri(vscode.Uri.joinPath(
            this.context.extensionUri, 'static', 'viewjson.css'));
        let viewjson = { files: [] };
        let mapJson = {};
        if (this.nowJson) {
            for (let i = 0; i < this.nowJson.trees.length; i++) {
                let id = this.nowJson.trees[i].path;
                let a = this.nowJson.trees[i].path.split("/");
                let pid: any;
                pid = a.slice(0, -1).join("/");
                for (let j = 1; j < a.length; j++) {
                    let t = a.slice(0, -j).join("/");
                    if (t) { mapJson[t] = 2; }; // 标记所有目录
                }
                if (!pid) {
                    pid = -1;
                }
                let title = this.nowJson.trees[i].title;
                let extension = this.nowJson.trees[i].extension;
                viewjson.files.push({ id: id, pid: pid, title: title, extension: extension });
            }
            for (let i = 0; i < this.nowJson.trees.length; i++) {
                if (this.nowJson.trees[i].extension === "") { mapJson[this.nowJson.trees[i].path] = 1; }
            }
            for (let t in mapJson) { // 添加没有根目录的路径
                if (mapJson[t] === 2) {
                    let a = t.split("/");
                    let pid: any;
                    pid = a.slice(0, -1).join("/");
                    if (!pid) {
                        pid = -1;
                    }
                    if (t) { viewjson.files.push({ id: t, pid: pid, title: "", extension: "" }); }
                }
            }
            this.sortJson(viewjson.files);
            console.log(mapJson);
        }
        console.log(viewjson);

        const nonce = this.getNonce();

        return /* html */`
			<!DOCTYPE html>
			<html lang="en">
			<head>
				<meta charset="UTF-8">
				<meta name="viewport" content="width=device-width, initial-scale=1.0">

				<link href="${styleResetUri}" rel="stylesheet" />

				<title>项目描述</title>
			</head>
			<body>
				<div class="content">
                    <ul class="contextmenu">
                        <!--<li><a href="javascript:return false;" class="openFile">打开文件</a></li>-->
                        <li><a href="javascript:return false;" class="reDesc">编辑描述</a></li>
                        <li><a href="javascript:return false;" class="delJson">删除</a></li>
                    </ul>
                    <div id="treeView"></div>
				</div>
				<script>var data = ${JSON.stringify(viewjson, null, 4)};</script>
                <script nonce="${nonce}" src='${jqueryUri}'></script>
                <script nonce="${nonce}" src='${muneUri}'></script>
                <script nonce="${nonce}" src='${toolsUri}'></script>
                <script nonce="${nonce}" src='${handledataUri}'></script>
                <script nonce="${nonce}" src='${indexUri}'></script>
			</body>
			</html>`;
    }
    sortJson(files: any[]) {
        files.sort(({ extension: a, id: ap }, { extension: b, id: bp }) => {
            let aa = a ? 1 : 0;
            let bb = b ? 1 : 0;
            return aa - bb ? aa - bb : ap - bp;
        });
    }

    getNonce() {
        let text = '';
        const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
        for (let i = 0; i < 32; i++) {
            text += possible.charAt(Math.floor(Math.random() * possible.length));
        }
        return text;
    }



} 

有感兴趣朋友的欢迎优化此插件,私信讨论一起摸鱼。

源码已经放在gitee上: https://gitee.com/beautiful_corridors/file-desc/tree/main/

评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

流星蝴蝶没有剑

篮球弹弹弹

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值