├── .editorconfig ├── .github └── workflows │ └── workflow.yml ├── .gitignore ├── LICENSE ├── README.md ├── Resources ├── CallStackWindow.ico ├── CallStackWindow_256x.png ├── Preview.png ├── StackTraceExplorer.cs └── StackTraceExplorer.vsct ├── StackTraceExplorer.Shared ├── CustomLinkVisualLineText.cs ├── Generators │ ├── FileLinkElementGenerator.cs │ └── MemberLinkElementGenerator.cs ├── Helpers │ ├── ClickHelper.cs │ ├── SolutionHelper.cs │ ├── StringHelper.cs │ └── TraceHelper.cs ├── Models │ ├── StackTrace.cs │ └── StackTracesViewModel.cs ├── StackTraceEditor.cs ├── StackTraceExplorer.Shared.projitems ├── StackTraceExplorer.Shared.shproj ├── StackTraceExplorer.resx ├── StackTraceExplorerToolWindow.cs ├── StackTraceExplorerToolWindowCommand.cs ├── StackTraceExplorerToolWindowControl.xaml ├── StackTraceExplorerToolWindowControl.xaml.cs └── StackTraceExplorerToolWindowPackage.cs ├── StackTraceExplorer.Tests ├── FileRegexTests.cs ├── LongestSuffixTests.cs ├── MemberRegexTests.cs ├── Properties │ └── AssemblyInfo.cs ├── SolutionHelperTests.cs └── StackTraceExplorer.Tests.csproj ├── StackTraceExplorer.VS2019 ├── CallStackWindow.ico ├── Properties │ └── AssemblyInfo.cs ├── StackTraceExplorer.VS2019.csproj └── source.extension.vsixmanifest ├── StackTraceExplorer.VS2022 ├── CallStackWindow.ico ├── Properties │ └── AssemblyInfo.cs ├── StackTraceExplorer.VS2022.csproj └── source.extension.vsixmanifest ├── StackTraceExplorer.sln ├── nuget.config ├── publish-manifest.VS2019.json └── publish-manifest.VS2022.json /.editorconfig: -------------------------------------------------------------------------------- 1 | [*.cs] 2 | 3 | # VSTHRD200: Use "Async" suffix for async methods 4 | dotnet_diagnostic.VSTHRD200.severity = none 5 | 6 | # IDE0008: Use explicit type 7 | dotnet_diagnostic.IDE0008.severity = none 8 | 9 | # VSTHRD111: Use ConfigureAwait(bool) 10 | dotnet_diagnostic.VSTHRD111.severity = none 11 | 12 | # IDE0022: Use block body for methods 13 | dotnet_diagnostic.IDE0022.severity = none 14 | -------------------------------------------------------------------------------- /.github/workflows/workflow.yml: -------------------------------------------------------------------------------- 1 | name: StackTraceExplorer 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | - 'feature/**' 8 | 9 | env: 10 | version: '3.0.${{ github.run_number }}' 11 | repoUrl: ${{ github.server_url }}/${{ github.repository }} 12 | 13 | jobs: 14 | build: 15 | name: 🛠️ Build 16 | runs-on: windows-latest 17 | steps: 18 | - name: Checkout 19 | uses: actions/checkout@v4 20 | 21 | - name: Update Assembly Version 22 | uses: dannevesdantas/set-version-assemblyinfo@v.1.0.0 23 | with: 24 | version: ${{ env.version }} 25 | 26 | - name: Update Vsix Version (VS2019) 27 | uses: cezarypiatek/VsixVersionAction@1.0 28 | with: 29 | version: ${{ env.version }} 30 | vsix-manifest-file: 'StackTraceExplorer.VS2019\source.extension.vsixmanifest' 31 | 32 | - name: Update Vsix Version (VS2022) 33 | uses: cezarypiatek/VsixVersionAction@1.0 34 | with: 35 | version: ${{ env.version }} 36 | vsix-manifest-file: 'StackTraceExplorer.VS2022\source.extension.vsixmanifest' 37 | 38 | - name: Setup MSBuild 39 | uses: microsoft/setup-msbuild@v2 40 | 41 | - name: NuGet restore 42 | run: nuget restore StackTraceExplorer.sln -ConfigFile nuget.config 43 | 44 | - name: Build VSIX 45 | run: msbuild StackTraceExplorer.sln /t:Rebuild /p:Configuration=Release 46 | env: 47 | DeployExtension: False 48 | 49 | - name: Publish Build Artifacts 50 | uses: actions/upload-artifact@v4 51 | with: 52 | name: StackTraceExplorer 53 | path: | 54 | **\*.vsix 55 | publish-manifest.*.json 56 | readme.md 57 | 58 | release: 59 | name: 🚀 Release 60 | needs: build 61 | runs-on: windows-latest 62 | environment: Release 63 | steps: 64 | - name: Download artifact 65 | uses: actions/download-artifact@v4 66 | 67 | - name: Tag release 68 | id: tag_release 69 | uses: mathieudutour/github-tag-action@v6.2 70 | with: 71 | custom_tag: '${{ env.version }}' 72 | github_token: ${{ secrets.GITHUB_TOKEN }} 73 | 74 | - name: Create a GitHub release 75 | uses: ncipollo/release-action@v1 76 | with: 77 | tag: ${{ steps.tag_release.outputs.new_tag }} 78 | name: ${{ steps.tag_release.outputs.new_tag }} 79 | body: ${{ steps.tag_release.outputs.changelog }} 80 | artifacts: "**/*.vsix" 81 | skipIfReleaseExists: true 82 | 83 | - name: Publish to Marketplace - VS2019 84 | uses: cezarypiatek/VsixPublisherAction@1.1 85 | with: 86 | extension-file: StackTraceExplorer/StackTraceExplorer.VS2019/bin/release/StackTraceExplorer.VS2019.vsix 87 | publish-manifest-file: StackTraceExplorer/publish-manifest.VS2019.json 88 | personal-access-code: ${{ secrets.VS_PUBLISHER_ACCESS_TOKEN }} 89 | 90 | - name: Publish to Marketplace - VS2022 91 | uses: cezarypiatek/VsixPublisherAction@1.1 92 | with: 93 | extension-file: StackTraceExplorer/StackTraceExplorer.VS2022/bin/release/StackTraceExplorer.VS2022.vsix 94 | publish-manifest-file: StackTraceExplorer/publish-manifest.VS2022.json 95 | personal-access-code: ${{ secrets.VS_PUBLISHER_ACCESS_TOKEN }} 96 | 97 | - name: Publish to Open VSIX Gallery - VS2019 98 | run: | 99 | curl -L 'https://www.vsixgallery.com/api/upload?repo=${{ env.repoUrl }}&issuetracker=${{ env.repoUrl }}/issues' -F 'file=@"StackTraceExplorer/StackTraceExplorer.VS2019/bin/release/StackTraceExplorer.VS2019.vsix"' 100 | 101 | - name: Publish to Open VSIX Gallery - VS2022 102 | run: | 103 | curl -L 'https://www.vsixgallery.com/api/upload?repo=${{ env.repoUrl }}&issuetracker=${{ env.repoUrl }}/issues' -F 'file=@"StackTraceExplorer/StackTraceExplorer.VS2022/bin/release/StackTraceExplorer.VS2022.vsix"' -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Build results 2 | [Bb]in/ 3 | [Oo]bj/ 4 | [Ll]og/ 5 | [Ll]ogs/ 6 | 7 | # The packages folder can be ignored because of Package Restore 8 | **/[Pp]ackages/* 9 | 10 | # User-specific files 11 | *.suo 12 | *.user 13 | 14 | # Visual Studio options directory 15 | .vs/ 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 - 2021 Samir Boulema 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Stack Trace Explorer 2 | Parse those pesty unreadable long stack traces. Stack Trace Explorer provides syntax highlighting and easy navigation to elements in the stack trace. 3 | 4 | [![StackTraceExplorer](https://github.com/sboulema/StackTraceExplorer/actions/workflows/workflow.yml/badge.svg)](https://github.com/sboulema/StackTraceExplorer/actions/workflows/workflow.yml) 5 | [![Sponsor](https://img.shields.io/badge/-Sponsor-fafbfc?logo=GitHub%20Sponsors)](https://github.com/sponsors/sboulema) 6 | 7 | ## Installing 8 | [Visual Studio Marketplace](https://marketplace.visualstudio.com/items?itemName=SamirBoulema.StackTraceExplorer) 9 | 10 | [Github Releases](https://github.com/sboulema/StackTraceExplorer/releases) 11 | 12 | [Open VSIX Gallery](http://vsixgallery.com/extension/StackTraceExplorer.Samir%20Boulema/) 13 | 14 | ## Usage 15 | You can find the Stack Trace Explorer at 16 | 17 | `View - Other Windows - Stack Trace Explorer` 18 | 19 | or the default keybinding 20 | 21 | `Ctrl + S, T` 22 | 23 | ## Features 24 | - Clicking a filepath will open the file at the line mentioned in the stacktrace 25 | - Clicking a method will open the corresponding file at the start of that method 26 | - Wrap long stacktraces 27 | - Syntax highlighting 28 | - Dark theme support 29 | - Tabs 30 | 31 | ### Opening a new tab 32 | There are multiple ways open opening new tabs to show your stack traces: 33 | 34 | **Copy a stack trace to your clipboard:** 35 | - Click the paste as new tab but 36 | - Select a tab, make sure your cursor is not in the text editor, Paste your stack trace with `Ctrl + V` 37 | 38 | **Stack trace from file:** 39 | - Click the open file button 40 | - Drag & drop the file to the toolwindow 41 | 42 | ## Supported stack trace formats 43 | - Visual Studio 44 | - Application Insights 45 | - [Ben.Demystifier](https://github.com/benaadams/Ben.Demystifier) 46 | 47 | ## Screenshots 48 | ![Screenshot](https://i.imgur.com/42mKURv.png) 49 | 50 | ## Debug extension 51 | 52 | On `StackTraceExplorer.VS2022` project property, set 'Debug' properties as the following: 53 | * set radio button to `Start external program` 54 | * browse for Visual Studio executable (like `C:\Program Files\Microsoft Visual Studio\2022\Community\Common7\IDE\devenv.exe`) 55 | * set `/RootSuffix Exp` in `Command line arguments` 56 | 57 | ## Thanks 58 | - [Resharper](https://www.jetbrains.com/resharper/) (for the initial idea to recreate this) 59 | - [Terrajobst](https://github.com/terrajobst/stack-trace-explorer) (for some inspiration on optimizing the extension) 60 | - [dlstucki](https://github.com/dlstucki) (for a large PR optimizing StackTraceExplorer for large solutions) 61 | - [pmiossec](https://github.com/pmiossec) (for several PRs reviving StackTraceExplorer) -------------------------------------------------------------------------------- /Resources/CallStackWindow.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sboulema/StackTraceExplorer/267f8f387860823f5bcab79ebba20564ba357488/Resources/CallStackWindow.ico -------------------------------------------------------------------------------- /Resources/CallStackWindow_256x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sboulema/StackTraceExplorer/267f8f387860823f5bcab79ebba20564ba357488/Resources/CallStackWindow_256x.png -------------------------------------------------------------------------------- /Resources/Preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sboulema/StackTraceExplorer/267f8f387860823f5bcab79ebba20564ba357488/Resources/Preview.png -------------------------------------------------------------------------------- /Resources/StackTraceExplorer.cs: -------------------------------------------------------------------------------- 1 | // ------------------------------------------------------------------------------ 2 | // 3 | // This file was generated by VSIX Synchronizer 4 | // 5 | // ------------------------------------------------------------------------------ 6 | namespace StackTraceExplorer 7 | { 8 | using System; 9 | 10 | /// 11 | /// Helper class that exposes all GUIDs used across VS Package. 12 | /// 13 | internal sealed partial class PackageGuids 14 | { 15 | public const string guidStackTraceExplorerToolWindowPackageString = "0485ea98-864e-461f-945f-3c8f9c994842"; 16 | public static Guid guidStackTraceExplorerToolWindowPackage = new Guid(guidStackTraceExplorerToolWindowPackageString); 17 | 18 | public const string guidStackTraceExplorerToolWindowPackageCmdSetString = "8de831e0-1853-4232-8c10-da7fac5a2b54"; 19 | public static Guid guidStackTraceExplorerToolWindowPackageCmdSet = new Guid(guidStackTraceExplorerToolWindowPackageCmdSetString); 20 | } 21 | /// 22 | /// Helper class that encapsulates all CommandIDs uses across VS Package. 23 | /// 24 | internal sealed partial class PackageIds 25 | { 26 | public const int StackTraceExplorerToolWindowCommandId = 0x0100; 27 | } 28 | } -------------------------------------------------------------------------------- /Resources/StackTraceExplorer.vsct: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /StackTraceExplorer.Shared/CustomLinkVisualLineText.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Windows; 4 | using System.Windows.Input; 5 | using System.Windows.Media; 6 | using System.Windows.Media.TextFormatting; 7 | using Community.VisualStudio.Toolkit; 8 | using ICSharpCode.AvalonEdit.Document; 9 | using ICSharpCode.AvalonEdit.Rendering; 10 | using StackTraceExplorer.Helpers; 11 | 12 | namespace StackTraceExplorer 13 | { 14 | /// 15 | /// VisualLineElement that represents a piece of text and is a clickable link. 16 | /// 17 | public class CustomLinkVisualLineText : VisualLineText 18 | { 19 | public string[] Link { get; } 20 | 21 | public bool RequireControlModifierForClick { get; set; } 22 | 23 | public Brush ForegroundBrush { get; set; } 24 | 25 | public Action ClickFunction { get; set; } 26 | 27 | public TextDocument TextDocument { get; set; } 28 | 29 | public StackTraceEditor TextEditor { get; set; } 30 | 31 | OutputWindowPane OutputWindowPane { get; } 32 | 33 | /// 34 | /// Creates a visual line text element with the specified length. 35 | /// It uses the and its 36 | /// to find the actual text string. 37 | /// 38 | public CustomLinkVisualLineText(string[] theLink, VisualLine parentVisualLine, int length, 39 | Brush foregroundBrush, Action clickFunction, bool requireControlModifierForClick, 40 | TextDocument textDocument, StackTraceEditor textEditor) 41 | : base(parentVisualLine, length) 42 | { 43 | RequireControlModifierForClick = requireControlModifierForClick; 44 | Link = theLink; 45 | ForegroundBrush = foregroundBrush; 46 | ClickFunction = clickFunction; 47 | TextDocument = textDocument; 48 | TextEditor = textEditor; 49 | } 50 | 51 | public override TextRun CreateTextRun(int startVisualColumn, ITextRunConstructionContext context) 52 | { 53 | TextRunProperties.SetForegroundBrush(ForegroundBrush); 54 | 55 | var lineNumber = TextDocument.GetLineByOffset(context.VisualLine.StartOffset).LineNumber; 56 | 57 | if (LinkIsClickable() && 58 | TraceHelper.LineNumber == lineNumber && 59 | TraceHelper.CurrentColumn >= RelativeTextOffset && 60 | TraceHelper.CurrentColumn <= RelativeTextOffset + VisualLength) 61 | { 62 | TextRunProperties.SetTextDecorations(TextDecorations.Underline); 63 | } 64 | 65 | return base.CreateTextRun(startVisualColumn, context); 66 | } 67 | 68 | private bool LinkIsClickable() 69 | { 70 | if (!Link.Any()) 71 | { 72 | return false; 73 | } 74 | 75 | return RequireControlModifierForClick 76 | ? (Keyboard.Modifiers & ModifierKeys.Control) == ModifierKeys.Control 77 | : true; 78 | } 79 | 80 | protected override void OnQueryCursor(QueryCursorEventArgs e) 81 | { 82 | if (!LinkIsClickable()) 83 | { 84 | return; 85 | } 86 | 87 | e.Handled = true; 88 | e.Cursor = Cursors.Hand; 89 | 90 | TraceHelper.TextEditor = TextEditor; 91 | TraceHelper.SetCurrentMouseOffset(e); 92 | 93 | (e.Source as TextView).Redraw(); 94 | } 95 | 96 | protected override void OnMouseDown(MouseButtonEventArgs e) 97 | { 98 | if (e.ChangedButton == MouseButton.Left && !e.Handled && LinkIsClickable()) 99 | { 100 | e.Handled = true; 101 | 102 | ClickFunction(Link, this.TextEditor); 103 | TraceHelper.ViewModel.AddClickedLine(this); 104 | 105 | (e.Source as TextView).Redraw(); 106 | } 107 | } 108 | 109 | protected override VisualLineText CreateInstance(int length) 110 | => new CustomLinkVisualLineText(Link, ParentVisualLine, length, 111 | ForegroundBrush, ClickFunction, false, TextDocument, TextEditor); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /StackTraceExplorer.Shared/Generators/FileLinkElementGenerator.cs: -------------------------------------------------------------------------------- 1 | using System.Text.RegularExpressions; 2 | using System.Windows.Media; 3 | using ICSharpCode.AvalonEdit.Rendering; 4 | using Microsoft.VisualStudio.PlatformUI; 5 | using Microsoft.VisualStudio.Shell; 6 | using StackTraceExplorer.Helpers; 7 | 8 | namespace StackTraceExplorer.Generators 9 | { 10 | public class FileLinkElementGenerator : VisualLineElementGenerator 11 | { 12 | // To use this class: 13 | // textEditor.TextArea.TextView.ElementGenerators.Add(new FileLinkElementGenerator()); 14 | 15 | private readonly StackTraceEditor _textEditor; 16 | public static readonly Regex FilePathRegex = new Regex(@" (?(?(?:[A-Za-z]\:|\\|/)(?:[\\/a-zA-Z_\-\s0-9\.\(\)]+)+):(?:line|Zeile|строка|ligne)?\s?(?\d+))", RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.ExplicitCapture); 17 | 18 | public FileLinkElementGenerator(StackTraceEditor textEditor) 19 | { 20 | _textEditor = textEditor; 21 | } 22 | 23 | private Match FindMatch(int startOffset) 24 | { 25 | // fetch the end offset of the VisualLine being generated 26 | var endOffset = CurrentContext.VisualLine.LastDocumentLine.EndOffset; 27 | var document = CurrentContext.Document; 28 | var relevantText = document.GetText(startOffset, endOffset - startOffset); 29 | return FilePathRegex.Match(relevantText); 30 | } 31 | 32 | /// Gets the first offset >= startOffset where the generator wants to construct 33 | /// an element. 34 | /// Return -1 to signal no interest. 35 | public override int GetFirstInterestedOffset(int startOffset) 36 | { 37 | var m = FindMatch(startOffset); 38 | return m.Success ? startOffset + m.Index : -1; 39 | } 40 | 41 | /// Constructs an element at the specified offset. 42 | /// May return null if no element should be constructed. 43 | public override VisualLineElement ConstructElement(int offset) 44 | { 45 | var match = FindMatch(offset); 46 | 47 | // check whether there's a match exactly at offset 48 | if (!match.Success || match.Index != 0) 49 | { 50 | return null; 51 | } 52 | 53 | var line = new CustomLinkVisualLineText( 54 | new[] { match.Groups["path"].Value, match.Groups["line"].Value }, 55 | CurrentContext.VisualLine, 56 | match.Groups["place"].Length + 1, 57 | ToBrush(EnvironmentColors.ControlLinkTextBrushKey), 58 | ClickHelper.HandleFileLinkClicked, 59 | false, 60 | CurrentContext.Document, 61 | _textEditor 62 | ); 63 | 64 | if (TraceHelper.ViewModel.IsClickedLine(line)) 65 | { 66 | line.ForegroundBrush = ToBrush(EnvironmentColors.ControlLinkTextBrushKey); 67 | } 68 | 69 | return line; 70 | } 71 | 72 | private static SolidColorBrush ToBrush(ThemeResourceKey key) 73 | { 74 | var color = VSColorTheme.GetThemedColor(key); 75 | return new SolidColorBrush(Color.FromArgb(color.A, color.R, color.G, color.B)); 76 | } 77 | } 78 | } -------------------------------------------------------------------------------- /StackTraceExplorer.Shared/Generators/MemberLinkElementGenerator.cs: -------------------------------------------------------------------------------- 1 | using System.Text.RegularExpressions; 2 | using System.Windows.Media; 3 | using ICSharpCode.AvalonEdit.Rendering; 4 | using Microsoft.VisualStudio.PlatformUI; 5 | using Microsoft.VisualStudio.Shell; 6 | using StackTraceExplorer.Helpers; 7 | using System.Linq; 8 | 9 | namespace StackTraceExplorer.Generators 10 | { 11 | public class MemberLinkElementGenerator : VisualLineElementGenerator 12 | { 13 | // To use this class: 14 | // textEditor.TextArea.TextView.ElementGenerators.Add(new MemberLinkElementGenerator()); 15 | 16 | private readonly StackTraceEditor _textEditor; 17 | private static readonly Regex MemberVisualStudioRegex = new Regex(@"(?(?[A-Za-z0-9<>_`+]+\.)*(?(.ctor|[A-Za-z0-9<>_\[,\]|+])+\(.*?\)))", RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.ExplicitCapture); 18 | private static readonly Regex MemberAppInsightsRegex = new Regex(@"(?(?[A-Za-z0-9<>_`+]+\.)*(?(.ctor|[A-Za-z0-9<>_\[,\]|+])+)) \(.+:\d+\)", RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.ExplicitCapture); 19 | private static readonly Regex MemberDemystifiedRegex = new Regex(@"(async )?([A-Za-z0-9<>_`+]+ )(?(?[A-Za-z0-9<>_`+]+\.)*(?(.ctor|[A-Za-z0-9<>_\[,\]|+])+\(.*?\))) ", RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.ExplicitCapture); 20 | 21 | private string _fullMatchText; 22 | 23 | public MemberLinkElementGenerator(StackTraceEditor textEditor) 24 | { 25 | _textEditor = textEditor; 26 | } 27 | 28 | private Match FindMatch(int startOffset) 29 | { 30 | // fetch the end offset of the VisualLine being generated 31 | var endOffset = CurrentContext.VisualLine.LastDocumentLine.EndOffset; 32 | var document = CurrentContext.Document; 33 | var relevantText = document.GetText(startOffset, endOffset - startOffset); 34 | return FindMatch(relevantText); 35 | } 36 | 37 | public static Match FindMatch(string text) 38 | { 39 | var match = MemberDemystifiedRegex.Match(text); 40 | if (match.Success) 41 | return match; 42 | match = MemberAppInsightsRegex.Match(text); 43 | if (match.Success) 44 | return match; 45 | return MemberVisualStudioRegex.Match(text); 46 | } 47 | 48 | /// Gets the first offset >= startOffset where the generator wants to construct 49 | /// an element. 50 | /// Return -1 to signal no interest. 51 | public override int GetFirstInterestedOffset(int startOffset) 52 | { 53 | var m = FindMatch(startOffset); 54 | return m.Success ? startOffset + m.Index : -1; 55 | } 56 | 57 | /// Constructs an element at the specified offset. 58 | /// May return null if no element should be constructed. 59 | public override VisualLineElement ConstructElement(int offset) 60 | { 61 | var match = FindMatch(offset); 62 | // check whether there's a match exactly at offset 63 | if (!match.Success || match.Index != 0) return null; 64 | 65 | // The first match returns the full method definition 66 | if (string.IsNullOrEmpty(_fullMatchText)) 67 | { 68 | _fullMatchText = match.Groups["member"].Value; 69 | } 70 | 71 | var captures = match.Groups["namespace"].Captures.Cast().Select(c => c.Value).ToList(); 72 | captures.Add(match.Groups["method"].Value); 73 | 74 | var firstCapture = captures[0]; 75 | var lineElement = new CustomLinkVisualLineText( 76 | new [] { _fullMatchText, firstCapture }, 77 | CurrentContext.VisualLine, 78 | firstCapture.TrimEnd('.').Length, 79 | ToBrush(EnvironmentColors.ControlLinkTextBrushKey), 80 | ClickHelper.HandleMemberLinkClicked, 81 | false, 82 | CurrentContext.Document, 83 | _textEditor 84 | ); 85 | 86 | // If we have created elements for the entire definition, reset. 87 | // So we can create elements for more definitions 88 | if (_fullMatchText.EndsWith(firstCapture)) 89 | { 90 | _fullMatchText = null; 91 | } 92 | 93 | if (TraceHelper.ViewModel.IsClickedLine(lineElement)) 94 | { 95 | lineElement.ForegroundBrush = ToBrush(EnvironmentColors.ControlLinkTextBrushKey); 96 | } 97 | 98 | return lineElement; 99 | } 100 | 101 | private static SolidColorBrush ToBrush(ThemeResourceKey key) 102 | { 103 | var color = VSColorTheme.GetThemedColor(key); 104 | return new SolidColorBrush(Color.FromArgb(color.A, color.R, color.G, color.B)); 105 | } 106 | } 107 | } -------------------------------------------------------------------------------- /StackTraceExplorer.Shared/Helpers/ClickHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | using Community.VisualStudio.Toolkit; 8 | using Microsoft.VisualStudio.Shell; 9 | using Microsoft.VisualStudio.Text; 10 | using Microsoft.VisualStudio.Text.Editor; 11 | using File = System.IO.File; 12 | using Path = System.IO.Path; 13 | using Task = System.Threading.Tasks.Task; 14 | 15 | namespace StackTraceExplorer.Helpers 16 | { 17 | public static class ClickHelper 18 | { 19 | /// 20 | /// Handle click event on a file path in a stacktrace 21 | /// 22 | /// File path 23 | /// File found 24 | public static void HandleFileLinkClicked(string[] input, StackTraceEditor stackTraceEditor) 25 | { 26 | ThreadHelper.JoinableTaskFactory.Run(async () => 27 | { 28 | var stopwatch = Stopwatch.StartNew(); 29 | OutputWindowPane outputWindow = await stackTraceEditor.EnsureOutputWindowPaneAsync(); 30 | try 31 | { 32 | var path = await Find(input[0], outputWindow, stopwatch); 33 | 34 | if (!File.Exists(path)) 35 | { 36 | await WriteOutputAsync(outputWindow, $"FileLinkClicked: {input[0]}: Unable to resolve file ({stopwatch.Elapsed})", true); 37 | return; 38 | } 39 | 40 | var documentView = await VS.Documents.OpenAsync(path); 41 | 42 | ITextSnapshotLine line = documentView.TextBuffer.CurrentSnapshot.GetLineFromLineNumber(int.Parse(input[1]) - 1); 43 | documentView?.TextView?.ViewScroller.EnsureSpanVisible(line.Extent); 44 | var selectionBroker = documentView.TextView?.GetMultiSelectionBroker(); 45 | if (selectionBroker != null) 46 | { 47 | selectionBroker.SetSelection(new Microsoft.VisualStudio.Text.Selection(line.Extent)); 48 | } 49 | 50 | await WriteOutputAsync(outputWindow, $"FileLinkClicked: {input[0]} finished ({stopwatch.Elapsed})", true); 51 | } 52 | catch (Exception e) 53 | { 54 | await WriteOutputAsync(outputWindow, $"FileLinkClicked: {input[0]} error ({stopwatch.Elapsed}):\r\n{e}", true); 55 | } 56 | }); 57 | } 58 | 59 | /// 60 | /// Handle click event on a member in a stacktrace 61 | /// 62 | /// Function name 63 | public static void HandleMemberLinkClicked(string[] input, StackTraceEditor stackTraceEditor) 64 | { 65 | ThreadHelper.JoinableTaskFactory.Run(async () => 66 | { 67 | var stopwatch = Stopwatch.StartNew(); 68 | string typeOrMemberName = GetTypeOrMemberName(input); 69 | OutputWindowPane outputWindow = await stackTraceEditor.EnsureOutputWindowPaneAsync(); 70 | try 71 | { 72 | if (SolutionHelper.CompilationServiceNotInitialized) 73 | { 74 | await WriteOutputAsync(outputWindow, $"MemberLinkClicked: {typeOrMemberName}: Compilation service not initialized. Build solution first...", showInStatusBar: true); 75 | return; 76 | } 77 | 78 | var member = SolutionHelper.Resolve(typeOrMemberName); 79 | if (member == null) 80 | { 81 | await WriteOutputAsync(outputWindow, $"MemberLinkClicked: {typeOrMemberName}: unable to resolve member ({stopwatch.Elapsed})", showInStatusBar: true); 82 | return; 83 | } 84 | 85 | var location = member.Locations.FirstOrDefault(); 86 | 87 | var documentView = await VS.Documents.OpenAsync(location.SourceTree.FilePath); 88 | var line = documentView.TextBuffer.CurrentSnapshot.GetLineFromLineNumber(location.GetLineSpan().StartLinePosition.Line); 89 | documentView?.TextView?.ViewScroller.EnsureSpanVisible(line.Extent); 90 | var selectionBroker = documentView.TextView?.GetMultiSelectionBroker(); 91 | if (selectionBroker != null) 92 | { 93 | var snapshotSpan = new SnapshotSpan(documentView.TextView.TextSnapshot, location.SourceSpan.Start, location.SourceSpan.Length); 94 | selectionBroker.SetSelection(new Microsoft.VisualStudio.Text.Selection(snapshotSpan)); 95 | } 96 | 97 | await WriteOutputAsync(outputWindow, $"MemberLinkClicked: {typeOrMemberName} finished ({stopwatch.Elapsed})", true); 98 | } 99 | catch (Exception e) 100 | { 101 | await WriteOutputAsync(outputWindow, $"MemberLinkClicked: {typeOrMemberName} error:\r\n{e}", true); 102 | } 103 | }); 104 | } 105 | 106 | static async Task WriteOutputAsync(OutputWindowPane outputWindow, string message, bool showInStatusBar = false) 107 | { 108 | await outputWindow.WriteLineAsync(message); 109 | if (showInStatusBar) 110 | { 111 | await VS.StatusBar.ShowMessageAsync(message); 112 | } 113 | } 114 | 115 | private static readonly Dictionary CacheFolders = new Dictionary(); 116 | 117 | /// 118 | /// Given a path to a file, try to find a project item that closely matches the file path, 119 | /// but is not an exact match 120 | /// 121 | /// 122 | /// 123 | public static async Task Find(string path, OutputWindowPane outputWindow, Stopwatch stopwatch) 124 | { 125 | if (string.IsNullOrEmpty(path)) 126 | { 127 | return string.Empty; 128 | } 129 | 130 | if (File.Exists(path)) 131 | { 132 | await WriteOutputAsync(outputWindow, $"FindFile: '{path}': full path exists ({stopwatch.Elapsed})"); 133 | return path; 134 | } 135 | 136 | foreach (var mapping in CacheFolders) 137 | { 138 | if (path.StartsWith(mapping.Key)) 139 | { 140 | var potentialPath = path.Replace(mapping.Key, mapping.Value); 141 | if (File.Exists(potentialPath)) 142 | { 143 | await WriteOutputAsync(outputWindow, $"FindFile: '{potentialPath}': full path exists ({stopwatch.Elapsed})"); 144 | return potentialPath; 145 | } 146 | } 147 | } 148 | 149 | string fileNameOnly = Path.GetFileName(path); 150 | 151 | var solution = await VS.Solutions.GetCurrentSolutionAsync(); 152 | if (solution == null) 153 | { 154 | await WriteOutputAsync(outputWindow, $"FindFile: '{path}': No solution ({stopwatch.Elapsed})"); 155 | return string.Empty; 156 | } 157 | 158 | var solutionDir = new DirectoryInfo(Path.GetDirectoryName(solution.FullPath)); 159 | try 160 | { 161 | await outputWindow.WriteLineAsync($"FindFile: '{path}' looking for '{fileNameOnly}' in '{solutionDir.FullName}'"); 162 | FileInfo[] fileInfos = solutionDir.GetFiles($"{fileNameOnly}", SearchOption.AllDirectories); 163 | string[] files = fileInfos.Select(fi => fi.FullName).ToArray(); 164 | await outputWindow.WriteLineAsync($"FindFile: '{path}' found {files.Length} potential matches ({stopwatch.Elapsed})"); 165 | if (files.Length == 0) 166 | { 167 | return string.Empty; 168 | } 169 | 170 | var candidates = new List(); 171 | candidates.AddRange(files.Select(f => f.Replace('\\', '/'))); 172 | candidates.AddRange(files.Select(f => f.Replace('/', '\\'))); 173 | 174 | string file = StringHelper.FindLongestMatchingSuffix(path, candidates.ToArray(), StringComparison.OrdinalIgnoreCase); 175 | 176 | if (file != null) 177 | { 178 | var paths = LongestUncommonPath(path, file); 179 | CacheFolders[paths.Item1] = paths.Item2; 180 | await outputWindow.WriteLineAsync($"FindFile: Returning file: '{file}' ({stopwatch.Elapsed})"); 181 | return file; 182 | } 183 | } 184 | catch (Exception e) 185 | { 186 | await outputWindow.WriteLineAsync($"FindFile: '{path}' ({stopwatch.Elapsed}) error:\r\n{e}"); 187 | } 188 | 189 | await outputWindow.WriteLineAsync($"FindFile: '{path}' returning original path! Is this bad? ({stopwatch.Elapsed})'"); 190 | return path; 191 | } 192 | 193 | private static (string, string) LongestUncommonPath(string s1, string s2) 194 | { 195 | var minLength = Math.Min(s1.Length, s2.Length); 196 | var s1Sep = s1.Replace('/', '\\'); 197 | var s2Sep = s2.Replace('/', '\\'); 198 | for (int i = 0; i < minLength; i++) 199 | { 200 | if (s1Sep[s1Sep.Length-i-1] == s2Sep[s2Sep.Length-i-1]) 201 | { 202 | continue; 203 | } 204 | else 205 | { 206 | var pathsCached = (s1.Substring(0, s1.Length - i + 1), s2.Substring(0, s2.Length - i + 1)); 207 | return pathsCached; 208 | } 209 | } 210 | 211 | //Should not happen... 212 | return (string.Empty, string.Empty); 213 | } 214 | 215 | /// 216 | /// Construct the member identification 217 | /// 218 | /// 219 | /// 220 | public static string GetTypeOrMemberName(string[] input) 221 | { 222 | var needle = input[1].TrimEnd('.'); 223 | var index = input[0].IndexOf(needle); 224 | 225 | var result = input[0].Substring(0, index + needle.Length); 226 | 227 | return result; 228 | } 229 | 230 | internal static void ClearCache() 231 | { 232 | CacheFolders.Clear(); 233 | } 234 | } 235 | } 236 | -------------------------------------------------------------------------------- /StackTraceExplorer.Shared/Helpers/SolutionHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.Immutable; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using Microsoft.CodeAnalysis; 8 | 9 | namespace StackTraceExplorer.Helpers 10 | { 11 | public static class SolutionHelper 12 | { 13 | public static Solution Solution { get; set; } 14 | public static ImmutableArray Compilations { get; set; } 15 | 16 | public static async Task> GetCompilationsAsync(Solution solution) 17 | { 18 | var compilationTasks = new Task[solution.ProjectIds.Count]; 19 | for (var i = 0; i < compilationTasks.Length; i++) 20 | { 21 | var project = solution.GetProject(solution.ProjectIds[i]); 22 | compilationTasks[i] = project.GetCompilationAsync(); 23 | } 24 | 25 | _ = await Task.WhenAll(compilationTasks); 26 | 27 | Compilations = compilationTasks.Select(t => t.Result).ToImmutableArray(); 28 | return Compilations; 29 | } 30 | 31 | public static bool CompilationServiceNotInitialized => Compilations.IsDefault; 32 | 33 | public static ISymbol Resolve(string memberName) 34 | { 35 | ISymbol symbol = Compilations.Select(c => Resolve(c, memberName)).FirstOrDefault(s => s != null); 36 | return symbol; 37 | } 38 | 39 | public static ISymbol Resolve(Compilation compilation, string methodName) 40 | { 41 | var parts = methodName.Replace(".ctor", "#ctor").Split('.'); 42 | 43 | var currentContainer = (INamespaceOrTypeSymbol)compilation.Assembly.Modules.Single().GlobalNamespace; 44 | 45 | for (var i = 0; currentContainer != null && i < parts.Length - 1; i++) 46 | { 47 | ParseTypeName(parts[i], out var typeOrNamespaceName, out var typeArity); 48 | if (string.IsNullOrEmpty(typeOrNamespaceName) && IsCompilerGeneratedName(parts[i])) 49 | { 50 | // We could be dealing with a name like "SolutionHelperTests.<>c.b__6_5()". On the member "<>c" we need to 51 | // skip to the next part in order to find a member with the name, such as 'CreateSomeStackTraces" from the example above. 52 | continue; 53 | } 54 | 55 | currentContainer = currentContainer 56 | .GetMembers(typeOrNamespaceName) 57 | .Where(n => typeArity == 0 || (n is INamedTypeSymbol t && t.Arity == typeArity)) 58 | .FirstOrDefault() as INamespaceOrTypeSymbol; 59 | } 60 | 61 | if (currentContainer == null) 62 | { 63 | return null; 64 | } 65 | 66 | string lastPart = parts.Last(); 67 | var name = GetMemberName(lastPart); 68 | var members = currentContainer.GetMembers(name); 69 | 70 | if (!members.Any()) 71 | { 72 | return null; 73 | } 74 | 75 | int methodArity = GetMethodArity(lastPart); 76 | IReadOnlyList parameterTypes = GetMethodParameterTypes(lastPart); 77 | bool isCompilerGenerated = IsCompilerGeneratedName(lastPart); 78 | 79 | foreach (ISymbol member in members) 80 | { 81 | switch (member.Kind) 82 | { 83 | case SymbolKind.Method: 84 | if (member is IMethodSymbol method) 85 | { 86 | // isCompilerGenerated allows for "b__7(String s)" to match "CreateSomeStackTraces" which 87 | // might have a different number of parameters than what is found here. 88 | if (IsMatch(method, parameterTypes, methodArity) || isCompilerGenerated) 89 | { 90 | return method; 91 | } 92 | } 93 | 94 | break; 95 | case SymbolKind.NamedType: 96 | ParseTypeName(lastPart, out _, out int typeArity); 97 | if (member is INamedTypeSymbol namedTypeSymbol && namedTypeSymbol.Arity == typeArity) 98 | { 99 | return member; 100 | } 101 | break; 102 | default: 103 | return member; 104 | } 105 | } 106 | 107 | return null; 108 | } 109 | 110 | /// 111 | /// Determine if the name is generated by the compiler. Examples include iterators, closures, anonymous methods, lambdas 112 | /// examples: 113 | /// Types: "<>c", "<>c__DisplayClass", and so on "<>c__DisplayClass5_0`2" 114 | /// Methods: "b__6_5()", "b__2_1(IAsyncResult r)" 115 | /// 116 | static bool IsCompilerGeneratedName(string memberName) 117 | { 118 | return memberName.StartsWith("<", StringComparison.OrdinalIgnoreCase); 119 | } 120 | 121 | private static void ParseTypeName(string typeName, out string name, out int arity) 122 | { 123 | name = typeName; 124 | arity = 0; 125 | 126 | var backtick = typeName.IndexOf('`'); 127 | int angleBracketOpen = typeName.IndexOf('<'); 128 | if (angleBracketOpen >= 0) 129 | { 130 | int angleBracketClose = typeName.IndexOf('>', angleBracketOpen); 131 | if (angleBracketClose > 0) 132 | { 133 | name = typeName.Substring(angleBracketOpen + 1, angleBracketClose - angleBracketOpen - 1); 134 | } 135 | } 136 | 137 | if (backtick >= 0) 138 | { 139 | name = typeName.Substring(0, backtick); 140 | var arityText = typeName.Substring(backtick + 1); 141 | arity = int.Parse(arityText); 142 | } 143 | } 144 | 145 | public static string GetMemberName(string memberNameAndSignature) 146 | { 147 | string result = memberNameAndSignature; 148 | if (IsCompilerGeneratedName(memberNameAndSignature)) 149 | { 150 | // This could be an anonymous method name like "b__6_5()", in that case 151 | // return "CreateSomeStackTraces" 152 | result = memberNameAndSignature.Split(new[] { '<', '>' }, StringSplitOptions.RemoveEmptyEntries)[0]; 153 | } 154 | else 155 | { 156 | int firstSeparator = memberNameAndSignature.IndexOfAny(new[] { '(', '[', '`' }); 157 | if (firstSeparator != -1) 158 | { 159 | result = memberNameAndSignature.Substring(0, firstSeparator); 160 | } 161 | 162 | if (result == "#ctor") 163 | { 164 | result = ".ctor"; 165 | } 166 | } 167 | 168 | return result; 169 | } 170 | 171 | private static int GetMethodArity(string methodNameAndSignature) 172 | { 173 | var parenthesis = methodNameAndSignature.IndexOf('('); 174 | if (parenthesis < 0) 175 | { 176 | return 0; 177 | } 178 | 179 | var openBracket = methodNameAndSignature.IndexOf('[', 0, parenthesis); 180 | if (openBracket < 0) 181 | { 182 | return 0; 183 | } 184 | 185 | var closeBracket = methodNameAndSignature.IndexOf(']', 0, parenthesis); 186 | if (closeBracket < 0) 187 | { 188 | return 0; 189 | } 190 | 191 | var result = 1; 192 | for (var i = openBracket; i <= closeBracket; i++) 193 | { 194 | if (methodNameAndSignature[i] == ',') 195 | { 196 | result++; 197 | } 198 | } 199 | 200 | return result; 201 | } 202 | 203 | private static IReadOnlyList GetMethodParameterTypes(string methodNameAndSignature) 204 | { 205 | var openParenthesis = methodNameAndSignature.IndexOf('('); 206 | if (openParenthesis < 0) 207 | { 208 | return Array.Empty(); 209 | } 210 | 211 | var closeParenthesis = methodNameAndSignature.IndexOf(')'); 212 | var signatureStart = openParenthesis + 1; 213 | var signatureLength = closeParenthesis - signatureStart; 214 | var signature = methodNameAndSignature.Substring(signatureStart, signatureLength); 215 | var parameters = signature.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries); 216 | 217 | for (var i = 0; i < parameters.Length; i++) 218 | { 219 | parameters[i] = parameters[i].Trim(); 220 | } 221 | 222 | var result = new List(parameters.Length); 223 | 224 | foreach (var parameter in parameters) 225 | { 226 | var space = parameter.IndexOf(' '); 227 | var typeName = parameter.Substring(0, space); 228 | result.Add(typeName); 229 | } 230 | 231 | return result; 232 | } 233 | 234 | private static bool IsMatch(IMethodSymbol method, IReadOnlyList parameterTypes, int methodArity) 235 | { 236 | if (method.Arity != methodArity || method.Parameters.Length != parameterTypes.Count) 237 | { 238 | return false; 239 | } 240 | 241 | for (var i = 0; i < method.Parameters.Length; i++) 242 | { 243 | var symbolTypeName = GetTypeName(method.Parameters[i]); 244 | var frameTypename = parameterTypes[i]; 245 | 246 | if (symbolTypeName != frameTypename) 247 | { 248 | return false; 249 | } 250 | } 251 | 252 | return true; 253 | } 254 | 255 | private static string GetTypeName(IParameterSymbol symbol) 256 | { 257 | var sb = new StringBuilder(); 258 | 259 | if (symbol.Type is IArrayTypeSymbol array) 260 | { 261 | sb.Append(array.ElementType.MetadataName); 262 | sb.Append("[]"); 263 | } 264 | else if (symbol.Type is IPointerTypeSymbol pointer) 265 | { 266 | sb.Append(pointer.PointedAtType.MetadataName); 267 | sb.Append('*'); 268 | } 269 | else 270 | { 271 | sb.Append(symbol.Type.MetadataName); 272 | } 273 | 274 | if (symbol.RefKind != RefKind.None) 275 | { 276 | sb.Append("&"); 277 | } 278 | 279 | return sb.ToString(); 280 | } 281 | } 282 | } -------------------------------------------------------------------------------- /StackTraceExplorer.Shared/Helpers/StringHelper.cs: -------------------------------------------------------------------------------- 1 | namespace StackTraceExplorer.Helpers 2 | { 3 | using System; 4 | using System.IO; 5 | using System.Linq; 6 | 7 | public static class StringHelper 8 | { 9 | private static readonly char[] Separators = new char[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar }; 10 | public static string FindLongestMatchingSuffix(string searchString, string[] candidates, StringComparison comparisonType) 11 | { 12 | int nextSeparatorIndex = 0; 13 | int previousSeparatorIndex = -1; 14 | while (true) 15 | { 16 | string suffixToSearchFor; 17 | if (previousSeparatorIndex == -1) 18 | { 19 | // Search for the whole string the first time 20 | suffixToSearchFor = searchString; 21 | } 22 | else 23 | { 24 | nextSeparatorIndex = searchString.IndexOfAny(Separators, previousSeparatorIndex); 25 | if (nextSeparatorIndex == -1) 26 | { 27 | break; 28 | } 29 | 30 | suffixToSearchFor = searchString.Substring(nextSeparatorIndex); 31 | } 32 | 33 | string match = candidates.FirstOrDefault(s => s.EndsWith(suffixToSearchFor, comparisonType)); 34 | if (match != null) 35 | { 36 | return match; 37 | } 38 | 39 | previousSeparatorIndex = nextSeparatorIndex + 1; 40 | } 41 | 42 | throw new ArgumentException("None of the candidates match", nameof(candidates)); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /StackTraceExplorer.Shared/Helpers/TraceHelper.cs: -------------------------------------------------------------------------------- 1 | using System.Windows.Input; 2 | using Microsoft.VisualStudio.ComponentModelHost; 3 | using StackTraceExplorer.Models; 4 | using TextEditor = ICSharpCode.AvalonEdit.TextEditor; 5 | 6 | namespace StackTraceExplorer.Helpers 7 | { 8 | public static class TraceHelper 9 | { 10 | public static int LineNumber; 11 | public static TextEditor TextEditor; 12 | public static int CurrentColumn; 13 | public static IComponentModel ComponentModel; 14 | public static StackTracesViewModel ViewModel; 15 | 16 | public static void SetCurrentMouseOffset(QueryCursorEventArgs e) 17 | { 18 | var pos = TextEditor.GetPositionFromPoint(e.GetPosition(TextEditor)); 19 | 20 | if (pos == null) 21 | { 22 | return; 23 | } 24 | 25 | LineNumber = pos.Value.Line; 26 | CurrentColumn = pos.Value.Column; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /StackTraceExplorer.Shared/Models/StackTrace.cs: -------------------------------------------------------------------------------- 1 | using ICSharpCode.AvalonEdit.Document; 2 | using Microsoft.VisualStudio.PlatformUI; 3 | using StackTraceExplorer.Helpers; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Text.RegularExpressions; 8 | 9 | namespace StackTraceExplorer.Shared.Models 10 | { 11 | public class StackTrace : ObservableObject 12 | { 13 | public TextDocument Document { get; set; } 14 | 15 | public List ClickedLines { get; set; } = new List(); 16 | 17 | public StackTrace(string trace = null) 18 | => SetStackTrace(trace); 19 | 20 | private bool _wordWrap; 21 | 22 | public bool WordWrap 23 | { 24 | get => _wordWrap; 25 | set => SetProperty(ref _wordWrap, value); 26 | } 27 | 28 | public void SetStackTrace(string trace) 29 | { 30 | Document = new TextDocument { Text = WrapStackTrace(trace) }; 31 | ClickHelper.ClearCache(); 32 | NotifyPropertyChanged("Document"); 33 | } 34 | 35 | public void AddClickedLine(CustomLinkVisualLineText line) 36 | => ClickedLines.Add(line); 37 | 38 | public bool IsClickedLine(CustomLinkVisualLineText line) 39 | => ClickedLines.Any(l => l.Link.SequenceEqual(line.Link)); 40 | 41 | private string WrapStackTrace(string trace) 42 | { 43 | if (string.IsNullOrEmpty(trace)) 44 | { 45 | return string.Empty; 46 | } 47 | 48 | if (trace.Contains(Environment.NewLine)) 49 | { 50 | return trace; 51 | } 52 | 53 | var lines = Regex 54 | .Split(trace, @"(?=\s+(at|в|à)\s+)", RegexOptions.Compiled) 55 | .Where(line => !string.IsNullOrEmpty(line)) 56 | .Where(line => !string.IsNullOrWhiteSpace(line)) 57 | .Where(line => line != "at") 58 | .Where(line => line != "в") 59 | .Where(line => line != "à"); 60 | 61 | return string.Join(Environment.NewLine, lines); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /StackTraceExplorer.Shared/Models/StackTracesViewModel.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.PlatformUI; 2 | using StackTraceExplorer.Shared.Models; 3 | using System.Collections.ObjectModel; 4 | 5 | namespace StackTraceExplorer.Models 6 | { 7 | public class StackTracesViewModel : ObservableObject 8 | { 9 | private ObservableCollection _stackTraces = new ObservableCollection(); 10 | 11 | public ObservableCollection StackTraces 12 | { 13 | get => _stackTraces; 14 | set => SetProperty(ref _stackTraces, value); 15 | } 16 | 17 | private int _selectedStackTraceIndex; 18 | 19 | public int SelectedStackTraceIndex 20 | { 21 | get => _selectedStackTraceIndex; 22 | set => SetProperty(ref _selectedStackTraceIndex, value); 23 | } 24 | 25 | public void AddStackTrace(string trace) 26 | { 27 | _stackTraces.Add(new StackTrace(trace)); 28 | NotifyPropertyChanged("StackTraces"); 29 | } 30 | 31 | public void SetStackTrace(string trace) 32 | => _stackTraces[_selectedStackTraceIndex].SetStackTrace(trace); 33 | 34 | public void AddClickedLine(CustomLinkVisualLineText line) 35 | => _stackTraces[_selectedStackTraceIndex].AddClickedLine(line); 36 | 37 | public bool IsClickedLine(CustomLinkVisualLineText line) 38 | => _stackTraces[_selectedStackTraceIndex].IsClickedLine(line); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /StackTraceExplorer.Shared/StackTraceEditor.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Community.VisualStudio.Toolkit; 3 | using ICSharpCode.AvalonEdit; 4 | using StackTraceExplorer.Generators; 5 | 6 | namespace StackTraceExplorer 7 | { 8 | public class StackTraceEditor : TextEditor 9 | { 10 | OutputWindowPane outputWindowPane; 11 | 12 | public StackTraceEditor() 13 | { 14 | TextArea.TextView.ElementGenerators.Add(new FileLinkElementGenerator(this)); 15 | TextArea.TextView.ElementGenerators.Add(new MemberLinkElementGenerator(this)); 16 | } 17 | 18 | public async Task EnsureOutputWindowPaneAsync() 19 | { 20 | const string OutputWindowName = nameof(StackTraceExplorer); 21 | if (this.outputWindowPane == null) 22 | { 23 | this.outputWindowPane = await VS.Windows.CreateOutputWindowPaneAsync(OutputWindowName); 24 | } 25 | 26 | return this.outputWindowPane; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /StackTraceExplorer.Shared/StackTraceExplorer.Shared.projitems: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | $(MSBuildAllProjects);$(MSBuildThisFileFullPath) 5 | true 6 | 2732f00a-56da-436f-8cc4-baea756330d3 7 | 8 | 9 | StackTraceExplorer.Shared 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | StackTraceExplorerToolWindowControl.xaml 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | MSBuild:Compile 35 | Designer 36 | 37 | 38 | -------------------------------------------------------------------------------- /StackTraceExplorer.Shared/StackTraceExplorer.Shared.shproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 2732f00a-56da-436f-8cc4-baea756330d3 5 | 14.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /StackTraceExplorer.Shared/StackTraceExplorer.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | text/microsoft-resx 91 | 92 | 93 | 1.3 94 | 95 | 96 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 97 | 98 | 99 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.3500.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 100 | 101 | -------------------------------------------------------------------------------- /StackTraceExplorer.Shared/StackTraceExplorerToolWindow.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using System.Windows; 6 | using Community.VisualStudio.Toolkit; 7 | using Microsoft.VisualStudio.Imaging; 8 | using Microsoft.VisualStudio.Shell; 9 | 10 | namespace StackTraceExplorer 11 | { 12 | public class StackTraceExplorerToolWindow : BaseToolWindow 13 | { 14 | private StackTraceExplorerToolWindowControl _control; 15 | 16 | public override string GetTitle(int toolWindowId) => "Stack Trace Explorer"; 17 | 18 | public override Type PaneType => typeof(Pane); 19 | 20 | public override async Task CreateAsync(int toolWindowId, CancellationToken cancellationToken) 21 | { 22 | RegisterEvents(); 23 | 24 | await Package.JoinableTaskFactory.SwitchToMainThreadAsync(); 25 | 26 | _control = new StackTraceExplorerToolWindowControl(); 27 | 28 | return _control; 29 | } 30 | 31 | [Guid("7648448a-48ab-4c10-968a-1b2ce0386050")] 32 | public class Pane : ToolWindowPane 33 | { 34 | public Pane() 35 | { 36 | BitmapImageMoniker = KnownMonikers.CallStackWindow; 37 | } 38 | } 39 | 40 | private void RegisterEvents() 41 | { 42 | VS.Events.WindowEvents.ActiveFrameChanged += WindowEvents_ActiveFrameChanged; 43 | } 44 | 45 | private void WindowEvents_ActiveFrameChanged(ActiveFrameChangeEventArgs obj) 46 | { 47 | if (obj.NewFrame.Caption.Equals("Stack Trace Explorer")) 48 | { 49 | _control.EnsureOneStackTrace(); 50 | } 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /StackTraceExplorer.Shared/StackTraceExplorerToolWindowCommand.cs: -------------------------------------------------------------------------------- 1 | using Community.VisualStudio.Toolkit; 2 | using Microsoft.VisualStudio.Shell; 3 | using Task = System.Threading.Tasks.Task; 4 | 5 | namespace StackTraceExplorer 6 | { 7 | [Command(PackageGuids.guidStackTraceExplorerToolWindowPackageCmdSetString, PackageIds.StackTraceExplorerToolWindowCommandId)] 8 | internal sealed class StackTraceExplorerToolWindowCommand : BaseCommand 9 | { 10 | protected override async Task ExecuteAsync(OleMenuCmdEventArgs e) 11 | => await StackTraceExplorerToolWindow.ShowAsync(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /StackTraceExplorer.Shared/StackTraceExplorerToolWindowControl.xaml: -------------------------------------------------------------------------------- 1 |  14 | 15 | 18 | 19 | 20 | 23 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 125 | 126 | 133 | 134 | 141 | 142 | 143 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | -------------------------------------------------------------------------------- /StackTraceExplorer.Shared/StackTraceExplorerToolWindowControl.xaml.cs: -------------------------------------------------------------------------------- 1 | using ICSharpCode.AvalonEdit; 2 | using Microsoft.VisualStudio.LanguageServices; 3 | using StackTraceExplorer.Helpers; 4 | using StackTraceExplorer.Models; 5 | using System.IO; 6 | using System.Linq; 7 | using System.Windows; 8 | using System.Windows.Input; 9 | 10 | namespace StackTraceExplorer 11 | { 12 | public partial class StackTraceExplorerToolWindowControl 13 | { 14 | public StackTracesViewModel ViewModel { get; set; } 15 | 16 | public StackTraceExplorerToolWindowControl() 17 | { 18 | InitializeComponent(); 19 | 20 | KeyDown += StackTraceExplorerToolWindowControl_KeyDown; 21 | Drop += StackTraceExplorerToolWindowControl_Drop; 22 | 23 | ViewModel = new StackTracesViewModel(); 24 | DataContext = ViewModel; 25 | TraceHelper.ViewModel = ViewModel; 26 | 27 | EnsureOneStackTrace(); 28 | } 29 | 30 | /// 31 | /// Always have one tab available in the toolwindow 32 | /// 33 | public void EnsureOneStackTrace() 34 | { 35 | if (!ViewModel.StackTraces.Any()) 36 | { 37 | AddStackTrace(); 38 | } 39 | } 40 | 41 | /// 42 | /// Add a tab to the toolwindow with the pasted stacktrace 43 | /// 44 | /// stack trace 45 | public void AddStackTrace(string trace = "") 46 | { 47 | ViewModel.AddStackTrace(trace); 48 | StackTraceTabs.SelectedIndex = StackTraceTabs.Items.Count - 1; 49 | } 50 | 51 | /// 52 | /// Add a tab to the toolwindow after presenting an open file dialog 53 | /// 54 | public void AddStackTraceFromFile() 55 | { 56 | var openFileDialog = new System.Windows.Forms.OpenFileDialog(); 57 | if (openFileDialog.ShowDialog() == System.Windows.Forms.DialogResult.OK) 58 | { 59 | AddStackTraceFromPath(openFileDialog.FileName); 60 | } 61 | } 62 | 63 | /// 64 | /// Add a tab to the toolwindow with file contents 65 | /// 66 | /// path to the file 67 | public void AddStackTraceFromPath(string path) 68 | { 69 | if (File.Exists(path)) 70 | { 71 | AddStackTrace(File.ReadAllText(path)); 72 | } 73 | } 74 | 75 | #region Events 76 | private void ButtonPaste_OnClick(object sender, RoutedEventArgs e) 77 | => ViewModel.SetStackTrace(Clipboard.GetText()); 78 | 79 | private void ButtonPasteAsNew_OnClick(object sender, RoutedEventArgs e) 80 | => AddStackTrace(Clipboard.GetText()); 81 | 82 | private void ButtonOpenFile_OnClick(object sender, RoutedEventArgs e) 83 | => AddStackTraceFromFile(); 84 | 85 | // In use through XAML binding 86 | private async void TextEditor_TextChanged(object sender, System.EventArgs e) 87 | { 88 | var textEditor = sender as TextEditor; 89 | var trace = textEditor.Document?.Text; 90 | 91 | if (string.IsNullOrEmpty(trace)) 92 | { 93 | return; 94 | } 95 | 96 | int selectionStart = textEditor.SelectionStart; 97 | textEditor.TextChanged -= TextEditor_TextChanged; 98 | ViewModel.SetStackTrace(trace); 99 | textEditor.TextChanged += TextEditor_TextChanged; 100 | textEditor.SelectionStart = selectionStart; 101 | 102 | var workspace = TraceHelper.ComponentModel.GetService(); 103 | SolutionHelper.Solution = workspace.CurrentSolution; 104 | await SolutionHelper.GetCompilationsAsync(workspace.CurrentSolution); 105 | } 106 | 107 | private void StackTraceExplorerToolWindowControl_Drop(object sender, DragEventArgs e) 108 | { 109 | var dropped = (string[])e.Data.GetData(DataFormats.FileDrop); 110 | var files = dropped; 111 | 112 | if (!files.Any()) 113 | { 114 | return; 115 | } 116 | 117 | foreach (var file in files) 118 | { 119 | AddStackTraceFromPath(file); 120 | } 121 | 122 | e.Handled = true; 123 | } 124 | 125 | private void CloseButton_MouseDown(object sender, MouseButtonEventArgs e) 126 | { 127 | if (StackTraceTabs.SelectedIndex >= 0) 128 | { 129 | ViewModel.StackTraces.RemoveAt(StackTraceTabs.SelectedIndex); 130 | } 131 | 132 | EnsureOneStackTrace(); 133 | } 134 | 135 | private void StackTraceExplorerToolWindowControl_KeyDown(object sender, KeyEventArgs e) 136 | { 137 | if (e.Key == Key.V && Keyboard.Modifiers == ModifierKeys.Control) 138 | { 139 | AddStackTrace(Clipboard.GetText()); 140 | } 141 | base.OnKeyDown(e); 142 | } 143 | #endregion 144 | } 145 | } -------------------------------------------------------------------------------- /StackTraceExplorer.Shared/StackTraceExplorerToolWindowPackage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | using System.Threading; 4 | using Community.VisualStudio.Toolkit; 5 | using Microsoft.VisualStudio.ComponentModelHost; 6 | using Microsoft.VisualStudio.Shell; 7 | using StackTraceExplorer.Helpers; 8 | using Task = System.Threading.Tasks.Task; 9 | 10 | namespace StackTraceExplorer 11 | { 12 | [Guid(PackageGuids.guidStackTraceExplorerToolWindowPackageString)] 13 | [PackageRegistration(UseManagedResourcesOnly = true, AllowsBackgroundLoading = true)] 14 | [InstalledProductRegistration("#110", "#112", "1.0", IconResourceID = 400)] 15 | [ProvideToolWindow(typeof(StackTraceExplorerToolWindow.Pane))] 16 | [ProvideMenuResource("Menus.ctmenu", 1)] 17 | public sealed class StackTraceExplorerToolWindowPackage : ToolkitPackage 18 | { 19 | protected override async Task InitializeAsync(CancellationToken cancellationToken, IProgress progress) 20 | { 21 | StackTraceExplorerToolWindow.Initialize(this); 22 | 23 | await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); 24 | 25 | await StackTraceExplorerToolWindowCommand.InitializeAsync(this); 26 | 27 | TraceHelper.ComponentModel = await GetServiceAsync(typeof(SComponentModel)) as IComponentModel; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /StackTraceExplorer.Tests/FileRegexTests.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.VisualStudio.TestTools.UnitTesting; 2 | using StackTraceExplorer.Generators; 3 | 4 | namespace StackTraceExplorer.Tests 5 | { 6 | [TestClass] 7 | public class FileRegexTests 8 | { 9 | [DataTestMethod] 10 | [DataRow( 11 | @"at Bla.Program.InnerClass.InnerMain() in C:\repos\Bla.Program\InnerClass.cs:line 168", 12 | @"C:\repos\Bla.Program\InnerClass.cs:line 168", 13 | @"C:\repos\Bla.Program\InnerClass.cs", 14 | "168")] 15 | [DataRow( 16 | @"at Bla.Program.InnerClass.InnerMain() in D:\repos\Bla.Program\Dash-File.cs:line 3005", 17 | @"D:\repos\Bla.Program\Dash-File.cs:line 3005", 18 | @"D:\repos\Bla.Program\Dash-File.cs", 19 | "3005")] 20 | [DataRow( 21 | @"at Bla.Program.InnerClass.InnerMain() in C:\repos\Bla.Program\Dot.File.cs:line 3", 22 | @"C:\repos\Bla.Program\Dot.File.cs:line 3", 23 | @"C:\repos\Bla.Program\Dot.File.cs", 24 | "3")] 25 | [DataRow( 26 | @"at Program.ApplicationMdi.b__59_0() in E:\Repos\Underscore_File.cs:line 375", 27 | @"E:\Repos\Underscore_File.cs:line 375", 28 | @"E:\Repos\Underscore_File.cs", 29 | "375")] 30 | [DataRow( 31 | @"at CodeNav.Helpers.HistoryHelper.AddItemToHistory(CodeNav.VS2019, Version= 8.8.28.0, Culture= neutral, PublicKeyToken= null: D:\a\CodeNav\CodeNav\CodeNav.Shared\Helpers\HistoryHelper.cs:21)", 32 | @"D:\a\CodeNav\CodeNav\CodeNav.Shared\Helpers\HistoryHelper.cs:21", 33 | @"D:\a\CodeNav\CodeNav\CodeNav.Shared\Helpers\HistoryHelper.cs", 34 | "21")] 35 | [DataRow( 36 | @"at Dapper.SqlMapper+d__69`1.MoveNext (Dapper, Version=2.0.0.0, Culture=neutral, PublicKeyToken=null: /_/Dapper/SqlMapper.Async.cs:1241)", 37 | @"/_/Dapper/SqlMapper.Async.cs:1241", 38 | @"/_/Dapper/SqlMapper.Async.cs", 39 | "1241")] 40 | public void ShouldMatch(string input, string expectedPlace, string expectedFile, string expectedLine) 41 | { 42 | var match = FileLinkElementGenerator.FilePathRegex.Match(input); 43 | 44 | Assert.IsTrue(match.Success, "Match was not a success!"); 45 | Assert.AreEqual(expectedPlace, match.Groups["place"].Value, nameof(expectedPlace)); 46 | Assert.AreEqual(expectedFile, match.Groups["path"].Value, nameof(expectedFile)); 47 | Assert.AreEqual(expectedLine, match.Groups["line"].Value, nameof(expectedLine)); 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /StackTraceExplorer.Tests/LongestSuffixTests.cs: -------------------------------------------------------------------------------- 1 | namespace StackTraceExplorer.Tests 2 | { 3 | using System; 4 | using Microsoft.VisualStudio.TestTools.UnitTesting; 5 | using StackTraceExplorer.Helpers; 6 | 7 | [TestClass] 8 | public class LongestSuffixTests 9 | { 10 | [DataTestMethod] 11 | [DataRow( 12 | @"D:\bldserver01\src\product\Project1\Sample.cs", 13 | new[] { @"C:\Sample.cs", @"C:\src\Project1\Sample.cs", @"D:\src\product\Project1\Sample.cs" }, 14 | @"D:\src\product\Project1\Sample.cs")] 15 | [DataRow( 16 | @"D:\bldserver01\src\product\Project1\Sample.cs", 17 | new[] { @"C:\Sample.cs", @"C:\src\Project1\Sample.cs", @"C:\product\Project1\Sample.cs" }, 18 | @"C:\product\Project1\Sample.cs")] 19 | [DataRow( 20 | @"D:\test\product\Project1\Sample.cs", 21 | new[] { @"C:\test\product\Project1\Sample.cs", @"D:\test\product\Project1\Sample.cs" }, 22 | @"D:\test\product\Project1\Sample.cs")] 23 | [DataRow( 24 | @"/src/test/product/Project1/Sample.cs", 25 | new[] { @"/target/test/product/Project1/Sample.cs", @"/src/product/Project1/Sample.cs" }, 26 | @"/target/test/product/Project1/Sample.cs")] 27 | public void LongestSuffixMatch(string path, string[] candidates, string expected) 28 | { 29 | string longestMatched = StringHelper.FindLongestMatchingSuffix(path, candidates, StringComparison.OrdinalIgnoreCase); 30 | Assert.AreEqual(expected, longestMatched); 31 | } 32 | 33 | [DataTestMethod] 34 | [DataRow( 35 | @"D:\src\Project1\DifferentFileName.cs", 36 | new[] { @"C:\DifferentFileName2.cs", @"C:\src\Project1\DifferentFileName2.cs", }, 37 | "None of the candidates match")] 38 | [DataRow( 39 | @"C:\CandidateFileNameMatchingWithSuffix.cs", 40 | new[] { @"C:\OtherCandidateFileNameMatchingWithSuffix.cs", }, 41 | "None of the candidates match")] 42 | public void LongestSuffixMatchNoMatches(string path, string[] candidates, string exceptionText) 43 | { 44 | var exception = Assert.ThrowsException(() => StringHelper.FindLongestMatchingSuffix(path, candidates, StringComparison.OrdinalIgnoreCase)); 45 | Assert.IsTrue(exception.Message.Contains(exceptionText), $"Exception.Message should contain '{exceptionText}' (actual:{exception.Message})"); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /StackTraceExplorer.Tests/MemberRegexTests.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using System.Text.RegularExpressions; 3 | using FluentAssertions; 4 | using Microsoft.VisualStudio.TestTools.UnitTesting; 5 | using StackTraceExplorer.Generators; 6 | 7 | namespace StackTraceExplorer.Tests 8 | { 9 | [TestClass] 10 | public class MemberRegexTests 11 | { 12 | [DataTestMethod] 13 | [DataRow( 14 | "Bla.Program.Main(String[] args)", 15 | "Bla.Program.Main(String[] args)", 16 | new[] { "Bla.", "Program." }, 17 | "Main(String[] args)")] 18 | [DataRow( 19 | @"at Bla.Program.InnerClass.InnerMain() in C:\repos\Bla.Program\InnerClass.cs:line 168", 20 | "Bla.Program.InnerClass.InnerMain()", 21 | new[] { "Bla.", "Program.", "InnerClass." }, 22 | "InnerMain()")] 23 | [DataRow( 24 | @"at Bla.Program.ApplicationMdi.b__59_0() in C:\Repos\Bla.Program\Views\ApplicationMdi.cs:line 375", 25 | "Bla.Program.ApplicationMdi.b__59_0()", 26 | new[] { "Bla.", "Program.", "ApplicationMdi." }, 27 | "b__59_0()")] 28 | [DataRow( 29 | @"at Bla.Program.ApplicationMdi.<>c.b__59_0() in C:\Repos\Bla.Program\Views\ApplicationMdi.cs:line 375", 30 | "Bla.Program.ApplicationMdi.<>c.b__59_0()", 31 | new[] { "Bla.", "Program.", "ApplicationMdi.", "<>c." }, 32 | "b__59_0()")] 33 | [DataRow( 34 | "at Company.Common.AsyncResult.End(IAsyncResult result)", 35 | "Company.Common.AsyncResult.End(IAsyncResult result)", 36 | new[] { "Company.", "Common.", "AsyncResult." }, 37 | "End(IAsyncResult result)")] 38 | [DataRow( 39 | "at Company.Common.AsyncResult.End[TAsyncResult](IAsyncResult result)", 40 | "Company.Common.AsyncResult.End[TAsyncResult](IAsyncResult result)", 41 | new[] { "Company.", "Common.", "AsyncResult." }, 42 | "End[TAsyncResult](IAsyncResult result)")] 43 | [DataRow( 44 | "Company.SomeType`1.StepCallback(IAsyncResult result)", 45 | "Company.SomeType`1.StepCallback(IAsyncResult result)", 46 | new[] { "Company.", "SomeType`1." }, 47 | "StepCallback(IAsyncResult result)")] 48 | [DataRow( 49 | @" at StackTraceExplorer.TestClass.End[T,V](String s) in D:\StackTraceExplorer\TestClass.cs:line 38", 50 | "StackTraceExplorer.TestClass.End[T,V](String s)", 51 | new[] { "StackTraceExplorer.", "TestClass." }, 52 | "End[T,V](String s)")] 53 | [DataRow( 54 | " at SolutionHelperTests.ClassWithGenericTypeArgs`2.StaticMethod[V]()", 55 | "SolutionHelperTests.ClassWithGenericTypeArgs`2.StaticMethod[V]()", 56 | new[] { "SolutionHelperTests.", "ClassWithGenericTypeArgs`2.", }, 57 | "StaticMethod[V]()")] 58 | [DataRow( 59 | " at StackTraceExplorer.Tests.SolutionHelperTests.<>c.b__6_3() in D:\\SolutionHelperTests.cs:line 52", 60 | "StackTraceExplorer.Tests.SolutionHelperTests.<>c.b__6_3()", 61 | new[] { "StackTraceExplorer.", "Tests.", "SolutionHelperTests.", "<>c." }, 62 | "b__6_3()")] 63 | [DataRow("at Sample.ClassWithGenericTypeArgs`1..ctor(Boolean throwException)", 64 | "Sample.ClassWithGenericTypeArgs`1..ctor(Boolean throwException)", 65 | new[] { "Sample.", "ClassWithGenericTypeArgs`1.", }, 66 | ".ctor(Boolean throwException)")] 67 | [DataRow(@"StackTraceExplorer.Tests.SolutionHelperTests.ClassWithGenericTypeArgs`1.StaticMethod[C]() in D:\StackTraceExplorer.Tests\SolutionHelperTests.cs:line 114", 68 | "StackTraceExplorer.Tests.SolutionHelperTests.ClassWithGenericTypeArgs`1.StaticMethod[C]()", 69 | new[] { "StackTraceExplorer.", "Tests.", "SolutionHelperTests.", "ClassWithGenericTypeArgs`1." }, 70 | "StaticMethod[C]()")] 71 | [DataRow(@"StackTraceExplorer.Tests.SolutionHelperTests.g__Parse|155_0(IEnumerable`1 lines) in C:\StackTraceExplorer.Tests\DummyFile.cs:line 21", 72 | "StackTraceExplorer.Tests.SolutionHelperTests.g__Parse|155_0(IEnumerable`1 lines)", 73 | new[] { "StackTraceExplorer.", "Tests.", "SolutionHelperTests." }, 74 | "g__Parse|155_0(IEnumerable`1 lines)")] 75 | [DataRow(@"StackTraceExplorer.Tests.SolutionHelperTests.ThreadHelper+<>c__DisplayClass13_0+<b__0>d.MoveNext() in C:\StackTraceExplorer.Tests\DummyFile.cs:line 21", 76 | "StackTraceExplorer.Tests.SolutionHelperTests.ThreadHelper+<>c__DisplayClass13_0+<b__0>d.MoveNext()", 77 | new[] { "StackTraceExplorer.", "Tests.", "SolutionHelperTests.", "ThreadHelper+<>c__DisplayClass13_0+<b__0>d." }, 78 | "MoveNext()")] 79 | public void ShouldMatchVisualStudioStacktraces(string input, string expectedMatch, string[] expectedCaptures, string expectedMethod) 80 | => ShouldMatchStacktraces(input, expectedMatch, expectedCaptures, expectedMethod); 81 | 82 | public void ShouldMatchStacktraces(string input, string expectedMatch, string[] expectedCaptures, string expectedMethod) 83 | { 84 | var match = MemberLinkElementGenerator.FindMatch(input); 85 | 86 | Assert.IsTrue(match.Success, "Match was not a success!"); 87 | Assert.AreEqual(expectedMatch, match.Groups["member"].Value, nameof(expectedMatch)); 88 | 89 | var captures = match.Groups["namespace"].Captures.Cast().Select(c => c.Value).ToArray(); 90 | captures.Should().BeEquivalentTo(expectedCaptures); 91 | 92 | Assert.AreEqual(expectedMethod, match.Groups["method"].Value, nameof(expectedMethod)); 93 | } 94 | 95 | [DataTestMethod] 96 | [DataRow(@"Microsoft.Azure.WebJobs.Host.Executors.FunctionExecutor+d__35.MoveNext (Microsoft.Azure.WebJobs.Host, Version=3.0.41.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35: D:\a\_work\1\s\src\Microsoft.Azure.WebJobs.Host\Executors\FunctionExecutor.cs:663)", 97 | "Microsoft.Azure.WebJobs.Host.Executors.FunctionExecutor+d__35.MoveNext", 98 | new[] { "Microsoft.", "Azure.", "WebJobs.", "Host.", "Executors.", "FunctionExecutor+d__35." }, 99 | "MoveNext")] 100 | public void ShouldMatchAppInsightsStackTraces(string input, string expectedMatch, string[] expectedCaptures, string expectedMethod) 101 | => ShouldMatchStacktraces(input, expectedMatch, expectedCaptures, expectedMethod); 102 | 103 | [DataTestMethod] 104 | [DataRow(@"Task> GitCommands.GitModule.GetRemotesAsync()+ParseRemotes(IEnumerable lines) in C:/.../gitextensions/GitCommands/Git/GitModule.cs:line 2196", 105 | "GitCommands.GitModule.GetRemotesAsync()+ParseRemotes(IEnumerable lines)", 106 | new[] { "GitCommands.", "GitModule." }, 107 | "GetRemotesAsync()+ParseRemotes(IEnumerable lines)")] 108 | public void ShouldMatchDemystifiedStackTraces(string input, string expectedMatch, string[] expectedCaptures, string expectedMethod) 109 | => ShouldMatchStacktraces(input, expectedMatch, expectedCaptures, expectedMethod); 110 | 111 | [DataTestMethod] 112 | [DataRow("whatever[0]")] 113 | [DataRow("Normal sentence (pretty much)")] 114 | [DataRow("Normal sentence [pretty much]")] 115 | public void ShouldNotMatch(string input) 116 | { 117 | var match = MemberLinkElementGenerator.FindMatch(input); 118 | Assert.IsFalse(match.Success, $"Input {input} should not match."); 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /StackTraceExplorer.Tests/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("StackTraceExplorer.Tests")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("StackTraceExplorer.Tests")] 13 | [assembly: AssemblyCopyright("Copyright © 2018")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("865d31df-a8cc-4c50-9eb5-aa04e555360b")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /StackTraceExplorer.Tests/SolutionHelperTests.cs: -------------------------------------------------------------------------------- 1 | namespace StackTraceExplorer.Tests 2 | { 3 | using System; 4 | using System.Diagnostics; 5 | using System.IO; 6 | using System.Runtime.CompilerServices; 7 | using Microsoft.CodeAnalysis; 8 | using Microsoft.CodeAnalysis.CSharp; 9 | using Microsoft.VisualStudio.TestTools.UnitTesting; 10 | using StackTraceExplorer.Helpers; 11 | 12 | [TestClass] 13 | public class SolutionHelperTests 14 | { 15 | public SolutionHelperTests() 16 | { 17 | this.Compilation = CreateCompilationForCurrentFile(); 18 | } 19 | 20 | CSharpCompilation Compilation { get; } 21 | 22 | [DataTestMethod] 23 | [DataRow("End[TAsyncResult](IAsyncResult result)", "End")] 24 | [DataRow("Main(String[] args)", "Main")] 25 | [DataRow("GenericType`1", "GenericType")] 26 | [DataRow("d__0", "GetSteps")] 27 | [DataRow("get_TestProperty()", "get_TestProperty")] 28 | public void GetMemberName(string input, string expected) 29 | { 30 | string memberName = SolutionHelper.GetMemberName(input); 31 | Assert.AreEqual(expected, memberName); 32 | } 33 | 34 | [DataTestMethod] 35 | [DataRow("StackTraceExplorer.Tests." + nameof(SolutionHelperTests), "StackTraceExplorer.Tests.SolutionHelperTests")] 36 | [DataRow("StackTraceExplorer.Tests.SolutionHelperTests..ctor()", "StackTraceExplorer.Tests.SolutionHelperTests.SolutionHelperTests()")] 37 | [DataRow("StackTraceExplorer.Tests.SolutionHelperTests.ResolveMember(String memberName, String expectedMatch)", "StackTraceExplorer.Tests.SolutionHelperTests.ResolveMember(string, string)")] 38 | [DataRow("StackTraceExplorer.Tests.SolutionHelperTests.ThisMethodThrows[T](String s)", "StackTraceExplorer.Tests.SolutionHelperTests.ThisMethodThrows(string)")] 39 | [DataRow("StackTraceExplorer.Tests.SolutionHelperTests.ThisMethodThrows[T,V](String s)", "StackTraceExplorer.Tests.SolutionHelperTests.ThisMethodThrows(string)")] 40 | [DataRow("StackTraceExplorer.Tests.ClassWithGenericTypeArgs`1", "StackTraceExplorer.Tests.ClassWithGenericTypeArgs")] 41 | [DataRow("StackTraceExplorer.Tests." + nameof(ClassWithGenericTypeArgs) + "`2", "StackTraceExplorer.Tests.ClassWithGenericTypeArgs")] 42 | [DataRow("StackTraceExplorer.Tests.ClassWithGenericTypeArgs`1..ctor(Boolean throwException)", "StackTraceExplorer.Tests.ClassWithGenericTypeArgs.ClassWithGenericTypeArgs(bool)")] 43 | [DataRow("StackTraceExplorer.Tests.ClassWithGenericTypeArgs`1.StaticMethod[C]()", "StackTraceExplorer.Tests.ClassWithGenericTypeArgs.StaticMethod()")] 44 | [DataRow("StackTraceExplorer.Tests.SolutionHelperTests.<>c.<" + nameof(GenerateStackTracesForTesting) + ">b__6_3()", "StackTraceExplorer.Tests.SolutionHelperTests.GenerateStackTracesForTesting()")] 45 | [DataRow("StackTraceExplorer.Tests.SolutionHelperTests.<>c__DisplayClass6_0.<" + nameof(GenerateStackTracesForTesting) + ">b__7(String s)", "StackTraceExplorer.Tests.SolutionHelperTests.GenerateStackTracesForTesting()")] 46 | [DataRow("StackTraceExplorer.Tests.SolutionHelperTests.GetExceptionToString[T](Action`1 action, T value)", "StackTraceExplorer.Tests.SolutionHelperTests.GetExceptionToString(Action, T)")] 47 | public void ResolveMember(string memberName, string expectedMatch) 48 | { 49 | ISymbol symbol = SolutionHelper.Resolve(this.Compilation, memberName); 50 | Assert.IsNotNull(symbol, $"Symbol {memberName} should be found"); 51 | Trace.WriteLine($"Found: {symbol}"); 52 | Assert.AreEqual(expectedMatch, symbol.ToString(), "Resolved member was not correct"); 53 | } 54 | 55 | [TestMethod] 56 | public void GenerateStackTracesForTesting() 57 | { 58 | // This isn't a test, per se, but it is useful for creating sample text to try pasting into the StackTrace window. 59 | Trace.WriteLine(GetExceptionToString(() => ClassWithGenericTypeArgs.StaticMethod())); 60 | Trace.WriteLine(GetExceptionToString(() => new ClassWithGenericTypeArgs(throwException: true))); 61 | Trace.WriteLine(GetExceptionToString(() => new ClassWithGenericTypeArgs(throwException: false).InstanceMethod())); 62 | 63 | Trace.WriteLine(GetExceptionToString(() => ClassWithGenericTypeArgs.StaticMethod())); 64 | Trace.WriteLine(GetExceptionToString(() => new ClassWithGenericTypeArgs(throwException: true))); 65 | Trace.WriteLine(GetExceptionToString((s) => new ClassWithGenericTypeArgs(throwException: false).InstanceMethod(), "someString")); 66 | 67 | // Create some funny compiler generated names 68 | Trace.WriteLine(GetExceptionToString(() => throw new InvalidOperationException(this.ToString()))); 69 | foreach (string value in new[] { "Test String" }) 70 | { 71 | Trace.WriteLine(GetExceptionToString((s) => throw new InvalidOperationException(value), value)); 72 | } 73 | } 74 | 75 | public static CSharpCompilation CreateCompilationForCurrentFile([CallerFilePath] string fileName = "") 76 | { 77 | var compilation = CSharpCompilation.Create("TempAssembly"); 78 | string sourceText = File.ReadAllText(fileName); 79 | SyntaxTree tree = CSharpSyntaxTree.ParseText(sourceText); 80 | compilation = compilation.AddSyntaxTrees(tree); 81 | return compilation; 82 | } 83 | 84 | public static string GetExceptionToString(Action action) 85 | { 86 | try 87 | { 88 | action(); 89 | } 90 | catch (Exception e) 91 | { 92 | return e.ToString(); 93 | } 94 | 95 | return string.Empty; 96 | } 97 | 98 | public static string GetExceptionToString(Action action, T value) 99 | { 100 | try 101 | { 102 | action(value); 103 | } 104 | catch (Exception e) 105 | { 106 | return e.ToString(); 107 | } 108 | 109 | return string.Empty; 110 | } 111 | 112 | // Used in Test DataRows 113 | static void ThisMethodThrows(string s) 114 | { 115 | throw new NotImplementedException("foo"); 116 | } 117 | 118 | // Used in Test DataRows 119 | static void ThisMethodThrows(string s) 120 | { 121 | throw new NotImplementedException("foo"); 122 | } 123 | } 124 | 125 | class ClassWithGenericTypeArgs 126 | { 127 | public ClassWithGenericTypeArgs(bool throwException) 128 | { 129 | if (throwException) 130 | { 131 | throw new NotImplementedException(); 132 | } 133 | } 134 | 135 | public string InstanceMethod() 136 | { 137 | throw new NotImplementedException(); 138 | } 139 | 140 | public static string StaticMethod() 141 | { 142 | throw new NotImplementedException(); 143 | } 144 | } 145 | 146 | class ClassWithGenericTypeArgs 147 | { 148 | public ClassWithGenericTypeArgs(bool throwException) 149 | { 150 | if (throwException) 151 | { 152 | throw new NotImplementedException(); 153 | } 154 | } 155 | 156 | public string InstanceMethod() 157 | { 158 | throw new NotImplementedException(); 159 | } 160 | 161 | public static string StaticMethod() 162 | { 163 | throw new NotImplementedException(); 164 | } 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /StackTraceExplorer.Tests/StackTraceExplorer.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | Debug 7 | AnyCPU 8 | {865D31DF-A8CC-4C50-9EB5-AA04E555360B} 9 | Library 10 | Properties 11 | StackTraceExplorer.Tests 12 | StackTraceExplorer.Tests 13 | v4.7.2 14 | 512 15 | {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 16 | 15.0 17 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 18 | $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages 19 | False 20 | UnitTest 21 | 22 | 23 | 24 | 25 | true 26 | full 27 | false 28 | bin\Debug\ 29 | DEBUG;TRACE 30 | prompt 31 | 4 32 | 33 | 34 | pdbonly 35 | true 36 | bin\Release\ 37 | TRACE 38 | prompt 39 | 4 40 | 41 | 42 | 43 | ..\packages\MSTest.TestFramework.2.1.2\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.dll 44 | 45 | 46 | ..\packages\MSTest.TestFramework.2.1.2\lib\net45\Microsoft.VisualStudio.TestPlatform.TestFramework.Extensions.dll 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | StackTraceExplorer.cs 60 | True 61 | True 62 | StackTraceExplorer.vsct 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | .editorconfig 73 | 74 | 75 | 76 | 77 | 6.1.2.30 78 | 79 | 80 | 17.0.76.257-pre 81 | 82 | 83 | 84 | 4.0.1 85 | 86 | 87 | 4.0.1 88 | 89 | 90 | 2.2.8 91 | 92 | 93 | 2.2.8 94 | 95 | 96 | 97 | 98 | StackTraceExplorer.vsct 99 | VsctGenerator 100 | StackTraceExplorer.cs 101 | 102 | 103 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /StackTraceExplorer.VS2019/CallStackWindow.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sboulema/StackTraceExplorer/267f8f387860823f5bcab79ebba20564ba357488/StackTraceExplorer.VS2019/CallStackWindow.ico -------------------------------------------------------------------------------- /StackTraceExplorer.VS2019/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("StackTraceExplorer")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("StackTraceExplorer")] 13 | [assembly: AssemblyCopyright("")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // Version information for an assembly consists of the following four values: 23 | // 24 | // Major Version 25 | // Minor Version 26 | // Build Number 27 | // Revision 28 | // 29 | // You can specify all the values or you can default the Build and Revision Numbers 30 | // by using the '*' as shown below: 31 | // [assembly: AssemblyVersion("1.0.*")] 32 | [assembly: AssemblyVersion("1.0.0.0")] 33 | [assembly: AssemblyFileVersion("1.0.0.0")] 34 | -------------------------------------------------------------------------------- /StackTraceExplorer.VS2019/StackTraceExplorer.VS2019.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 16.0 5 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 6 | 7 | 8 | true 9 | 10 | 11 | 12 | 13 | 15.0 14 | 15 | 16 | 17 | true 18 | 19 | 20 | 21 | 22 | 23 | 24 | CallStackWindow.ico 25 | 26 | 27 | 28 | Debug 29 | AnyCPU 30 | 2.0 31 | {82b43b9b-a64c-4715-b499-d71e9ca2bd60};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 32 | {393FF8FD-00C8-471C-8C5A-EA97B229886A} 33 | Library 34 | Properties 35 | StackTraceExplorer 36 | StackTraceExplorer.VS2019 37 | v4.8 38 | true 39 | true 40 | true 41 | true 42 | true 43 | false 44 | False 45 | 46 | 47 | true 48 | full 49 | false 50 | bin\Debug\ 51 | DEBUG;TRACE 52 | prompt 53 | 4 54 | False 55 | 56 | 57 | pdbonly 58 | true 59 | bin\Release\ 60 | TRACE 61 | prompt 62 | 4 63 | False 64 | 65 | 66 | 67 | StackTraceExplorer.cs 68 | True 69 | True 70 | StackTraceExplorer.vsct 71 | 72 | 73 | 74 | 75 | 76 | Resources\LICENSE 77 | true 78 | 79 | 80 | Resources\CallStackWindow_256x.png 81 | true 82 | 83 | 84 | Resources\Preview.png 85 | true 86 | 87 | 88 | 89 | Designer 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 6.1.2.30 111 | 112 | 113 | 15.0.527 114 | 115 | 116 | 16.0.29.6 117 | runtime; build; native; contentfiles; analyzers; buildtransitive 118 | all 119 | 120 | 121 | 3.11.0 122 | 123 | 124 | 2.10.0 125 | 126 | 127 | 16.10.230 128 | 129 | 130 | 17.12.2069 131 | runtime; build; native; contentfiles; analyzers; buildtransitive 132 | all 133 | 134 | 135 | 136 | 137 | StackTraceExplorer.vsct 138 | Menus.ctmenu 139 | VsctGenerator 140 | StackTraceExplorer.cs 141 | 142 | 143 | 144 | 145 | 146 | 153 | -------------------------------------------------------------------------------- /StackTraceExplorer.VS2019/source.extension.vsixmanifest: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Stack Trace Explorer 2019 6 | Parse those pesty unreadable long stack traces. Stack Trace Explorer provides syntax highlighting and easy navigation to elements in the stack trace. 7 | https://marketplace.visualstudio.com/vsgallery/0886a4d9-35e3-431a-b86c-bf0e346ad036 8 | Resources\LICENSE 9 | https://github.com/sboulema/StackTraceExplorer/blob/master/README.md 10 | https://github.com/sboulema/StackTraceExplorer/releases 11 | Resources\CallStackWindow_256x.png 12 | Resources\Preview.png 13 | stacktrace, debug 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /StackTraceExplorer.VS2022/CallStackWindow.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sboulema/StackTraceExplorer/267f8f387860823f5bcab79ebba20564ba357488/StackTraceExplorer.VS2022/CallStackWindow.ico -------------------------------------------------------------------------------- /StackTraceExplorer.VS2022/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("StackTraceExplorer")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("StackTraceExplorer")] 13 | [assembly: AssemblyCopyright("")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // Version information for an assembly consists of the following four values: 23 | // 24 | // Major Version 25 | // Minor Version 26 | // Build Number 27 | // Revision 28 | // 29 | // You can specify all the values or you can default the Build and Revision Numbers 30 | // by using the '*' as shown below: 31 | // [assembly: AssemblyVersion("1.0.*")] 32 | [assembly: AssemblyVersion("1.0.0.0")] 33 | [assembly: AssemblyFileVersion("1.0.0.0")] 34 | -------------------------------------------------------------------------------- /StackTraceExplorer.VS2022/StackTraceExplorer.VS2022.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 16.0 5 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 6 | 7 | 8 | true 9 | 10 | 11 | 12 | 13 | 15.0 14 | 15 | 16 | 17 | true 18 | 19 | 20 | 21 | 22 | 23 | 24 | CallStackWindow.ico 25 | 26 | 27 | 28 | Debug 29 | AnyCPU 30 | 2.0 31 | {82b43b9b-a64c-4715-b499-d71e9ca2bd60};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 32 | {3EA63EFB-6A5B-4CDA-9F53-14D94C0DB12B} 33 | Library 34 | Properties 35 | StackTraceExplorer 36 | StackTraceExplorer.VS2022 37 | v4.8 38 | true 39 | true 40 | true 41 | true 42 | true 43 | false 44 | False 45 | 46 | 47 | true 48 | full 49 | false 50 | bin\Debug\ 51 | DEBUG;TRACE 52 | prompt 53 | 4 54 | False 55 | 56 | 57 | pdbonly 58 | true 59 | bin\Release\ 60 | TRACE 61 | prompt 62 | 4 63 | False 64 | 65 | 66 | 67 | StackTraceExplorer.cs 68 | True 69 | True 70 | StackTraceExplorer.vsct 71 | 72 | 73 | 74 | 75 | 76 | Resources\LICENSE 77 | true 78 | 79 | 80 | 81 | Designer 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 6.1.2.30 103 | 104 | 105 | 17.0.527 106 | 107 | 108 | 16.0.29.6 109 | runtime; build; native; contentfiles; analyzers; buildtransitive 110 | all 111 | 112 | 113 | 4.0.1 114 | 115 | 116 | 4.0.1 117 | 118 | 119 | 17.12.2069 120 | runtime; build; native; contentfiles; analyzers; buildtransitive 121 | all 122 | 123 | 124 | 125 | 126 | Resources\CallStackWindow_256x.png 127 | true 128 | 129 | 130 | Resources\Preview.png 131 | true 132 | 133 | 134 | StackTraceExplorer.vsct 135 | Menus.ctmenu 136 | VsctGenerator 137 | StackTraceExplorer.cs 138 | 139 | 140 | 141 | 142 | 143 | 150 | -------------------------------------------------------------------------------- /StackTraceExplorer.VS2022/source.extension.vsixmanifest: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Stack Trace Explorer 2022 6 | Parse those pesty unreadable long stack traces. Stack Trace Explorer provides syntax highlighting and easy navigation to elements in the stack trace. 7 | https://marketplace.visualstudio.com/vsgallery/0886a4d9-35e3-431a-b86c-bf0e346ad036 8 | Resources\LICENSE 9 | https://github.com/sboulema/StackTraceExplorer/blob/master/README.md 10 | https://github.com/sboulema/StackTraceExplorer/releases 11 | Resources\CallStackWindow_256x.png 12 | Resources\Preview.png 13 | stacktrace, debug 14 | 15 | 16 | 17 | amd64 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /StackTraceExplorer.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.0.31521.260 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{EE2CB841-FBAB-42FF-8233-E762CC77D778}" 7 | ProjectSection(SolutionItems) = preProject 8 | .editorconfig = .editorconfig 9 | publish-manifest.VS2019.json = publish-manifest.VS2019.json 10 | publish-manifest.VS2022.json = publish-manifest.VS2022.json 11 | README.md = README.md 12 | .github\workflows\workflow.yml = .github\workflows\workflow.yml 13 | EndProjectSection 14 | EndProject 15 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StackTraceExplorer.VS2019", "StackTraceExplorer.VS2019\StackTraceExplorer.VS2019.csproj", "{393FF8FD-00C8-471C-8C5A-EA97B229886A}" 16 | EndProject 17 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StackTraceExplorer.Tests", "StackTraceExplorer.Tests\StackTraceExplorer.Tests.csproj", "{865D31DF-A8CC-4C50-9EB5-AA04E555360B}" 18 | EndProject 19 | Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "StackTraceExplorer.Shared", "StackTraceExplorer.Shared\StackTraceExplorer.Shared.shproj", "{2732F00A-56DA-436F-8CC4-BAEA756330D3}" 20 | EndProject 21 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StackTraceExplorer.VS2022", "StackTraceExplorer.VS2022\StackTraceExplorer.VS2022.csproj", "{3EA63EFB-6A5B-4CDA-9F53-14D94C0DB12B}" 22 | EndProject 23 | Global 24 | GlobalSection(SharedMSBuildProjectFiles) = preSolution 25 | StackTraceExplorer.Shared\StackTraceExplorer.Shared.projitems*{2732f00a-56da-436f-8cc4-baea756330d3}*SharedItemsImports = 13 26 | StackTraceExplorer.Shared\StackTraceExplorer.Shared.projitems*{393ff8fd-00c8-471c-8c5a-ea97b229886a}*SharedItemsImports = 4 27 | StackTraceExplorer.Shared\StackTraceExplorer.Shared.projitems*{3ea63efb-6a5b-4cda-9f53-14d94c0db12b}*SharedItemsImports = 4 28 | StackTraceExplorer.Shared\StackTraceExplorer.Shared.projitems*{865d31df-a8cc-4c50-9eb5-aa04e555360b}*SharedItemsImports = 4 29 | EndGlobalSection 30 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 31 | Debug|Any CPU = Debug|Any CPU 32 | Debug|x86 = Debug|x86 33 | Release|Any CPU = Release|Any CPU 34 | Release|x86 = Release|x86 35 | EndGlobalSection 36 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 37 | {393FF8FD-00C8-471C-8C5A-EA97B229886A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 38 | {393FF8FD-00C8-471C-8C5A-EA97B229886A}.Debug|Any CPU.Build.0 = Debug|Any CPU 39 | {393FF8FD-00C8-471C-8C5A-EA97B229886A}.Debug|x86.ActiveCfg = Debug|x86 40 | {393FF8FD-00C8-471C-8C5A-EA97B229886A}.Debug|x86.Build.0 = Debug|x86 41 | {393FF8FD-00C8-471C-8C5A-EA97B229886A}.Release|Any CPU.ActiveCfg = Release|Any CPU 42 | {393FF8FD-00C8-471C-8C5A-EA97B229886A}.Release|Any CPU.Build.0 = Release|Any CPU 43 | {393FF8FD-00C8-471C-8C5A-EA97B229886A}.Release|x86.ActiveCfg = Release|x86 44 | {393FF8FD-00C8-471C-8C5A-EA97B229886A}.Release|x86.Build.0 = Release|x86 45 | {865D31DF-A8CC-4C50-9EB5-AA04E555360B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 46 | {865D31DF-A8CC-4C50-9EB5-AA04E555360B}.Debug|Any CPU.Build.0 = Debug|Any CPU 47 | {865D31DF-A8CC-4C50-9EB5-AA04E555360B}.Debug|x86.ActiveCfg = Debug|Any CPU 48 | {865D31DF-A8CC-4C50-9EB5-AA04E555360B}.Debug|x86.Build.0 = Debug|Any CPU 49 | {865D31DF-A8CC-4C50-9EB5-AA04E555360B}.Release|Any CPU.ActiveCfg = Release|Any CPU 50 | {865D31DF-A8CC-4C50-9EB5-AA04E555360B}.Release|Any CPU.Build.0 = Release|Any CPU 51 | {865D31DF-A8CC-4C50-9EB5-AA04E555360B}.Release|x86.ActiveCfg = Release|Any CPU 52 | {865D31DF-A8CC-4C50-9EB5-AA04E555360B}.Release|x86.Build.0 = Release|Any CPU 53 | {3EA63EFB-6A5B-4CDA-9F53-14D94C0DB12B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 54 | {3EA63EFB-6A5B-4CDA-9F53-14D94C0DB12B}.Debug|Any CPU.Build.0 = Debug|Any CPU 55 | {3EA63EFB-6A5B-4CDA-9F53-14D94C0DB12B}.Debug|x86.ActiveCfg = Debug|x86 56 | {3EA63EFB-6A5B-4CDA-9F53-14D94C0DB12B}.Debug|x86.Build.0 = Debug|x86 57 | {3EA63EFB-6A5B-4CDA-9F53-14D94C0DB12B}.Release|Any CPU.ActiveCfg = Release|Any CPU 58 | {3EA63EFB-6A5B-4CDA-9F53-14D94C0DB12B}.Release|Any CPU.Build.0 = Release|Any CPU 59 | {3EA63EFB-6A5B-4CDA-9F53-14D94C0DB12B}.Release|x86.ActiveCfg = Release|x86 60 | {3EA63EFB-6A5B-4CDA-9F53-14D94C0DB12B}.Release|x86.Build.0 = Release|x86 61 | EndGlobalSection 62 | GlobalSection(SolutionProperties) = preSolution 63 | HideSolutionNode = FALSE 64 | EndGlobalSection 65 | GlobalSection(ExtensibilityGlobals) = postSolution 66 | SolutionGuid = {6CFED3C7-F32D-41F2-B165-6CE47F688579} 67 | EndGlobalSection 68 | EndGlobal 69 | -------------------------------------------------------------------------------- /nuget.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /publish-manifest.VS2019.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/vsix-publish", 3 | "categories": [ "coding" ], 4 | "identity": { 5 | "internalName": "StackTraceExplorer" 6 | }, 7 | "overview": "readme.md", 8 | "priceCategory": "free", 9 | "publisher": "SamirBoulema", 10 | "private": false, 11 | "qna": true, 12 | "repo": "https://github.com/sboulema/StackTraceExplorer" 13 | } -------------------------------------------------------------------------------- /publish-manifest.VS2022.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/vsix-publish", 3 | "categories": [ "coding" ], 4 | "identity": { 5 | "internalName": "StackTraceExplorer2022" 6 | }, 7 | "overview": "readme.md", 8 | "priceCategory": "free", 9 | "publisher": "SamirBoulema", 10 | "private": false, 11 | "qna": true, 12 | "repo": "https://github.com/sboulema/StackTraceExplorer" 13 | } --------------------------------------------------------------------------------