欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

VScode插件视图显示本地文件目录树

程序员文章站 2022-07-12 20:47:33
...

前言
    最近工作中需要用到vscode开发插件,作为一个没用使过vscode开发插件的小白,发现官网的教程还是很详细的。另外还发现了一篇适合小白的博文(VScode插件开发全攻略(小铭同学)),大家也可以看看,写得很好。
    写这篇博文的目的是为了整理一下自己学的东西,毕竟记录和回想还是不一样的。

1.开发环境搭建

    这里给出需要用到的工具的下载地址,下载后直接安装就可以了,注意需要给node.js和Git设置环境变量。

  1. Node.js
    可以使用命令:npm root -g 查看全局包的存放地址。也可以修改全局包的存放地址,这里就不多说了。
  2. Git
  3. VS code
  4. 命令安装 Yeoman 和 VS Code Extension Generator:
    npm install -g yo generator-code
    安装的文件位置,在上面说到的全局包里面。
    Yeoman:一个脚手架工具,通过yeoman可以快速创建代码模板。需要配置环境变量,配好后可以查看版本信息:yo --version
    VS Code Extension Generator:VS Code的模板。

2.创建一个TypeScript项目

yo code
? What type of extension do you want to create? New Extension (TypeScript)
? What's the name of your extension? DirTreeDemo
? What's the identifier of your extension? dirtreedemo
? What's the description of your extension? DirTreeDemo
? Initialize a git repository? Yes
? Which package manager to use? npm
code dirtreedemo

    在编辑器中按下 F5 ,在新窗口中 Ctrl+Shift+P,输入命令 Hello World,会看到 Hello World from DirTreeDemo! 的通知。
    (强烈建议看官网教程,有操作视频视频)
VScode插件视图显示本地文件目录树
    你也可以修改命令,或者修改通知的消息:
VScode插件视图显示本地文件目录树VScode插件视图显示本地文件目录树
注意: 可能按下 F5 后会报找不到js文件之类的错误,这是因为你的vscode不能直接将.ts文件编译成.js文件,这里 可能解决你的问题。

3.自定义视图容器

    添加一个视图容器,并给其添加两个视图。视图的图标,可以去 阿里 下载。
VScode插件视图显示本地文件目录树
    按下 F5 可以看到
VScode插件视图显示本地文件目录树
    可以给视图标题的视图顶部和视图项上下文菜单添加一些嵌入式图标,并给其绑定一些命令事件,用来实现某些效果。
    在package.json的 contributes.menu 中:
        “view/title”:在视图标题中显示操作的位置。设置"group": “navigation”,则和标题同行显示,位于标题右边;不设置"group",则显示在标题同行最有边的 菜单中。
        “view/item/context”:显示树项目动作的位置。设置"group": “inline”,则和视图项同行显示,位于标题右边;不设置"group",则在 鼠标右键 时显示。
    嵌入式图标排序:默认package.json中的先后顺序,如需要更改,可以在在group的值的后边加上 @序号 ,如:“group”: “aaa@qq.com”。

4.打开本地文件夹

    给 树视图测试-1 添加一个嵌入式图标,用来向用户显示“文件打开”对话框。

//package.json
{
	...
	"activationEvents": [
		"onView:TreeViewTest",//**视图容器
		"onCommand:TreeViewTest_One.opendir"//**命令
	],
	"contributes": {
		"commands": [
			{
				"command": "TreeViewTest_One.opendir",//定义一个命令
				"title": "打开文件夹",//鼠标停放时显示的信息
				"icon": {//设置显示图标
					"light": "images/open_light.svg",
					"dark": "images/open_dark.svg"
				}
			}
		],
		"viewsContainers": {
			"activitybar": [{//添加视图容器
				"id": "TreeViewTest",
				"title": "我的目录树",
				"icon": "images/person.svg"
			}]
		},
		"views": {
			"TreeViewTest": [//给视图容器添加视图
				{
					"id": "TreeViewTest_One",
					"name": "树视图测试-1"
				},
				{
					"id": "TreeViewTest_TWO",
					"name": "树视图测试-2"
				}
			]
		},
		"menus": {
			"view/title": [{
					"command": "TreeViewTest_One.opendir",
					"when": "view == TreeViewTest_One",//控制此操作只在视图 TreeViewTest_One 中生效
					"group": "navigation"//显示视图标题的右边,同行显示
				}]
		}
	},
	...
}

    注册命令 TreeViewTest_One.opendir ,为命令编写执行操作:

