Procedura dettagliata: Implementare frammenti di codice

È possibile creare frammenti di codice e includerli in un'estensione dell'editor in modo che gli utenti dell'estensione possano aggiungerli al proprio codice.

Un frammento di codice è un frammento di codice o un altro testo che può essere incorporato in un file. Per visualizzare tutti i frammenti di codice registrati per specifici linguaggi di programmazione, scegliere Gestione frammenti di codice dal menu Strumenti. Per inserire un frammento di codice in un file, fare clic con il pulsante destro del mouse su dove si desidera inserire il frammento, scegliere Inserisci frammento o Racchiudi con, individuare il frammento desiderato e quindi fare doppio clic su di esso. Premere TAB o MAIUSC+ TAB per modificare le parti pertinenti del frammento e quindi premere INVIO o ESC per accettarlo. Per altre informazioni, vedere Frammenti di codice.

Un frammento di codice è contenuto in un file XML con estensione snippet*. Un frammento di codice può contenere campi evidenziati dopo l'inserimento del frammento in modo che l'utente possa trovarli e modificarli. Un file di frammento di codice fornisce anche informazioni per Gestione frammenti di codice in modo che possa visualizzare il nome del frammento nella categoria corretta. Per informazioni sullo schema del frammento di codice, vedere Informazioni di riferimento sullo schema dei frammenti di codice.

Questa procedura dettagliata illustra come eseguire queste attività:

  1. Creare e registrare frammenti di codice per un linguaggio specifico.

  2. Aggiungere il comando Inserisci frammento a un menu di scelta rapida.

  3. Implementare l'espansione del frammento.

    Questa procedura dettagliata si basa sulla procedura dettagliata: Visualizzazione del completamento dell'istruzione.

Creare e registrare frammenti di codice

In genere, i frammenti di codice sono associati a un servizio di linguaggio registrato. Tuttavia, non è necessario implementare un LanguageService per registrare frammenti di codice. Specificare invece un GUID nel file di indice del frammento di codice e quindi usare lo stesso GUID nell'oggetto ProvideLanguageCodeExpansionAttribute aggiunto al progetto.

La procedura seguente illustra come creare frammenti di codice e associarli a un GUID specifico.

  1. Creare la struttura di directory seguente:

    %InstallDir%\TestSnippets\Snippets\1033\

    dove %InstallDir% è la cartella di installazione di Visual Studio. Anche se questo percorso viene in genere usato per installare frammenti di codice, è possibile specificare qualsiasi percorso.

  2. Nella cartella \1033\ creare un file xml e denominarlo TestSnippets.xml. Anche se questo nome viene in genere usato per un file di indice del frammento di codice, è possibile specificare qualsiasi nome purché abbia un'estensione di file xml . Aggiungere il testo seguente, quindi eliminare il GUID segnaposto e aggiungere il proprio.

    <?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. Creare un file nella cartella del frammento di codice, denominarlo test.snippet e quindi aggiungere il testo seguente:

    <?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>
    

    I passaggi seguenti illustrano come registrare i frammenti di codice.

Per registrare frammenti di codice per un GUID specifico

  1. Aprire il progetto CompletionTest . Per informazioni su come creare questo progetto, vedere Procedura dettagliata: Visualizzare il completamento dell'istruzione.

  2. Nel progetto aggiungere riferimenti agli assembly seguenti:

    • Microsoft.VisualStudio.TextManager.Interop

    • Microsoft.VisualStudio.TextManager.Interop.8.0

    • microsoft.msxml

  3. Nel progetto aprire il file source.extension.vsixmanifest .

  4. Assicurarsi che la scheda Asset contenga un tipo di contenuto VsPackage e che Project sia impostato sul nome del progetto.

  5. Selezionare il progetto CompletionTest e nel Finestra Proprietà impostare Genera file Pkgdef su true. Salvare il progetto.

  6. Aggiungere una classe statica SnippetUtilities al progetto.

    static class SnippetUtilities
    
  7. Nella classe SnippetUtilities definire un GUID e assegnargli il valore usato nel file SnippetsIndex.xml .

    internal const string LanguageServiceGuidStr = "00000000-0000-0000-0000-00000000";
    
  8. Aggiungere l'oggetto ProvideLanguageCodeExpansionAttribute alla TestCompletionHandler classe . Questo attributo può essere aggiunto a qualsiasi classe pubblica o interna (non statica) nel progetto. Potrebbe essere necessario aggiungere una using direttiva per lo spazio dei nomi 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. Compilare ed eseguire il progetto. Nell'istanza sperimentale di Visual Studio che inizia quando viene eseguito il progetto, il frammento appena registrato deve essere visualizzato in Gestione frammenti di codice nel linguaggio TestSnippets .

Aggiungere il comando Inserisci frammento al menu di scelta rapida

Il comando Inserisci frammento di codice non è incluso nel menu di scelta rapida per un file di testo. Pertanto, è necessario abilitare il comando .

Per aggiungere il comando Inserisci frammento al menu di scelta rapida

  1. Aprire il file di TestCompletionCommandHandler classe.

    Poiché questa classe implementa IOleCommandTarget, è possibile attivare il comando Inserisci frammento nel QueryStatus metodo . Prima di abilitare il comando, verificare che questo metodo non venga chiamato all'interno di una funzione di automazione perché quando si fa clic sul comando Inserisci frammento di codice viene visualizzata l'interfaccia utente di selezione frammenti.

    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. Compilare ed eseguire il progetto. Nell'istanza sperimentale aprire un file con estensione zzz e quindi fare clic con il pulsante destro del mouse in un punto qualsiasi. Il comando Inserisci frammento dovrebbe essere visualizzato nel menu di scelta rapida.

