Поделиться через


Пошаговое руководство. Реализация фрагментов кода

Вы можете создать фрагменты кода и включить их в расширение редактора, чтобы пользователи расширения могли добавлять их в собственный код.

Фрагмент кода — это фрагмент кода или другой текст, который можно включить в файл. Чтобы просмотреть все фрагменты кода, зарегистрированные для определенных языков программирования, в меню "Сервис " щелкните "Диспетчер фрагментов кода". Чтобы вставить фрагмент в файл, щелкните правой кнопкой мыши нужный фрагмент кода, нажмите кнопку "Вставить фрагмент" или "Окружить с помощью", найдите нужный фрагмент кода и дважды щелкните его. Нажмите клавишу TAB или SHIFT+TAB, чтобы изменить соответствующие части фрагмента кода, а затем нажмите клавишу ВВОД или ESC, чтобы принять его. Дополнительные сведения см. в фрагментах кода.

Фрагмент кода содержится в XML-файле с расширением имени файла .snippet*. Фрагмент может содержать поля, выделенные после вставки фрагмента, чтобы пользователь смог найти и изменить их. Файл фрагмента кода также содержит сведения для диспетчера фрагментов кода, чтобы он смог отобразить имя фрагмента в правильной категории. Сведения о схеме фрагмента кода см . в справочнике по схеме фрагментов кода.

В этом пошаговом руководстве показано, как выполнить следующие задачи:

  1. Создание и регистрация фрагментов кода для определенного языка.

  2. Добавьте команду вставки фрагмента в контекстное меню.

  3. Реализуйте расширение фрагмента кода.

    Это пошаговое руководство основано на пошаговом руководстве. Завершение инструкции Display.

Создание и регистрация фрагментов кода

Как правило, фрагменты кода связаны с зарегистрированной языковой службой. Однако для регистрации фрагментов кода не требуется реализовать LanguageService . Вместо этого просто укажите GUID в файле индекса фрагмента кода, а затем используйте тот же ИДЕНТИФИКАТОР GUID, который добавляется в ProvideLanguageCodeExpansionAttribute проект.

В следующих шагах показано, как создать фрагменты кода и связать их с определенным ИДЕНТИФИКАТОРом GUID.

  1. Создайте следующую структуру каталогов:

    %InstallDir%\TestSnippets\Snippets\1033\

    где %InstallDir% — это папка установки Visual Studio. (Хотя этот путь обычно используется для установки фрагментов кода, можно указать любой путь.)

  2. В папке \1033\ создайте XML-файл и назовите его TestSnippets.xml. (Хотя это имя обычно используется для файла индекса фрагмента кода, можно указать любое имя, если он имеет расширение ИМЕНИ XML-файла .) Добавьте следующий текст, а затем удалите идентификатор GUID заполнителя и добавьте свой собственный.

    <?xml version="1.0" encoding="utf-8" ?>
    <SnippetCollection>
        <Language Lang="TestSnippets" Guid="{00000000-0000-0000-0000-000000000000}">
            <SnippetDir>
                <OnOff>On</OnOff>
                <Installed>true</Installed>
                <Locale>1033</Locale>
                <DirPath>%InstallRoot%\TestSnippets\Snippets\%LCID%\</DirPath>
                <LocalizedName>Snippets</LocalizedName>
            </SnippetDir>
        </Language>
    </SnippetCollection>
    
  3. Создайте файл в папке фрагмента кода, присвойте ему имя .snippetи добавьте следующий текст:

    <?xml version="1.0" encoding="utf-8" ?>
    <CodeSnippets  xmlns="http://schemas.microsoft.com/VisualStudio/2005/CodeSnippet">
        <CodeSnippet Format="1.0.0">
            <Header>
                <Title>Test replacement fields</Title>
                <Shortcut>test</Shortcut>
                <Description>Code snippet for testing replacement fields</Description>
                <Author>MSIT</Author>
                <SnippetTypes>
                    <SnippetType>Expansion</SnippetType>
                </SnippetTypes>
            </Header>
            <Snippet>
                <Declarations>
                    <Literal>
                      <ID>param1</ID>
                        <ToolTip>First field</ToolTip>
                        <Default>first</Default>
                    </Literal>
                    <Literal>
                        <ID>param2</ID>
                        <ToolTip>Second field</ToolTip>
                        <Default>second</Default>
                    </Literal>
                </Declarations>
                <References>
                   <Reference>
                       <Assembly>System.Windows.Forms.dll</Assembly>
                   </Reference>
                </References>
                <Code Language="TestSnippets">
                    <![CDATA[MessageBox.Show("$param1$");
         MessageBox.Show("$param2$");]]>
                </Code>
            </Snippet>
        </CodeSnippet>
    </CodeSnippets>
    

    Ниже показано, как зарегистрировать фрагменты кода.

