Share via


Visual Studio Code 편집기 확장성 사용

Visual Studio 편집기에서는 해당 기능에 추가되는 확장을 지원합니다. 예를 들어 기존 언어로 코드를 삽입하고 수정하는 확장이 있습니다.

새 Visual Studio 확장성 모델의 초기 릴리스에서는 다음 기능만 지원됩니다.

  • 열고 닫는 텍스트 보기를 수신 대기합니다.
  • 텍스트 보기(편집기) 상태 변경을 수신 대기합니다.
  • 문서의 텍스트와 선택 영역/캐럿 위치를 읽습니다.
  • 텍스트 편집 및 선택/캐리트 변경 수행
  • 새 문서 형식 정의
  • 새 텍스트 보기 여백을 사용하여 텍스트 보기 확장

Visual Studio 편집기는 일반적으로 모든 유형의 텍스트 파일(문서라고 함)을 편집하는 기능을 나타냅니다. 편집을 위해 개별 파일을 열 수 있으며 열려 있는 편집기 창을 TextView/>라고 합니다.

편집기 개체 모델은 편집기 개념에 설명되어 있습니다.

시작하기

확장 코드는 다양한 진입점(사용자가 Visual Studio와 상호 작용할 때 발생하는 상황)에 대한 응답으로 실행되도록 구성할 수 있습니다. 편집기 확장성은 현재 수신기, EditorExtensibility Service 개체 및 명령의 세 가지 진입점을 지원합니다.

이벤트 수신기는 편집기 창에서 특정 작업이 발생할 때 트리거되며 코드로 TextView표시됩니다. 예를 들어 사용자가 편집 TextViewChanged 기에 무언가를 입력하면 이벤트가 발생합니다. 편집기 창이 열리거나 닫히고 TextViewOpenedTextViewClosed 이벤트가 발생하는 경우

편집기 서비스 개체는 텍스트 편집 수행과 같은 실시간 편집기 기능을 노출하는 클래스의 EditorExtensibility 인스턴스입니다.

메뉴, 상황에 맞는 메뉴 또는 도구 모음에 배치할 수 있는 항목을 클릭하여 사용자가 명령을 시작합니다.

텍스트 보기 수신기 추가

ITextViewChangedListener와ITextViewOpenClosedListener의 두 가지 유형의 수신기가 있습니다. 이러한 수신기를 함께 사용하여 텍스트 편집기의 열기, 닫기 및 수정을 관찰할 수 있습니다.

그런 다음 ExtensionPart 기본 클래스 및 ITextViewChangedListener, ITextViewOpenClosedListener, 또는 둘 다를 구현하는 새 클래스를 만들고 VisualStudioContribution 특성을 추가합니다.

그런 다음, ITextViewChangedListenerITextViewOpenClosedListener에 필요한 대로 TextViewExtensionConfiguration 속성을 구현하여 C# 파일을 편집할 때 수신기를 적용합니다.

public TextViewExtensionConfiguration TextViewExtensionConfiguration => new()
{
    AppliesTo = new[] { DocumentFilter.FromDocumentType("CSharp") },
};

다른 프로그래밍 언어 및 파일 형식에 사용할 수 있는 문서 형식은 이 문서의 뒷부분에 나와 있으며 필요한 경우 사용자 지정 파일 형식도 정의할 수 있습니다.

두 수신기를 모두 구현하기로 결정한 경우 완성된 클래스 선언은 다음과 같아야 합니다.

  [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") },
      };
      ...

ITextViewOpenClosedListener와ITextViewChangedListener 모두 TextViewExtensionConfiguration 속성을 선언하므로 구성은 두 수신기에 모두 적용됩니다.

확장을 실행하면 다음이 표시됩니다.

이러한 각 메서드는 사용자가 작업을 호출할 때 텍스트 보기 및 텍스트 문서의 상태를 포함하는 ITextViewSnapshot 과 IDE가 보류 중인 작업을 취소하려고 할 때 사용할 IsCancellationRequested == true CancellationToken을 전달합니다.

확장과 관련된 시기 정의

확장은 일반적으로 지원되는 특정 문서 유형 및 시나리오와만 관련이 있으므로 해당 적용 가능성을 명확하게 정의하는 것이 중요합니다. AppliesTo configuration)을 여러 가지 방법으로 사용하여 확장의 적용 가능성을 명확하게 정의할 수 있습니다. 확장 프로그램에서 지원하는 코드 언어와 같은 파일 형식을 지정하거나 파일 이름 또는 경로에 따라 패턴에 일치하여 확장 프로그램의 적용 가능성을 더욱 구체화할 수 있습니다.

AppliesTo 구성을 사용하여 프로그래밍 언어 지정

