Passo a passo: Exibir chaves correspondentesWalkthrough: Display matching braces

Implemente recursos de baseada na linguagem, por exemplo, definindo as chaves que você deseja corresponder e adicionando uma marca de marcador de texto para as chaves correspondentes, quando o cursor está em uma das chaves a correspondência de chaves.Implement language-based features, such as, brace matching by defining the braces you want to match, and adding a text marker tag to the matching braces when the caret is on one of the braces. Você pode definir chaves no contexto de um idioma, definir sua própria extensão de nome de arquivo e o tipo de conteúdo e aplicar as marcas para apenas esse tipo ou aplicam as marcas a um tipo de conteúdo existente (como "texto").You can define braces in the context of a language, define your own file name extension and content type, and apply the tags to just that type or apply the tags to an existing content type (such as "text"). A instrução a seguir mostra como aplicar marcas para o tipo de conteúdo "texto" a correspondência de chaves.The following walkthrough shows how to apply brace matching tags to the "text" content type.

Pré-requisitosPrerequisites

A partir do Visual Studio 2015, você não instale o SDK do Visual Studio no Centro de download.Starting in Visual Studio 2015, you don't install the Visual Studio SDK from the download center. Ela está incluída como um recurso opcional na instalação do Visual Studio.It's included as an optional feature in Visual Studio setup. Você também pode instalar o SDK do VS mais tarde.You can also install the VS SDK later on. Para obter mais informações, consulte instalar o SDK do Visual Studio.For more information, see Install the Visual Studio SDK.

Criar um projeto de Managed Extensibility Framework (MEF)Create a Managed Extensibility Framework (MEF) project

Para criar um projeto MEFTo create a MEF project

  1. Crie um projeto de classificador do Editor.Create an Editor Classifier project. Nomeie a solução BraceMatchingTest.Name the solution BraceMatchingTest.

  2. Adicione um modelo de item de classificação de Editor para o projeto.Add an Editor Classifier item template to the project. Para obter mais informações, consulte criar uma extensão com um modelo de item editor.For more information, see Create an extension with an editor item template.

  3. Exclua os arquivos de classe existentes.Delete the existing class files.

Implementar um marcador de correspondência de chavesImplement a brace matching tagger

Para obter um efeito semelhante ao que é usado no Visual Studio de realce de chave, você pode implementar um marcador de tipo TextMarkerTag.To get a brace highlighting effect that resembles the one that's used in Visual Studio, you can implement a tagger of type TextMarkerTag. O código a seguir mostra como definir o marcador para pares de chave em qualquer nível de aninhamento.The following code shows how to define the tagger for brace pairs at any level of nesting. Neste exemplo, os pares de chave de [] e {} são definidos no construtor de marcador, mas em uma implementação de linguagem completa, a chave relevante pares seriam definidas na especificação da linguagem.In this example, the brace pairs of [] and {} are defined in the tagger constructor, but in a full language implementation, the relevant brace pairs would be defined in the language specification.

