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导航栈添加记录,导致栈中充斥无关条目,且原位置未被正确记录。
修复步骤:
手动记录原位置到导航栈
在执行跳转逻辑前,主动将当前光标位置添加到导航栈,确保原位置可通过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); } } }替换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
相关产品推荐
相关产品推荐

