Trabajar con texto en el editor
El código de extensión se puede configurar para ejecutarse en respuesta a varios puntos de entrada (situaciones que se producen cuando un usuario interactúa con Visual Studio). La extensibilidad del editor admite actualmente tres puntos de entrada: agentes de escucha, EditorExtensibility objeto de servicio y comandos.
Los agentes de escucha de eventos se desencadenan cuando se producen determinadas acciones en una ventana del editor, representada en código por un TextView
. Por ejemplo, cuando un usuario escribe algo en el editor, se produce un evento TextViewChanged
. Cuando se abre o cierra una ventana de edición, se producen eventos TextViewOpened
y TextViewClosed
.
El objeto de servicio editor es una instancia de la clase EditorExtensibility
, que expone la funcionalidad del editor en tiempo real, como realizar modificaciones de texto.
los comandos los inicia el usuario haciendo clic en un elemento, que puede colocar en un menú, menú contextual o barra de herramientas.
Agregar un agente de escucha de vista de texto
Hay dos tipos de agentes de escucha, ITextViewChangedListener y ITextViewOpenClosedListener. Juntos, estos agentes de escucha se pueden usar para observar la apertura, cierre y modificación de los editores de texto.
A continuación, cree una nueva clase, implemente la clase base ExtensionPart y ITextViewChangedListener
, ITextViewOpenClosedListener
, o ambas, y agregue un atributo visualStudioContribution.
A continuación, implemente la propiedad TextViewExtensionConfiguration, según lo requerido por ITextViewChangedListener y ITextViewOpenClosedListener, haciendo que el agente de escucha se aplique al editar archivos de C#:
public TextViewExtensionConfiguration TextViewExtensionConfiguration => new()
{
AppliesTo = new[] { DocumentFilter.FromDocumentType("CSharp") },
};
Los tipos de documento disponibles para otros lenguajes de programación y tipos de archivo se enumeran más adelante en este artículoy los tipos de archivo personalizados también se pueden definir cuando sea necesario.
Suponiendo que decida implementar ambos escuchadores, la declaración final de la clase debería ser similar a la siguiente:
[VisualStudioContribution]
public sealed class TextViewOperationListener :
ExtensionPart, // This is the extension part base class containing infrastructure necessary to use VS services.
ITextViewOpenClosedListener, // Indicates this part listens for text view lifetime events.
ITextViewChangedListener // Indicates this part listens to text view changes.
{
public TextViewExtensionConfiguration TextViewExtensionConfiguration => new()
{
// Indicates this part should only light up in C# files.
AppliesTo = new[] { DocumentFilter.FromDocumentType("CSharp") },
};
...
Dado que ITextViewOpenClosedListener y ITextViewChangedListener declaran la propiedad TextViewExtensionConfiguration, la configuración se aplica a ambos escuchadores.
Al ejecutar la extensión, debería ver lo siguiente:
- ITextViewOpenClosedListener.TextViewOpenedAsync se llama cada vez que el usuario abre una vista de texto.
- ITextViewOpenClosedListener.TextViewClosedAsync se llama cada vez que el usuario cierra una vista de texto.
- ITextViewChangedListener.TextViewChangedAsync se llama cada vez que un usuario realiza un cambio en un documento de texto mostrado por una vista de texto.
Cada uno de estos métodos se pasa un ITextViewSnapshot que contiene el estado de la vista de texto y el documento de texto en el momento en que el usuario invocó la acción y un CancellationToken que tendrá IsCancellationRequested == true
cuando el IDE desee cancelar una acción pendiente.
Definir cuándo es relevante la extensión
La extensión suele ser relevante solo para determinados escenarios y tipos de documentos admitidos, por lo que es importante definir claramente su aplicabilidad. Puede usar AppliesTo configuración) de varias maneras para definir claramente la aplicabilidad de una extensión. Puede especificar qué tipos de archivo, como los lenguajes de código que admite la extensión, o bien refinar aún más la aplicabilidad de una extensión mediante la coincidencia en un patrón basado en el nombre de archivo o la ruta de acceso.
Especificar lenguajes de programación con la configuración de AppliesTo
La configuración AppliesTo indica los escenarios del lenguaje de programación en los que se debe activar la extensión. Se escribe como AppliesTo = new[] { DocumentFilter.FromDocumentType("CSharp") }
, donde el tipo de documento es un nombre conocido de un lenguaje integrado en Visual Studio o personalizado definido en una extensión de Visual Studio.
Algunos tipos de documento conocidos se muestran en la tabla siguiente:
Tipo de Documento | Descripción |
---|---|
"CSharp" | C# |
"C/C++" | C, C++, encabezados e IDL |
"TypeScript" | Lenguajes de tipos TypeScript y JavaScript. |
"HTML" | HTML |
"JSON" | JSON |
"texto" | Archivos de texto, incluidos descendientes jerárquicos de "código", que descienden de "text". |
"código" | C, C++, C#, etc. |
Los DocumentTypes son jerárquicos. Es decir, C# y C++ descienden de "código", por lo que declarar "código" hace que la extensión se active para todos los lenguajes de código, C#, C, C++, etc.
Definición de un nuevo tipo de documento
Puede definir un nuevo tipo de documento, por ejemplo, para admitir un lenguaje de código personalizado, agregando una propiedad estática DocumentTypeConfiguration a cualquier clase del proyecto de extensión y marcando la propiedad con el atributo VisualStudioContribution
.
DocumentTypeConfiguration
permite definir un nuevo tipo de documento, especificar que hereda uno o varios otros tipos de documento y especificar una o varias extensiones de archivo que se usan para identificar el tipo de archivo:
using Microsoft.VisualStudio.Extensibility.Editor;
internal static class MyDocumentTypes
{
[VisualStudioContribution]
internal static DocumentTypeConfiguration MarkdownDocumentType => new("markdown")
{
FileExtensions = new[] { ".md", ".mdk", ".markdown" },
BaseDocumentType = DocumentType.KnownValues.Text,
};
}
Las definiciones de tipo de documento se combinan con definiciones de tipo de contenido proporcionadas por la extensibilidad heredada de Visual Studio, lo que permite asignar extensiones de archivo adicionales a los tipos de documento existentes.
Selectores de documentos
Además de DocumentFilter.FromDocumentType, DocumentFilter.FromGlobPattern permite limitar aún más la aplicabilidad de la extensión de manera que se active solamente cuando la ruta de acceso del archivo del documento coincida con un patrón de comodín (glob):
[VisualStudioContribution]
public sealed class TextViewOperationListener
: ExtensionPart, ITextViewOpenClosedListener, ITextViewChangedListener
{
public TextViewExtensionConfiguration TextViewExtensionConfiguration => new()
{
AppliesTo = new[]
{
DocumentFilter.FromDocumentType("CSharp"),
DocumentFilter.FromGlobPattern("**/tests/*.cs"),
},
};
[VisualStudioContribution]
public sealed class TextViewOperationListener
: ExtensionPart, ITextViewOpenClosedListener, ITextViewChangedListener
{
public TextViewExtensionConfiguration TextViewExtensionConfiguration => new()
{
AppliesTo = new[]
{
DocumentFilter.FromDocumentType(MyDocumentTypes.MarkdownDocumentType),
DocumentFilter.FromGlobPattern("docs/*.md", relativePath: true),
},
};
El parámetro pattern
representa un patrón global que coincide con la ruta de acceso absoluta del documento.
Los patrones Glob pueden tener la sintaxis siguiente:
*
para que coincida con cero o más caracteres en un segmento de ruta?
para que coincida con un carácter en un segmento de ruta**
para que coincida con cualquier número de segmentos de ruta, incluido ninguno{}
para agrupar condiciones (por ejemplo,**/*.{ts,js}
corresponde a todos los archivos TypeScript y JavaScript)[]
declarar un intervalo de caracteres para que coincidan en un segmento de ruta de acceso (por ejemplo,example.[0-9]
para que coincidan enexample.0
,example.1
, ...)[!...]
con el fin de negar un intervalo de caracteres para coincidir en un segmento de ruta (por ejemplo,example.[!0-9]
para coincidir conexample.a
,example.b
, pero no conexample.0
)
Una barra diagonal inversa (\
) no es válida dentro de un patrón global. Asegúrese de convertir las barras diagonales inversas a la barra diagonal al crear el patrón global.
Funcionalidad del editor de acceso
Las clases de extensión del editor heredan de ExtensionPart. La clase ExtensionPart
expone la propiedad Extensibility. Con esta propiedad, puede solicitar una instancia del objeto EditorExtensibility. Puede usar este objeto para acceder a la funcionalidad del editor en tiempo real, como realizar modificaciones.
EditorExtensibility editorService = this.Extensibility.Editor();
Acceso al estado del editor dentro de un comando
ExecuteCommandAsync()
en cada Command
se pasa un IClientContext
que contiene una instantánea del estado del IDE en el momento en que se invocó el comando. Puede acceder al documento activo a través de la interfaz ITextViewSnapshot
, que obtiene desde el objeto EditorExtensibility
llamando al método asincrónico GetActiveTextViewAsync
:
using ITextViewSnapshot textView = await this.Extensibility.Editor().GetActiveTextViewAsync(clientContext, cancellationToken);
Una vez que tenga ITextViewSnapshot
, puede acceder al estado del editor. ITextViewSnapshot
es una vista inmutable del estado del editor en un momento dado, por lo que debe usar las otras interfaces del modelo de objetos Editor para realizar modificaciones.