Регистрация фрагментов кода для определенного GUID

  1. Откройте проект CompletionTest. Сведения о создании этого проекта см. в пошаговом руководстве. Завершение инструкции Display.

  2. В проекте добавьте ссылки на следующие сборки:

    • Microsoft.VisualStudio.TextManager.Interop

    • Microsoft.VisualStudio.TextManager.Interop.8.0

    • microsoft.msxml

  3. В проекте откройте файл source.extension.vsixmanifest .

  4. Убедитесь, что вкладка "Активы" содержит тип контента VsPackage , а проект имеет имя проекта.

  5. Выберите проект CompletionTest и в окно свойств задайте для параметра Generate Pkgdef File значение true. Сохраните проект.

  6. Добавьте статический SnippetUtilities класс в проект.

    static class SnippetUtilities
    
  7. В классе SnippetUtilities определите GUID и присвойте ему значение, используемое в файле SnippetsIndex.xml .

    internal const string LanguageServiceGuidStr = "00000000-0000-0000-0000-00000000";
    
  8. Добавьте его в ProvideLanguageCodeExpansionAttribute TestCompletionHandler класс. Этот атрибут можно добавить в любой общедоступный или внутренний (нестатический) класс в проекте. (Возможно, вам потребуется добавить директиву using для пространства имен Microsoft.VisualStudio.Shell.)

    [ProvideLanguageCodeExpansion(
    SnippetUtilities.LanguageServiceGuidStr,
    "TestSnippets", //the language name
    0,              //the resource id of the language
    "TestSnippets", //the language ID used in the .snippet files
    @"%InstallRoot%\TestSnippets\Snippets\%LCID%\TestSnippets.xml",
        //the path of the index file
    SearchPaths = @"%InstallRoot%\TestSnippets\Snippets\%LCID%\",
    ForceCreateDirs = @"%InstallRoot%\TestSnippets\Snippets\%LCID%\")]
    internal class TestCompletionCommandHandler : IOleCommandTarget
    
  9. Постройте и запустите проект. В экспериментальном экземпляре Visual Studio, который запускается при запуске проекта, фрагмент кода, который вы только что зарегистрировали, должен отображаться в диспетчере фрагментов кода на языке TestSnippets.

Добавление команды "Вставка фрагмента" в контекстное меню

Команда insert Snippet не включена в контекстное меню текстового файла. Поэтому необходимо включить команду.

Добавление команды "Вставка фрагмента" в контекстное меню

  1. TestCompletionCommandHandler Откройте файл класса.

    Так как этот класс реализует IOleCommandTarget, можно активировать команду Insert Snippet в методе QueryStatus . Прежде чем включить команду, проверка, что этот метод не вызывается внутри функции автоматизации, так как при щелчке команды вставки фрагмента кода отображается пользовательский интерфейс средства выбора фрагмента кода.

    public int QueryStatus(ref Guid pguidCmdGroup, uint cCmds, OLECMD[] prgCmds, IntPtr pCmdText)
    {
        if (!VsShellUtilities.IsInAutomationFunction(m_provider.ServiceProvider))
        {
            if (pguidCmdGroup == VSConstants.VSStd2K && cCmds > 0)
            {
                // make the Insert Snippet command appear on the context menu 
                if ((uint)prgCmds[0].cmdID == (uint)VSConstants.VSStd2KCmdID.INSERTSNIPPET)
                {
                    prgCmds[0].cmdf = (int)Constants.MSOCMDF_ENABLED | (int)Constants.MSOCMDF_SUPPORTED;
                    return VSConstants.S_OK;
                }
            }
        }
    
        return m_nextCommandHandler.QueryStatus(ref pguidCmdGroup, cCmds, prgCmds, pCmdText);
    }
    
  2. Постройте и запустите проект. В экспериментальном экземпляре откройте файл с расширением ZZZ-файла и щелкните правой кнопкой мыши в любом месте. Команда "Вставка фрагмента" должна отображаться в контекстном меню.

Реализация расширения фрагмента в пользовательском интерфейсе средства выбора фрагментов

В этом разделе показано, как реализовать расширение фрагмента кода, чтобы пользовательский интерфейс средства выбора фрагмента отображался при щелчке фрагмента кода в контекстном меню. Фрагмент кода также расширяется, когда пользователь вводит ярлык фрагмента кода, а затем нажимает клавишу TAB.

Чтобы отобразить пользовательский интерфейс средства выбора фрагментов кода и включить принятие фрагмента кода навигации и вставки, используйте Exec этот метод. Сама вставка обрабатывается методом OnItemChosen .

Реализация расширения фрагмента кода использует устаревшие Microsoft.VisualStudio.TextManager.Interop интерфейсы. При переводе из текущих классов редакторов в устаревший код помните, что устаревшие интерфейсы используют сочетание номеров строк и столбцов для указания расположений в текстовом буфере, но текущие классы используют один индекс. Таким образом, если буфер имеет три строки, каждая из которых имеет 10 символов (плюс новая строка, которая подсчитывается как один символ), четвертый символ в третьей строке находится на позиции 27 в текущей реализации, но он находится в строке 2, позиции 3 в старой реализации.

Реализация расширения фрагмента кода

  1. В файл, содержащий TestCompletionCommandHandler класс, добавьте следующие using директивы.

    using Microsoft.VisualStudio.Text.Operations;
    using MSXML;
    using System.ComponentModel.Composition;
    
  2. Сделайте TestCompletionCommandHandler класс реализацией IVsExpansionClient интерфейса.

    internal class TestCompletionCommandHandler : IOleCommandTarget, IVsExpansionClient
    
  3. TestCompletionCommandHandlerProvider В классе импортируйте ITextStructureNavigatorSelectorServiceфайл .

    [Import]
    internal ITextStructureNavigatorSelectorService NavigatorService { get; set; }
    
  4. Добавьте некоторые частные поля для интерфейсов расширения кода и IVsTextView.

    IVsTextView m_vsTextView;
    IVsExpansionManager m_exManager;
    IVsExpansionSession m_exSession;
    
  5. В конструкторе TestCompletionCommandHandler класса задайте следующие поля.

    internal TestCompletionCommandHandler(IVsTextView textViewAdapter, ITextView textView, TestCompletionHandlerProvider provider)
    {
        this.m_textView = textView;
        m_vsTextView = textViewAdapter;
        m_provider = provider;
        //get the text manager from the service provider
        IVsTextManager2 textManager = (IVsTextManager2)m_provider.ServiceProvider.GetService(typeof(SVsTextManager));
        textManager.GetExpansionManager(out m_exManager);
        m_exSession = null;
    
        //add the command to the command chain
        textViewAdapter.AddCommandFilter(this, out m_nextCommandHandler);
    }
    
  6. Чтобы отобразить средство выбора фрагмента, когда пользователь щелкает команду Insert Snippet , добавьте следующий код в Exec метод. (Чтобы сделать это объяснение более читаемым, код, используемый для завершения инструкции, Exec()не отображается; вместо этого блоки кода добавляются в существующий метод.) Добавьте следующий блок кода после кода, который проверка символа.

    //code previously written for Exec
    if (pguidCmdGroup == VSConstants.VSStd2K && nCmdID == (uint)VSConstants.VSStd2KCmdID.TYPECHAR)
    {
        typedChar = (char)(ushort)Marshal.GetObjectForNativeVariant(pvaIn);
    }
    //the snippet picker code starts here
    if (nCmdID == (uint)VSConstants.VSStd2KCmdID.INSERTSNIPPET)
    {
        IVsTextManager2 textManager = (IVsTextManager2)m_provider.ServiceProvider.GetService(typeof(SVsTextManager));
    
        textManager.GetExpansionManager(out m_exManager);
    
        m_exManager.InvokeInsertionUI(
            m_vsTextView,
            this,      //the expansion client
            new Guid(SnippetUtilities.LanguageServiceGuidStr),
            null,       //use all snippet types
            0,          //number of types (0 for all)
            0,          //ignored if iCountTypes == 0
            null,       //use all snippet kinds
            0,          //use all snippet kinds
            0,          //ignored if iCountTypes == 0
            "TestSnippets", //the text to show in the prompt
            string.Empty);  //only the ENTER key causes insert 
    
        return VSConstants.S_OK;
    }
    
  7. Если фрагмент содержит поля, которые можно перемещать, сеанс расширения остается открытым, пока расширение не будет явно принято; Если фрагмент не имеет полей, сеанс закрывается и возвращается как null InvokeInsertionUI метод. В методе Exec после добавления кода пользовательского интерфейса средства выбора фрагмента кода, добавленного на предыдущем шаге, добавьте следующий код для обработки навигации фрагмента кода (когда пользователь нажимает клавишу TAB или SHIFT+TAB после вставки фрагмента).

    //the expansion insertion is handled in OnItemChosen
    //if the expansion session is still active, handle tab/backtab/return/cancel
    if (m_exSession != null)
    {
        if (nCmdID == (uint)VSConstants.VSStd2KCmdID.BACKTAB)
        {
            m_exSession.GoToPreviousExpansionField();
            return VSConstants.S_OK;
        }
        else if (nCmdID == (uint)VSConstants.VSStd2KCmdID.TAB)
        {
    
            m_exSession.GoToNextExpansionField(0); //false to support cycling through all the fields
            return VSConstants.S_OK;
        }
        else if (nCmdID == (uint)VSConstants.VSStd2KCmdID.RETURN || nCmdID == (uint)VSConstants.VSStd2KCmdID.CANCEL)
        {
            if (m_exSession.EndCurrentExpansion(0) == VSConstants.S_OK)
            {
                m_exSession = null;
                return VSConstants.S_OK;
            }
        }
    }
    
  8. Чтобы вставить фрагмент кода, когда пользователь вводит соответствующий ярлык, а затем нажимает клавишу TAB, добавьте код в Exec метод. Закрытый метод, вставляющий фрагмент кода, будет показан на следующем шаге. Добавьте следующий код после добавленного на предыдущем шаге кода навигации.

    //neither an expansion session nor a completion session is open, but we got a tab, so check whether the last word typed is a snippet shortcut 
    if (m_session == null && m_exSession == null && nCmdID == (uint)VSConstants.VSStd2KCmdID.TAB)
    {
        //get the word that was just added 
        CaretPosition pos = m_textView.Caret.Position;
        TextExtent word = m_provider.NavigatorService.GetTextStructureNavigator(m_textView.TextBuffer).GetExtentOfWord(pos.BufferPosition - 1); //use the position 1 space back
        string textString = word.Span.GetText(); //the word that was just added
        //if it is a code snippet, insert it, otherwise carry on
        if (InsertAnyExpansion(textString, null, null))
            return VSConstants.S_OK;
    }
    
  9. Реализуйте методы IVsExpansionClient интерфейса. В этой реализации единственными методами, интересующими вас, являются EndExpansion и OnItemChosen. Другие методы должны просто возвращать S_OK.

    public int EndExpansion()
    {
        m_exSession = null;
        return VSConstants.S_OK;
    }
    
    public int FormatSpan(IVsTextLines pBuffer, TextSpan[] ts)
    {
        return VSConstants.S_OK;
    }
    
    public int GetExpansionFunction(IXMLDOMNode xmlFunctionNode, string bstrFieldName, out IVsExpansionFunction pFunc)
    {
        pFunc = null;
        return VSConstants.S_OK;
    }
    
    public int IsValidKind(IVsTextLines pBuffer, TextSpan[] ts, string bstrKind, out int pfIsValidKind)
    {
        pfIsValidKind = 1;
        return VSConstants.S_OK;
    }
    
    public int IsValidType(IVsTextLines pBuffer, TextSpan[] ts, string[] rgTypes, int iCountTypes, out int pfIsValidType)
    {
        pfIsValidType = 1;
        return VSConstants.S_OK;
    }
    
    public int OnAfterInsertion(IVsExpansionSession pSession)
    {
        return VSConstants.S_OK;
    }
    
    public int OnBeforeInsertion(IVsExpansionSession pSession)
    {
        return VSConstants.S_OK;
    }
    
    public int PositionCaretForEditing(IVsTextLines pBuffer, TextSpan[] ts)
    {
        return VSConstants.S_OK;
    }
    
  10. Реализуйте метод OnItemChosen. Вспомогательный метод, который фактически вставляет расширения, рассматривается на следующем шаге. Предоставляет TextSpan сведения о строке и столбце, которые можно получить из .IVsTextView

    public int OnItemChosen(string pszTitle, string pszPath)
    {
        InsertAnyExpansion(null, pszTitle, pszPath);
        return VSConstants.S_OK;
    }
    
  11. Следующий закрытый метод вставляет фрагмент кода на основе ярлыка или заголовка и пути. Затем InsertNamedExpansion вызывает метод с фрагментом кода.

    private bool InsertAnyExpansion(string shortcut, string title, string path)
    {
        //first get the location of the caret, and set up a TextSpan
        int endColumn, startLine;
        //get the column number from  the IVsTextView, not the ITextView
        m_vsTextView.GetCaretPos(out startLine, out endColumn);
    
        TextSpan addSpan = new TextSpan();
        addSpan.iStartIndex = endColumn;
        addSpan.iEndIndex = endColumn;
        addSpan.iStartLine = startLine;
        addSpan.iEndLine = startLine;
    
        if (shortcut != null) //get the expansion from the shortcut
        {
            //reset the TextSpan to the width of the shortcut, 
            //because we're going to replace the shortcut with the expansion
            addSpan.iStartIndex = addSpan.iEndIndex - shortcut.Length;
    
            m_exManager.GetExpansionByShortcut(
                this,
                new Guid(SnippetUtilities.LanguageServiceGuidStr),
                shortcut,
                m_vsTextView,
                new TextSpan[] { addSpan },
                0,
                out path,
                out title);
    
        }
        if (title != null && path != null)
        {
            IVsTextLines textLines;
            m_vsTextView.GetBuffer(out textLines);
            IVsExpansion bufferExpansion = (IVsExpansion)textLines;
    
            if (bufferExpansion != null)
            {
                int hr = bufferExpansion.InsertNamedExpansion(
                    title,
                    path,
                    addSpan,
                    this,
                    new Guid(SnippetUtilities.LanguageServiceGuidStr),
                    0,
                   out m_exSession);
                if (VSConstants.S_OK == hr)
                {
                    return true;
                }
            }
        }
        return false;
    }
    

Расширение фрагмента кода сборки и тестирования

Вы можете проверить, работает ли расширение фрагмента в проекте.

  1. Постройте решение. При запуске этого проекта в отладчике запускается второй экземпляр Visual Studio.

  2. Откройте текстовый файл и введите текст.

  3. Щелкните правой кнопкой мыши текст и нажмите кнопку "Вставить фрагмент".

  4. Пользовательский интерфейс средства выбора фрагментов должен отображаться со всплывющим окном, в который указаны поля замены тестов. Дважды щелкните всплывающее окно.

    Следующий фрагмент кода должен быть вставлен.

    MessageBox.Show("first");
    MessageBox.Show("second");
    

    Не нажимайте клавишу ВВОД или ESC.

  5. Нажмите клавишу TAB и SHIFT+TAB, чтобы переключаться между "первой" и "второй".

  6. Примите вставку, нажав клавишу ВВОД или ESC.

  7. В другой части текста введите "test" и нажмите клавишу TAB. Так как "test" — это ярлык фрагмента кода, фрагмент кода должен быть вставлен снова.