You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何将自定义导航程序化集成到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命名空间),这是官方推荐的添加返回导航标记的公开方式。

实现步骤

  1. 从IAsyncServiceProvider获取IVsNavigationManager实例:
var navigationManager = await serviceProvider.GetServiceAsync(typeof(SVsNavigationManager)) as IVsNavigationManager;
  1. 创建VsNavigationEntry对象,填充导航起点的信息(用于返回时定位):
var navigationEntry = new VsNavigationEntry();
navigationEntry.pszFileName = 当前活动文档的完整路径;
navigationEntry.iLine = 当前光标所在行号;
navigationEntry.iCol = 当前光标所在列号;
navigationEntry.dwFlags = (uint)_VSNAVIGATIONENTRYFLAGS.VSNAV_ENTRYFLAG_DEFAULT;
  1. 调用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 22:00:32