Para implementar um marcador de correspondência de chavesTo implement a brace matching tagger

  1. Adicione um arquivo de classe e nomeie-a correspondência de chaves.Add a class file and name it BraceMatching.

  2. Importe os seguintes namespaces.Import the following namespaces.

    using System;
    using System.Linq;
    using System.Collections.Generic;
    using System.ComponentModel.Composition;
    using Microsoft.VisualStudio.Text;
    using Microsoft.VisualStudio.Text.Editor;
    using Microsoft.VisualStudio.Text.Tagging;
    using Microsoft.VisualStudio.Utilities;
    
    Imports System.ComponentModel.Composition
    Imports Microsoft.VisualStudio.Text
    Imports Microsoft.VisualStudio.Text.Editor
    Imports Microsoft.VisualStudio.Text.Tagging
    Imports Microsoft.VisualStudio.Utilities
    
  3. Definir uma classe BraceMatchingTagger que herda de ITagger<T> do tipo TextMarkerTag.Define a class BraceMatchingTagger that inherits from ITagger<T> of type TextMarkerTag.

    internal class BraceMatchingTagger : ITagger<TextMarkerTag>
    
    Friend Class BraceMatchingTagger
        Implements ITagger(Of TextMarkerTag)
    
  4. Adicione propriedades para a exibição de texto, o buffer de origem, o ponto do instantâneo atual e também um conjunto de pares de chave.Add properties for the text view, the source buffer, the current snapshot point, and also a set of brace pairs.

    ITextView View { get; set; }
    ITextBuffer SourceBuffer { get; set; }
    SnapshotPoint? CurrentChar { get; set; }
    private Dictionary<char, char> m_braceList;
    
    Private _View As ITextView
    Private Property View() As ITextView
        Get
            Return _View
        End Get
        Set(ByVal value As ITextView)
            _View = value
        End Set
    End Property
    Private _SourceBuffer As ITextBuffer
    Private Property SourceBuffer() As ITextBuffer
        Get
            Return _SourceBuffer
        End Get
        Set(ByVal value As ITextBuffer)
            _SourceBuffer = value
        End Set
    End Property
    Private _CurrentChar As System.Nullable(Of SnapshotPoint)
    Private Property CurrentChar() As System.Nullable(Of SnapshotPoint)
        Get
            Return _CurrentChar
        End Get
        Set(ByVal value As System.Nullable(Of SnapshotPoint))
            _CurrentChar = value
        End Set
    End Property
    Private m_braceList As Dictionary(Of Char, Char)
    
  5. No construtor de marcador, defina as propriedades e assinar os eventos de alteração do modo de exibição PositionChanged e LayoutChanged.In the tagger constructor, set the properties and subscribe to the view change events PositionChanged and LayoutChanged. Neste exemplo, para fins ilustrativos, os pares correspondentes também são definidos no construtor.In this example, for illustrative purposes, the matching pairs are also defined in the constructor.

    internal BraceMatchingTagger(ITextView view, ITextBuffer sourceBuffer)
    {
        //here the keys are the open braces, and the values are the close braces
        m_braceList = new Dictionary<char, char>();
        m_braceList.Add('{', '}');
        m_braceList.Add('[', ']');
        m_braceList.Add('(', ')');
        this.View = view;
        this.SourceBuffer = sourceBuffer;
        this.CurrentChar = null;
    
        this.View.Caret.PositionChanged += CaretPositionChanged;
        this.View.LayoutChanged += ViewLayoutChanged;
    }
    
    Friend Sub New(ByVal view As ITextView, ByVal sourceBuffer As ITextBuffer)
        'here the keys are the open braces, and the values are the close braces
        m_braceList = New Dictionary(Of Char, Char)()
        m_braceList.Add("{"c, "}"c)
        m_braceList.Add("["c, "]"c)
        m_braceList.Add("("c, ")"c)
        Me.View = view
        Me.SourceBuffer = sourceBuffer
        Me.CurrentChar = Nothing
    
        AddHandler Me.View.Caret.PositionChanged, AddressOf Me.CaretPositionChanged
        AddHandler Me.View.LayoutChanged, AddressOf Me.ViewLayoutChanged
    End Sub
    
  6. Como parte do ITagger<T> implementação, declare um evento TagsChanged.As part of the ITagger<T> implementation, declare a TagsChanged event.

    public event EventHandler<SnapshotSpanEventArgs> TagsChanged;
    
    Public Event TagsChanged As EventHandler(Of SnapshotSpanEventArgs) _
        Implements ITagger(Of TextMarkerTag).TagsChanged
    
  7. Os manipuladores de eventos atualizar a posição atual do cursor do CurrentChar propriedade e gerar o evento TagsChanged.The event handlers update the current caret position of the CurrentChar property and raise the TagsChanged event.

    void ViewLayoutChanged(object sender, TextViewLayoutChangedEventArgs e)
    {
        if (e.NewSnapshot != e.OldSnapshot) //make sure that there has really been a change
        {
            UpdateAtCaretPosition(View.Caret.Position);
        }
    }
    
    void CaretPositionChanged(object sender, CaretPositionChangedEventArgs e)
    {
        UpdateAtCaretPosition(e.NewPosition);
    }
    void UpdateAtCaretPosition(CaretPosition caretPosition)
    {
        CurrentChar = caretPosition.Point.GetPoint(SourceBuffer, caretPosition.Affinity);
    
        if (!CurrentChar.HasValue)
            return;
    
        var tempEvent = TagsChanged;
        if (tempEvent != null)
            tempEvent(this, new SnapshotSpanEventArgs(new SnapshotSpan(SourceBuffer.CurrentSnapshot, 0,
                SourceBuffer.CurrentSnapshot.Length)));
    }
    
    Private Sub ViewLayoutChanged(ByVal sender As Object, ByVal e As TextViewLayoutChangedEventArgs)
        If e.NewSnapshot IsNot e.OldSnapshot Then
            'make sure that there has really been a change
            UpdateAtCaretPosition(View.Caret.Position)
        End If
    End Sub
    
    Private Sub CaretPositionChanged(ByVal sender As Object, ByVal e As CaretPositionChangedEventArgs)
        UpdateAtCaretPosition(e.NewPosition)
    End Sub
    
    Private Sub UpdateAtCaretPosition(ByVal caretPosition As CaretPosition)
        CurrentChar = caretPosition.Point.GetPoint(SourceBuffer, caretPosition.Affinity)
    
        If Not CurrentChar.HasValue Then
            Exit Sub
        End If
    
        RaiseEvent TagsChanged(Me, New SnapshotSpanEventArgs(New SnapshotSpan(SourceBuffer.CurrentSnapshot, 0, SourceBuffer.CurrentSnapshot.Length)))
    End Sub
    
  8. Implementar o GetTags método para corresponder chaves a quando o caractere atual for uma chave de abertura ou quando o caractere anterior for um colchete de fechamento, como no Visual Studio.Implement the GetTags method to match braces either when the current character is an open brace or when the previous character is a close brace, as in Visual Studio. Quando a correspondência for encontrada, esse método cria uma instância de duas marcas, um para a chave de abertura e outra para o colchete de fechamento.When the match is found, this method instantiates two tags, one for the open brace and one for the close brace.

    public IEnumerable<ITagSpan<TextMarkerTag>> GetTags(NormalizedSnapshotSpanCollection spans)
    {
        if (spans.Count == 0)   //there is no content in the buffer
            yield break;
    
        //don't do anything if the current SnapshotPoint is not initialized or at the end of the buffer
        if (!CurrentChar.HasValue || CurrentChar.Value.Position >= CurrentChar.Value.Snapshot.Length)
            yield break;
    
        //hold on to a snapshot of the current character
        SnapshotPoint currentChar = CurrentChar.Value;
    
        //if the requested snapshot isn't the same as the one the brace is on, translate our spans to the expected snapshot
        if (spans[0].Snapshot != currentChar.Snapshot)
        {
            currentChar = currentChar.TranslateTo(spans[0].Snapshot, PointTrackingMode.Positive);
        }
    
        //get the current char and the previous char
        char currentText = currentChar.GetChar();
        SnapshotPoint lastChar = currentChar == 0 ? currentChar : currentChar - 1; //if currentChar is 0 (beginning of buffer), don't move it back
        char lastText = lastChar.GetChar();
        SnapshotSpan pairSpan = new SnapshotSpan();
    
        if (m_braceList.ContainsKey(currentText))   //the key is the open brace
        {
            char closeChar;
            m_braceList.TryGetValue(currentText, out closeChar);
            if (BraceMatchingTagger.FindMatchingCloseChar(currentChar, currentText, closeChar, View.TextViewLines.Count, out pairSpan) == true)
            {
                yield return new TagSpan<TextMarkerTag>(new SnapshotSpan(currentChar, 1), new TextMarkerTag("blue"));
                yield return new TagSpan<TextMarkerTag>(pairSpan, new TextMarkerTag("blue"));
            }
        }
        else if (m_braceList.ContainsValue(lastText))    //the value is the close brace, which is the *previous* character 
        {
            var open = from n in m_braceList
                       where n.Value.Equals(lastText)
                       select n.Key;
            if (BraceMatchingTagger.FindMatchingOpenChar(lastChar, (char)open.ElementAt<char>(0), lastText, View.TextViewLines.Count, out pairSpan) == true)
            {
                yield return new TagSpan<TextMarkerTag>(new SnapshotSpan(lastChar, 1), new TextMarkerTag("blue"));
                yield return new TagSpan<TextMarkerTag>(pairSpan, new TextMarkerTag("blue"));
            }
        }
    }
    
    Public Function GetTags(ByVal spans As NormalizedSnapshotSpanCollection) As IEnumerable(Of ITagSpan(Of TextMarkerTag)) Implements ITagger(Of Microsoft.VisualStudio.Text.Tagging.TextMarkerTag).GetTags
        If spans.Count = 0 Then
            'there is no content in the buffer
            Exit Function
        End If
    
        'don't do anything if the current SnapshotPoint is not initialized or at the end of the buffer
        If Not CurrentChar.HasValue OrElse CurrentChar.Value.Position >= CurrentChar.Value.Snapshot.Length Then
            Exit Function
        End If
    
        'hold on to a snapshot of the current character
        Dim currentChar__1 As SnapshotPoint = CurrentChar.Value
    
        'if the requested snapshot isn't the same as the one the brace is on, translate our spans to the expected snapshot
        If spans(0).Snapshot IsNot currentChar__1.Snapshot Then
            currentChar__1 = currentChar__1.TranslateTo(spans(0).Snapshot, PointTrackingMode.Positive)
        End If
    
        'get the current char and the previous char
        Dim currentText As Char = currentChar__1.GetChar()
        Dim lastChar As SnapshotPoint = If(CInt(currentChar__1) = 0, currentChar__1, currentChar__1 - 1)
        'if currentChar is 0 (beginning of buffer), don't move it back
        Dim lastText As Char = lastChar.GetChar()
        Dim pairSpan As New SnapshotSpan()
    
        If m_braceList.ContainsKey(currentText) Then
            'the key is the open brace
            Dim closeChar As Char
            m_braceList.TryGetValue(currentText, closeChar)
            If BraceMatchingTagger.FindMatchingCloseChar(currentChar__1, currentText, closeChar, View.TextViewLines.Count, pairSpan) = True Then
                Exit Function
            End If
        ElseIf m_braceList.ContainsValue(lastText) Then
            'the value is the close brace, which is the *previous* character 
            Dim open = From n In m_braceList _
                Where n.Value.Equals(lastText) _
                Select n.Key
            If BraceMatchingTagger.FindMatchingOpenChar(lastChar, CChar(open.ElementAt(0)), lastText, View.TextViewLines.Count, pairSpan) = True Then
                Exit Function
            End If
        End If
    End Function
    
  9. Os seguintes métodos privados encontrar a chave correspondente em qualquer nível de aninhamento.The following private methods find the matching brace at any level of nesting. O primeiro método encontra o caractere de fechamento que corresponde ao caractere aberto:The first method finds the close character that matches the open character:

    private static bool FindMatchingCloseChar(SnapshotPoint startPoint, char open, char close, int maxLines, out SnapshotSpan pairSpan)
    {
        pairSpan = new SnapshotSpan(startPoint.Snapshot, 1, 1);
        ITextSnapshotLine line = startPoint.GetContainingLine();
        string lineText = line.GetText();
        int lineNumber = line.LineNumber;
        int offset = startPoint.Position - line.Start.Position + 1;
    
        int stopLineNumber = startPoint.Snapshot.LineCount - 1;
        if (maxLines > 0)
            stopLineNumber = Math.Min(stopLineNumber, lineNumber + maxLines);
    
        int openCount = 0;
        while (true)
        {
            //walk the entire line
            while (offset < line.Length)
            {
                char currentChar = lineText[offset];
                if (currentChar == close) //found the close character
                {
                    if (openCount > 0)
                    {
                        openCount--;
                    }
                    else    //found the matching close
                    {
                        pairSpan = new SnapshotSpan(startPoint.Snapshot, line.Start + offset, 1);
                        return true;
                    }
                }
                else if (currentChar == open) // this is another open
                {
                    openCount++;
                }
                offset++;
            }
    
            //move on to the next line
            if (++lineNumber > stopLineNumber)
                break;
    
            line = line.Snapshot.GetLineFromLineNumber(lineNumber);
            lineText = line.GetText();
            offset = 0;
        }
    
        return false;
    }
    
    Private Shared Function FindMatchingCloseChar(ByVal startPoint As SnapshotPoint, ByVal open As Char, ByVal close As Char, ByVal maxLines As Integer, ByRef pairSpan As SnapshotSpan) As Boolean
        pairSpan = New SnapshotSpan(startPoint.Snapshot, 1, 1)
        Dim line As ITextSnapshotLine = startPoint.GetContainingLine()
        Dim lineText As String = line.GetText()
        Dim lineNumber As Integer = line.LineNumber
        Dim offset As Integer = startPoint.Position - line.Start.Position + 1
    
        Dim stopLineNumber As Integer = startPoint.Snapshot.LineCount - 1
        If maxLines > 0 Then
            stopLineNumber = Math.Min(stopLineNumber, lineNumber + maxLines)
        End If
    
        Dim openCount As Integer = 0
        While True
            'walk the entire line
            While offset < line.Length
                Dim currentChar As Char = lineText(offset)
                If currentChar = close Then
                    'found the close character
                    If openCount > 0 Then
                        openCount -= 1
                    Else
                        'found the matching close
                        pairSpan = New SnapshotSpan(startPoint.Snapshot, line.Start + offset, 1)
                        Return True
                    End If
                ElseIf currentChar = open Then
                    ' this is another open
                    openCount += 1
                End If
                offset += 1
            End While
    
            'move on to the next line
            If System.Threading.Interlocked.Increment(lineNumber) > stopLineNumber Then
                Exit While
            End If
    
            line = line.Snapshot.GetLineFromLineNumber(lineNumber)
            lineText = line.GetText()
            offset = 0
        End While
    
        Return False
    End Function
    
  10. O método auxiliar a seguir localiza o caractere aberto que corresponde a um caractere de fechamento:The following helper method finds the open character that matches a close character:

    private static bool FindMatchingOpenChar(SnapshotPoint startPoint, char open, char close, int maxLines, out SnapshotSpan pairSpan)
    {
        pairSpan = new SnapshotSpan(startPoint, startPoint);
    
        ITextSnapshotLine line = startPoint.GetContainingLine();
    
        int lineNumber = line.LineNumber;
        int offset = startPoint - line.Start - 1; //move the offset to the character before this one
    
        //if the offset is negative, move to the previous line
        if (offset < 0)
        {
            line = line.Snapshot.GetLineFromLineNumber(--lineNumber);
            offset = line.Length - 1;
        }
    
        string lineText = line.GetText();
    
        int stopLineNumber = 0;
        if (maxLines > 0)
            stopLineNumber = Math.Max(stopLineNumber, lineNumber - maxLines);
    
        int closeCount = 0;
    
        while (true)
        {
            // Walk the entire line
            while (offset >= 0)
            {
                char currentChar = lineText[offset];
    
                if (currentChar == open)
                {
                    if (closeCount > 0)
                    {
                        closeCount--;
                    }
                    else // We've found the open character
                    {
                        pairSpan = new SnapshotSpan(line.Start + offset, 1); //we just want the character itself
                        return true;
                    }
                }
                else if (currentChar == close)
                {
                    closeCount++;
                }
                offset--;
            }
    
            // Move to the previous line
            if (--lineNumber < stopLineNumber)
                break;
    
            line = line.Snapshot.GetLineFromLineNumber(lineNumber);
            lineText = line.GetText();
            offset = line.Length - 1;
        }
        return false;
    }
    
    Private Shared Function FindMatchingOpenChar(ByVal startPoint As SnapshotPoint, ByVal open As Char, ByVal close As Char, ByVal maxLines As Integer, ByRef pairSpan As SnapshotSpan) As Boolean
        pairSpan = New SnapshotSpan(startPoint, startPoint)
    
        Dim line As ITextSnapshotLine = startPoint.GetContainingLine()
    
        Dim lineNumber As Integer = line.LineNumber
        Dim offset As Integer = startPoint - line.Start - 1
        'move the offset to the character before this one
        'if the offset is negative, move to the previous line
        If offset < 0 Then
            line = line.Snapshot.GetLineFromLineNumber(System.Threading.Interlocked.Decrement(lineNumber))
            offset = line.Length - 1
        End If
    
        Dim lineText As String = line.GetText()
    
        Dim stopLineNumber As Integer = 0
        If maxLines > 0 Then
            stopLineNumber = Math.Max(stopLineNumber, lineNumber - maxLines)
        End If
    
        Dim closeCount As Integer = 0
    
        While True
            ' Walk the entire line
            While offset >= 0
                Dim currentChar As Char = lineText(offset)
    
                If currentChar = open Then
                    If closeCount > 0 Then
                        closeCount -= 1
                    Else
                        ' We've found the open character
                        pairSpan = New SnapshotSpan(line.Start + offset, 1)
                        'we just want the character itself
                        Return True
                    End If
                ElseIf currentChar = close Then
                    closeCount += 1
                End If
                offset -= 1
            End While
    
            ' Move to the previous line
            If System.Threading.Interlocked.Decrement(lineNumber) < stopLineNumber Then
                Exit While
            End If
    
            line = line.Snapshot.GetLineFromLineNumber(lineNumber)
            lineText = line.GetText()
            offset = line.Length - 1
        End While
        Return False
    End Function
    

