From e44c57fa3c3c42c94e81c84f210b376250a73a70 Mon Sep 17 00:00:00 2001 From: Dark Daskin Date: Fri, 28 Jun 2019 18:06:14 +0300 Subject: [PATCH] Fixed handling of multiple tabs with same full path. For example, counstructor/code views of a Windows Form. --- .../DisplayPathResolverTests_NoSolution.cs | 43 +++++++++++++++ VSTabPath/Models/DisplayPathResolver.cs | 55 ++++++++++++++----- 2 files changed, 85 insertions(+), 13 deletions(-) diff --git a/VSTabPath.Tests/DisplayPathResolverTests_NoSolution.cs b/VSTabPath.Tests/DisplayPathResolverTests_NoSolution.cs index 1e4e14f..4b2ba27 100644 --- a/VSTabPath.Tests/DisplayPathResolverTests_NoSolution.cs +++ b/VSTabPath.Tests/DisplayPathResolverTests_NoSolution.cs @@ -155,6 +155,49 @@ public void WhenDuplicatesDifferByCase_ShowPaths() // TODO: VSCode with different drives: c:\… d:\… + [TestMethod] + public void WhenDuplicateFullPaths_DoNotShowPaths() + { + var models = new[] + { + new TabModel(@"c:\root\Directory1\1.txt"), + new TabModel(@"c:\root\Directory1\1.txt"), + new TabModel(@"c:\Root\Directory1\1.txt"), + }; + + var resolver = new DisplayPathResolver + { + models[0], + models[1], + models[2], + }; + + Assert.AreEqual(null, models[0].DisplayPath); + Assert.AreEqual(null, models[1].DisplayPath); + Assert.AreEqual(null, models[2].DisplayPath); + } + + [TestMethod] + public void WhenDuplicatesHaveSamePaths_ShowPaths() + { + var models = new[] + { + new TabModel(@"c:\root\Directory1\1.txt"), + new TabModel(@"c:\root\Directory1\1.txt"), + new TabModel(@"c:\Root\Directory2\1.txt"), + }; + + var resolver = new DisplayPathResolver + { + models[0], + models[1], + models[2], + }; + Assert.AreEqual(@"c:\…\Directory1", models[0].DisplayPath); + Assert.AreEqual(@"c:\…\Directory1", models[1].DisplayPath); + Assert.AreEqual(@"c:\…\Directory2", models[2].DisplayPath); + } + [TestMethod] public void WhenNewTabIsAdded_UpdatePaths() { diff --git a/VSTabPath/Models/DisplayPathResolver.cs b/VSTabPath/Models/DisplayPathResolver.cs index 32869b1..187a172 100644 --- a/VSTabPath/Models/DisplayPathResolver.cs +++ b/VSTabPath/Models/DisplayPathResolver.cs @@ -13,10 +13,13 @@ public class DisplayPathResolver : IEnumerable private static readonly char[] DirectorySeparators = {Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar}; + private static readonly IEqualityComparer PathComparer = StringComparer.OrdinalIgnoreCase; - private readonly List _models = new List(); + private Dictionary> _modelsByFullPath = new Dictionary>(PathComparer); private string _solutionRootPath; + private IEnumerable ModelsWithUniqueFullPaths => _modelsByFullPath.Values.Select(list => list[0]); + public string SolutionRootPath { get => _solutionRootPath; @@ -39,7 +42,9 @@ public DisplayPathResolver(string solutionRootPath) public void Add(TabModel model) { - _models.Add(model); + if (!_modelsByFullPath.TryGetValue(model.FullPath, out var models)) + _modelsByFullPath.Add(model.FullPath, models = new List()); + models.Add(model); model.PropertyChanged += OnModelPropertyChanged; @@ -50,14 +55,19 @@ public void Remove(TabModel model) { model.PropertyChanged -= OnModelPropertyChanged; - _models.Remove(model); + if (_modelsByFullPath.TryGetValue(model.FullPath, out var models)) + { + models.Remove(model); + if (models.Count == 0) + _modelsByFullPath.Remove(model.FullPath); + } UpdateModels(model.FileName); } public IEnumerator GetEnumerator() { - return _models.GetEnumerator(); + return _modelsByFullPath.Values.SelectMany(m => m).GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() @@ -67,22 +77,35 @@ IEnumerator IEnumerable.GetEnumerator() private void OnModelPropertyChanged(object sender, PropertyChangedEventArgs args) { - if(args.PropertyName == nameof(TabModel.FullPath)) + if (args.PropertyName == nameof(TabModel.FullPath)) + { + RegroupTabs(); UpdateModels(); + } + } + + private void RegroupTabs() + { + _modelsByFullPath = this + .GroupBy(m => m.FullPath) + .ToDictionary(g => g.Key, g => g.ToList(), PathComparer); } private void UpdateModels(string fileName) { - var modelsToUpdate = _models.Where(m => PathEquals(m.FileName, fileName)).ToList(); + var modelsToUpdate = ModelsWithUniqueFullPaths + .Where(m => PathEquals(m.FileName, fileName)) + .ToList(); if (modelsToUpdate.Count == 1) - modelsToUpdate[0].DisplayPath = null; + UpdateDisplayPathsByFullPath(modelsToUpdate[0], null); else UpdateModelsWithDuplicateFilename(modelsToUpdate); } private void UpdateModels() { - var modelsByFileName = _models.ToLookup(m => m.FileName, StringComparer.OrdinalIgnoreCase); + var modelsByFileName = ModelsWithUniqueFullPaths + .ToLookup(m => m.FileName, PathComparer); var modelsWithDuplicateFileName = modelsByFileName .Where(g => g.Count() > 1) @@ -94,7 +117,13 @@ private void UpdateModels() .Where(g => g.Count() == 1) .SelectMany(g => g); foreach (var model in modelsWithUniqueFileName) - model.DisplayPath = null; + UpdateDisplayPathsByFullPath(model, null); + } + + private void UpdateDisplayPathsByFullPath(TabModel example, string displayPath) + { + foreach (var model in _modelsByFullPath[example.FullPath]) + model.DisplayPath = displayPath; } private void UpdateModelsWithDuplicateFilename(IReadOnlyCollection models) @@ -102,7 +131,7 @@ private void UpdateModelsWithDuplicateFilename(IReadOnlyCollection mod var modelsAtSolutionRoot = models .Where(m => PathEquals(m.DirectoryName, SolutionRootPath)); foreach (var model in modelsAtSolutionRoot) - model.DisplayPath = @".\"; + UpdateDisplayPathsByFullPath(model, @".\"); var modelsOutsideSolutionRoot = models .Where(m => !IsBaseOf(SolutionRootPath, m.DirectoryName) && !PathEquals(m.DirectoryName, SolutionRootPath)); @@ -149,7 +178,7 @@ private string[] GetPathParts(string path, bool isUnderSolutionRoot) return path.Split(DirectorySeparators, StringSplitOptions.RemoveEmptyEntries); } - private static void ResolvePartialDisplayPaths(Dictionary> models, bool isUnderSolutionRoot) + private void ResolvePartialDisplayPaths(Dictionary> models, bool isUnderSolutionRoot) { // When path is outside solution root, always include the root segment (i.e. drive letter). // A separator has to be appended to keep it after Path.Combine. @@ -160,7 +189,7 @@ private static void ResolvePartialDisplayPaths(Dictionary> models) + private void ResolvePartialDisplayPaths(Dictionary> models) { // Include one more segment from the end. foreach (var segments in models.Values) @@ -196,7 +225,7 @@ private static void ResolvePartialDisplayPaths(Dictionary