//extension.ts
...
export function activate(context: vscode.ExtensionContext) {
	...
	//注册命令 TreeViewTest_One.opendir
	context.subscriptions.push(vscode.commands.registerCommand('TreeViewTest_One.opendir', () => {
		let options = {
			canSelectFiles: false,		//是否可选择文件
			canSelectFolders: true,		//是否可选择目录
			canSelectMany: false,		//是否可多选
			defaultUri: vscode.Uri.file("D:/VScode"),	//默认打开的文件夹
			openLabel: '选择文件夹'
		};
		//向用户显示“文件打开”对话框,允许用户选择用于打开目的的文件。
		vscode.window.showOpenDialog(options).then(result => {
			if(result === undefined){
				vscode.window.showInformationMessage("can't open dir.");
			}
			else{
				vscode.window.showInformationMessage("open dir: " + result.toString());
			}
		});
	}));
}
...

5.处理树数据并显示到视图

    vscode提供了很多API,这里用到:
        TreeItem:class,用来处理视图项item,可以给其绑定命令,设置提示信息等。
        TreeDataProvider:interface,处理树数据。必须实现的方法:

  • getTreeItem(element: T): TreeItem | Thenable :返回给定element或根的子代(如果未传递任何元素)。
  • getChildren(element?: T): ProviderResult<T[]> :返回在视图中显示的元素的UI表示形式(TreeItem)。
//myTreeData.ts
import { TreeDataProvider, TreeItem, TreeItemCollapsibleState, ProviderResult, window } from "vscode";
import * as  fs from "fs";
import * as path from "path";

export class MyTreeData implements TreeDataProvider<MyTreeItem>{
    constructor(private rootPath: string){
    }

    getTreeItem(element: MyTreeItem) : MyTreeItem | Thenable<MyTreeItem> {
        return element;
    }

    getChildren(element?: MyTreeItem | undefined): ProviderResult<MyTreeItem[]>{
        if(!this.rootPath){
            window.showInformationMessage('No file in empty directory');
            return Promise.resolve([]);
        }
        if(element === undefined){
            return Promise.resolve(this.searchFiles(this.rootPath));
        }
        else{
            return Promise.resolve(this.searchFiles(path.join(element.parentPath, element.label)));
        }
    }
    //查找文件,文件夹
    private searchFiles(parentPath: string): MyTreeItem[] {
        var treeDir: MyTreeItem[] = [];
        if(this.pathExists(parentPath)){
            var fsReadDir = fs.readdirSync(parentPath, 'utf-8');
            fsReadDir.forEach(fileName => {
                var filePath = path.join(parentPath, fileName);//用绝对路径
                if(fs.statSync(filePath).isDirectory()){//目录
                    treeDir.push(new MyTreeItem(fileName, parentPath, TreeItemCollapsibleState.Collapsed));
                }
                else{//文件
                    treeDir.push(new MyTreeItem(fileName, parentPath, TreeItemCollapsibleState.None));
                }
            });
        }
        return treeDir;
    }   
    //判断路径是否存在
    private pathExists(filePath: string): boolean{
        try{
            fs.accessSync(filePath);
        }
        catch(err){
            return false;
        }
        return true;
    }
}