Implementar um provedor de marcador de correspondência de chavesImplement a brace matching tagger provider

Além de implementar um marcador, você também deve implementar e exportar um provedor de marcador.In addition to implementing a tagger, you must also implement and export a tagger provider. Nesse caso, o tipo do provedor de conteúdo é "text".In this case, the content type of the provider is "text". Portanto, correspondência de chaves será exibido em todos os tipos de arquivos de texto, mas uma implementação mais completa se aplica somente a um tipo específico de conteúdo a correspondência de chaves.So, brace matching will appear in all types of text files, but a fuller implementation applies brace matching only to a specific content type.

Para implementar um provedor de marcador de correspondência de chavesTo implement a brace matching tagger provider

  1. Declarar um provedor de marcador que herda de IViewTaggerProvider, nomeie-o BraceMatchingTaggerProvider e exportá-lo com um ContentTypeAttribute de "text" e um TagTypeAttribute de TextMarkerTag.Declare a tagger provider that inherits from IViewTaggerProvider, name it BraceMatchingTaggerProvider, and export it with a ContentTypeAttribute of "text" and a TagTypeAttribute of TextMarkerTag.

    [Export(typeof(IViewTaggerProvider))]
    [ContentType("text")]
    [TagType(typeof(TextMarkerTag))]
    internal class BraceMatchingTaggerProvider : IViewTaggerProvider
    
    <Export(GetType(IViewTaggerProvider))> _
    <ContentType("text")> _
    <TagType(GetType(TextMarkerTag))> _
    Friend Class BraceMatchingTaggerProvider
        Implements IViewTaggerProvider
    
  2. Implementar o CreateTagger método para instanciar um BraceMatchingTagger.Implement the CreateTagger method to instantiate a BraceMatchingTagger.

    public ITagger<T> CreateTagger<T>(ITextView textView, ITextBuffer buffer) where T : ITag
    {
        if (textView == null)
            return null;
    
        //provide highlighting only on the top-level buffer
        if (textView.TextBuffer != buffer)
            return null;
    
        return new BraceMatchingTagger(textView, buffer) as ITagger<T>;
    }
    
    Public Function CreateTagger(Of T As ITag)(ByVal textView As ITextView, ByVal buffer As ITextBuffer) As ITagger(Of T) Implements IViewTaggerProvider.CreateTagger
        If textView Is Nothing Then
            Return Nothing
        End If
    
        'provide highlighting only on the top-level buffer
        If textView.TextBuffer IsNot buffer Then
            Return Nothing
        End If
    
        Return TryCast(New BraceMatchingTagger(textView, buffer), ITagger(Of T))
    End Function
    

