VSCode Extension中的Virtual Documents使用笔记

  我们在用TypeScript编写VSCode Extension应用时,可以通过VSCode API提供的内置Command "vscode.diff"来快速比较两个文档,有关该命令的参数介绍可以查看官方文档。基本用法如下:

vscode.commands.executeCommand("vscode.diff", vscode.Uri.file(filePath1), vscode.Uri.file(filePath2), "Comparing Files");

  这里的filePath1和filePath2为要进行比较的两个文档的路径。也就是说,这两个文档是必须真实存在的,而且路径能够被VSCode访问。有时为了需要,在进行比较时我们也可以将文档内容暂时输出到系统临时目录,然后从临时目录加载文档内容。获取系统临时目录的方法可以参考下面的代码:

import * as os from "os";
import * as path from "path";
import * as process from "process";

let platform = os.platform();
let isWin = platform === "win32";
let isLinux = platform === "linux";
let tempDir = isWin ? process.env.TEMP : (isLinux ? path.join(process.env.HOME, 'tmp') : process.env.TMPDIR);
console.log(tempDir);

   但是使用系统临时目录会带来另外一个问题,看下面的截图,在比较文档的界面右上角,有一个菜单可以直接点击打开文档,此时是从临时目录打开的,但有时我们并不想让用户知道文档是暂时存放在临时目录里的。有没有什么解决办法呢?我没有找到通过配置的方式将该菜单隐藏或者改变其行为,但是有两个变通的方法:一是不使用系统临时目录,仍然从文档的原始位置进行加载;二是使用VSCode提供的Virtual Documents

  下面是使用Virtual Documents之后的界面,可以看到与之前相比少了显示文档的路径和打开文档的菜单。

  下面是具体的实现。

  按照官方文档的介绍,我们需要定义一个TextDocumentContentProvider类的实例,其中的provideTextDocumentContent方法会返回Virtual Documents的具体内容。

 1 private async getDocumentText(fileFullPath: string): Promise<string> {
 2     return new Promise<string>(resolve => {
 3         fs.readFile(fileFullPath, "UTF-8", (err, data) => {
 4             if (err) {
 5                 console.log(err);
 6                 resolve("");
 7             } else {
 8                 resolve(data);
 9             }
10         });
11     });
12 }
13 
14 private sourceProvider = new class implements vscode.TextDocumentContentProvider {
15     onDidChangeEmitter = new vscode.EventEmitter<vscode.Uri>();
16     onDidChange = this.onDidChangeEmitter.event;
17 
18     async provideTextDocumentContent(uri: vscode.Uri): Promise<string> {
19         let oSource = await getDocumentText(uri.path);
20         let oSourceContent = JSON.stringify(oSource, null, "\t");
21         return oSourceContent;
22     }
23 };

  其中第19行通过getDocumentText方法从指定的路径读取了文档的内容,然后使用JSON.stringify将其格式化为标准的JSON文档,并返回对应的内容。onDidChange提供了文档被更新时的事件,我们可以在文档内容被修改时手动触发该事件,稍后会介绍。

  接下来是注册command。

subscriptions.push(vscode.workspace.registerTextDocumentContentProvider("sourceSchema", sourceProvider));

  注意这里的第一个参数schema非常重要!后面在传递文件url时都要带上这个schema,相当于它是一个标识,用来表示对该url的操作都通过sourceProvider类提供的方法来处理。

  相应地,如果vscode.diff的第二个文档你也希望使用Virtual Documents,那么就还需要定义TextDocumentContentProvider类的另一个实例。provideTextDocumentContent方法

1 subscriptions.push(vscode.workspace.registerTextDocumentContentProvider("compareSchema", new class implements vscode.TextDocumentContentProvider {
2     onDidChangeEmitter = new vscode.EventEmitter<vscode.Uri>();
3     onDidChange = this.onDidChangeEmitter.event;
4 
5     async provideTextDocumentContent(uri: vscode.Uri): Promise<string> {
6         return ""; // return file content from here
7     }
8 }));

  这里我简化了代码实现,没有单独定义TextDocumentContentProvider类的实例,而是直接通过subscriptions.push来注册command。其中的provideTextDocumentContent方法可以根据需要返回文档的内容,这里就不再给出对应的代码。

  好了,下面我们来看看在vscode.diff中如何使用它们。

const uriSource = vscode.Uri.parse("sourceSchema:" + filePath1);
const uriCompare = vscode.Uri.parse("compareSchema:" + filePath2);
vscode.commands.executeCommand("vscode.diff", uriSource, uriCompare, "Comparing Files");

  注意这里的filePath1和filePath2的前面都要加上对应的schema,否则文档被打开时就不会使用对应的TextDocumentContentProvider类的实例来处理,而会报找不到文档路径的错误。

  当修改文档内容后,可以手动触发TextDocumentContentProvider类的onDidChange事件来刷新已打开的文档内容,下面是对应的伪代码:

await writeFile(vscode.Uri.parse(filePath1).fsPath, fileContent);
sourceProvider.onDidChangeEmitter.fire(vscode.Uri.parse("sourceSchema:" + filePath1));

  另一个需要注意的地方是,通过Virtual Documents创建的文档都是以只读方式打开的,所以用户无法在编辑器中手动修改文件内容,我们可以在TextDocumentContentProvider类的provideTextDocumentContent方法中将已修改的内容返回。其它有关Document提供的事件可以查阅官方文档。

posted @ 2021-07-28 13:05  Jaxu  阅读(818)  评论(0编辑  收藏  举报