如何将自定义导航程序化集成到Visual Studio返回导航列表
在Visual Studio扩展中添加返回导航标记(Go-Back Marker)的公开API方案
问题背景
我正在开发一款带有自定义Code Map的Visual Studio扩展,核心是树形视图控件,每个节点对应特定代码元素。点击树节点可以导航到代码编辑器中对应元素的声明位置,导航功能的简化实现代码如下:
public static async Task NavigateAsync( Microsoft.VisualStudio.Shell.IAsyncServiceProvider serviceProvider, string filePath,int lineNumber, int character) { // 获取当前文档的IWpfTextView IWpfTextView wpfTextView = await OpenCodeWindowAsync(serviceProvider, filePath); // 生成目标导航位置 ITextSnapshotLine textLine = wpfTextView.TextSnapshot.GetLineFromLineNumber(lineNumber); int absoluteOffset = textLine.Start + character; // 展开目标位置所在的折叠区域 SnapshotPoint point = wpfTextView.TextSnapshot.GetPoint(absoluteOffset); SnapshotSpan span = new SnapshotSpan(point, length: 0); await serviceProvider.ExpandAllRegionsContainingSpanAsync(span, wpfTextView); // 移动光标并滚动到目标位置 CaretPosition newCaretPosition = wpfTextView.MoveCaretTo(absoluteOffset); wpfTextView.ViewScroller.EnsureSpanVisible(span, EnsureSpanVisibleOptions.AlwaysCenter); }
导航功能运行正常,但Visual Studio左上角的Navigate Backward返回导航列表未更新。根据MSDN文档,VS会创建名为go-back markers的返回导航日志条目,我需要通过编程方式添加此类标记,但未找到合适的公开API。
已尝试以下无效方法:
- 使用旧版VS COM接口实现导航
- 调用“Go To Definition”命令
- 查看VS开源代码未找到可用实现
目前正在尝试通过Roslyn内部服务解决,但更倾向于使用公开API,寻求相关解决方案。
解决方案
使用IVsNavigationManager服务(官方公开API)
Visual Studio提供了IVsNavigationManager接口(位于Microsoft.VisualStudio.Shell.Interop命名空间),这是官方推荐的添加返回导航标记的公开方式。
实现步骤
- 从
IAsyncServiceProvider获取IVsNavigationManager实例:
var navigationManager = await serviceProvider.GetServiceAsync(typeof(SVsNavigationManager)) as IVsNavigationManager;
- 创建
VsNavigationEntry对象,填充导航起点的信息(用于返回时定位):
var navigationEntry = new VsNavigationEntry(); navigationEntry.pszFileName = 当前活动文档的完整路径; navigationEntry.iLine = 当前光标所在行号; navigationEntry.iCol = 当前光标所在列号; navigationEntry.dwFlags = (uint)_VSNAVIGATIONENTRYFLAGS.VSNAV_ENTRYFLAG_DEFAULT;
- 调用
AddNavigationItem方法添加返回标记:
navigationManager.AddNavigationItem(navigationEntry, 0);
整合后的完整导航方法
将上述逻辑整合到原有代码中,修改后的方法如下:
using Microsoft.VisualStudio.Shell.Interop; using EnvDTE; public static async Task NavigateAsync( Microsoft.VisualStudio.Shell.IAsyncServiceProvider serviceProvider, string filePath, int lineNumber, int character) { // 记录导航前的光标位置,作为返回标记的起点 var dte = await serviceProvider.GetServiceAsync(typeof(DTE)) as DTE; string currentDocPath = null; int currentLine = 0, currentCol = 0; if (dte?.ActiveDocument != null && dte.ActiveDocument.Selection is TextSelection sel) { currentDocPath = dte.ActiveDocument.FullName; currentLine = sel.ActivePoint.Line; currentCol = sel.ActivePoint.Column; } // 原有导航逻辑 IWpfTextView wpfTextView = await OpenCodeWindowAsync(serviceProvider, filePath); ITextSnapshotLine textLine = wpfTextView.TextSnapshot.GetLineFromLineNumber(lineNumber); int absoluteOffset = textLine.Start + character; SnapshotPoint point = wpfTextView.TextSnapshot.GetPoint(absoluteOffset); SnapshotSpan span = new SnapshotSpan(point, length: 0); await serviceProvider.ExpandAllRegionsContainingSpanAsync(span, wpfTextView); CaretPosition newCaretPosition = wpfTextView.MoveCaretTo(absoluteOffset); wpfTextView.ViewScroller.EnsureSpanVisible(span, EnsureSpanVisibleOptions.AlwaysCenter); // 添加返回导航标记 var navigationManager = await serviceProvider.GetServiceAsync(typeof(SVsNavigationManager)) as IVsNavigationManager; if (navigationManager != null && !string.IsNullOrEmpty(currentDocPath)) { var entry = new VsNavigationEntry(); entry.pszFileName = currentDocPath; entry.iLine = currentLine; entry.iCol = currentCol; entry.dwFlags = (uint)_VSNAVIGATIONENTRYFLAGS.VSNAV_ENTRYFLAG_DEFAULT; // 0表示添加新条目,1表示替换最后一个条目 navigationManager.AddNavigationItem(entry, 0); } }
注意事项
- 确保项目已引用
Microsoft.VisualStudio.Shell.Interop程序集,VS扩展项目通常默认已包含该引用。 - 如果需要关联项目层级,可以通过
DTE获取对应项目的IVsHierarchy实例,赋值给VsNavigationEntry.pHierarchy,提升返回导航的精准度。 AddNavigationItem的第二个参数fReplaceLast可根据需求调整:0添加新条目,1替换最后一个条目。
替代方案:使用IVsTrackSelectionEx服务
如果IVsNavigationManager无法满足需求,可通过IVsTrackSelectionEx服务通知VS更新导航状态,不过该方式更偏向选择跟踪,而非直接添加返回标记:
var trackSelection = await serviceProvider.GetServiceAsync(typeof(SVsTrackSelectionEx)) as IVsTrackSelectionEx; if (trackSelection != null) { trackSelection.OnElementValueChanged((uint)VSConstants.VSSELELEMID.SEID_ResultList, 0, null); }
优先推荐使用IVsNavigationManager方案,更贴合添加返回标记的需求。
内容的提问来源于stack exchange,提问作者SENya
相关产品推荐
相关产品推荐

