VScode 插件 对项目的文件、目录描述
第一次写vscode插件,遇到不少坑,插件还在更新中。。。
想法其实在刚刚接触web代码编程的时候,就有想过要找这种功能的插件,一直没有找到,就拖到了现在。
代码编程的灵感来自于idea文件描述插件【TreeInfotip】。期待高效率开发。
在公司写了项目才发现,项目结构是真的长,过了一个礼拜天回来,忘记项目中文件的作用了,还得找半天。摸鱼的时间,写个目录描述插件。
1. 插件效果
- 右键文件或者目录即可添加描述
- 输入框输入描述
- 点击插件生成的
DirectoryV2.json
文件 即可查看,文件在项目根目录 - 这样以后想要修改代码的时候,可以很快的找到需要修改的文件,不用在想半天这个文件时干嘛的,省了不少蛋白质。
- 支持右键菜单
目前打开文件还有一些bug,遇到图片就无法打开了,还在优化中。
2. 本地安装【插件暂未发布微软,还在完善中】
- Git 下载项目:
不需要修改代码的朋友,下载之后可以直接将 vsix文件安装到软件中
3. 部分代码:
项目配置文件,主要需要注意 activationEvents
、contributes
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;
}
}
有感兴趣朋友的欢迎优化此插件,私信讨论一起摸鱼。