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

VSIX插件中DTE.Find.Execute导致VS2019后退导航异常的解决咨询

问题描述

我正在开发用于VS2019的VSIX插件,已实现一个菜单命令,可根据光标位置文本跳转到对应代码位置。使用DTE.Find.Execute接口完成需求后功能正常,但执行命令后VS2019内置的View.Navigate Backward功能出现问题:无法跳转回命令调用前的代码位置,且新旧位置间存在大量无关代码位置记录。

核心代码

bool GoToRegexJump(string originalText)
{
    string config = UtilsClass.GetRegexJumpConfig(Package);
    JArray array = JArray.Parse(config);
    string project = UtilsClass.GetSolutionSort(Package).ToString();
    foreach (var item in array)
    {
        string projects = item[Constants.USERSETTINGS_REGEX_JUMP_PROJECT].ToObject<string>();
        string files = item[Constants.USERSETTINGS_REGEX_JUMP_FILES].ToObject<string>();
        string[] allowProjects = projects.Split(new string[] { "|" }, StringSplitOptions.RemoveEmptyEntries);
        string key = item[Constants.USERSETTINGS_REGEX_JUMP_KEY].ToObject<string>();
        bool matchCase = item[Constants.USERSETTINGS_REGEX_JUMP_MATCH_CASE].ToObject<bool>();

        if (allowProjects.Contains(project))
        {
            var match = Regex.Match(originalText, key);
            if (match.Success)
            {
                string pattern = item[Constants.USERSETTINGS_REGEX_JUMP_VALUE].ToObject<string>();
                for (int i = 0; i < match.Groups.Count; ++i)
                {
                    string value = match.Groups[i].Value;
                    value = Regex.Escape(value);
                    pattern = pattern.Replace($"${i}", value);
                }
                
                if (FindText(
                        pattern
                        , false
                        , vsFindTarget.vsFindTargetSolution, vsFindPatternSyntax.vsFindPatternSyntaxRegExpr
                        , false, matchCase, string.IsNullOrEmpty(files) ? "*.h;*.cpp" : files))
                {
                    return true;
                }
            }
        }
    }

    return false;
}

bool FindText(string text, bool backwards = false, vsFindTarget target = vsFindTarget.vsFindTargetCurrentDocument, vsFindPatternSyntax syntax = vsFindPatternSyntax.vsFindPatternSyntaxLiteral, bool matchWholeWord = false, bool matchCase = false, string filesOfType = "*.h;*.cpp", string searchPath = "")
{
    Microsoft.VisualStudio.Shell.ThreadHelper.ThrowIfNotOnUIThread();

    var dte = Package.GetService<DTE, DTE2>();
    if (dte == null)
    {
        return false;
    }

    if (target == vsFindTarget.vsFindTargetCurrentDocument)
    {
        var textView = GetActiveTextView();
        if (textView == null)
        {
            return false;
        }
    }

    dte.Find.MatchCase = matchCase;
    dte.Find.MatchWholeWord = matchWholeWord;
    dte.Find.Action = vsFindAction.vsFindActionFind;
    dte.Find.SearchPath = searchPath;
    dte.Find.Target = target;
    dte.Find.Backwards = backwards;
    dte.Find.MatchInHiddenText = true;
    dte.Find.PatternSyntax = syntax;
    dte.Find.FindWhat = text;
    dte.Find.FilesOfType = filesOfType;

    var findResult = dte.Find.Execute();

    if (findResult == vsFindResult.vsFindResultFound)
    {
        return true;
    }

    if (UtilsClass.IsDebugMode(Package))
    {
        UtilsClass.PrintMessage(
            $"Found {dte.Find.FindWhat} Failed,SearchPath={dte.Find.SearchPath},FilesOfType={dte.Find.FilesOfType}。");
    }

    return false;
}

解决方案

问题根源是DTE.Find.Execute()执行全局搜索时,会多次切换文档或光标位置,每次切换都会自动向VS导航栈添加记录,导致栈中充斥无关条目,且原位置未被正确记录。