Implementare l'espansione del frammento nell'interfaccia utente di Selezione frammenti

Questa sezione illustra come implementare l'espansione del frammento di codice in modo che venga visualizzata l'interfaccia utente della selezione frammenti quando si fa clic su Inserisci frammento di codice dal menu di scelta rapida. Un frammento di codice viene espanso anche quando un utente digita il collegamento del frammento di codice e quindi preme TAB.

Per visualizzare l'interfaccia utente della selezione frammenti di codice e per abilitare l'accettazione del frammento di codice di spostamento e post-inserimento, usare il Exec metodo . L'inserimento stesso viene gestito dal OnItemChosen metodo .

L'implementazione dell'espansione del frammento di codice usa interfacce legacy Microsoft.VisualStudio.TextManager.Interop . Quando si esegue la conversione dalle classi dell'editor corrente al codice legacy, tenere presente che le interfacce legacy usano una combinazione di numeri di riga e numeri di colonna per specificare le posizioni in un buffer di testo, ma le classi correnti usano un indice. Pertanto, se un buffer ha tre righe ognuna delle quali ha 10 caratteri (più una nuova riga, che conta come un carattere), il quarto carattere nella terza riga si trova nella posizione 27 nell'implementazione corrente, ma si trova alla riga 2, posizione 3 nell'implementazione precedente.

Per implementare l'espansione del frammento

  1. Nel file contenente la TestCompletionCommandHandler classe aggiungere le direttive seguenti using .

    using Microsoft.VisualStudio.Text.Operations;
    using MSXML;
    using System.ComponentModel.Composition;
    
  2. Rendere la TestCompletionCommandHandler classe implementare l'interfaccia IVsExpansionClient .

    internal class TestCompletionCommandHandler : IOleCommandTarget, IVsExpansionClient
    
  3. TestCompletionCommandHandlerProvider Nella classe importare .ITextStructureNavigatorSelectorService

    [Import]
    internal ITextStructureNavigatorSelectorService NavigatorService { get; set; }
    
  4. Aggiungere alcuni campi privati per le interfacce di espansione del codice e .IVsTextView

    IVsTextView m_vsTextView;
    IVsExpansionManager m_exManager;
    IVsExpansionSession m_exSession;
    
  5. Nel costruttore della TestCompletionCommandHandler classe impostare i campi seguenti.

    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. Per visualizzare la selezione frammenti di codice quando l'utente fa clic sul comando Inserisci frammento di codice , aggiungere il codice seguente al Exec metodo . Per rendere più leggibile questa spiegazione, il codice usato per il Exec()completamento dell'istruzione non viene visualizzato. Al contrario, i blocchi di codice vengono aggiunti al metodo esistente. Aggiungere il blocco di codice seguente dopo il codice che verifica la presenza di un carattere.

    //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. Se un frammento di codice contiene campi che possono essere spostati, la sessione di espansione viene mantenuta aperta fino a quando l'espansione non viene accettata in modo esplicito; se il frammento di codice non contiene campi, la sessione viene chiusa e viene restituita come null dal InvokeInsertionUI metodo . Exec Nel metodo, dopo il codice dell'interfaccia utente di selezione del frammento aggiunto nel passaggio precedente, aggiungere il codice seguente per gestire lo spostamento dei frammenti di codice (quando l'utente preme TAB o MAIUSC+TAB dopo l'inserimento del frammento).

    //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. Per inserire il frammento di codice quando l'utente digita il collegamento corrispondente e quindi preme TAB, aggiungi codice al Exec metodo . Il metodo privato che inserisce il frammento verrà visualizzato in un passaggio successivo. Aggiungere il codice seguente dopo il codice di spostamento aggiunto nel passaggio precedente.

    //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. Implementare i metodi dell'interfaccia IVsExpansionClient . In questa implementazione, gli unici metodi di interesse sono EndExpansion e OnItemChosen. Gli altri metodi devono restituire S_OKsemplicemente .

    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. Implementa il metodo OnItemChosen. Il metodo helper che inserisce effettivamente le espansioni viene trattato in un passaggio successivo. fornisce TextSpan informazioni sulla riga e sulla colonna, che è possibile ottenere da IVsTextView.

    public int OnItemChosen(string pszTitle, string pszPath)
    {
        InsertAnyExpansion(null, pszTitle, pszPath);
        return VSConstants.S_OK;
    }
    
  11. Il metodo privato seguente inserisce un frammento di codice, in base al collegamento o al titolo e al percorso. Chiama quindi il InsertNamedExpansion metodo con il frammento di codice.

    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;
    }
    

Espansione del frammento di codice di compilazione e test

È possibile verificare se l'espansione del frammento funziona nel progetto.

  1. Compilare la soluzione. Quando si esegue questo progetto nel debugger, viene avviata una seconda istanza di Visual Studio.

  2. Aprire un file di testo e digitare del testo.

  3. Fare clic con il pulsante destro del mouse in un punto del testo e quindi scegliere Inserisci frammento.

  4. L'interfaccia utente di selezione del frammento di codice dovrebbe essere visualizzata con un popup che indica Campi di sostituzione test. Fare doppio clic sul popup.

    Il frammento di codice seguente deve essere inserito.

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

    Non premere INVIO o ESC.

  5. Premere TAB e SPOSTA+TAB per passare da "first" a "second".

  6. Accettare l'inserimento premendo INVIO o ESC.

  7. In una parte diversa del testo digitare "test" e quindi premere TAB. Poiché "test" è il collegamento al frammento di codice, il frammento di codice deve essere nuovamente inserito.