Compilar e testar o códigoBuild and test the code

Para testar esse código, compile a solução BraceMatchingTest e executá-lo na instância experimental.To test this code, build the BraceMatchingTest solution and run it in the experimental instance.

Para compilar e testar a solução BraceMatchingTestTo build and test BraceMatchingTest solution

  1. Compile a solução.Build the solution.

  2. Quando você executar esse projeto no depurador, uma segunda instância do Visual Studio é iniciada.When you run this project in the debugger, a second instance of Visual Studio is started.

  3. Crie um arquivo de texto e digite algum texto que inclui chaves correspondentes.Create a text file and type some text that includes matching braces.

    hello {  
    goodbye}  
    
    {}  
    
    {hello}  
    
  4. Quando você posiciona o cursor antes de uma chave de abertura, essa chave e o colchete de fechamento correspondente deve ser realçado.When you position the caret before an open brace, both that brace and the matching close brace should be highlighted. Quando você posiciona o cursor logo após o colchete de fechamento, essa chave e a chave de abertura correspondente deve ser realçada.When you position the cursor just after the close brace, both that brace and the matching open brace should be highlighted.

Consulte tambémSee also

Passo a passo: Vincular um tipo de conteúdo para uma extensão de nome de arquivoWalkthrough: Link a content type to a file name extension