AppliesTo 구성은 확장이 활성화되어야 하는 프로그래밍 언어 시나리오를 나타냅니다. 문서 형식이 Visual Studio에 기본 제공되는 언어의 잘 알려진 이름이거나 Visual Studio 확장에 정의된 사용자 지정으로 AppliesTo = new[] { DocumentFilter.FromDocumentType("CSharp") }로 작성 됩니다.

잘 알려진 문서 유형은 다음 표에 나와 있습니다.

DocumentType 설명
"CSharp" C#
"C/C++" C, C++, 헤더 및 IDL
"TypeScript" TypeScript and JavaScript 타입 언어
"HTML" HTML
"JSON" JSON
"text" "text"에서 내림차순인 "code"의 계층적 하위 항목을 포함하는 텍스트 파일입니다.
"코드" C, C++, C#, and so on.

DocumentType은 계층적입니다. 즉, C# 및 C++는 모두 "코드"에서 내림차순이므로 "코드"를 선언하면 모든 코드 언어, C#, C, C++등에 대해 확장이 활성화됩니다.

새 문서 유형 정의

예를 들어 확장 프로젝트의 모든 클래스에 정적 DocumentTypeConfiguration 속성을 추가하고 속성을 특성으로 VisualStudioContribution 표시하여 사용자 지정 코드 언어를 지원하기 위해 새 문서 형식을 정의할 수 있습니다.

DocumentTypeConfiguration 를 사용하면 새 문서 형식을 정의하고, 하나 이상의 다른 문서 형식을 상속하도록 지정하고, 파일 형식을 식별하는 데 사용되는 하나 이상의 파일 확장자를 지정할 수 있습니다.

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

문서 형식 정의는 레거시 Visual Studio 확장성에서 제공하는 콘텐츠 형식 정의와 병합되므로 추가 파일 확장자를 기존 문서 형식에 매핑할 수 있습니다.

문서 선택기들

DocumentFilter.FromDocumentType 외에도 DocumentFilter.FromGlobPattern 을 사용하면 문서의 파일 경로가 glob(wild카드) 패턴과 일치하는 경우에만 활성화하여 확장의 적용 가능성을 추가로 제한할 수 있습니다.

[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),
        },
    };

pattern 매개 변수는 문서의 절대 경로와 일치하는 glob 패턴을 나타냅니다.

