Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #4601 from comintern/refs
💯
- Loading branch information
Showing
88 changed files
with
6,899 additions
and
2,314 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,194 @@ | ||
using System; | ||
using System.ComponentModel; | ||
using System.IO; | ||
using System.Runtime.CompilerServices; | ||
using System.Runtime.InteropServices; | ||
using System.Runtime.InteropServices.ComTypes; | ||
using Rubberduck.Parsing.ComReflection; | ||
using Rubberduck.VBEditor; | ||
using Rubberduck.VBEditor.SafeComWrappers; | ||
using Rubberduck.VBEditor.SafeComWrappers.Abstract; | ||
using Rubberduck.VBEditor.Utility; | ||
|
||
namespace Rubberduck.AddRemoveReferences | ||
{ | ||
public class ReferenceModel : INotifyPropertyChanged | ||
{ | ||
public event PropertyChangedEventHandler PropertyChanged; | ||
|
||
private ReferenceModel() | ||
{ | ||
_info = new Lazy<ReferenceInfo>(GenerateInfo); | ||
} | ||
|
||
public ReferenceModel(ReferenceInfo info, ReferenceKind type, bool recent = false, bool pinned = false) : this() | ||
{ | ||
Guid = info.Guid; | ||
Name = info.Name; | ||
Description = Name; | ||
FullPath = info.FullPath; | ||
Major = info.Major; | ||
Minor = info.Minor; | ||
IsRecent = recent; | ||
IsPinned = pinned; | ||
Type = type; | ||
} | ||
|
||
public ReferenceModel(IVBProject project, int priority) : this() | ||
{ | ||
Name = project.Name ?? string.Empty; | ||
Priority = priority; | ||
Guid = Guid.Empty; | ||
Description = project.Description ?? project.Name; | ||
FullPath = project.FileName ?? string.Empty; | ||
IsBuiltIn = false; | ||
Type = ReferenceKind.Project; | ||
} | ||
|
||
public ReferenceModel(RegisteredLibraryInfo info) : this() | ||
{ | ||
Name = info.Name ?? string.Empty; | ||
Guid = info.Guid; | ||
Description = string.IsNullOrEmpty(info.Description) ? Path.GetFileNameWithoutExtension(info.FullPath) : info.Description; | ||
Major = info.Major; | ||
Minor = info.Minor; | ||
FullPath = info.FullPath; | ||
LocaleName = info.LocaleName; | ||
IsBuiltIn = false; | ||
Type = ReferenceKind.TypeLibrary; | ||
Flags = (TypeLibTypeFlags)info.Flags; | ||
IsRegistered = true; | ||
} | ||
|
||
public ReferenceModel(RegisteredLibraryInfo info, IReference reference, int priority) : this(info) | ||
{ | ||
Priority = priority; | ||
IsBuiltIn = reference.IsBuiltIn; | ||
IsBroken = reference.IsBroken; | ||
IsReferenced = true; | ||
} | ||
|
||
public ReferenceModel(IReference reference, int priority) : this() | ||
{ | ||
Priority = priority; | ||
Name = reference.Name; | ||
Guid = Guid.TryParse(reference.Guid, out var guid) ? guid : Guid.Empty; | ||
Description = string.IsNullOrEmpty(reference.Description) ? Path.GetFileNameWithoutExtension(reference.FullPath) : reference.Description; | ||
Major = reference.Major; | ||
Minor = reference.Minor; | ||
FullPath = reference.FullPath; | ||
IsBuiltIn = reference.IsBuiltIn; | ||
IsBroken = reference.IsBroken; | ||
IsReferenced = true; | ||
Type = reference.Type; | ||
} | ||
|
||
public ReferenceModel(string path, ITypeLib reference, IComLibraryProvider provider) : this() | ||
{ | ||
FullPath = path; | ||
|
||
var documentation = provider.GetComDocumentation(reference); | ||
Name = documentation.Name; | ||
Description = documentation.DocString; | ||
|
||
var info = provider.GetReferenceInfo(reference, Name, path); | ||
Guid = info.Guid; | ||
Major = info.Major; | ||
Minor = info.Minor; | ||
} | ||
|
||
public ReferenceModel(string path, bool broken = false) : this() | ||
{ | ||
FullPath = path; | ||
try | ||
{ | ||
Name = Path.GetFileName(path) ?? path; | ||
Description = Name; | ||
} | ||
catch | ||
{ | ||
// Yeah, that's probably busted. | ||
IsBroken = true; | ||
return; | ||
} | ||
|
||
IsBroken = broken; | ||
} | ||
|
||
private bool _pinned; | ||
public bool IsPinned | ||
{ | ||
get => _pinned; | ||
set | ||
{ | ||
_pinned = value; | ||
NotifyPropertyChanged(); | ||
} | ||
} | ||
|
||
public bool IsRecent { get; set; } | ||
public bool IsRegistered { get; set; } | ||
public bool IsReferenced { get; set; } | ||
|
||
public int? Priority { get; set; } | ||
|
||
public string Name { get; } = string.Empty; | ||
public Guid Guid { get; } | ||
public string Description { get; } = string.Empty; | ||
public string FullPath { get; } | ||
public string LocaleName { get; } = string.Empty; | ||
|
||
public bool IsBuiltIn { get; set; } | ||
public bool IsBroken { get; } | ||
public TypeLibTypeFlags Flags { get; set; } | ||
public ReferenceKind Type { get; } | ||
|
||
private string FullPath32 { get; } = string.Empty; | ||
private string FullPath64 { get; } = string.Empty; | ||
public int Major { get; set; } | ||
public int Minor { get; set; } | ||
public string Version => $"{Major}.{Minor}"; | ||
|
||
public ReferenceStatus Status | ||
{ | ||
get | ||
{ | ||
var status = IsPinned ? ReferenceStatus.Pinned : ReferenceStatus.None; | ||
if (!Priority.HasValue) | ||
{ | ||
return IsRecent ? status | ReferenceStatus.Recent : status; | ||
} | ||
|
||
if (IsBroken) | ||
{ | ||
return status | ReferenceStatus.Broken; | ||
} | ||
|
||
if (IsBuiltIn) | ||
{ | ||
return status | ReferenceStatus.BuiltIn; | ||
} | ||
|
||
return status | (IsReferenced ? ReferenceStatus.Loaded : ReferenceStatus.Added); | ||
} | ||
} | ||
|
||
private readonly Lazy<ReferenceInfo> _info; | ||
private ReferenceInfo GenerateInfo() => new ReferenceInfo(Guid, Name, FullPath, Major, Minor); | ||
public ReferenceInfo ToReferenceInfo() => _info.Value; | ||
|
||
public bool Matches(ReferenceInfo info) | ||
{ | ||
return Major == info.Major && Minor == info.Minor && | ||
FullPath.Equals(info.FullPath, StringComparison.OrdinalIgnoreCase) || | ||
FullPath32.Equals(info.FullPath, StringComparison.OrdinalIgnoreCase) || | ||
FullPath64.Equals(info.FullPath, StringComparison.OrdinalIgnoreCase) || | ||
Guid.Equals(info.Guid); | ||
} | ||
|
||
private void NotifyPropertyChanged([CallerMemberName] string propertyName = "") | ||
{ | ||
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); | ||
} | ||
} | ||
} |
179 changes: 179 additions & 0 deletions
179
Rubberduck.Core/AddRemoveReferences/ReferenceReconciler.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,179 @@ | ||
using System.Collections.Generic; | ||
using System.IO; | ||
using System.Linq; | ||
using System.Runtime.InteropServices; | ||
using Rubberduck.Interaction; | ||
using Rubberduck.Parsing.ComReflection; | ||
using Rubberduck.Resources; | ||
using Rubberduck.Settings; | ||
using Rubberduck.SettingsProvider; | ||
using Rubberduck.UI.AddRemoveReferences; | ||
using Rubberduck.VBEditor.SafeComWrappers.Abstract; | ||
|
||
namespace Rubberduck.AddRemoveReferences | ||
{ | ||
public interface IReferenceReconciler | ||
{ | ||
List<ReferenceModel> ReconcileReferences(IAddRemoveReferencesModel model); | ||
List<ReferenceModel> ReconcileReferences(IAddRemoveReferencesModel model, List<ReferenceModel> allReferences); | ||
ReferenceModel TryAddReference(IVBProject project, string path); | ||
ReferenceModel TryAddReference(IVBProject project, ReferenceModel reference); | ||
ReferenceModel GetLibraryInfoFromPath(string path); | ||
void UpdateSettings(IAddRemoveReferencesModel model, bool recent = false); | ||
} | ||
|
||
public class ReferenceReconciler : IReferenceReconciler | ||
{ | ||
public static readonly List<string> TypeLibraryExtensions = new List<string> { ".olb", ".tlb", ".dll", ".ocx", ".exe" }; | ||
|
||
private readonly IMessageBox _messageBox; | ||
private readonly IConfigProvider<ReferenceSettings> _settings; | ||
private readonly IComLibraryProvider _libraryProvider; | ||
|
||
public ReferenceReconciler( | ||
IMessageBox messageBox, | ||
IConfigProvider<ReferenceSettings> settings, | ||
IComLibraryProvider libraryProvider) | ||
{ | ||
_messageBox = messageBox; | ||
_settings = settings; | ||
_libraryProvider = libraryProvider; | ||
} | ||
|
||
public List<ReferenceModel> ReconcileReferences(IAddRemoveReferencesModel model) | ||
{ | ||
if (model?.NewReferences is null || !model.NewReferences.Any()) | ||
{ | ||
return new List<ReferenceModel>(); | ||
} | ||
|
||
return ReconcileReferences(model, model.NewReferences.ToList()); | ||
} | ||
|
||
//TODO test for simple adds. | ||
public List<ReferenceModel> ReconcileReferences(IAddRemoveReferencesModel model, List<ReferenceModel> allReferences) | ||
{ | ||
if (model is null || allReferences is null || !allReferences.Any()) | ||
{ | ||
return new List<ReferenceModel>(); | ||
} | ||
|
||
var selected = allReferences.Where(reference => !reference.IsBuiltIn && reference.Priority.HasValue) | ||
.ToDictionary(reference => reference.FullPath); | ||
|
||
var output = selected.Values.Where(reference => reference.IsBuiltIn).ToList(); | ||
|
||
var project = model.Project.Project; | ||
using (var references = project.References) | ||
{ | ||
foreach (var reference in references) | ||
{ | ||
try | ||
{ | ||
if (!reference.IsBuiltIn) | ||
{ | ||
references.Remove(reference); | ||
} | ||
} | ||
finally | ||
{ | ||
reference.Dispose(); | ||
} | ||
} | ||
output.AddRange(selected.Values.OrderBy(selection => selection.Priority) | ||
.Select(reference => TryAddReference(project, reference)).Where(added => added != null)); | ||
} | ||
|
||
UpdateSettings(model, true); | ||
return output; | ||
} | ||
|
||
public ReferenceModel GetLibraryInfoFromPath(string path) | ||
{ | ||
try | ||
{ | ||
var extension = Path.GetExtension(path)?.ToLowerInvariant() ?? string.Empty; | ||
if (string.IsNullOrEmpty(extension)) | ||
{ | ||
return null; | ||
} | ||
|
||
// LoadTypeLibrary will attempt to open files in the host, so only attempt on possible COM servers. | ||
if (TypeLibraryExtensions.Contains(extension)) | ||
{ | ||
var type = _libraryProvider.LoadTypeLibrary(path); | ||
return new ReferenceModel(path, type, _libraryProvider); | ||
} | ||
return new ReferenceModel(path); | ||
} | ||
catch | ||
{ | ||
// Most likely this is unloadable. If not, it we can't fail here because it could have come from the Apply | ||
// button in the AddRemoveReferencesDialog. Wait for it... :-P | ||
return new ReferenceModel(path, true); | ||
} | ||
} | ||
|
||
public ReferenceModel TryAddReference(IVBProject project, string path) | ||
{ | ||
using (var references = project.References) | ||
{ | ||
try | ||
{ | ||
using (var reference = references.AddFromFile(path)) | ||
{ | ||
return reference is null ? null : new ReferenceModel(reference, references.Count) { IsRecent = true }; | ||
} | ||
} | ||
catch (COMException ex) | ||
{ | ||
_messageBox.NotifyWarn(ex.Message, RubberduckUI.References_AddFailedCaption); | ||
} | ||
return null; | ||
} | ||
} | ||
|
||
public ReferenceModel TryAddReference(IVBProject project, ReferenceModel reference) | ||
{ | ||
using (var references = project.References) | ||
{ | ||
try | ||
{ | ||
using (references.AddFromFile(reference.FullPath)) | ||
{ | ||
reference.Priority = references.Count; | ||
reference.IsRecent = true; | ||
return reference; | ||
} | ||
} | ||
catch (COMException ex) | ||
{ | ||
_messageBox.NotifyWarn(ex.Message, RubberduckUI.References_AddFailedCaption); | ||
} | ||
return null; | ||
} | ||
} | ||
|
||
public void UpdateSettings(IAddRemoveReferencesModel model, bool recent = false) | ||
{ | ||
if (model?.Settings is null || model.References is null) | ||
{ | ||
return; | ||
} | ||
|
||
if (recent) | ||
{ | ||
model.Settings.UpdateRecentReferencesForHost(model.HostApplication, | ||
model.References.Where(reference => reference.IsReferenced && !reference.IsBuiltIn) | ||
.Select(reference => reference.ToReferenceInfo()).ToList()); | ||
|
||
} | ||
|
||
model.Settings.UpdatePinnedReferencesForHost(model.HostApplication, | ||
model.References.Where(reference => reference.IsPinned).Select(reference => reference.ToReferenceInfo()) | ||
.ToList()); | ||
|
||
_settings.Save(model.Settings); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
using System; | ||
|
||
namespace Rubberduck.AddRemoveReferences | ||
{ | ||
[Flags] | ||
public enum ReferenceStatus | ||
{ | ||
None = 0, | ||
BuiltIn = 1 << 1, | ||
Loaded = 1 << 2, | ||
Broken = 1 << 3, | ||
Pinned = 1 << 4, | ||
Recent = 1 << 5, | ||
Added = 1 << 6 | ||
} | ||
} |
Oops, something went wrong.