程式碼片段 (受管理的封裝架構) 的支援
程式碼片段是一種插入的原始程式檔的程式碼。 程式碼片段本身是以 XML 為基礎的範本,內含一組欄位。 之後便會插入程式碼片段,且可以有不同的值,用來插入程式碼片段的內容而定,會反白顯示這些欄位。 立即插入程式碼片段後,語言服務可以格式化程式碼片段。
程式碼片段會插入一種特殊的編輯模式,可讓使用 TAB 鍵巡覽至程式碼片段的欄位。 欄位可以支援 IntelliSense 樣式下拉式功鄋瞴。 使用者認可到原始程式檔的程式碼片段輸入 ENTER 或 ESC 鍵。 如果要進一步了解程式碼片段,請參閱程式碼片段。
管理套件架構支援程式碼片段
受管理的封裝架構 (MPF) 支援大部份的程式碼片段功能,無法讀取要插入程式碼片段的範本,並啟用特殊編輯模式。 支援透過管理ExpansionProvider類別。
當Source類別執行個體化時, CreateExpansionProvider中的方法LanguageService類別呼叫以取得ExpansionProvider物件 (請注意,基底LanguageService類別永遠會傳回新的ExpansionProvider每個物件Source物件)。
MPF 不支援擴充功能。 擴充函式是命名的函式內嵌於程式碼片段範本,並傳回一或多個要放在欄位中的值。 傳回的值所用的語言服務本身透過ExpansionFunction物件。 ExpansionFunction物件必須實作語言服務支援擴充功能。
提供程式碼片段的支援
若要啟用的程式碼片段支援,您必須提供系統,或安裝的程式碼片段,您必須提供使用者可將這些程式碼片段的方法。 有三個步驟來啟用程式碼片段的支援:
正在安裝的程式碼片段檔案。
啟用語言服務的程式碼片段。
叫用ExpansionProvider物件。
安裝程式碼片段檔案
通常一個程式碼片段範本每個檔案時,會以 XML 檔中的範本儲存所有的程式碼片段的語言。 如需使用程式碼片段範本的 XML 結構描述的詳細資訊,請參閱程式碼片段結構描述參考。 每個程式碼片段範本識別語言 id。 此語言 ID 在登錄中指定了,而且會放入Language <Code> 的屬性 在範本中的標記。
通常會有兩個儲存程式碼片段範本檔案的位置: 1) 已安裝您的語言,以及 2) 在 [使用者] 資料夾中。 這些位置新增至登錄所以,Visual Studio 程式碼片段管理員為 true 即可的程式碼片段。 使用者的資料夾是儲存使用者所建立的程式碼片段的位置。
已安裝的程式碼片段的範本檔案的典型資料夾配置看起來像這樣: [InstallRoot]\[TestLanguage]\Snippets\[LCID]\Snippets。
[InstallRoot]是在安裝程式語言的資料夾。
[TestLanguage]是您的語言與資料夾名稱的名稱。
[LCID]的地區設定識別碼。 這是您的程式碼片段的方式當地語系化的版本會儲存。 例如,英文的地區設定識別碼是 1033,所以 [LCID] 已由 1033年。
必須提供一個額外的檔案也就是未索引的檔案,通常會呼叫 SnippetsIndex.xml 或 ExpansionsIndex.xml (您可以使用任何有效的檔名,以.xml 結尾)。 這個檔案通常儲存在 [InstallRoot]\[TestLanguage] 資料夾,並指定程式碼片段資料夾,以及語言識別碼的確切位置的語言服務所使用的程式碼片段的 GUID。 索引檔案的確切路徑會放入登錄中,如稍後所述 〈 安裝登錄項目 〉。 下面是 SnippetsIndex.xml 檔案的範例:
<?xml version="1.0" encoding="utf-8" ?>
<SnippetCollection>
<Language Lang="Testlanguage" Guid="{b614a40a-80d9-4fac-a6ad-fc2868fff7cd}">
<SnippetDir>
<OnOff>On</OnOff>
<Installed>true</Installed>
<Locale>1033</Locale>
<DirPath>%InstallRoot%\TestLanguage\Snippets\%LCID%\Snippets\</DirPath>
<LocalizedName>Snippets</LocalizedName>
</SnippetDir>
</Language>
</SnippetCollection>
<Language> 標記用來指定語言識別碼 ( Lang屬性) 和語言服務的 GUID。
本範例假設您已安裝語言服務在 Visual Studio 的安裝資料夾。 %的 LCID %會被取代的使用者目前的地區設定 id。 多個 <SnippetDir> 標籤可以加入,一個用於每個不同的目錄和地區設定。 此外,程式碼片段資料夾可以包含子資料夾,每一種會 <SnippetSubDir> 用來識別在索引檔案 內嵌於 <SnippetDir> 的標記 標記。
使用者也可以建立自己的程式碼片段,您的語言。 這些通常儲存在使用者的 [設定] 資料夾,例如 [TestDocs]\Code Snippets\[TestLanguage]\Test 程式碼片段,其中 [TestDocs] 是 Visual Studio 的使用者的 [設定] 資料夾的位置。
下列的替代項目可以放入儲存在 <DirPath> 中的路徑 索引檔中的標記。
項目 |
描述 |
---|---|
%LCID% |
地區設定 ID。 |
%Installroot% |
Visual Studio,比方說,檔必要 Visual Studio 8 根安裝資料夾。 |
%Projdir% |
包含目前的專案資料夾。 |
%Projitem% |
包含目前的專案項目資料夾。 |
%Testdocs% |
資料夾的使用者設定] 資料夾中,例如 「 C:\Documents 」 和 「 Settings\[使用者名稱]\My Documents\Visual Studio\8。 |
啟用語言服務的程式碼片段
您可以啟用程式碼片段的語言服務藉由新增ProvideLanguageCodeExpansionAttribute屬性設定為您的 VSPackage (請參閱正在註冊語言服務 (管理的套件架構)如需詳細資訊)。 ShowRoots和SearchPaths參數是選擇性的但您必須加上SearchPaths參數名稱,以通知程式碼片段管理員您的程式碼片段的位置。
如何使用這個屬性的範例如下:
[ProvideLanguageCodeExpansion(
typeof(TestSnippetLanguageService),
"Test Snippet Language", // Name of language used as registry key
0, // Resource ID of localized name of language service
"Test Snippet Language", // Name of Language attribute in snippet template
@"%InstallRoot%\Test Snippet Language\Snippets\%LCID%\SnippetsIndex.xml", // Path to snippets index
SearchPaths = @"%InstallRoot%\Test Snippet Language\Snippets\%LCID%\")] // Path to snippets
呼叫擴充提供者
語言服務控制任何程式碼片段中,以及插入會叫用的方式插入的動作。
呼叫程式碼片段的擴充提供者
若要叫用擴充提供者的兩種方式: 使用功能表命令或使用 [完成] 清單中的捷徑。
使用功能表指令來插入程式碼片段
若要使用功能表命令,以顯示程式碼片段的瀏覽器,您加入功能表命令,然後呼叫DisplayExpansionBrowser中的方法ExpansionProvider介面,該功能表命令的回應。
.Vsct 檔案中加入命令和按鈕。 您可以找到在執行動作的指示逐步解說: 使用 Visual Studio 的封裝範本建立功能表命令。
衍生類別從ViewFilter類別並覆寫QueryCommandStatus方法,以指示新的功能表命令的支援。 本範例永遠會啟用功能表指令。
using Microsoft.VisualStudio.Package; namespace TestLanguagePackage { class TestViewFilter : ViewFilter { public TestViewFilter(CodeWindowManager mgr, IVsTextView view) : base(mgr, view) { } protected override int QueryCommandStatus(ref Guid guidCmdGroup, uint nCmdId) { int hr = base.QueryCommandStatus(ref guidCmdGroup, nCmdId); // If the base class did not recognize the command then // see if we can handle the command. if (hr == (int)Microsoft.VisualStudio.OLE.Interop.Constants.OLECMDERR_E_UNKNOWNGROUP) { if (guidCmdGroup == GuidList.guidTestLanguagePackageCmdSet) { if (nCmdId == PkgCmdIDList.InvokeCodeSnippetsBrowser) { hr = (int)(OLECMDF.OLECMDF_SUPPORTED | OLECMDF.OLECMDF_ENABLED); } } } return hr; } } }
覆寫HandlePreExec中的方法ViewFilter類別,以取得ExpansionProvider物件和呼叫DisplayExpansionBrowser上該物件的方法。
using Microsoft.VisualStudio.Package; namespace TestLanguagePackage { class TestViewFilter : ViewFilter { public override bool HandlePreExec(ref Guid guidCmdGroup, uint nCmdId, uint nCmdexecopt, IntPtr pvaIn, IntPtr pvaOut) { if (base.HandlePreExec(ref guidCmdGroup, nCmdId, nCmdexecopt, pvaIn, pvaOut)) { // Base class handled the command. Do nothing more here. return true; } if (guidCmdGroup == GuidList.guidTestLanguagePackageCmdSet) { if (nCmdId == PkgCmdIDList.InvokeCodeSnippetsBrowser) { ExpansionProvider ep = this.GetExpansionProvider(); if (this.TextView != null && ep != null) { bool bDisplayed = ep.DisplayExpansionBrowser( this.TextView, "TestLanguagePackage Snippet:", null, false, null, false); } return true; // Handled the command. } } return false; // Did not handle the command. } } }
下列的方法,在ExpansionProvider類別由呼叫 Visual Studio 指定的順序插入程式碼片段的過程:
-
後OnAfterInsertion呼叫方法時,在插入程式碼片段和ExpansionProvider物件處於一種特殊的編輯模式,用來修改剛插入的程式碼片段。
使用快速鍵來插入程式碼片段
實作完成清單中是捷徑的更深入比實作功能表命令。 您必須先將程式碼片段的捷徑,加入 IntelliSense 的文字完成清單。 然後您必須偵測已經完成的結果插入程式碼片段捷徑名稱。 最後,您必須取得的程式碼片段的標題和使用的捷徑名稱的路徑,並傳遞該資訊以InsertNamedExpansion上的方法ExpansionProvider方法。
如果要將程式碼片段的捷徑加入至 word 的 [完成] 清單中,將它們以Declarations物件在您AuthoringScope類別。 您必須確定您可以識別為程式碼片段名稱的捷徑。 如需範例,請參閱 逐步解說: 才會列出已安裝的程式碼片段 (受管理的封裝架構)。
您可以偵測到所插入的程式碼片段捷徑,在OnAutoComplete方法的Declarations類別。 因為原始程式檔已插入程式碼片段名稱,必須移除擴充插入時。 InsertNamedExpansion方法會將告訴您的程式碼片段 ; 插入點的範圍 如果範圍會包含原始程式檔中的整個程式碼片段名稱,該名稱會取代程式碼片段。
下面是一個版本的Declarations類別會處理程式碼片段插入指定捷徑的名稱。 其他的方法,在Declarations類別已經省略,以利檢視。 請注意,這個類別的建構函式LanguageService物件。 這可以傳入的版本AuthoringScope物件 (例如,您的實作AuthoringScope類別可能要花LanguageService其建構函式中的物件,並傳遞至該物件您TestDeclarations類別建構函式)。
[C#]
using Microsoft.VisualStudio.Package;
using Microsoft.VisualStudio.TextManager.Interop;
namespace TestLanguagePackage
{
internal class TestDeclarations : Declarations
{
private ArrayList declarations;
private LanguageService languageService;
private TextSpan commitSpan;
public TestDeclarations(LanguageService langService)
: base()
{
languageService = langService;
declarations = new ArrayList();
}
// This method is used to add declarations to the internal list.
public void AddDeclaration(TestDeclaration declaration)
{
declarations.Add(declaration);
}
// This method is called to get the string to commit to the source buffer.
// Note that the initial extent is only what the user has typed so far.
public override string OnCommit(IVsTextView textView,
string textSoFar,
char commitCharacter,
int index,
ref TextSpan initialExtent)
{
// We intercept this call only to get the initial extent
// of what was committed to the source buffer.
commitSpan = initialExtent;
return base.OnCommit(textView,
textSoFar,
commitCharacter,
index,
ref initialExtent);
}
// This method is called after the string has been committed to the source buffer.
public override char OnAutoComplete(IVsTextView textView,
string committedText,
char commitCharacter,
int index)
{
TestDeclaration item = declarations[index] as TestDeclaration;
if (item != null)
{
// In this example, TestDeclaration identifies types with a string.
// You can choose a different approach.
if (item.Type == "snippet")
{
Source src = languageService.GetSource(textView);
if (src != null)
{
ExpansionProvider ep = src.GetExpansionProvider();
if (ep != null)
{
string title;
string path;
int commitLength = commitSpan.iEndIndex - commitSpan.iStartIndex;
if (commitLength < committedText.Length)
{
// Replace everything that was inserted
// so calculate the span of the full
// insertion, taking into account what
// was inserted when the commitSpan
// was obtained in the first place.
commitSpan.iEndIndex += (committedText.Length - commitLength);
}
if (ep.FindExpansionByShortcut(textView,
committedText,
commitSpan,
true,
out title,
out path))
{
ep.InsertNamedExpansion(textView,
title,
path,
commitSpan,
false);
}
}
}
}
}
return '\0';
}
}
}
當語言服務取得的捷徑名稱時,它會呼叫FindExpansionByShortcut方法,以取得檔名和程式碼片段的標題。 語言服務接著會呼叫InsertNamedExpansion中的方法ExpansionProvider類別來插入程式碼片段。 Visual Studio 會呼叫下列方法中指定的順序ExpansionProvider在過程中插入程式碼片段的類別:
如需有關如何取得一份已安裝的程式碼片段的語言服務的詳細資訊,請參閱逐步解說: 才會列出已安裝的程式碼片段 (受管理的封裝架構)。
實作 ExpansionFunction 類別
擴充函式是命名的函式內嵌於程式碼片段範本,並傳回一或多個要放在欄位中的值。 若要在您的語言服務支援擴充功能,您必須衍生類別,以從ExpansionFunction類別並實作GetCurrentValue方法。 您必須再覆寫CreateExpansionFunction中的方法LanguageService類別以傳回新的具現化安裝的版本ExpansionFunction您支援的每個擴充函式的類別。 如果您支援的擴充函式的可能值清單時,您也必須覆寫GetIntellisenseList中的方法ExpansionFunction類別以傳回這些值的清單。
擴充函式,函式引數,或是需要存取其他的欄位應該無法編輯的欄位,與相關聯,作為擴充提供者可能無法完全初始化擴充函式被呼叫時。 如此一來,擴充函式不能夠取得它的引數或任何其他的欄位的值。
範例
以下是範例的簡單擴充函式的呼叫GetName ,即可實作。 這個擴充函式附加一個數字基底類別名稱來擴充函式會具現化每次 (其對應到每個時間相關聯的程式碼片段插入)。
using Microsoft.VisualStudio.Package;
namespace TestLanguagePackage
{
public class TestLanguageService : LanguageService
{
private int classNameCounter = 0;
public override ExpansionFunction CreateExpansionFunction(
ExpansionProvider provider,
string functionName)
{
ExpansionFunction function = null;
if (functionName == "GetName")
{
++classNameCounter;
function = new TestGetNameExpansionFunction(provider, classNameCounter);
}
return function;
}
}
internal class TestGetNameExpansionFunction : ExpansionFunction
{
private int nameCount;
TestGetNameExpansionFunction(ExpansionProvider provider, int counter)
: base(provider)
{
nameCount = counter;
}
public override string GetCurrentValue()
{
string name = "TestClass";
name += nameCount.ToString();
return name;
}
}
}
請參閱
工作
逐步解說: 才會列出已安裝的程式碼片段 (受管理的封裝架構)