自定義編輯器 API
自定義編輯器允許擴充套件建立完全可定製的讀/寫編輯器,這些編輯器將取代 VS Code 的標準文字編輯器來處理特定型別的檔案。它們有多種用途,例如:
- 直接在 VS Code 中預覽著色器或 3D 模型等資產。
- 為 Markdown 或 XAML 等語言建立所見即所得編輯器。
- 為 CSV、JSON 或 XML 等資料檔案提供替代的視覺渲染。
- 為二進位制或文字檔案構建完全可定製的編輯體驗。
本文件概述了自定義編輯器 API 以及實現自定義編輯器的基礎知識。我們將探討兩種自定義編輯器型別及其區別,以及哪種適合您的用例。然後,對於這兩種自定義編輯器型別,我們將介紹構建一個行為良好的自定義編輯器的基礎知識。
儘管自定義編輯器是一個強大的新擴充套件點,但實現一個基本的自定義編輯器實際上並不難!不過,如果您正在開發第一個 VS Code 擴充套件,您可能需要等到對 VS Code API 的基礎知識更加熟悉後再深入研究自定義編輯器。自定義編輯器建立在許多 VS Code 概念之上,例如 Webview 和文字文件,因此如果您同時學習所有這些新概念,可能會感到不知所措。
但是,如果您已經準備就緒,並正在考慮您將要構建的各種酷炫自定義編輯器,那麼讓我們開始吧!請務必下載 自定義編輯器擴充套件示例,以便您可以按照文件進行操作,並瞭解自定義編輯器 API 的組合方式。
連結
VS Code API 用法
自定義編輯器 API 基礎知識
自定義編輯器是為特定資源顯示的替代檢視,取代 VS Code 的標準文字編輯器。自定義編輯器有兩個部分:使用者與之互動的檢視,以及您的擴充套件用於與底層資源互動的文件模型。
自定義編輯器的檢視部分是使用 Webview 實現的。這使您可以使用標準的 HTML、CSS 和 JavaScript 構建自定義編輯器的使用者介面。Webview 不能直接訪問 VS Code API,但它們可以透過來回傳遞訊息與擴充套件進行通訊。有關 Webview 的更多資訊以及使用它們的最佳實踐,請檢視我們的 Webview 文件。
自定義編輯器的另一部分是文件模型。此模型是您的擴充套件如何理解它正在處理的資源(檔案)。CustomTextEditorProvider 使用 VS Code 的標準 TextDocument 作為其文件模型,並且檔案中的所有更改都使用 VS Code 的標準文字編輯 API 來表達。另一方面,CustomReadonlyEditorProvider 和 CustomEditorProvider 允許您提供自己的文件模型,這使它們可以用於非文字檔案格式。
自定義編輯器每個資源只有一個文件模型,但可能存在該文件的多個編輯器例項(檢視)。例如,想象一下您打開了一個具有 CustomTextEditorProvider 的檔案,然後執行 **檢視:拆分編輯器** 命令。在這種情況下,仍然只有一個 TextDocument,因為工作區中仍然只有一個資源的副本,但現在該資源有兩個 Webview。
CustomEditor vs CustomTextEditor
有兩種自定義編輯器:自定義文字編輯器和自定義編輯器。它們之間的主要區別在於它們如何定義其文件模型。
CustomTextEditorProvider 使用 VS Code 的標準 TextDocument 作為其資料模型。您可以將 CustomTextEditor 用於任何基於文字的檔案型別。CustomTextEditor 的實現要容易得多,因為 VS Code 已經知道如何處理文字檔案,因此可以實現諸如儲存檔案和為熱退出備份檔案之類的操作。
另一方面,使用 CustomEditorProvider,您的擴充套件會帶來自己的文件模型。這意味著您可以使用 CustomEditor 處理二進位制格式,例如影像,但這也意味著您的擴充套件負責更多工作,包括實現儲存和備份。如果您的自定義編輯器是隻讀的,例如用於預覽的自定義編輯器,則可以跳過許多此類複雜性。
在決定使用哪種自定義編輯器型別時,決定通常很簡單:如果您處理基於文字的檔案格式,請使用 CustomTextEditorProvider;對於二進位制檔案格式,請使用 CustomEditorProvider。
貢獻點
customEditors 貢獻點是您的擴充套件如何告知 VS Code 其提供的自定義編輯器。例如,VS Code 需要知道您的自定義編輯器處理哪些型別的檔案,以及如何在任何 UI 中識別您的自定義編輯器。
以下是 自定義編輯器擴充套件示例 的基本 customEditor 貢獻。
"contributes": {
"customEditors": [
{
"viewType": "catEdit.catScratch",
"displayName": "Cat Scratch",
"selector": [
{
"filenamePattern": "*.cscratch"
}
],
"priority": "default"
}
]
}
customEditors 是一個數組,因此您的擴充套件可以貢獻多個自定義編輯器。讓我們分解一下自定義編輯器條目本身。
-
viewType- 您自定義編輯器的唯一識別符號。這是 VS Code 如何將
package.json中的自定義編輯器貢獻與程式碼中的自定義編輯器實現相關聯。它在所有擴充套件中必須是唯一的,因此請使用特定於您的擴充套件的唯一識別符號,而不是通用的viewType,例如"preview",例如"viewType": "myAmazingExtension.svgPreview"。 -
displayName- 在 VS Code UI 中標識自定義編輯器的名稱。顯示名稱顯示給使用者,例如在 **檢視:重新開啟方式** 下拉列表中。
-
selector- 指定自定義編輯器對哪些檔案有效。selector是一個或多個 Glob 模式 的陣列。這些 Glob 模式與檔名匹配,以確定自定義編輯器是否可用於這些檔案。filenamePattern,例如*.png,將為所有 PNG 檔案啟用自定義編輯器。您還可以建立更具體的模式,根據檔名或目錄名進行匹配,例如
**/translations/*.json。 -
priority- (可選) 指定何時使用自定義編輯器。priority控制資源開啟時自定義編輯器的使用時間。可能的值為:"default"- 嘗試為匹配自定義編輯器selector的每個檔案使用自定義編輯器。如果一個檔案有多個自定義編輯器,使用者必須選擇他們想要使用的自定義編輯器。"option"- 預設不使用自定義編輯器,但允許使用者切換到它或將其配置為預設值。
自定義編輯器啟用
當用戶開啟您的自定義編輯器之一時,VS Code 會觸發 onCustomEditor:VIEW_TYPE 啟用事件。在啟用期間,您的擴充套件必須呼叫 registerCustomEditorProvider 以使用預期的 viewType 註冊自定義編輯器。
重要的是要注意,onCustomEditor 僅在 VS Code 需要建立自定義編輯器例項時呼叫。如果 VS Code 只是向用戶顯示有關可用自定義編輯器的一些資訊(例如使用 **檢視:重新開啟方式** 命令),您的擴充套件不會被啟用。
自定義文字編輯器
自定義文字編輯器允許您為文字檔案建立自定義編輯器。這可以是任何內容,從純粹的非結構化文字到 CSV,再到 JSON 或 XML。自定義文字編輯器使用 VS Code 的標準 TextDocument 作為其文件模型。
自定義編輯器擴充套件示例 包括一個簡單的自定義文字編輯器示例,用於貓抓檔案(這些檔案只是以 .cscratch 副檔名結尾的 JSON 檔案)。讓我們看看實現自定義文字編輯器的一些重要部分。
自定義文字編輯器生命週期
VS Code 處理自定義文字編輯器的檢視元件(Webview)和模型元件(TextDocument)的生命週期。當需要建立新的自定義編輯器例項時,VS Code 會呼叫您的擴充套件,並在使用者關閉標籤頁時清理編輯器例項和文件模型。
為了理解這一切是如何工作的,讓我們來回顧一下當用戶開啟自定義文字編輯器以及使用者關閉自定義文字編輯器時,從擴充套件的角度來看會發生什麼。
開啟自定義文字編輯器
使用 自定義編輯器擴充套件示例,當用戶首次開啟 .cscratch 檔案時會發生以下情況:
-
VS Code 觸發
onCustomEditor:catCustoms.catScratch啟用事件。如果我們的擴充套件尚未啟用,這將啟用它。在啟用期間,我們的擴充套件必須透過呼叫
registerCustomEditorProvider來確保為catCustoms.catScratch註冊CustomTextEditorProvider。 -
然後,VS Code 會在註冊的
CustomTextEditorProvider上呼叫resolveCustomTextEditor,用於catCustoms.catScratch。此方法接受要開啟的資源的
TextDocument和WebviewPanel。擴充套件必須填充此 Webview 的初始 HTML 內容。
resolveCustomTextEditor 返回後,我們的自定義編輯器將顯示給使用者。Webview 中顯示的內容完全由我們的擴充套件決定。
每次開啟自定義編輯器時,都會發生相同的流程,即使您拆分了自定義編輯器。每個自定義編輯器例項都有自己的 WebviewPanel,儘管多個自定義文字編輯器將共享同一個 TextDocument,如果它們是針對同一個資源的話。記住:將 TextDocument 視為資源的模型,而 Webview 面板是該模型的檢視。
關閉自定義文字編輯器
當用戶關閉自定義文字編輯器時,VS Code 會在 WebviewPanel 上觸發 WebviewPanel.onDidDispose 事件。此時,您的擴充套件應清理與該編輯器關聯的任何資源(事件訂閱、檔案監視器等)。
當給定資源的最後一個自定義編輯器關閉時,該資源的 TextDocument 也將被處置,前提是沒有其他編輯器使用它,也沒有其他擴充套件持有它。您可以檢查 TextDocument.isClosed 屬性來檢視 TextDocument 是否已關閉。一旦 TextDocument 關閉,使用自定義編輯器開啟同一個資源將導致開啟一個新的 TextDocument。
將更改與 TextDocument 同步
由於自定義文字編輯器使用 TextDocument 作為其文件模型,因此它們負責在自定義編輯器中發生編輯時更新 TextDocument,以及在 TextDocument 更改時更新自身。
從 Webview 到 TextDocument
自定義文字編輯器中的編輯可以採取多種形式,例如點選按鈕、更改文字、拖動專案等。每當使用者在自定義文字編輯器中編輯檔案本身時,擴充套件都必須更新 TextDocument。貓抓擴充套件的實現方式如下:
-
使用者點選 Webview 中的 **新增抓取** 按鈕。此 訊息已釋出 從 Webview 回到擴充套件。
-
擴充套件接收到訊息。然後,它更新文件的內部模型(在貓抓示例中,這只是向 JSON 新增一個新條目)。
-
擴充套件建立一個
WorkspaceEdit,將更新後的 JSON 寫入文件。此編輯使用vscode.workspace.applyEdit應用。
儘量將工作區編輯保持在更新文件所需的最小更改範圍。另請記住,如果您處理的是 JSON 之類的語言,您的擴充套件應該嘗試遵守使用者現有的格式約定(空格與製表符、縮排大小等)。
從 TextDocument 到 Webview
當 TextDocument 更改時,您的擴充套件還需要確保其 Webview 反映文件的新狀態。文字文件可能因使用者操作(如撤銷、重做或還原檔案)、其他擴充套件使用 WorkspaceEdit,或使用者在 VS Code 的預設文字編輯器中開啟檔案而更改。貓抓擴充套件的實現方式如下:
-
在擴充套件中,我們訂閱
vscode.workspace.onDidChangeTextDocument事件。此事件針對TextDocument的每次更改(包括我們自定義編輯器進行的更改!)觸發。 -
當收到我們有編輯器處理的文件的更改時,我們會向 Webview 傳送一個包含其新文件狀態的訊息。然後,Webview 會更新自身以渲染更新後的文件。
重要的是要記住,自定義編輯器觸發的任何檔案編輯都會導致 onDidChangeTextDocument 被觸發。確保您的擴充套件不會陷入更新迴圈,即使用者在 Webview 中進行編輯,觸發 onDidChangeTextDocument,導致 Webview 更新,導致 Webview 觸發您的擴充套件的另一項更新,從而觸發 onDidChangeTextDocument,依此類推。
還要記住,如果您處理的是 JSON 或 XML 等結構化語言,文件可能並不總是處於有效狀態。您的擴充套件必須能夠優雅地處理錯誤,或者向用戶顯示錯誤訊息,以便他們瞭解出了什麼問題以及如何修復。
最後,如果更新您的 Webview 成本很高,請考慮 去抖動 Webview 的更新。
自定義編輯器
CustomEditorProvider 和 CustomReadonlyEditorProvider 允許您為二進位制檔案格式建立自定義編輯器。此 API 讓您可以完全控制檔案如何顯示給使用者、如何進行編輯,並允許您的擴充套件掛鉤到 save 和其他檔案操作。同樣,如果您正在為基於文字的檔案格式構建編輯器,請考慮使用 CustomTextEditor,因為它們的實現要簡單得多。
自定義編輯器擴充套件示例 包括一個簡單的自定義二進位制編輯器示例,用於 paw draw 檔案(這些檔案只是以 .pawdraw 副檔名結尾的 JPEG 檔案)。讓我們看看為二進位制檔案構建自定義編輯器需要哪些內容。
CustomDocument
使用自定義編輯器,您的擴充套件負責實現自己的文件模型,透過 CustomDocument 介面。這使您的擴充套件可以自由地在 CustomDocument 上儲存任何需要與自定義編輯器互動的資料,但也意味著您的擴充套件必須實現基本的文件操作,例如儲存和備份檔案資料以進行熱退出。
每個開啟的檔案有一個 CustomDocument。使用者可以為單個資源開啟多個編輯器(例如,透過拆分當前自定義編輯器),但所有這些編輯器都將由同一個 CustomDocument 支援。
自定義編輯器生命週期
supportsMultipleEditorsPerDocument
預設情況下,VS Code 只允許每個自定義文件有一個編輯器。此限制使正確實現自定義編輯器更加容易,因為您不必擔心同步多個自定義編輯器例項。
但是,如果您的擴充套件可以支援,我們建議在註冊自定義編輯器時設定 supportsMultipleEditorsPerDocument: true,以便可以為同一文件開啟多個編輯器例項。這將使您的自定義編輯器比 VS Code 的普通文字編輯器更像。
開啟自定義編輯器 當用戶開啟與 customEditor 貢獻點匹配的檔案時,VS Code 會觸發 onCustomEditor 啟用事件,然後呼叫為提供的檢視型別註冊的提供程式。CustomEditorProvider 有兩個角色:提供自定義編輯器的文件,然後提供編輯器本身。以下是 自定義編輯器擴充套件示例 中的 catCustoms.pawDraw 編輯器所發生情況的有序列表:
-
VS Code 觸發
onCustomEditor:catCustoms.pawDraw啟用事件。如果我們的擴充套件尚未啟用,這將啟用它。我們還必須確保我們的擴充套件在啟用期間為
catCustoms.pawDraw編輯器註冊CustomReadonlyEditorProvider或CustomEditorProvider。 -
VS Code 呼叫我們為
catCustoms.pawDraw編輯器註冊的CustomReadonlyEditorProvider或CustomEditorProvider上的openCustomDocument。在這裡,我們的擴充套件會收到一個資源 URI,並且必須為該資源返回一個新的
CustomDocument。此時,我們的擴充套件應該建立其文件的內部模型。這可能涉及從磁碟讀取和解析初始資源狀態,或初始化我們新的CustomDocument。我們的擴充套件可以透過建立實現
CustomDocument的新類來定義此模型。請記住,此初始化階段完全由擴充套件決定;VS Code 不關心擴充套件在CustomDocument上儲存的任何其他資訊。 -
VS Code 呼叫
resolveCustomEditor,傳入來自步驟 2 的CustomDocument和一個新的WebviewPanel。在這裡,我們的擴充套件必須填充自定義編輯器的初始 HTML。如果需要,我們還可以保留對
WebviewPanel的引用,以便以後引用它,例如在命令中。
resolveCustomEditor 返回後,我們的自定義編輯器將顯示給使用者。
如果使用者在另一個編輯器組中使用我們的自定義編輯器開啟同一資源(例如,透過拆分第一個編輯器),則擴充套件的工作會變得更簡單。在這種情況下,VS Code 只是呼叫 resolveCustomEditor,傳入我們在第一個編輯器開啟時建立的同一個 CustomDocument。
關閉自定義編輯器
假設我們有兩個為同一資源開啟的自定義編輯器例項。當用戶關閉這些編輯器時,VS Code 會通知我們的擴充套件,以便它可以清理與編輯器關聯的任何資源。
當第一個編輯器例項關閉時,VS Code 會在關閉的編輯器中的 WebviewPanel 上觸發 WebviewPanel.onDidDispose 事件。此時,我們的擴充套件必須清理與該特定編輯器例項關聯的任何資源。
當第二個編輯器關閉時,VS Code 再次觸發 WebviewPanel.onDidDispose。但是現在我們也關閉了與 CustomDocument 相關的所有編輯器。當沒有更多編輯器用於 CustomDocument 時,VS Code 會在其上呼叫 CustomDocument.dispose。我們的擴充套件的 dispose 實現必須清理與文件關聯的任何資源。
如果使用者隨後使用我們的自定義編輯器重新開啟同一資源,我們將透過一個新的 CustomDocument 回到整個 openCustomDocument、resolveCustomEditor 流程。
只讀自定義編輯器
許多以下部分僅適用於支援編輯的自定義編輯器,儘管這聽起來有些矛盾,但許多自定義編輯器根本不需要編輯功能。例如,影像預覽。或記憶體轉儲的視覺化渲染。兩者都可以使用自定義編輯器實現,但兩者都不需要可編輯。這就是 CustomReadonlyEditorProvider 的用武之地。
CustomReadonlyEditorProvider 允許您建立不支援編輯的自定義編輯器。它們仍然可以互動,但不支援撤銷和儲存等操作。與完全可編輯的編輯器相比,實現只讀自定義編輯器要簡單得多。
可編輯自定義編輯器基礎知識
可編輯自定義編輯器允許您掛鉤到標準的 VS Code 操作,例如撤銷和重做、儲存和熱退出。這使得可編輯自定義編輯器非常強大,但也意味著正確實現它們比實現可編輯自定義文字編輯器或只讀自定義編輯器要複雜得多。
可編輯自定義編輯器由 CustomEditorProvider 實現。此介面擴充套件了 CustomReadonlyEditorProvider,因此您需要實現基本的 openCustomDocument 和 resolveCustomEditor 操作,以及一組編輯特定的操作。讓我們看看 CustomEditorProvider 的編輯特定部分。
編輯
可編輯自定義文件的更改透過編輯來表達。編輯可以是任何內容,從文字更改到影像旋轉,再到列表重新排序。VS Code 將編輯的具體內容完全留給您的擴充套件,但 VS Code 需要知道何時發生編輯。編輯是 VS Code 標記文件為髒(dirty)的方式,這反過來又啟用了自動儲存和備份。
每當使用者在自定義編輯器的任何 Webview 中進行編輯時,您的擴充套件都必須從其 CustomEditorProvider 觸發 onDidChangeCustomDocument 事件。onDidChangeCustomDocument 事件根據您的自定義編輯器實現可以觸發兩種事件型別:CustomDocumentContentChangeEvent 和 CustomDocumentEditEvent。
CustomDocumentContentChangeEvent
CustomDocumentContentChangeEvent 是一種最基本的編輯。它的唯一功能是告知 VS Code 文件已被編輯。
當擴充套件從 onDidChangeCustomDocument 觸發 CustomDocumentContentChangeEvent 時,VS Code 會將關聯的文件標記為髒。此時,文件變回非髒的唯一方法是使用者儲存或還原它。使用 CustomDocumentContentChangeEvent 的自定義編輯器不支援撤銷/重做。
CustomDocumentEditEvent
CustomDocumentEditEvent 是一種更復雜的編輯,支援撤銷/重做。您應該始終嘗試使用 CustomDocumentEditEvent 實現您的自定義編輯器,僅在無法實現撤銷/重做時才回退到使用 CustomDocumentContentChangeEvent。
CustomDocumentEditEvent 具有以下欄位:
document- 編輯所屬的CustomDocument。label- 可選文字,描述發生了哪種型別的編輯(例如:“Crop”、“Insert”...)。undo- 呼叫 VS Code 以在需要撤銷編輯時呼叫的函式。redo- 呼叫 VS Code 以在需要重做編輯時呼叫的函式。
當擴充套件從 onDidChangeCustomDocument 觸發 CustomDocumentEditEvent 時,VS Code 會將關聯的文件標記為髒。要使文件不再是髒的,使用者可以透過儲存或還原文件,或撤銷/重做迴文檔的最後儲存狀態來做到這一點。
當需要撤銷或重做某個特定編輯時,VS Code 會呼叫編輯器上的 undo 和 redo 方法。VS Code 維護一個內部編輯堆疊,因此如果您的擴充套件用三個編輯觸發 onDidChangeCustomDocument,我們稱它們為 a、b、c:
onDidChangeCustomDocument(a);
onDidChangeCustomDocument(b);
onDidChangeCustomDocument(c);
以下使用者操作序列將導致這些呼叫:
undo — c.undo()
undo — b.undo()
redo — b.redo()
redo — c.redo()
redo — no op, no more edits
為了實現撤銷/重做,您的擴充套件必須更新其關聯的自定義文件的內部狀態,並更新文件的所有關聯 Webview,以便它們反映文件的新狀態。請記住,單個資源可能有多個 Webview。這些必須始終顯示相同的檔案資料。例如,影像編輯器的多個例項必須始終顯示相同的畫素資料,但可以允許每個編輯器例項具有自己的縮放級別和 UI 狀態。
儲存
當用戶儲存自定義編輯器時,您的擴充套件負責將資源當前狀態寫入磁碟。您的自定義編輯器如何做到這一點很大程度上取決於您擴充套件的 CustomDocument 型別以及您的擴充套件如何跟蹤內部編輯。
儲存的第一步是獲取要寫入磁碟的資料流。常見的方法包括:
-
跟蹤資源狀態,以便可以快速對其進行序列化。
例如,一個基本的影像編輯器可能維護一個畫素資料緩衝區。
-
重放自上次儲存以來的編輯以生成新檔案。
例如,一個更高效的影像編輯器可能會跟蹤自上次儲存以來的編輯,例如
crop、rotate、scale。在儲存時,它會將這些編輯應用於檔案的上次儲存狀態以生成新檔案。 -
請求
WebviewPanel為要儲存的自定義編輯器提供檔案資料。但請記住,自定義編輯器即使在不可見時也可以儲存。因此,建議您的擴充套件的
save實現不依賴於WebviewPanel。如果不可能,您可以使用WebviewPanelOptions.retainContextWhenHidden設定,以便 Webview 即使在隱藏時也保持活動狀態。retainContextWhenHidden確實有大量的記憶體開銷,因此在使用它時要謹慎。
獲取資源資料後,您通常應該使用 工作區 FS API 將其寫入磁碟。FS API 接受 UInt8Array 資料,並且可以寫出二進位制和基於文字的檔案。對於二進位制檔案資料,只需將二進位制資料放入 UInt8Array。對於文字檔案資料,請使用 Buffer 將字串轉換為 UInt8Array。
const writeData = Buffer.from('my text data', 'utf8');
vscode.workspace.fs.writeFile(fileUri, writeData);
後續步驟
如果您想了解更多關於 VS Code 擴充套件性的資訊,請嘗試以下主題: