Tutorial: Implementación de fragmentos de código
Puede crear fragmentos de código e incluirlos en una extensión del editor para que los usuarios de la extensión puedan agregarlos a su propio código.
Un fragmento de código es un fragmento de código u otro texto que se puede incorporar en un archivo. Para ver todos los fragmentos de código registrados para lenguajes de programación concretos, en el menú Herramientas , haga clic en Administrador de fragmentos de código. Para insertar un fragmento de código en un archivo, haga clic con el botón derecho donde desee el fragmento de código, haga clic en Insertar fragmento de código o Envolver con, busque el fragmento de código que desee y, a continuación, haga doble clic en él. Presione Tab o Mayús+Tab para modificar las partes pertinentes del fragmento de código y, a continuación, presione Entrar o Esc para aceptarlo. Para obtener más información, consulte Fragmentos de código.
Un fragmento de código se encuentra en un archivo XML que tiene la extensión de nombre de archivo .snippet*. Un fragmento de código puede contener campos resaltados después de insertar el fragmento de código para que el usuario pueda encontrarlos y cambiarlos. Un archivo de fragmento de código también proporciona información para el Administrador de fragmentos de código para que pueda mostrar el nombre del fragmento de código en la categoría correcta. Para obtener información sobre el esquema de fragmento de código, consulte Referencia de esquema de fragmentos de código.
En este tutorial se enseña a realizar estas tareas:
Cree y registre fragmentos de código para un idioma específico.
Agregue el comando Insertar fragmento de código a un menú contextual.
Implemente la expansión de fragmentos de código.
Este tutorial se basa en Walkthrough: Display statement completion.
Creación y registro de fragmentos de código
Normalmente, los fragmentos de código están asociados a un servicio de lenguaje registrado. Sin embargo, no es necesario implementar un LanguageService para registrar fragmentos de código. En su lugar, solo tiene que especificar un GUID en el archivo de índice de fragmento de código y, a continuación, usar el mismo GUID en el ProvideLanguageCodeExpansionAttribute que se agrega al proyecto.
En los pasos siguientes se muestra cómo crear fragmentos de código y asociarlos a un GUID específico.
Cree la siguiente estructura de directorios:
%InstallDir%\TestSnippets\Snippets\1033\
donde %InstallDir% es la carpeta de instalación de Visual Studio. (Aunque esta ruta de acceso se usa normalmente para instalar fragmentos de código, puede especificar cualquier ruta de acceso).
En la carpeta \1033\, cree un archivo .xml y asígnelo el nombre TestSnippets.xml. (Aunque este nombre se usa normalmente para un archivo de índice de fragmento de código, puede especificar cualquier nombre siempre que tenga una extensión de nombre de archivo .xml ). Agregue el texto siguiente y, a continuación, elimine el GUID del marcador de posición y agregue el suyo propio.
<?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>
Cree un archivo en la carpeta de fragmento de código, asígnele el nombre test
.snippet
y agregue el texto siguiente:<?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>
Los pasos siguientes muestran cómo registrar los fragmentos de código.
Para registrar fragmentos de código para un GUID específico
Abra el proyecto CompletionTest . Para obtener información sobre cómo crear este proyecto, vea Tutorial: Visualización de la finalización de instrucciones.
En el proyecto, agregue referencias a los ensamblados siguientes:
Microsoft.VisualStudio.TextManager.Interop
Microsoft.VisualStudio.TextManager.Interop.8.0
microsoft.msxml
En el proyecto, abra el archivo source.extension.vsixmanifest .
Asegúrese de que la pestaña Activos contiene un tipo de contenido VsPackage y que Project está establecido en el nombre del proyecto.
Seleccione el proyecto CompletionTest y, en el ventana Propiedades establezca Generar archivo Pkgdef en true. Guarde el proyecto.
Agregue una clase estática
SnippetUtilities
al proyecto.En la clase SnippetUtilities, defina un GUID y asígnele el valor que usó en el archivo SnippetsIndex.xml .
Agregue a ProvideLanguageCodeExpansionAttribute la
TestCompletionHandler
clase . Este atributo se puede agregar a cualquier clase pública o interna (no estática) del proyecto. (Es posible que tenga que agregar unausing
directiva para el espacio de nombres 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
Compile y ejecute el proyecto. En la instancia experimental de Visual Studio que se inicia cuando se ejecuta el proyecto, el fragmento de código que acaba de registrar debe mostrarse en el Administrador de fragmentos de código en el lenguaje TestSnippets .
Agregar el comando Insertar fragmento de código al menú contextual
El comando Insertar fragmento de código no se incluye en el menú contextual de un archivo de texto. Por lo tanto, debe habilitar el comando .
Para agregar el comando Insertar fragmento de código al menú contextual
Abra el archivo de
TestCompletionCommandHandler
clase.Dado que esta clase implementa IOleCommandTarget, puede activar el comando Insertar fragmento de código en el QueryStatus método . Antes de habilitar el comando, compruebe que no se llama a este método dentro de una función de automatización porque cuando se hace clic en el comando Insertar fragmento de código, muestra la interfaz de usuario (UI) del selector de fragmentos de código.
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); }
Compile y ejecute el proyecto. En la instancia experimental, abra un archivo que tenga la extensión de nombre de archivo .zzz y, a continuación, haga clic con el botón derecho en cualquier parte de él. El comando Insertar fragmento de código debe aparecer en el menú contextual.
Implementación de la expansión de fragmentos de código en la interfaz de usuario del selector de fragmentos
En esta sección se muestra cómo implementar la expansión de fragmentos de código para que se muestre la interfaz de usuario del selector de fragmentos de código cuando se hace clic en Insertar fragmento de código en el menú contextual. Un fragmento de código también se expande cuando un usuario escribe el acceso directo del fragmento de código y, a continuación, presiona tab.
Para mostrar la interfaz de usuario del selector de fragmentos de código y para habilitar la navegación y la aceptación del fragmento de código posterior a la inserción, use el Exec método . El método controla la OnItemChosen inserción en sí.
La implementación de la expansión de fragmentos de código usa interfaces heredadas Microsoft.VisualStudio.TextManager.Interop . Al traducir de las clases del editor actuales al código heredado, recuerde que las interfaces heredadas usan una combinación de números de línea y números de columna para especificar ubicaciones en un búfer de texto, pero las clases actuales usan un índice. Por lo tanto, si un búfer tiene tres líneas cada una de las cuales tiene 10 caracteres (más una nueva línea, que cuenta como un carácter), el cuarto carácter de la tercera línea está en la posición 27 de la implementación actual, pero está en la línea 2, posición 3 en la implementación anterior.
Para implementar la expansión de fragmentos de código
En el archivo que contiene la
TestCompletionCommandHandler
clase , agregue las siguientesusing
directivas.Haga que la
TestCompletionCommandHandler
clase implemente la IVsExpansionClient interfaz.En la
TestCompletionCommandHandlerProvider
clase , importe .ITextStructureNavigatorSelectorServiceAgregue algunos campos privados para las interfaces de expansión de código y IVsTextView.
En el constructor de la
TestCompletionCommandHandler
clase , establezca los campos siguientes.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); }
Para mostrar el selector de fragmentos de código cuando el usuario hace clic en el comando Insertar fragmento de código, agregue el código siguiente al Exec método . (Para que esta explicación sea más legible, no se muestra el
Exec()
código que se usa para la finalización de instrucciones; en su lugar, los bloques de código se agregan al método existente). Agregue el siguiente bloque de código después del código que comprueba si hay un carácter.//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; }
Si un fragmento de código tiene campos que se pueden navegar, la sesión de expansión se mantiene abierta hasta que se acepte explícitamente la expansión; si el fragmento de código no tiene campos, la sesión se cierra y se devuelve como
null
por el InvokeInsertionUI método . En el Exec método , después del código de interfaz de usuario del selector de fragmentos de código que agregó en el paso anterior, agregue el código siguiente para controlar la navegación del fragmento de código (cuando el usuario presiona la pestaña Tab o mayús+ después de la inserción del fragmento de código).//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; } } }
Para insertar el fragmento de código cuando el usuario escribe el acceso directo correspondiente y, a continuación, presiona Tab, agregue código al Exec método . El método privado que inserta el fragmento de código se mostrará en un paso posterior. Agregue el código siguiente después del código de navegación que agregó en el paso anterior.
//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; }
Implemente los métodos de la IVsExpansionClient interfaz. En esta implementación, los únicos métodos de interés son EndExpansion y OnItemChosen. Los otros métodos solo deben devolver 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; }
Implemente el método OnItemChosen. El método auxiliar que realmente inserta las expansiones se trata en un paso posterior. TextSpan proporciona información de línea y columna, que puede obtener de .IVsTextView
El siguiente método privado inserta un fragmento de código basado en el acceso directo o en el título y la ruta de acceso. A continuación, llama al InsertNamedExpansion método con el fragmento de código.
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; }
Expansión de fragmentos de código de compilación y prueba
Puede probar si la expansión de fragmentos de código funciona en el proyecto.
Compile la solución. Al ejecutar este proyecto en el depurador, se inicia una segunda instancia de Visual Studio.
Abra un archivo de texto y escriba texto.
Haga clic con el botón derecho en algún lugar del texto y, a continuación, haga clic en Insertar fragmento de código.
La interfaz de usuario del selector de fragmentos de código debe aparecer con un elemento emergente que indica Probar campos de reemplazo. Haga doble clic en el elemento emergente.
Se debe insertar el siguiente fragmento de código.
MessageBox.Show("first"); MessageBox.Show("second");
No presione Entrar o Esc.
Presione Tab y Mayús+Tab para alternar entre "primero" y "segundo".
Acepte la inserción presionando Entrar o Esc.
En otra parte del texto, escriba "test" y presione Tab. Dado que "test" es el acceso directo del fragmento de código, el fragmento de código se debe insertar de nuevo.