手把手教你開發(fā)自己的VSCode插件
一、前言
在VSCode市場上搜索一個PDF閱讀插件,找到了下載量最高的插件。在檢查了插件的源代碼之后,發(fā)現它直接嵌入了pdf.js的Web界面。
圖片
開發(fā)這樣的插件并不復雜,只需要一些插件開發(fā)的知識。
接下來,將在這里與大家分享這個插件是如何開發(fā)的。雖然與源代碼可能有些許不同,但基本原理是相同的。
二、定義自定義編輯器
默認情況下,VSCode不直接支持查看某些特定類型的文件。但是,你可以通過使用自定義編輯器(customerEditors)來擴展其功能。
通過customerEditors,你可以創(chuàng)建完全可自定義的讀/寫編輯器,以替換VSCode的標準文本編輯器,用于處理特定類型的資源。
例如,在編輯Markdown文件時,可以創(chuàng)建一個自定義編輯器,從而實時預覽Markdown的渲染效果。
對于PDF文件的預覽,同樣也可以使用customerEditors功能。
首先,在插件的pacakge.json文件中定義它:
"contributes": {
"customEditors": [
{
"viewType": "dodo-reader.pdfEditor",
"displayName": "PDF Viewer",
"selector": [
{
"filenamePattern": "*.pdf"
}
]
}
]
}
三、注冊自定義編輯器
創(chuàng)建一個實現vscode.CustomEditorProvider接口的類。該接口包含了用于管理自定義編輯器(例如打開和保存)的方法。例如:
class PdfEditorProvider implements Partial {
constructor() {
// 可在此處進行初始化操作
}
resolveCustomEditor(document: CustomDocument, webviewPanel: WebviewPanel, _token: CancellationToken) {
// 基于URI創(chuàng)建一個自定義文檔,并返回一個自定義文檔對象
}
openCustomDocument(uri: vscode.Uri, openContext: vscode.CustomDocumentOpenContext, token: vscode.CancellationToken) {
// 將自定義文檔與Webview面板關聯,并處理編輯器內容與Webview之間的交互
}
}
// 注冊你的提供程序
const myProvider = new PdfEditorProvider();
const disposable = vscode.window.registerCustomEditorProvider('dodo-reader.pdfEditor', myProvider);
context.subscriptions.push(disposable);
四、改進視圖提供程序
如上所述注冊自定義編輯器之后,下一步就是加強PdfEditorProvider接口,以定義視圖的顯示方式。
顯示涉及使用網絡視圖,而你將使用pdf.js的Web視圖程序。首先,下載預構建的程序(現代瀏覽器),并將其提取到你的項目目錄中。
圖片
這個程序可以直接在瀏覽器中訪問。在目錄中啟動一個服務,并打開地址:
你可以通過添加查詢參數?file=fileUrl來打開PDF文件。
4.1 openCustomDocument
當打開PDF文件時,首先會調用openCustomDocument方法。你可以基于傳入的URI創(chuàng)建一個自定義文檔對象。該文檔對象將包含你想要編輯的內容。在這個程序中,不進行任何處理,而是直接返回一個包含URI的對象,該對象將傳遞給resolveCustomEditor方法的document參數。
openCustomDocument(uri: vscode.Uri, openContext: vscode.CustomDocumentOpenContext, token: vscode.CancellationToken) {
return {
uri,
dispose: () => { }
};
}
4.2 resolveCustomEditor
在resolveCustomEditor方法中,可以定義要顯示的視圖,程序如下。
resolveCustomEditor(document: CustomDocument, webviewPanel: WebviewPanel, _token: CancellationToken) {
webviewPanel.webview.html = 'Hello World!';
}
要顯示PDF文件,只需將下載的PDF程序的HTML內容替換為當前的webviewPanel.webview.html值。最初認為可以更改為:
// 嵌入一個iframe以查看PDF
webviewPanel.webview.html = `
`
這個方法是最簡單的,但令人意外的是,iframe沒有顯示內容。這可能是由于VSCode的安全策略限制造成的。
因此,在這里考慮了另一種方法:讀取PDF視圖主頁的HTML內容,并將其賦值給webviewPanel.webview.html。
然而,這種方法可能會遇到一個問題:如何向PDF視圖程序提供文件鏈接。
如前所述,PDF視圖程序將通過查詢參數“file.”獲取文件鏈接。如果沒有提供此參數,程序將讀取默認鏈接。以下是一個相關的源代碼示例:
file = params.get("file") ?? _app_options.AppOptions.get("defaultUrl");
然而,直接賦值HTML文本不能通過URL提供“file”的值。這可能需要修改源代碼。盡管修改源代碼可能會帶來一些不便,但一開始似乎也沒有其他辦法,所以不得不嘗試一下。一種方法是改變獲取“file”值的方式,直接從全局變量中獲取。可以使用如下方法在HTML內容中添加“file”值:
window.file = 'https://...'
然后將上述源代碼修改為:
file = window.file ?? _app_options.AppOptions.get("defaultUrl");
然而,就在我以為成功即將到來的時候,出現了一個紅色錯誤:“加載PDF時出現錯誤。文件來源與閱覽器不匹配”。
經過進一步調查,在相應的源代碼中發(fā)現了一個“fileinputchange”事件監(jiān)聽處理程序,如下所示:
var webViewerFileInputChange = function (evt) {
if (PDFViewerApplication.pdfViewer?.isInPresentationMode) {
return;
}
const file = evt.fileInput.files[0];
PDFViewerApplication.open({
url: URL.createObjectURL(file),
originalUrl: file.name
});
};
從代碼中很容易看出,一旦檢測到文件發(fā)生更改,就會立即調用open方法打開文件。重要的是要注意,該open方法并不驗證URL是否與當前源相匹配。目前,它使用了一個blob鏈接。在這種情況下,我們是否可以在初始化后直接調用open方法來打開文件呢?經過一些改造,最終證明是可行的。以下是修改后的代碼示例:
resolveCustomEditor(document: CustomDocument, webviewPanel: WebviewPanel, _token: CancellationToken) {
webviewPanel.webview.options = {
enableScripts: true,
localResourceRoots: [vscode.Uri.file(path.dirname(document.uri.fsPath)), this.context.extensionUri]
};
const base = vscode.Uri.joinPath(this.context.extensionUri, 'dist/web/pdf/web/')
webviewPanel.webview.html = readFileSync(path.join(base.fsPath, 'viewer.html'), 'utf8').replace('', `
`)
}
對上面的關鍵代碼進行分析:
- localResourceRoots參數:該參數的目的是定義可以通過Web URL訪問的目錄。在這里,需要明確定義兩個目錄:一個用于打開PDF文件的當前目錄,另一個用于插件所在的目錄。確保兩者都正確定義,否則可能在訪問時遇到401錯誤。
- tag:通過設置tag,可以為網頁內部加載資源提供基本路徑。確保資源能夠正確加載。
- setTimeout函數:在調用程序打開文件時使用setTimeout的目的是在打開默認PDF文件后再打開所需的PDF文件。這樣可以防止后面執(zhí)行打開默認PDF的操作覆蓋想要打開的PDF(盡管實際上會出現默認文件錯誤,但不會產生實質性影響)。
與之前提到的PDF插件實現相比,這種實現要簡單得多,但原理基本相同。