diff --git a/src/VisualStudio/Core/Def/Implementation/Library/FindResults/LibraryManager_GoToDefinition.cs b/src/VisualStudio/Core/Def/Implementation/Library/FindResults/LibraryManager_GoToDefinition.cs index 28ec5e4c958a0..76cd46ce3914b 100644 --- a/src/VisualStudio/Core/Def/Implementation/Library/FindResults/LibraryManager_GoToDefinition.cs +++ b/src/VisualStudio/Core/Def/Implementation/Library/FindResults/LibraryManager_GoToDefinition.cs @@ -1,7 +1,10 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; +using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Editor.Navigation; using Microsoft.VisualStudio.LanguageServices.Implementation.Utilities; using Roslyn.Utilities; @@ -10,24 +13,93 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.Library.FindRes { internal partial class LibraryManager { - private IList CreateGoToDefinitionItems(IEnumerable items) + private IList CreateNavigableItemTreeItems(IEnumerable items) + { + var itemsList = items.ToList(); + if (itemsList.Count == 0) + { + return new List(); + } + + // Collect all the documents in the list of items we're presenting. Then determine + // what number of common path elements they have in common. We can avoid showing + // these common locations, thus presenting a much cleaner result view to the user. + var documents = new HashSet(); + CollectDocuments(itemsList, documents); + + var commonPathElements = CountCommonPathElements(documents); + + return CreateNavigableItemTreeItems(itemsList, commonPathElements); + } + + private int CountCommonPathElements(HashSet documents) + { + Debug.Assert(documents.Count > 0); + var commonPathElements = 0; + for (var index = 0; ; index++) + { + var pathPortion = GetPathPortion(documents.First(), index); + if (pathPortion == null) + { + return commonPathElements; + } + + foreach (var document in documents) + { + if (GetPathPortion(document, index) != pathPortion) + { + return commonPathElements; + } + } + + commonPathElements++; + } + } + + private string GetPathPortion(Document document, int index) + { + if (index == 0) + { + return document.Project.Name; + } + + index--; + if (index < document.Folders.Count) + { + return document.Folders[index]; + } + + return null; + } + + private void CollectDocuments(IEnumerable items, HashSet documents) + { + foreach (var item in items) + { + documents.Add(item.Document); + + CollectDocuments(item.ChildItems, documents); + } + } + + private IList CreateNavigableItemTreeItems(IEnumerable items, int commonPathElements) { var sourceListItems = from item in items where IsValidSourceLocation(item.Document, item.SourceSpan) - select CreateTreeItem(item); + select CreateTreeItem(item, commonPathElements); return sourceListItems.ToList(); } - private AbstractTreeItem CreateTreeItem(INavigableItem item) + private AbstractTreeItem CreateTreeItem(INavigableItem item, int commonPathElements) { var displayText = !item.ChildItems.IsEmpty ? item.DisplayName : null; - var result = new SourceReferenceTreeItem(item.Document, item.SourceSpan, item.Glyph.GetGlyphIndex(), displayText); + var result = new SourceReferenceTreeItem(item.Document, item.SourceSpan, item.Glyph.GetGlyphIndex(), commonPathElements, displayText); if (!item.ChildItems.IsEmpty) { - var childItems = CreateGoToDefinitionItems(item.ChildItems); + var childItems = CreateNavigableItemTreeItems(item.ChildItems, commonPathElements); result.Children.AddRange(childItems); result.SetReferenceCount(childItems.Count); } @@ -37,7 +109,7 @@ private AbstractTreeItem CreateTreeItem(INavigableItem item) public void PresentNavigableItems(string title, IEnumerable items) { - PresentObjectList(title, new ObjectList(CreateGoToDefinitionItems(items), this)); + PresentObjectList(title, new ObjectList(CreateNavigableItemTreeItems(items), this)); } } } diff --git a/src/VisualStudio/Core/Def/Implementation/Library/FindResults/TreeItems/AbstractSourceTreeItem.cs b/src/VisualStudio/Core/Def/Implementation/Library/FindResults/TreeItems/AbstractSourceTreeItem.cs index e3e7682561e12..6309c8adf450b 100644 --- a/src/VisualStudio/Core/Def/Implementation/Library/FindResults/TreeItems/AbstractSourceTreeItem.cs +++ b/src/VisualStudio/Core/Def/Implementation/Library/FindResults/TreeItems/AbstractSourceTreeItem.cs @@ -1,5 +1,6 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using System.Collections.Generic; using System.IO; using System.Text; using System.Threading; @@ -27,7 +28,7 @@ internal abstract class AbstractSourceTreeItem : AbstractTreeItem private static readonly ObjectPool s_filePathBuilderPool = new ObjectPool(() => new StringBuilder()); - public AbstractSourceTreeItem(Document document, TextSpan sourceSpan, ushort glyphIndex) + public AbstractSourceTreeItem(Document document, TextSpan sourceSpan, ushort glyphIndex, int commonPathElements = 0) : base(glyphIndex) { // We store the document ID, line and offset for navigation so that we @@ -37,7 +38,7 @@ public AbstractSourceTreeItem(Document document, TextSpan sourceSpan, ushort gly _workspace = document.Project.Solution.Workspace; _documentId = document.Id; _projectName = document.Project.Name; - _filePath = GetFilePath(document); + _filePath = GetFilePath(document, commonPathElements); _sourceSpan = sourceSpan; var text = document.GetTextAsync(CancellationToken.None).WaitAndGetResult(CancellationToken.None); @@ -64,18 +65,27 @@ public override int GoToSource() return VSConstants.S_OK; } - private static string GetFilePath(Document document) + private static string GetFilePath(Document document, int commonPathElements) { var builder = s_filePathBuilderPool.Allocate(); try { - builder.Append(document.Project.Name); - builder.Append('\\'); + if (commonPathElements <= 0) + { + builder.Append(document.Project.Name); + builder.Append('\\'); + } + commonPathElements--; foreach (var folder in document.Folders) { - builder.Append(folder); - builder.Append('\\'); + if (commonPathElements <= 0) + { + builder.Append(folder); + builder.Append('\\'); + } + + commonPathElements--; } builder.Append(Path.GetFileName(document.FilePath)); diff --git a/src/VisualStudio/Core/Def/Implementation/Library/FindResults/TreeItems/SourceReferenceTreeItem.cs b/src/VisualStudio/Core/Def/Implementation/Library/FindResults/TreeItems/SourceReferenceTreeItem.cs index dd29057c0336a..7470db7b4ff0f 100644 --- a/src/VisualStudio/Core/Def/Implementation/Library/FindResults/TreeItems/SourceReferenceTreeItem.cs +++ b/src/VisualStudio/Core/Def/Implementation/Library/FindResults/TreeItems/SourceReferenceTreeItem.cs @@ -1,6 +1,7 @@ // Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.Collections.Generic; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.Text; @@ -8,8 +9,8 @@ namespace Microsoft.VisualStudio.LanguageServices.Implementation.Library.FindRes { internal class SourceReferenceTreeItem : AbstractSourceTreeItem, IComparable { - public SourceReferenceTreeItem(Document document, TextSpan sourceSpan, ushort glyphIndex, string displayText = null) - : base(document, sourceSpan, glyphIndex) + public SourceReferenceTreeItem(Document document, TextSpan sourceSpan, ushort glyphIndex, int commonPathElements = 0, string displayText = null) + : base(document, sourceSpan, glyphIndex, commonPathElements) { if (displayText != null) {