export class MyTreeItem extends TreeItem{
    constructor(
        public readonly label: string,      //存储当前标签
        public readonly parentPath: string,   //存储当前标签的路径,不包含该标签这个目录
        public readonly collapsibleState: TreeItemCollapsibleState
    ){
        super(label, collapsibleState);
    }
    //设置鼠标悬停在此项上时的工具提示文本
    get tooltip():string{
        return path.join(this.parentPath, this.label);
    }
    //为每项添加点击事件的命令
    command = {
        title: "this.label",
        command: 'MyTreeItem.itemClick',
        arguments: [    //传递两个参数
            this.label,
            path.join(this.parentPath, this.label)
        ]
    };
    contextValue = 'MyTreeItem';//提供给 when 使用
}

    修改 extension.ts 文件:

...
import { MyTreeData } from './myTreeData';
export function activate(context: vscode.ExtensionContext) {
	...
	//注册命令 TreeViewTest_One.opendir
	context.subscriptions.push(vscode.commands.registerCommand('TreeViewTest_One.opendir', () => {
		let options = {
			canSelectFiles: false,		//是否可选择文件
			canSelectFolders: true,		//是否可选择目录
			canSelectMany: false,		//是否可多选
			defaultUri: vscode.Uri.file("D:/VScode"),	//默认打开的文件夹
			openLabel: '选择文件夹'
		};
		vscode.window.showOpenDialog(options).then(result => {
			if(result === undefined){
				vscode.window.showInformationMessage("can't open dir.");
			}
			else{
				//vscode.window.showInformationMessage("open dir: " + result.toString());
				//TODO: 这里 URI 转本地路径,暂时先这样,以后再改
				var loadUri = result[0].path.toString();
				var loadDir = loadUri.substr(1, loadUri.length);
				vscode.window.showInformationMessage("open dir: " + loadDir);
				vscode.window.registerTreeDataProvider('TreeViewTest_One', new MyTreeData(loadDir));
			}
		});
	}));
	//注册命令 MyTreeItem.itemClick
	context.subscriptions.push(vscode.commands.registerCommand('MyTreeItem.itemClick', (label, filePath) => {
		//TODO:可以获取文件内容显示出来,这里暂时只打印入参
		console.log("label : " + label);
		console.log("filePath : " + filePath);
	}));
}
...

    按 F5 ,可以把这个项目目录导入进去,得到:
VScode插件视图显示本地文件目录树

6.给视图项添加嵌入式图标

//package.json
{
	...
	"activationEvents": [
		...
		"onCommand:TreeViewTest_One.item.add",
		"onCommand:TreeViewTest_One.item.delete"
	],
	"contributes": {
		"commands": [
			...
			{
				"command": "TreeViewTest_One.item.add",
				"title": "添加",
				"icon": {
					"light": "images/add_light.svg",
					"dark": "images/add_dark.svg"
				}
			},
			{
				"command": "TreeViewTest_One.item.delete",
				"title": "删除",
				"icon": {
					"light": "images/delete_light.svg",
					"dark": "images/delete_dark.svg"
				}
			}
		],
		...
		"menus": {
			"view/item/context": [
				{//直接显示在右侧
					"command": "TreeViewTest_One.item.add",
					"when": "view == TreeViewTest_One && viewItem == MyTreeItem",
					"group": "inline"
				},
				{//鼠标右击后显示
					"command": "TreeViewTest_One.item.delete",
					"when": "view == TreeViewTest_One && viewItem == MyTreeItem"
				}
			]
		}
	},
}
...

    注册命令

//function activate 中 添加
//注册命令 TreeViewTest_One.item.add
	context.subscriptions.push(vscode.commands.registerCommand('TreeViewTest_One.item.add', () => {
		//TODO:你想要执行的操作,这里只弹出信息
		vscode.window.showInformationMessage('add');
	}));

	//注册命令 TreeViewTest_One.item.delete
	context.subscriptions.push(vscode.commands.registerCommand('TreeViewTest_One.item.delete', () => {
		//TODO:你想要执行的操作,这里只弹出信息
		vscode.window.showInformationMessage('delete');
	}));

    执行结果:
VScode插件视图显示本地文件目录树

7.问题

  • 如何让视图中目录的右键事件和文件的右键事件不一样?
  • 如何设置视图中,空白处(非视图item处)的右键点击事件?
    希望有大佬可以给我解惑,谢谢。
相关标签: VScode插件