From fe8652c5b38f1c00f2caf16091f95c8a4694f71a Mon Sep 17 00:00:00 2001 From: Samir Boulema Date: Wed, 11 Mar 2026 20:59:33 +0100 Subject: [PATCH 1/6] feat: Implement outlining --- src/CodeNav/Services/IInProcService.cs | 4 ++ src/CodeNav/Services/InProcService.cs | 77 +++++++++++++++++++++++++- 2 files changed, 79 insertions(+), 2 deletions(-) diff --git a/src/CodeNav/Services/IInProcService.cs b/src/CodeNav/Services/IInProcService.cs index 9496ff0..aeffc9a 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 = "OutOfProcComponent.InProcService"; diff --git a/src/CodeNav/Services/InProcService.cs b/src/CodeNav/Services/InProcService.cs index 0484883..8bf10ad 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; @@ -15,15 +16,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; } @@ -39,6 +43,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 @@ -46,7 +119,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(); @@ -61,7 +134,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; From 9c199551f46c3fdb79c57836856f909fb6040d2f Mon Sep 17 00:00:00 2001 From: Samir Boulema Date: Fri, 13 Mar 2026 08:49:46 +0100 Subject: [PATCH 2/6] docs: Add xml doc --- src/CodeNav.OutOfProc/ViewModels/CodeItem.cs | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) 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; From 12eb213effed4220ee91a808a57f5cf84a975b18 Mon Sep 17 00:00:00 2001 From: Samir Boulema Date: Wed, 18 Mar 2026 15:41:47 +0100 Subject: [PATCH 3/6] build: Fix upmerge --- src/CodeNav/Services/InProcService.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/CodeNav/Services/InProcService.cs b/src/CodeNav/Services/InProcService.cs index 2cd3cfd..5790f68 100644 --- a/src/CodeNav/Services/InProcService.cs +++ b/src/CodeNav/Services/InProcService.cs @@ -120,17 +120,13 @@ 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(); -<<<<<<< feature/outlining - var span = new SnapshotSpan(textView.TextSnapshot, start, length); -======= if (!textView.TextBuffer.ContentType.TypeName.Equals("CSharp")) { // TODO: Log that the cursor and thus active text view is not in a csharp editor, for example the output window. return; } - var span = new SnapshotSpan(textView.TextSnapshot, new Span(start, length)); ->>>>>>> main + 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(); From b9cb4d2482bd19f5ae23d0b9a0553521eb9d6b31 Mon Sep 17 00:00:00 2001 From: Samir Boulema Date: Wed, 18 Mar 2026 19:26:23 +0100 Subject: [PATCH 4/6] feat: working outlining --- src/CodeNav.OutOfProc/ExtensionEntrypoint.cs | 4 +- .../Helpers/OutliningHelper.cs | 97 ++++++++++++++++++- .../Services/CodeDocumentService.cs | 6 +- .../ViewModels/CodeClassItem.cs | 19 +++- src/CodeNav/Services/InProcService.cs | 12 ++- 5 files changed, 131 insertions(+), 7 deletions(-) diff --git a/src/CodeNav.OutOfProc/ExtensionEntrypoint.cs b/src/CodeNav.OutOfProc/ExtensionEntrypoint.cs index 937d33c..e5714f0 100644 --- a/src/CodeNav.OutOfProc/ExtensionEntrypoint.cs +++ b/src/CodeNav.OutOfProc/ExtensionEntrypoint.cs @@ -1,4 +1,5 @@ -using CodeNav.OutOfProc.Services; +using CodeNav.OutOfProc.Helpers; +using CodeNav.OutOfProc.Services; using Microsoft.Extensions.DependencyInjection; using Microsoft.VisualStudio.Extensibility; @@ -36,5 +37,6 @@ protected override void InitializeServices(IServiceCollection serviceCollection) // As of now, any instance that ingests VisualStudioExtensibility is required to be added as a scoped // service. serviceCollection.AddScoped(); + serviceCollection.AddScoped(); } } diff --git a/src/CodeNav.OutOfProc/Helpers/OutliningHelper.cs b/src/CodeNav.OutOfProc/Helpers/OutliningHelper.cs index d74008b..51c58a7 100644 --- a/src/CodeNav.OutOfProc/Helpers/OutliningHelper.cs +++ b/src/CodeNav.OutOfProc/Helpers/OutliningHelper.cs @@ -1,17 +1,94 @@ using CodeNav.OutOfProc.Extensions; using CodeNav.OutOfProc.Interfaces; using CodeNav.OutOfProc.ViewModels; +using CodeNav.Services; +using Microsoft; +using Microsoft.VisualStudio.Extensibility; +using Microsoft.VisualStudio.Extensibility.Helpers; namespace CodeNav.OutOfProc.Helpers; -public static class OutliningHelper +public class OutliningHelper : DisposableObject { + private readonly VisualStudioExtensibility _extensibility; + private readonly Task _initializationTask; + private IInProcService? _inProcService; + + public OutliningHelper(VisualStudioExtensibility extensibility) + { + _extensibility = extensibility; + _initializationTask = Task.Run(InitializeAsync); + } + + public async Task CollapseOutlineRegion(int start, int length) + { + try + { + Assumes.NotNull(_inProcService); + await _inProcService.CollapseOutlineRegion(start, length); + } + catch (Exception e) + { + // TODO: Add logging + } + } + + public async Task ExpandOutlineRegion(int start, int length) + { + try + { + Assumes.NotNull(_inProcService); + await _inProcService.ExpandOutlineRegion(start, length); + } + catch (Exception e) + { + // TODO: Add logging + } + } + + public static async Task CollapseOutlineRegion(CodeItem codeItem) + { + if (codeItem.CodeDocumentViewModel?.CodeDocumentService?.OutliningHelper == null) + { + return; + } + + await codeItem.CodeDocumentViewModel.CodeDocumentService.OutliningHelper.CollapseOutlineRegion(codeItem.Span.Start, codeItem.Span.Length); + } + + public static async Task ExpandOutlineRegion(CodeItem codeItem) + { + if (codeItem.CodeDocumentViewModel?.CodeDocumentService?.OutliningHelper == null) + { + return; + } + + await codeItem.CodeDocumentViewModel.CodeDocumentService.OutliningHelper.ExpandOutlineRegion(codeItem.Span.Start, codeItem.Span.Length); + } + + /// + /// Collapses all nodes in the specified code document view model. + /// + /// Used in the main toolbar and in the code item context menu + /// The code document view model whose nodes will be collapsed. public static void CollapseAll(CodeDocumentViewModel? codeDocumentViewModel) => SetIsExpanded(codeDocumentViewModel, isExpanded: false); + /// + /// Expands all nodes in the specified code document view model. + /// + /// Used in the main toolbar and in the code item context menu + /// The code document view model whose nodes will be expanded. public static void ExpandAll(CodeDocumentViewModel? codeDocumentViewModel) => SetIsExpanded(codeDocumentViewModel, isExpanded: true); + /// + /// Sets the expanded state for all member items within the specified code document view model. + /// + /// Only items that implement the IMembers interface are affected. + /// The code document view model containing the code items to update. If null, no action is taken. + /// A value indicating whether the member items should be expanded () or collapsed (). private static void SetIsExpanded(CodeDocumentViewModel? codeDocumentViewModel, bool isExpanded) { codeDocumentViewModel? @@ -23,4 +100,22 @@ private static void SetIsExpanded(CodeDocumentViewModel? codeDocumentViewModel, .ToList() .ForEach(item => item.IsExpanded = isExpanded); } + + private async Task InitializeAsync() + { + (_inProcService as IDisposable)?.Dispose(); + _inProcService = await _extensibility + .ServiceBroker + .GetProxyAsync(IInProcService.Configuration.ServiceDescriptor, cancellationToken: default); + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (isDisposing) + { + (_inProcService as IDisposable)?.Dispose(); + } + } } diff --git a/src/CodeNav.OutOfProc/Services/CodeDocumentService.cs b/src/CodeNav.OutOfProc/Services/CodeDocumentService.cs index 5ac731b..8592c1b 100644 --- a/src/CodeNav.OutOfProc/Services/CodeDocumentService.cs +++ b/src/CodeNav.OutOfProc/Services/CodeDocumentService.cs @@ -10,7 +10,9 @@ namespace CodeNav.OutOfProc.Services; -public class CodeDocumentService(OutputWindowService logService) +public class CodeDocumentService( + OutputWindowService logService, + OutliningHelper outliningHelper) { /// /// DataContext for the tool window. @@ -31,6 +33,8 @@ public class CodeDocumentService(OutputWindowService logService) public OutputWindowService LogService => logService; + public OutliningHelper OutliningHelper => outliningHelper; + public async Task UpdateCodeDocumentViewModel( VisualStudioExtensibility? extensibility, ITextViewSnapshot? textView, diff --git a/src/CodeNav.OutOfProc/ViewModels/CodeClassItem.cs b/src/CodeNav.OutOfProc/ViewModels/CodeClassItem.cs index 9a7a0cc..8bf82e9 100644 --- a/src/CodeNav.OutOfProc/ViewModels/CodeClassItem.cs +++ b/src/CodeNav.OutOfProc/ViewModels/CodeClassItem.cs @@ -1,4 +1,5 @@ -using CodeNav.OutOfProc.Interfaces; +using CodeNav.OutOfProc.Helpers; +using CodeNav.OutOfProc.Interfaces; using Microsoft.VisualStudio.Extensibility; using Microsoft.VisualStudio.Extensibility.UI; using System.Runtime.Serialization; @@ -35,8 +36,18 @@ public bool IsExpanded { if (_isExpanded != value) { - SetProperty(ref _isExpanded, value); + SetProperty(ref _isExpanded, value); + IsExpandedChanged?.Invoke(this, EventArgs.Empty); + + if (value) + { + _ = OutliningHelper.ExpandOutlineRegion(this); + } + else + { + _ = OutliningHelper.CollapseOutlineRegion(this); + } } } } @@ -51,6 +62,10 @@ public Visibility HasMembersVisibility ? Visibility.Visible : Visibility.Collapsed; + /// + /// Command use to collapse and expand class/region/namespace code items + /// when double-clicking on the expander header + /// [DataMember] public AsyncCommand ToggleExpandCollapseCommand { get; } public async Task ToggleExpandCollapse(object? commandParameter, IClientContext clientContext, CancellationToken cancellationToken) diff --git a/src/CodeNav/Services/InProcService.cs b/src/CodeNav/Services/InProcService.cs index 5790f68..f0db111 100644 --- a/src/CodeNav/Services/InProcService.cs +++ b/src/CodeNav/Services/InProcService.cs @@ -53,6 +53,9 @@ public async Task ExpandOutlineRegion(int start, int length) var outliningManager = await GetOutliningManager(textView); + // Switch to the UI thread to ensure we can interact with the outline regions. + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + var outlineRegion = await GetOutlineRegionForSpan(textView, outliningManager, start, length); // Check if the outline region is collapsed before expanding @@ -79,6 +82,9 @@ public async Task CollapseOutlineRegion(int start, int length) var outliningManager = await GetOutliningManager(textView); + // Switch to the UI thread to ensure we can interact with the outline regions. + await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); + var outlineRegion = await GetOutlineRegionForSpan(textView, outliningManager, start, length); outliningManager.TryCollapse(outlineRegion); @@ -98,8 +104,10 @@ public async Task CollapseOutlineRegion(int start, int 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); + // Get the first outline region that has the same span start or end + return outlineRegions.FirstOrDefault(outlineRegion + => GetSpan(outlineRegion).Start == start || + GetSpan(outlineRegion).End == start + length); } private SnapshotSpan GetSpan(ICollapsible outlineRegion) From a89b27af700605e42d57d04a5733496ec8c64595 Mon Sep 17 00:00:00 2001 From: Samir Boulema Date: Wed, 18 Mar 2026 20:29:04 +0100 Subject: [PATCH 5/6] feat: Create in/out-proc methods for events --- .../Helpers/OutliningHelper.cs | 37 ++++++++++-- .../Services/IOutOfProcService.cs | 2 + .../Services/OutOfProcService.cs | 20 +++++-- src/CodeNav/Services/IInProcService.cs | 2 + src/CodeNav/Services/InProcService.cs | 59 ++++++++++++++++++- 5 files changed, 109 insertions(+), 11 deletions(-) diff --git a/src/CodeNav.OutOfProc/Helpers/OutliningHelper.cs b/src/CodeNav.OutOfProc/Helpers/OutliningHelper.cs index 51c58a7..69e5438 100644 --- a/src/CodeNav.OutOfProc/Helpers/OutliningHelper.cs +++ b/src/CodeNav.OutOfProc/Helpers/OutliningHelper.cs @@ -20,6 +20,19 @@ public OutliningHelper(VisualStudioExtensibility extensibility) _initializationTask = Task.Run(InitializeAsync); } + public async Task SubscribeToRegionEvents() + { + try + { + Assumes.NotNull(_inProcService); + await _inProcService.SubscribeToRegionEvents(); + } + catch (Exception e) + { + // TODO: Add logging + } + } + public async Task CollapseOutlineRegion(int start, int length) { try @@ -67,20 +80,34 @@ public static async Task ExpandOutlineRegion(CodeItem codeItem) } /// - /// Collapses all nodes in the specified code document view model. + /// Set IsExpanded property to false on all code items /// /// Used in the main toolbar and in the code item context menu /// The code document view model whose nodes will be collapsed. public static void CollapseAll(CodeDocumentViewModel? codeDocumentViewModel) - => SetIsExpanded(codeDocumentViewModel, isExpanded: false); + => SetAllIsExpanded(codeDocumentViewModel, isExpanded: false); /// - /// Expands all nodes in the specified code document view model. + /// Set IsExpanded property to true on all code items /// /// Used in the main toolbar and in the code item context menu /// The code document view model whose nodes will be expanded. public static void ExpandAll(CodeDocumentViewModel? codeDocumentViewModel) - => SetIsExpanded(codeDocumentViewModel, isExpanded: true); + => SetAllIsExpanded(codeDocumentViewModel, isExpanded: true); + + public static void SetIsExpanded(CodeDocumentViewModel? codeDocumentViewModel, int spanStart, int spanEnd, bool isExpanded) + { + codeDocumentViewModel? + .CodeItems + .Flatten() + .FilterNull() + .Where(item => item is IMembers) + .Where(codeItem => codeItem.Span.Start == spanStart || + codeItem.Span.End == spanEnd) + .Cast() + .ToList() + .ForEach(codeItem => codeItem.IsExpanded = isExpanded); + } /// /// Sets the expanded state for all member items within the specified code document view model. @@ -89,7 +116,7 @@ public static void ExpandAll(CodeDocumentViewModel? codeDocumentViewModel) /// The code document view model containing the code items to update. If null, no action is taken. /// A value indicating whether the member items should be expanded () or collapsed (). - private static void SetIsExpanded(CodeDocumentViewModel? codeDocumentViewModel, bool isExpanded) + private static void SetAllIsExpanded(CodeDocumentViewModel? codeDocumentViewModel, bool isExpanded) { codeDocumentViewModel? .CodeItems diff --git a/src/CodeNav.OutOfProc/Services/IOutOfProcService.cs b/src/CodeNav.OutOfProc/Services/IOutOfProcService.cs index 87bdfb0..103ebab 100644 --- a/src/CodeNav.OutOfProc/Services/IOutOfProcService.cs +++ b/src/CodeNav.OutOfProc/Services/IOutOfProcService.cs @@ -6,6 +6,8 @@ public interface IOutOfProcService { Task DoSomethingAsync(CancellationToken cancellationToken); + Task SetCodeItemIsExpanded(int spanStart, int spanEnd, bool isExpanded); + public static class Configuration { public const string ServiceName = "CodeNav.OutOfProcService"; diff --git a/src/CodeNav.OutOfProc/Services/OutOfProcService.cs b/src/CodeNav.OutOfProc/Services/OutOfProcService.cs index ff33349..b98c183 100644 --- a/src/CodeNav.OutOfProc/Services/OutOfProcService.cs +++ b/src/CodeNav.OutOfProc/Services/OutOfProcService.cs @@ -1,4 +1,5 @@ -using Microsoft.ServiceHub.Framework; +using CodeNav.OutOfProc.Helpers; +using Microsoft.ServiceHub.Framework; using Microsoft.VisualStudio.Extensibility; using Microsoft.VisualStudio.Extensibility.Shell; @@ -7,11 +8,15 @@ namespace CodeNav.OutOfProc.Services; [VisualStudioContribution] internal class OutOfProcService : IOutOfProcService, IBrokeredService { - private readonly VisualStudioExtensibility extensibility; + private readonly VisualStudioExtensibility _extensibility; + private readonly CodeDocumentService _codeDocumentService; - public OutOfProcService(VisualStudioExtensibility extensibility) + public OutOfProcService( + VisualStudioExtensibility extensibility, + CodeDocumentService codeDocumentService) { - this.extensibility = extensibility; + _extensibility = extensibility; + _codeDocumentService = codeDocumentService; } public static BrokeredServiceConfiguration BrokeredServiceConfiguration @@ -22,8 +27,13 @@ public static BrokeredServiceConfiguration BrokeredServiceConfiguration public static ServiceRpcDescriptor ServiceDescriptor => IOutOfProcService.Configuration.ServiceDescriptor; + public async Task SetCodeItemIsExpanded(int spanStart, int spanEnd, bool isExpanded) + { + OutliningHelper.SetIsExpanded(_codeDocumentService.CodeDocumentViewModel, spanStart, spanEnd, isExpanded); + } + public async Task DoSomethingAsync(CancellationToken cancellationToken) { - await this.extensibility.Shell().ShowPromptAsync("Hello from in-proc! (Showing this message from (out-of-proc)", PromptOptions.OK, cancellationToken); + await _extensibility.Shell().ShowPromptAsync("Hello from in-proc! (Showing this message from (out-of-proc)", PromptOptions.OK, cancellationToken); } } diff --git a/src/CodeNav/Services/IInProcService.cs b/src/CodeNav/Services/IInProcService.cs index a5f2173..a98fe1f 100644 --- a/src/CodeNav/Services/IInProcService.cs +++ b/src/CodeNav/Services/IInProcService.cs @@ -12,6 +12,8 @@ public interface IInProcService Task CollapseOutlineRegion(int start, int length); + Task SubscribeToRegionEvents(); + 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 f0db111..b666816 100644 --- a/src/CodeNav/Services/InProcService.cs +++ b/src/CodeNav/Services/InProcService.cs @@ -1,4 +1,5 @@ -using Microsoft; +using CodeNav.OutOfProc.Services; +using Microsoft; using Microsoft.VisualStudio; using Microsoft.VisualStudio.Editor; using Microsoft.VisualStudio.Extensibility; @@ -44,6 +45,62 @@ 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 SubscribeToRegionEvents() + { + // Not using context.GetActiveTextViewAsync here because VisualStudio.Extensibility doesn't support outlining yet. + var textView = await GetCurrentTextViewAsync(); + + var outliningManager = await GetOutliningManager(textView); + + outliningManager.RegionsExpanded -= OutliningManager_RegionsExpanded; + outliningManager.RegionsExpanded += OutliningManager_RegionsExpanded; + + outliningManager.RegionsCollapsed -= OutliningManager_RegionsCollapsed; + outliningManager.RegionsCollapsed += OutliningManager_RegionsCollapsed; + } + + private async void OutliningManager_RegionsExpanded(object sender, RegionsExpandedEventArgs e) + { + var outOfProcService = await _extensibility.ServiceBroker + .GetProxyAsync(IOutOfProcService.Configuration.ServiceDescriptor, cancellationToken: default); + + try + { + Assumes.NotNull(outOfProcService); + + foreach (var region in e.ExpandedRegions) + { + var span = GetSpan(region); + await outOfProcService.SetCodeItemIsExpanded(span.Start, span.End, isExpanded: true); + } + } + finally + { + (outOfProcService as IDisposable)?.Dispose(); + } + } + + private async void OutliningManager_RegionsCollapsed(object sender, RegionsCollapsedEventArgs e) + { + var outOfProcService = await _extensibility.ServiceBroker + .GetProxyAsync(IOutOfProcService.Configuration.ServiceDescriptor, cancellationToken: default); + + try + { + Assumes.NotNull(outOfProcService); + + foreach (var region in e.CollapsedRegions) + { + var span = GetSpan(region); + await outOfProcService.SetCodeItemIsExpanded(span.Start, span.End, isExpanded: false); + } + } + finally + { + (outOfProcService as IDisposable)?.Dispose(); + } + } + public async Task ExpandOutlineRegion(int start, int length) { try From d40e20fbaec5f917657e6b3b744c766f9f218652 Mon Sep 17 00:00:00 2001 From: Samir Boulema Date: Fri, 20 Mar 2026 08:35:33 +0100 Subject: [PATCH 6/6] feat: Subscribe to events --- src/CodeNav.OutOfProc/Services/CodeDocumentService.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/CodeNav.OutOfProc/Services/CodeDocumentService.cs b/src/CodeNav.OutOfProc/Services/CodeDocumentService.cs index 8592c1b..4d498e9 100644 --- a/src/CodeNav.OutOfProc/Services/CodeDocumentService.cs +++ b/src/CodeNav.OutOfProc/Services/CodeDocumentService.cs @@ -115,6 +115,9 @@ public async Task UpdateCodeDocumentViewModel( // we should consider not expanding by default OutliningHelper.ExpandAll(CodeDocumentViewModel); + // TODO: test + await OutliningHelper.SubscribeToRegionEvents(); + await logService.WriteInfo(textView, $"Expanding all code items"); return CodeDocumentViewModel;