연습: 전구 제안 표시
전구는 Visual Studio 편집기의 아이콘으로, 일련의 작업(예: 기본 제공 코드 분석기 또는 코드 리팩터링으로 식별된 문제에 대한 수정 사항)을 표시하도록 확장됩니다.
Visual C# 및 Visual Basic 편집기에서 .NET Compiler Platform("Roslyn")을 사용하여 전구를 자동으로 표시하는 작업으로 자체 코드 분석기를 작성하고 패키징할 수도 있습니다. 자세한 내용은 다음을 참조하세요.
방법: Visual Basic 진단 및 코드 수정 작성
C++와 같은 다른 언어도 몇 가지 빠른 작업에 대한 전구를 제공합니다(예: 해당 함수의 스텁 구현을 생성하라는 제안).
전구의 모양은 다음과 같습니다. Visual Basic 또는 Visual C# 프로젝트에서 유효하지 않은 변수 이름 아래에 빨간색 물결선이 나타납니다. 잘못된 식별자를 마우스로 가리키면 커서 근처에 전구가 나타납니다.
전구 옆에 있는 아래쪽 화살표를 클릭하면 일련의 제안된 작업이 선택한 작업의 미리 보기와 함께 나타납니다. 이 경우 작업을 실행할 경우 코드에 적용된 변경 내용이 표시됩니다.
전구를 사용하여 나만의 제안된 작업을 제공할 수 있습니다. 예를 들어 여는 중괄호를 새 줄로 이동하거나 이전 줄의 끝으로 이동하는 작업을 제공할 수 있습니다. 다음 연습에서는 현재 단어에 표시되고 두 개의 제안 동작(대문자로 변환 및 를 소문자로 변환)이 있는 전구를 만드는 방법을 보여 줍니다.
MEF(Managed Extensibility Framework) 프로젝트 만들기
C# VSIX 프로젝트를 만듭니다. (새 프로젝트 대화 상자에서 Visual C#/확장성, VSIX 프로젝트를 차례로 선택합니다.) 솔루션 이름을
LightBulbTest
로 지정합니다.프로젝트에 편집기 분류자 항목 템플릿을 추가합니다. 자세한 내용은 편집기 항목 템플릿을 사용하여 확장 만들기를 참조하세요.
기존 클래스 파일을 삭제합니다.
프로젝트에 다음 참조를 추가하고 로컬 복사를
False
로 설정합니다.Microsoft.VisualStudio.Language.Intellisense
새 클래스 파일을 추가하고 이름을 LightBulbTest로 지정합니다.
다음 using 지시문을 추가합니다.
using System; using System.Linq; using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.VisualStudio.Language.Intellisense; using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Editor; using Microsoft.VisualStudio.Text.Operations; using Microsoft.VisualStudio.Utilities; using System.ComponentModel.Composition; using System.Threading;
전구 원본 공급자 구현
LightBulbTest.cs 클래스 파일에서 LightBulbTest 클래스를 삭제합니다. ISuggestedActionsSourceProvider를 구현하는 TestSuggestedActionsSourceProvider라는 클래스를 추가합니다. 이름이 Test Suggested Actions이고 ContentTypeAttribute가 "text"인 파일을 내보냅니다.
[Export(typeof(ISuggestedActionsSourceProvider))] [Name("Test Suggested Actions")] [ContentType("text")] internal class TestSuggestedActionsSourceProvider : ISuggestedActionsSourceProvider
원본 공급자 클래스 내에서 ITextStructureNavigatorSelectorService를 가져와 속성으로 추가합니다.
[Import(typeof(ITextStructureNavigatorSelectorService))] internal ITextStructureNavigatorSelectorService NavigatorService { get; set; }
ISuggestedActionsSource 개체를 반환하도록 CreateSuggestedActionsSource 메서드를 구현합니다. 소스는 다음 섹션에 설명되어 있습니다.
public ISuggestedActionsSource CreateSuggestedActionsSource(ITextView textView, ITextBuffer textBuffer) { if (textBuffer == null || textView == null) { return null; } return new TestSuggestedActionsSource(this, textView, textBuffer); }
ISuggestedActionSource 구현
제안된 작업 원본은 제안된 작업 집합을 수집하고 올바른 컨텍스트에 추가하는 역할을 합니다. 이 경우 컨텍스트는 현재 단어이며 제안된 작업은 다음 섹션에 설명된 UpperCaseSuggestedAction 및 LowerCaseSuggestedAction입니다.
ISuggestedActionsSource를 구현하는 TestSuggestedActionsSource 클래스를 추가합니다.
internal class TestSuggestedActionsSource : ISuggestedActionsSource
제안된 작업 원본 공급자, 텍스트 버퍼 및 텍스트 보기에 대한 프라이빗 읽기 전용 필드를 추가합니다.
private readonly TestSuggestedActionsSourceProvider m_factory; private readonly ITextBuffer m_textBuffer; private readonly ITextView m_textView;
프라이빗 필드를 설정하는 생성자를 추가합니다.
public TestSuggestedActionsSource(TestSuggestedActionsSourceProvider testSuggestedActionsSourceProvider, ITextView textView, ITextBuffer textBuffer) { m_factory = testSuggestedActionsSourceProvider; m_textBuffer = textBuffer; m_textView = textView; }
현재 커서 아래에 있는 단어를 반환하는 프라이빗 메서드를 추가합니다. 다음 메서드는 커서의 현재 위치를 살펴보고 텍스트 구조 탐색기에 단어의 범위를 묻습니다. 커서가 단어 위에 있으면 TextExtent가 out 매개 변수에 반환됩니다. 그렇지 않으면
out
매개 변수가null
이고 메서드가false
를 반환합니다.private bool TryGetWordUnderCaret(out TextExtent wordExtent) { ITextCaret caret = m_textView.Caret; SnapshotPoint point; if (caret.Position.BufferPosition > 0) { point = caret.Position.BufferPosition - 1; } else { wordExtent = default(TextExtent); return false; } ITextStructureNavigator navigator = m_factory.NavigatorService.GetTextStructureNavigator(m_textBuffer); wordExtent = navigator.GetExtentOfWord(point); return true; }
HasSuggestedActionsAsync 메서드를 구현합니다. 편집기는 이 메서드를 호출하여 전구를 표시할지 여부를 확인합니다. 이 호출은 자주(예를 들어 커서가 한 줄에서 다른 줄로 이동하거나 오류 물결선 위로 마우스를 가리킬 때마다) 수행됩니다. 이 메서드가 작동하는 동안 다른 UI 작업을 계속할 수 있도록 비동기식입니다. 대부분의 경우 이 메서드는 현재 행에 대한 일부 구문 분석 및 분석을 수행해야 하므로 처리에 다소 시간이 걸릴 수 있습니다.
이 구현에서는 TextExtent를 비동기적으로 가져오고 범위가 중요한지 여부(예: 공백 이외의 텍스트가 있는지 여부)를 결정합니다.
public Task<bool> HasSuggestedActionsAsync(ISuggestedActionCategorySet requestedActionCategories, SnapshotSpan range, CancellationToken cancellationToken) { return Task.Factory.StartNew(() => { TextExtent extent; if (TryGetWordUnderCaret(out extent)) { // don't display the action if the extent has whitespace return extent.IsSignificant; } return false; }); }
다른 ISuggestedAction 개체를 포함하는 SuggestedActionSet 개체의 배열을 반환하는 GetSuggestedActions 메서드를 구현합니다. 이 메서드는 전구가 확장될 때 호출됩니다.
Warning
HasSuggestedActionsAsync()
및GetSuggestedActions()
의 구현이 일관성이 있는지 확인해야 합니다. 즉,HasSuggestedActionsAsync()
가true
를 반환하면GetSuggestedActions()
에 표시할 몇 가지 작업이 있어야 합니다. 대부분의 경우HasSuggestedActionsAsync()
가GetSuggestedActions()
직전에 호출되지만 항상 그런 것은 아닙니다. 예를 들어 사용자가 (CTRL+ .)를 눌러 전구 작업을 호출하면GetSuggestedActions()
만 호출됩니다.public IEnumerable<SuggestedActionSet> GetSuggestedActions(ISuggestedActionCategorySet requestedActionCategories, SnapshotSpan range, CancellationToken cancellationToken) { TextExtent extent; if (TryGetWordUnderCaret(out extent) && extent.IsSignificant) { ITrackingSpan trackingSpan = range.Snapshot.CreateTrackingSpan(extent.Span, SpanTrackingMode.EdgeInclusive); var upperAction = new UpperCaseSuggestedAction(trackingSpan); var lowerAction = new LowerCaseSuggestedAction(trackingSpan); return new SuggestedActionSet[] { new SuggestedActionSet(new ISuggestedAction[] { upperAction, lowerAction }) }; } return Enumerable.Empty<SuggestedActionSet>(); }
SuggestedActionsChanged
이벤트를 정의합니다.public event EventHandler<EventArgs> SuggestedActionsChanged;
구현을 완료하려면
Dispose()
및TryGetTelemetryId()
메서드에 대한 구현을 추가합니다. 원격 분석을 수행하지 않으려면false
를 반환하고 GUID를Empty
로 설정하면 됩니다.public void Dispose() { } public bool TryGetTelemetryId(out Guid telemetryId) { // This is a sample provider and doesn't participate in LightBulb telemetry telemetryId = Guid.Empty; return false; }
전구 작업 구현
프로젝트에서 Microsoft.VisualStudio.Imaging.Interop.14.0.DesignTime.dll에 대한 참조를 추가하고 로컬 복사를
False
로 설정합니다.두 개의 클래스를 만듭니다. 첫 번째 클래스의 이름은
UpperCaseSuggestedAction
이고 두 번째 클래스의 이름은LowerCaseSuggestedAction
입니다. 두 클래스 모두 ISuggestedAction를 구현합니다.internal class UpperCaseSuggestedAction : ISuggestedAction internal class LowerCaseSuggestedAction : ISuggestedAction
한 클래스는 ToUpper를 호출하고 다른 클래스는 ToLower를 호출한다는 점을 제외하고 두 클래스는 유사합니다. 다음 단계에서는 대문자 동작 클래스만 설명하지만 두 클래스를 모두 구현해야 합니다. 대문자 동작 구현 단계를 소문자 동작 구현 패턴으로 사용합니다.
이러한 클래스에 대해 다음 using 지시문을 추가합니다.
using Microsoft.VisualStudio.Imaging.Interop; using System.Windows; using System.Windows.Controls; using System.Windows.Documents; using System.Windows.Media;
전용 필드 집합을 선언합니다.
private ITrackingSpan m_span; private string m_upper; private string m_display; private ITextSnapshot m_snapshot;
필드를 설정하는 생성자를 추가합니다.
public UpperCaseSuggestedAction(ITrackingSpan span) { m_span = span; m_snapshot = span.TextBuffer.CurrentSnapshot; m_upper = span.GetText(m_snapshot).ToUpper(); m_display = string.Format("Convert '{0}' to upper case", span.GetText(m_snapshot)); }
작업 미리 보기를 표시할 수 있도록 GetPreviewAsync 메서드를 구현합니다.
public Task<object> GetPreviewAsync(CancellationToken cancellationToken) { var textBlock = new TextBlock(); textBlock.Padding = new Thickness(5); textBlock.Inlines.Add(new Run() { Text = m_upper }); return Task.FromResult<object>(textBlock); }
빈 SuggestedActionSet 열거형을 반환하도록 GetActionSetsAsync 메서드를 구현합니다.
public Task<IEnumerable<SuggestedActionSet>> GetActionSetsAsync(CancellationToken cancellationToken) { return Task.FromResult<IEnumerable<SuggestedActionSet>>(null); }
속성을 다음과 같이 구현합니다.
public bool HasActionSets { get { return false; } } public string DisplayText { get { return m_display; } } public ImageMoniker IconMoniker { get { return default(ImageMoniker); } } public string IconAutomationText { get { return null; } } public string InputGestureText { get { return null; } } public bool HasPreview { get { return true; } }
범위에 있는 텍스트를 해당 대문자로 바꾸어 Invoke 메서드를 구현합니다.
public void Invoke(CancellationToken cancellationToken) { m_span.TextBuffer.Replace(m_span.GetSpan(m_snapshot), m_upper); }
Warning
전구 작업 Invoke 메서드는 UI를 표시하지 않을 것으로 예상됩니다. 작업으로 인해 새 UI(예: 미리 보기 또는 선택 대화 상자)가 표시되는 경우, Invoke 메서드 내에서 직접 UI를 표시하지 말고 대신 Invoke에서 돌아온 후 UI를 표시하도록 예약합니다.
구현을 완료하려면
Dispose()
및TryGetTelemetryId()
메서드를 추가합니다.public void Dispose() { } public bool TryGetTelemetryId(out Guid telemetryId) { // This is a sample action and doesn't participate in LightBulb telemetry telemetryId = Guid.Empty; return false; }
잊지말고
LowerCaseSuggestedAction
에 대해 동일한 작업을 수행하여 표시 텍스트를 "'{0}'를 소문자로 변환"으로 변경하고 ToUpper를 ToLower로 호출하도록 합니다.
코드 빌드 및 테스트
이 코드를 테스트하려면 LightBulbTest 솔루션을 빌드하고 실험적 인스턴스에서 실행합니다.
솔루션을 빌드합니다.
디버거에서 이 프로젝트를 실행하면 Visual Studio의 두 번째 인스턴스가 시작됩니다.
텍스트 파일을 만들고 일부 텍스트를 입력합니다. 텍스트 왼쪽에 전구가 표시됩니다.
전구를 가리킵니다. 아래쪽 화살표가 표시됩니다.
전구를 클릭하면 두 개의 제안된 작업이 선택한 작업의 미리 보기와 함께 표시됩니다.
첫 번째 작업을 클릭하면 현재 단어의 모든 텍스트가 대문자로 변환됩니다. 두 번째 작업을 클릭하면 모든 텍스트가 소문자로 변환됩니다.