Skip to content

Commit

Permalink
Fixed handling of multiple tabs with same full path.
Browse files Browse the repository at this point in the history
For example, counstructor/code views of a Windows Form.
  • Loading branch information
DarkDaskin committed Jun 28, 2019
1 parent 70fed0f commit e44c57f
Show file tree
Hide file tree
Showing 2 changed files with 85 additions and 13 deletions.
43 changes: 43 additions & 0 deletions VSTabPath.Tests/DisplayPathResolverTests_NoSolution.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
{
Expand Down
55 changes: 42 additions & 13 deletions VSTabPath/Models/DisplayPathResolver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,13 @@ public class DisplayPathResolver : IEnumerable<TabModel>

private static readonly char[] DirectorySeparators =
{Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar};
private static readonly IEqualityComparer<string> PathComparer = StringComparer.OrdinalIgnoreCase;

private readonly List<TabModel> _models = new List<TabModel>();
private Dictionary<string, List<TabModel>> _modelsByFullPath = new Dictionary<string, List<TabModel>>(PathComparer);
private string _solutionRootPath;

private IEnumerable<TabModel> ModelsWithUniqueFullPaths => _modelsByFullPath.Values.Select(list => list[0]);

public string SolutionRootPath
{
get => _solutionRootPath;
Expand All @@ -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<TabModel>());
models.Add(model);

model.PropertyChanged += OnModelPropertyChanged;

Expand All @@ -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<TabModel> GetEnumerator()
{
return _models.GetEnumerator();
return _modelsByFullPath.Values.SelectMany(m => m).GetEnumerator();
}

IEnumerator IEnumerable.GetEnumerator()
Expand All @@ -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)
Expand All @@ -94,15 +117,21 @@ 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<TabModel> models)
{
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));
Expand Down Expand Up @@ -149,7 +178,7 @@ private string[] GetPathParts(string path, bool isUnderSolutionRoot)
return path.Split(DirectorySeparators, StringSplitOptions.RemoveEmptyEntries);
}

private static void ResolvePartialDisplayPaths(Dictionary<TabModel, List<(string value, bool isIncluded)>> models, bool isUnderSolutionRoot)
private void ResolvePartialDisplayPaths(Dictionary<TabModel, List<(string value, bool isIncluded)>> 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.
Expand All @@ -160,7 +189,7 @@ private static void ResolvePartialDisplayPaths(Dictionary<TabModel, List<(string
ResolvePartialDisplayPaths(models);
}

private static void ResolvePartialDisplayPaths(Dictionary<TabModel, List<(string value, bool isIncluded)>> models)
private void ResolvePartialDisplayPaths(Dictionary<TabModel, List<(string value, bool isIncluded)>> models)
{
// Include one more segment from the end.
foreach (var segments in models.Values)
Expand Down Expand Up @@ -196,7 +225,7 @@ private static void ResolvePartialDisplayPaths(Dictionary<TabModel, List<(string
}
}

model.Key.DisplayPath = Path.Combine(finalSegments.ToArray());
UpdateDisplayPathsByFullPath(model.Key, Path.Combine(finalSegments.ToArray()));
}

// Find ambiguous paths and resolve them.
Expand Down

0 comments on commit e44c57f

Please sign in to comment.