Glob 패턴에는 다음 구문이 있을 수 있습니다.

  • * 경로 세그먼트에서 0개 이상의 문자와 일치하려면
  • ? 경로 세그먼트의 한 문자에 일치하려면
  • ** 없음을 포함하여 경로 세그먼트의 개수와 일치하려면
  • {} 조건을 그룹화하려면(예: **​/*.{ts,js} 모든 TypeScript 및 JavaScript 파일과 일치)
  • [] 경로 세그먼트에서 일치시킬 문자 범위를 선언하려면(예 example.[0-9] : 일치 example.0할 문자 범위 , example.1... )
  • [!...] 경로 세그먼트에서 일치시킬 문자 범위를 부정하려면(예 example.[!0-9] : 일치 example.a할 문자는 example.b있지만 일치하지 않음 example.0)

백슬래시(\)는 glob 패턴 내에서 유효하지 않습니다. glob 패턴을 만들 때 백슬래시를 슬래시로 변환해야 합니다.

편집기 기능 액세스

편집기 확장 클래스는 ExtensionPart에서 상속됩니다. 이 ExtensionPart 클래스는 확장성 속성을 노출합니다. 이 속성을 사용하여 EditorExtensibility 개체의 인스턴스를 요청할 수 있습니다. 이 개체를 사용하여 편집 수행과 같은 실시간 편집기 기능에 액세스할 수 있습니다.

EditorExtensibility editorService = this.Extensibility.Editor();

명령 내의 편집기 상태 액세스

ExecuteCommandAsync()Command 에서 명령어가 호출된 시점의 IDE 상태 스냅샷을 포함하는 IClientContext 이 전달됩니다. 비동기 GetActiveTextViewAsync 메서드를 호출하여 EditorExtensibility 개체에서 가져오는 ITextViewSnapshot 인터페이스를 통해 현재 문서에 액세스할 수 있습니다.

using ITextViewSnapshot textView = await this.Extensibility.Editor().GetActiveTextViewAsync(clientContext, cancellationToken);

일단 ITextViewSnapshot이 있으면 편집기 상태에 액세스할 수 있습니다. ITextViewSnapshot 는 특정 시점에 편집기 상태를 변경할 수 없는 보기이므로 편집기 개체 모델의 다른 인터페이스를 사용하여 편집해야 합니다.

확장에서 텍스트 문서 변경

편집, 즉, Visual Studio 편집기에서 열린 텍스트 문서의 변경 내용은 사용자 상호 작용, Visual Studio의 스레드(예: 언어 서비스 및 기타 확장)에서 발생할 수 있습니다. 실시간으로 발생하는 문서 텍스트의 변경 내용을 처리하도록 확장이 준비되어야 합니다.

비동기 디자인 패턴을 사용하여 Visual Studio IDE 프로세스와 통신하는 기본 Visual Studio IDE 프로세스 외부에서 실행되는 확장입니다. 즉, C#의 키워드(keyword) 표시 async 되고 메서드 이름의 접미사로 강화된 Async 비동기 메서드 호출을 사용합니다. 비동기성은 사용자 작업에 응답할 것으로 예상되는 편집기의 컨텍스트에서 상당한 이점입니다. 기존 동기 API 호출은 예상보다 오래 걸리는 경우 사용자 입력에 대한 응답을 중지하고 API 호출이 완료될 때까지 지속되는 UI 고정을 만듭니다. 최신 대화형 애플리케이션에 대한 사용자의 기대는 텍스트 편집기가 항상 반응성이 기본 작동을 차단하지 않는다는 것입니다. 따라서 확장을 비동기식으로 사용하는 것은 사용자의 기대를 충족하는 데 필수적입니다.

비동기 및 await를 사용한 비동기 프로그래밍의 비동기 프로그래밍에 대해 자세히 알아봅니다.

새 Visual Studio 확장성 모델에서 확장은 사용자를 기준으로 하는 두 번째 클래스입니다. 편집기 또는 텍스트 문서를 직접 수정할 수 없습니다. 모든 상태 변경은 비동기적이며 협조적이며 Visual Studio IDE는 확장을 대신하여 요청된 변경을 수행합니다. 확장은 문서 또는 텍스트 보기의 특정 버전에 대해 하나 이상의 변경 내용을 요청할 수 있지만 문서의 해당 영역이 변경된 경우와 같이 확장의 변경 내용이 거부될 수 있습니다.

EditorExtensibility에 대한 EditAsync() 메서드를 사용하여 편집이 요청됩니다.

레거시 Visual Studio 확장에 익숙한 경우 ITextDocumentEditorITextBufferITextDocument에서 메서드를 변경하는 상태와 거의 동일하며 대부분의 동일한 기능을 지원합니다.

MutationResult result = await this.Extensibility.Editor().EditAsync(
batch =>
{
    var editor = document.AsEditable(batch);
    editor.Replace(textView.Selection.Extent, newGuidString);
},
cancellationToken);

편집이 잘못되지 않도록 편집기 확장의 편집은 다음과 같이 적용됩니다.

  1. 확장은 문서의 최신 버전에 따라 편집을 요청합니다.
  2. 해당 요청에는 하나 이상의 텍스트 편집, 캐리트 위치 변경 등이 포함될 수 있습니다. 구현하는 모든 IEditable 형식은 ITextViewSnapshotITextDocumentSnapshot을 포함하여 단일 EditAsync() 요청에서 변경할 수 있습니다. 편집은 편집기에서 수행되며, 이 작업은 을 통해 AsEditable()특정 클래스에서 요청할 수 있습니다.
  3. 편집 요청은 Visual Studio IDE로 전송되며, 요청이 만들어진 버전 이후 변경되는 개체가 변경되지 않은 경우에만 성공합니다. 문서가 변경된 경우 변경 내용이 거부될 수 있으므로 확장이 최신 버전에서 다시 시도해야 합니다. 돌연변이 연산의 결과는 에 result저장됩니다.
  4. 편집은 원자성으로 적용되며, 이는 다른 실행 스레드에서 중단되지 않음을 의미합니다. 가장 좋은 방법은 좁은 시간 프레임 내에 발생해야 하는 모든 변경 내용을 단일 EditAsync() 호출로 수행하여 사용자 편집에서 발생하는 예기치 않은 동작 또는 편집 간에 발생하는 언어 서비스 작업(예: 확장 편집이 캐럿 이동 Roslyn C#과 인터리브되는 경우)을 줄이는 것입니다.

비동기 실행

ITextViewSnapshot.GetTextDocumentAsync 는 Visual Studio 확장에서 텍스트 문서의 복사본을 엽니다. 확장은 별도의 프로세스에서 실행되므로 모든 확장 상호 작용은 비동기적이고 협조적이며 다음과 같은 몇 가지 주의 사항이 있습니다.

주의

GetTextDocumentAsync 사용자가 만든 이후 많은 변경을 수행한 경우 Visual Studio 클라이언트에서 더 이상 캐시되지 않을 수 있으므로 이전 ITextDocument버전에서 호출하면 실패할 수 있습니다. 이러한 이유로 나중에 문서에 액세스하기 위해 저장 ITextView 하려는 경우 실패를 허용할 수 없는 경우 즉시 호출 GetTextDocumentAsync 하는 것이 좋습니다. 이렇게 하면 문서의 해당 버전에 대한 텍스트 콘텐츠를 확장에 가져와 해당 버전의 복사본이 만료되기 전에 확장으로 전송되도록 합니다.

주의

GetTextDocumentAsync 또는 MutateAsync 사용자가 문서를 닫으면 실패할 수 있습니다.

동시 실행

⚠️ 편집기 확장은 때때로 동시에 실행될 수 있습니다.

초기 릴리스에는 편집기 확장 코드를 동시에 실행할 수 있는 알려진 문제가 있습니다. 각 비동기 메서드는 올바른 순서로 호출되도록 보장되지만 첫 번째 await 메서드 이후의 연속은 인터리브될 수 있습니다. 확장이 실행 순서에 의존하는 경우 이 문제가 해결될 때까지 순서를 유지하기 위해 들어오는 요청 큐를 기본 고려하는 것이 좋습니다.

자세한 내용은 StreamJsonRpc 기본 순서 및 동시성을 참조하세요.

새 여백으로 Visual Studio 편집기 확장

확장은 Visual Studio 편집기에서 새 텍스트 보기 여백을 추가할 수 있습니다. 텍스트 뷰 여백은 네 면 중 하나의 텍스트 보기에 연결된 사각형 UI 컨트롤입니다.

텍스트 보기 여백은 여백 컨테이너( ContainerMarginPlacement.KnownValues참조)에 배치되고 다른 여백에 상대적으로 전후로 정렬됩니다( MarginPlacement.KnownValues참조).

텍스트 보기 여백 공급자는 ITextViewMarginProvider 인터페이스를 구현하고, TextViewMarginProviderConfiguration을 구현하여 제공하는 여백을 구성하고, 활성화될 때 CreateVisualElementAsync를 통해 여백에 호스트되는 UI 컨트롤을 제공합니다.

VisualStudio.Extensibility의 확장은 Visual Studio에서 처리되지 않을 수 있으므로 WPF를 텍스트 보기 여백 콘텐츠의 프레젠테이션 계층으로 직접 사용할 수 없습니다. 대신 텍스트 보기 여백에 콘텐츠를 제공하려면 RemoteUserControl 및 해당 컨트롤에 해당하는 데이터 템플릿을 만들어야 합니다. 아래에는 몇 가지 간단한 예제가 있지만 텍스트 보기 여백 UI 콘텐츠를 만들 때 원격 UI 설명서를 읽는 것이 좋습니다.

/// <summary>
/// Configures the margin to be placed to the left of built-in Visual Studio line number margin.
/// </summary>
public TextViewMarginProviderConfiguration TextViewMarginProviderConfiguration => new(marginContainer: ContainerMarginPlacement.KnownValues.BottomRightCorner)
{
    Before = new[] { MarginPlacement.KnownValues.RowMargin },
};

/// <summary>
/// Creates a remotable visual element representing the content of the margin.
/// </summary>
public async Task<IRemoteUserControl> CreateVisualElementAsync(ITextViewSnapshot textView, CancellationToken cancellationToken)
{
    var documentSnapshot = await textView.GetTextDocumentAsync(cancellationToken);
    var dataModel = new WordCountData();
    dataModel.WordCount = CountWords(documentSnapshot);
    this.dataModels[textView.Uri] = dataModel;
    return new MyMarginContent(dataModel);
}

텍스트 뷰 여백 공급자는 여백 배치를 구성하는 것 외에도 GridCellLengthGridUnitType 속성을 사용하여 여백을 배치해야 하는 그리드 셀의 크기를 구성할 수도 있습니다.

텍스트 보기 여백은 일반적으로 텍스트 보기와 관련된 일부 데이터(예: 현재 줄 번호 또는 오류 수)를 시각화하므로 대부분의 텍스트 보기 여백 공급자는 텍스트 보기 이벤트를 수신 대기하여 텍스트 보기 열기, 텍스트 보기 닫기 및 사용자 입력에 반응하려고 합니다.

Visual Studio는 사용자가 여는 적용 가능한 텍스트 보기 수에 관계없이 텍스트 보기 여백 공급자의 인스턴스를 하나만 만들므로 여백에 상태 저장 데이터가 표시되면 공급자는 현재 열려 있는 텍스트 보기의 상태를 유지해야 합니다.

자세한 내용은 Word 개수 여백 샘플을 참조 하세요.

콘텐츠를 텍스트 보기 줄에 맞춰야 하는 세로 텍스트 보기 여백은 아직 지원되지 않습니다.

편집기 개념의 편집기 인터페이스 및 형식에 대해 알아봅니다.

간단한 편집기 기반 확장에 대한 샘플 코드를 검토합니다.

고급 사용자는 편집기 RPC 지원에 대해 알아볼 수 있습니다.