共用抽象契約
本章節彙總了 Lertaro.PluginSdk 中跨多個介面複用的基礎資料模型、唯讀契約與設定驅動抽象。
1. 檢索結果模型 ISearchResult
在 Lertaro 的架構中,外掛模組對搜尋結果的觀察始終基於唯讀契約 ISearchResult,禁止直接篡改宿主底層的核心索引資料結構:
namespace Lertaro.PluginSdk.Abstractions;
public interface ISearchResult
{
string Name { get; } // 檔案或項目的顯示名稱(如 "Lertaro.exe")
string FullPath { get; } // 絕對實體路徑(如 "C:\Program Files\Lertaro\Lertaro.exe")
string ContextDirectory { get; } // 所在父級目錄路徑(如 "C:\Program Files\Lertaro")
bool IsDir { get; } // 是否為目錄/資料夾
bool IsApplication { get; } // 是否為可執行程式或捷徑
bool[]? GetHighlightMask(string text, string query) => null; // 字元級反白遮罩計算
FileMetadata Metadata => default; // 高效能檔案中繼資料(大小、修改時間等)
string? InstantActionArgument => null; // 即時結果所指向的目標
}FullPath 是大多數動作據以工作的識別碼,因此一個作用於「非路徑」事物的即時結果——activatewindow:12345、kill:4321、自訂命令的載入內容——會改將該目標攜帶在 InstantActionArgument 中,而提供者就在那裡讀取它。對於其他每一種列,它都保持 null:一般的檔案與資料夾結果、外掛模組搜尋動作、歷程項目。
NOTE
ISearchResult.Metadata 包含的資料由宿主底層的 USN / MFT 記憶體索引直接注入,讀取該屬性完全不產生任何磁碟 I/O 或 IPC 呼叫。僅當你需要查詢不屬於目前結果集的外部路徑時,才需要呼叫 FileMetadataService.GetMetadataAsync。
2. 檔案中繼資料結構 FileMetadata
public readonly record struct FileMetadata(
long Size,
DateTime Created,
DateTime Modified,
DateTime Accessed
);- 時間戳記均為本機時間(Local Time)。
- 若
Metadata == default(即各欄位均為 0 或DateTime.MinValue),表示該結果並非由實體檔案索引產生(例如由某個即時計算外掛模組動態產生)。 - 可透過
Metadata.Modified != default準確區分「中繼資料不可用」與「大小恰好為 0 位元組的合法真實檔案」。
3. 宿主安全控制介面 IPluginSearchWindow
當動作執行回呼(如 ISearchResultAction.Execute)被觸發時,宿主會傳入 IPluginSearchWindow 執行個體,供外掛模組安全調度宿主視窗:
public interface IPluginSearchWindow
{
void LocateInExplorerExternal(string path); // 在檔案總管或設定的檔案管理器中反白定位
void OpenFileOrFolderExternal(string path); // 使用關聯程式普通啟動
void OpenFileOrFolderAsAdminExternal(string path);// 提權以管理員身分啟動
void HideWindow(); // 隱藏目前搜尋視窗
}4. 結構描述驅動的設定體系 IConfigurable
如果你的外掛模組需要提供個人化設定項目,只需在外掛模組類別上實作 IConfigurable 介面,宿主便會在設定 → 外掛模組 → 設定中自動根據 Schema 轉譯出原生美觀的表單介面,無需手寫任何 XAML:
public interface IConfigurable
{
PluginConfigSchema GetConfigSchema();
}核心欄位類型 ConfigFieldType
| 欄位類型 | 轉譯控制項與說明 |
|---|---|
Boolean | 切換開關(Toggle Switch)或核取方塊。 |
Text | 文字輸入框。使用者清空時 RequireNonEmpty 會自動回復為 DefaultValue;MaxLength 會限制長度(0 或未設定表示不限制);SelectionStart / SelectionLength 設定這個欄位所開啟的輸入對話方塊中從 0 開始計算的初始選取範圍。 |
Integer | 數字微調輸入框。支援設定最小值與最大值範圍。 |
Choice | 下拉式選單。透過 Choices 或 ChoiceOptions 清單指定可選項目。 |
Array | 清單值。帶有 SubFields 時,它是一個記錄的清單,以主從編輯器轉譯(每個項目一個巢狀表單——檔案篩選、自訂命令與網頁搜尋外掛模組所使用的形態);沒有 SubFields 時,它是一個純量的簡單清單,以精簡的單欄編輯器轉譯。SDK 完全沒有給 DefaultValue 預設值(宣告中是 object?、null!),這就是儲存庫內每個外掛模組要表示空清單時都傳入 new List<object>() 的原因。 |
Object | 透過其 SubFields 編輯的單一結構化值,沒有 Array 的清單附加功能。 |
Group | 包含子欄位清單(SubFields)的可折疊卡片分組。 |
StringList | 支援多行編輯、項目增刪排序與自動換行的多行清單方塊;實際換行只以視覺標記顯示,不會寫入設定值。 |
Hotkey | 專屬按鍵錄製框。可設定 RequireModifier = true 強制要求必須包含修飾鍵。 |
FilePath / FolderPath | 附帶「瀏覽...」檔案/資料夾原生選取器按鈕的路徑輸入框。 |
CustomControl | 允許外掛模組直接掛載一個自訂的 WPF UIElement 控制項執行個體(也可經由 CustomControl 這條路徑抵達)。 |
Button | 顯示操作按鈕並呼叫欄位的 OnClick 委派,不儲存設定值。 |
其餘的 PluginConfigField 成員是宿主圍繞這些類型所繪製或持久化的資訊:Key(持久化的設定名稱)、GroupKey(該欄位位於哪張 Group 卡片)、LabelKey / DescriptionKey(翻譯鍵,而非字面文字)、RequireNonEmpty、Choices / ChoiceOptions / SubFields、IsTriggerWord(見 核心檢索與動作 的「觸發詞」一節)、MaxLength、SelectionStart / SelectionLength、CustomControl、OnClick,以及兩個讓外掛模組把值存放在宿主設定儲存區之外的委派:Func<object?>? GetValue 與 Action<object?>? SetValue。
圖示欄位
將欄位的 Schema 鍵設為 Icon 並使用 Text 類型時,宿主會顯示圖示預覽並支援直接輸入 WPF Path Data。貼上完整 SVG/XML 文件時,宿主會擷取並合併所有 <path d> 值,只儲存轉換後的 WPF Path Data;圖示內容無效時會清空並透過主題化錯誤對話方塊提示。不需要圖示時,空值仍然有效。
PluginConfigSchema 亦支援設定 OnSave 與 OnRollback 生命週期委派:使用者按下確定/套用提交時觸發 OnSave 執行自訂持久化,取消或回復時觸發 OnRollback 復原狀態。
本地化選擇標籤
當選項需要本地化標籤,同時仍要儲存穩定的設定值時,應使用 ChoiceOptions。PluginConfigChoice.Value 會寫入外掛模組設定,LabelKey 會解析為介面顯示文字;如果儲存值與顯示文字相同,繼續使用舊的 Choices 清單即可。
new PluginConfigField
{
Key = "DisplayMode",
FieldType = ConfigFieldType.Choice,
DefaultValue = "FriendlyName",
ChoiceOptions =
[
new PluginConfigChoice
{
Value = "FriendlyName",
LabelKey = "DisplayMode_FriendlyName"
}
]
}5. 完整搜尋視窗檔案結果 IFullSearchFileResultProvider
如果外掛模組需要向完整搜尋視窗提供真實的檔案或資料夾列,可以實作 IFullSearchFileResultProvider:
public interface IFullSearchFileResultProvider : IPluginComponent
{
IReadOnlyList<InstantResultItem> GetFileResults(string query, int limit);
// 選填。預設實作會走訪 GetFileResults,所以在這個成員出現之前寫的提供者無需改動。
IEnumerable<InstantResultItem> GetFileResultsStreamed(string query, int limit);
}主機在完整搜尋視窗自身的檔案搜尋仍在串流返回時,於背景執行緒呼叫提供者,並在結果一到就繪製,而不是等到搜尋結束。外掛模組不處理目前查詢時應傳回空清單。傳回的每個 InstantResultItem 都必須對應一個實際存在的檔案或資料夾,這樣完整視窗的路徑、大小和類型欄位才有意義。若提供者的回答需要數秒(例如全文索引遍歷),可改寫 GetFileResultsStreamed,邊找到邊交出命中,讓前幾列先上螢幕、其餘繼續查詢;因為介面的預設實作只是走訪 GetFileResults,改寫是選填的。此元件在設定 → 外掛模組中有自己的啟用/停用開關,以自己的元件類型為準——關掉外掛模組的即時結果提供者並不會連它一起關掉,反之亦然。
6. 使用者設定路徑解析 UserPathResolver
當外掛模組接受使用者輸入或設定中的路徑時,應使用 Lertaro.PluginSdk.Helpers.UserPathResolver,在呼叫檔案系統 API 前統一處理環境變數和 Windows Shell 虛擬路徑:
string expanded = UserPathResolver.Expand(rawPath); // 接收 string?,返回 string
bool isVirtual = UserPathResolver.IsVirtualPath(expanded);
string resolved = UserPathResolver.Resolve(rawPath); // 選填的第二參數見下方說明
// Resolve 與 ResolveForNavigation 都接受一個選填的 Func<string, string>?,用來在詢問檔案
// 系統之前,把無法解析的虛擬 Token 轉成真實路徑;若沒有提供解析委派、也沒有東西可解析,
// 作為最後手段就會原封不動地返回傳入的內容。
// 當路徑即將被開啟或瀏覽時,請用 ResolveForNavigation 而不是 Resolve:它還會額外把一個
// 虛擬 Shell 項目標準化為宿主得以導航的檔案系統目標。
string target = UserPathResolver.ResolveForNavigation(rawPath);Expand 會移除前後空白並展開 %USERPROFILE% 等環境變數。Resolve 會先展開環境變數,再盡可能將 shell:Downloads 或 ::{CLSID} 等標記解析為實體路徑。對於 shell:AppsFolder 這類沒有實體路徑的虛擬資料夾,Resolve 會改為傳回其標準名 ::{CLSID},讓同一個資料夾的各種寫法彼此相等;該結果仍然是虛擬路徑。只有 Shell 完全無法解析的標記才會原樣傳回。傳給檔案系統 API 前應使用 IsVirtualPath 檢查結果。目錄索引 API 只有在路徑解析為真實且被索引涵蓋的資料夾後才能列舉內容。