修复步骤:

  1. 手动记录原位置到导航栈
    在执行跳转逻辑前,主动将当前光标位置添加到导航栈,确保原位置可通过Navigate Backward找回。使用VS的IVsNavigationManager服务实现:

    using Microsoft.VisualStudio.Shell;
    using Microsoft.VisualStudio.TextManager.Interop;
    using Microsoft.VisualStudio.Navigation;
    
    // 在GoToRegexJump方法开头添加以下代码
    ThreadHelper.ThrowIfNotOnUIThread();
    var dte = Package.GetService<DTE, DTE2>();
    var navigationManager = Package.GetService<IVsNavigationManager, IVsNavigationManager>();
    
    if (navigationManager != null && dte.ActiveDocument != null)
    {
        IVsTextManager textManager = Package.GetService<IVsTextManager, IVsTextManager>();
        if (textManager != null)
        {
            IVsTextView vsTextView;
            textManager.GetActiveView(1, null, out vsTextView);
            
            if (vsTextView != null)
            {
                var navItem = new VsNavigationItem
                {
                    DocumentMoniker = dte.ActiveDocument.FullName
                };
                
                // 获取当前光标位置
                int line, col;
                vsTextView.GetCaretPos(out line, out col);
                navItem.Line = line;
                navItem.Column = col;
                
                // 添加到导航栈
                navigationManager.AddNavigationItem(navItem, 0);
            }
        }
    }
    
  2. 替换DTE.Find为IVsFindManager,手动控制跳转
    使用更底层的IVsFindManager替代DTE.Find,它不会自动添加多余导航记录,能更精准控制查找和跳转流程:

    using Microsoft.VisualStudio.Shell.Interop;
    
    // 替换原FindText方法为以下实现
    bool FindText(string text, bool backwards = false, vsFindTarget target = vsFindTarget.vsFindTargetCurrentDocument, vsFindPatternSyntax syntax = vsFindPatternSyntax.vsFindPatternSyntaxLiteral, bool matchWholeWord = false, bool matchCase = false, string filesOfType = "*.h;*.cpp", string searchPath = "")
    {
        ThreadHelper.ThrowIfNotOnUIThread();
        
        IVsFindManager findManager = Package.GetService<IVsFindManager, IVsFindManager>();
        if (findManager == null) return false;
    
        // 获取查找范围
        IVsFindScope findScope = null;
        switch (target)
        {
            case vsFindTarget.vsFindTargetSolution:
                findManager.GetDefaultScope((uint)__VSFINDSCOPE.FDS_SOLUTION, out findScope);
                break;
            case vsFindTarget.vsFindTargetCurrentDocument:
                findManager.GetDefaultScope((uint)__VSFINDSCOPE.FDS_CURRENTDOCUMENT, out findScope);
                break;
        }
        if (findScope == null) return false;
    
        // 设置查找选项
        uint options = 0;
        if (matchCase) options |= (uint)__VSFINDOPTIONS.FR_MATCHCASE;
        if (matchWholeWord) options |= (uint)__VSFINDOPTIONS.FR_WHOLEWORD;
        if (syntax == vsFindPatternSyntax.vsFindPatternSyntaxRegExpr) options |= (uint)__VSFINDOPTIONS.FR_REGEX;
    
        // 执行查找
        IVsFindResult findResult;
        int hr = findManager.FindString((uint)__VSFINDFINDHOW.FH_FIRST, text, options, findScope, out findResult);
        if (hr != 0 || findResult == null) return false;
    
        // 手动跳转到查找结果位置
        string filePath;
        int line, col;
        findResult.GetResultInfo(out filePath, out line, out col);
        
        var dte = Package.GetService<DTE, DTE2>();
        var doc = dte.Documents.Open(filePath);
        doc.Activate();
        var selection = doc.Selection as TextSelection;
        // VS的行号和列号从1开始,查找结果返回的是0索引
        selection.MoveToLineAndOffset(line + 1, col + 1);
    
        return true;
    }
    

说明

  • 手动添加原位置到导航栈,确保Navigate Backward能正确回到命令执行前的位置
  • 使用IVsFindManager避免了DTE.Find自动生成多余导航记录的问题,全程手动控制跳转流程,导航栈只会保留原位置和目标位置两个有效条目

内容的提问来源于stack exchange,提问作者hchlqlz

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 01:58:13