diff --git a/src/CodeNav.OutOfProc/ViewModels/CodeItem.cs b/src/CodeNav.OutOfProc/ViewModels/CodeItem.cs index b817576..d014069 100644 --- a/src/CodeNav.OutOfProc/ViewModels/CodeItem.cs +++ b/src/CodeNav.OutOfProc/ViewModels/CodeItem.cs @@ -1,6 +1,5 @@ using CodeNav.OutOfProc.Constants; using CodeNav.OutOfProc.Helpers; -using CodeNav.OutOfProc.Services; using CodeNav.Services; using Microsoft; using Microsoft.CodeAnalysis.Text; @@ -76,8 +75,20 @@ public CodeItem() [DataMember] public string Tooltip { get; set; } = string.Empty; + /// + /// Path to the file containing the code item + /// + /// + /// Used for opening the file if it's different from the currently active one + /// public Uri? FilePath { get; set; } + /// + /// Full name of the code item + /// + /// + /// Used in constructing a unique id + /// internal string FullName = string.Empty; public CodeItemKindEnum Kind; diff --git a/src/CodeNav/Services/IInProcService.cs b/src/CodeNav/Services/IInProcService.cs index 8b233aa..a5f2173 100644 --- a/src/CodeNav/Services/IInProcService.cs +++ b/src/CodeNav/Services/IInProcService.cs @@ -8,6 +8,10 @@ public interface IInProcService Task TextViewScrollToSpan(int start, int length); + Task ExpandOutlineRegion(int start, int length); + + Task CollapseOutlineRegion(int start, int length); + public static class Configuration { public const string ServiceName = "CodeNav.InProcService"; diff --git a/src/CodeNav/Services/InProcService.cs b/src/CodeNav/Services/InProcService.cs index 4b322fc..8aa36b8 100644 --- a/src/CodeNav/Services/InProcService.cs +++ b/src/CodeNav/Services/InProcService.cs @@ -7,6 +7,7 @@ using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Editor; +using Microsoft.VisualStudio.Text.Outlining; using Microsoft.VisualStudio.TextManager.Interop; namespace CodeNav.Services; @@ -16,15 +17,18 @@ internal class InProcService : IInProcService { private readonly VisualStudioExtensibility _extensibility; private readonly MefInjection _editorAdaptersFactoryService; + private readonly MefInjection _outliningManagerFactoryService; private readonly AsyncServiceProviderInjection _textManager; public InProcService( VisualStudioExtensibility extensibility, MefInjection editorAdaptersFactoryService, + MefInjection outliningManagerFactoryService, AsyncServiceProviderInjection textManager) { _extensibility = extensibility; _editorAdaptersFactoryService = editorAdaptersFactoryService; + _outliningManagerFactoryService = outliningManagerFactoryService; _textManager = textManager; } @@ -40,6 +44,75 @@ public async Task DoSomethingAsync(CancellationToken cancellationToken) await _extensibility.Shell().ShowPromptAsync("Hello from out-of-proc! (Showing this message from (in-proc)", PromptOptions.OK, cancellationToken); } + public async Task ExpandOutlineRegion(int start, int length) + { + try + { + // Not using context.GetActiveTextViewAsync here because VisualStudio.Extensibility doesn't support outlining yet. + var textView = await GetCurrentTextViewAsync(); + + var outliningManager = await GetOutliningManager(textView); + + var outlineRegion = await GetOutlineRegionForSpan(textView, outliningManager, start, length); + + // Check if the outline region is collapsed before expanding + if (outlineRegion?.IsCollapsed != true || + outlineRegion is not ICollapsed collapsedOutlineRegion) + { + return; + } + + outliningManager.Expand(collapsedOutlineRegion); + } + catch (Exception) + { + // TODO: Implement in-proc error logging + } + } + + public async Task CollapseOutlineRegion(int start, int length) + { + try + { + // Not using context.GetActiveTextViewAsync here because VisualStudio.Extensibility doesn't support outlining yet. + var textView = await GetCurrentTextViewAsync(); + + var outliningManager = await GetOutliningManager(textView); + + var outlineRegion = await GetOutlineRegionForSpan(textView, outliningManager, start, length); + + outliningManager.TryCollapse(outlineRegion); + } + catch (Exception) + { + // TODO: Implement in-proc error logging + } + } + + private async Task GetOutlineRegionForSpan( + IWpfTextView textView, IOutliningManager outliningManager, + int start, int length) + { + // Get all outline regions for the given span + var span = new SnapshotSpan(textView.TextSnapshot, start, length); + + var outlineRegions = outliningManager.GetAllRegions(span); + + // Get the first outline region that has the same span start + return outlineRegions.FirstOrDefault(outlineRegion => GetSpan(outlineRegion).Start == start); + } + + private SnapshotSpan GetSpan(ICollapsible outlineRegion) + => outlineRegion.Extent.GetSpan(outlineRegion.Extent.TextBuffer.CurrentSnapshot); + + private async Task GetOutliningManager(IWpfTextView textView) + { + var outliningManagerService = await _outliningManagerFactoryService.GetServiceAsync(); + var outliningManager = outliningManagerService.GetOutliningManager(textView); + + return outliningManager; + } + public async Task TextViewScrollToSpan(int start, int length) { try @@ -47,7 +120,7 @@ public async Task TextViewScrollToSpan(int start, int length) // Not using context.GetActiveTextViewAsync here because VisualStudio.Extensibility doesn't support viewscroller yet. var textView = await GetCurrentTextViewAsync(); - var span = new SnapshotSpan(textView.TextSnapshot, new Span(start, length)); + var span = new SnapshotSpan(textView.TextSnapshot, start, length); // Switch to the UI thread to ensure we can interact with the view scroller. await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); @@ -62,7 +135,7 @@ public async Task TextViewScrollToSpan(int start, int length) private async Task GetCurrentTextViewAsync() { - IVsEditorAdaptersFactoryService editorAdapter = await _editorAdaptersFactoryService.GetServiceAsync(); + var editorAdapter = await _editorAdaptersFactoryService.GetServiceAsync(); var view = editorAdapter.GetWpfTextView(await GetCurrentNativeTextViewAsync()); Assumes.Present(view); return view;