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提供的事件可以查閱官方文檔。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章