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